diff --git a/.hgignore b/.hgignore index 4a0311dcc..1fbad60fd 100644 --- a/.hgignore +++ b/.hgignore @@ -144,6 +144,8 @@ external external_stlport 3rdParty .svn +thumbs.db +Thumbs.db # build code/nel/build/* @@ -206,3 +208,5 @@ code/ryzom/server/src/ryzom_admin_service/ryzom_admin_service code/ryzom/server/src/ryzom_naming_service/ryzom_naming_service code/ryzom/server/src/ryzom_welcome_service/ryzom_welcome_service code/ryzom/server/src/tick_service/tick_service +# WebTT temp dir +code/ryzom/tools/server/www/webtt/app/tmp diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 4d429e693..b73545d14 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -91,6 +91,7 @@ NL_CONFIGURE_CHECKS() #Platform specifics SETUP_EXTERNAL() +NL_GEN_REVISION_H() IF(WIN32) SET(WINSOCK2_LIB ws2_32.lib) @@ -256,7 +257,7 @@ IF(WIN32) IF(WITH_TOOLS) SET(CMAKE_INSTALL_MFC_LIBRARIES TRUE) ENDIF(WITH_TOOLS) - INCLUDE(InstallRequiredSystemLibraries) + #INCLUDE(InstallRequiredSystemLibraries) ENDIF(WIN32) INCLUDE(CPack) diff --git a/code/CMakeModules/FindDirectXSDK.cmake b/code/CMakeModules/FindDirectXSDK.cmake index 1f832cf95..9947778db 100644 --- a/code/CMakeModules/FindDirectXSDK.cmake +++ b/code/CMakeModules/FindDirectXSDK.cmake @@ -14,17 +14,22 @@ FIND_PATH(DXSDK_DIR "Include/dxsdkver.h" PATHS "$ENV{DXSDK_DIR}" + "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)" + "C:/Program Files/Microsoft DirectX SDK (June 2010)" + "C:/Program Files (x86)/Microsoft DirectX SDK (February 2010)" + "C:/Program Files/Microsoft DirectX SDK (February 2010)" + "C:/Program Files (x86)/Microsoft DirectX SDK (November 2007)" + "C:/Program Files/Microsoft DirectX SDK (November 2007)" + "C:/Program Files (x86)/Microsoft DirectX SDK" + "C:/Program Files/Microsoft DirectX SDK" ) MACRO(FIND_DXSDK_LIBRARY MYLIBRARY MYLIBRARYNAME) FIND_LIBRARY(${MYLIBRARY} NAMES ${MYLIBRARYNAME} PATHS - "${DXSDK_LIBRARY_DIR}" - "$ENV{DXSDK_DIR}" - "$ENV{DXSDK_DIR}/Lib" - "$ENV{DXSDK_DIR}/Lib/x86" - ) + "${DXSDK_LIBRARY_DIR}" + ) ENDMACRO(FIND_DXSDK_LIBRARY MYLIBRARY MYLIBRARYNAME) IF(DXSDK_DIR) diff --git a/code/CMakeModules/FindFreeType.cmake b/code/CMakeModules/FindFreeType.cmake index b9d00d96a..4f3c84cbe 100644 --- a/code/CMakeModules/FindFreeType.cmake +++ b/code/CMakeModules/FindFreeType.cmake @@ -57,6 +57,13 @@ FIND_LIBRARY(FREETYPE_LIBRARY IF(FREETYPE_LIBRARY AND FREETYPE_INCLUDE_DIRS) SET(FREETYPE_FOUND "YES") + IF(WITH_STATIC_EXTERNAL AND APPLE) + FIND_PACKAGE(BZip2) + IF(BZIP2_FOUND) + SET(FREETYPE_INCLUDE_DIRS ${FREETYPE_INCLUDE_DIRS} ${BZIP2_INCLUDE_DIR}) + SET(FREETYPE_LIBRARY ${FREETYPE_LIBRARY} ${BZIP2_LIBRARIES}) + ENDIF(BZIP2_FOUND) + ENDIF(WITH_STATIC_EXTERNAL AND APPLE) IF(NOT FREETYPE_FIND_QUIETLY) MESSAGE(STATUS "Found FreeType: ${FREETYPE_LIBRARY}") ENDIF(NOT FREETYPE_FIND_QUIETLY) diff --git a/code/CMakeModules/FindGTK2.cmake b/code/CMakeModules/FindGTK2.cmake index 7aa65e100..e3e91ed21 100644 --- a/code/CMakeModules/FindGTK2.cmake +++ b/code/CMakeModules/FindGTK2.cmake @@ -66,6 +66,7 @@ else (GTK2_LIBRARIES AND GTK2_INCLUDE_DIRS) /usr/lib64/glib-2.0/include /usr/lib/glib-2.0/include /sw/lib/glib-2.0/include + /usr/lib/x86_64-linux-gnu/glib-2.0/include ) gtk2_debug_message("GTK2_GLIBCONFIG_INCLUDE_DIR is ${GTK2_GLIBCONFIG_INCLUDE_DIR}") @@ -95,9 +96,25 @@ else (GTK2_LIBRARIES AND GTK2_INCLUDE_DIRS) /usr/lib/gtk-2.0/include /usr/lib64/gtk-2.0/include /sw/lib/gtk-2.0/include + /usr/lib/x86_64-linux-gnu/gtk-2.0/include ) gtk2_debug_message("GTK2_GDK_INCLUDE_DIR is ${GTK2_GDK_INCLUDE_DIR}") + find_path(GTK2_GDK_PIXBUF_INCLUDE_DIR + NAMES + gdk-pixbuf/gdk-pixbuf.h + PATHS + ${_GDK2IncDir} + /opt/gnome/lib/gtk-2.0/include + /opt/gnome/lib64/gtk-2.0/include + /opt/lib/gtk-2.0/include + /usr/lib/gtk-2.0/include + /usr/lib64/gtk-2.0/include + /sw/lib/gtk-2.0/include + /usr/include/gdk-pixbuf-2.0 + ) + gtk2_debug_message("GTK2_GDK_PIXBUF_INCLUDE_DIR is ${GTK2_GDK_PIXBUF_INCLUDE_DIR}") + find_path(GTK2_GTKGL_INCLUDE_DIR NAMES gtkgl/gtkglarea.h @@ -357,6 +374,7 @@ else (GTK2_LIBRARIES AND GTK2_INCLUDE_DIRS) ${GTK2_GLIBCONFIG_INCLUDE_DIR} ${GTK2_GLIB_INCLUDE_DIR} ${GTK2_GDK_INCLUDE_DIR} + ${GTK2_GDK_PIXBUF_INCLUDE_DIR} ${GTK2_GLADE_INCLUDE_DIR} ${GTK2_PANGO_INCLUDE_DIR} ${GTK2_CAIRO_INCLUDE_DIR} @@ -364,7 +382,7 @@ else (GTK2_LIBRARIES AND GTK2_INCLUDE_DIRS) ) if (GTK2_GTK_LIBRARY AND GTK2_GTK_INCLUDE_DIR) - if (GTK2_GDK_LIBRARY AND GTK2_GDK_PIXBUF_LIBRARY AND GTK2_GDK_INCLUDE_DIR) + if (GTK2_GDK_LIBRARY AND GTK2_GDK_PIXBUF_LIBRARY AND GTK2_GDK_INCLUDE_DIR AND GTK2_GDK_PIXBUF_INCLUDE_DIR) if (GTK2_GMODULE_LIBRARY) if (GTK2_GTHREAD_LIBRARY) if (GTK2_GOBJECT_LIBRARY) @@ -423,9 +441,9 @@ else (GTK2_LIBRARIES AND GTK2_INCLUDE_DIRS) else (GTK2_GMODULE_LIBRARY) message(SEND_ERROR "Could not find GMODULE") endif (GTK2_GMODULE_LIBRARY) - else (GTK2_GDK_LIBRARY AND GTK2_GDK_PIXBUF_LIBRARY AND GTK2_GDK_INCLUDE_DIR) + else (GTK2_GDK_LIBRARY AND GTK2_GDK_PIXBUF_LIBRARY AND GTK2_GDK_INCLUDE_DIR AND GTK2_GDK_PIXBUF_INCLUDE_DIR) message(SEND_ERROR "Could not find GDK (GDK_PIXBUF)") - endif (GTK2_GDK_LIBRARY AND GTK2_GDK_PIXBUF_LIBRARY AND GTK2_GDK_INCLUDE_DIR) + endif (GTK2_GDK_LIBRARY AND GTK2_GDK_PIXBUF_LIBRARY AND GTK2_GDK_INCLUDE_DIR AND GTK2_GDK_PIXBUF_INCLUDE_DIR) else (GTK2_GTK_LIBRARY AND GTK2_GTK_INCLUDE_DIR) message(SEND_ERROR "Could not find GTK2-X11") endif (GTK2_GTK_LIBRARY AND GTK2_GTK_INCLUDE_DIR) diff --git a/code/CMakeModules/FindMercurial.cmake b/code/CMakeModules/FindMercurial.cmake new file mode 100644 index 000000000..9c252ad17 --- /dev/null +++ b/code/CMakeModules/FindMercurial.cmake @@ -0,0 +1,108 @@ +# - Extract information from a subversion working copy +# The module defines the following variables: +# Mercurial_HG_EXECUTABLE - path to hg command line client +# Mercurial_VERSION_HG - version of hg command line client +# Mercurial_FOUND - true if the command line client was found +# MERCURIAL_FOUND - same as Mercurial_FOUND, set for compatiblity reasons +# +# The minimum required version of Mercurial can be specified using the +# standard syntax, e.g. FIND_PACKAGE(Mercurial 1.4) +# +# If the command line client executable is found two macros are defined: +# Mercurial_WC_INFO( ) +# Mercurial_WC_LOG( ) +# Mercurial_WC_INFO extracts information of a subversion working copy at +# a given location. This macro defines the following variables: +# _WC_URL - url of the repository (at ) +# _WC_ROOT - root url of the repository +# _WC_REVISION - current revision +# _WC_LAST_CHANGED_AUTHOR - author of last commit +# _WC_LAST_CHANGED_DATE - date of last commit +# _WC_LAST_CHANGED_REV - revision of last commit +# _WC_INFO - output of command `hg info ' +# Mercurial_WC_LOG retrieves the log message of the base revision of a +# subversion working copy at a given location. This macro defines the +# variable: +# _LAST_CHANGED_LOG - last log of base revision +# Example usage: +# FIND_PACKAGE(Mercurial) +# IF(MERCURIAL_FOUND) +# Mercurial_WC_INFO(${PROJECT_SOURCE_DIR} Project) +# MESSAGE("Current revision is ${Project_WC_REVISION}") +# Mercurial_WC_LOG(${PROJECT_SOURCE_DIR} Project) +# MESSAGE("Last changed log is ${Project_LAST_CHANGED_LOG}") +# ENDIF(MERCURIAL_FOUND) + +#============================================================================= +# Copyright 2006-2009 Kitware, Inc. +# Copyright 2006 Tristan Carel +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +FIND_PROGRAM(Mercurial_HG_EXECUTABLE hg + DOC "mercurial command line client") +MARK_AS_ADVANCED(Mercurial_HG_EXECUTABLE) + +IF(Mercurial_HG_EXECUTABLE) + EXECUTE_PROCESS(COMMAND ${Mercurial_HG_EXECUTABLE} --version + OUTPUT_VARIABLE Mercurial_VERSION_HG + OUTPUT_STRIP_TRAILING_WHITESPACE) + + STRING(REGEX REPLACE ".*version ([\\.0-9]+).*" + "\\1" Mercurial_VERSION_HG "${Mercurial_VERSION_HG}") + + MACRO(Mercurial_WC_INFO dir prefix) + EXECUTE_PROCESS(COMMAND ${Mercurial_HG_EXECUTABLE} tip + WORKING_DIRECTORY ${dir} + OUTPUT_VARIABLE ${prefix}_WC_INFO + ERROR_VARIABLE Mercurial_hg_info_error + RESULT_VARIABLE Mercurial_hg_info_result + OUTPUT_STRIP_TRAILING_WHITESPACE) + + IF(NOT ${Mercurial_hg_info_result} EQUAL 0) + MESSAGE(SEND_ERROR "Command \"${Mercurial_HG_EXECUTABLE} tip\" failed with output:\n${Mercurial_hg_info_error}") + ELSE(NOT ${Mercurial_hg_info_result} EQUAL 0) + + STRING(REGEX REPLACE "^(.*\n)?Repository Root: ([^\n]+).*" + "\\2" ${prefix}_WC_ROOT "${${prefix}_WC_INFO}") + STRING(REGEX REPLACE "^(.*\n)?changeset: *([0-9]+).*" + "\\2" ${prefix}_WC_REVISION "${${prefix}_WC_INFO}") + STRING(REGEX REPLACE "^(.*\n)?Last Changed Author: ([^\n]+).*" + "\\2" ${prefix}_WC_LAST_CHANGED_AUTHOR "${${prefix}_WC_INFO}") + STRING(REGEX REPLACE "^(.*\n)?Last Changed Rev: ([^\n]+).*" + "\\2" ${prefix}_WC_LAST_CHANGED_REV "${${prefix}_WC_INFO}") + STRING(REGEX REPLACE "^(.*\n)?Last Changed Date: ([^\n]+).*" + "\\2" ${prefix}_WC_LAST_CHANGED_DATE "${${prefix}_WC_INFO}") + + ENDIF(NOT ${Mercurial_hg_info_result} EQUAL 0) + + ENDMACRO(Mercurial_WC_INFO) + + MACRO(Mercurial_WC_LOG dir prefix) + # This macro can block if the certificate is not signed: + # hg ask you to accept the certificate and wait for your answer + # This macro requires a hg server network access (Internet most of the time) + # and can also be slow since it access the hg server + EXECUTE_PROCESS(COMMAND + ${Mercurial_HG_EXECUTABLE} --non-interactive log -r BASE ${dir} + OUTPUT_VARIABLE ${prefix}_LAST_CHANGED_LOG + ERROR_VARIABLE Mercurial_hg_log_error + RESULT_VARIABLE Mercurial_hg_log_result + OUTPUT_STRIP_TRAILING_WHITESPACE) + + IF(NOT ${Mercurial_hg_log_result} EQUAL 0) + MESSAGE(SEND_ERROR "Command \"${Mercurial_HG_EXECUTABLE} log -r BASE ${dir}\" failed with output:\n${Mercurial_hg_log_error}") + ENDIF(NOT ${Mercurial_hg_log_result} EQUAL 0) + ENDMACRO(Mercurial_WC_LOG) +ENDIF(Mercurial_HG_EXECUTABLE) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Mercurial DEFAULT_MSG Mercurial_HG_EXECUTABLE) diff --git a/code/CMakeModules/FindMySQL.cmake b/code/CMakeModules/FindMySQL.cmake index b9970f63c..a00e36992 100644 --- a/code/CMakeModules/FindMySQL.cmake +++ b/code/CMakeModules/FindMySQL.cmake @@ -54,10 +54,14 @@ ELSE(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) IF(MYSQL_INCLUDE_DIR) IF(MYSQL_LIBRARY_RELEASE) - SET(MYSQL_LIBRARIES "optimized;${MYSQL_LIBRARY_RELEASE}") + SET(MYSQL_LIBRARIES optimized ${MYSQL_LIBRARY_RELEASE}) IF(MYSQL_LIBRARY_DEBUG) - SET(MYSQL_LIBRARIES "${MYSQL_LIBRARIES};debug;${MYSQL_LIBRARY_DEBUG}") + SET(MYSQL_LIBRARIES ${MYSQL_LIBRARIES} debug ${MYSQL_LIBRARY_DEBUG}) ENDIF(MYSQL_LIBRARY_DEBUG) + FIND_PACKAGE(OpenSSL) + IF(OPENSSL_FOUND) + SET(MYSQL_LIBRARIES ${MYSQL_LIBRARIES} ${OPENSSL_LIBRARIES}) + ENDIF(OPENSSL_FOUND) ENDIF(MYSQL_LIBRARY_RELEASE) ENDIF(MYSQL_INCLUDE_DIR) diff --git a/code/CMakeModules/GetRevision.cmake b/code/CMakeModules/GetRevision.cmake new file mode 100644 index 000000000..d38215aba --- /dev/null +++ b/code/CMakeModules/GetRevision.cmake @@ -0,0 +1,60 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6.3) + +# ROOT_DIR should be set to root of the repository (where to find the .svn or .hg directory) +# SOURCE_DIR should be set to root of your code (where to find CMakeLists.txt) + +# Replace spaces by semi-columns +IF(CMAKE_MODULE_PATH) + STRING(REPLACE " " ";" CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) +ENDIF(CMAKE_MODULE_PATH) + +SET(CMAKE_MODULE_PATH ${SOURCE_DIR}/CMakeModules ${CMAKE_MODULE_PATH}) + +IF(NOT ROOT_DIR AND SOURCE_DIR) + SET(ROOT_DIR ${SOURCE_DIR}) +ENDIF(NOT ROOT_DIR AND SOURCE_DIR) + +IF(NOT SOURCE_DIR AND ROOT_DIR) + SET(SOURCE_DIR ${ROOT_DIR}) +ENDIF(NOT SOURCE_DIR AND ROOT_DIR) + +MACRO(NOW RESULT) + IF (WIN32) + EXECUTE_PROCESS(COMMAND "wmic" "os" "get" "localdatetime" OUTPUT_VARIABLE DATETIME) + IF(NOT DATETIME MATCHES "ERROR") + STRING(REGEX REPLACE ".*\n([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9]).*" "\\1-\\2-\\3 \\4:\\5:\\6" ${RESULT} "${DATETIME}") + ENDIF(NOT DATETIME MATCHES "ERROR") + ELSEIF(UNIX) + EXECUTE_PROCESS(COMMAND "date" "+%Y-%m-%d %H:%M:%S" OUTPUT_VARIABLE DATETIME) + STRING(REGEX REPLACE "([0-9: -]+).*" "\\1" ${RESULT} "${DATETIME}") + ELSE (WIN32) + MESSAGE(SEND_ERROR "date not implemented") + SET(${RESULT} "0000-00-00 00:00:00") + ENDIF (WIN32) +ENDMACRO(NOW) + +IF(EXISTS "${ROOT_DIR}/.svn/") + FIND_PACKAGE(Subversion) + + IF(SUBVERSION_FOUND) + Subversion_WC_INFO(${ROOT_DIR} ER) + SET(REVISION ${ER_WC_REVISION}) + ENDIF(SUBVERSION_FOUND) +ENDIF(EXISTS "${ROOT_DIR}/.svn/") + +IF(EXISTS "${ROOT_DIR}/.hg/") + FIND_PACKAGE(Mercurial) + + IF(MERCURIAL_FOUND) + Mercurial_WC_INFO(${ROOT_DIR} ER) + SET(REVISION ${ER_WC_REVISION}) + ENDIF(MERCURIAL_FOUND) +ENDIF(EXISTS "${ROOT_DIR}/.hg/") + +IF(REVISION) + IF(EXISTS ${SOURCE_DIR}/revision.h.in) + NOW(BUILD_DATE) + CONFIGURE_FILE(${SOURCE_DIR}/revision.h.in revision.h.txt) + EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E copy revision.h.txt revision.h) # copy_if_different + ENDIF(EXISTS ${SOURCE_DIR}/revision.h.in) +ENDIF(REVISION) diff --git a/code/CMakeModules/PCHSupport.cmake b/code/CMakeModules/PCHSupport.cmake index bb34aebfe..ae5b30ee2 100644 --- a/code/CMakeModules/PCHSupport.cmake +++ b/code/CMakeModules/PCHSupport.cmake @@ -8,44 +8,40 @@ # ADD_PRECOMPILED_HEADER_TO_TARGET _targetName _input _pch_output_to_use # ADD_NATIVE_PRECOMPILED_HEADER _targetName _inputh _inputcpp -IF(CMAKE_COMPILER_IS_GNUCXX) +IF(MSVC) + SET(PCHSupport_FOUND TRUE) + SET(_PCH_include_prefix "/I") +ELSE(MSVC) + IF(CMAKE_COMPILER_IS_GNUCXX) + EXEC_PROGRAM(${CMAKE_CXX_COMPILER} + ARGS ${CMAKE_CXX_COMPILER_ARG1} -dumpversion + OUTPUT_VARIABLE gcc_compiler_version) - EXEC_PROGRAM( - ${CMAKE_CXX_COMPILER} - ARGS ${CMAKE_CXX_COMPILER_ARG1} -dumpversion - OUTPUT_VARIABLE gcc_compiler_version) - - IF(gcc_compiler_version MATCHES "4\\.[0-9]\\.[0-9]") - SET(PCHSupport_FOUND TRUE) - ELSE(gcc_compiler_version MATCHES "4\\.[0-9]\\.[0-9]") - IF(gcc_compiler_version MATCHES "3\\.4\\.[0-9]") + IF(gcc_compiler_version MATCHES "4\\.[0-9]\\.[0-9]") SET(PCHSupport_FOUND TRUE) - ENDIF(gcc_compiler_version MATCHES "3\\.4\\.[0-9]") - ENDIF(gcc_compiler_version MATCHES "4\\.[0-9]\\.[0-9]") + ELSE(gcc_compiler_version MATCHES "4\\.[0-9]\\.[0-9]") + IF(gcc_compiler_version MATCHES "3\\.4\\.[0-9]") + SET(PCHSupport_FOUND TRUE) + ENDIF(gcc_compiler_version MATCHES "3\\.4\\.[0-9]") + ENDIF(gcc_compiler_version MATCHES "4\\.[0-9]\\.[0-9]") + ELSE(CMAKE_COMPILER_IS_GNUCXX) + # TODO: make tests for other compilers than GCC + SET(PCHSupport_FOUND TRUE) + ENDIF(CMAKE_COMPILER_IS_GNUCXX) SET(_PCH_include_prefix "-I") - -ELSE(CMAKE_COMPILER_IS_GNUCXX) - - IF(WIN32) - SET(PCHSupport_FOUND TRUE) # for experimental msvc support - SET(_PCH_include_prefix "/I") - ELSE(WIN32) - SET(PCHSupport_FOUND FALSE) - ENDIF(WIN32) - -ENDIF(CMAKE_COMPILER_IS_GNUCXX) +ENDIF(MSVC) MACRO(_PCH_GET_COMPILE_FLAGS _out_compile_flags) STRING(TOUPPER "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}" _flags_var_name) SET(${_out_compile_flags} ${${_flags_var_name}} ) - IF(CMAKE_COMPILER_IS_GNUCXX) + IF(NOT MSVC) GET_TARGET_PROPERTY(_targetType ${_PCH_current_target} TYPE) IF(${_targetType} STREQUAL SHARED_LIBRARY OR ${_targetType} STREQUAL MODULE_LIBRARY) LIST(APPEND ${_out_compile_flags} "-fPIC") ENDIF(${_targetType} STREQUAL SHARED_LIBRARY OR ${_targetType} STREQUAL MODULE_LIBRARY) - ENDIF(CMAKE_COMPILER_IS_GNUCXX) + ENDIF(NOT MSVC) GET_DIRECTORY_PROPERTY(DIRINC INCLUDE_DIRECTORIES ) FOREACH(item ${DIRINC}) @@ -100,17 +96,13 @@ MACRO(_PCH_GET_COMPILE_COMMAND out_command _input _inputcpp _output) SET(pchsupport_compiler_cxx_arg1 "") ENDIF(CMAKE_CXX_COMPILER_ARG1) - IF(CMAKE_COMPILER_IS_GNUCXX) - SET(${out_command} - ${CMAKE_CXX_COMPILER} ${pchsupport_compiler_cxx_arg1} ${_compile_FLAGS} -x c++-header -o ${_output} -c ${_input} - ) - ELSE(CMAKE_COMPILER_IS_GNUCXX) + IF(MSVC) _PCH_GET_PDB_FILENAME(PDB_FILE ${_PCH_current_target}) - SET(${out_command} - ${CMAKE_CXX_COMPILER} ${pchsupport_compiler_cxx_arg1} ${_compile_FLAGS} /Yc /Fp\"${_output}\" ${_inputcpp} /c /Fd\"${PDB_FILE}\" - ) - ENDIF(CMAKE_COMPILER_IS_GNUCXX) -ENDMACRO(_PCH_GET_COMPILE_COMMAND ) + SET(${out_command} ${CMAKE_CXX_COMPILER} ${pchsupport_compiler_cxx_arg1} ${_compile_FLAGS} /Yc /Fp\"${_output}\" ${_inputcpp} /c /Fd\"${PDB_FILE}\") + ELSE(MSVC) + SET(${out_command} ${CMAKE_CXX_COMPILER} ${pchsupport_compiler_cxx_arg1} ${_compile_FLAGS} -x c++-header -o ${_output} -c ${_input}) + ENDIF(MSVC) +ENDMACRO(_PCH_GET_COMPILE_COMMAND) MACRO(GET_PRECOMPILED_HEADER_OUTPUT _targetName _input _output) IF(MSVC) @@ -128,7 +120,9 @@ MACRO(ADD_PRECOMPILED_HEADER_TO_TARGET _targetName _input _pch_output_to_use ) SET(oldProps "") ENDIF(${oldProps} MATCHES NOTFOUND) - IF(CMAKE_COMPILER_IS_GNUCXX) + IF(MSVC) + SET(_target_cflags "${oldProps} /Yu\"${_input}\" /FI\"${_input}\" /Fp\"${_pch_output_to_use}\"") + ELSE(MSVC) # to do: test whether compiler flags match between target _targetName # and _pch_output_to_use FILE(TO_NATIVE_PATH ${_pch_output_to_use} _native_pch_path) @@ -137,11 +131,7 @@ MACRO(ADD_PRECOMPILED_HEADER_TO_TARGET _targetName _input _pch_output_to_use ) # on all remote machines set # PCH_ADDITIONAL_COMPILER_FLAGS to -fpch-preprocess SET(_target_cflags "${oldProps} ${PCH_ADDITIONAL_COMPILER_FLAGS}-include ${_input} -Winvalid-pch") - ELSE(CMAKE_COMPILER_IS_GNUCXX) - IF(MSVC) - SET(_target_cflags "${oldProps} /Yu\"${_input}\" /FI\"${_input}\" /Fp\"${_pch_output_to_use}\"") - ENDIF(MSVC) - ENDIF(CMAKE_COMPILER_IS_GNUCXX) + ENDIF(MSVC) SET_TARGET_PROPERTIES(${_targetName} PROPERTIES COMPILE_FLAGS ${_target_cflags}) IF(oldProps) @@ -184,8 +174,31 @@ MACRO(ADD_PRECOMPILED_HEADER _targetName _inputh _inputcpp) ADD_PRECOMPILED_HEADER_TO_TARGET(${_targetName} ${_inputh} ${_output}) ENDMACRO(ADD_PRECOMPILED_HEADER) +# Macro to move PCH creation file to the front of files list +MACRO(FIX_PRECOMPILED_HEADER _files _pch) + # Remove .cpp creating PCH from the list + LIST(REMOVE_ITEM ${_files} ${_pch}) + # Prepend .cpp creating PCH to the list + LIST(INSERT ${_files} 0 ${_pch}) +ENDMACRO(FIX_PRECOMPILED_HEADER) + MACRO(ADD_NATIVE_PRECOMPILED_HEADER _targetName _inputh _inputcpp) - IF(CMAKE_GENERATOR MATCHES Visual*) + SET(PCH_METHOD 0) + + # 0 => creating a new target for PCH, works for all makefiles + # 1 => setting PCH for VC++ project, works for VC++ projects + # 2 => setting PCH for XCode project, works for XCode projects + IF(CMAKE_GENERATOR MATCHES "Visual Studio") + SET(PCH_METHOD 1) + ELSEIF(CMAKE_GENERATOR MATCHES "NMake Makefiles" AND MFC_FOUND AND CMAKE_MFC_FLAG) + # To fix a bug with MFC + # Don't forget to use FIX_PRECOMPILED_HEADER before creating the target +# SET(PCH_METHOD 1) + ELSEIF(CMAKE_GENERATOR MATCHES "Xcode") + SET(PCH_METHOD 2) + ENDIF(CMAKE_GENERATOR MATCHES "Visual Studio") + + IF(PCH_METHOD EQUAL 1) # Auto include the precompile (useful for moc processing, since the use of # precompiled is specified at the target level # and I don't want to specifiy /F- for each moc/res/ui generated files (using Qt) @@ -200,26 +213,24 @@ MACRO(ADD_NATIVE_PRECOMPILED_HEADER _targetName _inputh _inputcpp) #also inlude ${oldProps} to have the same compile options SET_SOURCE_FILES_PROPERTIES(${_inputcpp} PROPERTIES COMPILE_FLAGS "${oldProps} /Yc\"${_inputh}\"") - ELSE(CMAKE_GENERATOR MATCHES Visual*) - IF(CMAKE_GENERATOR MATCHES Xcode) - # For Xcode, cmake needs my patch to process - # GCC_PREFIX_HEADER and GCC_PRECOMPILE_PREFIX_HEADER as target properties + ELSEIF(PCH_METHOD EQUAL 2) + # For Xcode, cmake needs my patch to process + # GCC_PREFIX_HEADER and GCC_PRECOMPILE_PREFIX_HEADER as target properties - GET_TARGET_PROPERTY(oldProps ${_targetName} COMPILE_FLAGS) - IF(${oldProps} MATCHES NOTFOUND) - SET(oldProps "") - ENDIF(${oldProps} MATCHES NOTFOUND) + GET_TARGET_PROPERTY(oldProps ${_targetName} COMPILE_FLAGS) + IF(${oldProps} MATCHES NOTFOUND) + SET(oldProps "") + ENDIF(${oldProps} MATCHES NOTFOUND) - # When buiding out of the tree, precompiled may not be located - # Use full path instead. - GET_FILENAME_COMPONENT(fullPath ${_inputh} ABSOLUTE) + # When buiding out of the tree, precompiled may not be located + # Use full path instead. + GET_FILENAME_COMPONENT(fullPath ${_inputh} ABSOLUTE) - SET_TARGET_PROPERTIES(${_targetName} PROPERTIES XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${fullPath}") - SET_TARGET_PROPERTIES(${_targetName} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES") - ELSE(CMAKE_GENERATOR MATCHES Xcode) - #Fallback to the "old" precompiled suppport - ADD_PRECOMPILED_HEADER(${_targetName} ${_inputh} ${_inputcpp}) - ENDIF(CMAKE_GENERATOR MATCHES Xcode) - ENDIF(CMAKE_GENERATOR MATCHES Visual*) + SET_TARGET_PROPERTIES(${_targetName} PROPERTIES XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${fullPath}") + SET_TARGET_PROPERTIES(${_targetName} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES") + ELSE(PCH_METHOD EQUAL 1) + #Fallback to the "old" precompiled suppport + ADD_PRECOMPILED_HEADER(${_targetName} ${_inputh} ${_inputcpp}) + ENDIF(PCH_METHOD EQUAL 1) ENDMACRO(ADD_NATIVE_PRECOMPILED_HEADER) diff --git a/code/CMakeModules/nel.cmake b/code/CMakeModules/nel.cmake index 5c5efc4a9..e6bf57101 100644 --- a/code/CMakeModules/nel.cmake +++ b/code/CMakeModules/nel.cmake @@ -1,3 +1,11 @@ +# Force Release configuration for compiler checks +SET(CMAKE_TRY_COMPILE_CONFIGURATION "Release") + +# Force Release configuration by default +IF(NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) +ENDIF(NOT CMAKE_BUILD_TYPE) + ### # Helper macro that generates .pc and installs it. # Argument: name - the name of the .pc package, e.g. "nel-pacs.pc" @@ -5,10 +13,41 @@ MACRO(NL_GEN_PC name) IF(NOT WIN32 AND WITH_INSTALL_LIBRARIES) CONFIGURE_FILE(${name}.in "${CMAKE_CURRENT_BINARY_DIR}/${name}") - INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/${name}" DESTINATION lib/pkgconfig) + IF(CMAKE_LIBRARY_ARCHITECTURE) + INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/${name}" DESTINATION lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig) + ELSE(CMAKE_LIBRARY_ARCHITECTURE) + INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/${name}" DESTINATION lib/pkgconfig) + ENDIF(CMAKE_LIBRARY_ARCHITECTURE) ENDIF(NOT WIN32 AND WITH_INSTALL_LIBRARIES) ENDMACRO(NL_GEN_PC) +### +# Helper macro that generates revision.h from revision.h.in +### +MACRO(NL_GEN_REVISION_H) + IF(EXISTS ${CMAKE_SOURCE_DIR}/revision.h.in) + INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}) + ADD_DEFINITIONS(-DHAVE_REVISION_H) + SET(HAVE_REVISION_H ON) + + # a custom target that is always built + ADD_CUSTOM_TARGET(revision ALL + DEPENDS ${CMAKE_BINARY_DIR}/revision.h) + + # creates revision.h using cmake script + ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_BINARY_DIR}/revision.h + COMMAND ${CMAKE_COMMAND} + -DSOURCE_DIR=${CMAKE_SOURCE_DIR} + -DROOT_DIR=${CMAKE_SOURCE_DIR}/.. + -P ${CMAKE_SOURCE_DIR}/CMakeModules/GetRevision.cmake) + + # revision.h is a generated file + SET_SOURCE_FILES_PROPERTIES(${CMAKE_BINARY_DIR}/revision.h + PROPERTIES GENERATED TRUE + HEADER_FILE_ONLY TRUE) + ENDIF(EXISTS ${CMAKE_SOURCE_DIR}/revision.h.in) +ENDMACRO(NL_GEN_REVISION_H) + ### # ### @@ -37,7 +76,10 @@ ENDMACRO(NL_TARGET_DRIVER) # Argument: ### MACRO(NL_DEFAULT_PROPS name label) - SET_TARGET_PROPERTIES(${name} PROPERTIES PROJECT_LABEL ${label}) + # Note: This is just a workaround for a CMake bug generating VS10 files with a colon in the project name. + # CMake Bug ID: http://www.cmake.org/Bug/view.php?id=11819 + STRING(REGEX REPLACE "\\:" " -" proj_label ${label}) + SET_TARGET_PROPERTIES(${name} PROPERTIES PROJECT_LABEL ${proj_label}) GET_TARGET_PROPERTY(type ${name} TYPE) IF(${type} STREQUAL SHARED_LIBRARY) # Set versions only if target is a shared library @@ -156,7 +198,7 @@ Remove the CMakeCache.txt file and try again from another folder, e.g.: rm CMakeCache.txt mkdir cmake cd cmake - cmake -G \"Unix Makefiles\" .. + cmake .. ") ENDIF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) @@ -201,6 +243,17 @@ MACRO(NL_SETUP_DEFAULT_OPTIONS) ### # Optional support ### + + # Check if CMake is launched from a Debian packaging script + SET(DEB_HOST_GNU_CPU $ENV{DEB_HOST_GNU_CPU}) + + # Don't strip if generating a .deb + IF(DEB_HOST_GNU_CPU) + OPTION(WITH_SYMBOLS "Keep debug symbols in binaries" ON ) + ELSE(DEB_HOST_GNU_CPU) + OPTION(WITH_SYMBOLS "Keep debug symbols in binaries" OFF) + ENDIF(DEB_HOST_GNU_CPU) + IF(WIN32) OPTION(WITH_STLPORT "With STLport support." ON ) ELSE(WIN32) @@ -302,27 +355,80 @@ MACRO(NL_SETUP_BUILD) ENDIF(CMAKE_BUILD_TYPE MATCHES "Release") ENDIF(CMAKE_BUILD_TYPE MATCHES "Debug") + SET(HOST_CPU ${CMAKE_SYSTEM_PROCESSOR}) + + IF(HOST_CPU MATCHES "amd64") + SET(HOST_CPU "x86_64") + ELSEIF(HOST_CPU MATCHES "i.86") + SET(HOST_CPU "x86") + ENDIF(HOST_CPU MATCHES "amd64") + # Determine target CPU -# IF(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") - IF(NOT CMAKE_SIZEOF_VOID_P) - INCLUDE (CheckTypeSize) - CHECK_TYPE_SIZE("void*" CMAKE_SIZEOF_VOID_P) - ENDIF(NOT CMAKE_SIZEOF_VOID_P) + IF(NOT TARGET_CPU) + SET(TARGET_CPU $ENV{DEB_HOST_GNU_CPU}) + ENDIF(NOT TARGET_CPU) - # Using 32 or 64 bits libraries + # If not specified, use the same CPU as host + IF(NOT TARGET_CPU) + SET(TARGET_CPU ${CMAKE_SYSTEM_PROCESSOR}) + ENDIF(NOT TARGET_CPU) + + IF(TARGET_CPU MATCHES "amd64") + SET(TARGET_CPU "x86_64") + ELSEIF(TARGET_CPU MATCHES "i.86") + SET(TARGET_CPU "x86") + ENDIF(TARGET_CPU MATCHES "amd64") + + # DEB_HOST_ARCH_ENDIAN is 'little' or 'big' + # DEB_HOST_ARCH_BITS is '32' or '64' + + # If target and host CPU are the same + IF("${HOST_CPU}" STREQUAL "${TARGET_CPU}") + # x86-compatible CPU + IF(HOST_CPU MATCHES "x86") + IF(NOT CMAKE_SIZEOF_VOID_P) + INCLUDE (CheckTypeSize) + CHECK_TYPE_SIZE("void*" CMAKE_SIZEOF_VOID_P) + ENDIF(NOT CMAKE_SIZEOF_VOID_P) + + # Using 32 or 64 bits libraries + IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(TARGET_CPU "x86_64") + ELSE(CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(TARGET_CPU "x86") + ENDIF(CMAKE_SIZEOF_VOID_P EQUAL 8) + ENDIF(HOST_CPU MATCHES "x86") + # TODO: add checks for ARM and PPC + ELSE("${HOST_CPU}" STREQUAL "${TARGET_CPU}") + MESSAGE(STATUS "Compiling on ${HOST_CPU} for ${TARGET_CPU}") + ENDIF("${HOST_CPU}" STREQUAL "${TARGET_CPU}") + + IF(TARGET_CPU STREQUAL "x86_64") + SET(TARGET_X64 1) + SET(PLATFORM_CFLAGS "${PLATFORM_CFLAGS} -DHAVE_X86_64") + ELSEIF(TARGET_CPU STREQUAL "x86") SET(TARGET_X86 1) - IF(CMAKE_SIZEOF_VOID_P EQUAL 8) - SET(ARCH "x86_64") - SET(TARGET_X64 1) - ADD_DEFINITIONS(-DHAVE_X86_64) - ELSE(CMAKE_SIZEOF_VOID_P EQUAL 8) - SET(ARCH "x86") - ADD_DEFINITIONS(-DHAVE_X86) - ENDIF(CMAKE_SIZEOF_VOID_P EQUAL 8) -# ADD_DEFINITIONS(-DHAVE_IA64) -# ENDIF(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86") + SET(PLATFORM_CFLAGS "${PLATFORM_CFLAGS} -DHAVE_X86") + ENDIF(TARGET_CPU STREQUAL "x86_64") - IF(WIN32) + # Fix library paths suffixes for Debian MultiArch + SET(DEBIAN_MULTIARCH $ENV{DEB_HOST_MULTIARCH}) + + IF(DEBIAN_MULTIARCH) + SET(CMAKE_LIBRARY_ARCHITECTURE ${DEBIAN_MULTIARCH}) + ENDIF(DEBIAN_MULTIARCH) + + IF(CMAKE_LIBRARY_ARCHITECTURE) + SET(CMAKE_LIBRARY_PATH /lib/${CMAKE_LIBRARY_ARCHITECTURE} /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} ${CMAKE_LIBRARY_PATH}) + IF(TARGET_X64) + SET(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /lib64 /usr/lib64) + ENDIF(TARGET_X64) + IF(TARGET_X86) + SET(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /lib32 /usr/lib32) + ENDIF(TARGET_X86) + ENDIF(CMAKE_LIBRARY_ARCHITECTURE) + + IF(MSVC) IF(MSVC10) # /Ox is working with VC++ 2010, but custom optimizations don't exist SET(SPEED_OPTIMIZATIONS "/Ox /GF /GS-") @@ -342,10 +448,10 @@ MACRO(NL_SETUP_BUILD) MESSAGE(FATAL_ERROR "Can't determine compiler version ${MSVC_VERSION}") ENDIF(MSVC10) - SET(PLATFORM_CFLAGS "${PLATFORM_CFLAGS} /D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_WARNINGS /DWIN32 /D_WINDOWS /W3 /Zi /Zm1000 /MP /Gy-") + SET(PLATFORM_CFLAGS "${PLATFORM_CFLAGS} /D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_WARNINGS /DWIN32 /D_WINDOWS /W3 /Zm1000 /MP /Gy-") # Common link flags - SET(PLATFORM_LINKFLAGS "-DEBUG") + SET(PLATFORM_LINKFLAGS "") IF(TARGET_X64) # Fix a bug with Intellisense @@ -360,12 +466,32 @@ MACRO(NL_SETUP_BUILD) # Exceptions are only set for C++ SET(PLATFORM_CXXFLAGS "${PLATFORM_CFLAGS} /EHa") - SET(NL_DEBUG_CFLAGS "/MDd /RTC1 /D_DEBUG ${MIN_OPTIMIZATIONS}") - SET(NL_RELEASE_CFLAGS "/MD /D NDEBUG ${SPEED_OPTIMIZATIONS}") - SET(NL_DEBUG_LINKFLAGS "/NODEFAULTLIB:msvcrt /INCREMENTAL:YES") - SET(NL_RELEASE_LINKFLAGS "/OPT:REF /OPT:ICF /INCREMENTAL:NO") - ELSE(WIN32) - SET(PLATFORM_CFLAGS "-g -pipe -ftemplate-depth-48 -D_REENTRANT -Wall -ansi -W -Wpointer-arith -Wsign-compare -Wno-deprecated-declarations -Wno-multichar -Wno-unused -fno-strict-aliasing") + IF(WITH_SYMBOLS) + SET(NL_RELEASE_CFLAGS "/Zi ${NL_RELEASE_CFLAGS}") + SET(NL_RELEASE_LINKFLAGS "/DEBUG ${NL_RELEASE_LINKFLAGS}") + ELSE(WITH_SYMBOLS) + SET(NL_RELEASE_LINKFLAGS "/RELEASE ${NL_RELEASE_LINKFLAGS}") + ENDIF(WITH_SYMBOLS) + + SET(NL_DEBUG_CFLAGS "/Zi /MDd /RTC1 /D_DEBUG ${MIN_OPTIMIZATIONS} ${NL_DEBUG_CFLAGS}") + SET(NL_RELEASE_CFLAGS "/MD /DNDEBUG ${SPEED_OPTIMIZATIONS} ${NL_RELEASE_CFLAGS}") + SET(NL_DEBUG_LINKFLAGS "/DEBUG /OPT:NOREF /OPT:NOICF /NODEFAULTLIB:msvcrt /INCREMENTAL:YES ${NL_DEBUG_LINKFLAGS}") + SET(NL_RELEASE_LINKFLAGS "/OPT:REF /OPT:ICF /INCREMENTAL:NO ${NL_RELEASE_LINKFLAGS}") + ELSE(MSVC) + IF(HOST_CPU STREQUAL "x86_64" AND TARGET_CPU STREQUAL "x86") + SET(PLATFORM_CFLAGS "${PLATFORM_CFLAGS} -m32 -march=i686") + ENDIF(HOST_CPU STREQUAL "x86_64" AND TARGET_CPU STREQUAL "x86") + + IF(HOST_CPU STREQUAL "x86" AND TARGET_CPU STREQUAL "x86_64") + SET(PLATFORM_CFLAGS "${PLATFORM_CFLAGS} -m64") + ENDIF(HOST_CPU STREQUAL "x86" AND TARGET_CPU STREQUAL "x86_64") + + SET(PLATFORM_CFLAGS "${PLATFORM_CFLAGS} -D_REENTRANT -pipe -ftemplate-depth-48 -Wall -W -Wpointer-arith -Wsign-compare -Wno-deprecated-declarations -Wno-multichar -Wno-unused -fno-strict-aliasing") + + IF(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + SET(PLATFORM_CFLAGS "${PLATFORM_CFLAGS} -ansi") + ENDIF(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + IF(WITH_COVERAGE) SET(PLATFORM_CFLAGS "-fprofile-arcs -ftest-coverage ${PLATFORM_CFLAGS}") ENDIF(WITH_COVERAGE) @@ -382,12 +508,20 @@ MACRO(NL_SETUP_BUILD) SET(PLATFORM_CXXFLAGS ${PLATFORM_CFLAGS}) IF(NOT APPLE) - SET(PLATFORM_LINKFLAGS "-Wl,--no-undefined -Wl,--as-needed") + SET(PLATFORM_LINKFLAGS "${PLATFORM_LINKFLAGS} -Wl,--no-undefined -Wl,--as-needed") ENDIF(NOT APPLE) - SET(NL_DEBUG_CFLAGS "-DNL_DEBUG -D_DEBUG") - SET(NL_RELEASE_CFLAGS "-DNL_RELEASE -DNDEBUG -O6") - ENDIF(WIN32) + IF(WITH_SYMBOLS) + SET(NL_RELEASE_CFLAGS "${NL_RELEASE_CFLAGS} -g") + ELSE(WITH_SYMBOLS) + IF(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + SET(NL_RELEASE_LINKFLAGS "-Wl,-s ${NL_RELEASE_LINKFLAGS}") + ENDIF(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + ENDIF(WITH_SYMBOLS) + + SET(NL_DEBUG_CFLAGS "-DNL_DEBUG -D_DEBUG ${NL_DEBUG_CFLAGS}") + SET(NL_RELEASE_CFLAGS "-DNL_RELEASE -DNDEBUG -O3 ${NL_RELEASE_CFLAGS}") + ENDIF(MSVC) ENDMACRO(NL_SETUP_BUILD) MACRO(NL_SETUP_BUILD_FLAGS) @@ -451,7 +585,11 @@ MACRO(NL_SETUP_PREFIX_PATHS) IF(WIN32) SET(NL_LIB_PREFIX "../lib" CACHE PATH "Installation path for libraries.") ELSE(WIN32) - SET(NL_LIB_PREFIX "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation path for libraries.") + IF(CMAKE_LIBRARY_ARCHITECTURE) + SET(NL_LIB_PREFIX "${CMAKE_INSTALL_PREFIX}/lib/${CMAKE_LIBRARY_ARCHITECTURE}" CACHE PATH "Installation path for libraries.") + ELSE(CMAKE_LIBRARY_ARCHITECTURE) + SET(NL_LIB_PREFIX "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation path for libraries.") + ENDIF(CMAKE_LIBRARY_ARCHITECTURE) ENDIF(WIN32) ENDIF(NOT NL_LIB_PREFIX) @@ -460,7 +598,11 @@ MACRO(NL_SETUP_PREFIX_PATHS) IF(WIN32) SET(NL_DRIVER_PREFIX "../lib" CACHE PATH "Installation path for drivers.") ELSE(WIN32) - SET(NL_DRIVER_PREFIX "${CMAKE_INSTALL_PREFIX}/lib/nel" CACHE PATH "Installation path for drivers.") + IF(CMAKE_LIBRARY_ARCHITECTURE) + SET(NL_DRIVER_PREFIX "${CMAKE_INSTALL_PREFIX}/lib/${CMAKE_LIBRARY_ARCHITECTURE}/nel" CACHE PATH "Installation path for drivers.") + ELSE(CMAKE_LIBRARY_ARCHITECTURE) + SET(NL_DRIVER_PREFIX "${CMAKE_INSTALL_PREFIX}/lib/nel" CACHE PATH "Installation path for drivers.") + ENDIF(CMAKE_LIBRARY_ARCHITECTURE) ENDIF(WIN32) ENDIF(NOT NL_DRIVER_PREFIX) @@ -529,50 +671,67 @@ MACRO(SETUP_EXTERNAL) ENDIF(WITH_EXTERNAL) IF(WIN32) - INCLUDE(${CMAKE_ROOT}/Modules/Platform/Windows-cl.cmake) + FIND_PACKAGE(External REQUIRED) + IF(MSVC10) IF(NOT MSVC10_REDIST_DIR) # If you have VC++ 2010 Express, put x64/Microsoft.VC100.CRT/*.dll in ${EXTERNAL_PATH}/redist SET(MSVC10_REDIST_DIR "${EXTERNAL_PATH}/redist") ENDIF(NOT MSVC10_REDIST_DIR) - GET_FILENAME_COMPONENT(VC_ROOT_DIR "[HKEY_CURRENT_USER\\Software\\Microsoft\\VisualStudio\\10.0_Config;InstallDir]" ABSOLUTE) - # VC_ROOT_DIR is set to "registry" when a key is not found - IF(VC_ROOT_DIR MATCHES "registry") - GET_FILENAME_COMPONENT(VC_ROOT_DIR "[HKEY_CURRENT_USER\\Software\\Microsoft\\VCExpress\\10.0_Config;InstallDir]" ABSOLUTE) - IF(VC_ROOT_DIR MATCHES "registry") - MESSAGE(FATAL_ERROR "Unable to find VC++ 2010 directory!") - ENDIF(VC_ROOT_DIR MATCHES "registry") - ENDIF(VC_ROOT_DIR MATCHES "registry") - # convert IDE fullpath to VC++ path - STRING(REGEX REPLACE "Common7/.*" "VC" VC_DIR ${VC_ROOT_DIR}) - ELSE(MSVC10) - IF(${CMAKE_MAKE_PROGRAM} MATCHES "Common7") + + IF(NOT VC_DIR) + IF(NOT VC_ROOT_DIR) + GET_FILENAME_COMPONENT(VC_ROOT_DIR "[HKEY_CURRENT_USER\\Software\\Microsoft\\VisualStudio\\10.0_Config;InstallDir]" ABSOLUTE) + # VC_ROOT_DIR is set to "registry" when a key is not found + IF(VC_ROOT_DIR MATCHES "registry") + GET_FILENAME_COMPONENT(VC_ROOT_DIR "[HKEY_CURRENT_USER\\Software\\Microsoft\\VCExpress\\10.0_Config;InstallDir]" ABSOLUTE) + IF(VC_ROOT_DIR MATCHES "registry") + FILE(TO_CMAKE_PATH $ENV{VS100COMNTOOLS} VC_ROOT_DIR) + IF(NOT VC_ROOT_DIR) + MESSAGE(FATAL_ERROR "Unable to find VC++ 2010 directory!") + ENDIF(NOT VC_ROOT_DIR) + ENDIF(VC_ROOT_DIR MATCHES "registry") + ENDIF(VC_ROOT_DIR MATCHES "registry") + ENDIF(NOT VC_ROOT_DIR) # convert IDE fullpath to VC++ path - STRING(REGEX REPLACE "Common7/.*" "VC" VC_DIR ${CMAKE_MAKE_PROGRAM}) - ELSE(${CMAKE_MAKE_PROGRAM} MATCHES "Common7") - # convert compiler fullpath to VC++ path - STRING(REGEX REPLACE "VC/bin/.+" "VC" VC_DIR ${CMAKE_CXX_COMPILER}) - ENDIF(${CMAKE_MAKE_PROGRAM} MATCHES "Common7") + STRING(REGEX REPLACE "Common7/.*" "VC" VC_DIR ${VC_ROOT_DIR}) + ENDIF(NOT VC_DIR) + ELSE(MSVC10) + IF(NOT VC_DIR) + IF(${CMAKE_MAKE_PROGRAM} MATCHES "Common7") + # convert IDE fullpath to VC++ path + STRING(REGEX REPLACE "Common7/.*" "VC" VC_DIR ${CMAKE_MAKE_PROGRAM}) + ELSE(${CMAKE_MAKE_PROGRAM} MATCHES "Common7") + # convert compiler fullpath to VC++ path + STRING(REGEX REPLACE "VC/bin/.+" "VC" VC_DIR ${CMAKE_CXX_COMPILER}) + ENDIF(${CMAKE_MAKE_PROGRAM} MATCHES "Common7") + ENDIF(NOT VC_DIR) ENDIF(MSVC10) ELSE(WIN32) - IF(CMAKE_FIND_LIBRARY_SUFFIXES AND NOT APPLE) + IF(APPLE) IF(WITH_STATIC_EXTERNAL) - SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a") + SET(CMAKE_FIND_LIBRARY_SUFFIXES .a .dylib .so) ELSE(WITH_STATIC_EXTERNAL) - SET(CMAKE_FIND_LIBRARY_SUFFIXES ".so") + SET(CMAKE_FIND_LIBRARY_SUFFIXES .dylib .so .a) ENDIF(WITH_STATIC_EXTERNAL) - ENDIF(CMAKE_FIND_LIBRARY_SUFFIXES AND NOT APPLE) + ELSE(APPLE) + IF(WITH_STATIC_EXTERNAL) + SET(CMAKE_FIND_LIBRARY_SUFFIXES .a .so) + ELSE(WITH_STATIC_EXTERNAL) + SET(CMAKE_FIND_LIBRARY_SUFFIXES .so .a) + ENDIF(WITH_STATIC_EXTERNAL) + ENDIF(APPLE) ENDIF(WIN32) IF(WITH_STLPORT) FIND_PACKAGE(STLport REQUIRED) INCLUDE_DIRECTORIES(${STLPORT_INCLUDE_DIR}) - IF(WIN32) + IF(MSVC) SET(VC_INCLUDE_DIR "${VC_DIR}/include") FIND_PACKAGE(WindowsSDK REQUIRED) # use VC++ and Windows SDK include paths INCLUDE_DIRECTORIES(${VC_INCLUDE_DIR} ${WINSDK_INCLUDE_DIR}) - ENDIF(WIN32) + ENDIF(MSVC) ENDIF(WITH_STLPORT) ENDMACRO(SETUP_EXTERNAL) diff --git a/code/acinclude.m4 b/code/acinclude.m4 deleted file mode 100644 index 1f9286bf9..000000000 --- a/code/acinclude.m4 +++ /dev/null @@ -1,1396 +0,0 @@ -# ========================================================================= -# -# Macros used by Nevrax in configure.in files. -# -# $Id: acinclude.m4,v 1.20 2005-04-04 10:07:29 cado Exp $ -# -# ========================================================================= - -# ========================================================================= -# WARNING: The original version of this file is placed in the $CVSROOT/code -# directory. -# There is links in the $CVSROOT/code sub-directories to that file -# (ex: $CVSROOT/code/nel), so be careful of the consequences of -# any modification of that file. -# ========================================================================= - -# ========================================================================= -# Macros available in that file. -# -# -# AM_NEL_DEBUG -# -# Option: none. -# Description: manage the different debug and the release mode by setting -# correctly the CFLAGS and CXXFLAGS variables. -# -# -# AM_PATH_NEL -# -# Option: none. -# Description: check the instalation of the NeL library and set the -# CXXFLAGS and LIBS variables to use it. -# -# -# AM_PATH_STLPORT -# -# Option: none. -# Description: check the instalation of the STLPort library and set the -# CXXFLAGS and LIBS variables to use it. -# -# -# AM_PATH_OPENGL -# -# Option: "yes" if the use of the OpenGL library is mandatory. -# Description: check the instalation of the OpenGL library and set the -# OPENGL_CFLAGS and OPENGL_LIBS variables to use it. -# -# -# AM_PATH_FREETYPE -# -# Option: "yes" if the use of the Freetype library is mandatory. -# Description: check the instalation of the OpenGL library and set the -# FREETYPE_CFLAGS and FREETYPE_LIBS variables to use it. -# -# -# AM_PATH_XF86VIDMODE -# -# Option: none. -# Description: check the instalation of the OpenGL library and set the -# XF86VIDMODE_CFLAGS and XF86VIDMODE_LIBS variables to use it. -# -# -# AM_PATH_OPENAL -# -# Option: "yes" if the use of the OpenAL library is mandatory. -# Description: check the instalation of the OpenGL library and set the -# OPENAL_CFLAGS and OPENAL_LIBS variables to use it. -# -# -# AM_PATH_PYTHON -# -# Option: "yes" if the use of the Python library is mandatory. -# Description: check the instalation of the OpenGL library and set the -# PYTHON_CFLAGS and PYTHON_LIBS variables to use it. -# -# AM_PATH_CCACHE -# -# Option: none. -# Description: check the instalation of the Ccache utility. -# -# ========================================================================= - - -# ========================================================================= -# AM_NEL_DEBUG - -AC_DEFUN([AM_NEL_DEBUG], -[ - -MAX_C_OPTIMIZE="-O6" - -STL_DEBUG="-D__STL_DEBUG" - -NL_DEBUG="-DNL_DEBUG" -NL_DEBUG_FAST="-DNL_DEBUG_FAST" -NL_RELEASE_DEBUG="-DNL_RELEASE_DEBUG" -NL_RELEASE="-DNL_RELEASE_DEBUG" - -AC_ARG_WITH(debug, - [ --with-debug[=full|medium|fast] - Build a debug version (huge libraries). - Full mode set only NeL and STL debug flags. - Medium mode set NeL debug flags with inline - optimization (default mode). - Fast mode is like the Medium mode with some basic - optimization. - --without-debug Build without debugging code (default)], - [with_debug=$withval], - [with_debug=no]) - -# Build optimized or debug version ? -# First check for gcc and g++ -if test "$ac_cv_prog_gcc" = "yes" -then - DEBUG_CFLAGS="-g" - DEBUG_OPTIMIZE_CC="-O" - OPTIMIZE_CFLAGS="$MAX_C_OPTIMIZE" -else - DEBUG_CFLAGS="-g" - DEBUG_OPTIMIZE_CC="" - OPTIMIZE_CFLAGS="" -fi - -if test "$ac_cv_prog_cxx_g" = "yes" -then - DEBUG_CXXFLAGS="-g" - DEBUG_OPTIMIZE_CXX="-O" - OPTIMIZE_CXXFLAGS="-O3" - OPTIMIZE_INLINE_CXXFLAGS="-finline-functions" -else - DEBUG_CXXFLAGS="-g" - DEBUG_OPTIMIZE_CXX="" - OPTIMIZE_CXXFLAGS="" - OPTIMIZE_INLINE_CXXFLAGS="" -fi - -if test "$with_debug" = "yes" -o "$with_debug" = "medium" -then - # Medium debug. Inline optimization - CFLAGS="$DEBUG_CFLAGS $OPTIMIZE_INLINE_CFLAGS $NL_DEBUG $NL_DEBUG_FAST $CFLAGS" - CXXFLAGS="$DEBUG_CXXFLAGS $OPTIMIZE_INLINE_CXXFLAGS $NL_DEBUG $NL_DEBUG_FAST $CXXFLAGS" -else - if test "$with_debug" = "full" - then - # Full debug. Very slow in some cases - CFLAGS="$DEBUG_CFLAGS $NL_DEBUG $STL_DEBUG $CFLAGS" - CXXFLAGS="$DEBUG_CXXFLAGS $NL_DEBUG $STL_DEBUG $CXXFLAGS" - else - if test "$with_debug" = "fast" - then - # Fast debug. - CFLAGS="$DEBUG_CFLAGS $DEBUG_OPTIMIZE_CC $OPTIMIZE_INLINE_CFLAGS $NL_DEBUG $CFLAGS" - CXXFLAGS="$DEBUG_CXXFLAGS $DEBUG_OPTIMIZE_CXX $OPTIMIZE_INLINE_CXXFLAGS $NL_DEBUG $CXXFLAGS" - else - # Optimized version. No debug - CFLAGS="$OPTIMIZE_CFLAGS $NL_RELEASE_DEBUG $CFLAGS" - CXXFLAGS="$OPTIMIZE_CXXFLAGS $NL_RELEASE_DEBUG $CXXFLAGS" - fi - fi -fi - -# AC_MSG_RESULT([CFLAGS = $CFLAGS]) -# AC_MSG_RESULT([CXXGLAGS = $CXXFLAGS]) - -]) - - -# ========================================================================= -# MY_NEL_HEADER_CHK : NeL header files checking macros - -AC_DEFUN([MY_NEL_HEADER_CHK], -[ AC_REQUIRE_CPP() - -chk_message_obj="$1" -header="$2" -macro="$3" -is_mandatory="$4" - -if test $is_mandatory = "yes" -then - - _CPPFLAGS="$CPPFLAGS" - - CPPFLAGS="$CXXFLAGS $NEL_CFLAGS" - - AC_MSG_CHECKING(for $header) - - AC_EGREP_CPP( yo_header, -[#include <$header> -#ifdef $macro - yo_header -#endif], - have_header="yes", - have_header="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_header" = "yes" - then - AC_MSG_RESULT(yes) - else - if test "$is_mandatory" = "yes" - then - AC_MSG_ERROR([$chk_message_obj must be installed (http://www.nevrax.org).]) - else - AC_MSG_RESULT(no) - fi - fi -fi - - -]) - - -# ========================================================================= -# MY_NEL_LIB_CHK : NeL library checking macros - -AC_DEFUN([MY_NEL_LIB_CHK], -[ AC_REQUIRE_CPP() - -chk_message_obj="$1" -nel_test_lib="$2" -is_mandatory="$3" - -if test $is_mandatory = "yes" -then - - AC_CHECK_LIB($nel_test_lib, main,,[AC_MSG_ERROR([$chk_message_obj must be installed (http://www.nevrax.org).])]) -fi -]) - - -# ========================================================================= -# AM_PATH_NEL : NeL checking macros -AC_DEFUN([AM_PATH_NEL], -[ AC_REQUIRE_CPP() - -AC_ARG_WITH( nel, - [ --with-nel= path to the NeL install files directory. - e.g. /usr/local/nel]) - -AC_ARG_WITH( nel-include, - [ --with-nel-include= - path to the NeL header files directory. - e.g. /usr/local/nel/include]) - -AC_ARG_WITH( nel-lib, - [ --with-nel-lib= - path to the NeL library files directory. - e.g. /usr/local/nel/lib]) - - -nelmisc_is_mandatory="$1" -nelnet_is_mandatory="$2" -nel3d_is_mandatory="$3" -nelpacs_is_mandatory="$4" -nelsound_is_mandatory="$5" -nelai_is_mandatory="$6" -nelgeorges_is_mandatory="$7" - -# Check for nel-config -AC_PATH_PROG(NEL_CONFIG, nel-config, no) - -# -# Configure options (--with-nel*) have precendence -# over nel-config only set variables if they are not -# specified -# -if test "$NEL_CONFIG" != "no" -then - if test -z "$with_nel" -a -z "$with_nel_include" - then - CXXFLAGS="$CXXFLAGS `nel-config --cflags`" - fi - - if test -z "$with_nel" -a -z "$with_nel_lib" - then - LDFLAGS="`nel-config --ldflags` $LDFLAGS" - fi -fi - -# -# Set nel_libraries and nel_includes according to -# user specification (--with-nel*) if any. -# --with-nel-include and --with-nel-lib have precendence -# over --with-nel -# -if test "$with_nel" = "no" -then - # The user explicitly disabled the use of the NeL - AC_MSG_ERROR([NeL is mandatory: do not specify --without-nel]) -else - if test "$with_nel" -a "$with_nel" != "yes" - then - nel_includes="$with_nel/include" - nel_libraries="$with_nel/lib" - fi -fi - -if test "$with_nel_include" -then - nel_includes="$with_nel_include" -fi - -if test "$with_nel_lib" -then - nel_libraries="$with_nel_lib" -fi - -# -# Set compilation variables -# -if test "$nel_includes" -then - CXXFLAGS="$CXXFLAGS -I$nel_includes" -fi - -if test "$nel_libraries" -then - LDFLAGS="-L$nel_libraries $LDFLAGS" -fi - -# -# Collect headers information and bark if missing and -# mandatory -# - -MY_NEL_HEADER_CHK([NeL Misc], [nel/misc/types_nl.h], [NL_TYPES_H], $nelmisc_is_mandatory) -MY_NEL_HEADER_CHK([NeL Network], [nel/net/sock.h], [NL_SOCK_H], $nelnet_is_mandatory) -MY_NEL_HEADER_CHK([NeL 3D], [nel/3d/u_camera.h], [NL_U_CAMERA_H], $nel3d_is_mandatory) -MY_NEL_HEADER_CHK([NeL PACS], [nel/pacs/u_global_position.h], [NL_U_GLOBAL_POSITION_H], $nelpacs_is_mandatory) -MY_NEL_HEADER_CHK([NeL Sound], [nel/sound/u_source.h], [NL_U_SOURCE_H], $nelsound_is_mandatory) -MY_NEL_HEADER_CHK([NeL AI], [nel/ai/nl_ai.h], [_IA_NEL_H], $nelai_is_mandatory) -MY_NEL_HEADER_CHK([NeL Georges], [nel/georges/common.h], [NLGEORGES_COMMON_H], $nelgeorges_is_mandatory) - -# -# Collect libraries information and bark if missing and -# mandatory -# - -MY_NEL_LIB_CHK([NeL Misc], [nelmisc], $nelmisc_is_mandatory) -MY_NEL_LIB_CHK([NeL Network], [nelnet], $nelnet_is_mandatory) -MY_NEL_LIB_CHK([NeL 3D], [nel3d], $nel3d_is_mandatory) -MY_NEL_LIB_CHK([NeL PACS], [nelpacs], $nelpacs_is_mandatory) -MY_NEL_LIB_CHK([NeL Sound], [nelsnd], $nelsound_is_mandatory) -MY_NEL_LIB_CHK([NeL AI], [nelai], $nelai_is_mandatory) -MY_NEL_LIB_CHK([NeL Georges], [nelgeorges], $nelgeorges_is_mandatory) - -]) - - -# ========================================================================= -# AM_PATH_STLPORT : STLPort checking macros - -AC_DEFUN([AM_PATH_STLPORT], -[ AC_REQUIRE_CPP() - -AC_ARG_WITH( stlport, - [ --with-stlport= path to the STLPort install files directory. - e.g. /usr/local/stlport]) - -AC_ARG_WITH( stlport-include, - [ --with-stlport-include= - path to the STLPort header files directory. - e.g. /usr/local/stlport/stlport]) - -AC_ARG_WITH( stlport-lib, - [ --with-stlport-lib= - path to the STLPort library files directory. - e.g. /usr/local/stlport/lib]) - -if test "$with_debug" = "full" -then - stlport_lib="stlport_gcc_debug" -else - stlport_lib="stlport_gcc" -fi - -if test "$with_debug" = "full" -then - stlport_lib2="stlport_gcc_debug" -else - stlport_lib2="stlport_gcc" -fi - -if test "$with_stlport" = no -then - # The user explicitly disabled the use of the STLPorts - AC_MSG_ERROR([STLPort is mandatory: do not specify --without-stlport]) -else - stlport_includes="/usr/include/stlport" - if test "$with_stlport" -a "$with_stlport" != yes - then - stlport_includes="$with_stlport/stlport" - stlport_libraries="$with_stlport/lib" - - if test ! -d "$stlport_includes" - then - stlport_includes="$with_stlport/include/stlport" - fi - fi -fi - -if test "$with_stlport_include" -then - stlport_includes="$with_stlport_include" -fi - -if test "$with_stlport_lib" -then - stlport_libraries="$with_stlport_lib" -fi - -# Check for the 'pthread' library. SLTPort needs it. -AC_CHECK_LIB(pthread, main, , [AC_MSG_ERROR([cannot find the pthread library.])]) -AC_CHECK_LIB(dl, dlopen, , [AC_MSG_ERROR([cannot find the dl library.])]) - -AC_LANG_SAVE -AC_LANG_CPLUSPLUS - -# Put STLPorts includes in CXXFLAGS -if test "$stlport_includes" -then - CXXFLAGS="$CXXFLAGS -I$stlport_includes" -fi - -# Put STLPorts libraries directory in LIBS -if test "$stlport_libraries" -then - LIBS="-L$stlport_libraries $LIBS" -else - stlport_libraries='default' -fi - -# Put STLPort GCC libraries directory in LIBS -if test "$stlport_libraries2" -then - LIBS="-L$stlport_libraries2 $LIBS" -else - stlport_libraries2='default' -fi - -# Test the headers - -AC_CHECK_HEADER(algorithm, - have_stlport_headers="yes", - have_stlport_headers="no" ) - -AC_MSG_CHECKING(for STLPort headers) - -if test "$have_stlport_headers" = "yes" -then - AC_MSG_RESULT([$stlport_includes]) -else - AC_MSG_RESULT(no) -fi - -AC_CHECK_LIB($stlport_lib, main,, have_stlport_libraries="no") - -AC_MSG_CHECKING(for STLPort library) - -if test "$have_stlport_libraries" != "no" -then - AC_MSG_RESULT([$stlport_libraries]) -else - AC_MSG_RESULT(no) -fi - -AC_CHECK_LIB($stlport_lib2, main,, have_stlport_libraries="no") - -AC_MSG_CHECKING(for STLPort GCC library) - -if test "$have_stlport_libraries2" != "no" -then - AC_MSG_RESULT([$stlport_libraries2]) -else - AC_MSG_RESULT(no) -fi - -if test "$have_stlport_headers" = "yes" && - (test "$have_stlport_libraries" != "no" || test "$have_stlport_libraries2" != "no") -then - have_stlport="yes" -else - have_stlport="no" -fi - -if test "$have_stlport" = "no" -then - AC_MSG_ERROR([STLPort must be installed (http://www.stlport.org).]) -fi - -AC_LANG_RESTORE - -]) - - -# ========================================================================= -# AM_PATH_OPENGL : OpenGL checking macros - -AC_DEFUN([AM_PATH_OPENGL], -[ AC_MSG_CHECKING(for OpenGL headers and GL Version >= 1.2) - -is_mandatory="$1" - -AC_REQUIRE_CPP() - -AC_ARG_WITH( opengl, - [ --with-opengl= path to the OpenGL install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( opengl-include, - [ --with-opengl-include= - path to the OpenGL header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( opengl-lib, - [ --with-opengl-lib= - path to the OpenGL library files directory. - e.g. /usr/local/lib]) - -opengl_lib="GL" - -if test "$with_opengl" -then - opengl_includes="$with_opengl/include" - opengl_libraries="$with_opengl/lib" -fi - -if test "$with_opengl_include" -then - opengl_includes="$with_opengl_include" -fi - -if test "$with_opengl_lib" -then - opengl_libraries="$with_opengl_lib" -fi - -# Set OPENGL_CFLAGS -if test "$opengl_includes" -then - OPENGL_CFLAGS="-I$opengl_includes" -fi - -# Set OPENGL_LIBS -if test "$opengl_libraries" -then - OPENGL_LIBS="-L$opengl_libraries" -fi -OPENGL_LIBS="$OPENGL_LIBS -l$opengl_lib" - -# Test the headers -_CPPFLAGS="$CPPFLAGS" - -CPPFLAGS="$CXXFLAGS $OPENGL_CFLAGS" - -AC_EGREP_CPP( yo_opengl, -[#include -#if defined(GL_VERSION_1_2) - yo_opengl -#endif], - have_opengl_headers="yes", - have_opengl_headers="no" ) - -if test "$have_opengl_headers" = "yes" -then - if test "$opengl_includes" - then - AC_MSG_RESULT([$opengl_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Checking the GLEXT version >= 7 -AC_MSG_CHECKING(for and GLEXT version >= 7) - -AC_EGREP_CPP( yo_glext_version, -[#include -#ifdef GL_GLEXT_VERSION -#if GL_GLEXT_VERSION >= 7 - yo_glext_version -#endif -#endif], - have_glext="yes", - have_glext="no" ) - -if test "$have_glext" = "yes" -then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT([no, can be downloaded from http://oss.sgi.com/projects/ogl-sample/ABI/]) -fi - -# Test the libraries -AC_MSG_CHECKING(for OpenGL libraries) - -CPPFLAGS="$CXXFLAGS $OPENGL_LIBS" - -AC_TRY_LINK( , , have_opengl_libraries="yes", have_opengl_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_opengl_libraries" = "yes" -then - if test "$opengl_libraries" - then - AC_MSG_RESULT([$opengl_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -opengl_libraries="$opengl_libraries" - -if test "$have_opengl_headers" = "yes" \ - -a "$have_glext" = "yes" \ - -a "$have_opengl_libraries" = "yes" -then - have_opengl="yes" -else - have_opengl="no" -fi - -if test "$have_opengl" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([OpenGL >= 1.2 must be installed (http://www.mesa3d.org)]) -fi - -AC_SUBST(OPENGL_CFLAGS) -AC_SUBST(OPENGL_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_FREETYPE : FreeType checking macros - -AC_DEFUN([AM_PATH_FREETYPE], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -AC_ARG_WITH( freetype, - [ --with-freetype= path to the FreeType install files directory. - e.g. /usr/local/freetype]) - -AC_ARG_WITH( freetype-include, - [ --with-freetype-include= - path to the FreeType header files directory. - e.g. /usr/local/freetype/include]) - -AC_ARG_WITH( freetype-lib, - [ --with-freetype-lib= - path to the FreeType library files directory. - e.g. /usr/local/freetype/lib]) - -freetype_lib="freetype" - - -AC_PATH_PROG(FREETYPE_CONFIG, freetype-config, no) - -if test "$FREETYPE_CONFIG" = "no" -then - have_freetype_config="no" -else - FREETYPE_CFLAGS=`freetype-config --cflags` - FREETYPE_LIBS=`freetype-config --libs` - have_freetype_config="yes" -fi - -if test "$with_freetype" -then - freetype_includes="$with_freetype/include" - freetype_libraries="$with_freetype/lib" -fi - -if test "$with_freetype_include" -then - freetype_includes="$with_freetype_include" -fi - -if test "$with_freetype_lib" -then - freetype_libraries="$with_freetype_lib" -fi - -if test "$freetype_includes" -then - FREETYPE_CFLAGS="-I$freetype_includes" -fi - -# Checking the FreeType 2 instalation -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS=" $FREETYPE_CFLAGS $CXXFLAGS" - -AC_MSG_CHECKING(for FreeType version = 2) - -AC_EGREP_CPP( yo_freetype2, -[#include -#if FREETYPE_MAJOR == 2 - yo_freetype2 -#endif], - have_freetype2="yes", - have_freetype2="no") - -if test "$have_freetype2" = "yes" -then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for FreeType libraries) - -if test $freetype_libraries -then - FREETYPE_LIBS="-L$freetype_libraries -l$freetype_lib" -fi - -CPPFLAGS="$FREETYPE_LIBS $CXXFLAGS" - -AC_TRY_LINK( , , have_freetype_libraries="yes", have_freetype_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_freetype_libraries" = "yes" -then - if test "$freetype_libraries" - then - AC_MSG_RESULT([$freetype_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -if test "$have_freetype2" = "yes" && test "$have_freetype_libraries" = "yes" -then - have_freetype="yes" -else - have_freetype="no" -fi - -if test "$have_freetype" = "no" && test "$is_mandatory" = "yes" -then - AC_MSG_ERROR([FreeType 2 must be installed (http://freetype.sourceforge.net)]) -fi - -AC_SUBST(FREETYPE_CFLAGS) -AC_SUBST(FREETYPE_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_XF86VIDMODE : XF86VidMode checking macros - -AC_DEFUN([AM_PATH_XF86VIDMODE], -[ AC_MSG_CHECKING(for XF86VidMode extension) - -AC_REQUIRE_CPP() - -AC_ARG_WITH( xf86vidmode-lib, - [ --with-xf86vidmode-lib= - path to the XF86VidMode library. - e.g. /usr/X11R6/lib] ) - -xf86vidmode_lib="Xxf86vm" - -if test "$with_xf86vidmode_lib" = no -then - # The user explicitly disabled the use of XF86VidMode - have_xf86vidmode="disabled" - AC_MSG_RESULT(disabled) -else - if test "$with_xf86vidmode_lib" - then - xf86vidmode_libraries="$with_xf86vidmode_lib" - fi - - XF86VIDMODE_CFLAGS="-DXF86VIDMODE" -fi - -if test -z "$have_xf86vidmode" -# -a "$with_xf86vidmode_lib" -then - if test "$xf86vidmode_libraries" - then - XF86VIDMODE_LIBS="-L$xf86vidmode_libraries" - fi - - XF86VIDMODE_LIBS="$XF86VIDMODE_LIBS -l$xf86vidmode_lib" - - _CPPFLAGS="$CPPFLAGS" - - CPPFLAGS="$CXXFLAGS $XF86VIDMODE_LIBS" - - AC_TRY_LINK( , , have_xf86vidmode_libraries="yes", have_xf86vidmode_libraries="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_xf86vidmode_libraries" = "yes" - then - have_xf86vidmode="yes" - if test "$xf86vidmode_libraries" - then - AC_MSG_RESULT($xf86vidmode_libraries) - else - AC_MSG_RESULT(yes) - fi - else - have_xf86vidmode="no" - AC_MSG_RESULT(no, no fullscreen support available.) - fi - - xf86vidmode_libraries="$xf86vidmode_libraries" - -fi - -AC_SUBST(XF86VIDMODE_CFLAGS) -AC_SUBST(XF86VIDMODE_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_OPENAL : OpenAL checking macros - -AC_DEFUN([AM_PATH_OPENAL], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the OpenAL files location -AC_ARG_WITH( openal, - [ --with-openal= path to the OpenAL install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( openal-include, - [ --with-openal-include= - path to the OpenAL header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( openal-lib, - [ --with-openal-lib= - path to the OpenAL library files directory. - e.g. /usr/local/lib]) - -openal_lib="openal" - -if test $with_openal -then - openal_includes="$with_openal/include" - openal_libraries="$with_openal/lib" -fi - -if test "$with_openal_include" -then - openal_includes="$with_openal_include" -fi - -if test "$with_openal_lib" -then - openal_libraries="$with_openal_lib" -fi - - -# Set OPENAL_CFLAGS -if test "$openal_includes" -then - OPENAL_CFLAGS="-I$openal_includes" -fi - -# Set OPENAL_LIBS -if test "$openal_libraries" -then - OPENAL_LIBS="-L$openal_libraries" -fi -OPENAL_LIBS="$OPENAL_LIBS -l$openal_lib" - -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS="$CXXFLAGS $OPENAL_CFLAGS" - -AC_MSG_CHECKING(for OpenAL headers) -AC_EGREP_CPP( yo_openal, -[#include -#ifdef AL_VERSION - yo_openal -#endif], - have_openal_headers="yes", - have_openal_headers="no" ) - -if test "$have_openal_headers" = "yes" -then - if test "$openal_includes" - then - AC_MSG_RESULT([$openal_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for OpenAL libraries) - -CPPFLAGS="$CXXFLAGS $OPENAL_LIBS" - -AC_TRY_LINK( , , have_openal_libraries="yes", have_openal_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_openal_libraries" = "yes" -then - if test "$openal_libraries" - then - AC_MSG_RESULT([$openal_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -openal_libraries="$openal_libraries" - -if test "$have_openal_headers" = "yes" \ - && test "$have_openal_libraries" = "yes" -then - have_openal="yes" -else - have_openal="no" -fi - -if test "$have_openal" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([OpenAL is needed to compile NeL (http://www.openal.org).]) -fi - -AC_SUBST(OPENAL_CFLAGS) -AC_SUBST(OPENAL_LIBS) - - -]) - - -# ========================================================================= -# AM_PATH_PYTHON : Python checking macros - -AC_DEFUN([AM_PATH_PYTHON], -[ python_version_required="$1" - -is_mandatory="$2" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the Python files location -AC_ARG_WITH( python, - [ --with-python= path to the Python prefix installation directory. - e.g. /usr/local], - [ PYTHON_PREFIX=$with_python ] -) - -AC_ARG_WITH( python-version, - [ --with-python-version= - Python version to use, e.g. 1.5], - [ PYTHON_VERSION=$with_python_version ] -) - -if test ! "$PYTHON_PREFIX" = "" -then - PATH="$PYTHON_PREFIX/bin:$PATH" -fi - -if test ! "$PYTHON_VERSION" = "" -then - PYTHON_EXEC="python$PYTHON_VERSION" -else - PYTHON_EXEC="python python2.1 python2.0 python1.5" -fi - -AC_PATH_PROGS(PYTHON, $PYTHON_EXEC, no, $PATH) - -if test "$PYTHON" != "no" -then - PYTHON_PREFIX=`$PYTHON -c 'import sys; print "%s" % (sys.prefix)'` - PYTHON_VERSION=`$PYTHON -c 'import sys; print "%s" % (sys.version[[:3]])'` - - is_python_version_enough=`expr $python_version_required \<= $PYTHON_VERSION` -fi - - -if test "$PYTHON" = "no" || test "$is_python_version_enough" != "1" -then - - if test "$is_mandatory" = "yes" - then - AC_MSG_ERROR([Python $python_version_required must be installed (http://www.python.org)]) - else - have_python="no" - fi - -else - - python_includes="$PYTHON_PREFIX/include/python$PYTHON_VERSION" - python_libraries="$PYTHON_PREFIX/lib/python$PYTHON_VERSION/config" - python_lib="python$PYTHON_VERSION" - - PYTHON_CFLAGS="-I$python_includes" - PYTHON_LIBS="-L$python_libraries -l$python_lib" - - _CPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CXXFLAGS ${PYTHON_CFLAGS}" - - # Test the headers - AC_MSG_CHECKING(for Python headers) - - AC_EGREP_CPP( yo_python, - [#include - yo_python - ], - have_python_headers="yes", - have_python_headers="no" ) - - if test "$have_python_headers" = "yes" - then - AC_MSG_RESULT([$python_includes]) - else - AC_MSG_RESULT(no) - fi - - # Test the libraries - AC_MSG_CHECKING(for Python libraries) - - CPPFLAGS="$CXXFLAGS $PYTHON_CFLAGS" - - AC_TRY_LINK( , , have_python_libraries="yes", have_python_libraries="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_python_libraries" = "yes" - then - if test "$python_libraries" - then - AC_MSG_RESULT([$python_libraries]) - else - AC_MSG_RESULT(yes) - fi - else - AC_MSG_RESULT(no) - fi - - if test "$have_python_headers" = "yes" \ - && test "$have_python_libraries" = "yes" - then - have_python="yes" - else - have_python="no" - fi - - if test "$have_python" = "no" -a "$is_mandatory" = "yes" - then - AC_MSG_ERROR([Python is needed to compile NeL (http://www.python.org).]) - fi - - AC_SUBST(PYTHON_CFLAGS) - AC_SUBST(PYTHON_LIBS) - -fi - -]) - - -# ========================================================================= -# AM_PATH_CCACHE : Ccache checking macros - -AC_DEFUN([AM_PATH_CCACHE], -[ - -AC_ARG_WITH( ccache, - [ --with-ccache use ccache for compiling.], - [ using_ccache=$with_ccache ] -) - -AC_PATH_PROG(CCACHE, ccache) -if test "$CCACHE" -a "$using_ccache" = "yes" -then - CC="ccache $CC" - CXX="ccache $CXX" -fi - -]) - -# ========================================================================= -# AM_PATH_MYSQL : MySQL library - -# AM_PATH_MYSQL([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) -# Test for MYSQL, and define MYSQL_CFLAGS and MYSQL_LIBS -# -AC_DEFUN([AM_PATH_MYSQL], -[# -# Get the cflags and libraries from the mysql_config script -# -AC_ARG_WITH(mysql-prefix,[ --with-mysql-prefix=PFX Prefix where MYSQL is installed (optional)], - mysql_prefix="$withval", mysql_prefix="") -AC_ARG_WITH(mysql-exec-prefix,[ --with-mysql-exec-prefix=PFX Exec prefix where MYSQL is installed (optional)], - mysql_exec_prefix="$withval", mysql_exec_prefix="") -AC_ARG_ENABLE(mysqltest, [ --disable-mysqltest Do not try to compile and run a test MYSQL program], - , enable_mysqltest=yes) - - if test x$mysql_exec_prefix != x ; then - mysql_args="$mysql_args --exec-prefix=$mysql_exec_prefix" - if test x${MYSQL_CONFIG+set} != xset ; then - MYSQL_CONFIG=$mysql_exec_prefix/bin/mysql_config - fi - fi - if test x$mysql_prefix != x ; then - mysql_args="$mysql_args --prefix=$mysql_prefix" - if test x${MYSQL_CONFIG+set} != xset ; then - MYSQL_CONFIG=$mysql_prefix/bin/mysql_config - fi - fi - - AC_REQUIRE([AC_CANONICAL_TARGET]) - AC_PATH_PROG(MYSQL_CONFIG, mysql_config, no) - min_mysql_version=ifelse([$1], ,0.11.0,$1) - AC_MSG_CHECKING(for MYSQL - version >= $min_mysql_version) - no_mysql="" - if test "$MYSQL_CONFIG" = "no" ; then - no_mysql=yes - else - MYSQL_CFLAGS=`$MYSQL_CONFIG $mysqlconf_args --cflags | sed -e "s/'//g"` - MYSQL_LIBS=`$MYSQL_CONFIG $mysqlconf_args --libs | sed -e "s/'//g"` - - mysql_major_version=`$MYSQL_CONFIG $mysql_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` - mysql_minor_version=`$MYSQL_CONFIG $mysql_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` - mysql_micro_version=`$MYSQL_CONFIG $mysql_config_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` - if test "x$enable_mysqltest" = "xyes" ; then - ac_save_CFLAGS="$CFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $MYSQL_CFLAGS" - LIBS="$LIBS $MYSQL_LIBS" -# -# Now check if the installed MYSQL is sufficiently new. (Also sanity -# checks the results of mysql_config to some extent -# - rm -f conf.mysqltest - AC_TRY_RUN([ -#include -#include -#include -#include - -char* -my_strdup (char *str) -{ - char *new_str; - - if (str) - { - new_str = (char *)malloc ((strlen (str) + 1) * sizeof(char)); - strcpy (new_str, str); - } - else - new_str = NULL; - - return new_str; -} - -int main (int argc, char *argv[]) -{ - int major, minor, micro; - char *tmp_version; - - /* This hangs on some systems (?) - system ("touch conf.mysqltest"); - */ - { FILE *fp = fopen("conf.mysqltest", "a"); if ( fp ) fclose(fp); } - - /* HP/UX 9 (%@#!) writes to sscanf strings */ - tmp_version = my_strdup("$min_mysql_version"); - if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { - printf("%s, bad version string\n", "$min_mysql_version"); - exit(1); - } - - if (($mysql_major_version > major) || - (($mysql_major_version == major) && ($mysql_minor_version > minor)) || - (($mysql_major_version == major) && ($mysql_minor_version == minor) && ($mysql_micro_version >= micro))) - { - return 0; - } - else - { - printf("\n*** 'mysql_config --version' returned %d.%d.%d, but the minimum version\n", $mysql_major_version, $mysql_minor_version, $mysql_micro_version); - printf("*** of MYSQL required is %d.%d.%d. If mysql_config is correct, then it is\n", major, minor, micro); - printf("*** best to upgrade to the required version.\n"); - printf("*** If mysql_config was wrong, set the environment variable MYSQL_CONFIG\n"); - printf("*** to point to the correct copy of mysql_config, and remove the file\n"); - printf("*** config.cache before re-running configure\n"); - return 1; - } -} - -],, no_mysql=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - fi - if test "x$no_mysql" = x ; then - AC_MSG_RESULT(yes) - ifelse([$2], , :, [$2]) - else - AC_MSG_RESULT(no) - if test "$MYSQL_CONFIG" = "no" ; then - echo "*** The mysql_config script installed by MYSQL could not be found" - echo "*** If MYSQL was installed in PREFIX, make sure PREFIX/bin is in" - echo "*** your path, or set the MYSQL_CONFIG environment variable to the" - echo "*** full path to mysql_config." - else - if test -f conf.mysqltest ; then - : - else - echo "*** Could not run MYSQL test program, checking why..." - CFLAGS="$CFLAGS $MYSQL_CFLAGS" - LIBS="$LIBS $MYSQL_LIBS" - AC_TRY_LINK([ -#include -#include - -int main(int argc, char *argv[]) -{ return 0; } -#undef main -#define main K_and_R_C_main -], [ return 0; ], - [ echo "*** The test program compiled, but did not run. This usually means" - echo "*** that the run-time linker is not finding MYSQL or finding the wrong" - echo "*** version of MYSQL. If it is not finding MYSQL, you'll need to set your" - echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" - echo "*** to the installed location Also, make sure you have run ldconfig if that" - echo "*** is required on your system" - echo "***" - echo "*** If you have an old version installed, it is best to remove it, although" - echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], - [ echo "*** The test program failed to compile or link. See the file config.log for the" - echo "*** exact error that occured. This usually means MYSQL was incorrectly installed" - echo "*** or that you have moved MYSQL since it was installed. In the latter case, you" - echo "*** may want to edit the mysql_config script: $MYSQL_CONFIG" ]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - fi - MYSQL_CFLAGS="" - MYSQL_LIBS="" - ifelse([$3], , :, [$3]) - fi - AC_SUBST(MYSQL_CFLAGS) - AC_SUBST(MYSQL_LIBS) - rm -f conf.mysqltest -]) - -# ========================================================================= -# AM_PATH_FMOD : FMOD checking macros - -AC_DEFUN([AM_PATH_FMOD], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the FMOD files location -AC_ARG_WITH( fmod, - [ --with-fmod= path to the FMOD install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( fmod-include, - [ --with-fmod-include= - path to the FMOD header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( fmod-lib, - [ --with-fmod-lib= - path to the FMOD library files directory. - e.g. /usr/local/lib]) - -fmod_lib="fmod" - -if test $with_fmod -then - fmod_includes="$with_fmod/include" - fmod_libraries="$with_fmod/lib" -fi - -if test "$with_fmod_include" -then - fmod_includes="$with_fmod_include" -fi - -if test "$with_fmod_lib" -then - fmod_libraries="$with_fmod_lib" -fi - - -# Set FMOD_CFLAGS -if test "$fmod_includes" -then - FMOD_CFLAGS="-I$fmod_includes" -fi - -# Set FMOD_LIBS -if test "$fmod_libraries" -then - FMOD_LIBS="-L$fmod_libraries" -fi -FMOD_LIBS="$FMOD_LIBS -l$fmod_lib" - -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS="$CXXFLAGS $FMOD_CFLAGS" - -AC_MSG_CHECKING(for FMOD headers) -AC_EGREP_CPP( yo_fmod, -[#include -#ifdef FMOD_VERSION - yo_fmod -#endif], - have_fmod_headers="yes", - have_fmod_headers="no" ) - -if test "$have_fmod_headers" = "yes" -then - if test "$fmod_includes" - then - AC_MSG_RESULT([$fmod_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for FMOD libraries) - -CPPFLAGS="$CXXFLAGS $FMOD_LIBS" - -AC_TRY_LINK( , , have_fmod_libraries="yes", have_fmod_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_fmod_libraries" = "yes" -then - if test "$fmod_libraries" - then - AC_MSG_RESULT([$fmod_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -fmod_libraries="$fmod_libraries" - -if test "$have_fmod_headers" = "yes" \ - && test "$have_fmod_libraries" = "yes" -then - have_fmod="yes" -else - have_fmod="no" -fi - -if test "$have_fmod" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([FMOD is needed to compile NeL (http://www.fmod.org).]) -fi - -AC_SUBST(FMOD_CFLAGS) -AC_SUBST(FMOD_LIBS) - -]) - -# ========================================================================= -# End of file - diff --git a/code/nel/Makefile.am b/code/nel/Makefile.am deleted file mode 100644 index f2af0b6d0..000000000 --- a/code/nel/Makefile.am +++ /dev/null @@ -1,46 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in \ - configure \ - libtool \ - config.guess \ - config.sub \ - ltconfig \ - aclocal.m4 \ - config.h.in \ - install-sh \ - missing \ - mkinstalldirs \ - ltmain.sh \ - include/nelconfig.h \ - include/nelconfig.h.in \ - include/nel/nelconfig.h - -DISTCLEANFILES = include/stamp-h \ - include/stamp-h.in - -SUBDIRS = include src @TOOLS_SUBDIR@ @SAMPLE_SUBDIR@ - -bin_SCRIPTS = nel-config - -EXTRA_DIST = nel.dsw \ - nel.sln \ - nel_8.sln \ - autogen.sh \ - nel.m4 \ - automacros \ - doc \ - kdevelop \ - tools \ - samples - -dist-hook: - find $(distdir) -name CVS -print | xargs rm -fr - find $(distdir) -name .svn -print | xargs rm -fr - -m4datadir = $(datadir)/aclocal -m4data_DATA = nel.m4 - -# End of Makefile.am - diff --git a/code/nel/acinclude.m4 b/code/nel/acinclude.m4 deleted file mode 100644 index 7a6655bff..000000000 --- a/code/nel/acinclude.m4 +++ /dev/null @@ -1,1214 +0,0 @@ -# ========================================================================= -# -# Macros used by Nevrax in configure.in files. -# -# -# ========================================================================= - -# ========================================================================= -# WARNING: The original version of this file is placed in the $CVSROOT/code -# directory. -# There is links in the $CVSROOT/code sub-directories to that file -# (ex: $CVSROOT/code/nel), so be careful of the consequences of -# any modification of that file. -# ========================================================================= - -# ========================================================================= -# Macros available in that file. -# -# -# AM_NEL_DEBUG -# -# Option: none. -# Description: manage the different debug and the release mode by setting -# correctly the CFLAGS and CXXFLAGS variables. -# -# -# AM_PATH_NEL -# -# Option: none. -# Description: check the instalation of the NeL library and set the -# CXXFLAGS and LIBS variables to use it. -# -# -# AM_PATH_OPENGL -# -# Option: "yes" if the use of the OpenGL library is mandatory. -# Description: check the instalation of the OpenGL library and set the -# OPENGL_CFLAGS and OPENGL_LIBS variables to use it. -# -# -# AM_PATH_FREETYPE -# -# Option: "yes" if the use of the Freetype library is mandatory. -# Description: check the instalation of the OpenGL library and set the -# FREETYPE_CFLAGS and FREETYPE_LIBS variables to use it. -# -# -# AM_PATH_XF86VIDMODE -# -# Option: none. -# Description: check the instalation of the OpenGL library and set the -# XF86VIDMODE_CFLAGS and XF86VIDMODE_LIBS variables to use it. -# -# -# AM_PATH_OPENAL -# -# Option: "yes" if the use of the OpenAL library is mandatory. -# Description: check the instalation of the OpenGL library and set the -# OPENAL_CFLAGS and OPENAL_LIBS variables to use it. -# -# -# AM_PATH_PYTHON -# -# Option: "yes" if the use of the Python library is mandatory. -# Description: check the instalation of the OpenGL library and set the -# PYTHON_CFLAGS and PYTHON_LIBS variables to use it. -# -# ========================================================================= - - -# ========================================================================= -# AM_NEL_DEBUG - -AC_DEFUN([AM_NEL_DEBUG], -[ - -MAX_C_OPTIMIZE="-O6" - -NL_DEBUG="-DNL_DEBUG" -NL_RELEASE="-DNL_RELEASE" - -AC_ARG_WITH(debug, - [ --with-debug[=full|medium|fast] - Build a debug version (huge libraries). - Full mode set no optimization. - Medium mode set NeL debug flags with inline - optimization (default mode). - Fast mode is like the Medium mode with some basic - optimization. - --without-debug Build without debugging code (default)], - [with_debug=$withval], - [with_debug=no]) - -# Build optimized or debug version ? -# First check for gcc and g++ -if test "$ac_cv_prog_gcc" = "yes" -then - DEBUG_CFLAGS="-g -O0" - DEBUG_OPTIMIZE_CC="-O" - OPTIMIZE_CFLAGS="$MAX_C_OPTIMIZE" -else - DEBUG_CFLAGS="" - DEBUG_OPTIMIZE_CC="" - OPTIMIZE_CFLAGS="" -fi - -if test "$ac_cv_prog_cxx_g" = "yes" -then - DEBUG_CXXFLAGS="-g -O0" - DEBUG_OPTIMIZE_CXX="-O" - OPTIMIZE_CXXFLAGS="-O3" - OPTIMIZE_INLINE_CXXFLAGS="-finline-functions" -else - DEBUG_CXXFLAGS="" - DEBUG_OPTIMIZE_CXX="" - OPTIMIZE_CXXFLAGS="" - OPTIMIZE_INLINE_CXXFLAGS="" -fi - -if test "$with_debug" = "yes" -o "$with_debug" = "medium" -then - # Medium debug. Inline optimization - CFLAGS="$DEBUG_CFLAGS $OPTIMIZE_INLINE_CFLAGS $NL_DEBUG $CFLAGS" - CXXFLAGS="$DEBUG_CXXFLAGS $OPTIMIZE_INLINE_CXXFLAGS $NL_DEBUG $CXXFLAGS" -else - if test "$with_debug" = "full" - then - # Full debug. Very slow in some cases - CFLAGS="$DEBUG_CFLAGS $NL_DEBUG $CFLAGS" - CXXFLAGS="$DEBUG_CXXFLAGS $NL_DEBUG $CXXFLAGS" - else - if test "$with_debug" = "fast" - then - # Fast debug. - CFLAGS="$DEBUG_CFLAGS $DEBUG_OPTIMIZE_CC $OPTIMIZE_INLINE_CFLAGS $NL_DEBUG $CFLAGS" - CXXFLAGS="$DEBUG_CXXFLAGS $DEBUG_OPTIMIZE_CXX $OPTIMIZE_INLINE_CXXFLAGS $NL_DEBUG $CXXFLAGS" - else - # Optimized version. No debug - CFLAGS="$OPTIMIZE_CFLAGS $NL_RELEASE $CFLAGS" - CXXFLAGS="$OPTIMIZE_CXXFLAGS $NL_RELEASE $CXXFLAGS" - fi - fi -fi - -# AC_MSG_RESULT([CFLAGS = $CFLAGS]) -# AC_MSG_RESULT([CXXGLAGS = $CXXFLAGS]) - -]) - - -# ========================================================================= -# MY_NEL_HEADER_CHK : NeL header files checking macros - -AC_DEFUN([MY_NEL_HEADER_CHK], -[ AC_REQUIRE_CPP() - -chk_message_obj="$1" -header="$2" -macro="$3" -is_mandatory="$4" - -if test $is_mandatory = "yes" -then - - _CPPFLAGS="$CPPFLAGS" - - CPPFLAGS="$CXXFLAGS $NEL_CFLAGS" - - AC_MSG_CHECKING(for $header) - - AC_EGREP_CPP( yo_header, -[#include <$header> -#ifdef $macro - yo_header -#endif], - have_header="yes", - have_header="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_header" = "yes" - then - AC_MSG_RESULT(yes) - else - if test "$is_mandatory" = "yes" - then - AC_MSG_ERROR([$chk_message_obj must be installed (http://dev.ryzom.com).]) - else - AC_MSG_RESULT(no) - fi - fi -fi - - -]) - - -# ========================================================================= -# MY_NEL_LIB_CHK : NeL library checking macros - -AC_DEFUN([MY_NEL_LIB_CHK], -[ AC_REQUIRE_CPP() - -chk_message_obj="$1" -nel_test_lib="$2" -is_mandatory="$3" - -if test $is_mandatory = "yes" -then - - AC_CHECK_LIB($nel_test_lib, main,,[AC_MSG_ERROR([$chk_message_obj must be installed (http://dev.ryzom.com).])]) -fi -]) - - -# ========================================================================= -# AM_PATH_NEL : NeL checking macros -AC_DEFUN([AM_PATH_NEL], -[ AC_REQUIRE_CPP() - -AC_ARG_WITH( nel, - [ --with-nel= path to the NeL install files directory. - e.g. /usr/local/nel]) - -AC_ARG_WITH( nel-include, - [ --with-nel-include= - path to the NeL header files directory. - e.g. /usr/local/nel/include]) - -AC_ARG_WITH( nel-lib, - [ --with-nel-lib= - path to the NeL library files directory. - e.g. /usr/local/nel/lib]) - - -nelmisc_is_mandatory="$1" -nelnet_is_mandatory="$2" -nel3d_is_mandatory="$3" -nelpacs_is_mandatory="$4" -nelsound_is_mandatory="$5" -nelai_is_mandatory="$6" -nelgeorges_is_mandatory="$7" - -# Check for nel-config -AC_PATH_PROG(NEL_CONFIG, nel-config, no) - -# -# Configure options (--with-nel*) have precendence -# over nel-config only set variables if they are not -# specified -# -if test "$NEL_CONFIG" != "no" -then - if test -z "$with_nel" -a -z "$with_nel_include" - then - CXXFLAGS="$CXXFLAGS `nel-config --cflags`" - fi - - if test -z "$with_nel" -a -z "$with_nel_lib" - then - LDFLAGS="`nel-config --ldflags` $LDFLAGS" - fi -fi - -# -# Set nel_libraries and nel_includes according to -# user specification (--with-nel*) if any. -# --with-nel-include and --with-nel-lib have precendence -# over --with-nel -# -if test "$with_nel" = "no" -then - # The user explicitly disabled the use of the NeL - AC_MSG_ERROR([NeL is mandatory: do not specify --without-nel]) -else - if test "$with_nel" -a "$with_nel" != "yes" - then - nel_includes="$with_nel/include" - nel_libraries="$with_nel/lib" - fi -fi - -if test "$with_nel_include" -then - nel_includes="$with_nel_include" -fi - -if test "$with_nel_lib" -then - nel_libraries="$with_nel_lib" -fi - -# -# Set compilation variables -# -if test "$nel_includes" -then - CXXFLAGS="$CXXFLAGS -I$nel_includes" -fi - -if test "$nel_libraries" -then - LDFLAGS="-L$nel_libraries $LDFLAGS" -fi - -# -# Collect headers information and bark if missing and -# mandatory -# - -MY_NEL_HEADER_CHK([NeL Misc], [nel/misc/types_nl.h], [NL_TYPES_H], $nelmisc_is_mandatory) -MY_NEL_HEADER_CHK([NeL Network], [nel/net/sock.h], [NL_SOCK_H], $nelnet_is_mandatory) -MY_NEL_HEADER_CHK([NeL 3D], [nel/3d/u_camera.h], [NL_U_CAMERA_H], $nel3d_is_mandatory) -MY_NEL_HEADER_CHK([NeL PACS], [nel/pacs/u_global_position.h], [NL_U_GLOBAL_POSITION_H], $nelpacs_is_mandatory) -MY_NEL_HEADER_CHK([NeL Sound], [nel/sound/u_source.h], [NL_U_SOURCE_H], $nelsound_is_mandatory) -MY_NEL_HEADER_CHK([NeL AI], [nel/ai/nl_ai.h], [_IA_NEL_H], $nelai_is_mandatory) -MY_NEL_HEADER_CHK([NeL Georges], [nel/georges/common.h], [NLGEORGES_COMMON_H], $nelgeorges_is_mandatory) - -# -# Collect libraries information and bark if missing and -# mandatory -# - -MY_NEL_LIB_CHK([NeL Misc], [nelmisc], $nelmisc_is_mandatory) -MY_NEL_LIB_CHK([NeL Network], [nelnet], $nelnet_is_mandatory) -MY_NEL_LIB_CHK([NeL 3D], [nel3d], $nel3d_is_mandatory) -MY_NEL_LIB_CHK([NeL PACS], [nelpacs], $nelpacs_is_mandatory) -MY_NEL_LIB_CHK([NeL Sound], [nelsnd], $nelsound_is_mandatory) -MY_NEL_LIB_CHK([NeL AI], [nelai], $nelai_is_mandatory) -MY_NEL_LIB_CHK([NeL Georges], [nelgeorges], $nelgeorges_is_mandatory) - -]) - -# ========================================================================= -# AM_PATH_OPENGL : OpenGL checking macros - -AC_DEFUN([AM_PATH_OPENGL], -[ AC_MSG_CHECKING(for OpenGL headers and GL Version >= 1.2) - -is_mandatory="$1" - -AC_REQUIRE_CPP() - -AC_ARG_WITH( opengl, - [ --with-opengl= path to the OpenGL install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( opengl-include, - [ --with-opengl-include= - path to the OpenGL header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( opengl-lib, - [ --with-opengl-lib= - path to the OpenGL library files directory. - e.g. /usr/local/lib]) - -opengl_lib="GL" - -if test "$with_opengl" -then - opengl_includes="$with_opengl/include" - opengl_libraries="$with_opengl/lib" -fi - -if test "$with_opengl_include" -then - opengl_includes="$with_opengl_include" -fi - -if test "$with_opengl_lib" -then - opengl_libraries="$with_opengl_lib" -fi - -# Set OPENGL_CFLAGS -if test "$opengl_includes" -then - OPENGL_CFLAGS="-I$opengl_includes" -fi - -# Set OPENGL_LIBS -if test "$opengl_libraries" -then - OPENGL_LIBS="-L$opengl_libraries" -fi -OPENGL_LIBS="$OPENGL_LIBS -l$opengl_lib" - -# Test the headers -_CPPFLAGS="$CPPFLAGS" - -CPPFLAGS="$CXXFLAGS $OPENGL_CFLAGS" - -AC_EGREP_CPP( yo_opengl, -[#include -#if defined(GL_VERSION_1_2) - yo_opengl -#endif], - have_opengl_headers="yes", - have_opengl_headers="no" ) - -if test "$have_opengl_headers" = "yes" -then - if test "$opengl_includes" - then - AC_MSG_RESULT([$opengl_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Checking the GLEXT version >= 7 -AC_MSG_CHECKING(for and GLEXT version >= 7) - -AC_EGREP_CPP( yo_glext_version, -[#include -#ifdef GL_GLEXT_VERSION -#if GL_GLEXT_VERSION >= 7 - yo_glext_version -#endif -#endif], - have_glext="yes", - have_glext="no" ) - -if test "$have_glext" = "yes" -then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT([no, can be downloaded from http://www.opengl.org/registry/]) -fi - -# Test the libraries -AC_MSG_CHECKING(for OpenGL libraries) - -CPPFLAGS="$CXXFLAGS $OPENGL_LIBS" - -AC_TRY_LINK( , , have_opengl_libraries="yes", have_opengl_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_opengl_libraries" = "yes" -then - if test "$opengl_libraries" - then - AC_MSG_RESULT([$opengl_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -opengl_libraries="$opengl_libraries" - -if test "$have_opengl_headers" = "yes" \ - -a "$have_glext" = "yes" \ - -a "$have_opengl_libraries" = "yes" -then - have_opengl="yes" -else - have_opengl="no" -fi - -if test "$have_opengl" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([OpenGL >= 1.2 must be installed (http://www.mesa3d.org)]) -fi - -AC_SUBST(OPENGL_CFLAGS) -AC_SUBST(OPENGL_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_FREETYPE : FreeType checking macros - -AC_DEFUN([AM_PATH_FREETYPE], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -AC_ARG_WITH( freetype, - [ --with-freetype= path to the FreeType install files directory. - e.g. /usr/local/freetype]) - -AC_ARG_WITH( freetype-include, - [ --with-freetype-include= - path to the FreeType header files directory. - e.g. /usr/local/freetype/include]) - -AC_ARG_WITH( freetype-lib, - [ --with-freetype-lib= - path to the FreeType library files directory. - e.g. /usr/local/freetype/lib]) - -freetype_lib="freetype" - - -AC_PATH_PROG(FREETYPE_CONFIG, freetype-config, no) - -if test "$FREETYPE_CONFIG" = "no" -then - have_freetype_config="no" -else - FREETYPE_CFLAGS=`freetype-config --cflags` - FREETYPE_LIBS=`freetype-config --libs` - have_freetype_config="yes" -fi - -if test "$with_freetype" -then - freetype_includes="$with_freetype/include" - freetype_libraries="$with_freetype/lib" -fi - -if test "$with_freetype_include" -then - freetype_includes="$with_freetype_include" -fi - -if test "$with_freetype_lib" -then - freetype_libraries="$with_freetype_lib" -fi - -if test "$freetype_includes" -then - FREETYPE_CFLAGS="-I$freetype_includes" -fi - -# Checking the FreeType 2 instalation -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS=" $FREETYPE_CFLAGS $CXXFLAGS" - -AC_MSG_CHECKING(for FreeType version = 2) - -AC_EGREP_CPP( yo_freetype2, -[#include -#if FREETYPE_MAJOR == 2 - yo_freetype2 -#endif], - have_freetype2="yes", - have_freetype2="no") - -if test "$have_freetype2" = "yes" -then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for FreeType libraries) - -if test $freetype_libraries -then - FREETYPE_LIBS="-L$freetype_libraries -l$freetype_lib" -fi - -CPPFLAGS="$FREETYPE_LIBS $CXXFLAGS" - -AC_TRY_LINK( , , have_freetype_libraries="yes", have_freetype_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_freetype_libraries" = "yes" -then - if test "$freetype_libraries" - then - AC_MSG_RESULT([$freetype_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -if test "$have_freetype2" = "yes" && test "$have_freetype_libraries" = "yes" -then - have_freetype="yes" -else - have_freetype="no" -fi - -if test "$have_freetype" = "no" && test "$is_mandatory" = "yes" -then - AC_MSG_ERROR([FreeType 2 must be installed (http://freetype.sourceforge.net)]) -fi - -AC_SUBST(FREETYPE_CFLAGS) -AC_SUBST(FREETYPE_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_XF86VIDMODE : XF86VidMode checking macros - -AC_DEFUN([AM_PATH_XF86VIDMODE], -[ AC_MSG_CHECKING(for XF86VidMode extension) - -AC_REQUIRE_CPP() - -AC_ARG_WITH( xf86vidmode-lib, - [ --with-xf86vidmode-lib= - path to the XF86VidMode library. - e.g. /usr/X11R6/lib] ) - -xf86vidmode_lib="Xxf86vm" - -if test "$with_xf86vidmode_lib" = no -then - # The user explicitly disabled the use of XF86VidMode - have_xf86vidmode="disabled" - AC_MSG_RESULT(disabled) -else - if test "$with_xf86vidmode_lib" - then - xf86vidmode_libraries="$with_xf86vidmode_lib" - fi - - XF86VIDMODE_CFLAGS="-DXF86VIDMODE" -fi - -if test -z "$have_xf86vidmode" -# -a "$with_xf86vidmode_lib" -then - if test "$xf86vidmode_libraries" - then - XF86VIDMODE_LIBS="-L$xf86vidmode_libraries" - fi - - XF86VIDMODE_LIBS="$XF86VIDMODE_LIBS -l$xf86vidmode_lib" - - _CPPFLAGS="$CPPFLAGS" - - CPPFLAGS="$CXXFLAGS $XF86VIDMODE_LIBS" - - AC_TRY_LINK( , , have_xf86vidmode_libraries="yes", have_xf86vidmode_libraries="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_xf86vidmode_libraries" = "yes" - then - have_xf86vidmode="yes" - if test "$xf86vidmode_libraries" - then - AC_MSG_RESULT($xf86vidmode_libraries) - else - AC_MSG_RESULT(yes) - fi - else - have_xf86vidmode="no" - AC_MSG_RESULT(no, no fullscreen support available.) - fi - - xf86vidmode_libraries="$xf86vidmode_libraries" - -fi - -AC_SUBST(XF86VIDMODE_CFLAGS) -AC_SUBST(XF86VIDMODE_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_OPENAL : OpenAL checking macros - -AC_DEFUN([AM_PATH_OPENAL], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the OpenAL files location -AC_ARG_WITH( openal, - [ --with-openal= path to the OpenAL install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( openal-include, - [ --with-openal-include= - path to the OpenAL header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( openal-lib, - [ --with-openal-lib= - path to the OpenAL library files directory. - e.g. /usr/local/lib]) - -openal_lib="openal" -alut_lib="alut" - -if test $with_openal -then - openal_includes="$with_openal/include" - openal_libraries="$with_openal/lib" -fi - -if test "$with_openal_include" -then - openal_includes="$with_openal_include" -fi - -if test "$with_openal_lib" -then - openal_libraries="$with_openal_lib" -fi - - -# Set OPENAL_CFLAGS -if test "$openal_includes" -then - OPENAL_CFLAGS="-I$openal_includes" -fi - -# Set OPENAL_LIBS -if test "$openal_libraries" -then - OPENAL_LIBS="-L$openal_libraries" -fi -OPENAL_LIBS="$OPENAL_LIBS -l$openal_lib -l$alut_lib" - -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS="$CXXFLAGS $OPENAL_CFLAGS" - -AC_MSG_CHECKING(for OpenAL headers) -AC_EGREP_CPP( yo_openal, -[#include -#include -#ifdef AL_VERSION - yo_openal -#endif], - have_openal_headers="yes", - have_openal_headers="no" ) - -if test "$have_openal_headers" = "yes" -then - if test "$openal_includes" - then - AC_MSG_RESULT([$openal_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for OpenAL libraries) - -CPPFLAGS="$CXXFLAGS $OPENAL_LIBS" - -AC_TRY_LINK( , , have_openal_libraries="yes", have_openal_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_openal_libraries" = "yes" -then - if test "$openal_libraries" - then - AC_MSG_RESULT([$openal_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -openal_libraries="$openal_libraries" - -if test "$have_openal_headers" = "yes" \ - && test "$have_openal_libraries" = "yes" -then - have_openal="yes" -else - have_openal="no" -fi - -if test "$have_openal" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([OpenAL is needed to compile NeL (http://www.openal.org).]) -fi - -AC_SUBST(OPENAL_CFLAGS) -AC_SUBST(OPENAL_LIBS) -AC_SUBST([have_openal]) - -]) - - -# ========================================================================= -# AM_PATH_PYTHON : Python checking macros - -AC_DEFUN([AM_PATH_PYTHON], -[ python_version_required="$1" - -is_mandatory="$2" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the Python files location -AC_ARG_WITH( python, - [ --with-python= path to the Python prefix installation directory. - e.g. /usr/local], - [ PYTHON_PREFIX=$with_python ] -) - -AC_ARG_WITH( python-version, - [ --with-python-version= - Python version to use, e.g. 1.5], - [ PYTHON_VERSION=$with_python_version ] -) - -if test ! "$PYTHON_PREFIX" = "" -then - PATH="$PYTHON_PREFIX/bin:$PATH" -fi - -if test ! "$PYTHON_VERSION" = "" -then - PYTHON_EXEC="python$PYTHON_VERSION" -else - PYTHON_EXEC="python python2.1 python2.0 python1.5" -fi - -AC_PATH_PROGS(PYTHON, $PYTHON_EXEC, no, $PATH) - -if test "$PYTHON" != "no" -then - PYTHON_PREFIX=`$PYTHON -c 'import sys; print "%s" % (sys.prefix)'` - PYTHON_VERSION=`$PYTHON -c 'import sys; print "%s" % (sys.version[[:3]])'` - - is_python_version_enough=`expr $python_version_required \<= $PYTHON_VERSION` -fi - - -if test "$PYTHON" = "no" || test "$is_python_version_enough" != "1" -then - - if test "$is_mandatory" = "yes" - then - AC_MSG_ERROR([Python $python_version_required must be installed (http://www.python.org)]) - else - have_python="no" - fi - -else - - python_includes="$PYTHON_PREFIX/include/python$PYTHON_VERSION" - python_libraries="$PYTHON_PREFIX/lib/python$PYTHON_VERSION/config" - python_lib="python$PYTHON_VERSION" - - PYTHON_CFLAGS="-I$python_includes" - PYTHON_LIBS="-L$python_libraries -l$python_lib" - - _CPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CXXFLAGS ${PYTHON_CFLAGS}" - - # Test the headers - AC_MSG_CHECKING(for Python headers) - - AC_EGREP_CPP( yo_python, - [#include - yo_python - ], - have_python_headers="yes", - have_python_headers="no" ) - - if test "$have_python_headers" = "yes" - then - AC_MSG_RESULT([$python_includes]) - else - AC_MSG_RESULT(no) - fi - - # Test the libraries - AC_MSG_CHECKING(for Python libraries) - - CPPFLAGS="$CXXFLAGS $PYTHON_CFLAGS" - - AC_TRY_LINK( , , have_python_libraries="yes", have_python_libraries="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_python_libraries" = "yes" - then - if test "$python_libraries" - then - AC_MSG_RESULT([$python_libraries]) - else - AC_MSG_RESULT(yes) - fi - else - AC_MSG_RESULT(no) - fi - - if test "$have_python_headers" = "yes" \ - && test "$have_python_libraries" = "yes" - then - have_python="yes" - else - have_python="no" - fi - - if test "$have_python" = "no" -a "$is_mandatory" = "yes" - then - AC_MSG_ERROR([Python is needed to compile NeL (http://www.python.org).]) - fi - - AC_SUBST(PYTHON_CFLAGS) - AC_SUBST(PYTHON_LIBS) - -fi - -]) - -# ========================================================================= -# AM_PATH_MYSQL : MySQL library - -# AM_PATH_MYSQL([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) -# Test for MYSQL, and define MYSQL_CFLAGS and MYSQL_LIBS -# -AC_DEFUN([AM_PATH_MYSQL], -[# -# Get the cflags and libraries from the mysql_config script -# -AC_ARG_WITH(mysql-prefix,[ --with-mysql-prefix=PFX Prefix where MYSQL is installed (optional)], - mysql_prefix="$withval", mysql_prefix="") -AC_ARG_WITH(mysql-exec-prefix,[ --with-mysql-exec-prefix=PFX Exec prefix where MYSQL is installed (optional)], - mysql_exec_prefix="$withval", mysql_exec_prefix="") -AC_ARG_ENABLE(mysqltest, [ --disable-mysqltest Do not try to compile and run a test MYSQL program], - , enable_mysqltest=yes) - - if test x$mysql_exec_prefix != x ; then - mysql_args="$mysql_args --exec-prefix=$mysql_exec_prefix" - if test x${MYSQL_CONFIG+set} != xset ; then - MYSQL_CONFIG=$mysql_exec_prefix/bin/mysql_config - fi - fi - if test x$mysql_prefix != x ; then - mysql_args="$mysql_args --prefix=$mysql_prefix" - if test x${MYSQL_CONFIG+set} != xset ; then - MYSQL_CONFIG=$mysql_prefix/bin/mysql_config - fi - fi - - AC_REQUIRE([AC_CANONICAL_TARGET]) - AC_PATH_PROG(MYSQL_CONFIG, mysql_config, no) - min_mysql_version=ifelse([$1], ,0.11.0,$1) - AC_MSG_CHECKING(for MYSQL - version >= $min_mysql_version) - no_mysql="" - if test "$MYSQL_CONFIG" = "no" ; then - no_mysql=yes - else - MYSQL_CFLAGS=`$MYSQL_CONFIG $mysqlconf_args --cflags | sed -e "s/'//g"` - MYSQL_LIBS=`$MYSQL_CONFIG $mysqlconf_args --libs | sed -e "s/'//g"` - - mysql_major_version=`$MYSQL_CONFIG $mysql_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` - mysql_minor_version=`$MYSQL_CONFIG $mysql_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` - mysql_micro_version=`$MYSQL_CONFIG $mysql_config_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` - if test "x$enable_mysqltest" = "xyes" ; then - ac_save_CFLAGS="$CFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $MYSQL_CFLAGS" - LIBS="$LIBS $MYSQL_LIBS" -# -# Now check if the installed MYSQL is sufficiently new. (Also sanity -# checks the results of mysql_config to some extent -# - rm -f conf.mysqltest - AC_TRY_RUN([ -#include -#include -#include -#include - -char* -my_strdup (char *str) -{ - char *new_str; - - if (str) - { - new_str = (char *)malloc ((strlen (str) + 1) * sizeof(char)); - strcpy (new_str, str); - } - else - new_str = NULL; - - return new_str; -} - -int main (int argc, char *argv[]) -{ - int major, minor, micro; - char *tmp_version; - - /* This hangs on some systems (?) - system ("touch conf.mysqltest"); - */ - { FILE *fp = fopen("conf.mysqltest", "a"); if ( fp ) fclose(fp); } - - /* HP/UX 9 (%@#!) writes to sscanf strings */ - tmp_version = my_strdup("$min_mysql_version"); - if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { - printf("%s, bad version string\n", "$min_mysql_version"); - exit(1); - } - - if (($mysql_major_version > major) || - (($mysql_major_version == major) && ($mysql_minor_version > minor)) || - (($mysql_major_version == major) && ($mysql_minor_version == minor) && ($mysql_micro_version >= micro))) - { - return 0; - } - else - { - printf("\n*** 'mysql_config --version' returned %d.%d.%d, but the minimum version\n", $mysql_major_version, $mysql_minor_version, $mysql_micro_version); - printf("*** of MYSQL required is %d.%d.%d. If mysql_config is correct, then it is\n", major, minor, micro); - printf("*** best to upgrade to the required version.\n"); - printf("*** If mysql_config was wrong, set the environment variable MYSQL_CONFIG\n"); - printf("*** to point to the correct copy of mysql_config, and remove the file\n"); - printf("*** config.cache before re-running configure\n"); - return 1; - } -} - -],, no_mysql=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - fi - if test "x$no_mysql" = x ; then - AC_MSG_RESULT(yes) - ifelse([$2], , :, [$2]) - else - AC_MSG_RESULT(no) - if test "$MYSQL_CONFIG" = "no" ; then - echo "*** The mysql_config script installed by MYSQL could not be found" - echo "*** If MYSQL was installed in PREFIX, make sure PREFIX/bin is in" - echo "*** your path, or set the MYSQL_CONFIG environment variable to the" - echo "*** full path to mysql_config." - else - if test -f conf.mysqltest ; then - : - else - echo "*** Could not run MYSQL test program, checking why..." - CFLAGS="$CFLAGS $MYSQL_CFLAGS" - LIBS="$LIBS $MYSQL_LIBS" - AC_TRY_LINK([ -#include -#include - -int main(int argc, char *argv[]) -{ return 0; } -#undef main -#define main K_and_R_C_main -], [ return 0; ], - [ echo "*** The test program compiled, but did not run. This usually means" - echo "*** that the run-time linker is not finding MYSQL or finding the wrong" - echo "*** version of MYSQL. If it is not finding MYSQL, you'll need to set your" - echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" - echo "*** to the installed location Also, make sure you have run ldconfig if that" - echo "*** is required on your system" - echo "***" - echo "*** If you have an old version installed, it is best to remove it, although" - echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], - [ echo "*** The test program failed to compile or link. See the file config.log for the" - echo "*** exact error that occured. This usually means MYSQL was incorrectly installed" - echo "*** or that you have moved MYSQL since it was installed. In the latter case, you" - echo "*** may want to edit the mysql_config script: $MYSQL_CONFIG" ]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - fi - MYSQL_CFLAGS="" - MYSQL_LIBS="" - ifelse([$3], , :, [$3]) - fi - AC_SUBST(MYSQL_CFLAGS) - AC_SUBST(MYSQL_LIBS) - rm -f conf.mysqltest -]) - -# ========================================================================= -# AM_PATH_FMOD : FMOD checking macros - -AC_DEFUN([AM_PATH_FMOD], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the FMOD files location -AC_ARG_WITH( fmod, - [ --with-fmod= path to the FMOD install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( fmod-include, - [ --with-fmod-include= - path to the FMOD header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( fmod-lib, - [ --with-fmod-lib= - path to the FMOD library files directory. - e.g. /usr/local/lib]) - -fmod_lib="fmod" - -if test $with_fmod -then - fmod_includes="$with_fmod/include" - fmod_libraries="$with_fmod/lib" -fi - -if test "$with_fmod_include" -then - fmod_includes="$with_fmod_include" -fi - -if test "$with_fmod_lib" -then - fmod_libraries="$with_fmod_lib" -fi - - -# Set FMOD_CFLAGS -if test "$fmod_includes" -then - FMOD_CFLAGS="-I$fmod_includes" -fi - -# Set FMOD_LIBS -if test "$fmod_libraries" -then - FMOD_LIBS="-L$fmod_libraries" -fi -FMOD_LIBS="$FMOD_LIBS -l$fmod_lib" - -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS="$CXXFLAGS $FMOD_CFLAGS" - -AC_MSG_CHECKING(for FMOD headers) -AC_EGREP_CPP( yo_fmod, -[#include -#ifdef FMOD_VERSION - yo_fmod -#endif], - have_fmod_headers="yes", - have_fmod_headers="no" ) - -if test "$have_fmod_headers" = "yes" -then - if test "$fmod_includes" - then - AC_MSG_RESULT([$fmod_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for FMOD libraries) - -CPPFLAGS="$CXXFLAGS $FMOD_LIBS" - -AC_TRY_LINK( , , have_fmod_libraries="yes", have_fmod_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_fmod_libraries" = "yes" -then - if test "$fmod_libraries" - then - AC_MSG_RESULT([$fmod_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -fmod_libraries="$fmod_libraries" - -if test "$have_fmod_headers" = "yes" \ - && test "$have_fmod_libraries" = "yes" -then - have_fmod="yes" -else - have_fmod="no" -fi - -if test "$have_fmod" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([FMOD is needed to compile NeL (http://www.fmod.org).]) -fi - -AC_SUBST(FMOD_CFLAGS) -AC_SUBST(FMOD_LIBS) -AC_SUBST([have_fmod]) - -]) - -# ========================================================================= -# End of file - diff --git a/code/nel/autogen.sh b/code/nel/autogen.sh deleted file mode 100755 index b863e1067..000000000 --- a/code/nel/autogen.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - - -WANT_AUTOMAKE="1.6" - -case `uname -s` in -Darwin) - LIBTOOLIZE=glibtoolize - ;; -*) - LIBTOOLIZE=libtoolize - ;; -esac - -# be able to customize the aclocal (for example to add extra param) -if test "x$ACLOCAL" = "x" -then - ACLOCAL=aclocal -fi - -echo "Creating macros..." && \ -$ACLOCAL -I automacros/ && \ -echo "Creating library tools..." && \ -$LIBTOOLIZE --force && \ -echo "Creating header templates..." && \ -autoheader && \ -echo "Creating Makefile templates..." && \ -automake --gnu --add-missing && \ -echo "Creating 'configure'..." && \ -autoconf && \ -echo "" && \ -echo "Run: ./configure; make; make install" && \ -echo "" diff --git a/code/nel/automacros/gtk-2.0.m4 b/code/nel/automacros/gtk-2.0.m4 deleted file mode 100644 index 3deba01be..000000000 --- a/code/nel/automacros/gtk-2.0.m4 +++ /dev/null @@ -1,196 +0,0 @@ -# Configure paths for GTK+ -# Owen Taylor 1997-2001 - -dnl AM_PATH_GTK_2_0([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND [, MODULES]]]]) -dnl Test for GTK+, and define GTK_CFLAGS and GTK_LIBS, if gthread is specified in MODULES, -dnl pass to pkg-config -dnl -AC_DEFUN([AM_PATH_GTK_2_0], -[dnl -dnl Get the cflags and libraries from pkg-config -dnl -AC_ARG_ENABLE(gtktest, [ --disable-gtktest do not try to compile and run a test GTK+ program], - , enable_gtktest=yes) - - pkg_config_args=gtk+-2.0 - for module in . $4 - do - case "$module" in - gthread) - pkg_config_args="$pkg_config_args gthread-2.0" - ;; - esac - done - - no_gtk="" - - AC_PATH_PROG(PKG_CONFIG, pkg-config, no) - - if test x$PKG_CONFIG != xno ; then - if pkg-config --atleast-pkgconfig-version 0.7 ; then - : - else - echo "*** pkg-config too old; version 0.7 or better required." - no_gtk=yes - PKG_CONFIG=no - fi - else - no_gtk=yes - fi - - min_gtk_version=ifelse([$1], ,2.0.0,$1) - AC_MSG_CHECKING(for GTK+ - version >= $min_gtk_version) - - if test x$PKG_CONFIG != xno ; then - ## don't try to run the test against uninstalled libtool libs - if $PKG_CONFIG --uninstalled $pkg_config_args; then - echo "Will use uninstalled version of GTK+ found in PKG_CONFIG_PATH" - enable_gtktest=no - fi - - if $PKG_CONFIG --atleast-version $min_gtk_version $pkg_config_args; then - : - else - no_gtk=yes - fi - fi - - if test x"$no_gtk" = x ; then - GTK_CFLAGS=`$PKG_CONFIG $pkg_config_args --cflags` - GTK_LIBS=`$PKG_CONFIG $pkg_config_args --libs` - gtk_config_major_version=`$PKG_CONFIG --modversion gtk+-2.0 | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` - gtk_config_minor_version=`$PKG_CONFIG --modversion gtk+-2.0 | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` - gtk_config_micro_version=`$PKG_CONFIG --modversion gtk+-2.0 | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` - if test "x$enable_gtktest" = "xyes" ; then - ac_save_CFLAGS="$CFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $GTK_CFLAGS" - LIBS="$GTK_LIBS $LIBS" -dnl -dnl Now check if the installed GTK+ is sufficiently new. (Also sanity -dnl checks the results of pkg-config to some extent) -dnl - rm -f conf.gtktest - AC_TRY_RUN([ -#include -#include -#include - -int -main () -{ - int major, minor, micro; - char *tmp_version; - - system ("touch conf.gtktest"); - - /* HP/UX 9 (%@#!) writes to sscanf strings */ - tmp_version = g_strdup("$min_gtk_version"); - if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { - printf("%s, bad version string\n", "$min_gtk_version"); - exit(1); - } - - if ((gtk_major_version != $gtk_config_major_version) || - (gtk_minor_version != $gtk_config_minor_version) || - (gtk_micro_version != $gtk_config_micro_version)) - { - printf("\n*** 'pkg-config --modversion gtk+-2.0' returned %d.%d.%d, but GTK+ (%d.%d.%d)\n", - $gtk_config_major_version, $gtk_config_minor_version, $gtk_config_micro_version, - gtk_major_version, gtk_minor_version, gtk_micro_version); - printf ("*** was found! If pkg-config was correct, then it is best\n"); - printf ("*** to remove the old version of GTK+. You may also be able to fix the error\n"); - printf("*** by modifying your LD_LIBRARY_PATH enviroment variable, or by editing\n"); - printf("*** /etc/ld.so.conf. Make sure you have run ldconfig if that is\n"); - printf("*** required on your system.\n"); - printf("*** If pkg-config was wrong, set the environment variable PKG_CONFIG_PATH\n"); - printf("*** to point to the correct configuration files\n"); - } - else if ((gtk_major_version != GTK_MAJOR_VERSION) || - (gtk_minor_version != GTK_MINOR_VERSION) || - (gtk_micro_version != GTK_MICRO_VERSION)) - { - printf("*** GTK+ header files (version %d.%d.%d) do not match\n", - GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION); - printf("*** library (version %d.%d.%d)\n", - gtk_major_version, gtk_minor_version, gtk_micro_version); - } - else - { - if ((gtk_major_version > major) || - ((gtk_major_version == major) && (gtk_minor_version > minor)) || - ((gtk_major_version == major) && (gtk_minor_version == minor) && (gtk_micro_version >= micro))) - { - return 0; - } - else - { - printf("\n*** An old version of GTK+ (%d.%d.%d) was found.\n", - gtk_major_version, gtk_minor_version, gtk_micro_version); - printf("*** You need a version of GTK+ newer than %d.%d.%d. The latest version of\n", - major, minor, micro); - printf("*** GTK+ is always available from ftp://ftp.gtk.org.\n"); - printf("***\n"); - printf("*** If you have already installed a sufficiently new version, this error\n"); - printf("*** probably means that the wrong copy of the pkg-config shell script is\n"); - printf("*** being found. The easiest way to fix this is to remove the old version\n"); - printf("*** of GTK+, but you can also set the PKG_CONFIG environment to point to the\n"); - printf("*** correct copy of pkg-config. (In this case, you will have to\n"); - printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n"); - printf("*** so that the correct libraries are found at run-time))\n"); - } - } - return 1; -} -],, no_gtk=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - fi - if test "x$no_gtk" = x ; then - AC_MSG_RESULT(yes (version $gtk_config_major_version.$gtk_config_minor_version.$gtk_config_micro_version)) - ifelse([$2], , :, [$2]) - else - AC_MSG_RESULT(no) - if test "$PKG_CONFIG" = "no" ; then - echo "*** A new enough version of pkg-config was not found." - echo "*** See http://pkgconfig.sourceforge.net" - else - if test -f conf.gtktest ; then - : - else - echo "*** Could not run GTK+ test program, checking why..." - ac_save_CFLAGS="$CFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $GTK_CFLAGS" - LIBS="$LIBS $GTK_LIBS" - AC_TRY_LINK([ -#include -#include -], [ return ((gtk_major_version) || (gtk_minor_version) || (gtk_micro_version)); ], - [ echo "*** The test program compiled, but did not run. This usually means" - echo "*** that the run-time linker is not finding GTK+ or finding the wrong" - echo "*** version of GTK+. If it is not finding GTK+, you'll need to set your" - echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" - echo "*** to the installed location Also, make sure you have run ldconfig if that" - echo "*** is required on your system" - echo "***" - echo "*** If you have an old version installed, it is best to remove it, although" - echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH" ], - [ echo "*** The test program failed to compile or link. See the file config.log for the" - echo "*** exact error that occured. This usually means GTK+ is incorrectly installed."]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - fi - GTK_CFLAGS="" - GTK_LIBS="" - ifelse([$3], , :, [$3]) - fi - AC_SUBST(GTK_CFLAGS) - AC_SUBST(GTK_LIBS) - rm -f conf.gtktest -]) diff --git a/code/nel/automacros/ogg.m4 b/code/nel/automacros/ogg.m4 deleted file mode 100644 index 0e1f1abf5..000000000 --- a/code/nel/automacros/ogg.m4 +++ /dev/null @@ -1,102 +0,0 @@ -# Configure paths for libogg -# Jack Moffitt 10-21-2000 -# Shamelessly stolen from Owen Taylor and Manish Singh - -dnl XIPH_PATH_OGG([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -dnl Test for libogg, and define OGG_CFLAGS and OGG_LIBS -dnl -AC_DEFUN([XIPH_PATH_OGG], -[dnl -dnl Get the cflags and libraries -dnl -AC_ARG_WITH(ogg,[ --with-ogg=PFX Prefix where libogg is installed (optional)], ogg_prefix="$withval", ogg_prefix="") -AC_ARG_WITH(ogg-libraries,[ --with-ogg-libraries=DIR Directory where libogg library is installed (optional)], ogg_libraries="$withval", ogg_libraries="") -AC_ARG_WITH(ogg-includes,[ --with-ogg-includes=DIR Directory where libogg header files are installed (optional)], ogg_includes="$withval", ogg_includes="") -AC_ARG_ENABLE(oggtest, [ --disable-oggtest Do not try to compile and run a test Ogg program],, enable_oggtest=yes) - - if test "x$ogg_libraries" != "x" ; then - OGG_LIBS="-L$ogg_libraries" - elif test "x$ogg_prefix" != "x" ; then - OGG_LIBS="-L$ogg_prefix/lib" - elif test "x$prefix" != "xNONE" ; then - OGG_LIBS="-L$prefix/lib" - fi - - OGG_LIBS="$OGG_LIBS -logg" - - if test "x$ogg_includes" != "x" ; then - OGG_CFLAGS="-I$ogg_includes" - elif test "x$ogg_prefix" != "x" ; then - OGG_CFLAGS="-I$ogg_prefix/include" - elif test "x$prefix" != "xNONE"; then - OGG_CFLAGS="-I$prefix/include" - fi - - AC_MSG_CHECKING(for Ogg) - no_ogg="" - - - if test "x$enable_oggtest" = "xyes" ; then - ac_save_CFLAGS="$CFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $OGG_CFLAGS" - LIBS="$LIBS $OGG_LIBS" -dnl -dnl Now check if the installed Ogg is sufficiently new. -dnl - rm -f conf.oggtest - AC_TRY_RUN([ -#include -#include -#include -#include - -int main () -{ - system("touch conf.oggtest"); - return 0; -} - -],, no_ogg=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - - if test "x$no_ogg" = "x" ; then - AC_MSG_RESULT(yes) - ifelse([$1], , :, [$1]) - else - AC_MSG_RESULT(no) - if test -f conf.oggtest ; then - : - else - echo "*** Could not run Ogg test program, checking why..." - CFLAGS="$CFLAGS $OGG_CFLAGS" - LIBS="$LIBS $OGG_LIBS" - AC_TRY_LINK([ -#include -#include -], [ return 0; ], - [ echo "*** The test program compiled, but did not run. This usually means" - echo "*** that the run-time linker is not finding Ogg or finding the wrong" - echo "*** version of Ogg. If it is not finding Ogg, you'll need to set your" - echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" - echo "*** to the installed location Also, make sure you have run ldconfig if that" - echo "*** is required on your system" - echo "***" - echo "*** If you have an old version installed, it is best to remove it, although" - echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], - [ echo "*** The test program failed to compile or link. See the file config.log for the" - echo "*** exact error that occured. This usually means Ogg was incorrectly installed" - echo "*** or that you have moved Ogg since it was installed." ]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - OGG_CFLAGS="" - OGG_LIBS="" - ifelse([$2], , :, [$2]) - fi - AC_SUBST(OGG_CFLAGS) - AC_SUBST(OGG_LIBS) - rm -f conf.oggtest -]) diff --git a/code/nel/automacros/pkg.m4 b/code/nel/automacros/pkg.m4 deleted file mode 100644 index c29b6c057..000000000 --- a/code/nel/automacros/pkg.m4 +++ /dev/null @@ -1,157 +0,0 @@ -# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- -# -# Copyright © 2004 Scott James Remnant . -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# PKG_PROG_PKG_CONFIG([MIN-VERSION]) -# ---------------------------------- -AC_DEFUN([PKG_PROG_PKG_CONFIG], -[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) -m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) -AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl -if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then - AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) -fi -if test -n "$PKG_CONFIG"; then - _pkg_min_version=m4_default([$1], [0.9.0]) - AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) - if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no]) - PKG_CONFIG="" - fi - -fi[]dnl -])# PKG_PROG_PKG_CONFIG - -# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) -# -# Check to see whether a particular set of modules exists. Similar -# to PKG_CHECK_MODULES(), but does not set variables or print errors. -# -# -# Similar to PKG_CHECK_MODULES, make sure that the first instance of -# this or PKG_CHECK_MODULES is called, or make sure to call -# PKG_CHECK_EXISTS manually -# -------------------------------------------------------------- -AC_DEFUN([PKG_CHECK_EXISTS], -[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl -if test -n "$PKG_CONFIG" && \ - AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then - m4_ifval([$2], [$2], [:]) -m4_ifvaln([$3], [else - $3])dnl -fi]) - - -# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) -# --------------------------------------------- -m4_define([_PKG_CONFIG], -[if test -n "$PKG_CONFIG"; then - if test -n "$$1"; then - pkg_cv_[]$1="$$1" - else - PKG_CHECK_EXISTS([$3], - [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], - [pkg_failed=yes]) - fi -else - pkg_failed=untried -fi[]dnl -])# _PKG_CONFIG - -# _PKG_SHORT_ERRORS_SUPPORTED -# ----------------------------- -AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], -[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi[]dnl -])# _PKG_SHORT_ERRORS_SUPPORTED - - -# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], -# [ACTION-IF-NOT-FOUND]) -# -# -# Note that if there is a possibility the first call to -# PKG_CHECK_MODULES might not happen, you should be sure to include an -# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac -# -# -# -------------------------------------------------------------- -AC_DEFUN([PKG_CHECK_MODULES], -[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl -AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl -AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl - -pkg_failed=no -AC_MSG_CHECKING([for $1]) - -_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) -_PKG_CONFIG([$1][_LIBS], [libs], [$2]) - -m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS -and $1[]_LIBS to avoid the need to call pkg-config. -See the pkg-config man page for more details.]) - -if test $pkg_failed = yes; then - _PKG_SHORT_ERRORS_SUPPORTED - if test $_pkg_short_errors_supported = yes; then - $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"` - else - $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` - fi - # Put the nasty error message in config.log where it belongs - echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD - - ifelse([$4], , [AC_MSG_ERROR(dnl -[Package requirements ($2) were not met: - -$$1_PKG_ERRORS - -Consider adjusting the PKG_CONFIG_PATH environment variable if you -installed software in a non-standard prefix. - -_PKG_TEXT -])], - [AC_MSG_RESULT([no]) - $4]) -elif test $pkg_failed = untried; then - ifelse([$4], , [AC_MSG_FAILURE(dnl -[The pkg-config script could not be found or is too old. Make sure it -is in your PATH or set the PKG_CONFIG environment variable to the full -path to pkg-config. - -_PKG_TEXT - -To get pkg-config, see .])], - [$4]) -else - $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS - $1[]_LIBS=$pkg_cv_[]$1[]_LIBS - AC_MSG_RESULT([yes]) - ifelse([$3], , :, [$3]) -fi[]dnl -])# PKG_CHECK_MODULES diff --git a/code/nel/automacros/vorbis.m4 b/code/nel/automacros/vorbis.m4 deleted file mode 100644 index 300cc6c7d..000000000 --- a/code/nel/automacros/vorbis.m4 +++ /dev/null @@ -1,122 +0,0 @@ -# Configure paths for libvorbis -# Jack Moffitt 10-21-2000 -# Shamelessly stolen from Owen Taylor and Manish Singh -# thomasvs added check for vorbis_bitrate_addblock which is new in rc3 - -dnl XIPH_PATH_VORBIS([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -dnl Test for libvorbis, and define VORBIS_CFLAGS and VORBIS_LIBS -dnl -AC_DEFUN([XIPH_PATH_VORBIS], -[dnl -dnl Get the cflags and libraries -dnl -AC_ARG_WITH(vorbis,[ --with-vorbis=PFX Prefix where libvorbis is installed (optional)], vorbis_prefix="$withval", vorbis_prefix="") -AC_ARG_WITH(vorbis-libraries,[ --with-vorbis-libraries=DIR Directory where libvorbis library is installed (optional)], vorbis_libraries="$withval", vorbis_libraries="") -AC_ARG_WITH(vorbis-includes,[ --with-vorbis-includes=DIR Directory where libvorbis header files are installed (optional)], vorbis_includes="$withval", vorbis_includes="") -AC_ARG_ENABLE(vorbistest, [ --disable-vorbistest Do not try to compile and run a test Vorbis program],, enable_vorbistest=yes) - - if test "x$vorbis_libraries" != "x" ; then - VORBIS_LIBS="-L$vorbis_libraries" - elif test "x$vorbis_prefix" != "x" ; then - VORBIS_LIBS="-L$vorbis_prefix/lib" - elif test "x$prefix" != "xNONE"; then - VORBIS_LIBS="-L$prefix/lib" - fi - - VORBIS_LIBS="$VORBIS_LIBS -lvorbis -lm" - VORBISFILE_LIBS="-lvorbisfile" - VORBISENC_LIBS="-lvorbisenc" - - if test "x$vorbis_includes" != "x" ; then - VORBIS_CFLAGS="-I$vorbis_includes" - elif test "x$vorbis_prefix" != "x" ; then - VORBIS_CFLAGS="-I$vorbis_prefix/include" - elif test "x$prefix" != "xNONE"; then - VORBIS_CFLAGS="-I$prefix/include" - fi - - - AC_MSG_CHECKING(for Vorbis) - no_vorbis="" - - - if test "x$enable_vorbistest" = "xyes" ; then - ac_save_CFLAGS="$CFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $VORBIS_CFLAGS $OGG_CFLAGS" - LIBS="$LIBS $VORBIS_LIBS $VORBISENC_LIBS $OGG_LIBS" -dnl -dnl Now check if the installed Vorbis is sufficiently new. -dnl - rm -f conf.vorbistest - AC_TRY_RUN([ -#include -#include -#include -#include -#include - -int main () -{ - vorbis_block vb; - vorbis_dsp_state vd; - vorbis_info vi; - - vorbis_info_init (&vi); - vorbis_encode_init (&vi, 2, 44100, -1, 128000, -1); - vorbis_analysis_init (&vd, &vi); - vorbis_block_init (&vd, &vb); - /* this function was added in 1.0rc3, so this is what we're testing for */ - vorbis_bitrate_addblock (&vb); - - system("touch conf.vorbistest"); - return 0; -} - -],, no_vorbis=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - - if test "x$no_vorbis" = "x" ; then - AC_MSG_RESULT(yes) - ifelse([$1], , :, [$1]) - else - AC_MSG_RESULT(no) - if test -f conf.vorbistest ; then - : - else - echo "*** Could not run Vorbis test program, checking why..." - CFLAGS="$CFLAGS $VORBIS_CFLAGS" - LIBS="$LIBS $VORBIS_LIBS $OGG_LIBS" - AC_TRY_LINK([ -#include -#include -], [ return 0; ], - [ echo "*** The test program compiled, but did not run. This usually means" - echo "*** that the run-time linker is not finding Vorbis or finding the wrong" - echo "*** version of Vorbis. If it is not finding Vorbis, you'll need to set your" - echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" - echo "*** to the installed location Also, make sure you have run ldconfig if that" - echo "*** is required on your system" - echo "***" - echo "*** If you have an old version installed, it is best to remove it, although" - echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], - [ echo "*** The test program failed to compile or link. See the file config.log for the" - echo "*** exact error that occured. This usually means Vorbis was incorrectly installed" - echo "*** or that you have moved Vorbis since it was installed." ]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - VORBIS_CFLAGS="" - VORBIS_LIBS="" - VORBISFILE_LIBS="" - VORBISENC_LIBS="" - ifelse([$2], , :, [$2]) - fi - AC_SUBST(VORBIS_CFLAGS) - AC_SUBST(VORBIS_LIBS) - AC_SUBST(VORBISFILE_LIBS) - AC_SUBST(VORBISENC_LIBS) - rm -f conf.vorbistest -]) diff --git a/code/nel/configure.ac b/code/nel/configure.ac deleted file mode 100644 index 84edccc05..000000000 --- a/code/nel/configure.ac +++ /dev/null @@ -1,599 +0,0 @@ -# ==================================================================== -# Configuration script for NeL -# ==================================================================== -# -# $Id: configure.ac,v 1.8 2005/04/14 15:54:32 cado Exp $ -# - -# ==================================================================== -# Process this file with autoconf to produce a configure script. -# ==================================================================== - -# If you want to change the version, must must change AC_INIT -# *and* AC_SUBST(LIBTOOL_VERSION) - -AC_PREREQ(2.57) -AC_INIT([nel],[0.8.0],[nel-all@nevrax.org]) -AM_INIT_AUTOMAKE([tar-ustar]) - -AC_CONFIG_SRCDIR(include/nel/misc/types_nl.h) -AM_CONFIG_HEADER(include/nelconfig.h) - -AC_SUBST(LIBTOOL_VERSION, [0:7:0]) - -# Checks for programs. -AC_CANONICAL_HOST -AC_PROG_CXX -AC_PROG_CPP -AC_PROG_YACC -AC_PROG_LEX -AC_PROG_INSTALL -AC_PROG_LN_S -AC_PROG_MAKE_SET -AC_PROG_LIBTOOL -AM_PROG_LIBTOOL -AM_SANITY_CHECK - -AC_SYS_LARGEFILE - -AM_MAINTAINER_MODE - - -# Template needed to generate the nelconfig.h.in -AH_TEMPLATE([NEL_DEFAULT_DISPLAYER],[Define to 1 if you want log on standard output]) -AH_TEMPLATE([NEL_LOG_IN_FILE],[Define to 1 if you want a debug log.log file in the current directory]) -AH_TEMPLATE([HAVE_X86],[Define to 1 if you are on a INTEL compatible processor]) -AH_TEMPLATE([HAVE_X86_64],[Define to 1 if you are on AMD opteron 64bits processor]) -AH_TEMPLATE([NL_USE_GTK], [Define to 1 if you want GTK support]) - -# Get host type info -if test "$host_cpu" = "i386" -o "$host_cpu" = "i486" -o "$host_cpu" = "i586" \ - -o "$host_cpu" = "i686" -o "$host_cpu" = "i786" -o "$host_cpu" = "x86_64" -then - AC_DEFINE([HAVE_X86]) -fi - -if test "$host_cpu" = "x86_64" -then - AC_DEFINE([HAVE_X86_64]) -fi - -# The following hack should ensure that configure doesnt add optimizing -# or debugging flags to CFLAGS or CXXFLAGS -CXXFLAGS="$CXXFLAGS -fno-strict-aliasing -ftemplate-depth-24 -fno-stack-protector" - - -# ==================================================================== -# Checks NeL modules (net, 3d) to install / Disable -# ==================================================================== - -# The misc is mandatory, it is use by the other modules. - -NEL_SUBDIRS="misc" - -# NeL libraries that are enabled by default - -# Network library -AC_ARG_ENABLE([net], - AC_HELP_STRING([--disable-net], - [disable compilation and install of NeL Network]), - [], - [enable_net=yes]) - -if test "$enable_net" = "no" -then - AC_MSG_RESULT([disable NeL Network]) -else - NEL_SUBDIRS="$NEL_SUBDIRS net" -fi - -# 3D library -AC_ARG_ENABLE([3d], - AC_HELP_STRING([--disable-3d], - [disable compilation and install of NeL 3D]), - [], - [enable_3d=yes]) - -if test "$enable_3d" = "no" -then - AC_MSG_RESULT([disable NeL 3D]) -else - NEL_SUBDIRS="$NEL_SUBDIRS 3d" -fi - -# PACS library -AC_ARG_ENABLE([pacs], - AC_HELP_STRING([--disable-pacs], - [disable compilation and install of NeL PACS]), - [], - [enable_pacs=yes]) - -if test "$enable_pacs" = "no" -then - AC_MSG_RESULT([disable NeL PACS]) -else - NEL_SUBDIRS="$NEL_SUBDIRS pacs" -fi - -# Georges library -AC_ARG_ENABLE([georges], - AC_HELP_STRING([--disable-georges], - [disable compilation and install of NeL Georges]), - [], - [enable_georges=yes]) - -if test "$enable_georges" = "no" -then - AC_MSG_RESULT([disable NeL Georges]) -else - NEL_SUBDIRS="$NEL_SUBDIRS georges" -fi - -# Ligo library -AC_ARG_ENABLE([ligo], - AC_HELP_STRING([--disable-ligo], - [disable compilation and install of NeL Ligo]), - [], - [enable_ligo=yes]) - -if test "$enable_ligo" = "no" -then - AC_MSG_RESULT([disable NeL Ligo]) -else - NEL_SUBDIRS="$NEL_SUBDIRS ligo" -fi - - -# NeL libraries that are disabled by default - -# Sound library -AC_ARG_ENABLE([sound], - AC_HELP_STRING([--enable-sound], - [enable compilation and install of NeL Sound]), - [], - [enable_sound=no]) - -if test "$enable_sound" = "yes" -then - AC_MSG_RESULT([enable NeL Sound]) - NEL_SUBDIRS="$NEL_SUBDIRS sound" -fi - -# CEGUI Renderer library -AC_ARG_ENABLE([cegui], - AC_HELP_STRING([--enable-cegui], - [enable compilation and install of NeL CEGUI Renderer]), - [], - [enable_cegui=no]) - -CEGUI_SUBDIR="" -if test "$enable_cegui" = "yes" -then - AC_MSG_RESULT([enable NeL CEGUI Renderer]) - NEL_SUBDIRS="$NEL_SUBDIRS cegui" - CEGUI_SUBDIR="cegui" -fi - -# Unit Tests -AC_ARG_ENABLE([tests], - AC_HELP_STRING([--enable-tests], - [enable unit tests of NeL]), - [], - [enable_tests=no]) - -if test "$enable_tests" = "yes" -then - AC_MSG_RESULT([enable NeL Unit Tests]) -fi - -# Code Coverage -AC_ARG_ENABLE([coverage], - AC_HELP_STRING([--enable-coverage], - [enable code coverage generation]), - [] - [enable_coverage=no]) - -if test "$enable_coverage" = "yes" -then - AC_MSG_RESULT([enable Code Coverage generation]) - - CXXFLAGS="$CXXFLAGS -fprofile-arcs -ftest-coverage" -fi - -# Enable/disable samples compilation. -AC_ARG_ENABLE([samples], - AC_HELP_STRING([--disable-samples], - [disable sample code]), - [], - [enable_samples="yes"]) - -if test "$enable_samples" = "no" -then - AC_MSG_RESULT([disable sample code.]) - SAMPLE_SUBDIR="" -else - SAMPLE_SUBDIR="samples" -fi - -# Enable/disable tools compilation. -AC_ARG_ENABLE([tools], - AC_HELP_STRING([--disable-tools], - [disable tools code]), - [], - [enable_tools="yes"]) - -if test "$enable_tools" = "no" -then - AC_MSG_RESULT([disable tools code.]) - TOOLS_SUBDIR="" -else - TOOLS_SUBDIR="tools" -fi - -AC_SUBST([enable_net]) -AC_SUBST([enable_3d]) -AC_SUBST([enable_pacs]) -AC_SUBST([enable_sound]) -AC_SUBST([enable_georges]) -AC_SUBST([enable_ligo]) -AC_SUBST([enable_cegui]) - -AC_SUBST([NEL_SUBDIRS]) -AC_SUBST([SAMPLE_SUBDIR]) -AC_SUBST([TOOLS_SUBDIR]) -AC_SUBST([CEGUI_SUBDIR]) - -# ==================================================================== -# Checks for programs. -# ==================================================================== - -# ==================================================================== -# Configure Settings -# ==================================================================== - -# Disable the static linking by default -# AC_DISABLE_STATIC - -# Use C++ compiler as a default for the compilation tests. -AC_LANG([C++]) - - -# ==================================================================== -# Debug/optimized compilation mode -# ==================================================================== - -AM_NEL_DEBUG - -AC_ARG_WITH([logging], - AC_HELP_STRING([--without-logging], - [be silent on stdout and in no log.log]), - [], - [with_logging=yes]) - -if test "$with_logging" = "yes" -then - AC_DEFINE([NEL_DEFAULT_DISPLAYER], 1) - AC_DEFINE([NEL_LOG_IN_FILE], 1) -fi - -# ==================================================================== -# Checks for typedefs, structures, and compiler characteristics. -# ==================================================================== - -# Test endianness -AC_C_BIGENDIAN - -# Supress GCC "multi-character character constant" warnings. -if test "$ac_cv_cxx_compiler_gnu" = "yes"; -then - if test "$with_debug" = "yes" - then - # - # When debugging variables are declared for the sole purpose of - # inspecting their content with a debugger. They are not used - # in the code itself and this is legitimate, hence the -Wno-unused - # - CXXFLAGS="$CXXFLAGS -Wno-unused" - fi -fi - -# Add some common define -if test "$ac_cv_cxx_compiler_gnu" = "yes"; -then - CXXFLAGS="$CXXFLAGS -D_REENTRANT -Wall -ansi -W -Wpointer-arith -Wsign-compare -Wno-deprecated-declarations -Wno-multichar -Wno-long-long -Wno-unused" -fi - - -# ==================================================================== -# Checks for header and lib files. -# ==================================================================== - -AC_FUNC_ALLOCA -AC_HEADER_DIRENT -AC_HEADER_STDC -AC_HEADER_TIME -AC_CHECK_HEADERS([arpa/inet.h fcntl.h float.h malloc.h netdb.h netinet/in.h stddef.h stdlib.h string.h sys/ioctl.h sys/socket.h unistd.h sys/time.h]) -AC_CHECK_LIB([pthread], [pthread_create]) -AC_CHECK_LIB([dl], [dlopen]) - - -# ==================================================================== -# Checks for typedefs, structures, and compiler characteristics. -# ==================================================================== - -AC_HEADER_STDBOOL -AC_C_CONST -AC_C_INLINE -AC_TYPE_SIZE_T -AC_HEADER_TIME -AC_STRUCT_TM -AC_C_VOLATILE -AC_CHECK_TYPES([ptrdiff_t]) -AC_CHECK_TYPES([size_t]) -AC_CHECK_TYPES([uintptr_t]) - - -# ==================================================================== -# Checks for library functions. -# ==================================================================== - -AC_FUNC_CLOSEDIR_VOID -AC_FUNC_ERROR_AT_LINE -AC_PROG_GCC_TRADITIONAL -AC_FUNC_MALLOC -AC_FUNC_MEMCMP -AC_FUNC_REALLOC -AC_FUNC_SELECT_ARGTYPES -AC_TYPE_SIGNAL -AC_FUNC_STAT -AC_FUNC_STRFTIME -AC_FUNC_FORK -AC_FUNC_VPRINTF -AC_CHECK_FUNCS([floor getcwd gethostbyaddr gethostbyname gethostname gettimeofday inet_ntoa memmove memset mkdir pow select socket sqrt strcasecmp strchr strdup strerror strrchr strstr strtoul sys/time.h]) - - -# ==================================================================== -# X11 - -AC_PATH_X - -if test ! "$no_x" = "yes" -then - if test ! X"$x_libraries" = X - then - LIBS="$LIBS -L$x_libraries" - fi - - if test ! X"$x_includes" = X - then - CXXFLAGS="$CXXFLAGS -I$x_includes" - fi -else - if test "$enable_3d" = "yes" - then - AC_MSG_ERROR([X11 must be installed for NeL 3d library, use --disable-3d if you don't need NeL 3d library]) - fi -fi - -# ==================================================================== -# LibXML - -# Use C compiler as a default for the libxml tests. -AC_LANG([C]) - -AM_PATH_XML2([2.0.0], [], [AC_MSG_ERROR([libxml2 must be installed])]) - -CXXFLAGS="$CXXFLAGS $XML_CFLAGS $XML_CPPFLAGS" - -LIBS="$LIBS $XML_LIBS" - -# Use C++ compiler as a default for the compilation tests. -AC_LANG([C++]) - -# ==================================================================== -# libpng - -AC_CHECK_HEADER(png.h, [], AC_MSG_ERROR([libpng must be installed])) - -# ==================================================================== -# libjpeg - -AC_CHECK_HEADER(jpeglib.h, [], AC_MSG_ERROR([libjpeg must be installed])) - -# ==================================================================== -# Checks for libraries. -# ==================================================================== - -# ==================================================================== -# GTK 2.0+ - -AC_ARG_WITH([gtk], - AC_HELP_STRING([--with-gtk], - [add GTK dependent code like GTK displayer]), - [], - [with_gtk=no]) - -if test "$with_gtk" = "yes" -then - AC_LANG([C]) - - AM_PATH_GTK_2_0([2.0.0], - CXXFLAGS="$CXXFLAGS $GTK_CFLAGS" - LIBS="$LIBS $GTK_LIBS" - AC_DEFINE(NL_USE_GTK, [], [Undef if you don't want to use anything GTK based like the GTK Displayer] -) - ) - - AC_LANG([C++]) - - AC_SUBST([with_gtk]) -fi - -# ==================================================================== -# CEGUI - -if test "$enable_cegui" = "yes" -then - PKG_CHECK_MODULES(CEGUI, CEGUI >= 0.4, - [], - [ - AC_MSG_ERROR([Couldn't find CEGUI or tests failed: -$CEGUI_PKG_ERRORS -Please go to http://crayzedsgui.sourceforge.net to get the latest, or check -config.log to see why the tests failed, and fix it.]) - ]) -fi - -# ==================================================================== -# FreeType 2 - -AM_PATH_FREETYPE($enable_3d) - - -# ==================================================================== -# OpenGL - -AM_PATH_OPENGL($enable_3d) - - -# ==================================================================== -# Check for XF86VidMode extension (-lXxf86vm) - -AM_PATH_XF86VIDMODE - - -# ==================================================================== -# FMOD, OpenAL - -if test "$enable_sound" = "yes" -then - AM_PATH_FMOD("no") - AM_PATH_OPENAL("no") - if test "$have_fmod" = "no" -a "$have_openal" = "no" - then - AC_MSG_ERROR([Either FMod or OpenAL must be installed to use sound.]) - fi - if test "$have_fmod" = "yes" - then - SOUND_SUBDIRS="fmod" - else - SOUND_SUBDIRS="" - fi - if test "$have_openal" = "yes" - then - SOUND_SUBDIRS="$SOUND_SUBDIRS openal" - - XIPH_PATH_OGG([], AC_MSG_ERROR([Driver OpenAL Requires libogg!])) - XIPH_PATH_VORBIS([], AC_MSG_ERROR([Driver OpenAL Requires libvorbis!])) - fi - AC_SUBST([SOUND_SUBDIRS]) -fi - - -# ==================================================================== -# CppTest - -#AM_PATH_CPPTEST($enable_tests) - -# ==================================================================== -# Arrange for the include directory to be in the search path even when -# build is done outside the source tree -# Put the nelconfig.h define -CXXFLAGS="$CXXFLAGS -I\${top_srcdir}/include -DHAVE_NELCONFIG_H" - -# ==================================================================== -# Checks for library functions. -# ==================================================================== - - -# ==================================================================== -# Output files to generate. -# ==================================================================== - -AC_CONFIG_FILES([Makefile \ - include/Makefile \ - include/nel/Makefile \ - include/nel/ligo/Makefile \ - include/nel/misc/Makefile \ - include/nel/net/Makefile \ - include/nel/3d/Makefile \ - include/nel/pacs/Makefile \ - include/nel/sound/Makefile \ - include/nel/georges/Makefile \ - include/nel/cegui/Makefile \ - src/Makefile \ - src/misc/Makefile \ - src/misc/nel-misc.pc \ - src/misc/config_file/Makefile \ - src/net/Makefile \ - src/3d/Makefile \ - src/3d/nel-3d.pc \ - src/3d/driver/Makefile \ - src/3d/driver/opengl/Makefile \ - src/3d/driver/opengl/nel-driverogl.pc \ - src/pacs/Makefile \ - src/sound/Makefile \ - src/sound/driver/Makefile \ - src/sound/driver/fmod/Makefile \ - src/sound/driver/openal/Makefile \ - src/georges/Makefile \ - src/ligo/Makefile \ - src/cegui/Makefile \ - tools/Makefile \ - tools/3d/Makefile \ - tools/3d/build_coarse_mesh/Makefile \ - tools/3d/build_far_bank/Makefile \ - tools/3d/build_smallbank/Makefile \ - tools/3d/ig_lighter/Makefile \ - tools/3d/ig_lighter_lib/Makefile \ - tools/3d/panoply_maker/Makefile \ - tools/3d/zone_dependencies/Makefile \ - tools/3d/zone_ig_lighter/Makefile \ - tools/3d/zone_lib/Makefile \ - tools/3d/zone_lighter/Makefile \ - tools/3d/zone_welder/Makefile \ - tools/misc/Makefile \ - tools/misc/bnp_make/Makefile \ - tools/misc/disp_sheet_id/Makefile \ - tools/misc/make_sheet_id/Makefile \ - tools/misc/xml_packer/Makefile \ - tools/pacs/Makefile \ - tools/pacs/build_ig_boxes/Makefile \ - tools/pacs/build_indoor_rbank/Makefile \ - tools/pacs/build_rbank/Makefile \ - samples/Makefile \ - samples/sound_sources/Makefile \ - samples/pacs/Makefile \ - samples/georges/Makefile \ - samples/3d/Makefile \ - samples/3d/font/Makefile \ - samples/3d/cluster_viewer/Makefile \ - samples/3d/cluster_viewer/shapes/Makefile \ - samples/3d/cluster_viewer/groups/Makefile \ - samples/3d/cluster_viewer/fonts/Makefile \ - samples/3d/cegui/Makefile \ - samples/misc/Makefile \ - samples/misc/command/Makefile \ - samples/misc/configfile/Makefile \ - samples/misc/debug/Makefile \ - samples/misc/i18n/Makefile \ - samples/misc/log/Makefile \ - samples/misc/strings/Makefile \ - samples/misc/types_check/Makefile \ - samples/net/Makefile \ - samples/net/chat/Makefile \ - samples/net/udp/Makefile \ - samples/net/login_system/Makefile \ - nel-config - -]) -AC_OUTPUT - -# samples/net/class_transport/Makefile \ -# tools/nel_unit_test/Makefile \ -# tools/nel_unit_test/misc_ut/Makefile \ -# tools/nel_unit_test/ligo_ut/Makefile \ -# tools/nel_unit_test/net_ut/Makefile \ -# tools/nel_unit_test/net_ut/net_service_lib_test/Makefile \ -# tools/nel_unit_test/net_ut/net_module_lib_test/Makefile \ -# End of configure.in diff --git a/code/nel/include/Makefile.am b/code/nel/include/Makefile.am deleted file mode 100644 index 193d2e405..000000000 --- a/code/nel/include/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = nel - -pkginclude_HEADERS = nelconfig.h - -# End of Makefile.am diff --git a/code/nel/include/nel/3d/Makefile.am b/code/nel/include/nel/3d/Makefile.am deleted file mode 100644 index 8118d8348..000000000 --- a/code/nel/include/nel/3d/Makefile.am +++ /dev/null @@ -1,343 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -includedir = ${prefix}/include/nel/3d - -include_HEADERS = \ -animatable.h \ -animated_lightmap.h \ -animated_material.h \ -animated_morph.h \ -animated_value.h \ -animation.h \ -animation_optimizer.h \ -animation_playlist.h \ -animation_set.h \ -animation_set_user.h \ -animation_time.h \ -anim_ctrl.h \ -anim_detail_trav.h \ -async_file_manager_3d.h \ -async_texture_block.h \ -async_texture_manager.h \ -bezier_patch.h \ -bloom_effect.h \ -bone.h \ -bsp_tree.h \ -camera_col.h \ -camera.h \ -channel_mixer.h \ -clip_trav.h \ -cloud.h \ -cloud_scape.h \ -cloud_scape_user.h \ -cluster.h \ -coarse_mesh_build.h \ -coarse_mesh_manager.h \ -computed_string.h \ -cube_grid.h \ -cube_map_builder.h \ -debug_vb.h \ -deform_2d.h \ -driver.h \ -driver_material_inline.h \ -driver_user.h \ -dru.h \ -event_mouse_listener.h \ -fasthls_modifier.h \ -fast_ptr_list.h \ -flare_model.h \ -flare_shape.h \ -font_generator.h \ -font_manager.h \ -frustum.h \ -heat_haze.h \ -height_map.h \ -hls_color_texture.h \ -hls_texture_bank.h \ -hls_texture_manager.h \ -hrc_trav.h \ -ig_surface_light_build.h \ -ig_surface_light.h \ -index_buffer.h \ -init_3d.h \ -instance_group_user.h \ -instance_lighter.h \ -key.h \ -landscape_collision_grid.h \ -landscape_def.h \ -landscape_face_vector_manager.h \ -landscape.h \ -landscapeig_manager.h \ -landscape_model.h \ -landscape_profile.h \ -landscape_user.h \ -landscapevb_allocator.h \ -landscapevb_info.h \ -landscape_vegetable_block.h \ -layered_ordering_table.h \ -light_contribution.h \ -light.h \ -light_influence_interpolator.h \ -lighting_manager.h \ -light_trav.h \ -light_user.h \ -load_balancing_trav.h \ -lod_character_builder.h \ -lod_character_instance.h \ -lod_character_manager.h \ -lod_character_shape_bank.h \ -lod_character_shape.h \ -lod_character_texture.h \ -logic_info.h \ -material.h \ -matrix_3x4.h \ -mesh_base.h \ -mesh_base_instance.h \ -mesh_blender.h \ -mesh_block_manager.h \ -mesh_geom.h \ -mesh.h \ -mesh_instance.h \ -mesh_morpher.h \ -mesh_mrm.h \ -mesh_mrm_instance.h \ -mesh_mrm_skinned.h \ -mesh_mrm_skinned_instance.h \ -mesh_multi_lod.h \ -mesh_multi_lod_instance.h \ -mesh_vertex_program.h \ -meshvp_per_pixel_light.h \ -meshvp_wind_tree.h \ -mini_col.h \ -motion_blur.h \ -mrm_builder.h \ -mrm_internal.h \ -mrm_level_detail.h \ -mrm_mesh.h \ -mrm_parameters.h \ -nelu.h \ -noise_3d.h \ -occlusion_query.h \ -ordering_table.h \ -packed_world.h \ -packed_zone.h \ -particle_system.h \ -particle_system_manager.h \ -particle_system_model.h \ -particle_system_process.h \ -particle_system_shape.h \ -patchdlm_context.h \ -patch.h \ -patch_rdr_pass.h \ -patchuv_locator.h \ -play_list_manager.h \ -play_list_manager_user.h \ -play_list_user.h \ -point_light.h \ -point_light_influence.h \ -point_light_model.h \ -point_light_named_array.h \ -point_light_named.h \ -portal.h \ -primitive_profile.h \ -ps_allocator.h \ -ps_attrib.h \ -ps_attrib_maker_bin_op.h \ -ps_attrib_maker_bin_op_inline.h \ -ps_attrib_maker.h \ -ps_attrib_maker_helper.h \ -ps_attrib_maker_iterators.h \ -ps_attrib_maker_template.h \ -ps_color.h \ -ps_direction.h \ -ps_dot.h \ -ps_edit.h \ -ps_emitter.h \ -ps_face.h \ -ps_face_look_at.h \ -ps_fan_light.h \ -ps_float.h \ -ps_force.h \ -ps_int.h \ -ps_iterator.h \ -ps_light.h \ -ps_located.h \ -ps_lod.h \ -ps_macro.h \ -ps_mesh.h \ -ps_misc.h \ -ps_particle2.h \ -ps_particle_basic.h \ -ps_particle.h \ -ps_plane_basis.h \ -ps_plane_basis_maker.h \ -ps_quad.h \ -ps_register_color_attribs.h \ -ps_register_float_attribs.h \ -ps_register_int_attribs.h \ -ps_register_plane_basis_attribs.h \ -ps_ribbon_base.h \ -ps_ribbon.h \ -ps_ribbon_look_at.h \ -ps_shockwave.h \ -ps_sound.h \ -ps_spawn_info.h \ -ps_tail_dot.h \ -ps_util.h \ -ps_zone.h \ -ptr_set.h \ -quad_effect.h \ -quad_grid_clip_cluster.h \ -quad_grid_clip_manager.h \ -quad_grid.h \ -quad_tree.h \ -radix_sort.h \ -raw_skin.h \ -raw_skinned.h \ -ray_mesh.h \ -register_3d.h \ -render_trav.h \ -root_model.h \ -scene_group.h \ -scene.h \ -scene_user.h \ -scissor.h \ -seg_remanence.h \ -seg_remanence_shape.h \ -shader.h \ -shadow_map.h \ -shadow_map_manager.h \ -shadow_poly_receiver.h \ -shadow_skin.h \ -shape_bank.h \ -shape_bank_user.h \ -shape.h \ -shape_info.h \ -shifted_triangle_cache.h \ -skeleton_model.h \ -skeleton_shape.h \ -skeleton_spawn_script.h \ -skeleton_weight.h \ -static_quad_grid.h \ -stripifier.h \ -surface_light_grid.h \ -tangent_space_build.h \ -target_anim_ctrl.h \ -tess_block.h \ -tessellation.h \ -tess_face_priority_list.h \ -tess_list.h \ -text_context.h \ -text_context_user.h \ -texture_blank.h \ -texture_blend.h \ -texture_bloom.h \ -texture_bump.h \ -texture_cube.h \ -texture_dlm.h \ -texture_emboss.h \ -texture_far.h \ -texture_file.h \ -texture_font.h \ -texture_grouped.h \ -texture.h \ -texture_mem.h \ -texture_multi_file.h \ -texture_near.h \ -texture_user.h \ -tile_bank.h \ -tile_color.h \ -tile_element.h \ -tile_far_bank.h \ -tile_light_influence.h \ -tile_lumel.h \ -tile_noise_map.h \ -tile_vegetable_desc.h \ -track_bezier.h \ -track.h \ -track_keyframer.h \ -track_sampled_common.h \ -track_sampled_quat.h \ -track_sampled_quat_small_header.h \ -track_sampled_vector.h \ -track_tcb.h \ -transformable.h \ -transform.h \ -transform_shape.h \ -trav_scene.h \ -u_3d_mouse_listener.h \ -u_animation.h \ -u_animation_set.h \ -u_bone.h \ -u_camera.h \ -u_cloud_scape.h \ -u_driver.h \ -u_instance_group.h \ -u_instance.h \ -u_instance_material.h \ -u_landscape.h \ -u_light.h \ -u_material.h \ -u_particle_system_instance.h \ -u_particle_system_sound.h \ -u_play_list.h \ -u_play_list_manager.h \ -u_point_light.h \ -u_ps_sound_impl.h \ -u_ps_sound_interface.h \ -u_scene.h \ -u_shape_bank.h \ -u_shape.h \ -u_skeleton.h \ -u_text_context.h \ -u_texture.h \ -u_track.h \ -u_transformable.h \ -u_transform.h \ -u_visual_collision_entity.h \ -u_visual_collision_manager.h \ -u_visual_collision_mesh.h \ -u_water_env_map.h \ -u_water.h \ -vegetable_blend_layer_model.h \ -vegetable_clip_block.h \ -vegetable_def.h \ -vegetable.h \ -vegetable_instance_group.h \ -vegetable_light_ex.h \ -vegetable_manager.h \ -vegetable_quadrant.h \ -vegetable_shape.h \ -vegetable_sort_block.h \ -vegetable_uv8.h \ -vegetablevb_allocator.h \ -vertex_buffer.h \ -vertex_buffer_heap.h \ -vertex_program.h \ -vertex_program_parse.h \ -vertex_stream_manager.h \ -viewport.h \ -visual_collision_entity.h \ -visual_collision_entity_user.h \ -visual_collision_manager.h \ -visual_collision_manager_user.h \ -visual_collision_mesh.h \ -water_env_map.h \ -water_env_map_user.h \ -water_height_map.h \ -water_model.h \ -water_pool_manager.h \ -water_shape.h \ -zone_corner_smoother.h \ -zone.h \ -zone_lighter.h \ -zone_manager.h \ -zone_search.h \ -zone_smoother.h \ -zone_symmetrisation.h \ -zone_tgt_smoother.h - -# End of Makefile.am diff --git a/code/nel/include/nel/3d/computed_string.h b/code/nel/include/nel/3d/computed_string.h index 16fe70fcb..914fa324e 100644 --- a/code/nel/include/nel/3d/computed_string.h +++ b/code/nel/include/nel/3d/computed_string.h @@ -27,12 +27,16 @@ #include #include +namespace NLMISC { + +class CMatrix; + +} namespace NL3D { class CTextureFont; -class CMatrix; struct CComputedString; // *************************************************************************** diff --git a/code/nel/include/nel/3d/cube_grid.h b/code/nel/include/nel/3d/cube_grid.h index 427aed624..483ea8000 100644 --- a/code/nel/include/nel/3d/cube_grid.h +++ b/code/nel/include/nel/3d/cube_grid.h @@ -227,7 +227,7 @@ void CCubeGrid::compile() // build the _StaticGrid _StaticGrids[i].build(_Grids[i]); // And reset the grid. contReset is necessary to clean the CBlockMemory. - contReset(_Grids[i]); + NLMISC::contReset(_Grids[i]); } // done diff --git a/code/nel/include/nel/3d/index_buffer.h b/code/nel/include/nel/3d/index_buffer.h index 320a06020..7beaafe0b 100644 --- a/code/nel/include/nel/3d/index_buffer.h +++ b/code/nel/include/nel/3d/index_buffer.h @@ -782,7 +782,7 @@ inline void CIndexBuffer::lock (CIndexBufferRead &accessor, uint first, uint las // *************************************************************************** -inline void CIndexBuffer::unlock (uint first, uint end) +inline void CIndexBuffer::unlock (uint /* first */, uint /* end */) { nlassertex (_LockCounter!=0, ("Index buffer not locked")); nlassert (_LockedBuffer || (!isResident() && _NonResidentIndexes.empty())); diff --git a/code/nel/include/nel/3d/instance_lighter.h b/code/nel/include/nel/3d/instance_lighter.h index 7b1dbb6e7..8b1ccf80a 100644 --- a/code/nel/include/nel/3d/instance_lighter.h +++ b/code/nel/include/nel/3d/instance_lighter.h @@ -147,7 +147,7 @@ public: static void addTriangles (const IShape &shape, const NLMISC::CMatrix& modelMT, std::vector& triangleArray, sint instanceId); // Progress callback - virtual void progress (const char *message, float progress) {} + virtual void progress (const char * /* message */, float /* progress */) {} /// \name Static PointLights mgt. diff --git a/code/nel/include/nel/3d/ps_attrib.h b/code/nel/include/nel/3d/ps_attrib.h index fc17dc787..83549a7b0 100644 --- a/code/nel/include/nel/3d/ps_attrib.h +++ b/code/nel/include/nel/3d/ps_attrib.h @@ -96,7 +96,7 @@ public: try { newStart = new uint8[sizeof(T) * capacity + (1 << snapPower)]; - T *newTab = (T *) ( (uint) (newStart + (1 << snapPower)) & ~((1 << snapPower) - 1)); // snap to a page + T *newTab = (T *) ( (size_t) (newStart + (1 << snapPower)) & ~((1 << snapPower) - 1)); // snap to a page diff --git a/code/nel/include/nel/3d/ps_attrib_maker_bin_op.h b/code/nel/include/nel/3d/ps_attrib_maker_bin_op.h index 4370fdeaa..285fa9b26 100644 --- a/code/nel/include/nel/3d/ps_attrib_maker_bin_op.h +++ b/code/nel/include/nel/3d/ps_attrib_maker_bin_op.h @@ -157,7 +157,7 @@ public: } /// return true if an operation is supported. The default support all ops - bool supportOp(CPSBinOp::BinOp op) { return true; } + bool supportOp(CPSBinOp::BinOp /* op */) { return true; } /// get the current operator CPSBinOp::BinOp getOp(void) const { return _Op; } diff --git a/code/nel/include/nel/3d/ps_attrib_maker_bin_op_inline.h b/code/nel/include/nel/3d/ps_attrib_maker_bin_op_inline.h index c25c051a5..8d98a1544 100644 --- a/code/nel/include/nel/3d/ps_attrib_maker_bin_op_inline.h +++ b/code/nel/include/nel/3d/ps_attrib_maker_bin_op_inline.h @@ -57,13 +57,13 @@ inline CPlaneBasis PSBinOpModulate(CPlaneBasis p1, CPlaneBasis p2) } template <> -inline CPlaneBasis PSBinOpAdd(CPlaneBasis p1, CPlaneBasis p2) +inline CPlaneBasis PSBinOpAdd(CPlaneBasis /* p1 */, CPlaneBasis /* p2 */) { nlassert(0); // not allowed for now return CPlaneBasis(NLMISC::CVector::Null); } template <> -inline CPlaneBasis PSBinOpSubtract(CPlaneBasis p1, CPlaneBasis p2) +inline CPlaneBasis PSBinOpSubtract(CPlaneBasis /* p1 */, CPlaneBasis /* p2 */) { nlassert(0); // not allowed for now return CPlaneBasis(NLMISC::CVector::Null); diff --git a/code/nel/include/nel/3d/ps_attrib_maker_iterators.h b/code/nel/include/nel/3d/ps_attrib_maker_iterators.h index 8729ffb10..d9cfbcdd3 100644 --- a/code/nel/include/nel/3d/ps_attrib_maker_iterators.h +++ b/code/nel/include/nel/3d/ps_attrib_maker_iterators.h @@ -60,7 +60,7 @@ namespace NL3D { GET_INLINE float get() const { return float(rand() * (1 / double(RAND_MAX))); } // this may be optimized with a table... void advance() {} - void advance(uint quantity) {} + void advance(uint /* quantity */) {} }; /// this iterator just return the same value @@ -69,7 +69,7 @@ namespace NL3D float Value; GET_INLINE float get() const { return Value; } void advance() {} - void advance(uint quantity) {} + void advance(uint /* quantity */) {} }; /// iterator that use dist to compute the value diff --git a/code/nel/include/nel/3d/ps_direction.h b/code/nel/include/nel/3d/ps_direction.h index 609278a80..eb8bbd827 100644 --- a/code/nel/include/nel/3d/ps_direction.h +++ b/code/nel/include/nel/3d/ps_direction.h @@ -38,7 +38,7 @@ public : /** The direction is taken from a global vector defined in the particle system * NULL or an empty string as a name disable the use of a global value */ - virtual void enableGlobalVectorValue(const std::string &name) {} + virtual void enableGlobalVectorValue(const std::string &/* name */) {} virtual std::string getGlobalVectorValueName() const { return ""; } }; diff --git a/code/nel/include/nel/3d/ps_edit.h b/code/nel/include/nel/3d/ps_edit.h index d3e1a53de..8342868f8 100644 --- a/code/nel/include/nel/3d/ps_edit.h +++ b/code/nel/include/nel/3d/ps_edit.h @@ -65,13 +65,13 @@ struct IPSMover virtual bool supportNonUniformScaling(void) const { NL_PS_FUNC(supportNonUniformScaling); return false ; } // set the scale of the object (uniform scale). The default does nothing - virtual void setScale(uint32 index, float scale) {} ; + virtual void setScale(uint32 /* index */, float /* scale */) {} // set a non uniform scale (if supported) - virtual void setScale(uint32 index, const NLMISC::CVector &s) { NL_PS_FUNC(setScale); } + virtual void setScale(uint32 /* index */, const NLMISC::CVector &/* s */) { NL_PS_FUNC(setScale); } // get the scale of the object - virtual NLMISC::CVector getScale(uint32 index) const { NL_PS_FUNC(getScale); return NLMISC::CVector(1.f, 1.f, 1.f) ; } + virtual NLMISC::CVector getScale(uint32 /* index */) const { NL_PS_FUNC(getScale); return NLMISC::CVector(1.f, 1.f, 1.f) ; } /** some object may not store a whole matrix (e.g planes) * this return true if only a normal is needed to set the orientation of the object @@ -79,10 +79,10 @@ struct IPSMover virtual bool onlyStoreNormal(void) const { NL_PS_FUNC(onlyStoreNormal); return false ; } /// if the object only needs a normal, this return the normal. If not, is return (0, 0, 0) - virtual NLMISC::CVector getNormal(uint32 index) { NL_PS_FUNC(getNormal); return NLMISC::CVector::Null ; } + virtual NLMISC::CVector getNormal(uint32 /* index */) { NL_PS_FUNC(getNormal); return NLMISC::CVector::Null ; } /// if the object only stores a normal, this set the normal of the object. Otherwise it has no effect - virtual void setNormal(uint32 index, NLMISC::CVector n) { NL_PS_FUNC(setNormal); } + virtual void setNormal(uint32 /* index */, NLMISC::CVector /* n */) { NL_PS_FUNC(setNormal); } // set a new orthogonal matrix for the object virtual void setMatrix(uint32 index, const NLMISC::CMatrix &m) = 0 ; diff --git a/code/nel/include/nel/3d/ps_force.h b/code/nel/include/nel/3d/ps_force.h index 5e961dc43..15e28948a 100644 --- a/code/nel/include/nel/3d/ps_force.h +++ b/code/nel/include/nel/3d/ps_force.h @@ -87,9 +87,9 @@ public: * 'accumulate' set to false. * NB : works only with integrable forces */ - virtual void integrate(float date, CPSLocated *src, uint32 startIndex, uint32 numObjects, NLMISC::CVector *destPos = NULL, NLMISC::CVector *destSpeed = NULL, - bool accumulate = false, - uint posStride = sizeof(NLMISC::CVector), uint speedStride = sizeof(NLMISC::CVector) + virtual void integrate(float /* date */, CPSLocated * /* src */, uint32 /* startIndex */, uint32 /* numObjects */, NLMISC::CVector * /* destPos */ = NULL, NLMISC::CVector * /* destSpeed */ = NULL, + bool /* accumulate */ = false, + uint /* posStride */ = sizeof(NLMISC::CVector), uint /* speedStride */ = sizeof(NLMISC::CVector) ) const { nlassert(0); // not an integrable force @@ -100,11 +100,11 @@ public: * If the start date is lower than the creation date, the initial position is used * NB : works only with integrable forces */ - virtual void integrateSingle(float startDate, float deltaT, uint numStep, - const CPSLocated *src, uint32 indexInLocated, - NLMISC::CVector *destPos, - bool accumulate = false, - uint posStride = sizeof(NLMISC::CVector)) const + virtual void integrateSingle(float /* startDate */, float /* deltaT */, uint /* numStep */, + const CPSLocated * /* src */, uint32 /* indexInLocated */, + NLMISC::CVector * /* destPos */, + bool /* accumulate */ = false, + uint /* posStride */ = sizeof(NLMISC::CVector)) const { nlassert(0); // not an integrable force } @@ -170,7 +170,7 @@ public: virtual void setIntensityScheme(CPSAttribMaker *scheme); // deriver have here the opportunity to setup the functor object. The default does nothing - virtual void setupFunctor(uint32 indexInLocated) { } + virtual void setupFunctor(uint32 /* indexInLocated */) { } /// get the attribute maker for a non constant intensity CPSAttribMaker *getIntensityScheme(void) { return _IntensityScheme; } @@ -493,22 +493,22 @@ public: #ifdef NL_OS_WINDOWS __forceinline #endif - void operator() (const NLMISC::CVector &pos, NLMISC::CVector &speed, float invMass) - { + void operator() (const NLMISC::CVector &/* pos */, NLMISC::CVector &speed, float invMass) + { speed -= (CParticleSystem::EllapsedTime * _K * invMass * speed); - } + } - virtual void serial(NLMISC::IStream &f) throw(NLMISC::EStream) - { - f.serialVersion(1); - // we don't save intensity info : it is saved by the owning object (and set before each use of this functor) - } + virtual void serial(NLMISC::IStream &f) throw(NLMISC::EStream) + { + f.serialVersion(1); + // we don't save intensity info : it is saved by the owning object (and set before each use of this functor) + } - // get the friction coefficient - float getK(void) const { return _K; } + // get the friction coefficient + float getK(void) const { return _K; } - // set the friction coefficient - void setK(float coeff) { _K = coeff; } + // set the friction coefficient + void setK(float coeff) { _K = coeff; } protected: // the friction coeff float _K; @@ -630,7 +630,7 @@ struct CPSTurbulForceFunc #ifdef NL_OS_WINDOWS __forceinline #endif - void operator() (const NLMISC::CVector &pos, NLMISC::CVector &speed, float invMass) + void operator() (const NLMISC::CVector &/* pos */, NLMISC::CVector &/* speed */, float /* invMass */) { nlassert(0); diff --git a/code/nel/include/nel/3d/static_quad_grid.h b/code/nel/include/nel/3d/static_quad_grid.h index 7890e12f4..5a0383c9e 100644 --- a/code/nel/include/nel/3d/static_quad_grid.h +++ b/code/nel/include/nel/3d/static_quad_grid.h @@ -172,7 +172,7 @@ template void CStaticQuadGrid::build(CQuadGrid &quadGrid) { clear(); - contReset(_Grid); + NLMISC::contReset(_Grid); // Copy from quadGrid, and init quads _Size= quadGrid.getSize(); diff --git a/code/nel/include/nel/3d/track_tcb.h b/code/nel/include/nel/3d/track_tcb.h index 8daf5cc2a..fc9c227e4 100644 --- a/code/nel/include/nel/3d/track_tcb.h +++ b/code/nel/include/nel/3d/track_tcb.h @@ -218,7 +218,7 @@ protected: date*= previous->OODeltaTime; NLMISC::clamp(date, 0,1); - date = ease(previous, date); + date = this->ease(previous, date); float hb[4]; this->computeHermiteBasis(date, hb); @@ -242,7 +242,7 @@ protected: ITrackKeyFramer::compile(); // Ease Precompute. - compileTCBEase(this->_MapKey, this->getLoopMode()); + this->compileTCBEase(this->_MapKey, this->getLoopMode()); // Tangents Precompute. @@ -314,7 +314,7 @@ private: float ksm,ksp,kdm,kdp; // compute tangents factors. - computeTCBFactors(key, timeBefore, time, timeAfter, rangeDelta, firstKey, endKey, isLoop, ksm,ksp,kdm,kdp); + this->computeTCBFactors(key, timeBefore, time, timeAfter, rangeDelta, firstKey, endKey, isLoop, ksm,ksp,kdm,kdp); // Delta. TKeyValueType delm, delp; @@ -413,7 +413,7 @@ public: ITrackKeyFramer::compile(); // Ease Precompute. - compileTCBEase(_MapKey, getLoopMode()); + this->compileTCBEase(_MapKey, getLoopMode()); TMapTimeCKey::iterator it; TMapTimeCKey::iterator itNext; diff --git a/code/nel/include/nel/3d/vertex_buffer.h b/code/nel/include/nel/3d/vertex_buffer.h index 83b2b90ea..a64ec0c2c 100644 --- a/code/nel/include/nel/3d/vertex_buffer.h +++ b/code/nel/include/nel/3d/vertex_buffer.h @@ -1223,7 +1223,7 @@ inline void CVertexBuffer::lock (CVertexBufferRead &accessor, uint first, uint l // -------------------------------------------------- -inline void CVertexBuffer::unlock (uint first, uint end) +inline void CVertexBuffer::unlock (uint /* first */, uint /* end */) { nlassertex (_LockCounter!=0, ("Vertex buffer not locked")); nlassert (_LockedBuffer || (!isResident() && _NonResidentVertices.empty())); diff --git a/code/nel/include/nel/Makefile.am b/code/nel/include/nel/Makefile.am deleted file mode 100644 index 7e4686f14..000000000 --- a/code/nel/include/nel/Makefile.am +++ /dev/null @@ -1,13 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -DIST_SUBDIRS = net 3d pacs sound misc georges ligo - -SUBDIRS = @NEL_SUBDIRS@ - -includedir = ${prefix}/include/nel - -# End of Makefile.am - diff --git a/code/nel/include/nel/cegui/Makefile.am b/code/nel/include/nel/cegui/Makefile.am deleted file mode 100644 index ba2530dff..000000000 --- a/code/nel/include/nel/cegui/Makefile.am +++ /dev/null @@ -1,11 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2001-08-01 08:45:06 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -includedir = ${prefix}/include/nel/cegui - -include_HEADERS = nelrenderer.h nelresourceprovider.h neltexture.h - -# End of Makefile.am diff --git a/code/nel/include/nel/georges/Makefile.am b/code/nel/include/nel/georges/Makefile.am deleted file mode 100644 index 153e9b102..000000000 --- a/code/nel/include/nel/georges/Makefile.am +++ /dev/null @@ -1,15 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -includedir = ${prefix}/include/nel/georges - -include_HEADERS = load_form.h \ - u_form_dfn.h \ - u_form_elm.h \ - u_form.h \ - u_form_loader.h \ - u_type.h - -# End of Makefile.am diff --git a/code/nel/include/nel/ligo/Makefile.am b/code/nel/include/nel/ligo/Makefile.am deleted file mode 100644 index 48fb3c54b..000000000 --- a/code/nel/include/nel/ligo/Makefile.am +++ /dev/null @@ -1,14 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -includedir = ${prefix}/include/nel/ligo - -include_HEADERS = ligo_config.h \ - primitive_class.h \ - primitive_configuration.h \ - primitive.h \ - primitive_utils.h - -# End of Makefile.am diff --git a/code/nel/include/nel/misc/Makefile.am b/code/nel/include/nel/misc/Makefile.am deleted file mode 100644 index 75bde6b95..000000000 --- a/code/nel/include/nel/misc/Makefile.am +++ /dev/null @@ -1,143 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -includedir = ${prefix}/include/nel/misc - -include_HEADERS = aabbox.h \ - algo.h \ - app_context.h \ - array_2d.h \ - async_file_manager.h \ - big_file.h \ - bitmap.h \ - bit_mem_stream.h \ - bit_set.h \ - block_memory.h \ - bsphere.h \ - buf_fifo.h \ - check_fpu.h \ - class_id.h \ - class_registry.h \ - command.h \ - common.h \ - config_file.h \ - contiguous_block_allocator.h \ - co_task.h \ - cpu_time_stat.h \ - debug.h \ - di_event_emitter.h \ - diff_tool.h \ - displayer.h \ - dummy_window.h \ - dynloadlib.h \ - eid_translator.h \ - entity_id.h \ - enum_bitset.h \ - eval_num_expr.h \ - event_emitter.h \ - event_emitter_multi.h \ - event_listener.h \ - event_server.h \ - events.h \ - factory.h \ - fast_floor.h \ - fast_mem.h \ - file.h \ - fixed_size_allocator.h \ - game_device_events.h \ - game_device.h \ - geom_ext.h \ - grid_traversal.h \ - gtk_displayer.h \ - heap_memory.h \ - hierarchical_timer.h \ - historic.h \ - i18n.h \ - input_device.h \ - input_device_manager.h \ - input_device_server.h \ - inter_window_msg_queue.h \ - i_xml.h \ - keyboard_device.h \ - line.h \ - log.h \ - matrix.h \ - md5.h \ - mem_displayer.h \ - mem_stream.h \ - mouse_device.h \ - mouse_smoother.h \ - mutable_container.h \ - mutex.h \ - noise_value.h \ - object_arena_allocator.h \ - object_vector.h \ - o_xml.h \ - path.h \ - plane.h \ - plane_inline.h \ - polygon.h \ - pool_memory.h \ - progress_callback.h \ - p_thread.h \ - quad.h \ - quat.h \ - random.h \ - reader_writer.h \ - rect.h \ - report.h \ - resource_ptr.h \ - resource_ptr_inline.h \ - rgba.h \ - sha1.h \ - shared_memory.h \ - sheet_id.h \ - singleton.h \ - smart_ptr.h \ - smart_ptr_inline.h \ - speaker_listener.h \ - sstring.h \ - static_map.h \ - stl_block_allocator.h \ - stl_block_list.h \ - stop_watch.h \ - stream.h \ - stream_inline.h \ - string_common.h \ - string_conversion.h \ - string_id_array.h \ - string_mapper.h \ - string_stream.h \ - system_info.h \ - task_manager.h \ - tds.h \ - thread.h \ - time_nl.h \ - timeout_assertion_thread.h \ - traits_nl.h \ - triangle.h \ - twin_map.h \ - types_nl.h \ - ucstring.h \ - uv.h \ - value_smoother.h \ - variable.h \ - vector_2d.h \ - vector_2f.h \ - vectord.h \ - vectord_inline.h \ - vector.h \ - vector_h.h \ - vector_inline.h \ - win32_util.h \ - win_displayer.h \ - window_displayer.h \ - win_event_emitter.h \ - win_thread.h \ - win_tray.h \ - words_dictionary.h \ - xml_pack.h - -# End of Makefile.am diff --git a/code/nel/include/nel/misc/bit_mem_stream.h b/code/nel/include/nel/misc/bit_mem_stream.h index 8a1af34cc..b294d5454 100644 --- a/code/nel/include/nel/misc/bit_mem_stream.h +++ b/code/nel/include/nel/misc/bit_mem_stream.h @@ -209,6 +209,8 @@ public: { #ifdef NL_DEBUG std::swap(_DbgData, other._DbgData); +#else + nlunreferenced(other); #endif } @@ -225,6 +227,10 @@ public: TBMSSerialInfo serialItem( bitpos, size, type, _DbgData->NextSymbol ); _DbgData->List.push_back( serialItem ); _DbgData->NextSymbol = NULL; +#else + nlunreferenced(bitpos); + nlunreferenced(size); + nlunreferenced(type); #endif } @@ -258,6 +264,10 @@ public: nlwarning( "Missing reserve() corresponding to poke()" ); } _DbgData->NextSymbol = NULL; +#else + nlunreferenced(bitpos); + nlunreferenced(size); + nlunreferenced(type); #endif } @@ -266,6 +276,8 @@ public: { #ifdef NL_DEBUG _DbgData->NextSymbol = symbol; +#else + nlunreferenced(symbol); #endif } @@ -308,6 +320,8 @@ public: } //nlassert( bitpos < (*_List)[_CurrentBrowsedItem].BitPos ); // occurs if stream overflow } +#else + nlunreferenced(bitpos); #endif *eventId = -1; return std::string(); @@ -380,7 +394,7 @@ public: * If you are using the stream only in output mode, you can use this method as a faster version * of clear() *if you don't serialize pointers*. */ - void resetBufPos() + virtual void resetBufPos() { // This is ensured in CMemStream::CMemStream() and CMemStream::clear() //if ( (!isReading()) && _Buffer.empty() ) @@ -463,7 +477,7 @@ public: } /// See doc in CMemStream::bufferToFill() - uint8 *bufferToFill( uint32 msgsize ) + virtual uint8 *bufferToFill( uint32 msgsize ) { _FreeBits = 8; _DbgInfo.clear(); @@ -640,7 +654,7 @@ public: virtual void serial(ucstring &b); virtual void serial(CBitMemStream &b) { serialMemStream(b); } - virtual void serialMemStream(CBitMemStream &b); + virtual void serialMemStream(CMemStream &b); //@} @@ -787,11 +801,7 @@ void displayBitStream( const CBitMemStream& msg, sint beginbitpos, sint endbitpo inline std::string CBMSDbgInfo::getEventLegendAtBitPos( CBitMemStream& bms, sint32 eventId ) { #ifdef NL_DEBUG - if ( eventId == -1 ) - { - return std::string(); - } - else + if ( eventId != -1 ) { nlassert( eventId < (sint32)_DbgData->List.size() ); TBMSSerialInfo& serialItem = _DbgData->List[eventId]; // works only with a vector! @@ -800,8 +810,11 @@ inline std::string CBMSDbgInfo::getEventLegendAtBitPos( CBitMemStream& bms, sint bms.getSerialItem( serialItem ).c_str(), (serialItem.Symbol!=NULL)?serialItem.Symbol:"" ); } #else - return std::string(); + nlunreferenced(bms); + nlunreferenced(eventId); #endif + + return std::string(); } diff --git a/code/ryzom/client/src/cdb.h b/code/nel/include/nel/misc/cdb.h similarity index 83% rename from code/ryzom/client/src/cdb.h rename to code/nel/include/nel/misc/cdb.h index 24c2dd5eb..d23986b0c 100644 --- a/code/ryzom/client/src/cdb.h +++ b/code/nel/include/nel/misc/cdb.h @@ -20,20 +20,20 @@ #define CDB_H // misc -#include "nel/misc/types_nl.h" -#include "nel/misc/smart_ptr.h" -#include "nel/misc/string_mapper.h" +#include "types_nl.h" +#include "smart_ptr.h" +#include "string_mapper.h" +#include "sstring.h" #include namespace NLMISC { - class IProgressCallback; - class CBitMemStream; -} - +class IProgressCallback; +class CBitMemStream; class CCDBNodeLeaf; class CCDBNodeBranch; +class CCDBBankHandler; ///global bool, must be set to true if we want to display database modification. See displayDBModifs in commands.cpp extern bool VerboseDatabase; @@ -48,7 +48,7 @@ extern bool VerboseDatabase; * \date 2002 */ -class ICDBNode : public NLMISC::CRefCount +class ICDBNode : public CRefCount { //----------------------------------------------------------------------- // end of IDBNode interface @@ -78,7 +78,7 @@ public: * \author Nevrax France * \date 2002 */ - class IPropertyObserver : public NLMISC::CRefCount + class IPropertyObserver : public CRefCount { public : virtual ~IPropertyObserver() {} @@ -198,7 +198,7 @@ public : * Build the structure of the database from a file * \param f is the stream */ - virtual void init( xmlNodePtr node, NLMISC::IProgressCallback &progressCallBack, bool mapBanks=false ) = 0; + virtual void init( xmlNodePtr node, IProgressCallback &progressCallBack, bool mapBanks=false, CCDBBankHandler *bankHandler = NULL ) = 0; /** * Save a backup of the database @@ -212,7 +212,7 @@ public : * \param gc the server gameCycle of this update. Any outdated update are aborted * \param f : the stream. */ - virtual void readDelta( NLMISC::TGameCycle gc, NLMISC::CBitMemStream & f ) = 0; + virtual void readDelta( TGameCycle gc, CBitMemStream & f ) = 0; /** * Get a node . Create it if it does not exist yet @@ -252,7 +252,7 @@ public : virtual bool setProp( CTextId& id, sint64 value ) = 0; /// Reset all leaf data from this point - virtual void resetData(NLMISC::TGameCycle gc, bool forceReset=false) = 0; + virtual void resetData(TGameCycle gc, bool forceReset=false) = 0; /** * Clear the node and his children @@ -302,10 +302,10 @@ public : virtual CCDBNodeLeaf *findLeafAtCount( uint& count ) = 0; /// Set the atomic branch flag (when all the modified nodes of a branch should be tranmitted at the same time) - void setAtomic( bool atomicBranch ) { _Atomic = atomicBranch; } + void setAtomic( bool atomicBranch ) { _AtomicFlag = atomicBranch; } /// Return true if the branch has the atomic flag - bool isAtomic() const { return _Atomic; } + bool isAtomic() const { return _AtomicFlag; } // test if the node is a leaf virtual bool isLeaf() const = 0; @@ -314,16 +314,16 @@ public : virtual void display (const std::string &/* prefix */){} /// Return the string id corresponding to the argument - static NLMISC::TStringId getStringId(const std::string& nodeName) + static TStringId getStringId(const std::string& nodeName) { - if (_DBSM == NULL) _DBSM = NLMISC::CStringMapper::createLocalMapper(); + if (_DBSM == NULL) _DBSM = CStringMapper::createLocalMapper(); return _DBSM->localMap(nodeName); } /// Return a pointer to the string corresponding to the argument - static const std::string *getStringFromId(NLMISC::TStringId nodeStringId) + static const std::string *getStringFromId(TStringId nodeStringId) { - if (_DBSM == NULL) _DBSM = NLMISC::CStringMapper::createLocalMapper(); + if (_DBSM == NULL) _DBSM = CStringMapper::createLocalMapper(); return &_DBSM->localUnmap(nodeStringId); } @@ -333,34 +333,36 @@ public : protected: /// Constructor - ICDBNode() : _Atomic(false) + ICDBNode() : _AtomicFlag(false) { - if (_DBSM == NULL) _DBSM = NLMISC::CStringMapper::createLocalMapper(); - _Name = NLMISC::CStringMapper::emptyId(); + if (_DBSM == NULL) _DBSM = CStringMapper::createLocalMapper(); + _Name = CStringMapper::emptyId(); } /// Constructor - ICDBNode (const std::string &name) : _Atomic(false) + ICDBNode (const std::string &name) : _AtomicFlag(false) { - if (_DBSM == NULL) _DBSM = NLMISC::CStringMapper::createLocalMapper(); + if (_DBSM == NULL) _DBSM = CStringMapper::createLocalMapper(); _Name = _DBSM->localMap(name); //_NameDbg = name; } // utility to build full name efficiently (without reallocating the string at each parent level) - void _buildFullName(NLMISC::CSString &fullName); + void _buildFullName(CSString &fullName); /// Atomic flag: is the branch an atomic group, or is the leaf a member of an atomic group - bool _Atomic : 1; + bool _AtomicFlag : 1; /// Name of the node - NLMISC::TStringId _Name; + TStringId _Name; //std::string _NameDbg; - static NLMISC::CStringMapper *_DBSM; + static CStringMapper *_DBSM; }; +} + #endif // CDB_H diff --git a/code/nel/include/nel/misc/cdb_bank_handler.h b/code/nel/include/nel/misc/cdb_bank_handler.h new file mode 100644 index 000000000..102026f4d --- /dev/null +++ b/code/nel/include/nel/misc/cdb_bank_handler.h @@ -0,0 +1,139 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef CDB_BANK_HANDLER +#define CDB_BANK_HANDLER + +#include +#include "nel/misc/types_nl.h" + +namespace NLMISC{ + +/** + @brief Manages the bank names and mappings of the CDB it's associated with + + Banks are numeric identifiers for the top-level branches of the CDB. + They are used for saving bandwidth, because the local CDBs are updated with deltas, + that identify the updatable top-level branch with this id. + The CCDBBankHandler manages the mapping of banks to their names, unified (node) index, + and the other way around. + + */ +class CCDBBankHandler{ +public: + /** + @brief The class' constructor + @param maxbanks the maximum number of banks we need to handle + */ + CCDBBankHandler( uint maxbanks ); + + /// Very surprisingly this is the destructor + ~CCDBBankHandler(){} + + /** + @brief Returns the unified (node) index for the specified bank Id. + @param bank The bank whose uid we need. + @return Returns an uid or static_cast< uint >( -1 ) on failure. + */ + uint getUIDForBank( uint bank ) const; + + /** + @brief Returns the bank Id for the specified unified (node) index. + @param uid The unified (node) index we need to translate to bank Id. + @return Returns a bank Id. + */ + uint getBankForUID( uint uid ) const{ return _UnifiedIndexToBank[ uid ]; } + + /// Returns the last unified (node) index we mapped. + uint getLastUnifiedIndex() const{ return _CDBLastUnifiedIndex; } + + /** + @brief Returns the number of bits used to store the number of nodes that belong to this bank. + @param bank The banks whose id bits we need. + @return Returns the number of bits used to store the number of nodes that belong to this bank. + */ + uint getFirstLevelIdBits( uint bank ) const{ return _FirstLevelIdBitsByBank[ bank ]; } + + /** + @brief Returns the name of the specified bank. + @param bank The id of the bank we need the name of. + @return Returns the name of the specified bank. + */ + std::string getBankName( uint bank ) const{ return _CDBBankNames[ bank ]; } + + /** + @brief Looks up the bank Id of the bank name specified. + @param name The name of the bank whose Id we need. + @return Returns the id of the bank, or static_cast< uint >( -1 ) on fail. + */ + uint getBankByName( const std::string &name ) const; + + /** + @brief Maps the specified bank name to a unified (node) index and vica versa. + @param bankName Name of the bank to map. + */ + void mapNodeByBank( const std::string &bankName ); + + /** + @brief Loads the known bank names from an array ( the order decides the bank Id ). + @param strings The array of the banks names. + @param size The size of the array. + */ + void fillBankNames( const char **strings, uint size ); + + /// Resets the node to bank mapping vector + void resetNodeBankMapping(){ _UnifiedIndexToBank.clear(); } + + /// Resets all maps, and sets _CDBLastUnifiedIndex to 0. + void reset(); + + uint getUnifiedIndexToBankSize() const{ return _UnifiedIndexToBank.size(); } + + /// Calculates the number of bits used to store the number of nodes that belong to the banks. + void calcIdBitsByBank(); + + /** + @brief Looks up the unified (node) index of a bank node. + @param bank The bank id of the node we are looking up. + @param index The index of the node within the bank. + @return Returns the unified (node) index of the specified bank node. + */ + uint getServerToClientUIDMapping( uint bank, uint index ) const{ return _CDBBankToUnifiedIndexMapping[ bank ][ index ]; } + +private: + /// Mapping from server database index to client database index (first-level nodes) + std::vector< std::vector< uint > > _CDBBankToUnifiedIndexMapping; + + /// Mapping from client database index to bank IDs (first-level nodes) + std::vector< uint > _UnifiedIndexToBank; + + /// Last index mapped + uint _CDBLastUnifiedIndex; + + /// Number of bits for first-level branches, by bank + std::vector< uint > _FirstLevelIdBitsByBank; + + /// Names of the CDB banks + std::vector< std::string > _CDBBankNames; + + /// The number of banks used + uint maxBanks; +}; + +} + +#endif + diff --git a/code/ryzom/client/src/cdb_branch.h b/code/nel/include/nel/misc/cdb_branch.h similarity index 53% rename from code/ryzom/client/src/cdb_branch.h rename to code/nel/include/nel/misc/cdb_branch.h index 405c7c479..a2255ca09 100644 --- a/code/ryzom/client/src/cdb_branch.h +++ b/code/nel/include/nel/misc/cdb_branch.h @@ -20,7 +20,8 @@ #define CDB_BRANCH_H #include "cdb.h" -#include "game_share/ryzom_database_banks.h" + +namespace NLMISC{ /** * Database Node which contains a set of properties @@ -32,6 +33,21 @@ class CCDBNodeBranch : public ICDBNode { public: + class ICDBDBBranchObserverHandle + { + public: + virtual ~ICDBDBBranchObserverHandle(){} + + virtual ICDBNode* owner() = 0; + virtual IPropertyObserver* observer() = 0; + virtual bool observesLeaf( const std::string &leafName ) = 0; + virtual bool inList( uint list ) = 0; + virtual void addToFlushableList() = 0; + virtual void removeFromFlushableList( uint list ) = 0; + virtual void removeFromFlushableList() = 0; + + }; + // default constructor CCDBNodeBranch(const std::string &name) : ICDBNode(name) { @@ -44,7 +60,7 @@ public: * Build the structure of the database from a file * \param f is the stream */ - void init( xmlNodePtr node, class NLMISC::IProgressCallback &progressCallBack, bool mapBanks=false ); + void init( xmlNodePtr node, class IProgressCallback &progressCallBack, bool mapBanks=false, CCDBBankHandler *bankHandler = NULL ); /** * Add a new sub node @@ -93,10 +109,10 @@ public: void write( CTextId& id, FILE * f); /// Update the database from the delta, but map the first level with the bank mapping (see _CDBBankToUnifiedIndexMapping) - void readAndMapDelta( NLMISC::TGameCycle gc, NLMISC::CBitMemStream& s, TCDBBank bank ); + void readAndMapDelta( TGameCycle gc, CBitMemStream& s, uint bank, CCDBBankHandler *bankHandler ); /// Update the database from a stream coming from the FE - void readDelta( NLMISC::TGameCycle gc, NLMISC::CBitMemStream & f ); + void readDelta( TGameCycle gc, CBitMemStream & f ); /** * Return the value of a property (the update flag is set to false) @@ -118,19 +134,16 @@ public: /// Clear the node and his children void clear(); - /// Reset the data corresponding to the bank (works only on top level node) - void resetBank( NLMISC::TGameCycle gc, TCDBBank bank) + void resetNode( TGameCycle gc, uint node ) { - //nlassert( getParent() == NULL ); - for ( uint i=0; i!=_Nodes.size(); ++i ) - { - if ( _UnifiedIndexToBank[i] == bank ) - _Nodes[i]->resetData(gc); - } + if( node > _Nodes.size() ) + return; + + _Nodes[ node ]->resetData( gc ); } /// Reset all leaf data from this point - void resetData(NLMISC::TGameCycle gc, bool forceReset=false) + void resetData(TGameCycle gc, bool forceReset=false) { for ( uint i=0; i!=_Nodes.size(); ++i ) { @@ -165,7 +178,7 @@ public: virtual void display (const std::string &prefix); - void removeNode (CTextId& id); + void removeNode (const CTextId& id); /** * add an observer to a property @@ -190,7 +203,7 @@ public: * and setting a branch observer on it, except you don't need to change your database paths * and update large amounts of code!). */ - void addBranchObserver(IPropertyObserver* observer, const std::vector& positiveLeafNameFilter=std::vector()); + void addBranchObserver( ICDBDBBranchObserverHandle* handle, const std::vector& positiveLeafNameFilter=std::vector()); /** * Easy version of addBranchObserver() (see above). @@ -198,7 +211,7 @@ public: * "" -> this node * "FOO:BAR" -> sub-branch "BAR" of "FOO" which is a sub-branch of this node */ - void addBranchObserver(const char *dbPathFromThisNode, ICDBNode::IPropertyObserver& observer, const char **positiveLeafNameFilter=NULL, uint positiveLeafNameFilterSize=0); + void addBranchObserver( ICDBDBBranchObserverHandle *handle, const char *dbPathFromThisNode, const char **positiveLeafNameFilter=NULL, uint positiveLeafNameFilterSize=0); // Remove observer from all sub-leaves bool removeBranchObserver(IPropertyObserver* observer); @@ -208,82 +221,14 @@ public: virtual bool isLeaf() const { return false; } - /** Update all observers of branchs that have been modified - */ - static void flushObserversCalls(); - // mark this branch and parent branch as 'modified'. This is usually called by sub-leaves - void linkInModifiedNodeList(NLMISC::TStringId modifiedLeafName); + void onLeafChanged( TStringId leafName ); /// Find a subnode at this level ICDBNode * find (const std::string &nodeName); - /// Main init - static void resetNodeBankMapping() { _UnifiedIndexToBank.clear(); } - - // reset all static mappings - static void reset(); - - /// Internal use only - static void mapNodeByBank( ICDBNode *node, const std::string& bankStr, bool clientOnly, uint nodeIndex ); - protected: - - - /** Struct identifying an observer of a db branch - * This struct can be linked in a list so that we can update observers only once per pass. - * An observer that watch a whole branch can be updated once and only once after each element of the branch has been modified - */ - class CDBBranchObsInfo - { - public: - NLMISC::CRefPtr Observer; - // 2 linked list are required : while the observer is notified, it can triger one other observer, so we must link it in another list - bool Touched[2]; - CDBBranchObsInfo *PrevNotifiedObserver[2]; // NULL means this is the head - CDBBranchObsInfo *NextNotifiedObserver[2]; - ICDBNode *Owner; - - // If non-empty, only a leaf whose name is found here will notify something - // This is equivalent to creating a sub-branch containing only the specified leaves - // and setting a branch observer on it, except you don't need to change your database paths - // and update large amounts of code and script! - std::vector PositiveLeafNameFilter; - - public: - - /// Constructor. See above for usage of positiveLeafNameFilter. - CDBBranchObsInfo(IPropertyObserver *obs = NULL, ICDBNode *owner = NULL, const std::vector& positiveLeafNameFilter=std::vector()) - { - Owner = owner; - Observer = obs; - Touched[0] = Touched[1] = false; - PrevNotifiedObserver[0] = PrevNotifiedObserver[1] = NULL; - NextNotifiedObserver[0] = NextNotifiedObserver[1] = NULL; - for (std::vector::const_iterator ipf=positiveLeafNameFilter.begin(); ipf!=positiveLeafNameFilter.end(); ++ipf) - { - PositiveLeafNameFilter.push_back(ICDBNode::getStringId(*ipf)); // the ids are also mapped at database init, we don't need to unmap them in destructor - } - } - ~CDBBranchObsInfo() - { - // should have been unlinked - nlassert(Touched[0] == false); - nlassert(Touched[1] == false); - nlassert(PrevNotifiedObserver[0] == NULL); - nlassert(PrevNotifiedObserver[1] == NULL); - nlassert(NextNotifiedObserver[0] == NULL); - nlassert(NextNotifiedObserver[1] == NULL); - } - // Unlink from the given list. This also clear the '_Touched' flag - void unlink(uint list); - void link(uint list, NLMISC::TStringId modifiedLeafName); - }; - - typedef std::list TObsList; // must use a list because pointers on CDBObserverInfo instances must remains valids - -protected: - + typedef std::list< ICDBDBBranchObserverHandle* > TObserverHandleList; CCDBNodeBranch *_Parent; @@ -298,34 +243,13 @@ protected: bool _Sorted : 1; // observers for this node or branch - TObsList _Observers; - - friend class CDBBranchObsInfo; - // Global list of modified nodes - static CDBBranchObsInfo *_FirstNotifiedObs[2]; - static CDBBranchObsInfo *_LastNotifiedObs[2]; - static uint _CurrNotifiedObsList; // 0 or 1 => tell in which list observers of modified values must be added - // current & next observers being notified : if such observer if removed during notification, pointer will be valids - static CDBBranchObsInfo *_CurrNotifiedObs; - static CDBBranchObsInfo *_NextNotifiedObs; - - /// Mapping from server database index to client database index (first-level nodes) - static std::vector _CDBBankToUnifiedIndexMapping [NB_CDB_BANKS]; - - // Mapping from client database index to TCDBBank (first-level nodes) - static std::vector _UnifiedIndexToBank; - - /// Last index mapped - static uint _CDBLastUnifiedIndex; - - /// Number of bits for first-level branches, by bank - static uint _FirstLevelIdBitsByBank [NB_CDB_BANKS]; + TObserverHandleList observerHandles; /// called by clear void removeAllBranchObserver(); - void removeBranchInfoIt(TObsList::iterator it); }; +} #endif // CDB_BRANCH_H diff --git a/code/nel/include/nel/misc/cdb_branch_observing_handler.h b/code/nel/include/nel/misc/cdb_branch_observing_handler.h new file mode 100644 index 000000000..d7ec508af --- /dev/null +++ b/code/nel/include/nel/misc/cdb_branch_observing_handler.h @@ -0,0 +1,128 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef CDB_BRANCH_OBS_HNDLR +#define CDB_BRANCH_OBS_HNDLR + +#include "nel/misc/cdb_branch.h" + +namespace NLMISC{ + + /** + @brief Manages the CDB branch observers. + + When a leaf's data changes, it notifies the branch, which then marks the observers as notifiable. + The marked observers can then be notified and flushed on request. + + */ + class CCDBBranchObservingHandler{ + + enum{ + MAX_OBS_LST = 2 + }; + + public: + CCDBBranchObservingHandler(); + + ~CCDBBranchObservingHandler(); + + /// Notifies the observers, and flushes the list + void flushObserverCalls(); + + void reset(); + + void addBranchObserver( CCDBNodeBranch *branch, ICDBNode::IPropertyObserver *observer, const std::vector< std::string >& positiveLeafNameFilter ); + + void addBranchObserver( CCDBNodeBranch *branch, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer, const char **positiveLeafNameFilter, uint positiveLeafNameFilterSize ); + + void removeBranchObserver( CCDBNodeBranch *branch, ICDBNode::IPropertyObserver* observer ); + + void removeBranchObserver( CCDBNodeBranch *branch, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer ); + + + ///Observer for branch observer flush events. + class IBranchObserverCallFlushObserver : public CRefCount{ + public: + virtual ~IBranchObserverCallFlushObserver(){} + virtual void onObserverCallFlush() = 0; + }; + + private: + void triggerFlushObservers(); + + public: + void addFlushObserver( IBranchObserverCallFlushObserver *observer ); + void removeFlushObserver( IBranchObserverCallFlushObserver *observer ); + + private: + + /** + @brief Handle to a branch observer. + + The handle stores the owner branch, the observer and remembers if it's marked for notifying the observer. + Also it manages adding/removing itself to/from the marked observer handles list, which is handled by CCDBBranchObservingHandler. + + */ + class CCDBDBBranchObserverHandle : public CCDBNodeBranch::ICDBDBBranchObserverHandle{ + + public: + + CCDBDBBranchObserverHandle( ICDBNode::IPropertyObserver *observer, CCDBNodeBranch *owner, CCDBBranchObservingHandler *handler ); + + ~CCDBDBBranchObserverHandle(); + + ICDBNode* owner(){ return _owner; } + + ICDBNode::IPropertyObserver* observer(){ return _observer; } + + bool observesLeaf( const std::string &leafName ); + + bool inList( uint list ); + + void addToFlushableList(); + + void removeFromFlushableList( uint list ); + + void removeFromFlushableList(); + + private: + + bool _inList[ MAX_OBS_LST ]; + + std::vector< std::string > _observedLeaves; + + CCDBNodeBranch *_owner; + + NLMISC::CRefPtr< ICDBNode::IPropertyObserver > _observer; + + CCDBBranchObservingHandler *_handler; + + }; + + std::list< CCDBNodeBranch::ICDBDBBranchObserverHandle* > flushableObservers[ MAX_OBS_LST ]; + + CCDBNodeBranch::ICDBDBBranchObserverHandle *currentHandle; + + uint currentList; + + std::vector< IBranchObserverCallFlushObserver* > flushObservers; + + }; +} + +#endif + + diff --git a/code/ryzom/client/src/cdb_check_sum.h b/code/nel/include/nel/misc/cdb_check_sum.h similarity index 93% rename from code/ryzom/client/src/cdb_check_sum.h rename to code/nel/include/nel/misc/cdb_check_sum.h index df0cf3959..02ce8f18b 100644 --- a/code/ryzom/client/src/cdb_check_sum.h +++ b/code/nel/include/nel/misc/cdb_check_sum.h @@ -19,8 +19,9 @@ #ifndef NL_CDB_CHECK_SUM_H #define NL_CDB_CHECK_SUM_H -#include "nel/misc/types_nl.h" +#include "types_nl.h" +namespace NLMISC{ /** * class implementing check sum for the client database @@ -81,6 +82,7 @@ private: }; +} #endif // NL_CDB_CHECK_SUM_H diff --git a/code/ryzom/client/src/cdb_leaf.h b/code/nel/include/nel/misc/cdb_leaf.h similarity index 88% rename from code/ryzom/client/src/cdb_leaf.h rename to code/nel/include/nel/misc/cdb_leaf.h index d28cff2c2..566394d97 100644 --- a/code/ryzom/client/src/cdb_leaf.h +++ b/code/nel/include/nel/misc/cdb_leaf.h @@ -21,7 +21,10 @@ #include "cdb.h" #include "cdb_branch.h" -#include "nel/misc/time_nl.h" +#include "time_nl.h" +#include "rgba.h" + +namespace NLMISC{ /** * Database node which contains a unique property @@ -50,16 +53,16 @@ public: void setValue8 (sint8 prop); inline bool getValueBool() { return (_Property!=(sint64)0 ); } void setValueBool (bool prop); - inline NLMISC::CRGBA getValueRGBA() + inline CRGBA getValueRGBA() { - NLMISC::CRGBA col; + CRGBA col; col.R = (uint8)(_Property&0xff); col.G = (uint8)((_Property>>8)&0xff); col.B = (uint8)((_Property>>16)&0xff); col.A = (uint8)((_Property>>24)&0xff); return col; } - void setValueRGBA (const NLMISC::CRGBA &color); + void setValueRGBA (const CRGBA &color); /// Return the value of the property before the database change inline sint64 getOldValue64() { return _oldProperty; } @@ -98,7 +101,7 @@ public: * Build the structure of the database from a file * \param f is the stream */ - void init( xmlNodePtr node, NLMISC::IProgressCallback &progressCallBack, bool mapBanks=false ); + void init( xmlNodePtr node, IProgressCallback &progressCallBack, bool mapBanks=false, CCDBBankHandler *bankHandler = NULL ); /** * Get a node @@ -132,7 +135,7 @@ public: * Update the database from a stream coming from the FE * \param f : the stream. */ - void readDelta(NLMISC::TGameCycle gc, NLMISC::CBitMemStream & f ); + void readDelta(TGameCycle gc, CBitMemStream & f ); /** * Return the value of a property (the update flag is set to false) @@ -154,10 +157,10 @@ public: /** * Set the value of a property, only if gc>=_LastChangeGC */ - bool setPropCheckGC(NLMISC::TGameCycle gc, sint64 value); + bool setPropCheckGC(TGameCycle gc, sint64 value); /// Reset all leaf data from this point - void resetData(NLMISC::TGameCycle gc, bool forceReset=false); + void resetData(TGameCycle gc, bool forceReset=false); /** * Clear the node and his children @@ -215,7 +218,7 @@ public: /// get the last change GameCycle (server tick) for this value - NLMISC::TGameCycle getLastChangeGC() const {return _LastChangeGC;} + TGameCycle getLastChangeGC() const {return _LastChangeGC;} private: @@ -234,7 +237,7 @@ private: /// gamecycle (servertick) of the last change for this value. /// change are made in readDelta only for change >= _LastChangeGC - NLMISC::TGameCycle _LastChangeGC; + TGameCycle _LastChangeGC; /// observers to call when the value really change std::vector _Observers; @@ -249,7 +252,7 @@ private: //////////////////// - +} #endif // CDB_LEAF_H diff --git a/code/nel/include/nel/misc/cdb_manager.h b/code/nel/include/nel/misc/cdb_manager.h new file mode 100644 index 000000000..de6ccd20f --- /dev/null +++ b/code/nel/include/nel/misc/cdb_manager.h @@ -0,0 +1,184 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#ifndef CDB_MANAGER_H +#define CDB_MANAGER_H + +#include "nel/misc/cdb_branch.h" +#include "nel/misc/cdb_leaf.h" +#include "nel/misc/cdb_bank_handler.h" +#include "nel/misc/cdb_branch_observing_handler.h" + +namespace NLMISC{ + + /// Class that encapsulates the separate CDB components + class CCDBManager{ + + public: + /** + The constructor + @param maxBanks - The maximum number of banks to be used + + */ + CCDBManager( const char *rootNodeName, uint maxBanks ); + + ~CCDBManager(); + + + /** + Returns the specified leaf node from the database. + @param name The name of the leaf node. + @param create Specifies if the node should be created if it doesn't exist yet. + + */ + CCDBNodeLeaf* getDbLeaf( const std::string &name, bool create = true ); + + + + /** + Returns the specified branch node from the database. + @param name The name of the branch. + + */ + CCDBNodeBranch* getDbBranch( const std::string &name ); + + + /** + Deletes the specified database node. + @param name The name of the database node. + + */ + void delDbNode( const std::string &name ); + + /** + Adds an observer to a branch of the database. + @param branchName The name of the branch we want to observe + @param observer The observer we want to add + @param positiveLeafNameFilter A vector of strings containing the names of the leaves we want to observe + + */ + void addBranchObserver( const char *branchName, ICDBNode::IPropertyObserver *observer, const std::vector< std::string >& positiveLeafNameFilter = std::vector< std::string >() ); + + /** + Adds an observer to a branch of the database. + @param branch The branch we want to observe + @param observer The observer we want to add + @param positiveLeafNameFilter A vector of strings containing the names of the leaves we want to observe + + */ + void addBranchObserver( CCDBNodeBranch *branch, ICDBNode::IPropertyObserver *observer, const std::vector< std::string >& positiveLeafNameFilter = std::vector< std::string >() ); + + + /** + Adds an observer to a branch of the database. + @param branchName The name of the branch we start from + @param dbPathFromThisNode The path to the branch we want to observe + @param observer The observer we want to add + @param positiveLeafNameFilter An array of strings containing the names of the leaves we want to observe + @param positiveLeafNameFilterSize The size of the array + + */ + void addBranchObserver( const char *branchName, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer, const char **positiveLeafNameFilter = NULL, uint positiveLeafNameFilterSize = 0 ); + + + /** + Adds an observer to a branch of the database. + @param branch The branch we start from + @param dbPathFromThisNode The path to the branch we want to observe + @param observer The observer we want to add + @param positiveLeafNameFilter An array of strings containing the names of the leaves we want to observe + @param positiveLeafNameFilterSize The size of the array + + */ + void addBranchObserver( CCDBNodeBranch *branch, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer, const char **positiveLeafNameFilter, uint positiveLeafNameFilterSize ); + + + /** + Removes an observer from a branch in the database. + @param branchName The name of the branch + @param observer The observer we want to remove + + */ + void removeBranchObserver( const char *branchName, ICDBNode::IPropertyObserver* observer ); + + + /** + Removes an observer from a branch in the database. + @param branch The branch + @param observer The observer we want to remove + + */ + void removeBranchObserver( CCDBNodeBranch *branch, ICDBNode::IPropertyObserver* observer ); + + + /** + Removes an observer from a branch in the database. + @param branchName The name of the branch we start from + @param dbPathFromThisNode The path to the branch we want to observe from the starting branch + @param observer The observer we want to remove + + */ + void removeBranchObserver( const char *branchName, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer ); + + + /** + Removes an observer from a branch in the database. + @param branchName The name of the branch we start from + @param dbPathFromThisNode The path to the branch we want to observe from the starting branch + @param observer The observer we want to remove + + */ + void removeBranchObserver( CCDBNodeBranch *branch, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer ); + + + /** + Adds a branch observer call flush observer. ( These are notified after the branch observers are notified ) + @param observer The observer + + */ + void addFlushObserver( CCDBBranchObservingHandler::IBranchObserverCallFlushObserver *observer ); + + + /** + Removes a branch observer call flush observer. + @param observer The observer + */ + void removeFlushObserver( CCDBBranchObservingHandler::IBranchObserverCallFlushObserver *observer ); + + /** + Notifies the observers whose observed branches were updated. + */ + void flushObserverCalls(); + + /** + Resets the specified bank. + @param gc GameCycle ( no idea what it is exactly, probably some time value ) + @param bank The banks we want to reset + + */ + void resetBank( uint gc, uint bank ); + + protected: + CCDBBankHandler bankHandler; + CCDBBranchObservingHandler branchObservingHandler; + CRefPtr< CCDBNodeBranch > _Database; + }; + +} + +#endif + diff --git a/code/nel/include/nel/misc/common.h b/code/nel/include/nel/misc/common.h index 42bf56d3b..fa9272386 100644 --- a/code/nel/include/nel/misc/common.h +++ b/code/nel/include/nel/misc/common.h @@ -342,6 +342,8 @@ std::string secondsToHumanReadable (uint32 time); /// Get a bytes or time in string format and convert it in seconds or bytes uint32 fromHumanReadable (const std::string &str); +/// Add digit grouping seperator to if value >= 10 000. Assumes input is numerical string. +std::string formatThousands(const std::string& s); /// This function executes a program in the background and returns instantly (used for example to launch services in AES). /// The program will be launched in the current directory diff --git a/code/nel/include/nel/misc/diff_tool.h b/code/nel/include/nel/misc/diff_tool.h index 64989fcaf..2f291c6a0 100644 --- a/code/nel/include/nel/misc/diff_tool.h +++ b/code/nel/include/nel/misc/diff_tool.h @@ -501,8 +501,8 @@ namespace STRING_MANAGER // callback->onSwap(it - context.Reference.begin(), refCount, context); callback->onSwap(index, refCount, context); -// swap(*it, context.Reference[refCount]); - swap(context.Reference[index], context.Reference[refCount]); +// std::swap(*it, context.Reference[refCount]); + std::swap(context.Reference[index], context.Reference[refCount]); } } else if (getHashValue(context.Addition, addCount) != getHashValue(context.Reference, refCount)) diff --git a/code/nel/include/nel/misc/eid_translator.h b/code/nel/include/nel/misc/eid_translator.h index 695f7724e..9937b72fd 100644 --- a/code/nel/include/nel/misc/eid_translator.h +++ b/code/nel/include/nel/misc/eid_translator.h @@ -146,6 +146,8 @@ public: TAdditionalInfoCb EntityInfoCallback; + static void removeShardFromName(ucstring& name); + private: // get all eid for a user using the user name or the user id void getByUser (uint32 uid, std::vector &res); diff --git a/code/nel/include/nel/misc/event_emitter.h b/code/nel/include/nel/misc/event_emitter.h index e678c2674..5c547d07f 100644 --- a/code/nel/include/nel/misc/event_emitter.h +++ b/code/nel/include/nel/misc/event_emitter.h @@ -49,7 +49,7 @@ public: * \param server */ virtual void submitEvents(CEventServer & server, bool allWindows) = 0; - + /** * Instruct the event emitter to send CGDMouseMove instead of CEventMouseMove. * diff --git a/code/nel/include/nel/misc/fast_id_map.h b/code/nel/include/nel/misc/fast_id_map.h new file mode 100644 index 000000000..dbd05bc76 --- /dev/null +++ b/code/nel/include/nel/misc/fast_id_map.h @@ -0,0 +1,151 @@ +/** + * \file fast_id_map.h + * \brief CFastIdMap + * \date 2012-04-10 19:28GMT + * \author Jan Boon (Kaetemi) + * CFastIdMap + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLMISC_FAST_ID_MAP_H +#define NLMISC_FAST_ID_MAP_H +#include + +// STL includes + +// NeL includes +#include + +// Project includes + +namespace NLMISC { + +/** + * \brief CFastIdMap + * \date 2012-04-10 19:28GMT + * \author Jan Boon (Kaetemi) + * This template allows for assigning unique uint32 identifiers to pointers. + * Useful when externally only exposing an identifier, when pointers may have been deleted. + * The identifier is made from two uint16's, one being the direct index in the identifier vector, + * and the other being a verification value that is increased when the identifier index is re-used. + * TId must be a typedef of uint32. + * TValue should be a pointer. + */ +template +class CFastIdMap +{ +protected: + struct CIdInfo + { + CIdInfo() { } + CIdInfo(uint16 verification, uint16 next, TValue value) : + Verification(verification), Next(next), Value(value) { } + uint16 Verification; + uint16 Next; + TValue Value; + }; + /// ID memory + std::vector m_Ids; + /// Nb of assigned IDs + uint m_Size; + /// Assigned IDs + uint16 m_Next; + +public: + CFastIdMap(TValue defaultValue) : m_Size(0), m_Next(0) + { + // Id 0 will contain the last available unused id, and be 0 if no more unused id's are available + // defaultValue will be returned when the ID is not found + m_Ids.push_back(CIdInfo(0, 0, defaultValue)); + } + + virtual ~CFastIdMap() { } + + void clear() + { + m_Ids.resize(1); + m_Ids[0].Next = 0; + } + + TId insert(TValue value) + { + // get next unused index + uint16 idx = m_Ids[0].Next; + if (idx == 0) + { + // size of used elements must be equal to the vector size minus one, when everything is allocated + nlassert((m_Ids.size() - 1) == m_Size); + + idx = m_Ids.size(); + uint16 verification = rand(); + m_Ids.push_back(CIdInfo(verification, m_Next, value)); + m_Next = idx; + return (TId)(((uint32)verification) << 16) & idx; + } + else + { + m_Ids[0].Next = m_Ids[idx].Next; // restore the last unused id + m_Ids[idx].Value = value; + return (TId)(((uint32)m_Ids[idx].Verification) << 16) & idx; + } + } + + void erase(TId id) + { + uint32 idx = ((uint32)id) & 0xFFFF; + uint16 verification = (uint16)(((uint32)id) >> 16); + if (m_Ids[idx].Verification == verification) + { + m_Ids[idx].Value = m_Ids[0].Value; // clean value for safety + m_Ids[idx].Verification = (uint16)(((uint32)m_Ids[idx].Verification + 1) & 0xFFFF); // change verification value, allow overflow :) + m_Ids[idx].Next = m_Ids[0].Next; // store the last unused id + m_Ids[0].Next = (uint16)idx; // set this as last unused id + } + else + { + nlwarning("Invalid ID"); + } + } + + TValue get(TId id) + { + uint32 idx = ((uint32)id) & 0xFFFF; + uint16 verification = (uint16)(((uint32)id) >> 16); + if (m_Ids[idx].Verification == verification) + { + return m_Ids[idx].Value; + } + else + { + nldebug("Invalid ID"); + return m_Ids[0].Value; + } + } + + inline uint size() { return m_Size; } + +}; /* class CFastIdMap */ + +} /* namespace NLMISC */ + +#endif /* #ifndef NLMISC_FAST_ID_MAP_H */ + +/* end of file */ diff --git a/code/nel/include/nel/misc/mem_stream.h b/code/nel/include/nel/misc/mem_stream.h index 2102129dd..1da99a48d 100644 --- a/code/nel/include/nel/misc/mem_stream.h +++ b/code/nel/include/nel/misc/mem_stream.h @@ -301,7 +301,7 @@ public: * If you are using the stream only in output mode, you can use this method as a faster version * of clear() *if you don't serialize pointers*. */ - void resetBufPos() { _Buffer.Pos = 0; } + virtual void resetBufPos() { _Buffer.Pos = 0; } /** * Resize the message buffer and fill data at position 0. @@ -340,7 +340,7 @@ public: * fill it with raw data using any filling function (warning: don't fill more than 'msgsize' * bytes!), then you are ready to read, using serial(), the data you've just filled. */ - uint8 *bufferToFill( uint32 msgsize ) + virtual uint8 *bufferToFill( uint32 msgsize ) { #ifdef NL_DEBUG nlassert( isReading() ); diff --git a/code/nel/include/nel/misc/mutex.h b/code/nel/include/nel/misc/mutex.h index e2205d666..1c01a6134 100644 --- a/code/nel/include/nel/misc/mutex.h +++ b/code/nel/include/nel/misc/mutex.h @@ -717,11 +717,11 @@ class CAutoMutex TMutex &_Mutex; // forbeden copy or assignent - CAutoMutex(const CAutoMutex &other) + CAutoMutex(const CAutoMutex &/* other */) { } - CAutoMutex &operator = (const CAutoMutex &other) + CAutoMutex &operator = (const CAutoMutex &/* other */) { return *this; } diff --git a/code/nel/include/nel/misc/p_thread.h b/code/nel/include/nel/misc/p_thread.h index cd027aa37..7e6a4d5a5 100644 --- a/code/nel/include/nel/misc/p_thread.h +++ b/code/nel/include/nel/misc/p_thread.h @@ -36,6 +36,12 @@ namespace NLMISC { class CPThread : public IThread { public: + enum TThreadState + { + ThreadStateNone, + ThreadStateRunning, + ThreadStateFinished, + }; /// Constructor CPThread( IRunnable *runnable, uint32 stackSize); @@ -48,6 +54,7 @@ public: virtual void wait(); virtual bool setCPUMask(uint64 cpuMask); virtual uint64 getCPUMask(); + virtual void setPriority(TThreadPriority priority); virtual std::string getUserName(); virtual IRunnable *getRunnable() @@ -58,10 +65,11 @@ public: /// Internal use IRunnable *Runnable; -private: - uint8 _State; // 0=not created, 1=started, 2=finished - uint32 _StackSize; + TThreadState _State; pthread_t _ThreadHandle; + +private: + uint32 _StackSize; }; /** diff --git a/code/nel/include/nel/misc/speaker_listener.h b/code/nel/include/nel/misc/speaker_listener.h index 7481e12c8..5f9f86352 100644 --- a/code/nel/include/nel/misc/speaker_listener.h +++ b/code/nel/include/nel/misc/speaker_listener.h @@ -118,7 +118,7 @@ namespace NLMISC _Speaker->registerListener(this); } - void unregisterListener(ISpeaker *speaker) + void unregisterListener(ISpeaker * /* speaker */) { nlassert(_Speaker != NULL); _Speaker->unregisterListener(this); diff --git a/code/nel/include/nel/misc/sstring.h b/code/nel/include/nel/misc/sstring.h index 513c681c9..fc17d67f5 100644 --- a/code/nel/include/nel/misc/sstring.h +++ b/code/nel/include/nel/misc/sstring.h @@ -55,7 +55,7 @@ public: /// ctor CSString(int i,const char *fmt="%d"); /// ctor - CSString(unsigned u,const char *fmt="%u"); + CSString(uint32 u,const char *fmt="%u"); /// ctor CSString(double d,const char *fmt="%f"); /// ctor @@ -76,14 +76,14 @@ public: char back() const; /// Return the n left hand most characters of a string - CSString left(unsigned count) const; + CSString left(uint32 count) const; /// Return the n right hand most characters of a string - CSString right(unsigned count) const; + CSString right(uint32 count) const; /// Return the string minus the n left hand most characters of a string - CSString leftCrop(unsigned count) const; + CSString leftCrop(uint32 count) const; /// Return the string minus the n right hand most characters of a string - CSString rightCrop(unsigned count) const; + CSString rightCrop(uint32 count) const; /// Return sub string up to but not including first instance of given character, starting at 'iterator' /// on exit 'iterator' indexes first character after extracted string segment @@ -116,9 +116,9 @@ public: /// Return sub string remaining after the first word CSString tailFromFirstWord() const; /// Count the number of words in a string - unsigned countWords() const; + uint32 countWords() const; /// Extract the given word - CSString word(unsigned idx) const; + CSString word(uint32 idx) const; /// Return first word or quote-encompassed sub-string - can remove extracted sub-string from source string CSString firstWordOrWords(bool truncateThis=false,bool useSlashStringEscape=true,bool useRepeatQuoteStringEscape=true); @@ -127,9 +127,9 @@ public: /// Return sub string following first word (or quote-encompassed sub-string) CSString tailFromFirstWordOrWords(bool useSlashStringEscape=true,bool useRepeatQuoteStringEscape=true) const; /// Count the number of words (or quote delimited sub-strings) in a string - unsigned countWordOrWords(bool useSlashStringEscape=true,bool useRepeatQuoteStringEscape=true) const; + uint32 countWordOrWords(bool useSlashStringEscape=true,bool useRepeatQuoteStringEscape=true) const; /// Extract the given words (or quote delimited sub-strings) - CSString wordOrWords(unsigned idx,bool useSlashStringEscape=true,bool useRepeatQuoteStringEscape=true) const; + CSString wordOrWords(uint32 idx,bool useSlashStringEscape=true,bool useRepeatQuoteStringEscape=true) const; /// Return first line - can remove extracted line from source string CSString firstLine(bool truncateThis=false); @@ -138,9 +138,9 @@ public: /// Return sub string remaining after the first line CSString tailFromFirstLine() const; /// Count the number of lines in a string - unsigned countLines() const; + uint32 countLines() const; /// Extract the given line - CSString line(unsigned idx) const; + CSString line(uint32 idx) const; /// A handy utility routine for knowing if a character is a white space character or not (' ','\t','\n','\r',26) static bool isWhiteSpace(char c); @@ -377,7 +377,7 @@ public: /// assignment operator CSString& operator=(int i); /// assignment operator - CSString& operator=(unsigned u); + CSString& operator=(uint32 u); /// assignment operator CSString& operator=(double d); @@ -561,7 +561,7 @@ inline CSString::CSString(int i,const char *fmt) *this=buf; } -inline CSString::CSString(unsigned u,const char *fmt) +inline CSString::CSString(uint32 u,const char *fmt) { char buf[1024]; sprintf(buf,fmt,u); @@ -611,26 +611,26 @@ inline char CSString::back() const return (*this)[size()-1]; } -inline CSString CSString::right(unsigned count) const +inline CSString CSString::right(uint32 count) const { if (count>=size()) return *this; return substr(size()-count); } -inline CSString CSString::rightCrop(unsigned count) const +inline CSString CSString::rightCrop(uint32 count) const { if (count>=size()) return CSString(); return substr(0,size()-count); } -inline CSString CSString::left(unsigned count) const +inline CSString CSString::left(uint32 count) const { return substr(0,count); } -inline CSString CSString::leftCrop(unsigned count) const +inline CSString CSString::leftCrop(uint32 count) const { if (count>=size()) return CSString(); @@ -639,7 +639,7 @@ inline CSString CSString::leftCrop(unsigned count) const inline CSString CSString::splitToWithIterator(char c,uint32& iterator) const { - unsigned i; + uint32 i; CSString result; for (i=iterator;i !!! - CSTLBlockAllocator(CBlockMemory *bm) + CSTLBlockAllocator(CBlockMemory * /* bm */) { } /// copy ctor diff --git a/code/nel/include/nel/misc/thread.h b/code/nel/include/nel/misc/thread.h index 82ef78a13..983e6b12a 100644 --- a/code/nel/include/nel/misc/thread.h +++ b/code/nel/include/nel/misc/thread.h @@ -68,6 +68,16 @@ public: } }; +/// Thread priorities, numbering follows Win32 for now +enum TThreadPriority +{ + ThreadPriorityLowest = -2, + ThreadPriorityLow = -1, + ThreadPriorityNormal = 0, + ThreadPriorityHigh = 1, + ThreadPriorityHighest = 2, +}; + /** * Thread base interface, must be implemented for all OS * \author Vianney Lecroart @@ -119,6 +129,9 @@ public: */ virtual uint64 getCPUMask()=0; + /// Set the thread priority. Thread must have been started before. + virtual void setPriority(TThreadPriority priority) = 0; + /** * Get the thread user name. * Under Linux return thread owner, under windows return the name of the logon user. diff --git a/code/nel/include/nel/misc/win_thread.h b/code/nel/include/nel/misc/win_thread.h index a2e8b5389..628942dde 100644 --- a/code/nel/include/nel/misc/win_thread.h +++ b/code/nel/include/nel/misc/win_thread.h @@ -49,6 +49,7 @@ public: virtual void wait(); virtual bool setCPUMask(uint64 cpuMask); virtual uint64 getCPUMask(); + virtual void setPriority(TThreadPriority priority); virtual std::string getUserName(); virtual IRunnable *getRunnable() @@ -69,8 +70,7 @@ public: void suspend(); // Resume the thread. No-op if already resumed void resume(); - // set priority as defined by "SetThreadpriority" - void setPriority(int priority); + // Priority boost void enablePriorityBoost(bool enabled); /// private use diff --git a/code/nel/include/nel/net/Makefile.am b/code/nel/include/nel/net/Makefile.am deleted file mode 100644 index 7a5dfe1be..000000000 --- a/code/nel/include/nel/net/Makefile.am +++ /dev/null @@ -1,48 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -includedir = ${prefix}/include/nel/net - -include_HEADERS = admin.h \ - buf_client.h \ - buf_net_base.h \ - buf_server.h \ - buf_sock.h \ - callback_client.h \ - callback_net_base.h \ - callback_server.h \ - cvar_log_filter.h \ - dummy_tcp_sock.h \ - email.h \ - inet_address.h \ - listen_sock.h \ - login_client.h \ - login_cookie.h \ - login_server.h \ - message.h \ - message_recorder.h \ - module_builder_parts.h \ - module_common.h \ - module_gateway.h \ - module.h \ - module_manager.h \ - module_message.h \ - module_socket.h \ - naming_client.h \ - net_displayer.h \ - net_log.h \ - net_manager.h \ - pacs_client.h \ - service.h \ - sock.h \ - tcp_sock.h \ - transport_class.h \ - udp_sim_sock.h \ - udp_sock.h \ - unified_network.h \ - unitime.h \ - varpath.h - -# End of Makefile.am diff --git a/code/nel/include/nel/net/module_builder_parts.h b/code/nel/include/nel/net/module_builder_parts.h index b080e521f..d2e653315 100644 --- a/code/nel/include/nel/net/module_builder_parts.h +++ b/code/nel/include/nel/net/module_builder_parts.h @@ -188,8 +188,8 @@ namespace NLNET // unused interceptors std::string fwdBuildModuleManifest() const { return std::string(); } - void fwdOnModuleSecurityChange(NLNET::IModuleProxy *moduleProxy) {} - bool fwdOnProcessModuleMessage(NLNET::IModuleProxy *sender, const NLNET::CMessage &message) {return false;} + void fwdOnModuleSecurityChange(NLNET::IModuleProxy * /* moduleProxy */) {} + bool fwdOnProcessModuleMessage(NLNET::IModuleProxy * /* sender */, const NLNET::CMessage &/* message */) {return false;} // check module up void fwdOnModuleUp(NLNET::IModuleProxy *moduleProxy) diff --git a/code/nel/include/nel/net/transport_class.h b/code/nel/include/nel/net/transport_class.h index 11161d7cd..f1110e492 100644 --- a/code/nel/include/nel/net/transport_class.h +++ b/code/nel/include/nel/net/transport_class.h @@ -75,7 +75,7 @@ public: enum TProp { PropUInt8, PropUInt16, PropUInt32, PropUInt64, PropSInt8, PropSInt16, PropSInt32, PropSInt64, - PropBool, PropFloat, PropDouble, PropString, PropDataSetRow, PropSheetId, PropUKN }; + PropBool, PropFloat, PropDouble, PropString, PropDataSetRow, PropSheetId, PropUCString, PropUKN }; // PropBool, PropFloat, PropDouble, PropString, PropDataSetRow, PropEntityId, PropSheetId, PropUKN }; @@ -160,6 +160,7 @@ public: case PropString: nlassert(sizeof(T) == sizeof (std::string)); break; // case PropEntityId: nlassert(sizeof(T) == sizeof (NLMISC::CEntityId)); break; case PropSheetId: nlassert(sizeof(T) == sizeof (NLMISC::CSheetId)); break; + case PropUCString: nlassert(sizeof(T) == sizeof (ucstring)); break; default: nlerror ("property %s have unknown type %d", name.c_str(), type); } @@ -334,7 +335,7 @@ protected: T *Value; - virtual void serialDefaultValue (NLMISC::IStream &f) + virtual void serialDefaultValue (NLMISC::IStream &/* f */) { // nothing } diff --git a/code/nel/include/nel/pacs/Makefile.am b/code/nel/include/nel/pacs/Makefile.am deleted file mode 100644 index 7160ec65c..000000000 --- a/code/nel/include/nel/pacs/Makefile.am +++ /dev/null @@ -1,17 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -includedir = ${prefix}/include/nel/pacs - -include_HEADERS = u_collision_desc.h \ - u_global_position.h \ - u_global_retriever.h \ - u_move_container.h \ - u_move_primitive.h \ - u_primitive_block.h \ - u_retriever_bank.h - -# End of Makefile.am - diff --git a/code/nel/include/nel/sound/Makefile.am b/code/nel/include/nel/sound/Makefile.am deleted file mode 100644 index 77dd246a6..000000000 --- a/code/nel/include/nel/sound/Makefile.am +++ /dev/null @@ -1,15 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -includedir = ${prefix}/include/nel/sound - -include_HEADERS = sound_animation.h \ - sound_anim_manager.h \ - sound_anim_marker.h \ - u_audio_mixer.h \ - u_listener.h \ - u_source.h - -# End of Makefile.am diff --git a/code/nel/include/nel/sound/audio_decoder.h b/code/nel/include/nel/sound/audio_decoder.h new file mode 100644 index 000000000..3babc1de1 --- /dev/null +++ b/code/nel/include/nel/sound/audio_decoder.h @@ -0,0 +1,107 @@ +/** + * \file audio_decoder.h + * \brief IAudioDecoder + * \date 2012-04-11 09:34GMT + * \author Jan Boon (Kaetemi) + * IAudioDecoder + */ + +/* + * Copyright (C) 2008-2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_AUDIO_DECODER_H +#define NLSOUND_AUDIO_DECODER_H +#include + +// STL includes + +// NeL includes + +// Project includes + +namespace NLSOUND { + +/** + * \brief IAudioDecoder + * \date 2008-08-30 11:38GMT + * \author Jan Boon (Kaetemi) + * IAudioDecoder is only used by the driver implementation to stream + * music files into a readable format (it's a simple decoder interface). + * You should not call these functions (getSongTitle) on nlsound or user level, + * as a driver might have additional music types implemented. + * TODO: Split IAudioDecoder into IAudioDecoder (actual decoding) and IMediaDemuxer (stream splitter), and change the interface to make more sense. + * TODO: Allow user application to register more decoders. + * TODO: Look into libavcodec for decoding audio? + */ +class IAudioDecoder +{ +private: + // pointers + /// Stream from file created by IAudioDecoder + NLMISC::IStream *_InternalStream; + +public: + IAudioDecoder(); + virtual ~IAudioDecoder(); + + /// Create a new music buffer, may return NULL if unknown type, destroy with delete. Filepath lookup done here. If async is true, it will stream from hd, else it will load in memory first. + static IAudioDecoder *createAudioDecoder(const std::string &filepath, bool async, bool loop); + + /// Create a new music buffer from a stream, type is file extension like "ogg" etc. + static IAudioDecoder *createAudioDecoder(const std::string &type, NLMISC::IStream *stream, bool loop); + + /// Get information on a music file (only artist and title at the moment). + static bool getInfo(const std::string &filepath, std::string &artist, std::string &title); + + /// Get audio/container extensions that are currently supported by the nel sound library. + static void getMusicExtensions(std::vector &extensions); + + /// Return if a music extension is supported by the nel sound library. + static bool isMusicExtensionSupported(const std::string &extension); + + /// Get how many bytes the music buffer requires for output minimum. + virtual uint32 getRequiredBytes() = 0; + + /// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end). + virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) = 0; + + /// Get the amount of channels (2 is stereo) in output. + virtual uint8 getChannels() = 0; + + /// Get the samples per second (often 44100) in output. + virtual uint getSamplesPerSec() = 0; + + /// Get the bits per sample (often 16) in output. + virtual uint8 getBitsPerSample() = 0; + + /// Get if the music has ended playing (never true if loop). + virtual bool isMusicEnded() = 0; + + /// Get the total time in seconds. + virtual float getLength() = 0; + + /// Set looping + virtual void setLooping(bool loop) = 0; +}; /* class IAudioDecoder */ + +} /* namespace NLSOUND */ + +#endif /* #ifndef NLSOUND_AUDIO_DECODER_H */ + +/* end of file */ diff --git a/code/nel/include/nel/sound/driver/music_buffer_vorbis.h b/code/nel/include/nel/sound/audio_decoder_vorbis.h similarity index 50% rename from code/nel/include/nel/sound/driver/music_buffer_vorbis.h rename to code/nel/include/nel/sound/audio_decoder_vorbis.h index 978f393da..bd7e45019 100644 --- a/code/nel/include/nel/sound/driver/music_buffer_vorbis.h +++ b/code/nel/include/nel/sound/audio_decoder_vorbis.h @@ -1,21 +1,33 @@ -// NeL - MMORPG Framework -// Copyright (C) 2010 Winch Gate Property Limited -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . +/** + * \file audio_decoder_vorbis.h + * \brief CAudioDecoderVorbis + * \date 2012-04-11 09:35GMT + * \author Jan Boon (Kaetemi) + * CAudioDecoderVorbis + */ -#ifndef NLSOUND_MUSIC_BUFFER_VORBIS_H -#define NLSOUND_MUSIC_BUFFER_VORBIS_H +/* + * Copyright (C) 2008-2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_AUDIO_DECODER_VORBIS_H +#define NLSOUND_AUDIO_DECODER_VORBIS_H +#include // STL includes @@ -30,21 +42,20 @@ #endif // NeL includes +#include // Project includes -#include "music_buffer.h" -namespace NLSOUND -{ +namespace NLSOUND { /** - * \brief CMusicBufferVorbis + * \brief CAudioDecoderVorbis * \date 2008-08-30 11:38GMT * \author Jan Boon (Kaetemi) - * CMusicBufferVorbis - * Create trough IMusicBuffer, type "ogg" + * CAudioDecoderVorbis + * Create trough IAudioDecoder, type "ogg" */ -class CMusicBufferVorbis : public IMusicBuffer +class CAudioDecoderVorbis : public IAudioDecoder { protected: // outside pointers @@ -59,8 +70,8 @@ protected: sint32 _StreamOffset; sint32 _StreamSize; public: - CMusicBufferVorbis(NLMISC::IStream *stream, bool loop); - virtual ~CMusicBufferVorbis(); + CAudioDecoderVorbis(NLMISC::IStream *stream, bool loop); + virtual ~CAudioDecoderVorbis(); inline NLMISC::IStream *getStream() { return _Stream; } inline sint32 getStreamSize() { return _StreamSize; } inline sint32 getStreamOffset() { return _StreamOffset; } @@ -78,7 +89,7 @@ public: virtual uint8 getChannels(); /// Get the samples per second (often 44100) in output. - virtual uint32 getSamplesPerSec(); + virtual uint getSamplesPerSec(); /// Get the bits per sample (often 16) in output. virtual uint8 getBitsPerSample(); @@ -89,12 +100,12 @@ public: /// Get the total time in seconds. virtual float getLength(); - /// Get the size of uncompressed data in bytes. - virtual uint getUncompressedSize(); -}; /* class CMusicBufferVorbis */ + /// Set looping + virtual void setLooping(bool loop); +}; /* class CAudioDecoderVorbis */ } /* namespace NLSOUND */ -#endif /* #ifndef NLSOUND_MUSIC_BUFFER_VORBIS_H */ +#endif /* #ifndef NLSOUND_AUDIO_DECODER_VORBIS_H */ /* end of file */ diff --git a/code/nel/include/nel/sound/audio_mixer_user.h b/code/nel/include/nel/sound/audio_mixer_user.h index 7cd0de051..9c9fd5c86 100644 --- a/code/nel/include/nel/sound/audio_mixer_user.h +++ b/code/nel/include/nel/sound/audio_mixer_user.h @@ -34,6 +34,11 @@ #include "nel/sound/mixing_track.h" #include "nel/sound/sound.h" #include "nel/sound/music_channel_fader.h" +#include "nel/sound/group_controller_root.h" + +// Current version is 2, Ryzom Live uses 1 +// Provided to allow compatibility with old binary files +#define NLSOUND_SHEET_VERSION_BUILT 1 namespace NLLIGO { class CLigoConfig; @@ -51,26 +56,6 @@ namespace NLSOUND { class CMusicSoundManager; class IReverbEffect; -/// Hasher functor for hashed container with pointer key. -template -struct THashPtr : public std::unary_function -{ - static const size_t bucket_size = 4; - static const size_t min_buckets = 8; - size_t operator () (const Pointer &ptr) const - { - //CHashSet::hasher h; - // transtype the pointer into int then hash it - //return h.operator()(uint(uintptr_t(ptr))); - return (size_t)(uintptr_t)ptr; - } - inline bool operator() (const Pointer &ptr1, const Pointer &ptr2) const - { - // delegate the work to someone else as well? - return (uintptr_t)ptr1 < (uintptr_t)ptr2; - } -}; - /** * Implementation of UAudioMixer * @@ -197,6 +182,9 @@ public: /// Get a TSoundId from a name (returns NULL if not found) virtual TSoundId getSoundId( const NLMISC::TStringId &name ); + /// Gets the group controller for the given group tree path with separator '/', if it doesn't exist yet it will be created. + /// Examples: "music", "effects", "dialog", "music/background", "music/loading", "music/player", etcetera + virtual UGroupController *getGroupController(const std::string &path); /** Add a logical sound source (returns NULL if name not found). * If spawn is true, the source will auto-delete after playing. If so, the return USource* pointer @@ -204,9 +192,9 @@ public: * pass a callback function that will be called (if not NULL) just before deleting the spawned * source. */ - virtual USource *createSource( const NLMISC::TStringId &name, bool spawn=false, TSpawnEndCallback cb=NULL, void *cbUserParam = NULL, NL3D::CCluster *cluster = 0, CSoundContext *context = 0 ); + virtual USource *createSource( const NLMISC::TStringId &name, bool spawn=false, TSpawnEndCallback cb=NULL, void *cbUserParam = NULL, NL3D::CCluster *cluster = 0, CSoundContext *context = 0, UGroupController *groupController = NULL); /// Add a logical sound source (by sound id). To remove a source, just delete it. See createSource(const char*) - virtual USource *createSource( TSoundId id, bool spawn=false, TSpawnEndCallback cb=NULL, void *cbUserParam = NULL, NL3D::CCluster *cluster = 0, CSoundContext *context = 0 ); + virtual USource *createSource( TSoundId id, bool spawn=false, TSpawnEndCallback cb=NULL, void *cbUserParam = NULL, NL3D::CCluster *cluster = 0, CSoundContext *context = 0, UGroupController *groupController = NULL); /// Add a source which was created by an EnvSound void addSource( CSourceCommon *source ); /** Delete a logical sound source. If you don't call it, the source will be auto-deleted @@ -242,6 +230,8 @@ public: virtual uint getSourcesInstanceCount() const { return (uint)_Sources.size(); } /// Return the number of playing sources (slow) virtual uint getPlayingSourcesCount() const; + uint countPlayingSimpleSources() const; // debug + uint countSimpleSources() const; // debug /// Return the number of available tracks virtual uint getAvailableTracksCount() const; /// Return the number of used tracks @@ -415,6 +405,7 @@ public: /// Add a source for play as possible (for non discadable sound) void addSourceWaitingForPlay(CSourceCommon *source); + void removeSourceWaitingForPlay(CSourceCommon *source); /// Read all user controled var sheets void initUserVar(); @@ -431,8 +422,6 @@ private: // utility function for automatic sample bank loading. bool tryToLoadSampleBank(const std::string &sampleName); - - typedef CHashSet > TSourceContainer; typedef CHashSet > TMixerUpdateContainer; typedef CHashMap, THashPtr > TBufferToSourceContainer; // typedef std::multimap TTimedEventContainer; @@ -565,6 +554,9 @@ private: // Instance of the background music manager CMusicSoundManager *_BackgroundMusicManager; + /// Group controller + CGroupControllerRoot _GroupController; + public: struct TSampleBankHeader { diff --git a/code/nel/include/nel/sound/background_source.h b/code/nel/include/nel/sound/background_source.h index cdf044776..14ea1cf53 100644 --- a/code/nel/include/nel/sound/background_source.h +++ b/code/nel/include/nel/sound/background_source.h @@ -36,7 +36,7 @@ class CBackgroundSource : public CSourceCommon , public CAudioMixerUser::IMixerU { public: /// Constructor - CBackgroundSource (CBackgroundSound *backgroundSound=NULL, bool spawn=false, TSpawnEndCallback cb=0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0); + CBackgroundSource (CBackgroundSound *backgroundSound=NULL, bool spawn=false, TSpawnEndCallback cb=0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0, CGroupController *groupController = NULL); /// Destructor ~CBackgroundSource (); diff --git a/code/nel/include/nel/sound/complex_source.h b/code/nel/include/nel/sound/complex_source.h index d27b5af5b..d1135b1ad 100644 --- a/code/nel/include/nel/sound/complex_source.h +++ b/code/nel/include/nel/sound/complex_source.h @@ -34,7 +34,7 @@ class CComplexSource : public CSourceCommon, public CAudioMixerUser::IMixerEvent { public: /// Constructor - CComplexSource (CComplexSound *soundPattern=NULL, bool spawn=false, TSpawnEndCallback cb=0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0); + CComplexSource (CComplexSound *soundPattern=NULL, bool spawn=false, TSpawnEndCallback cb=0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0, CGroupController *groupController = NULL); /// Destructor ~CComplexSource (); diff --git a/code/nel/include/nel/sound/containers.h b/code/nel/include/nel/sound/containers.h new file mode 100644 index 000000000..bd4fe7fb9 --- /dev/null +++ b/code/nel/include/nel/sound/containers.h @@ -0,0 +1,67 @@ +/** + * \file containers.h + * \brief CContainers + * \date 2012-04-10 13:57GMT + * \author Unknown (Unknown) + * CContainers + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_CONTAINERS_H +#define NLSOUND_CONTAINERS_H +#include + +// STL includes + +// NeL includes + +// Project includes + +namespace NLSOUND { + class CSourceCommon; + +/// Hasher functor for hashed container with pointer key. +template +struct THashPtr : public std::unary_function +{ + static const size_t bucket_size = 4; + static const size_t min_buckets = 8; + size_t operator () (const Pointer &ptr) const + { + //CHashSet::hasher h; + // transtype the pointer into int then hash it + //return h.operator()(uint(uintptr_t(ptr))); + return (size_t)(uintptr_t)ptr; + } + inline bool operator() (const Pointer &ptr1, const Pointer &ptr2) const + { + // delegate the work to someone else as well? + return (uintptr_t)ptr1 < (uintptr_t)ptr2; + } +}; + +typedef CHashSet > TSourceContainer; + +} /* namespace NLSOUND */ + +#endif /* #ifndef NLSOUND_CONTAINERS_H */ + +/* end of file */ diff --git a/code/nel/include/nel/sound/driver/buffer.h b/code/nel/include/nel/sound/driver/buffer.h index 5d488f1c7..27a8d9f00 100644 --- a/code/nel/include/nel/sound/driver/buffer.h +++ b/code/nel/include/nel/sound/driver/buffer.h @@ -48,6 +48,8 @@ public: /// Intel/DVI ADPCM format, only available for 1 channel at 16 bits per sample, encoded at 4 bits per sample. /// This is only implemented in the DSound and XAudio2 driver. FormatDviAdpcm = 11, + /// No format set. Used when a TBufferFormat value has not been set to any value yet. + FormatNotSet = (~0), }; /// The storage mode of this buffer. Also controls the X-RAM extension of OpenAL. enum TStorageMode diff --git a/code/nel/include/nel/sound/driver/music_buffer.h b/code/nel/include/nel/sound/driver/music_buffer.h deleted file mode 100644 index 571e27a31..000000000 --- a/code/nel/include/nel/sound/driver/music_buffer.h +++ /dev/null @@ -1,119 +0,0 @@ -// NeL - MMORPG Framework -// Copyright (C) 2010 Winch Gate Property Limited -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -#ifndef NLSOUND_MUSIC_BUFFER_H -#define NLSOUND_MUSIC_BUFFER_H - -namespace NLMISC -{ - class IStream; - class CIFile; -} - -namespace NLSOUND -{ - - /* - * TODO: Streaming - * Some kind of decent streaming functionality, to get rid of the current music implementation. Audio decoding should be done on nlsound level. IBuffer needs a writable implementation, it allocates and owns the data memory, which can be written to by nlsound. When buffer is written, a function needs to be called to 'finalize' the buffer (so it can be submitted to OpenAL for example). - * Required interface functions, IBuffer: - * /// Allocate a new writable buffer. If this buffer was already allocated, the previous data is released. - * /// May return NULL if the format or frequency is not supported by the driver. - * uint8 *IBuffer::openWritable(uint size, TBufferFormat bufferFormat, uint8 channels, uint8 bitsPerSample, uint32 frequency); - * /// Tell that you are done writing to this buffer, so it can be copied over to hardware if needed. - * /// If keepLocal is true, a local copy of the buffer will be kept (so allocation can be re-used later). - * /// keepLocal overrides the OptionLocalBufferCopy flag. The buffer can use this function internally. - * void IBuffer::lockWritable(bool keepLocal); - * Required interface functions, ISource: - * /// Enable or disable the streaming facilities. - * void ISource::setStreaming(bool streaming); - * /// Submits a new buffer to the stream. A buffer of 100ms length is optimal for streaming. - * /// Should be called by a thread which checks countStreamingBuffers every 100ms - * void ISource::submitStreamingBuffer(IBuffer *buffer); - * /// Returns the number of buffers that are queued (includes playing buffer). 3 buffers is optimal. - * uint ISource::countStreamingBuffers(); - * Other required interface functions, ISource: - * /// Enable or disable 3d calculations (to send directly to speakers). - * void ISource::set3DMode(bool enable); - * For compatibility with music trough fmod, ISoundDriver: - * /// Returns true if the sound driver has a native implementation of IMusicChannel (bad!). - * /// If this returns false, use the nlsound music channel, which goes trough Ctrack/ISource, - * /// The nlsound music channel requires support for IBuffer/ISource streaming. - * bool ISoundDriver::hasMusicChannel(); - */ - -/** - * \brief IMusicBuffer - * \date 2008-08-30 11:38GMT - * \author Jan Boon (Kaetemi) - * IMusicBuffer is only used by the driver implementation to stream - * music files into a readable format (it's a simple decoder interface). - * You should not call these functions (getSongTitle) on nlsound or user level, - * as a driver might have additional music types implemented. - * TODO: Change IMusicBuffer to IAudioDecoder, and change the interface to make more sense. - * TODO: Allow user application to register more decoders. - * TODO: Look into libavcodec for decoding audio. - */ -class IMusicBuffer -{ -private: - // pointers - /// Stream from file created by IMusicBuffer - NLMISC::IStream *_InternalStream; - -public: - IMusicBuffer(); - virtual ~IMusicBuffer(); - - /// Create a new music buffer, may return NULL if unknown type, destroy with delete. Filepath lookup done here. If async is true, it will stream from hd, else it will load in memory first. - static IMusicBuffer *createMusicBuffer(const std::string &filepath, bool async, bool loop); - - /// Create a new music buffer from a stream, type is file extension like "ogg" etc. - static IMusicBuffer *createMusicBuffer(const std::string &type, NLMISC::IStream *stream, bool loop); - - /// Get information on a music file (only artist and title at the moment). - static bool getInfo(const std::string &filepath, std::string &artist, std::string &title); - - /// Get how many bytes the music buffer requires for output minimum. - virtual uint32 getRequiredBytes() =0; - - /// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end). - virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) =0; - - /// Get the amount of channels (2 is stereo) in output. - virtual uint8 getChannels() =0; - - /// Get the samples per second (often 44100) in output. - virtual uint32 getSamplesPerSec() =0; - - /// Get the bits per sample (often 16) in output. - virtual uint8 getBitsPerSample() =0; - - /// Get if the music has ended playing (never true if loop). - virtual bool isMusicEnded() =0; - - /// Get the total time in seconds. - virtual float getLength() =0; - - /// Get the size of uncompressed data in bytes. - virtual uint getUncompressedSize() =0; -}; /* class IMusicBuffer */ - -} /* namespace NLSOUND */ - -#endif /* #ifndef NLSOUND_MUSIC_BUFFER_H */ - -/* end of file */ diff --git a/code/nel/include/nel/sound/driver/music_channel.h b/code/nel/include/nel/sound/driver/music_channel.h index 9878744c5..116865628 100644 --- a/code/nel/include/nel/sound/driver/music_channel.h +++ b/code/nel/include/nel/sound/driver/music_channel.h @@ -45,6 +45,9 @@ public: /// Stop the music previously loaded and played (the Memory is also freed) virtual void stop() =0; + /// Makes sure any resources are freed, but keeps available for next play call + virtual void reset() =0; + /// Pause the music previously loaded and played (the Memory is not freed) virtual void pause() =0; diff --git a/code/nel/include/nel/sound/driver/sound_driver.h b/code/nel/include/nel/sound/driver/sound_driver.h index 193026a42..c9551f16b 100644 --- a/code/nel/include/nel/sound/driver/sound_driver.h +++ b/code/nel/include/nel/sound/driver/sound_driver.h @@ -39,9 +39,11 @@ namespace NLSOUND #endif /* - * Sound sample format + * Deprecated sound sample format. + * For compatibility with old code. + * Do not modify. */ -enum TSampleFormat { SampleFormatUnknown, Mono8, Mono16ADPCM, Mono16, Stereo8, Stereo16 }; +enum TSampleFormat { Mono8, Mono16ADPCM, Mono16, Stereo8, Stereo16, SampleFormatUnknown = (~0) }; /** * Abstract sound driver (implemented in sound driver dynamic library) diff --git a/code/nel/include/nel/sound/driver/source.h b/code/nel/include/nel/sound/driver/source.h index 1858e6f24..b43c83914 100644 --- a/code/nel/include/nel/sound/driver/source.h +++ b/code/nel/include/nel/sound/driver/source.h @@ -353,7 +353,7 @@ public: * In streaming mode, the time spent during buffer outruns is not * counted towards the playback time, and the playback time is * be the current time position in the entire submitted queue. - * When using static buffers, the result is the tot time that the + * When using static buffers, the result is the total time that the * attached buffer has been playing. If the source is looping, the * time will be the total of all playbacks of the buffer. * When the source is stopped, this will return the time where the @@ -397,7 +397,7 @@ public: virtual void setSourceRelativeMode(bool mode = true) = 0; /// Get the source relative mode virtual bool getSourceRelativeMode() const = 0; - /// Set the min and max distances (default: 1, MAX_FLOAT) (3D mode only) + /// Set the min and max distances (default: 1, sqrt(MAX_FLOAT)) (3D mode only) virtual void setMinMaxDistances(float mindist, float maxdist, bool deferred = true) = 0; /// Get the min and max distances virtual void getMinMaxDistances(float& mindist, float& maxdist) const = 0; diff --git a/code/nel/include/nel/sound/group_controller.h b/code/nel/include/nel/sound/group_controller.h new file mode 100644 index 000000000..25b7034b6 --- /dev/null +++ b/code/nel/include/nel/sound/group_controller.h @@ -0,0 +1,102 @@ +/** + * \file group_controller.h + * \brief CGroupController + * \date 2012-04-10 09:29GMT + * \author Jan Boon (Kaetemi) + * CGroupController + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_GROUP_CONTROLLER_H +#define NLSOUND_GROUP_CONTROLLER_H +#include + +// STL includes +#include +#include + +// NeL includes +#include +#include +#include + +// Project includes + +namespace NLSOUND { + class CGroupControllerRoot; + +/** + * \brief CGroupController + * \date 2012-04-10 09:29GMT + * \author Jan Boon (Kaetemi) + * CGroupController + */ +class CGroupController : public UGroupController +{ +public: + friend class CGroupControllerRoot; + +private: + CGroupController *m_Parent; + std::map m_Children; + + /// Gain as set by the interface + float m_Gain; + + /// Gain including parent gain + float m_FinalGain; + + int m_NbSourcesInclChild; + TSourceContainer m_Sources; + +public: + CGroupController(CGroupController *parent); + + /// \name UGroupController + //@{ + virtual void setGain(float gain) { NLMISC::clamp(gain, 0.0f, 1.0f); if (m_Gain != gain) { m_Gain = gain; updateSourceGain(); } } + virtual float getGain() { return m_Gain; } + //@} + + inline float getFinalGain() const { return m_FinalGain; } + + void addSource(CSourceCommon *source); + void removeSource(CSourceCommon *source); + + virtual std::string getPath(); + +protected: + virtual ~CGroupController(); // subnodes can only be deleted by the root + +private: + inline float calculateTotalGain() { return m_Gain; } + virtual void calculateFinalGain(); + virtual void increaseSources(); + virtual void decreaseSources(); + void updateSourceGain(); + +}; /* class CGroupController */ + +} /* namespace NLSOUND */ + +#endif /* #ifndef NLSOUND_GROUP_CONTROLLER_H */ + +/* end of file */ diff --git a/code/nel/include/nel/sound/group_controller_root.h b/code/nel/include/nel/sound/group_controller_root.h new file mode 100644 index 000000000..2081fa28a --- /dev/null +++ b/code/nel/include/nel/sound/group_controller_root.h @@ -0,0 +1,70 @@ +/** + * \file group_controller_root.h + * \brief CGroupControllerRoot + * \date 2012-04-10 09:44GMT + * \author Jan Boon (Kaetemi) + * CGroupControllerRoot + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_GROUP_CONTROLLER_ROOT_H +#define NLSOUND_GROUP_CONTROLLER_ROOT_H +#include + +// STL includes + +// NeL includes +#include + +// Project includes +#include + +namespace NLSOUND { + +/** + * \brief CGroupControllerRoot + * \date 2012-04-10 09:44GMT + * \author Jan Boon (Kaetemi) + * CGroupControllerRoot + */ +class CGroupControllerRoot : public CGroupController, public NLMISC::CManualSingleton +{ +public: + CGroupControllerRoot(); + virtual ~CGroupControllerRoot(); + + /// Gets the group controller in a certain path with separator '/', if it doesn't exist yet it will be created. + CGroupController *getGroupController(const std::string &path); + +protected: + virtual std::string getPath(); + virtual void calculateFinalGain(); + virtual void increaseSources(); + virtual void decreaseSources(); + static bool isReservedName(const std::string &nodeName); + +}; /* class CGroupControllerRoot */ + +} /* namespace NLSOUND */ + +#endif /* #ifndef NLSOUND_GROUP_CONTROLLER_ROOT_H */ + +/* end of file */ diff --git a/code/nel/include/nel/sound/music_channel_fader.h b/code/nel/include/nel/sound/music_channel_fader.h index 4e6d35de4..8513c21e5 100644 --- a/code/nel/include/nel/sound/music_channel_fader.h +++ b/code/nel/include/nel/sound/music_channel_fader.h @@ -87,6 +87,8 @@ public: void init(ISoundDriver *soundDriver); void release(); + void reset(); + void update(); // time in seconds inline bool isInitOk() { return _SoundDriver != NULL; } diff --git a/code/nel/include/nel/sound/music_source.h b/code/nel/include/nel/sound/music_source.h index a23547c6c..be09c1837 100644 --- a/code/nel/include/nel/sound/music_source.h +++ b/code/nel/include/nel/sound/music_source.h @@ -35,7 +35,7 @@ class CMusicSource : public CSourceCommon { public: /// Constructor - CMusicSource (class CMusicSound *sound=NULL, bool spawn=false, TSpawnEndCallback cb=0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0); + CMusicSource (class CMusicSound *sound=NULL, bool spawn=false, TSpawnEndCallback cb=0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0, CGroupController *groupController = NULL); /// Destructor ~CMusicSource (); diff --git a/code/nel/include/nel/sound/simple_source.h b/code/nel/include/nel/sound/simple_source.h index ac2af0099..c330fd7fa 100644 --- a/code/nel/include/nel/sound/simple_source.h +++ b/code/nel/include/nel/sound/simple_source.h @@ -40,7 +40,7 @@ class CSimpleSource : public CSourceCommon, public CAudioMixerUser::IMixerEvent { public: /// Constructor - CSimpleSource(CSimpleSound *simpleSound = NULL, bool spawn = false, TSpawnEndCallback cb = 0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0); + CSimpleSource(CSimpleSound *simpleSound = NULL, bool spawn = false, TSpawnEndCallback cb = 0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0, CGroupController *groupController = NULL); /// Destructor virtual ~CSimpleSource(); @@ -97,14 +97,7 @@ public: * 1.0 -> no attenuation * values > 1 (amplification) not supported by most drivers */ - virtual void setGain( float gain ); - /** Set the gain amount (value inside [0, 1]) to map between 0 and the nominal gain - * (which is getSource()->getGain()). Does nothing if getSource() is null. - */ - virtual void setRelativeGain( float gain ); - /** Shift the frequency. 1.0f equals identity, each reduction of 50% equals a pitch shift - * of one octave. 0 is not a legal value. - */ + virtual void updateFinalGain(); virtual void setPitch( float pitch ); /// Set the source relative mode. If true, positions are interpreted relative to the listener position (default: false) virtual void setSourceRelativeMode( bool mode ); @@ -149,6 +142,8 @@ private: /// True when the sound is played muted and until the mixer event notifying the end. bool _PlayMuted; + bool _WaitingForPlay; + }; diff --git a/code/nel/include/nel/sound/sound.h b/code/nel/include/nel/sound/sound.h index 2112a0e73..e9d4f755c 100644 --- a/code/nel/include/nel/sound/sound.h +++ b/code/nel/include/nel/sound/sound.h @@ -30,6 +30,7 @@ namespace NLSOUND { class ISoundDriver; class IBuffer; class CSound; +class CGroupController; /// Sound names hash map @@ -60,8 +61,9 @@ public: SOUND_COMPLEX, SOUND_BACKGROUND, SOUND_CONTEXT, - SOUND_MUSIC, - SOUND_STREAM + SOUND_MUSIC, // soon to be deprecated hopefully + SOUND_STREAM, + SOUND_STREAM_FILE }; @@ -104,6 +106,8 @@ public: /// Return the max distance (if detailed()) virtual float getMaxDistance() const { return _MaxDist; } + inline CGroupController *getGroupController() const { return _GroupController; } + /// Set looping void setLooping( bool looping ) { _Looping = looping; } @@ -142,6 +146,9 @@ protected: /// An optional user var controler. NLMISC::TStringId _UserVarControler; + /// The group controller, always exists, owned by the audio mixer + CGroupController *_GroupController; + }; diff --git a/code/nel/include/nel/sound/source_common.h b/code/nel/include/nel/sound/source_common.h index 7781cfb02..1180fd68e 100644 --- a/code/nel/include/nel/sound/source_common.h +++ b/code/nel/include/nel/sound/source_common.h @@ -22,7 +22,7 @@ #include "nel/sound/u_stream_source.h" #include "nel/3d/cluster.h" #include "nel/sound/sound.h" - +#include "nel/sound/group_controller.h" namespace NLSOUND { @@ -36,11 +36,13 @@ public: SOURCE_SIMPLE, SOURCE_COMPLEX, SOURCE_BACKGROUND, - SOURCE_MUSIC, - SOURCE_STREAM + SOURCE_MUSIC, // DEPRECATED + SOURCE_STREAM, + SOURCE_STREAM_FILE }; - CSourceCommon(TSoundId id, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster); + /// When groupController is NULL it will use the groupcontroller specified in the TSoundId. You should manually specify the groupController if this source is a child of another source, so that the parent source controller of the user-specified .sound file is the one that will be used. + CSourceCommon(TSoundId id, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController); ~CSourceCommon(); @@ -63,6 +65,8 @@ public: void setGain( float gain ); void setRelativeGain( float gain ); float getRelativeGain() const; + /// Called whenever the gain is changed trough setGain, setRelativeGain or the group controller's gain settings change. + virtual void updateFinalGain() { } void setSourceRelativeMode( bool mode ); /// return the user param for the user callback void *getCallbackUserParam(void) const { return _CbUserParam; } @@ -74,6 +78,8 @@ public: virtual void getDirection( NLMISC::CVector& dir ) const { dir = _Direction; } /// Get the gain virtual float getGain() const { return _Gain; } + /// Get the final gain, including group controller changes. Use this when setting the physical source output gain. + inline float getFinalGain() const { return _Gain * _GroupController->getFinalGain(); } /// Get the pitch virtual float getPitch() const { return _Pitch; } /// Get the source relative mode @@ -99,15 +105,15 @@ public: /// \name Streaming source controls //@{ /// Set the sample format. (channels = 1, 2, ...; bitsPerSample = 8, 16; frequency = samples per second, 44100, ...) - virtual void setFormat(uint8 channels, uint8 bitsPerSample, uint32 frequency) { nlassert(false); } + virtual void setFormat(uint8 /* channels */, uint8 /* bitsPerSample */, uint32 /* frequency */) { nlassert(false); } /// Return the sample format information. - virtual void getFormat(uint8 &channels, uint8 &bitsPerSample, uint32 &frequency) const { nlassert(false); } + virtual void getFormat(uint8 &/* channels */, uint8 &/* bitsPerSample */, uint32 &/* frequency */) const { nlassert(false); } /// Get a writable pointer to the buffer of specified size. Use capacity to specify the required bytes. Returns NULL when all the buffer space is already filled. Call setFormat() first. - virtual uint8 *lock(uint capacity) { nlassert(false); return NULL; } + virtual uint8 *lock(uint /* capacity */) { nlassert(false); return NULL; } /// Notify that you are done writing to the locked buffer, so it can be copied over to hardware if needed. Set size to the number of bytes actually written to the buffer. Returns true if ok. - virtual bool unlock(uint size) { nlassert(false); return false; } + virtual bool unlock(uint /* size */) { nlassert(false); return false; } /// Get the recommended buffer size to use with lock()/unlock() - virtual void getRecommendedBufferSize(uint &samples, uint &bytes) const { nlassert(false); } + virtual void getRecommendedBufferSize(uint &/* samples */, uint &/* bytes */) const { nlassert(false); } /// Get the recommended sleep time based on the size of the last submitted buffer and the available buffer space virtual uint32 getRecommendedSleepTime() const { nlassert(false); return 0; } /// Return if there are still buffers available for playback. @@ -145,6 +151,9 @@ protected: /// An optional user var controler. NLMISC::TStringId _UserVarControler; + /// Group controller for gain + CGroupController *_GroupController; + }; } // NLSOUND diff --git a/code/nel/include/nel/sound/source_music_channel.h b/code/nel/include/nel/sound/source_music_channel.h new file mode 100644 index 000000000..7ec71fb66 --- /dev/null +++ b/code/nel/include/nel/sound/source_music_channel.h @@ -0,0 +1,100 @@ +/** + * \file source_music_channel.h + * \brief CSourceMusicChannel + * \date 2012-04-11 16:08GMT + * \author Jan Boon (Kaetemi) + * CSourceMusicChannel + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_SOURCE_MUSIC_CHANNEL_H +#define NLSOUND_SOURCE_MUSIC_CHANNEL_H +#include + +// STL includes + +// NeL includes +#include +#include + +// Project includes + +namespace NLSOUND { + class CStreamFileSource; + +/** + * \brief CSourceMusicChannel + * \date 2012-04-11 16:08GMT + * \author Jan Boon (Kaetemi) + * CSourceMusicChannel + */ +class CSourceMusicChannel : public IMusicChannel +{ +public: + CSourceMusicChannel(); + virtual ~CSourceMusicChannel(); + + /** Play some music (.ogg etc...) + * NB: if an old music was played, it is first stop with stopMusic() + * \param filepath file path, CPath::lookup is done here + * \param async stream music from hard disk, preload in memory if false + * \param loop must be true to play the music in loop. + */ + virtual bool play(const std::string &filepath, bool async, bool loop); + + /// Stop the music previously loaded and played (the Memory is also freed) + virtual void stop(); + + /// Makes sure any resources are freed, but keeps available for next play call + virtual void reset(); + + /// Pause the music previously loaded and played (the Memory is not freed) + virtual void pause(); + + /// Resume the music previously paused + virtual void resume(); + + /// Return true if a song is finished. + virtual bool isEnded(); + + /// Return true if the song is still loading asynchronously and hasn't started playing yet (false if not async), used to delay fading + virtual bool isLoadingAsync(); + + /// Return the total length (in second) of the music currently played + virtual float getLength(); + + /** Set the music volume (if any music played). (volume value inside [0 , 1]) (default: 1) + * NB: the volume of music is NOT affected by IListener::setGain() + */ + virtual void setVolume(float gain); + +private: + CStreamFileSound m_Sound; + CStreamFileSource *m_Source; + float m_Gain; + +}; /* class CSourceMusicChannel */ + +} /* namespace NLSOUND */ + +#endif /* #ifndef NLSOUND_SOURCE_MUSIC_CHANNEL_H */ + +/* end of file */ diff --git a/code/nel/include/nel/sound/stream_file_sound.h b/code/nel/include/nel/sound/stream_file_sound.h new file mode 100644 index 000000000..8c1cc1634 --- /dev/null +++ b/code/nel/include/nel/sound/stream_file_sound.h @@ -0,0 +1,94 @@ +/** + * \file stream_file_sound.h + * \brief CStreamFileSound + * \date 2012-04-11 09:57GMT + * \author Jan Boon (Kaetemi) + * CStreamFileSound + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_STREAM_FILE_SOUND_H +#define NLSOUND_STREAM_FILE_SOUND_H +#include + +// STL includes + +// NeL includes + +// Project includes +#include + +namespace NLSOUND { + class CSourceMusicChannel; + +/** + * \brief CStreamFileSound + * \date 2012-04-11 09:57GMT + * \author Jan Boon (Kaetemi) + * CStreamFileSound + */ +class CStreamFileSound : public CStreamSound +{ +public: + friend class CSourceMusicChannel; + +public: + CStreamFileSound(); + virtual ~CStreamFileSound(); + + /// Get the type of the sound. + virtual TSOUND_TYPE getSoundType() { return SOUND_STREAM_FILE; } + + /// Load the sound parameters from georges' form + virtual void importForm(const std::string& filename, NLGEORGES::UFormElm& formRoot); + + /// Used by the george sound plugin to check sound recursion (ie sound 'toto' use sound 'titi' witch also use sound 'toto' ...). + virtual void getSubSoundList(std::vector > &/* subsounds */) const { } + + /// Serialize the sound data. + virtual void serial(NLMISC::IStream &s); + + /// Return the length of the sound in ms + virtual uint32 getDuration() { return 0; } + + inline bool getAsync() { return m_Async; } + + inline const std::string &getFilePath() { return m_FilePath; } + +private: + /// Used by CSourceMusicChannel to set the filePath and default settings on other parameters. + void setMusicFilePath(const std::string &filePath, bool async = true, bool loop = false); + +private: + CStreamFileSound(const CStreamFileSound &); + CStreamFileSound &operator=(const CStreamFileSound &); + +private: + bool m_Async; + std::string m_FilePath; + +}; /* class CStreamFileSound */ + +} /* namespace NLSOUND */ + +#endif /* #ifndef NLSOUND_STREAM_FILE_SOUND_H */ + +/* end of file */ diff --git a/code/nel/include/nel/sound/stream_file_source.h b/code/nel/include/nel/sound/stream_file_source.h new file mode 100644 index 000000000..0cfee14d1 --- /dev/null +++ b/code/nel/include/nel/sound/stream_file_source.h @@ -0,0 +1,110 @@ +/** + * \file stream_file_source.h + * \brief CStreamFileSource + * \date 2012-04-11 09:57GMT + * \author Jan Boon (Kaetemi) + * CStreamFileSource + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_STREAM_FILE_SOURCE_H +#define NLSOUND_STREAM_FILE_SOURCE_H +#include + +// STL includes + +// NeL includes +#include + +// Project includes +#include +#include + +namespace NLSOUND { + class IAudioDecoder; + +/** + * \brief CStreamFileSource + * \date 2012-04-11 09:57GMT + * \author Jan Boon (Kaetemi) + * CStreamFileSource + */ +class CStreamFileSource : public CStreamSource, private NLMISC::IRunnable +{ +public: + CStreamFileSource(CStreamFileSound *streamFileSound = NULL, bool spawn = false, TSpawnEndCallback cb = 0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0, CGroupController *groupController = NULL); + virtual ~CStreamFileSource(); + + /// Return the source type + TSOURCE_TYPE getType() const { return SOURCE_STREAM_FILE; } + + /// \name Playback control + //@{ + /// Play + virtual void play(); + /// Stop playing + virtual void stop(); + /// Get playing state. Return false even if the source has stopped on its own. + virtual bool isPlaying(); + /// Pause (following legacy music channel implementation) + void pause(); + /// Resume (following legacy music channel implementation) + void resume(); + /// check if song ended (following legacy music channel implementation) + bool isEnded(); + /// (following legacy music channel implementation) + float getLength(); + /// check if still loading (following legacy music channel implementation) + bool isLoadingAsync(); + //@} + + /// \name Decoding thread + //@{ + virtual void getName (std::string &result) const { result = "CStreamFileSource"; } + virtual void run(); + //@} + + // TODO: getTime + +private: + bool prepareDecoder(); + inline bool bufferMore(uint bytes); + +private: + CStreamFileSource(const CStreamFileSource &); + CStreamFileSource &operator=(const CStreamFileSource &); + +private: + inline CStreamFileSound *getStreamFileSound() { return static_cast(m_StreamSound); } + + NLMISC::IThread *m_Thread; + + IAudioDecoder *m_AudioDecoder; + + bool m_Paused; + +}; /* class CStreamFileSource */ + +} /* namespace NLSOUND */ + +#endif /* #ifndef NLSOUND_STREAM_FILE_SOURCE_H */ + +/* end of file */ diff --git a/code/nel/include/nel/sound/stream_sound.h b/code/nel/include/nel/sound/stream_sound.h index 39e6c733c..4b0debe0c 100644 --- a/code/nel/include/nel/sound/stream_sound.h +++ b/code/nel/include/nel/sound/stream_sound.h @@ -46,7 +46,7 @@ public: virtual void importForm(const std::string& filename, NLGEORGES::UFormElm& formRoot); /// Used by the george sound plugin to check sound recursion (ie sound 'toto' use sound 'titi' witch also use sound 'toto' ...). - virtual void getSubSoundList(std::vector > &subsounds) const { } + virtual void getSubSoundList(std::vector > &/* subsounds */) const { } /// Serialize the sound data. virtual void serial(NLMISC::IStream &s); diff --git a/code/nel/include/nel/sound/stream_source.h b/code/nel/include/nel/sound/stream_source.h index a5ac7c9f8..40ed82e11 100644 --- a/code/nel/include/nel/sound/stream_source.h +++ b/code/nel/include/nel/sound/stream_source.h @@ -41,7 +41,7 @@ namespace NLSOUND { class CStreamSource : public CSourceCommon { public: - CStreamSource(CStreamSound *streamSound = NULL, bool spawn = false, TSpawnEndCallback cb = 0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0); + CStreamSource(CStreamSound *streamSound = NULL, bool spawn = false, TSpawnEndCallback cb = 0, void *cbUserParam = 0, NL3D::CCluster *cluster = 0, CGroupController *groupController = NULL); virtual ~CStreamSource(); /// Return the sound binded to the source (or NULL if there is no sound) @@ -55,6 +55,9 @@ public: virtual void setLooping(bool l); /// Play virtual void play(); +protected: + void stopInt(); +public: /// Stop playing virtual void stop(); /// Get playing state. Return false even if the source has stopped on its own. @@ -80,18 +83,7 @@ public: virtual void setVelocity(const NLMISC::CVector& vel); /// Set the direction vector (3D mode only, ignored in stereo mode) (default: (0,0,0) as non-directional) virtual void setDirection(const NLMISC::CVector& dir); - /** Set the gain (volume value inside [0 , 1]). (default: 1) - * 0.0 -> silence - * 0.5 -> -6dB - * 1.0 -> no attenuation - * values > 1 (amplification) not supported by most drivers - */ - virtual void setGain(float gain); - - /** Set the gain amount (value inside [0, 1]) to map between 0 and the nominal gain - * (which is getSource()->getGain()). Does nothing if getSource() is null. - */ - virtual void setRelativeGain(float gain); + virtual void updateFinalGain(); /** Shift the frequency. 1.0f equals identity, each reduction of 50% equals a pitch shift * of one octave. 0 is not a legal value. */ @@ -118,6 +110,9 @@ public: virtual bool hasFilledBuffersAvailable() const; //@} + /// Prepare the buffers in this stream for the given maximum capacity. (TODO: Move this into UStreamSource) + void preAllocate(uint capacity); + /// Return the track CTrack *getTrack() { return m_Track; } @@ -125,7 +120,7 @@ private: CStreamSource(const CStreamSource &); CStreamSource &operator=(const CStreamSource &); -private: +protected: /// Return the source type TSOURCE_TYPE getType() const { return SOURCE_STREAM; } @@ -173,7 +168,13 @@ private: /// The bytes per second according to the buffer format uint m_BytesPerSecond; - + + /// Waiting for play for high priority sources + bool m_WaitingForPlay; + + /// Inverse pitch + float m_PitchInv; + }; /* class CStreamSource */ } /* namespace NLSOUND */ diff --git a/code/nel/include/nel/sound/u_audio_mixer.h b/code/nel/include/nel/sound/u_audio_mixer.h index e1c2a274e..c4702c3f8 100644 --- a/code/nel/include/nel/sound/u_audio_mixer.h +++ b/code/nel/include/nel/sound/u_audio_mixer.h @@ -20,6 +20,7 @@ #include "nel/misc/types_nl.h" #include "nel/misc/string_mapper.h" #include "nel/sound/u_source.h" +#include "nel/sound/u_group_controller.h" #include "nel/ligo/primitive.h" #include @@ -285,15 +286,19 @@ public: /// Get a TSoundId from a name (returns NULL if not found) virtual TSoundId getSoundId( const NLMISC::TStringId &name ) = 0; + /// Gets the group controller for the given group tree path with separator '/', if it doesn't exist yet it will be created. + /// Examples: "music", "effects", "dialog", "music/background", "music/loading", "music/player", etcetera + virtual UGroupController *getGroupController(const std::string &path) = 0; + /** Add a logical sound source (returns NULL if name not found). * If spawn is true, the source will auto-delete after playing. If so, the return USource* pointer * is valid only before the time when calling play() plus the duration of the sound. You can * pass a callback function that will be called (if not NULL) just before deleting the spawned * source. */ - virtual USource *createSource( const NLMISC::TStringId &name, bool spawn=false, TSpawnEndCallback cb=NULL, void *callbackUserParam = NULL, NL3D::CCluster *cluster = 0, CSoundContext *context=0) = 0; + virtual USource *createSource(const NLMISC::TStringId &name, bool spawn=false, TSpawnEndCallback cb=NULL, void *callbackUserParam = NULL, NL3D::CCluster *cluster = 0, CSoundContext *context = 0, UGroupController *groupController = NULL) = 0; /// Add a logical sound source (by sound id). To remove a source, just delete it. See createSource(const char*) - virtual USource *createSource( TSoundId id, bool spawn=false, TSpawnEndCallback cb=NULL, void *callbackUserParam = NULL, NL3D::CCluster *cluster = 0, CSoundContext *context=0 ) = 0; + virtual USource *createSource(TSoundId id, bool spawn=false, TSpawnEndCallback cb=NULL, void *callbackUserParam = NULL, NL3D::CCluster *cluster = 0, CSoundContext *context = 0, UGroupController *groupController = NULL) = 0; /** Use this method to set the listener position instead of using getListener->setPos(); * It's because we have to update the background sounds in this case. diff --git a/code/nel/include/nel/sound/u_group_controller.h b/code/nel/include/nel/sound/u_group_controller.h new file mode 100644 index 000000000..5aadeb25c --- /dev/null +++ b/code/nel/include/nel/sound/u_group_controller.h @@ -0,0 +1,65 @@ +/** + * \file u_group_controller.h + * \brief UGroupController + * \date 2012-04-10 12:49GMT + * \author Jan Boon (Kaetemi) + * UGroupController + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#ifndef NLSOUND_U_GROUP_CONTROLLER_H +#define NLSOUND_U_GROUP_CONTROLLER_H +#include + +// STL includes + +// NeL includes + +// Project includes + +#define NLSOUND_SHEET_V1_DEFAULT_SOUND_GROUP_CONTROLLER "sound:effects:game" +#define NLSOUND_SHEET_V1_DEFAULT_SOUND_MUSIC_GROUP_CONTROLLER "sound:music:game" +#define NLSOUND_SHEET_V1_DEFAULT_SOUND_STREAM_GROUP_CONTROLLER "sound:dialog:game" + +namespace NLSOUND { + +/** + * \brief UGroupController + * \date 2012-04-10 12:49GMT + * \author Jan Boon (Kaetemi) + * UGroupController + */ +class UGroupController +{ +public: + virtual void setGain(float gain) = 0; + virtual float getGain() = 0; + +protected: + virtual ~UGroupController() { } + +}; /* class UGroupController */ + +} /* namespace NLSOUND */ + +#endif /* #ifndef NLSOUND_U_GROUP_CONTROLLER_H */ + +/* end of file */ diff --git a/code/nel/samples/3d/Makefile.am b/code/nel/samples/3d/Makefile.am deleted file mode 100644 index d00e99366..000000000 --- a/code/nel/samples/3d/Makefile.am +++ /dev/null @@ -1,11 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005/04/04 09:45:05 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = font cluster_viewer @CEGUI_SUBDIR@ - - -# End of Makefile.am - diff --git a/code/nel/samples/3d/cegui/Makefile.am b/code/nel/samples/3d/cegui/Makefile.am deleted file mode 100644 index afc817166..000000000 --- a/code/nel/samples/3d/cegui/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = demonel_8.sln demonel_8.vcproj demonel.rc demonel.sln demonel.vcproj icon1.ico datafiles - -bin_PROGRAMS = nel_sample_cegui - -nel_sample_cegui_SOURCES = main.cpp NeLDriver.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src @CEGUI_CFLAGS@ - -nel_sample_cegui_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la \ - ../../../src/cegui/libnelceguirenderer.la \ - @CEGUI_LIBS@ - - -# End of Makefile.am - diff --git a/code/nel/samples/3d/cluster_viewer/Makefile.am b/code/nel/samples/3d/cluster_viewer/Makefile.am deleted file mode 100644 index 5814a0394..000000000 --- a/code/nel/samples/3d/cluster_viewer/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005/04/04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = shapes groups fonts - -bin_PROGRAMS = cluster_viewer - -cluster_viewer_SOURCES = main.cpp - -cluster_viewerdir = $(datadir)/nel/samples/cluster_viewer - -cluster_viewer_DATA = readme.txt main.cvs - -AM_CXXFLAGS = -DCV_DIR="\"$(cluster_viewerdir)\"" -I$(top_srcdir)/src - -cluster_viewer_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la - - -# End of Makefile.am - diff --git a/code/nel/samples/3d/cluster_viewer/fonts/Makefile.am b/code/nel/samples/3d/cluster_viewer/fonts/Makefile.am deleted file mode 100644 index 11e9a8f31..000000000 --- a/code/nel/samples/3d/cluster_viewer/fonts/Makefile.am +++ /dev/null @@ -1,12 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -cluster_viewerdir = $(datadir)/nel/samples/cluster_viewer/fonts/ - -cluster_viewer_DATA = n019003l.pfb - -# End of Makefile.am - diff --git a/code/nel/samples/3d/cluster_viewer/groups/Makefile.am b/code/nel/samples/3d/cluster_viewer/groups/Makefile.am deleted file mode 100644 index 54cdb5867..000000000 --- a/code/nel/samples/3d/cluster_viewer/groups/Makefile.am +++ /dev/null @@ -1,12 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -cluster_viewerdir = $(datadir)/nel/samples/cluster_viewer/groups/ - -cluster_viewer_DATA = street.ig - -# End of Makefile.am - diff --git a/code/nel/samples/3d/cluster_viewer/shapes/Makefile.am b/code/nel/samples/3d/cluster_viewer/shapes/Makefile.am deleted file mode 100644 index 3b251867a..000000000 --- a/code/nel/samples/3d/cluster_viewer/shapes/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -cluster_viewerdir = $(datadir)/nel/samples/cluster_viewer/shapes/ - -cluster_viewer_DATA = box02.shape \ - sphere01.shape \ - sphere02.shape \ - sphere03.shape \ - sphere04.shape \ - sphere05.shape \ - sphere06.shape \ - sphere07.shape \ - sphere08.shape - -# End of Makefile.am - diff --git a/code/nel/samples/3d/font/Makefile.am b/code/nel/samples/3d/font/Makefile.am deleted file mode 100644 index 5548ec8f6..000000000 --- a/code/nel/samples/3d/font/Makefile.am +++ /dev/null @@ -1,21 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005/04/04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -bin_PROGRAMS = font - -font_SOURCES = main.cpp - -fontdir = $(datadir)/nel/samples/font -font_DATA = beteckna.ttf - -AM_CXXFLAGS = -DFONT_DIR="\"$(fontdir)\"" -I$(top_srcdir)/src - -font_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la - - -# End of Makefile.am - diff --git a/code/nel/samples/CMakeLists.txt b/code/nel/samples/CMakeLists.txt index 4f38264a0..5abcd985a 100644 --- a/code/nel/samples/CMakeLists.txt +++ b/code/nel/samples/CMakeLists.txt @@ -17,5 +17,5 @@ IF(WITH_PACS) ENDIF(WITH_PACS) IF(WITH_SOUND) - ADD_SUBDIRECTORY(sound_sources) + ADD_SUBDIRECTORY(sound) ENDIF(WITH_SOUND) diff --git a/code/nel/samples/Makefile.am b/code/nel/samples/Makefile.am deleted file mode 100644 index 98edb586d..000000000 --- a/code/nel/samples/Makefile.am +++ /dev/null @@ -1,12 +0,0 @@ -# -# $Id: Makefile.am,v 1.2 2005/04/13 12:37:26 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -DIST_SUBDIRS = sound_sources pacs georges 3d net misc - -SUBDIRS = pacs georges 3d net misc - -# End of Makefile.am - diff --git a/code/nel/samples/georges/Makefile.am b/code/nel/samples/georges/Makefile.am deleted file mode 100644 index bf2c83d5f..000000000 --- a/code/nel/samples/georges/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005/04/04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = georges_sample_8.vcproj georges_sample.sln georges_sample.vcproj - -bin_PROGRAMS = georges - -georges_SOURCES = main.cpp - -georgesdir = $(datadir)/nel/samples/georges - -georges_DATA = boolean.typ coolfilesinfo.dfn default.sample_config int.typ positiondata.dfn sample_config.dfn string.typ - -AM_CXXFLAGS = -DGF_DIR="\"$(georgesdir)\"" -I$(top_srcdir)/src - - -georges_LDADD = ../../src/misc/libnelmisc.la \ - ../../src/georges/libnelgeorges.la - - -# End of Makefile.am diff --git a/code/nel/samples/misc/Makefile.am b/code/nel/samples/misc/Makefile.am deleted file mode 100644 index 0e13cb6f1..000000000 --- a/code/nel/samples/misc/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ -# -# $Id: Makefile.am,v 1.2 2005-04-13 12:37:26 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = command configfile debug i18n log strings - -# End of Makefile.am - diff --git a/code/nel/samples/misc/command/Makefile.am b/code/nel/samples/misc/command/Makefile.am deleted file mode 100644 index 890a9fa18..000000000 --- a/code/nel/samples/misc/command/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = command_8.vcproj main.dsp main.dsw - -bin_PROGRAMS = command - -command_SOURCES = main.cpp - -commanddir = $(datadir)/nel/samples/command - -AM_CXXFLAGS = -I$(top_srcdir)/src - -command_LDADD = ../../../src/misc/libnelmisc.la - -# End of Makefile.am - diff --git a/code/nel/samples/misc/configfile/Makefile.am b/code/nel/samples/misc/configfile/Makefile.am deleted file mode 100644 index 538d3af28..000000000 --- a/code/nel/samples/misc/configfile/Makefile.am +++ /dev/null @@ -1,23 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = configfile_8.vcproj main.dsp main.dsw - -bin_PROGRAMS = configfile - -configfile_SOURCES = main.cpp - -configfiledir = $(datadir)/nel/samples/configfile - -configfile_DATA = simpletest.txt - -AM_CXXFLAGS = -DCF_DIR="\"$(configfiledir)\"" -I$(top_srcdir)/src - -configfile_LDADD = ../../../src/misc/libnelmisc.la - - -# End of Makefile.am - diff --git a/code/nel/samples/misc/debug/Makefile.am b/code/nel/samples/misc/debug/Makefile.am deleted file mode 100644 index d8d2c733a..000000000 --- a/code/nel/samples/misc/debug/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = debug_8.vcproj main.dsp main.dsw - -bin_PROGRAMS = debug - -debug_SOURCES = main.cpp - -debugdir = $(datadir)/nel/samples/debug - -AM_CXXFLAGS = -I$(top_srcdir)/src - -debug_LDADD = ../../../src/misc/libnelmisc.la - -# End of Makefile.am - diff --git a/code/nel/samples/misc/i18n/Makefile.am b/code/nel/samples/misc/i18n/Makefile.am deleted file mode 100644 index 6c9c574c9..000000000 --- a/code/nel/samples/misc/i18n/Makefile.am +++ /dev/null @@ -1,23 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = i18n_8.vcproj main.dsp main.dsw - -bin_PROGRAMS = i18n - -i18n_SOURCES = main.cpp - -i18ndir = $(datadir)/nel/samples/i18n - -i18n_DATA = en.uxt fr.uxt de.uxt - -AM_CXXFLAGS = -DI18N_DIR="\"$(i18ndir)\"" -I$(top_srcdir)/src - -i18n_LDADD = ../../../src/misc/libnelmisc.la - - -# End of Makefile.am - diff --git a/code/nel/samples/misc/log/Makefile.am b/code/nel/samples/misc/log/Makefile.am deleted file mode 100644 index 3d57e533d..000000000 --- a/code/nel/samples/misc/log/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = log_8.vcproj main.dsp main.dsw - -bin_PROGRAMS = log - -log_SOURCES = main.cpp - -logdir = $(datadir)/nel/samples/log - -AM_CXXFLAGS = -I$(top_srcdir)/src - -log_LDADD = ../../../src/misc/libnelmisc.la - -# End of Makefile.am - diff --git a/code/nel/samples/misc/strings/Makefile.am b/code/nel/samples/misc/strings/Makefile.am deleted file mode 100644 index 151e8ef32..000000000 --- a/code/nel/samples/misc/strings/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = main.dsp main.dsw strings_8.vcproj - -bin_PROGRAMS = strings - -strings_SOURCES = main.cpp - -stringsdir = $(datadir)/nel/samples/strings - -AM_CXXFLAGS = -I$(top_srcdir)/src - -strings_LDADD = ../../../src/misc/libnelmisc.la - -# End of Makefile.am - diff --git a/code/nel/samples/misc/types_check/Makefile.am b/code/nel/samples/misc/types_check/Makefile.am deleted file mode 100644 index 9c79d6aa2..000000000 --- a/code/nel/samples/misc/types_check/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = main.dsp main.dsw types_check_8.vcproj - -bin_PROGRAMS = types_check - -types_check_SOURCES = main.cpp - -types_checkdir = $(datadir)/nel/samples/types_check - -AM_CXXFLAGS = -I$(top_srcdir)/src - -types_check_LDADD = ../../../src/misc/libnelmisc.la - -# End of Makefile.am - diff --git a/code/nel/samples/net/Makefile.am b/code/nel/samples/net/Makefile.am deleted file mode 100644 index ad7388761..000000000 --- a/code/nel/samples/net/Makefile.am +++ /dev/null @@ -1,11 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005/04/13 12:37:26 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = chat udp login_system - - -# End of Makefile.am - diff --git a/code/nel/samples/net/chat/Makefile.am b/code/nel/samples/net/chat/Makefile.am deleted file mode 100644 index bc2df9ef9..000000000 --- a/code/nel/samples/net/chat/Makefile.am +++ /dev/null @@ -1,27 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005/04/13 12:37:26 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -bin_PROGRAMS = chatserver chatclient - -chatserver_SOURCES = server.cpp - -chatclient_SOURCES = client.cpp kbhit.h kbhit.cpp - -chatserverdir=$(datadir)/nel/samples/net/chat -chatserver_DATA=chat_service.cfg - -chatclientdir=$(datadir)/nel/samples/net/chat -chatclient_DATA=client.cfg - -AM_CXXFLAGS = -DCHAT_DIR="\"$(chatclientdir)\"" -I$(top_srcdir)/src - -chatserver_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/net/libnelnet.la - -chatclient_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/net/libnelnet.la - -# End of Makefile.am diff --git a/code/nel/samples/net/login_system/Makefile.am b/code/nel/samples/net/login_system/Makefile.am deleted file mode 100644 index 5ef4fe26c..000000000 --- a/code/nel/samples/net/login_system/Makefile.am +++ /dev/null @@ -1,30 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-13 12:37:26 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = main.dsw client.dsp client.cfg frontend_service.cfg frontend_service.dsp - -bin_PROGRAMS = nls_login_client nls_frontend_service - -nls_frontend_service_SOURCES = frontend_service.cpp - -nls_login_client_SOURCES = client.cpp - -nls_frontend_servicedir=$(datadir)/nel/samples/net/udp -nls_frontend_service_DATA=frontend_service.cfg - -nls_login_clientdir=$(datadir)/nel/samples/net/udp -nls_login_client_DATA=client.cfg - -AM_CXXFLAGS = -DLC_DIR="\"$(nls_login_clientdir)\"" -I$(top_srcdir)/src - -nls_frontend_service_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/net/libnelnet.la - -nls_login_client_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/net/libnelnet.la - -# End of Makefile.am - diff --git a/code/nel/samples/net/udp/Makefile.am b/code/nel/samples/net/udp/Makefile.am deleted file mode 100644 index 07a131fd8..000000000 --- a/code/nel/samples/net/udp/Makefile.am +++ /dev/null @@ -1,32 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005-04-13 12:37:26 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = main.dsw main.sln bench_service_8.vcproj bench_service.vcproj bench_service.dsp client_8.vcproj client.dsp client.vcproj readme.txt - -bin_PROGRAMS = udp_bench_service udp_bench_client - -udp_bench_service_SOURCES = bench_service.cpp receive_task.cpp receive_task.h - -udp_bench_client_SOURCES = client.cpp graph.cpp graph.h simlag.cpp simlag.h - -noinst_HEADERS = receive_task.h graph.h simlag.h - -udp_bench_servicedir=$(datadir)/nel/samples/net/udp -udp_bench_service_DATA= bench_service.cfg - -udp_bench_clientdir=$(datadir)/nel/samples/net/udp -udp_bench_client_DATA=client.cfg - -AM_CXXFLAGS = -DUDP_DIR="\"$(udp_bench_clientdir)\"" -I$(top_srcdir)/src - -udp_bench_service_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/net/libnelnet.la - -udp_bench_client_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/net/libnelnet.la - -# End of Makefile.am - diff --git a/code/nel/samples/pacs/Makefile.am b/code/nel/samples/pacs/Makefile.am deleted file mode 100644 index d7a552769..000000000 --- a/code/nel/samples/pacs/Makefile.am +++ /dev/null @@ -1,19 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005/04/04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -bin_PROGRAMS = pacs_sample - -pacs_sample_SOURCES = main.cpp object.cpp object.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -pacs_sample_LDADD = ../../src/misc/libnelmisc.la \ - ../../src/3d/libnel3d.la \ - ../../src/pacs/libnelpacs.la - - -# End of Makefile.am - diff --git a/code/nel/samples/sound/CMakeLists.txt b/code/nel/samples/sound/CMakeLists.txt new file mode 100644 index 000000000..6a7696fa4 --- /dev/null +++ b/code/nel/samples/sound/CMakeLists.txt @@ -0,0 +1,5 @@ + +ADD_SUBDIRECTORY(sound_sources) +ADD_SUBDIRECTORY(stream_file) +ADD_SUBDIRECTORY(stream_ogg_vorbis) + diff --git a/code/nel/samples/sound_sources/CMakeLists.txt b/code/nel/samples/sound/sound_sources/CMakeLists.txt similarity index 85% rename from code/nel/samples/sound_sources/CMakeLists.txt rename to code/nel/samples/sound/sound_sources/CMakeLists.txt index 4cf1ea427..b0f481e7c 100644 --- a/code/nel/samples/sound_sources/CMakeLists.txt +++ b/code/nel/samples/sound/sound_sources/CMakeLists.txt @@ -7,7 +7,7 @@ ADD_DEFINITIONS(-DNL_SOUND_DATA="\\"${NL_SHARE_PREFIX}/nl_sample_sound/\\"" ${LI INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR}) TARGET_LINK_LIBRARIES(nl_sample_sound_sources nelmisc nelsound) -NL_DEFAULT_PROPS(nl_sample_sound_sources "NeL, Samples: Sound System") +NL_DEFAULT_PROPS(nl_sample_sound_sources "NeL, Samples: Sound: Sound Sources") NL_ADD_RUNTIME_FLAGS(nl_sample_sound_sources) INSTALL(TARGETS nl_sample_sound_sources RUNTIME DESTINATION bin COMPONENT samplessound) diff --git a/code/nel/samples/sound_sources/data/DFN/alpha.typ b/code/nel/samples/sound/sound_sources/data/DFN/alpha.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/alpha.typ rename to code/nel/samples/sound/sound_sources/data/DFN/alpha.typ diff --git a/code/nel/samples/sound_sources/data/DFN/angle.typ b/code/nel/samples/sound/sound_sources/data/DFN/angle.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/angle.typ rename to code/nel/samples/sound/sound_sources/data/DFN/angle.typ diff --git a/code/nel/samples/sound_sources/data/DFN/backgound_sound_item.dfn b/code/nel/samples/sound/sound_sources/data/DFN/backgound_sound_item.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/backgound_sound_item.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/backgound_sound_item.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/background_flag_config.dfn b/code/nel/samples/sound/sound_sources/data/DFN/background_flag_config.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/background_flag_config.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/background_flag_config.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/background_sound.dfn b/code/nel/samples/sound/sound_sources/data/DFN/background_sound.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/background_sound.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/background_sound.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/basics/_typ.dfn b/code/nel/samples/sound/sound_sources/data/DFN/basics/_typ.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/_typ.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/basics/_typ.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/basics/_type.typ b/code/nel/samples/sound/sound_sources/data/DFN/basics/_type.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/_type.typ rename to code/nel/samples/sound/sound_sources/data/DFN/basics/_type.typ diff --git a/code/nel/samples/sound_sources/data/DFN/basics/boolean.typ b/code/nel/samples/sound/sound_sources/data/DFN/basics/boolean.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/boolean.typ rename to code/nel/samples/sound/sound_sources/data/DFN/basics/boolean.typ diff --git a/code/nel/samples/sound_sources/data/DFN/basics/filename.typ b/code/nel/samples/sound/sound_sources/data/DFN/basics/filename.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/filename.typ rename to code/nel/samples/sound/sound_sources/data/DFN/basics/filename.typ diff --git a/code/nel/samples/sound_sources/data/DFN/basics/float.typ b/code/nel/samples/sound/sound_sources/data/DFN/basics/float.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/float.typ rename to code/nel/samples/sound/sound_sources/data/DFN/basics/float.typ diff --git a/code/nel/samples/sound_sources/data/DFN/basics/iboolean.typ b/code/nel/samples/sound/sound_sources/data/DFN/basics/iboolean.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/iboolean.typ rename to code/nel/samples/sound/sound_sources/data/DFN/basics/iboolean.typ diff --git a/code/nel/samples/sound_sources/data/DFN/basics/int.typ b/code/nel/samples/sound/sound_sources/data/DFN/basics/int.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/int.typ rename to code/nel/samples/sound/sound_sources/data/DFN/basics/int.typ diff --git a/code/nel/samples/sound_sources/data/DFN/basics/string.typ b/code/nel/samples/sound/sound_sources/data/DFN/basics/string.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/string.typ rename to code/nel/samples/sound/sound_sources/data/DFN/basics/string.typ diff --git a/code/nel/samples/sound_sources/data/DFN/basics/typ.dfn b/code/nel/samples/sound/sound_sources/data/DFN/basics/typ.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/basics/typ.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/basics/typ.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/complex_sound.dfn b/code/nel/samples/sound/sound_sources/data/DFN/complex_sound.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/complex_sound.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/complex_sound.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/context_sound.dfn b/code/nel/samples/sound/sound_sources/data/DFN/context_sound.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/context_sound.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/context_sound.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/direction.dfn b/code/nel/samples/sound/sound_sources/data/DFN/direction.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/direction.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/direction.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/distance.typ b/code/nel/samples/sound/sound_sources/data/DFN/distance.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/distance.typ rename to code/nel/samples/sound/sound_sources/data/DFN/distance.typ diff --git a/code/nel/samples/sound_sources/data/DFN/doppler.typ b/code/nel/samples/sound/sound_sources/data/DFN/doppler.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/doppler.typ rename to code/nel/samples/sound/sound_sources/data/DFN/doppler.typ diff --git a/code/nel/samples/sound_sources/data/DFN/gain.typ b/code/nel/samples/sound/sound_sources/data/DFN/gain.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/gain.typ rename to code/nel/samples/sound/sound_sources/data/DFN/gain.typ diff --git a/code/nel/samples/sound_sources/data/DFN/listener.dfn b/code/nel/samples/sound/sound_sources/data/DFN/listener.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/listener.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/listener.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/mixer_config.dfn b/code/nel/samples/sound/sound_sources/data/DFN/mixer_config.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/mixer_config.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/mixer_config.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/music_sound.dfn b/code/nel/samples/sound/sound_sources/data/DFN/music_sound.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/music_sound.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/music_sound.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/parameter_id.typ b/code/nel/samples/sound/sound_sources/data/DFN/parameter_id.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/parameter_id.typ rename to code/nel/samples/sound/sound_sources/data/DFN/parameter_id.typ diff --git a/code/nel/samples/sound_sources/data/DFN/pattern_mode.typ b/code/nel/samples/sound/sound_sources/data/DFN/pattern_mode.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/pattern_mode.typ rename to code/nel/samples/sound/sound_sources/data/DFN/pattern_mode.typ diff --git a/code/nel/samples/sound_sources/data/DFN/priority.typ b/code/nel/samples/sound/sound_sources/data/DFN/priority.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/priority.typ rename to code/nel/samples/sound/sound_sources/data/DFN/priority.typ diff --git a/code/nel/samples/sound_sources/data/DFN/rolloff.typ b/code/nel/samples/sound/sound_sources/data/DFN/rolloff.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/rolloff.typ rename to code/nel/samples/sound/sound_sources/data/DFN/rolloff.typ diff --git a/code/nel/samples/sound_sources/data/DFN/simple_sound.dfn b/code/nel/samples/sound/sound_sources/data/DFN/simple_sound.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/simple_sound.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/simple_sound.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/sound.dfn b/code/nel/samples/sound/sound_sources/data/DFN/sound.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/sound.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/sound.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/sound_group.dfn b/code/nel/samples/sound/sound_sources/data/DFN/sound_group.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/sound_group.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/sound_group.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/sound_group_item.dfn b/code/nel/samples/sound/sound_sources/data/DFN/sound_group_item.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/sound_group_item.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/sound_group_item.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/soundbank.dfn b/code/nel/samples/sound/sound_sources/data/DFN/soundbank.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/soundbank.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/soundbank.dfn diff --git a/code/nel/samples/sound_sources/data/DFN/transposition.typ b/code/nel/samples/sound/sound_sources/data/DFN/transposition.typ similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/transposition.typ rename to code/nel/samples/sound/sound_sources/data/DFN/transposition.typ diff --git a/code/nel/samples/sound_sources/data/DFN/user_var_binding.dfn b/code/nel/samples/sound/sound_sources/data/DFN/user_var_binding.dfn similarity index 100% rename from code/nel/samples/sound_sources/data/DFN/user_var_binding.dfn rename to code/nel/samples/sound/sound_sources/data/DFN/user_var_binding.dfn diff --git a/code/nel/samples/sound_sources/data/animations/readme.txt b/code/nel/samples/sound/sound_sources/data/animations/readme.txt similarity index 100% rename from code/nel/samples/sound_sources/data/animations/readme.txt rename to code/nel/samples/sound/sound_sources/data/animations/readme.txt diff --git a/code/nel/samples/sound_sources/data/animations/test_anim.sound_anim b/code/nel/samples/sound/sound_sources/data/animations/test_anim.sound_anim similarity index 100% rename from code/nel/samples/sound_sources/data/animations/test_anim.sound_anim rename to code/nel/samples/sound/sound_sources/data/animations/test_anim.sound_anim diff --git a/code/nel/samples/sound_sources/data/background_sounds/background_sound.primitive b/code/nel/samples/sound/sound_sources/data/background_sounds/background_sound.primitive similarity index 100% rename from code/nel/samples/sound_sources/data/background_sounds/background_sound.primitive rename to code/nel/samples/sound/sound_sources/data/background_sounds/background_sound.primitive diff --git a/code/nel/samples/sound_sources/data/background_sounds/readme.txt b/code/nel/samples/sound/sound_sources/data/background_sounds/readme.txt similarity index 100% rename from code/nel/samples/sound_sources/data/background_sounds/readme.txt rename to code/nel/samples/sound/sound_sources/data/background_sounds/readme.txt diff --git a/code/nel/samples/sound_sources/data/cluster_sound/readme.txt b/code/nel/samples/sound/sound_sources/data/cluster_sound/readme.txt similarity index 100% rename from code/nel/samples/sound_sources/data/cluster_sound/readme.txt rename to code/nel/samples/sound/sound_sources/data/cluster_sound/readme.txt diff --git a/code/nel/samples/sound_sources/data/cluster_sound/test_clusters.sound_group b/code/nel/samples/sound/sound_sources/data/cluster_sound/test_clusters.sound_group similarity index 100% rename from code/nel/samples/sound_sources/data/cluster_sound/test_clusters.sound_group rename to code/nel/samples/sound/sound_sources/data/cluster_sound/test_clusters.sound_group diff --git a/code/nel/samples/sound_sources/data/default.mixer_config b/code/nel/samples/sound/sound_sources/data/default.mixer_config similarity index 100% rename from code/nel/samples/sound_sources/data/default.mixer_config rename to code/nel/samples/sound/sound_sources/data/default.mixer_config diff --git a/code/nel/samples/sound_sources/data/samplebank/base_samples/beep.wav b/code/nel/samples/sound/sound_sources/data/samplebank/base_samples/beep.wav similarity index 100% rename from code/nel/samples/sound_sources/data/samplebank/base_samples/beep.wav rename to code/nel/samples/sound/sound_sources/data/samplebank/base_samples/beep.wav diff --git a/code/nel/samples/sound_sources/data/samplebank/base_samples/tuut.wav b/code/nel/samples/sound/sound_sources/data/samplebank/base_samples/tuut.wav similarity index 100% rename from code/nel/samples/sound_sources/data/samplebank/base_samples/tuut.wav rename to code/nel/samples/sound/sound_sources/data/samplebank/base_samples/tuut.wav diff --git a/code/nel/samples/sound_sources/data/soundbank/beep.sound b/code/nel/samples/sound/sound_sources/data/soundbank/beep.sound similarity index 100% rename from code/nel/samples/sound_sources/data/soundbank/beep.sound rename to code/nel/samples/sound/sound_sources/data/soundbank/beep.sound diff --git a/code/nel/samples/sound_sources/data/soundbank/tuut.sound b/code/nel/samples/sound/sound_sources/data/soundbank/tuut.sound similarity index 100% rename from code/nel/samples/sound_sources/data/soundbank/tuut.sound rename to code/nel/samples/sound/sound_sources/data/soundbank/tuut.sound diff --git a/code/nel/samples/sound_sources/data/world_editor_classes.xml b/code/nel/samples/sound/sound_sources/data/world_editor_classes.xml similarity index 100% rename from code/nel/samples/sound_sources/data/world_editor_classes.xml rename to code/nel/samples/sound/sound_sources/data/world_editor_classes.xml diff --git a/code/nel/samples/sound_sources/main.cpp b/code/nel/samples/sound/sound_sources/main.cpp similarity index 100% rename from code/nel/samples/sound_sources/main.cpp rename to code/nel/samples/sound/sound_sources/main.cpp diff --git a/code/nel/samples/sound/stream_file/CMakeLists.txt b/code/nel/samples/sound/stream_file/CMakeLists.txt new file mode 100644 index 000000000..0552387cb --- /dev/null +++ b/code/nel/samples/sound/stream_file/CMakeLists.txt @@ -0,0 +1,12 @@ +FILE(GLOB SRC *.cpp *.h) + +ADD_EXECUTABLE(nl_sample_stream_file ${SRC}) + +INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR}) + +TARGET_LINK_LIBRARIES(nl_sample_stream_file nelmisc nelsound) +NL_DEFAULT_PROPS(nl_sample_stream_file "NeL, Samples: Sound: Stream File") +NL_ADD_RUNTIME_FLAGS(nl_sample_stream_file) + +INSTALL(TARGETS nl_sample_stream_file RUNTIME DESTINATION bin COMPONENT samplessound) + diff --git a/code/nel/samples/sound/stream_file/base_samples.sample_bank b/code/nel/samples/sound/stream_file/base_samples.sample_bank new file mode 100644 index 000000000..8e9b5b898 Binary files /dev/null and b/code/nel/samples/sound/stream_file/base_samples.sample_bank differ diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/_typ.dfn b/code/nel/samples/sound/stream_file/data/DFN/basics/_typ.dfn new file mode 100644 index 000000000..2f8971932 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/_typ.dfn @@ -0,0 +1,6 @@ + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/_type.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/_type.typ new file mode 100644 index 000000000..233beca79 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/_type.typ @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/boolean.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/boolean.typ new file mode 100644 index 000000000..fb6821f08 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/boolean.typ @@ -0,0 +1,6 @@ + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/filename.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/filename.typ new file mode 100644 index 000000000..bdad39a48 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/filename.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/float.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/float.typ new file mode 100644 index 000000000..250620f5e --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/float.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/iboolean.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/iboolean.typ new file mode 100644 index 000000000..d29217bed --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/iboolean.typ @@ -0,0 +1,6 @@ + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/int.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/int.typ new file mode 100644 index 000000000..5b88c4fc4 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/int.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/sint16.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/sint16.typ new file mode 100644 index 000000000..0adba3ad0 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/sint16.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/sint32.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/sint32.typ new file mode 100644 index 000000000..ce470d5fe --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/sint32.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/sint64.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/sint64.typ new file mode 100644 index 000000000..5b88c4fc4 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/sint64.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/sint8.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/sint8.typ new file mode 100644 index 000000000..462eab92c --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/sint8.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/string.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/string.typ new file mode 100644 index 000000000..89191b1eb --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/string.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/typ.dfn b/code/nel/samples/sound/stream_file/data/DFN/basics/typ.dfn new file mode 100644 index 000000000..d3c9e1757 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/typ.dfn @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/uint16.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/uint16.typ new file mode 100644 index 000000000..d0e98aad1 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/uint16.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/uint32.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/uint32.typ new file mode 100644 index 000000000..60dc00b3c --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/uint32.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/uint64.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/uint64.typ new file mode 100644 index 000000000..936a619c8 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/uint64.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/basics/uint8.typ b/code/nel/samples/sound/stream_file/data/DFN/basics/uint8.typ new file mode 100644 index 000000000..3dee1c5c8 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/basics/uint8.typ @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/alpha.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/alpha.typ new file mode 100644 index 000000000..a6f7d3bff --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/alpha.typ @@ -0,0 +1,2 @@ + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/angle.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/angle.typ new file mode 100644 index 000000000..db94698e0 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/angle.typ @@ -0,0 +1,2 @@ + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/backgound_sound_item.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/backgound_sound_item.dfn new file mode 100644 index 000000000..bbdfdf82a --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/backgound_sound_item.dfn @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tue Oct 08 14:39:12 2002 (boucher) Dfn Structure = +Tue Oct 08 15:03:40 2002 (boucher) Dfn Structure = +Mon Oct 14 16:09:48 2002 (boucher) Dfn Structure = +Wed Oct 16 18:22:30 2002 (boucher) Dfn Structure = +Thu Dec 19 16:26:49 2002 (boucher) Dfn Structure = +Wed Apr 09 19:38:51 2003 (AmazingSound) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/background_flag_config.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/background_flag_config.dfn new file mode 100644 index 000000000..bce6d82a8 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/background_flag_config.dfn @@ -0,0 +1,9 @@ + + + + + + + + Thu Jan 02 14:38:32 2003 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/background_sound.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/background_sound.dfn new file mode 100644 index 000000000..41288d7b8 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/background_sound.dfn @@ -0,0 +1,5 @@ + + + + Tue Oct 08 14:55:03 2002 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/complex_sound.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/complex_sound.dfn new file mode 100644 index 000000000..8164b9d7a --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/complex_sound.dfn @@ -0,0 +1,19 @@ + + + + + + + + + + + Tue Oct 08 14:29:42 2002 (boucher) Dfn Structure = +Tue Oct 08 14:35:40 2002 (boucher) Dfn Structure = +Thu Oct 10 16:15:18 2002 (boucher) Dfn Structure = +Fri Oct 11 10:26:17 2002 (boucher) Dfn Structure = +Fri Oct 11 11:14:25 2002 (boucher) Dfn Structure = +Fri Oct 11 11:15:41 2002 (boucher) Dfn Structure = +Fri Oct 11 16:01:43 2002 (boucher) Dfn Structure = +Wed Oct 16 11:36:41 2002 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/context_sound.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/context_sound.dfn new file mode 100644 index 000000000..f8d05e697 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/context_sound.dfn @@ -0,0 +1,5 @@ + + + + Thu Nov 07 14:01:21 2002 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/direction.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/direction.dfn new file mode 100644 index 000000000..190223cc1 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/direction.dfn @@ -0,0 +1,6 @@ + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/distance.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/distance.typ new file mode 100644 index 000000000..0981c09a0 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/distance.typ @@ -0,0 +1,2 @@ + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/doppler.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/doppler.typ new file mode 100644 index 000000000..2419f2d6c --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/doppler.typ @@ -0,0 +1,2 @@ + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/gain.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/gain.typ new file mode 100644 index 000000000..2000b2652 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/gain.typ @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/listener.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/listener.dfn new file mode 100644 index 000000000..ba52b5af0 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/listener.dfn @@ -0,0 +1,5 @@ + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/mixer_config.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/mixer_config.dfn new file mode 100644 index 000000000..61daec64b --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/mixer_config.dfn @@ -0,0 +1,14 @@ + + + + + + + + + + + Thu Jan 02 14:33:42 2003 (boucher) Dfn Structure = +Thu Jan 02 14:40:05 2003 (boucher) Dfn Structure = +Thu Jan 02 14:41:32 2003 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/music_sound.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/music_sound.dfn new file mode 100644 index 000000000..1935511b2 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/music_sound.dfn @@ -0,0 +1,13 @@ + + + + + + + + Tue Nov 02 11:28:10 2004 (berenguier) Dfn Structure = +Tue Nov 02 11:30:14 2004 (berenguier) Dfn Structure = +Tue Nov 02 11:40:09 2004 (berenguier) Dfn Structure = +Tue Nov 02 11:40:29 2004 (berenguier) Dfn Structure = +Wed Nov 03 10:54:07 2004 (berenguier) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/parameter_id.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/parameter_id.typ new file mode 100644 index 000000000..bc284afb2 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/parameter_id.typ @@ -0,0 +1,8 @@ + + + + + Mon Feb 10 17:31:53 2003 (boucher) Type Predef = +Mon Feb 10 17:31:53 2003 (boucher) Type Type = String +Mon Feb 10 17:31:53 2003 (boucher) Type UI = NonEditableCombo + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/pattern_mode.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/pattern_mode.typ new file mode 100644 index 000000000..b0ac96de5 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/pattern_mode.typ @@ -0,0 +1,10 @@ + + + + + + Tue Oct 08 14:27:39 2002 (boucher) Type Default = Chained +Tue Oct 08 14:27:39 2002 (boucher) Type Predef = +Tue Oct 08 14:27:39 2002 (boucher) Type Type = String +Tue Oct 08 14:27:39 2002 (boucher) Type UI = NonEditableCombo + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/priority.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/priority.typ new file mode 100644 index 000000000..d9bdbbd07 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/priority.typ @@ -0,0 +1,7 @@ + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/rolloff.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/rolloff.typ new file mode 100644 index 000000000..a5818a1a6 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/rolloff.typ @@ -0,0 +1,2 @@ + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/simple_sound.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/simple_sound.dfn new file mode 100644 index 000000000..60e97cc35 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/simple_sound.dfn @@ -0,0 +1,12 @@ + + + + + + + Wed Apr 09 21:13:23 2003 (AmazingSound) Dfn Structure = +Thu Apr 10 15:38:18 2003 (AmazingSound) Dfn Structure = +Thu Apr 10 16:24:05 2003 (AmazingSound) Dfn Structure = +Wed Apr 30 12:44:51 2003 (AmazingSound) Dfn Structure = +Wed Apr 30 12:45:39 2003 (AmazingSound) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/sound.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/sound.dfn new file mode 100644 index 000000000..a8c336b0b --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/sound.dfn @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + Tue Oct 08 12:33:33 2002 (boucher) Dfn Structure = +Tue Oct 08 14:35:19 2002 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/sound_group.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/sound_group.dfn new file mode 100644 index 000000000..b058b36ff --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/sound_group.dfn @@ -0,0 +1,8 @@ + + + + Thu Jan 09 11:48:30 2003 (boucher) Dfn Structure = +Thu Jan 09 11:51:28 2003 (boucher) Dfn Structure = +Thu Jan 09 11:52:03 2003 (boucher) Dfn Structure = +Thu Jan 09 11:52:08 2003 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/sound_group_item.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/sound_group_item.dfn new file mode 100644 index 000000000..00420dbb9 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/sound_group_item.dfn @@ -0,0 +1,7 @@ + + + + + Thu Jan 09 11:47:55 2003 (boucher) Dfn Structure = +Thu Jan 09 11:53:15 2003 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/soundbank.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/soundbank.dfn new file mode 100644 index 000000000..38a60a89a --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/soundbank.dfn @@ -0,0 +1,4 @@ + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/stream_file_sound.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/stream_file_sound.dfn new file mode 100644 index 000000000..7293fc854 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/stream_file_sound.dfn @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/stream_sound.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/stream_sound.dfn new file mode 100644 index 000000000..82eaa9a6d --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/stream_sound.dfn @@ -0,0 +1,7 @@ + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/transposition.typ b/code/nel/samples/sound/stream_file/data/DFN/sound/transposition.typ new file mode 100644 index 000000000..2d05a4471 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/transposition.typ @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/DFN/sound/user_var_binding.dfn b/code/nel/samples/sound/stream_file/data/DFN/sound/user_var_binding.dfn new file mode 100644 index 000000000..d2740f489 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/DFN/sound/user_var_binding.dfn @@ -0,0 +1,9 @@ + + + + + + Mon Feb 10 17:32:55 2003 (boucher) Dfn Structure = +Mon Feb 10 17:34:17 2003 (boucher) Dfn Structure = +Tue Feb 11 09:49:09 2003 (boucher) Dfn Structure = + diff --git a/code/nel/samples/sound/stream_file/data/animations/readme.txt b/code/nel/samples/sound/stream_file/data/animations/readme.txt new file mode 100644 index 000000000..43c7f150a --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/animations/readme.txt @@ -0,0 +1,3 @@ +This folder contains the sound track for animation. + +Put here all the .sound_anim file generated with NeL Object Viewer. \ No newline at end of file diff --git a/code/nel/samples/sound/stream_file/data/animations/test_anim.sound_anim b/code/nel/samples/sound/stream_file/data/animations/test_anim.sound_anim new file mode 100644 index 000000000..4377e81b6 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/animations/test_anim.sound_anim @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/background_sounds/background_sound.primitive b/code/nel/samples/sound/stream_file/data/background_sounds/background_sound.primitive new file mode 100644 index 000000000..26ff875f1 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/background_sounds/background_sound.primitive @@ -0,0 +1,144 @@ + + + + + + + + + class + audio + + + name + test_audio + + + + + class + sounds + + + name + sounds + + + + + + + + + class + sound_zone + + + name + test_zone + + + sound + beep.sound + + + + + + + + + + + class + sound_path + + + name + test_path + + + sound + tuut.sound + + + + + + + class + sound_point + + + name + test_point + + + sound + tuut.sound + + + + + + + class + sample_banks + + + name + sample_banks + + + + + + + + + bank_names + base_samples + + + class + sample_bank_zone + + + name + base_sample_zone + + + + + + + class + env_fx + + + name + env_fx + + + + + + + + + class + env_fx_zone + + + fx_name + BATHROOM + + + name + test_fx + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/background_sounds/readme.txt b/code/nel/samples/sound/stream_file/data/background_sounds/readme.txt new file mode 100644 index 000000000..6103b141c --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/background_sounds/readme.txt @@ -0,0 +1 @@ +This folder contains the background sound primitive files. \ No newline at end of file diff --git a/code/nel/samples/sound/stream_file/data/cluster_sound/readme.txt b/code/nel/samples/sound/stream_file/data/cluster_sound/readme.txt new file mode 100644 index 000000000..2668e737a --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/cluster_sound/readme.txt @@ -0,0 +1,11 @@ +This folder contains Georges sheets that link sound group defined in +the cluster system (edited in 3DS Max) to a sound sheet. + +This allow to put sound inside the cluster systems. + +Each sheet can contains any number of association. +It is a good practice to merge into a single sheet all +the sound groups/sound sheets thet belong the the +same building. + + \ No newline at end of file diff --git a/code/nel/samples/sound/stream_file/data/cluster_sound/test_clusters.sound_group b/code/nel/samples/sound/stream_file/data/cluster_sound/test_clusters.sound_group new file mode 100644 index 000000000..0ee91ba18 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/cluster_sound/test_clusters.sound_group @@ -0,0 +1,19 @@ + +
+ + + + + + + + + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/default.mixer_config b/code/nel/samples/sound/stream_file/data/default.mixer_config new file mode 100644 index 000000000..fd9ca5a59 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/default.mixer_config @@ -0,0 +1,54 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fri Oct 15 15:26:53 2004 (boucher) .SampleBanks[0] = base_samples + diff --git a/code/nel/samples/sound/stream_file/data/samplebank/base_samples/beep.wav b/code/nel/samples/sound/stream_file/data/samplebank/base_samples/beep.wav new file mode 100644 index 000000000..8b4108a4c Binary files /dev/null and b/code/nel/samples/sound/stream_file/data/samplebank/base_samples/beep.wav differ diff --git a/code/nel/samples/sound/stream_file/data/samplebank/base_samples/tuut.wav b/code/nel/samples/sound/stream_file/data/samplebank/base_samples/tuut.wav new file mode 100644 index 000000000..bae70bc0f Binary files /dev/null and b/code/nel/samples/sound/stream_file/data/samplebank/base_samples/tuut.wav differ diff --git a/code/nel/samples/sound/stream_file/data/soundbank/beep.sound b/code/nel/samples/sound/stream_file/data/soundbank/beep.sound new file mode 100644 index 000000000..dbda3735c --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/soundbank/beep.sound @@ -0,0 +1,19 @@ + +
+ + + + + + + + + + + + Fri Oct 15 15:23:46 2004 (boucher) .SoundType = simple_sound.dfn +Fri Oct 15 15:23:46 2004 (boucher) .SoundType.Filename = beep.wav +Fri Oct 15 15:23:46 2004 (boucher) .SoundType.MaxDistance = 1000 +Fri Oct 15 15:23:46 2004 (boucher) .SoundType.MinDistance = 1.2 +Fri Oct 15 15:27:24 2004 (boucher) .SoundType.Alpha = + diff --git a/code/nel/samples/sound/stream_file/data/soundbank/default_stream.sound b/code/nel/samples/sound/stream_file/data/soundbank/default_stream.sound new file mode 100644 index 000000000..c757128fc --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/soundbank/default_stream.sound @@ -0,0 +1,20 @@ + +
+ + + + + + + + + + + + + Thu Jan 28 23:30:44 2010 (Kaetemi) .AbsolutePosition = false +Thu Jan 28 23:30:44 2010 (Kaetemi) .Priority = Highest +Thu Jan 28 23:30:44 2010 (Kaetemi) .SoundType = stream_sound.dfn +Thu Jan 28 23:30:44 2010 (Kaetemi) .SoundType.MaxDistance = 100000 +Thu Jan 28 23:30:44 2010 (Kaetemi) .SoundType.MinDistance = 200000 + diff --git a/code/nel/samples/sound/stream_file/data/soundbank/stream_file.sound b/code/nel/samples/sound/stream_file/data/soundbank/stream_file.sound new file mode 100644 index 000000000..a98ae3a3c --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/soundbank/stream_file.sound @@ -0,0 +1,18 @@ + +
+ + + + + + + + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/data/soundbank/tuut.sound b/code/nel/samples/sound/stream_file/data/soundbank/tuut.sound new file mode 100644 index 000000000..c45cfba63 --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/soundbank/tuut.sound @@ -0,0 +1,20 @@ + +
+ + + + + + + + + + + + + Fri Oct 15 15:24:54 2004 (boucher) .SoundType = simple_sound.dfn +Fri Oct 15 15:24:54 2004 (boucher) .SoundType.Alpha = 0.5 +Fri Oct 15 15:24:54 2004 (boucher) .SoundType.Filename = tuut.wav +Fri Oct 15 15:24:54 2004 (boucher) .SoundType.MaxDistance = 50 +Fri Oct 15 15:24:54 2004 (boucher) .SoundType.MinDistance = 5 + diff --git a/code/nel/samples/sound/stream_file/data/world_editor_classes.xml b/code/nel/samples/sound/stream_file/data/world_editor_classes.xml new file mode 100644 index 000000000..68ba22ace --- /dev/null +++ b/code/nel/samples/sound/stream_file/data/world_editor_classes.xml @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/nel/samples/sound/stream_file/stream_file.cpp b/code/nel/samples/sound/stream_file/stream_file.cpp new file mode 100644 index 000000000..623ee4ff0 --- /dev/null +++ b/code/nel/samples/sound/stream_file/stream_file.cpp @@ -0,0 +1,184 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include + +// STL includes +#include +#ifdef NL_OS_WINDOWS +# include +#else +# include +#endif + +// NeL includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// For direct play/pause control. +// You should never include this! +#include + +// Project includes + +#ifndef NL_SOUND_DATA +#define NL_SOUND_DATA "." +#endif // NL_SOUND_DATA + +#define RYZOM_DATA "P:/data" +#define SAMPLE_OGG "Pyr Entrance.ogg" + +using namespace std; +using namespace NLMISC; +using namespace NLSOUND; + +namespace NLSAMPLE { + +static UAudioMixer *s_AudioMixer = NULL; +static USource *s_Source = NULL; +static CStreamFileSource *s_StreamFileSource = NULL; +static UGroupController *s_GroupController = NULL; + +static void initSample() +{ + if (!INelContext::isContextInitialised()) + new CApplicationContext(); + CPath::addSearchPath(NL_SOUND_DATA"/data", true, false); + + printf("Sample demonstrating OGG playback using stream file .sound sheets."); + printf("\n\n"); + + s_AudioMixer = UAudioMixer::createAudioMixer(); + + // Set the sample path before init, this allow the mixer to build the sample banks + s_AudioMixer->setSamplePath(NL_SOUND_DATA"/data/samplebank"); + // Packed sheet option, this mean we want packed sheet generated in 'data' folder + s_AudioMixer->setPackedSheetOption(NL_SOUND_DATA"/data", true); + + printf("Select NLSOUND Driver:\n"); + printf(" [1] FMod\n"); + printf(" [2] OpenAl\n"); + printf(" [3] DSound\n"); + printf(" [4] XAudio2\n"); + printf("> "); + int selection = getchar(); getchar(); + printf("\n"); + + // init with 8 tracks, EAX enabled, no ADPCM, and automatic sample bank loading + s_AudioMixer->init(8, true, false, NULL, true, (UAudioMixer::TDriver)(selection - '0')); + s_AudioMixer->setLowWaterMark(1); + + CVector initpos(0.0f, 0.0f, 0.0f); + CVector frontvec(0.0f, 1.0f, 0.0f); + CVector upvec(0.0f, 0.0f, 1.0f); + s_AudioMixer->getListener()->setPos(initpos); + s_AudioMixer->getListener()->setOrientation(frontvec, upvec); + + CPath::addSearchPath(RYZOM_DATA, true, false); + + //NLMISC::CHTimer::startBench(); + + s_Source = s_AudioMixer->createSource(CStringMapper::map("stream_file")); + nlassert(s_Source); + s_StreamFileSource = dynamic_cast(s_Source); + nlassert(s_StreamFileSource); + // s_Source->setSourceRelativeMode(true); + // s_Source->setPitch(2.0f); + + s_GroupController = s_AudioMixer->getGroupController("sound:dialog"); +} + +static void runSample() +{ + s_Source->play(); + + printf("Change volume with - and +\n"); + printf("Press ANY other key to exit\n"); + for (; ; ) + { +#ifdef NL_OS_WINDOWS + if (_kbhit()) + { + switch (_getch()) +#else + char ch; + if (read(0, &ch, 1)) + { + switch (ch) +#endif + { + case '+': + s_GroupController->setGain(s_GroupController->getGain() + 0.1f); + break; + case '-': + s_GroupController->setGain(s_GroupController->getGain() - 0.1f); + break; + case 'x': + s_Source->stop(); + break; + case 's': + s_Source->play(); + break; + case 'p': + s_StreamFileSource->pause(); + break; + case 'r': + s_StreamFileSource->resume(); + break; + case 'e': + s_AudioMixer->playMusic(SAMPLE_OGG, 1000, true, false); + break; + default: + return; + } + } + + s_AudioMixer->update(); + + nlSleep(40); + } +} + +static void releaseSample() +{ + //NLMISC::CHTimer::clear(); + s_GroupController = NULL; + s_StreamFileSource = NULL; + delete s_Source; s_Source = NULL; + delete s_AudioMixer; s_AudioMixer = NULL; +} + + + +} /* namespace NLSAMPLE */ + +int main() +{ + NLSAMPLE::initSample(); + NLSAMPLE::runSample(); + NLSAMPLE::releaseSample(); + return 0; +} + +/* end of file */ diff --git a/code/nel/samples/sound/stream_ogg_vorbis/CMakeLists.txt b/code/nel/samples/sound/stream_ogg_vorbis/CMakeLists.txt new file mode 100644 index 000000000..db978fc88 --- /dev/null +++ b/code/nel/samples/sound/stream_ogg_vorbis/CMakeLists.txt @@ -0,0 +1,12 @@ +FILE(GLOB SRC *.cpp *.h) + +ADD_EXECUTABLE(nl_sample_stream_ogg_vorbis ${SRC}) + +INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR}) + +TARGET_LINK_LIBRARIES(nl_sample_stream_ogg_vorbis nelmisc nelsound) +NL_DEFAULT_PROPS(nl_sample_stream_ogg_vorbis "NeL, Samples: Sound: Stream OGG Vorbis") +NL_ADD_RUNTIME_FLAGS(nl_sample_stream_ogg_vorbis) + +INSTALL(TARGETS nl_sample_stream_ogg_vorbis RUNTIME DESTINATION bin COMPONENT samplessound) + diff --git a/code/nel/samples/sound/stream_ogg_vorbis/stream_ogg_vorbis.cpp b/code/nel/samples/sound/stream_ogg_vorbis/stream_ogg_vorbis.cpp index 441eee932..e0f0df705 100644 --- a/code/nel/samples/sound/stream_ogg_vorbis/stream_ogg_vorbis.cpp +++ b/code/nel/samples/sound/stream_ogg_vorbis/stream_ogg_vorbis.cpp @@ -18,16 +18,10 @@ // STL includes #include -#include - -// 3rd Party includes #ifdef NL_OS_WINDOWS -# pragma warning( push ) -# pragma warning( disable : 4244 ) -#endif -#include -#ifdef NL_OS_WINDOWS -# pragma warning( pop ) +# include +#else +# include #endif // NeL includes @@ -40,6 +34,8 @@ #include #include #include +#include +#include #include // Project includes @@ -48,7 +44,7 @@ #define NL_SOUND_DATA "." #endif // NL_SOUND_DATA -#define SAMPLE_OGG "O:/src/samples/music_stream/data/aeon_1_10_mystic_river.ogg" +#define SAMPLE_OGG "D:/source/kaetemi/toverhex/src/samples/music_stream/data/aeon_1_10_mystic_river.ogg" using namespace std; using namespace NLMISC; @@ -56,275 +52,10 @@ using namespace NLSOUND; namespace NLSAMPLE { -/** - * \brief IAudioDecoder - * \date 2008-08-30 11:38GMT - * \author Jan Boon (Kaetemi) - * IAudioDecoder is only used by the driver implementation to stream - * music files into a readable format (it's a simple decoder interface). - * You should not call these functions (getSongTitle) on nlsound or user level, - * as a driver might have additional music types implemented. - * TODO: Split IAudioDecoder into IAudioDecoder (actual decoding) and IMediaDemuxer (stream splitter), and change the interface to make more sense. - * TODO: Allow user application to register more decoders. - * TODO: Look into libavcodec for decoding audio? - */ -class IAudioDecoder -{ -private: - // pointers - /// Stream from file created by IAudioDecoder - NLMISC::IStream *_InternalStream; - -public: - IAudioDecoder(); - virtual ~IAudioDecoder(); - - /// Create a new music buffer, may return NULL if unknown type, destroy with delete. Filepath lookup done here. If async is true, it will stream from hd, else it will load in memory first. - static IAudioDecoder *createAudioDecoder(const std::string &filepath, bool async, bool loop); - - /// Create a new music buffer from a stream, type is file extension like "ogg" etc. - static IAudioDecoder *createAudioDecoder(const std::string &type, NLMISC::IStream *stream, bool loop); - - /// Get information on a music file (only artist and title at the moment). - static bool getInfo(const std::string &filepath, std::string &artist, std::string &title); - - /// Get audio/container extensions that are currently supported by the nel sound library. - static void getMusicExtensions(std::vector &extensions); - - /// Return if a music extension is supported by the nel sound library. - static bool isMusicExtensionSupported(const std::string &extension); - - /// Get how many bytes the music buffer requires for output minimum. - virtual uint32 getRequiredBytes() = 0; - - /// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end). - virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) = 0; - - /// Get the amount of channels (2 is stereo) in output. - virtual uint8 getChannels() = 0; - - /// Get the samples per second (often 44100) in output. - virtual uint getSamplesPerSec() = 0; - - /// Get the bits per sample (often 16) in output. - virtual uint8 getBitsPerSample() = 0; - - /// Get if the music has ended playing (never true if loop). - virtual bool isMusicEnded() = 0; - - /// Get the total time in seconds. - virtual float getLength() = 0; -}; /* class IAudioDecoder */ - -/** - * \brief CAudioDecoderVorbis - * \date 2008-08-30 11:38GMT - * \author Jan Boon (Kaetemi) - * CAudioDecoderVorbis - * Create trough IAudioDecoder, type "ogg" - */ -class CAudioDecoderVorbis : public IAudioDecoder -{ -protected: - // outside pointers - NLMISC::IStream *_Stream; - - // pointers - - // instances - OggVorbis_File _OggVorbisFile; - bool _Loop; - bool _IsMusicEnded; - sint32 _StreamOffset; - sint32 _StreamSize; -public: - CAudioDecoderVorbis(NLMISC::IStream *stream, bool loop); - virtual ~CAudioDecoderVorbis(); - inline NLMISC::IStream *getStream() { return _Stream; } - inline sint32 getStreamSize() { return _StreamSize; } - inline sint32 getStreamOffset() { return _StreamOffset; } - - /// Get information on a music file (only artist and title at the moment). - static bool getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title); - - /// Get how many bytes the music buffer requires for output minimum. - virtual uint32 getRequiredBytes(); - - /// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end). - virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum); - - /// Get the amount of channels (2 is stereo) in output. - virtual uint8 getChannels(); - - /// Get the samples per second (often 44100) in output. - virtual uint getSamplesPerSec(); - - /// Get the bits per sample (often 16) in output. - virtual uint8 getBitsPerSample(); - - /// Get if the music has ended playing (never true if loop). - virtual bool isMusicEnded(); - - /// Get the total time in seconds. - virtual float getLength(); -}; /* class CAudioDecoderVorbis */ - -IAudioDecoder::IAudioDecoder() : _InternalStream(NULL) -{ - -} - -IAudioDecoder::~IAudioDecoder() -{ - if (_InternalStream) { delete _InternalStream; _InternalStream = NULL; } -} - -IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &filepath, bool async, bool loop) -{ - string lookup = CPath::lookup(filepath, false); - if (lookup.empty()) - { - nlwarning("Music file %s does not exist!", filepath.c_str()); - return NULL; - } - string type = CFile::getExtension(filepath); - - CIFile *ifile = new CIFile(); - ifile->setCacheFileOnOpen(!async); - ifile->allowBNPCacheFileOnOpen(!async); - ifile->open(lookup); - - IAudioDecoder *mb = createAudioDecoder(type, ifile, loop); - - if (mb) mb->_InternalStream = ifile; - else delete ifile; - - return mb; -} - -IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC::IStream *stream, bool loop) -{ - if (!stream) - { - nlwarning("Stream is NULL"); - return NULL; - } - string type_lower = toLower(type); - if (type_lower == "ogg") - { - return new CAudioDecoderVorbis(stream, loop); - } - else - { - nlwarning("Music file type unknown: '%s'", type_lower.c_str()); - return NULL; - } -} - -bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, std::string &title) -{ - string lookup = CPath::lookup(filepath, false); - if (lookup.empty()) - { - nlwarning("Music file %s does not exist!", filepath.c_str()); - return false; - } - string type = CFile::getExtension(filepath); - string type_lower = toLower(type); - - if (type_lower == "ogg") - { - CIFile ifile; - ifile.setCacheFileOnOpen(false); - ifile.allowBNPCacheFileOnOpen(false); - ifile.open(lookup); - return CAudioDecoderVorbis::getInfo(&ifile, artist, title); - } - else - { - nlwarning("Music file type unknown: '%s'", type_lower.c_str()); - artist.clear(); title.clear(); - return false; - } -} - -/// Get audio/container extensions that are currently supported by the nel sound library. -void IAudioDecoder::getMusicExtensions(std::vector &extensions) -{ - extensions.push_back("ogg"); - // extensions.push_back("wav"); // TODO: Easy. -} - -/// Return if a music extension is supported by the nel sound library. -bool IAudioDecoder::isMusicExtensionSupported(const std::string &extension) -{ - return (extension == "ogg"); -} - -size_t vorbisReadFunc(void *ptr, size_t size, size_t nmemb, void *datasource) -{ - CAudioDecoderVorbis *audio_decoder_vorbis = (CAudioDecoderVorbis *)datasource; - NLMISC::IStream *stream = audio_decoder_vorbis->getStream(); - nlassert(stream->isReading()); - sint32 length = (sint32)(size * nmemb); - if (length > audio_decoder_vorbis->getStreamSize() - stream->getPos()) - length = audio_decoder_vorbis->getStreamSize() - stream->getPos(); - stream->serialBuffer((uint8 *)ptr, length); - return length; -} - -int vorbisSeekFunc(void *datasource, ogg_int64_t offset, int whence) -{ - if (whence == SEEK_CUR && offset == 0) - { - // nlwarning(NLSOUND_XAUDIO2_PREFIX "This seek call doesn't do a damn thing, wtf."); - return 0; // ooookkaaaaaayyy - } - - CAudioDecoderVorbis *audio_decoder_vorbis = (CAudioDecoderVorbis *)datasource; - - NLMISC::IStream::TSeekOrigin origin; - switch (whence) - { - case SEEK_SET: - origin = NLMISC::IStream::begin; - break; - case SEEK_CUR: - origin = NLMISC::IStream::current; - break; - case SEEK_END: - origin = NLMISC::IStream::end; - break; - default: - // nlwarning(NLSOUND_XAUDIO2_PREFIX "Seeking to fake origin."); - return -1; - } - - if (audio_decoder_vorbis->getStream()->seek(SEEK_SET ? audio_decoder_vorbis->getStreamOffset() + (sint32)offset : (sint32)offset, origin)) return 0; - else return -1; -} - -//int vorbisCloseFunc(void *datasource) -//{ -// //CAudioDecoderVorbis *audio_decoder_vorbis = (CAudioDecoderVorbis *)datasource; -//} - -long vorbisTellFunc(void *datasource) -{ - CAudioDecoderVorbis *audio_decoder_vorbis = (CAudioDecoderVorbis *)datasource; - return (long)(audio_decoder_vorbis->getStream()->getPos() - audio_decoder_vorbis->getStreamOffset()); -} - -static ov_callbacks OV_CALLBACKS_NLMISC_STREAM = { - (size_t (*)(void *, size_t, size_t, void *)) vorbisReadFunc, - (int (*)(void *, ogg_int64_t, int)) vorbisSeekFunc, - (int (*)(void *)) NULL, //vorbisCloseFunc, - (long (*)(void *)) vorbisTellFunc -}; - static UAudioMixer *s_AudioMixer = NULL; static UStreamSource *s_StreamSource = NULL; static IAudioDecoder *s_AudioDecoder = NULL; +static UGroupController *s_GroupController = NULL; static void initSample() { @@ -367,6 +98,9 @@ static void initSample() string sample = SAMPLE_OGG; s_AudioDecoder = IAudioDecoder::createAudioDecoder(sample, false, false); s_StreamSource->setFormat(s_AudioDecoder->getChannels(), s_AudioDecoder->getBitsPerSample(), (uint32)s_AudioDecoder->getSamplesPerSec()); + //s_StreamSource->setPitch(2.0f); + + s_GroupController = s_AudioMixer->getGroupController("sound:dialog"); } //CMutex *s_Mutex = NULL; @@ -404,15 +138,33 @@ static void runSample() s_StreamSource->play(); - printf("Press ANY key to exit\n"); + printf("Change volume with - and +\n"); + printf("Press ANY other key to exit\n"); while (!s_AudioDecoder->isMusicEnded()) { +#ifdef NL_OS_WINDOWS if (_kbhit()) { - _getch(); - return; + switch (_getch()) +#else + char ch; + if (read(0, &ch, 1)) + { + switch (ch) +#endif + { + case '+': + s_GroupController->setGain(s_GroupController->getGain() + 0.1f); + break; + case '-': + s_GroupController->setGain(s_GroupController->getGain() - 0.1f); + break; + default: + return; + } } bufferMore(bytes); + s_AudioMixer->update(); //if (!s_StreamSource->asUSource()->isst) //{ // printf("*!playing!*"); @@ -424,122 +176,32 @@ static void runSample() while (s_StreamSource->hasFilledBuffersAvailable()) { nlSleep(40); + s_AudioMixer->update(); } s_StreamSource->stop(); printf("End of song\n"); printf("Press ANY key to exit\n"); - while (!_kbhit()) { s_AudioMixer->update(); Sleep(10); } _getch(); +#ifdef NL_OS_WINDOWS + while (!_kbhit()) +#else + char ch; + while (!read(0, &ch, 1)) +#endif + { s_AudioMixer->update(); nlSleep(10); } return; } static void releaseSample() { //NLMISC::CHTimer::clear(); + s_GroupController = NULL; delete s_AudioDecoder; s_AudioDecoder = NULL; delete s_StreamSource; s_StreamSource = NULL; delete s_AudioMixer; s_AudioMixer = NULL; } -CAudioDecoderVorbis::CAudioDecoderVorbis(NLMISC::IStream *stream, bool loop) -: _Stream(stream), _Loop(loop), _StreamSize(0), _IsMusicEnded(false) -{ - _StreamOffset = stream->getPos(); - stream->seek(0, NLMISC::IStream::end); - _StreamSize = stream->getPos(); - stream->seek(_StreamOffset, NLMISC::IStream::begin); - ov_open_callbacks(this, &_OggVorbisFile, NULL, 0, OV_CALLBACKS_NLMISC_STREAM); -} - -CAudioDecoderVorbis::~CAudioDecoderVorbis() -{ - ov_clear(&_OggVorbisFile); -} - -/// Get information on a music file (only artist and title at the moment). -bool CAudioDecoderVorbis::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title) -{ - CAudioDecoderVorbis mbv(stream, false); // just opens and closes the oggvorbisfile thing :) - vorbis_comment *vc = ov_comment(&mbv._OggVorbisFile, -1); - char *title_c = vorbis_comment_query(vc, "title", 0); - if (title_c) title = title_c; else title.clear(); - char *artist_c = vorbis_comment_query(vc, "artist", 0); - if (artist_c) artist = artist_c; else artist.clear(); - return true; -} - -uint32 CAudioDecoderVorbis::getRequiredBytes() -{ - return 0; // no minimum requirement of bytes to buffer out -} - -uint32 CAudioDecoderVorbis::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) -{ - int current_section = 0; // ??? - if (_IsMusicEnded) return 0; - nlassert(minimum <= maximum); // can't have this.. - uint32 bytes_read = 0; - do - { - // signed 16-bit or unsigned 8-bit little-endian samples - int br = ov_read(&_OggVorbisFile, (char *)&buffer[bytes_read], maximum - bytes_read, - 0, // Specifies big or little endian byte packing. 0 for little endian, 1 for b ig endian. Typical value is 0. - getBitsPerSample() == 8 ? 1 : 2, - getBitsPerSample() == 8 ? 0 : 1, // Signed or unsigned data. 0 for unsigned, 1 for signed. Typically 1. - ¤t_section); - // nlinfo(NLSOUND_XAUDIO2_PREFIX "current_section: %i", current_section); - if (br <= 0) - { - if (br == 0) - { - if (_Loop) - { - ov_pcm_seek(&_OggVorbisFile, 0); - //_Stream->seek(0, NLMISC::IStream::begin); - } - else - { - _IsMusicEnded = true; - break; - } - } - else - { - nlwarning("ov_read: %i", br); - } - } - else bytes_read += (uint32)br; - } while (bytes_read < minimum); - return bytes_read; -} - -uint8 CAudioDecoderVorbis::getChannels() -{ - vorbis_info *vi = ov_info(&_OggVorbisFile, -1); - return (uint8)vi->channels; -} - -uint CAudioDecoderVorbis::getSamplesPerSec() -{ - vorbis_info *vi = ov_info(&_OggVorbisFile, -1); - return (uint)vi->rate; -} - -uint8 CAudioDecoderVorbis::getBitsPerSample() -{ - return 16; -} - -bool CAudioDecoderVorbis::isMusicEnded() -{ - return _IsMusicEnded; -} - -float CAudioDecoderVorbis::getLength() -{ - return (float)ov_time_total(&_OggVorbisFile, -1); -} } /* namespace NLSAMPLE */ diff --git a/code/nel/samples/sound_sources/Makefile.am b/code/nel/samples/sound_sources/Makefile.am deleted file mode 100644 index 1e05afa90..000000000 --- a/code/nel/samples/sound_sources/Makefile.am +++ /dev/null @@ -1,23 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2005/04/04 09:45:06 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -bin_PROGRAMS = sound_sources - -sound_sources_SOURCES = main.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -sound_sources_LDADD = ../../src/misc/libnelmisc.la \ - ../../src/sound/libnelsnd.la \ - ../../src/sound/driver/libnelsnd_lowlevel.la \ - ../../src/sound/driver/fmod/libnel_drv_fmod.la \ - ../../src/ligo/libnelligo.la \ - ../../src/georges/libnelgeorges.la \ - ../../src/3d/libnel3d.la - - -# End of Makefile.am - diff --git a/code/nel/src/3d/Makefile.am b/code/nel/src/3d/Makefile.am deleted file mode 100644 index 441c4ee47..000000000 --- a/code/nel/src/3d/Makefile.am +++ /dev/null @@ -1,616 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = driver - -lib_LTLIBRARIES = libnel3d.la - -EXTRA_DIST = mesh_mrm_skin_template.cpp mesh_mrm_skinned_template.cpp - -libnel3d_la_SOURCES = \ - anim_ctrl.cpp \ - anim_detail_trav.cpp \ - anim_detail_trav.h \ - animatable.cpp \ - animatable.h \ - animated_lightmap.cpp \ - animated_lightmap.h \ - animated_material.cpp \ - animated_material.h \ - animated_morph.cpp \ - animated_morph.h \ - animated_value.cpp \ - animated_value.h \ - animation.cpp \ - animation.h \ - animation_optimizer.cpp \ - animation_optimizer.h \ - animation_playlist.cpp \ - animation_playlist.h \ - animation_set.cpp \ - animation_set.h \ - animation_set_user.cpp \ - animation_set_user.h \ - animation_time.cpp \ - async_file_manager_3d.cpp \ - async_file_manager_3d.h \ - async_texture_block.cpp \ - async_texture_block.h \ - async_texture_manager.cpp \ - async_texture_manager.h \ - bezier_patch.cpp \ - bezier_patch.h \ - bloom_effect.cpp \ - bloom_effect.h \ - bone.cpp \ - bone.h \ - bsp_tree.h \ - camera.cpp \ - camera.h \ - camera_col.cpp \ - camera_col.h \ - channel_mixer.cpp \ - channel_mixer.h \ - clip_trav.cpp \ - clip_trav.h \ - cloud.cpp \ - cloud.h \ - cloud_scape.cpp \ - cloud_scape.h \ - cloud_scape_user.cpp \ - cloud_scape_user.h \ - cluster.cpp \ - cluster.h \ - coarse_mesh_build.cpp \ - coarse_mesh_build.h \ - coarse_mesh_manager.cpp \ - coarse_mesh_manager.h \ - computed_string.cpp \ - computed_string.h \ - cube_grid.cpp \ - cube_grid.h \ - cube_map_builder.cpp \ - cube_map_builder.h \ - debug_vb.cpp \ - debug_vb.h \ - deform_2d.cpp \ - deform_2d.h \ - driver.cpp \ - driver.h \ - driver_material_inline.h \ - driver_user.cpp \ - driver_user.h \ - driver_user2.cpp \ - dru.cpp \ - dru.h \ - event_mouse_listener.cpp \ - event_mouse_listener.h \ - fast_ptr_list.cpp \ - fast_ptr_list.h \ - fasthls_modifier.cpp \ - fasthls_modifier.h \ - flare_model.cpp \ - flare_model.h \ - flare_shape.cpp \ - flare_shape.h \ - font_generator.cpp \ - font_generator.h \ - font_manager.cpp \ - font_manager.h \ - frustum.cpp \ - heat_haze.cpp \ - heat_haze.h \ - height_map.cpp \ - hls_color_texture.cpp \ - hls_color_texture.h \ - hls_texture_bank.cpp \ - hls_texture_bank.h \ - hls_texture_manager.cpp \ - hls_texture_manager.h \ - hrc_trav.cpp \ - hrc_trav.h \ - ig_surface_light.cpp \ - ig_surface_light.h \ - ig_surface_light_build.cpp \ - ig_surface_light_build.h \ - index_buffer.cpp \ - index_buffer.h \ - init_3d.cpp \ - init_3d.h \ - instance_group_user.cpp \ - instance_group_user.h \ - instance_lighter.cpp \ - instance_lighter.h \ - key.cpp \ - key.h \ - landscape.cpp \ - landscape.h \ - landscape_collision_grid.cpp \ - landscape_collision_grid.h \ - landscape_def.cpp \ - landscape_def.h \ - landscape_face_vector_manager.cpp \ - landscape_face_vector_manager.h \ - landscape_model.cpp \ - landscape_model.h \ - landscape_profile.cpp \ - landscape_profile.h \ - landscape_user.cpp \ - landscape_user.h \ - landscape_vegetable_block.cpp \ - landscape_vegetable_block.h \ - landscapeig_manager.cpp \ - landscapevb_allocator.cpp \ - landscapevb_allocator.h \ - landscapevb_info.cpp \ - landscapevb_info.h \ - layered_ordering_table.h \ - light.cpp \ - light.h \ - light_contribution.cpp \ - light_contribution.h \ - light_influence_interpolator.cpp \ - light_influence_interpolator.h \ - light_trav.cpp \ - light_trav.h \ - light_user.cpp \ - light_user.h \ - lighting_manager.cpp \ - lighting_manager.h \ - load_balancing_trav.cpp \ - load_balancing_trav.h \ - lod_character_builder.cpp \ - lod_character_builder.h \ - lod_character_instance.cpp \ - lod_character_instance.h \ - lod_character_manager.cpp \ - lod_character_manager.h \ - lod_character_shape.cpp \ - lod_character_shape.h \ - lod_character_shape_bank.cpp \ - lod_character_shape_bank.h \ - lod_character_texture.cpp \ - lod_character_texture.h \ - logic_info.cpp \ - material.cpp \ - material.h \ - matrix_3x4.cpp \ - matrix_3x4.h \ - mesh.cpp \ - mesh.h \ - mesh_base.cpp \ - mesh_base.h \ - mesh_base_instance.cpp \ - mesh_base_instance.h \ - mesh_blender.cpp \ - mesh_blender.h \ - mesh_block_manager.cpp \ - mesh_block_manager.h \ - mesh_geom.cpp \ - mesh_geom.h \ - mesh_instance.cpp \ - mesh_instance.h \ - mesh_morpher.cpp \ - mesh_morpher.h \ - mesh_mrm.cpp \ - mesh_mrm.h \ - mesh_mrm_instance.cpp \ - mesh_mrm_instance.h \ - mesh_mrm_skin.cpp \ - mesh_mrm_skin_template.cpp \ - mesh_mrm_skinned.cpp \ - mesh_mrm_skinned.h \ - mesh_mrm_skinned_instance.cpp \ - mesh_mrm_skinned_instance.h \ - mesh_mrm_skinned_template.cpp \ - mesh_multi_lod.cpp \ - mesh_multi_lod.h \ - mesh_multi_lod_instance.cpp \ - mesh_multi_lod_instance.h \ - mesh_vertex_program.cpp \ - mesh_vertex_program.h \ - meshvp_per_pixel_light.cpp \ - meshvp_per_pixel_light.h \ - meshvp_wind_tree.cpp \ - meshvp_wind_tree.h \ - mini_col.cpp \ - mini_col.h \ - motion_blur.cpp \ - motion_blur.h \ - mrm_builder.cpp \ - mrm_builder.h \ - mrm_internal.cpp \ - mrm_internal.h \ - mrm_level_detail.cpp \ - mrm_level_detail.h \ - mrm_mesh.cpp \ - mrm_mesh.h \ - mrm_parameters.cpp \ - mrm_parameters.h \ - nelu.cpp \ - nelu.h \ - noise_3d.cpp \ - noise_3d.h \ - occlusion_query.h \ - ordering_table.h \ - packed_zone.cpp \ - packed_zone.h \ - packed_world.cpp \ - packed_world.h \ - particle_system.cpp \ - particle_system.h \ - particle_system_manager.cpp \ - particle_system_manager.h \ - particle_system_model.cpp \ - particle_system_model.h \ - particle_system_process.cpp \ - particle_system_process.h \ - particle_system_shape.cpp \ - particle_system_shape.h \ - particle_system_sound_user.cpp \ - patch.cpp \ - patch.h \ - patch_lightmap.cpp \ - patch_noise.cpp \ - patch_rdr_pass.cpp \ - patch_rdr_pass.h \ - patch_render.cpp \ - patch_vegetable.cpp \ - patchdlm_context.cpp \ - patchdlm_context.h \ - patchuv_locator.cpp \ - patchuv_locator.h \ - play_list_manager.cpp \ - play_list_manager.h \ - play_list_manager_user.cpp \ - play_list_manager_user.h \ - play_list_user.cpp \ - play_list_user.h \ - point_light.cpp \ - point_light.h \ - point_light_model.cpp \ - point_light_model.h \ - point_light_named.cpp \ - point_light_named.h \ - point_light_named_array.cpp \ - point_light_named_array.h \ - portal.cpp \ - portal.h \ - primitive_profile.cpp \ - ps_allocator.cpp \ - ps_allocator.h \ - ps_attrib.h \ - ps_attrib_maker.h \ - ps_attrib_maker_bin_op.cpp \ - ps_attrib_maker_bin_op.h \ - ps_attrib_maker_bin_op_inline.h \ - ps_attrib_maker_helper.cpp \ - ps_attrib_maker_helper.h \ - ps_attrib_maker_iterators.h \ - ps_attrib_maker_template.cpp \ - ps_attrib_maker_template.h \ - ps_color.cpp \ - ps_color.h \ - ps_direction.h \ - ps_dot.cpp \ - ps_dot.h \ - ps_edit.h \ - ps_emitter.cpp \ - ps_emitter.h \ - ps_face.cpp \ - ps_face.h \ - ps_face_look_at.cpp \ - ps_face_look_at.h \ - ps_fan_light.cpp \ - ps_fan_light.h \ - ps_float.cpp \ - ps_float.h \ - ps_force.cpp \ - ps_force.h \ - ps_int.cpp \ - ps_int.h \ - ps_iterator.h \ - ps_light.cpp \ - ps_light.h \ - ps_located.cpp \ - ps_located.h \ - ps_lod.h \ - ps_macro.h \ - ps_mesh.cpp \ - ps_mesh.h \ - ps_misc.h \ - ps_particle.cpp \ - ps_particle.h \ - ps_particle2.cpp \ - ps_particle2.h \ - ps_particle_basic.cpp \ - ps_particle_basic.h \ - ps_plane_basis.h \ - ps_plane_basis_maker.cpp \ - ps_plane_basis_maker.h \ - ps_quad.cpp \ - ps_quad.h \ - ps_register_attribs.cpp \ - ps_register_color_attribs.h \ - ps_register_emitters.cpp \ - ps_register_float_attribs.h \ - ps_register_forces.cpp \ - ps_register_int_attribs.h \ - ps_register_particles.cpp \ - ps_register_plane_basis_attribs.h \ - ps_register_zones.cpp \ - ps_ribbon.cpp \ - ps_ribbon.h \ - ps_ribbon_base.cpp \ - ps_ribbon_base.h \ - ps_ribbon_look_at.cpp \ - ps_ribbon_look_at.h \ - ps_shockwave.cpp \ - ps_shockwave.h \ - ps_spawn_info.h \ - ps_sound.cpp \ - ps_sound.h \ - ps_tail_dot.cpp \ - ps_tail_dot.h \ - ps_util.cpp \ - ps_util.h \ - ps_zone.cpp \ - ps_zone.h \ - ptr_set.cpp \ - ptr_set.h \ - quad_effect.cpp \ - quad_effect.h \ - quad_grid.cpp \ - quad_grid.h \ - quad_grid_clip_cluster.cpp \ - quad_grid_clip_cluster.h \ - quad_grid_clip_manager.cpp \ - quad_grid_clip_manager.h \ - radix_sort.cpp \ - radix_sort.h \ - raw_skin.cpp \ - raw_skin.h \ - raw_skinned.cpp \ - raw_skinned.h \ - ray_mesh.cpp \ - ray_mesh.h \ - register_3d.cpp \ - register_3d.h \ - render_trav.cpp \ - render_trav.h \ - root_model.cpp \ - root_model.h \ - scene.cpp \ - scene.h \ - scene_group.cpp \ - scene_group.h \ - scene_user.cpp \ - scene_user.h \ - scissor.cpp \ - seg_remanence.cpp \ - seg_remanence.h \ - seg_remanence_shape.cpp \ - seg_remanence_shape.h \ - shader.cpp \ - shader.h \ - shadow_map.cpp \ - shadow_map.h \ - shadow_map_manager.cpp \ - shadow_map_manager.h \ - shadow_poly_receiver.cpp \ - shadow_poly_receiver.h \ - shadow_skin.cpp \ - shadow_skin.h \ - shape.cpp \ - shape.h \ - shape_bank.cpp \ - shape_bank.h \ - shape_bank_user.cpp \ - shape_bank_user.h \ - shape_info.cpp \ - shape_info.h \ - shifted_triangle_cache.cpp \ - shifted_triangle_cache.h \ - skeleton_model.cpp \ - skeleton_model.h \ - skeleton_shape.cpp \ - skeleton_shape.h \ - skeleton_spawn_script.cpp \ - skeleton_spawn_script.h \ - skeleton_weight.cpp \ - skeleton_weight.h \ - static_quad_grid.cpp \ - static_quad_grid.h \ - std3d.cpp \ - std3d.h \ - stripifier.cpp \ - stripifier.h \ - surface_light_grid.cpp \ - surface_light_grid.h \ - tangent_space_build.cpp \ - tangent_space_build.h \ - target_anim_ctrl.cpp \ - tess_block.cpp \ - tess_block.h \ - tess_face_priority_list.cpp \ - tess_face_priority_list.h \ - tess_list.cpp \ - tess_list.h \ - tessellation.cpp \ - tessellation.h \ - text_context.cpp \ - text_context.h \ - text_context_user.cpp \ - text_context_user.h \ - texture.cpp \ - texture.h \ - texture_blank.cpp \ - texture_blank.h \ - texture_bloom.cpp \ - texture_bloom.h \ - texture_blend.cpp \ - texture_blend.h \ - texture_bump.cpp \ - texture_bump.h \ - texture_cube.cpp \ - texture_cube.h \ - texture_dlm.cpp \ - texture_dlm.h \ - texture_emboss.cpp \ - texture_emboss.h \ - texture_far.cpp \ - texture_far.h \ - texture_file.cpp \ - texture_file.h \ - texture_font.cpp \ - texture_font.h \ - texture_grouped.cpp \ - texture_grouped.h \ - texture_mem.cpp \ - texture_mem.h \ - texture_multi_file.cpp \ - texture_multi_file.h \ - texture_near.cpp \ - texture_near.h \ - texture_user.cpp \ - texture_user.h \ - tile_bank.cpp \ - tile_bank.h \ - tile_color.cpp \ - tile_color.h \ - tile_element.cpp \ - tile_element.h \ - tile_far_bank.cpp \ - tile_far_bank.h \ - tile_light_influence.cpp \ - tile_light_influence.h \ - tile_lumel.cpp \ - tile_lumel.h \ - tile_noise_map.cpp \ - tile_noise_map.h \ - tile_vegetable_desc.cpp \ - tile_vegetable_desc.h \ - track.cpp \ - track.h \ - track_bezier.h \ - track_keyframer.cpp \ - track_keyframer.h \ - track_sampled_common.cpp \ - track_sampled_common.h \ - track_sampled_quat.cpp \ - track_sampled_quat.h \ - track_sampled_quat_small_header.cpp \ - track_sampled_quat_small_header.h \ - track_sampled_vector.cpp \ - track_sampled_vector.h \ - track_tcb.h \ - transform.cpp \ - transform.h \ - transform_shape.cpp \ - transform_shape.h \ - transformable.cpp \ - transformable.h \ - trav_scene.cpp \ - trav_scene.h \ - u_bone.cpp \ - u_camera.cpp \ - u_instance.cpp \ - u_instance_material.cpp \ - u_material.cpp \ - u_particle_system_instance.cpp \ - u_point_light.cpp \ - u_shape.cpp \ - u_skeleton.cpp \ - u_transform.cpp \ - u_transformable.cpp \ - u_visual_collision_mesh.cpp \ - u_water.cpp \ - vegetable.cpp \ - vegetable.h \ - vegetable_blend_layer_model.cpp \ - vegetable_blend_layer_model.h \ - vegetable_clip_block.cpp \ - vegetable_clip_block.h \ - vegetable_def.cpp \ - vegetable_def.h \ - vegetable_instance_group.cpp \ - vegetable_instance_group.h \ - vegetable_light_ex.cpp \ - vegetable_light_ex.h \ - vegetable_manager.cpp \ - vegetable_manager.h \ - vegetable_quadrant.cpp \ - vegetable_quadrant.h \ - vegetable_shape.cpp \ - vegetable_shape.h \ - vegetable_sort_block.cpp \ - vegetable_sort_block.h \ - vegetable_uv8.cpp \ - vegetable_uv8.h \ - vegetablevb_allocator.cpp \ - vegetablevb_allocator.h \ - vertex_buffer.cpp \ - vertex_buffer.h \ - vertex_buffer_heap.cpp \ - vertex_buffer_heap.h \ - vertex_program.cpp \ - vertex_program.h \ - vertex_program_parse.cpp \ - vertex_program_parse.h \ - vertex_stream_manager.cpp \ - vertex_stream_manager.h \ - viewport.cpp \ - visual_collision_entity.cpp \ - visual_collision_entity.h \ - visual_collision_entity_user.cpp \ - visual_collision_entity_user.h \ - visual_collision_manager.cpp \ - visual_collision_manager.h \ - visual_collision_manager_user.cpp \ - visual_collision_manager_user.h \ - visual_collision_mesh.cpp \ - visual_collision_mesh.h \ - water_env_map.cpp \ - water_env_map.h \ - water_env_map_user.cpp \ - water_env_map_user.h \ - water_height_map.cpp \ - water_height_map.h \ - water_model.cpp \ - water_model.h \ - water_pool_manager.cpp \ - water_pool_manager.h \ - water_shape.cpp \ - water_shape.h \ - zone.cpp \ - zone.h \ - zone_corner_smoother.cpp \ - zone_corner_smoother.h \ - zone_lighter.cpp \ - zone_lighter.h \ - zone_manager.cpp \ - zone_manager.h \ - zone_search.cpp \ - zone_search.h \ - zone_smoother.cpp \ - zone_smoother.h \ - zone_symmetrisation.cpp \ - zone_symmetrisation.h \ - zone_tgt_smoother.cpp \ - zone_tgt_smoother.h - -noinst_HEADERS = std3d.h -# mesh_vertex_program.h meshvp_wind_tree.h - -AM_CXXFLAGS = -I$(top_srcdir)/src @FREETYPE_CFLAGS@ - -libnel3d_la_LIBADD = @FREETYPE_LIBS@ -lc -ldl -lpthread - -libnel3d_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = nel-3d.pc - -# End of Makefile.am diff --git a/code/nel/src/3d/driver/Makefile.am b/code/nel/src/3d/driver/Makefile.am deleted file mode 100644 index 0603c2362..000000000 --- a/code/nel/src/3d/driver/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = direct3d - -SUBDIRS = opengl - -# End of Makefile.am diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d.cpp index 5ac1d2906..bfbd3fdb7 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d.cpp @@ -1228,7 +1228,7 @@ bool CDriverD3D::init (uint windowIcon, emptyProc exitFunc) ExitFunc = exitFunc; createCursors(); - + // Register a window class WNDCLASSW wc; diff --git a/code/nel/src/3d/driver/opengl/Makefile.am b/code/nel/src/3d/driver/opengl/Makefile.am deleted file mode 100644 index 744801217..000000000 --- a/code/nel/src/3d/driver/opengl/Makefile.am +++ /dev/null @@ -1,47 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = driver_opengl_8.vcproj \ - driver_opengl.vcproj \ - driver_opengl.dsp \ - driver_opengl.def - -lib_LTLIBRARIES = libnel_drv_opengl.la - -libnel_drv_opengl_la_SOURCES = driver_opengl.cpp \ - driver_opengl.h \ - driver_opengl_extension.cpp \ - driver_opengl_extension.h \ - driver_opengl_extension_def.h \ - driver_opengl_light.cpp \ - driver_opengl_mac.cpp \ - driver_opengl_material.cpp \ - driver_opengl_matrix.cpp \ - driver_opengl_states.cpp \ - driver_opengl_states.h \ - driver_opengl_texture.cpp \ - driver_opengl_vertex.cpp \ - driver_opengl_vertex_buffer_hard.cpp \ - driver_opengl_vertex_buffer_hard.h \ - driver_opengl_vertex_program.cpp \ - stdopengl.cpp \ - stdopengl.h \ - unix_event_emitter.cpp \ - unix_event_emitter.h - - -noinst_HEADERS = stdopengl.h - -AM_CXXFLAGS = -I$(top_srcdir)/src \ - @OPENGL_CFLAGS@ @XF86VIDMODE_CFLAGS@ - -libnel_drv_opengl_la_LIBADD = @OPENGL_LIBS@ @XF86VIDMODE_LIBS@ -lXmu -libnel_drv_opengl_la_LDFLAGS = -no-undefined - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = nel-driverogl.pc - -# End of Makefile.am - diff --git a/code/nel/src/3d/driver/opengl/driver_opengl_light.cpp b/code/nel/src/3d/driver/opengl/driver_opengl_light.cpp index e5c3cff6e..2fc25f94a 100644 --- a/code/nel/src/3d/driver/opengl/driver_opengl_light.cpp +++ b/code/nel/src/3d/driver/opengl/driver_opengl_light.cpp @@ -138,7 +138,7 @@ void CDriverGL::setLightInternal(uint8 num, const CLight& light) } else { - // Deactivate spot properties + // Disable spot properties #ifdef USE_OPENGLES glLightf (lightNum, GL_SPOT_CUTOFF, 180.f); glLightf (lightNum, GL_SPOT_EXPONENT, 0.f); diff --git a/code/nel/src/3d/driver/opengl/driver_opengl_vertex.cpp b/code/nel/src/3d/driver/opengl/driver_opengl_vertex.cpp index 502c971f1..7241d499b 100644 --- a/code/nel/src/3d/driver/opengl/driver_opengl_vertex.cpp +++ b/code/nel/src/3d/driver/opengl/driver_opengl_vertex.cpp @@ -978,7 +978,7 @@ void CDriverGL::setupGlArraysStd(CVertexBufferInfo &vb) // Check type nlassert (vb.Type[CVertexBuffer::Normal]==CVertexBuffer::Float3); _DriverGLStates.enableNormalArray(true); - nglArrayObjectATI(GL_NORMAL_ARRAY, 3, GL_FLOAT, vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::Normal]); + nglArrayObjectATI(GL_NORMAL_ARRAY, 3, GL_FLOAT, vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::Normal]); } else { diff --git a/code/nel/src/3d/nelu.cpp b/code/nel/src/3d/nelu.cpp index 80ef89ec4..3d143bce1 100644 --- a/code/nel/src/3d/nelu.cpp +++ b/code/nel/src/3d/nelu.cpp @@ -58,7 +58,7 @@ bool CNELU::initDriver (uint w, uint h, uint bpp, bool windowed, nlWindow syst CNELU::Driver = NULL; // Init driver. -#if defined(NL_OS_WINDOWS) +#ifdef NL_OS_WINDOWS if (direct3d) { CNELU::Driver= CDRU::createD3DDriver(); @@ -75,6 +75,7 @@ bool CNELU::initDriver (uint w, uint h, uint bpp, bool windowed, nlWindow syst nlwarning ("CNELU::initDriver: no driver found"); return false; } + if (!CNELU::Driver->init()) { nlwarning ("CNELU::initDriver: init() failed"); diff --git a/code/nel/src/3d/particle_system_shape.cpp b/code/nel/src/3d/particle_system_shape.cpp index 5e41ba530..fb7aaa405 100644 --- a/code/nel/src/3d/particle_system_shape.cpp +++ b/code/nel/src/3d/particle_system_shape.cpp @@ -39,7 +39,11 @@ namespace NL3D { using NLMISC::CIFile; +namespace { +NLMISC::CMutex s_PSSMutex; + +} /* anonymous namespace */ // private usage : macro to check the memory integrity #if defined(NL_DEBUG) && defined(NL_OS_WINDOWS) @@ -220,9 +224,8 @@ CParticleSystem *CParticleSystemShape::instanciatePS(CScene &scene, NLMISC::CCon return _SharedSystem; } - // avoid prb with concurent thread (may append if an instance group containing ps is loaded in background) - NLMISC::CMutex mutex; - mutex.enter(); + // avoid prb with concurrent thread (may happen if an instance group containing ps is loaded in background) + s_PSSMutex.enter(); #ifdef PS_FAST_ALLOC @@ -295,7 +298,7 @@ CParticleSystem *CParticleSystemShape::instanciatePS(CScene &scene, NLMISC::CCon } #endif - mutex.leave(); + s_PSSMutex.leave(); /*NLMISC::TTicks end = NLMISC::CTime::getPerformanceTime(); nlinfo("instanciation time = %.2f", (float) (1000 * NLMISC::CTime::ticksToSecond(end - start))); */ @@ -390,8 +393,7 @@ void CParticleSystemShape::flushTextures(IDriver &driver, uint selectedTexture) } else { - NLMISC::CMutex mutex; - mutex.enter(); + s_PSSMutex.enter(); // must create an instance just to flush the textures CParticleSystem *myInstance = NULL; @@ -436,7 +438,7 @@ void CParticleSystemShape::flushTextures(IDriver &driver, uint selectedTexture) #ifdef PS_FAST_ALLOC PSBlockAllocator = NULL; #endif - mutex.leave(); + s_PSSMutex.leave(); } for(uint k = 0; k < _CachedTex.size(); ++k) { diff --git a/code/nel/src/Makefile.am b/code/nel/src/Makefile.am deleted file mode 100644 index dbedcae88..000000000 --- a/code/nel/src/Makefile.am +++ /dev/null @@ -1,19 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -DIST_SUBDIRS = net 3d pacs sound misc georges ligo - -SUBDIRS = @NEL_SUBDIRS@ - -EXTRA_DIST = 3d.vcproj \ - georges.vcproj \ - ligo.vcproj \ - logic.vcproj \ - misc.vcproj \ - net.vcproj \ - pacs.vcproj \ - sound.vcproj - -# End of Makefile.am diff --git a/code/nel/src/Rules.mk b/code/nel/src/Rules.mk deleted file mode 100644 index cb5d3dc57..000000000 --- a/code/nel/src/Rules.mk +++ /dev/null @@ -1,56 +0,0 @@ -############################################################################# -# A few basic default rules and intrinsic rules - -# Load objects dependencies -ifeq (Dependencies.mk,$(wildcard Dependencies.mk)) -include Dependencies.mk -check-deps: - @echo - @echo Dependencies found [OK] - @echo -else -check-deps: - @echo - @echo "No dependencies found [ERROR]" - @echo "You should try 'make update' first" - @echo - @exit 1 -endif - -# Start off by over-riding the default build rules with our own intrinsics -.SUFFIXES: -.SUFFIXES: .cpp .o -.cpp.o: - $(CXX) -c $(CXXFLAGS) $< -o $@ - -# remove object files and core (if any) -clean: - find . -name "core*" -exec $(RM) {} \; - find . -name "*.o" -exec $(RM) {} \; - find . -name "*~" -exec $(RM) {} \; - find . -name "Dependencies.mk" -exec $(RM) {} \; - -# remove object files, core dump, and executable (if any) -distclean: - $(MAKE) clean - $(RM) $(TARGETS) - $(RM) $(TARGETS)_debug - -# make the thing again from scratch -again: - $(MAKE) distclean - $(MAKE) $(TARGETS) - -UPDATE_OBJS=`cat ../$(DSP_TARGET) | grep SOURCE | sed -e 's/\r$$//' | grep "\.cpp$$" | cut -d\\\\ -f3- | tr '\n' ' ' | sed -e 's/=/..\\\\/g' | tr '\n' ' ' | sed -e 's/\\\\/\\//g' | sed -e 's/\.cpp /\.o /g'` - -UPDATE_SRCS=`cat ../$(DSP_TARGET) | grep SOURCE | sed -e 's/\r$$//' | grep "\.cpp$$" | cut -d\\\\ -f3- | tr '\n' ' ' | sed -e 's/=/..\\\\/g' | tr '\n' ' ' | sed -e 's/\\\\/\\//g'` - -dep: update - -update: - ../gen_deps.sh $(CXX) $(CXXFLAGS) -- $(UPDATE_SRCS) > Dependencies.mk - echo "OBJS=$(UPDATE_OBJS)" > Objects.mk - -touch: - $(RM) $(TARGETS) - $(RM) $(TARGETS)_debug diff --git a/code/nel/src/Variables.mk b/code/nel/src/Variables.mk deleted file mode 100644 index 216b4b1c8..000000000 --- a/code/nel/src/Variables.mk +++ /dev/null @@ -1,20 +0,0 @@ -############################################################################# -# Setting up the global compiler settings... - -# The names of the executables -CXX = c++ -RM = rm -f -MAKE = make - -DBG = off - -FLAGS_CMN = -g -pipe -Wno-ctor-dtor-privacy -Wno-multichar -D_REENTRANT -DHAVE_X86 - -FLAGS_DBG_on = -O0 -finline-functions -DNL_DEBUG -DNL_DEBUG_FAST -FLAGS_DBG_off = -O3 -ftemplate-depth-24 -funroll-loops -DNL_RELEASE_DEBUG -DIR_DBG_on = debug -DIR_DBG_off = release - -ifeq (Objects.mk,$(wildcard Objects.mk)) -include Objects.mk -endif diff --git a/code/nel/src/cegui/Makefile.am b/code/nel/src/cegui/Makefile.am deleted file mode 100644 index 43786d8e8..000000000 --- a/code/nel/src/cegui/Makefile.am +++ /dev/null @@ -1,19 +0,0 @@ -# -# $Id: Makefile.am,v 1.7 2002-06-10 17:02:05 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -lib_LTLIBRARIES = libnelceguirenderer.la - -libnelceguirenderer_la_SOURCES = nelrenderer.cpp nelresourceprovider.cpp neltexture.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src @CEGUI_CFLAGS@ - -libnelceguirenderer_la_LIBADD = -lc -lpthread @CEGUI_LIBS@ - -libnelceguirenderer_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - - -# End of Makefile.am - diff --git a/code/nel/src/gen_deps.sh b/code/nel/src/gen_deps.sh deleted file mode 100755 index 45f41e17f..000000000 --- a/code/nel/src/gen_deps.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -command='' -sources='' -phase=0 -for arg in $* -do - if [ "$phase" == 0 ] && [ "$arg" == '--' ] - then - phase=1 - elif [ "$phase" == 0 ] - then - command="$command $arg" - elif [ "$phase" == 1 ] - then - sources="$sources $arg" - fi -done - -for src in $sources -do - obj=`echo $src | sed -e 's/.cpp$/.o/'` - $command -MT $obj -M $src -done - diff --git a/code/nel/src/georges/Makefile.am b/code/nel/src/georges/Makefile.am deleted file mode 100644 index 389110b9f..000000000 --- a/code/nel/src/georges/Makefile.am +++ /dev/null @@ -1,34 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = georges_file_format.txt - -lib_LTLIBRARIES = libnelgeorges.la - -libnelgeorges_la_SOURCES = form.cpp \ - form.h \ - form_loader.cpp \ - form_loader.h \ - form_dfn.cpp \ - form_dfn.h \ - form_elm.cpp \ - form_elm.h \ - stdgeorges.cpp \ - stdgeorges.h \ - header.cpp \ - header.h \ - load_form.cpp \ - type.cpp \ - type.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -libnelgeorges_la_LIBADD = -lc -lpthread - -libnelgeorges_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - - -# End of Makefile.am - diff --git a/code/nel/src/ligo/Makefile.am b/code/nel/src/ligo/Makefile.am deleted file mode 100644 index db6e5d423..000000000 --- a/code/nel/src/ligo/Makefile.am +++ /dev/null @@ -1,36 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -lib_LTLIBRARIES = libnelligo.la - -libnelligo_la_SOURCES = ligo_config.cpp \ - ligo_error.cpp \ - ligo_error.h \ - ligo_material.cpp \ - ligo_material.h \ - primitive.cpp \ - primitive_class.cpp \ - primitive_configuration.cpp \ - transition.cpp \ - transition.h \ - zone_bank.cpp \ - zone_bank.h \ - zone_edge.cpp \ - zone_edge.h \ - zone_region.cpp \ - zone_region.h \ - zone_template.cpp \ - zone_template.h \ - primitive_utils.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -libnelligo_la_LIBADD = -lc -lpthread - -libnelligo_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - - -# End of Makefile.am - diff --git a/code/nel/src/misc/Makefile.am b/code/nel/src/misc/Makefile.am deleted file mode 100644 index 345a210c4..000000000 --- a/code/nel/src/misc/Makefile.am +++ /dev/null @@ -1,142 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = config_file - -lib_LTLIBRARIES = libnelmisc.la - -libnelmisc_la_SOURCES = \ - aabbox.cpp \ - app_context.cpp \ - algo.cpp \ - async_file_manager.cpp \ - big_file.cpp \ - bit_mem_stream.cpp \ - bit_set.cpp \ - bitmap.cpp \ - bitmap_jpeg.cpp \ - bitmap_png.cpp \ - block_memory.cpp \ - bsphere.cpp \ - buf_fifo.cpp \ - class_id.cpp \ - class_registry.cpp \ - command.cpp \ - common.cpp \ - contiguous_block_allocator.cpp \ - cpu_time_stat.cpp \ - debug.cpp \ - di_event_emitter.cpp \ - di_game_device.cpp \ - di_game_device.h \ - di_keyboard_device.cpp \ - di_keyboard_device.h \ - di_mouse_device.cpp \ - di_mouse_device.h \ - diff_tool.cpp \ - displayer.cpp \ - eid_translator.cpp \ - entity_id.cpp \ - eval_num_expr.cpp \ - event_emitter.cpp \ - event_emitter_multi.cpp \ - event_listener.cpp \ - event_server.cpp \ - events.cpp \ - fast_floor.cpp \ - fast_mem.cpp \ - file.cpp \ - fixed_size_allocator.cpp \ - game_device.cpp \ - game_device_events.cpp \ - geom_ext.cpp \ - grid_traversal.cpp \ - gtk_displayer.cpp \ - heap_memory.cpp \ - hierarchical_timer.cpp \ - i18n.cpp \ - i_xml.cpp \ - input_device.cpp \ - input_device_server.cpp \ - keyboard_device.cpp \ - line.cpp \ - log.cpp \ - matrix.cpp \ - md5.cpp \ - mem_displayer.cpp \ - mem_stream.cpp \ - mouse_smoother.cpp \ - mutex.cpp \ - noise_value.cpp \ - o_xml.cpp \ - object_arena_allocator.cpp \ - object_vector.cpp \ - p_thread.cpp \ - path.cpp \ - plane.cpp \ - polygon.cpp \ - progress_callback.cpp \ - quad.cpp \ - quat.cpp \ - reader_writer.cpp \ - rect.cpp \ - report.cpp \ - rgba.cpp \ - sha1.cpp \ - shared_memory.cpp \ - sheet_id.cpp \ - smart_ptr.cpp \ - stdmisc.cpp \ - stdmisc.h \ - stl_block_allocator.cpp \ - stl_block_list.cpp \ - stop_watch.cpp \ - stream.cpp \ - string_common.cpp \ - string_id_array.cpp \ - string_mapper.cpp \ - system_info.cpp \ - system_utils.cpp \ - task_manager.cpp \ - tds.cpp \ - time_nl.cpp \ - triangle.cpp \ - uv.cpp \ - unicode.cpp \ - value_smoother.cpp \ - variable.cpp \ - vector.cpp \ - vector_2d.cpp \ - vector_2f.cpp \ - vector_h.cpp \ - vectord.cpp \ - win_displayer.cpp \ - win_event_emitter.cpp \ - win_thread.cpp \ - window_displayer.cpp \ - words_dictionary.cpp \ - dynloadlib.cpp \ - sstring.cpp \ - co_task.cpp \ - xml_pack.cpp \ - inter_window_msg_queue.cpp \ - win32_util.cpp - - -noinst_HEADERS = di_game_device.h \ - di_keyboard_device.h \ - di_mouse_device.h \ - stdmisc.h - - -libnelmisc_la_LIBADD = config_file/libconfig.la -lc -lpthread -lrt -ldl -lpng -ljpeg - -libnelmisc_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = nel-misc.pc - -# End of Makefile.am - diff --git a/code/nel/src/misc/bit_mem_stream.cpp b/code/nel/src/misc/bit_mem_stream.cpp index c050d9c61..007b95cbc 100644 --- a/code/nel/src/misc/bit_mem_stream.cpp +++ b/code/nel/src/misc/bit_mem_stream.cpp @@ -599,7 +599,7 @@ void CBitMemStream::append( const CBitMemStream& newBits ) /* * Serial bitmemstream */ -void CBitMemStream::serialMemStream(CBitMemStream &b) +void CBitMemStream::serialMemStream(CMemStream &b) { #ifdef LOG_ALL_TRAFFIC sint32 bitpos = getPosInBit(); diff --git a/code/nel/src/misc/bitmap_jpeg.cpp b/code/nel/src/misc/bitmap_jpeg.cpp index 835110d86..a4eaaa0b9 100644 --- a/code/nel/src/misc/bitmap_jpeg.cpp +++ b/code/nel/src/misc/bitmap_jpeg.cpp @@ -109,7 +109,7 @@ static void jpgDecompressSkip(j_decompress_ptr cinfo, long num_bytes) } } -static void jpgDecompressTerm(j_decompress_ptr cinfo) +static void jpgDecompressTerm(j_decompress_ptr /* cinfo */) { } diff --git a/code/ryzom/client/src/cdb.cpp b/code/nel/src/misc/cdb.cpp similarity index 90% rename from code/ryzom/client/src/cdb.cpp rename to code/nel/src/misc/cdb.cpp index f670932ec..8d4c6527f 100644 --- a/code/ryzom/client/src/cdb.cpp +++ b/code/nel/src/misc/cdb.cpp @@ -16,21 +16,19 @@ -#include "stdpch.h" - ////////////// // Includes // ////////////// -#include "cdb.h" -#include "cdb_branch.h" -#include +#include "nel/misc/cdb.h" +#include "nel/misc/cdb_branch.h" +#include "nel/misc/bit_mem_stream.h" //////////////// // Namespaces // //////////////// -using namespace NLMISC; using namespace std; +namespace NLMISC{ CStringMapper *ICDBNode::_DBSM = NULL; @@ -82,3 +80,6 @@ void ICDBNode::releaseStringMapper() delete _DBSM; _DBSM = NULL; } + +} + diff --git a/code/nel/src/misc/cdb_bank_handler.cpp b/code/nel/src/misc/cdb_bank_handler.cpp new file mode 100644 index 000000000..f090ece4c --- /dev/null +++ b/code/nel/src/misc/cdb_bank_handler.cpp @@ -0,0 +1,96 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "nel/misc/cdb_bank_handler.h" + +namespace NLMISC{ + CCDBBankHandler::CCDBBankHandler(uint maxbanks) : +_CDBBankToUnifiedIndexMapping( maxbanks, std::vector< uint >() ), +_FirstLevelIdBitsByBank( maxbanks ) + { + std::fill( _FirstLevelIdBitsByBank.begin(), _FirstLevelIdBitsByBank.end(), 0 ); + maxBanks = maxbanks; + } + + uint CCDBBankHandler::getUIDForBank( uint bank ) const + { + uint uid = static_cast< uint >( -1 ); + + for( uint i = 0; i < _UnifiedIndexToBank.size(); i++ ) + if( _UnifiedIndexToBank[ i ] == bank ) + return i; + + return uid; + } + + uint CCDBBankHandler::getBankByName( const std::string &name ) const + { + uint b = static_cast< uint >( -1 ); + + for( uint i = 0; i < _CDBBankNames.size(); i++ ) + if( _CDBBankNames[ i ].compare( name ) == 0 ) + return i; + + return b; + } + + void CCDBBankHandler::mapNodeByBank( const std::string &bankName ) + { + uint b = getBankByName( bankName ); + // no such bank + if( b == static_cast< uint >( -1 ) ) + return; + + _CDBBankToUnifiedIndexMapping[ b ].push_back( _CDBLastUnifiedIndex ); + ++_CDBLastUnifiedIndex; + _UnifiedIndexToBank.push_back( b ); + } + + void CCDBBankHandler::fillBankNames( const char **strings, uint size ) + { + _CDBBankNames.clear(); + + for( uint i = 0; i < size; i++ ) + _CDBBankNames.push_back( std::string( strings[ i ] ) ); + } + + void CCDBBankHandler::reset() + { + for( std::vector< std::vector< uint > >::iterator itr =_CDBBankToUnifiedIndexMapping.begin(); + itr != _CDBBankToUnifiedIndexMapping.end(); ++itr ) + itr->clear(); + + _UnifiedIndexToBank.clear(); + _CDBLastUnifiedIndex = 0; + } + + void CCDBBankHandler::calcIdBitsByBank() + { + for( uint bank = 0; bank != maxBanks; bank++ ) + { + uint nbNodesOfBank = static_cast< uint >( _CDBBankToUnifiedIndexMapping[ bank ].size() ); + uint idb = 0; + + if ( nbNodesOfBank > 0 ) + for ( idb = 1; nbNodesOfBank > unsigned( 1 << idb ) ; idb++ ) + ; + + _FirstLevelIdBitsByBank[ bank ] = idb; + } + } +} + + diff --git a/code/ryzom/client/src/cdb_branch.cpp b/code/nel/src/misc/cdb_branch.cpp similarity index 67% rename from code/ryzom/client/src/cdb_branch.cpp rename to code/nel/src/misc/cdb_branch.cpp index d7e3f185f..6d8301049 100644 --- a/code/ryzom/client/src/cdb_branch.cpp +++ b/code/nel/src/misc/cdb_branch.cpp @@ -16,8 +16,6 @@ -#include "stdpch.h" - //#define TRACE_READ_DELTA //#define TRACE_WRITE_DELTA //#define TRACE_SET_VALUE @@ -28,16 +26,14 @@ ////////////// // Includes // ////////////// -#include "cdb_branch.h" -#include "cdb_leaf.h" +#include "nel/misc/cdb_branch.h" +#include "nel/misc/cdb_leaf.h" #include "nel/misc/xml_auto_ptr.h" //#include -#include "interface_v3/interface_manager.h" //////////////// // Namespaces // //////////////// -using namespace NLMISC; using namespace std; @@ -46,6 +42,9 @@ using namespace std; #include "nel/misc/file.h" #include "nel/misc/i_xml.h" #include "nel/misc/progress_callback.h" +#include "nel/misc/bit_mem_stream.h" +#include "nel/misc/bit_set.h" +#include "nel/misc/cdb_bank_handler.h" #include //#include @@ -56,76 +55,9 @@ using namespace std; using namespace std; -using namespace NLMISC; -///////////// -// GLOBALS // -///////////// - -CCDBNodeBranch::CDBBranchObsInfo *CCDBNodeBranch::_FirstNotifiedObs[2] = { NULL, NULL }; -CCDBNodeBranch::CDBBranchObsInfo *CCDBNodeBranch::_LastNotifiedObs[2] = { NULL, NULL }; -CCDBNodeBranch::CDBBranchObsInfo *CCDBNodeBranch::_CurrNotifiedObs = NULL; -CCDBNodeBranch::CDBBranchObsInfo *CCDBNodeBranch::_NextNotifiedObs = NULL; - -// -uint CCDBNodeBranch::_CurrNotifiedObsList = 0; - -// Mapping from server database index to client database index (first-level nodes) -vector CCDBNodeBranch::_CDBBankToUnifiedIndexMapping [NB_CDB_BANKS]; - -// Mapping from client database index to TCDBBank (first-level nodes) -vector CCDBNodeBranch::_UnifiedIndexToBank; - -// Last index mapped -uint CCDBNodeBranch::_CDBLastUnifiedIndex = 0; - -// Number of bits for first-level branches, by bank -uint CCDBNodeBranch::_FirstLevelIdBitsByBank [NB_CDB_BANKS]; - -extern const char *CDBBankNames[INVALID_CDB_BANK+1]; - -// reset all static data -void CCDBNodeBranch::reset() -{ - for ( uint b=0; b &nodes, std::vector &nodesSorted, xmlNodePtr &child, const string& bankName, bool atomBranch, bool clientOnly, - NLMISC::IProgressCallback &progressCallBack, - bool mapBanks ) + IProgressCallback &progressCallBack, + bool mapBanks, CCDBBankHandler *bankHandler = NULL ) { nodesSorted.push_back(newNode); nodes.push_back(newNode); @@ -149,7 +81,7 @@ static /*inline*/ void addNode( ICDBNode *newNode, std::string newName, CCDBNode { if ( ! bankName.empty() ) { - CCDBNodeBranch::mapNodeByBank( newNode, bankName, clientOnly, (uint)nodes.size()-1 ); + bankHandler->mapNodeByBank( bankName ); //nldebug( "CDB: Mapping %s for %s (node %u)", newName.c_str(), bankName.c_str(), nodes.size()-1 ); } else @@ -159,7 +91,7 @@ static /*inline*/ void addNode( ICDBNode *newNode, std::string newName, CCDBNode } } -void CCDBNodeBranch::init( xmlNodePtr node, NLMISC::IProgressCallback &progressCallBack, bool mapBanks ) +void CCDBNodeBranch::init( xmlNodePtr node, IProgressCallback &progressCallBack, bool mapBanks, CCDBBankHandler *bankHandler ) { xmlNodePtr child; @@ -198,7 +130,7 @@ void CCDBNodeBranch::init( xmlNodePtr node, NLMISC::IProgressCallback &progressC // nlinfo("+ %s%d",name,i); string newName = string(name.getDatas())+toString(i); - addNode( new CCDBNodeBranch(newName), newName, this, _Nodes, _NodesByName, child, sBank, sAtom=="1", sClientonly=="1", progressCallBack, mapBanks ); + addNode( new CCDBNodeBranch(newName), newName, this, _Nodes, _NodesByName, child, sBank, sAtom=="1", sClientonly=="1", progressCallBack, mapBanks, bankHandler ); // nlinfo("-"); // Progress bar @@ -210,7 +142,7 @@ void CCDBNodeBranch::init( xmlNodePtr node, NLMISC::IProgressCallback &progressC // dealing with a single entry // nlinfo("+ %s",name); string newName = string(name.getDatas()); - addNode( new CCDBNodeBranch(newName), newName, this, _Nodes, _NodesByName, child, sBank, sAtom=="1", sClientonly=="1", progressCallBack, mapBanks ); + addNode( new CCDBNodeBranch(newName), newName, this, _Nodes, _NodesByName, child, sBank, sAtom=="1", sClientonly=="1", progressCallBack, mapBanks, bankHandler ); // nlinfo("-"); } @@ -247,7 +179,7 @@ void CCDBNodeBranch::init( xmlNodePtr node, NLMISC::IProgressCallback &progressC // nlinfo(" %s%d",name,i); string newName = string(name.getDatas())+toString(i); - addNode( new CCDBNodeLeaf(newName), newName, this, _Nodes, _NodesByName, child, sBank, false, false, progressCallBack, mapBanks ); + addNode( new CCDBNodeLeaf(newName), newName, this, _Nodes, _NodesByName, child, sBank, false, false, progressCallBack, mapBanks, bankHandler ); // Progress bar progressCallBack.popCropedValues (); @@ -257,7 +189,7 @@ void CCDBNodeBranch::init( xmlNodePtr node, NLMISC::IProgressCallback &progressC { // nlinfo(" %s",name); string newName = string(name.getDatas()); - addNode( new CCDBNodeLeaf(newName), newName, this, _Nodes, _NodesByName, child, sBank, false, false, progressCallBack, mapBanks ); + addNode( new CCDBNodeLeaf(newName), newName, this, _Nodes, _NodesByName, child, sBank, false, false, progressCallBack, mapBanks, bankHandler ); } // Progress bar @@ -267,18 +199,11 @@ void CCDBNodeBranch::init( xmlNodePtr node, NLMISC::IProgressCallback &progressC // count number of bits required to store the id if ( (mapBanks) && (getParent() == NULL) ) - { - nlassertex( _UnifiedIndexToBank.size() == countNode, ("Mapped: %u Nodes: %u", _UnifiedIndexToBank.size(), countNode) ); - + { + nlassert( bankHandler != NULL ); + nlassertex( bankHandler->getUnifiedIndexToBankSize() == countNode, ("Mapped: %u Nodes: %u", bankHandler->getUnifiedIndexToBankSize(), countNode) ); + bankHandler->calcIdBitsByBank(); _IdBits = 0; - for ( uint b=0; b!=NB_CDB_BANKS; ++b ) - { - uint nbNodesOfBank = (uint)_CDBBankToUnifiedIndexMapping[b].size(); - uint idb = 0; - if ( nbNodesOfBank > 0 ) - for ( idb=1; nbNodesOfBank > unsigned(1<getFirstLevelIdBits( bank ) ); // Translate bank index -> unified index - idx = _CDBBankToUnifiedIndexMapping[bank][idx]; + idx = bankHandler->getServerToClientUIDMapping( bank, idx ); if (idx >= _Nodes.size()) { throw Exception ("idx %d > _Nodes.size() %d ", idx, _Nodes.size()); @@ -492,7 +417,7 @@ void CCDBNodeBranch::readAndMapDelta( NLMISC::TGameCycle gc, NLMISC::CBitMemStre // readDelta // //----------------------------------------------- -void CCDBNodeBranch::readDelta( NLMISC::TGameCycle gc, CBitMemStream & f ) +void CCDBNodeBranch::readDelta( TGameCycle gc, CBitMemStream & f ) { if ( isAtomic() ) { @@ -628,7 +553,7 @@ void CCDBNodeBranch::display (const std::string &prefix) } } -void CCDBNodeBranch::removeNode (CTextId& id) +void CCDBNodeBranch::removeNode (const CTextId& id) { // Look for the node CCDBNodeBranch *pNode = dynamic_cast(getNode(id,false)); @@ -674,126 +599,14 @@ void CCDBNodeBranch::removeNode (CTextId& id) delete pNode; } - - - -//----------------------------------------------- -void CCDBNodeBranch::flushObserversCalls() +void CCDBNodeBranch::onLeafChanged( NLMISC::TStringId leafName ) { - H_AUTO ( RZ_Interface_flushObserversCalls ) + for( TObserverHandleList::iterator itr = observerHandles.begin(); itr != observerHandles.end(); ++itr ) + if( (*itr)->observesLeaf( *leafName ) ) + (*itr)->addToFlushableList(); -// nlassert(_CrtCheckMemory()); - _CurrNotifiedObs = _FirstNotifiedObs[_CurrNotifiedObsList]; - while (_CurrNotifiedObs) - { - // modified node should now store them in other list when they're modified - _CurrNotifiedObsList = 1 - _CurrNotifiedObsList; - // switch list so that modified node are stored in the other list - while (_CurrNotifiedObs) - { - _NextNotifiedObs = _CurrNotifiedObs->NextNotifiedObserver[1 - _CurrNotifiedObsList]; - nlassert(_CurrNotifiedObs->Owner); - if (_CurrNotifiedObs->Observer) - _CurrNotifiedObs->Observer->update(_CurrNotifiedObs->Owner); - if (_CurrNotifiedObs) // this may be modified by the call (if current observer is removed) - { - _CurrNotifiedObs->unlink(1 - _CurrNotifiedObsList); - } - _CurrNotifiedObs = _NextNotifiedObs; - } - nlassert(_FirstNotifiedObs[1 - _CurrNotifiedObsList] == NULL); - nlassert(_LastNotifiedObs[1 - _CurrNotifiedObsList] == NULL); - // update triggered link - CInterfaceLink::updateTrigeredLinks(); - // examine other list to see if nodes have been registered - _CurrNotifiedObs = _FirstNotifiedObs[_CurrNotifiedObsList]; - } - CInterfaceLink::updateTrigeredLinks(); // should call it at least once -// nlassert(_CrtCheckMemory()); -} - -//----------------------------------------------- -void CCDBNodeBranch::CDBBranchObsInfo::link(uint list, NLMISC::TStringId modifiedLeafName) -{ - // If there a filter set? - if (!PositiveLeafNameFilter.empty()) - { - // Don't link if modifiedLeafName is not in the filter - if (std::find(PositiveLeafNameFilter.begin(), PositiveLeafNameFilter.end(), modifiedLeafName) == PositiveLeafNameFilter.end()) - return; - } - -// nlassert(_CrtCheckMemory()); - nlassert(list < 2); - if (Touched[list]) return; // already inserted in list - Touched[list] = true; - nlassert(!PrevNotifiedObserver[list]); - nlassert(!NextNotifiedObserver[list]); - if (!_FirstNotifiedObs[list]) - { - _FirstNotifiedObs[list] = _LastNotifiedObs[list] = this; - } - else - { - nlassert(!_LastNotifiedObs[list]->NextNotifiedObserver[list]); - _LastNotifiedObs[list]->NextNotifiedObserver[list] = this; - PrevNotifiedObserver[list] = _LastNotifiedObs[list]; - _LastNotifiedObs[list] = this; - } -// nlassert(_CrtCheckMemory()); -} - -//----------------------------------------------- -void CCDBNodeBranch::CDBBranchObsInfo::unlink(uint list) -{ -// nlassert(_CrtCheckMemory()); - nlassert(list < 2); - if (!Touched[list]) - { - // not linked in this list - nlassert(PrevNotifiedObserver[list] == NULL); - nlassert(NextNotifiedObserver[list] == NULL); - return; - } - if (PrevNotifiedObserver[list]) - { - PrevNotifiedObserver[list]->NextNotifiedObserver[list] = NextNotifiedObserver[list]; - } - else - { - // this was the first node - _FirstNotifiedObs[list] = NextNotifiedObserver[list]; - } - if (NextNotifiedObserver[list]) - { - NextNotifiedObserver[list]->PrevNotifiedObserver[list] = PrevNotifiedObserver[list]; - } - else - { - // this was the last node - _LastNotifiedObs[list] = PrevNotifiedObserver[list]; - } - PrevNotifiedObserver[list] = NULL; - NextNotifiedObserver[list] = NULL; - Touched[list] = false; -// nlassert(_CrtCheckMemory()); -} - -//----------------------------------------------- -void CCDBNodeBranch::linkInModifiedNodeList(NLMISC::TStringId modifiedLeafName) -{ -// nlassert(_CrtCheckMemory()); - CCDBNodeBranch *curr = this; - do - { - for(TObsList::iterator it = curr->_Observers.begin(); it != curr->_Observers.end(); ++it) - { - it->link(_CurrNotifiedObsList, modifiedLeafName); - } - curr = curr->getParent(); - } - while(curr); -// nlassert(_CrtCheckMemory()); + if( _Parent != NULL ) + _Parent->onLeafChanged( leafName ); } @@ -866,14 +679,21 @@ bool CCDBNodeBranch::removeObserver(IPropertyObserver* observer, CTextId& id) //----------------------------------------------- -void CCDBNodeBranch::addBranchObserver(IPropertyObserver* observer, const std::vector& positiveLeafNameFilter) +void CCDBNodeBranch::addBranchObserver( ICDBDBBranchObserverHandle *handle, const std::vector& positiveLeafNameFilter) { - CDBBranchObsInfo oi(observer, this, positiveLeafNameFilter); - _Observers.push_front(oi); + CCDBNodeBranch::TObserverHandleList::iterator itr + = std::find( observerHandles.begin(), observerHandles.end(), handle ); + + if( itr != observerHandles.end() ){ + delete handle; + return; + } + + observerHandles.push_back( handle ); } //----------------------------------------------- -void CCDBNodeBranch::addBranchObserver(const char *dbPathFromThisNode, ICDBNode::IPropertyObserver& observer, const char **positiveLeafNameFilter, uint positiveLeafNameFilterSize) +void CCDBNodeBranch::addBranchObserver( ICDBDBBranchObserverHandle *handle, const char *dbPathFromThisNode, const char **positiveLeafNameFilter, uint positiveLeafNameFilterSize) { CCDBNodeBranch *branchNode; if (dbPathFromThisNode[0] == '\0') // empty string @@ -883,21 +703,37 @@ void CCDBNodeBranch::addBranchObserver(const char *dbPathFromThisNode, ICDBNode: else { branchNode = safe_cast(getNode(ICDBNode::CTextId(dbPathFromThisNode), false)); - BOMB_IF (!branchNode, (*getName()) << ":" << dbPathFromThisNode << " branch missing in DB", return); + if( branchNode == NULL ){ + std::string msg = *getName(); + msg += ":"; + msg += dbPathFromThisNode; + msg += " branch missing in DB"; + + nlerror( msg.c_str() ); + delete handle; + return; + } } std::vector leavesToMonitor(positiveLeafNameFilterSize); for (uint i=0; i!=positiveLeafNameFilterSize; ++i) { leavesToMonitor[i] = string(positiveLeafNameFilter[i]); } - branchNode->addBranchObserver(&observer, leavesToMonitor); + branchNode->addBranchObserver(handle, leavesToMonitor); } //----------------------------------------------- void CCDBNodeBranch::removeBranchObserver(const char *dbPathFromThisNode, ICDBNode::IPropertyObserver& observer) { CCDBNodeBranch *branchNode = safe_cast(getNode(ICDBNode::CTextId(dbPathFromThisNode), false)); - BOMB_IF (!branchNode, (*getName()) << ":" << dbPathFromThisNode << " branch missing in DB", return); + if( branchNode == NULL ){ + std::string msg = *getName(); + msg += ":"; + msg += dbPathFromThisNode; + msg += " branch missing in DB"; + nlerror( msg.c_str() ); + return; + } branchNode->removeBranchObserver(&observer); } @@ -906,55 +742,38 @@ void CCDBNodeBranch::removeBranchObserver(const char *dbPathFromThisNode, ICDBNo bool CCDBNodeBranch::removeBranchObserver(IPropertyObserver* observer) { bool found = false; - TObsList::iterator it = _Observers.begin(); - while (it != _Observers.end()) + + TObserverHandleList::iterator itr = observerHandles.begin(); + while( itr != observerHandles.end() ) { - if (it->Observer == observer) + if( (*itr)->observer() == observer ) { - removeBranchInfoIt(it); - it = _Observers.erase(it); + (*itr)->removeFromFlushableList(); + delete *itr; + itr = observerHandles.erase( itr ); found = true; } else { - ++it; + ++itr; } } + return found; } //----------------------------------------------- void CCDBNodeBranch::removeAllBranchObserver() { - TObsList::iterator it = _Observers.begin(); - while (it != _Observers.end()) - { - removeBranchInfoIt(it); - ++it; + for( TObserverHandleList::iterator itr = observerHandles.begin(); + itr != observerHandles.end(); ++itr ){ + (*itr)->removeFromFlushableList(); + delete *itr; } - _Observers.clear(); -} -//----------------------------------------------- -void CCDBNodeBranch::removeBranchInfoIt(TObsList::iterator it) -{ - CDBBranchObsInfo *oi = &(*it); - // if this node is currenlty being notified, update iterators - if (oi == _CurrNotifiedObs) - { - _CurrNotifiedObs = NULL; - } - if (oi == _NextNotifiedObs) - { - nlassert(_CurrNotifiedObsList < 2); - _NextNotifiedObs = _NextNotifiedObs->NextNotifiedObserver[1 - _CurrNotifiedObsList]; - } - // unlink observer from both update lists - oi->unlink(0); - oi->unlink(1); + observerHandles.clear(); } - //----------------------------------------------- // Useful for find //----------------------------------------------- @@ -1013,3 +832,5 @@ ICDBNode *CCDBNodeBranch::find(const std::string &nodeName) #undef TRACE_SET_VALUE #endif +} + diff --git a/code/nel/src/misc/cdb_branch_observing_handler.cpp b/code/nel/src/misc/cdb_branch_observing_handler.cpp new file mode 100644 index 000000000..8f531b9a3 --- /dev/null +++ b/code/nel/src/misc/cdb_branch_observing_handler.cpp @@ -0,0 +1,201 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "nel/misc/cdb_branch_observing_handler.h" + +namespace NLMISC{ + CCDBBranchObservingHandler::CCDBBranchObservingHandler() + { + reset(); + } + + CCDBBranchObservingHandler::~CCDBBranchObservingHandler() + { + reset(); + } + + void CCDBBranchObservingHandler::flushObserverCalls() + { + bool flushed = false; + + do + { + flushed = false; + uint oldList = currentList; + currentList = 1 - currentList; + + std::list< CCDBNodeBranch::ICDBDBBranchObserverHandle* >::iterator itr + = flushableObservers[ oldList ].begin(); + + while( itr != flushableObservers[ oldList ].end() ) + { + currentHandle = *itr; + ++itr; + + if( currentHandle->observer() != NULL ) + currentHandle->observer()->update( currentHandle->owner() ); + + // Update might have removed it + if( currentHandle != NULL ) + currentHandle->removeFromFlushableList( oldList ); + + currentHandle = NULL; + flushed = true; + } + triggerFlushObservers(); + + }while( flushed ); + + triggerFlushObservers(); + } + + void CCDBBranchObservingHandler::reset() + { + currentList = 0; + currentHandle = NULL; + + for( uint i = 0; i < MAX_OBS_LST; i++ ) + flushableObservers[ i ].clear(); + } + + void CCDBBranchObservingHandler::addBranchObserver( CCDBNodeBranch *branch, ICDBNode::IPropertyObserver *observer, const std::vector< std::string >& positiveLeafNameFilter ) + { + if( branch == NULL ) + return; + + CCDBDBBranchObserverHandle *handle = new CCDBDBBranchObserverHandle( observer, branch, this ); + branch->addBranchObserver( handle, positiveLeafNameFilter ); + } + + void CCDBBranchObservingHandler::addBranchObserver( CCDBNodeBranch *branch, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer, const char **positiveLeafNameFilter, uint positiveLeafNameFilterSize ) + { + if( branch == NULL ) + return; + + CCDBDBBranchObserverHandle *handle = new CCDBDBBranchObserverHandle( &observer, branch, this ); + branch->addBranchObserver( handle, dbPathFromThisNode, positiveLeafNameFilter, positiveLeafNameFilterSize ); + } + + void CCDBBranchObservingHandler::removeBranchObserver( CCDBNodeBranch *branch, ICDBNode::IPropertyObserver* observer ) + { + branch->removeBranchObserver( observer ); + } + + void CCDBBranchObservingHandler::removeBranchObserver( CCDBNodeBranch *branch, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer ) + { + branch->removeBranchObserver( dbPathFromThisNode, observer ); + } + + void CCDBBranchObservingHandler::triggerFlushObservers() + { + for( std::vector< IBranchObserverCallFlushObserver* >::iterator itr = flushObservers.begin(); + itr != flushObservers.end(); itr++ ) + (*itr)->onObserverCallFlush(); + } + + void CCDBBranchObservingHandler::addFlushObserver( IBranchObserverCallFlushObserver *observer ) + { + std::vector< IBranchObserverCallFlushObserver* >::iterator itr + = std::find( flushObservers.begin(), flushObservers.end(), observer ); + + if( itr != flushObservers.end() ) + return; + + flushObservers.push_back( observer ); + } + + void CCDBBranchObservingHandler::removeFlushObserver( IBranchObserverCallFlushObserver *observer ) + { + std::vector< IBranchObserverCallFlushObserver* >::iterator itr + = std::find( flushObservers.begin(), flushObservers.end(), observer ); + + if( itr == flushObservers.end() ) + return; + + flushObservers.erase( itr ); + } + + CCDBBranchObservingHandler::CCDBDBBranchObserverHandle::CCDBDBBranchObserverHandle( NLMISC::ICDBNode::IPropertyObserver *observer, NLMISC::CCDBNodeBranch *owner, CCDBBranchObservingHandler *handler ) + { + std::fill( _inList, _inList + MAX_OBS_LST, false ); + _observer = observer; + _owner = owner; + _handler = handler; + } + + CCDBBranchObservingHandler::CCDBDBBranchObserverHandle::~CCDBDBBranchObserverHandle() + { + _observer = NULL; + } + + bool CCDBBranchObservingHandler::CCDBDBBranchObserverHandle::observesLeaf( const std::string &leafName ) + { + if( !_observedLeaves.empty() ){ + std::vector< std::string >::iterator itr + = std::find( _observedLeaves.begin(), _observedLeaves.end(), leafName ); + + if( itr == _observedLeaves.end() ) + return false; + else + return true; + } + + return true; + } + + bool CCDBBranchObservingHandler::CCDBDBBranchObserverHandle::inList( uint list ) + { + return _inList[ list ]; + } + + void CCDBBranchObservingHandler::CCDBDBBranchObserverHandle::addToFlushableList() + { + uint list = _handler->currentList; + + if( _inList[ list ] ) + return; + + _handler->flushableObservers[ list ].push_back( this ); + _inList[ list ] = true; + } + + void CCDBBranchObservingHandler::CCDBDBBranchObserverHandle::removeFromFlushableList( uint list ) + { + if( !_inList[ list ] ) + return; + + std::list< CCDBNodeBranch::ICDBDBBranchObserverHandle* >::iterator itr + = std::find( _handler->flushableObservers[ list ].begin(), + _handler->flushableObservers[ list ].end(), this ); + + if( itr == _handler->flushableObservers[ list ].end() ) + return; + + if( _handler->currentHandle == this ) + _handler->currentHandle = NULL; + + _handler->flushableObservers[ list ].erase( itr ); + _inList[ list ] = false; + + } + + void CCDBBranchObservingHandler::CCDBDBBranchObserverHandle::removeFromFlushableList() + { + for( uint i = 0; i < MAX_OBS_LST; i++ ) + removeFromFlushableList( i ); + } +} + diff --git a/code/ryzom/client/src/cdb_check_sum.cpp b/code/nel/src/misc/cdb_check_sum.cpp similarity index 91% rename from code/ryzom/client/src/cdb_check_sum.cpp rename to code/nel/src/misc/cdb_check_sum.cpp index 44782fea3..4f5f32030 100644 --- a/code/ryzom/client/src/cdb_check_sum.cpp +++ b/code/nel/src/misc/cdb_check_sum.cpp @@ -16,10 +16,11 @@ -#include "stdpch.h" -#include "cdb_check_sum.h" +#include "nel/misc/cdb_check_sum.h" +namespace NLMISC{ + /* * Constructor */ @@ -40,3 +41,6 @@ void CCDBCheckSum::add(uint8 el) _Factor = (cipher + _Factor) * _Const1 + _Const2; _Sum += cipher; } + +} + diff --git a/code/ryzom/client/src/cdb_leaf.cpp b/code/nel/src/misc/cdb_leaf.cpp similarity index 90% rename from code/ryzom/client/src/cdb_leaf.cpp rename to code/nel/src/misc/cdb_leaf.cpp index b65f79c24..fce66bd4a 100644 --- a/code/ryzom/client/src/cdb_leaf.cpp +++ b/code/nel/src/misc/cdb_leaf.cpp @@ -16,9 +16,6 @@ -#include "stdpch.h" - - //#define TRACE_READ_DELTA //#define TRACE_WRITE_DELTA //#define TRACE_SET_VALUE @@ -28,22 +25,23 @@ ////////////// // Includes // ////////////// -#include "cdb_leaf.h" +#include "nel/misc/cdb_leaf.h" #include "nel/misc/xml_auto_ptr.h" +#include "nel/misc/bit_mem_stream.h" //#include -#include "interface_v3/interface_manager.h" //////////////// // Namespaces // //////////////// -using namespace NLMISC; using namespace std; +namespace NLMISC{ + //----------------------------------------------- // init //----------------------------------------------- -void CCDBNodeLeaf::init( xmlNodePtr node, NLMISC::IProgressCallback &/* progressCallBack */, bool /* mapBanks */ ) +void CCDBNodeLeaf::init( xmlNodePtr node, IProgressCallback &/* progressCallBack */, bool /* mapBanks */, CCDBBankHandler * /* bankHandler */ ) { CXMLAutoPtr type((const char*)xmlGetProp (node, (xmlChar*)"type")); nlassert((const char *) type != NULL); @@ -128,7 +126,7 @@ void CCDBNodeLeaf::write( CTextId& id, FILE * f) //----------------------------------------------- // readDelta //----------------------------------------------- -void CCDBNodeLeaf::readDelta(NLMISC::TGameCycle gc, CBitMemStream & f ) +void CCDBNodeLeaf::readDelta(TGameCycle gc, CBitMemStream & f ) { // If the property Type is valid. if(_Type > UNKNOWN && _Type < Nb_Prop_Type) @@ -184,7 +182,7 @@ void CCDBNodeLeaf::readDelta(NLMISC::TGameCycle gc, CBitMemStream & f ) //----------------------------------------------- // resetData //----------------------------------------------- -void CCDBNodeLeaf::resetData(NLMISC::TGameCycle gc, bool forceReset) +void CCDBNodeLeaf::resetData(TGameCycle gc, bool forceReset) { if(forceReset) { @@ -251,7 +249,7 @@ bool CCDBNodeLeaf::setProp( CTextId& id, sint64 value ) //----------------------------------------------- // setPropCheckGC //----------------------------------------------- -bool CCDBNodeLeaf::setPropCheckGC(NLMISC::TGameCycle gc, sint64 value) +bool CCDBNodeLeaf::setPropCheckGC(TGameCycle gc, sint64 value) { // Apply only if happens after the DB change if(gc>=_LastChangeGC) @@ -322,7 +320,7 @@ void CCDBNodeLeaf::setValueBool(bool prop) setValue64(newVal); } -void CCDBNodeLeaf::setValueRGBA (const NLMISC::CRGBA &color) +void CCDBNodeLeaf::setValueRGBA (const CRGBA &color) { sint64 newVal = (uint32)(color.R+(color.G<<8)+(color.B<<16)+(color.A<<24)); setValue64(newVal); @@ -367,9 +365,7 @@ void CCDBNodeLeaf::notifyObservers() } // mark parent branchs if (_Parent) - { - _Parent->linkInModifiedNodeList(_Name); - } + _Parent->onLeafChanged( _Name ); } @@ -390,3 +386,5 @@ void CCDBNodeLeaf::notifyObservers() #endif //############################################################################################# +} + diff --git a/code/nel/src/misc/cdb_manager.cpp b/code/nel/src/misc/cdb_manager.cpp new file mode 100644 index 000000000..a13527217 --- /dev/null +++ b/code/nel/src/misc/cdb_manager.cpp @@ -0,0 +1,149 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "nel/misc/cdb_manager.h" + +namespace NLMISC{ + + CCDBManager::CCDBManager( const char *rootNodeName, uint maxBanks ) : bankHandler( maxBanks ) + { + _Database = new CCDBNodeBranch( std::string( rootNodeName ) ); + } + + CCDBManager::~CCDBManager() + { + if( _Database != NULL ) + { + _Database->clear(); + delete _Database; + _Database = NULL; + } + } + + CCDBNodeLeaf* CCDBManager::getDbLeaf( const std::string &name, bool create ) + { + if( name.empty() ) + return NULL; + + CCDBNodeLeaf *leaf = NULL; + leaf = dynamic_cast< CCDBNodeLeaf* >( _Database->getNode( ICDBNode::CTextId( name ), create ) ); + return leaf; + } + + CCDBNodeBranch* CCDBManager::getDbBranch( const std::string &name ) + { + if( name.empty() ) + return NULL; + + CCDBNodeBranch *branch = NULL; + branch = dynamic_cast< CCDBNodeBranch* >( _Database->getNode( ICDBNode::CTextId( name ), false ) ); + return branch; + } + + + void CCDBManager::delDbNode( const stlpx_std::string &name ) + { + if( name.empty() ) + return; + + _Database->removeNode( ICDBNode::CTextId( name ) ); + } + + void CCDBManager::addBranchObserver( const char *branchName, ICDBNode::IPropertyObserver *observer, const std::vector< std::string >& positiveLeafNameFilter ) + { + CCDBNodeBranch *b = dynamic_cast< CCDBNodeBranch* >( _Database->getNode( ICDBNode::CTextId( std::string( branchName ) ), false ) ); + if( b == NULL ) + return; + branchObservingHandler.addBranchObserver( b, observer, positiveLeafNameFilter ); + } + + void CCDBManager::addBranchObserver( CCDBNodeBranch *branch, ICDBNode::IPropertyObserver *observer, const std::vector< std::string >& positiveLeafNameFilter ) + { + if( branch == NULL ) + return; + branchObservingHandler.addBranchObserver( branch, observer, positiveLeafNameFilter ); + } + + void CCDBManager::addBranchObserver( const char *branchName, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer, const char **positiveLeafNameFilter, uint positiveLeafNameFilterSize ) + { + CCDBNodeBranch *b = dynamic_cast< CCDBNodeBranch* >( _Database->getNode( ICDBNode::CTextId( std::string( branchName ) ), false ) ); + if( b == NULL ) + return; + branchObservingHandler.addBranchObserver( b, dbPathFromThisNode, observer, positiveLeafNameFilter, positiveLeafNameFilterSize ); + } + + void CCDBManager::addBranchObserver( CCDBNodeBranch *branch, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer, const char **positiveLeafNameFilter, uint positiveLeafNameFilterSize ) + { + if( branch == NULL ) + return; + branchObservingHandler.addBranchObserver( branch, dbPathFromThisNode, observer, positiveLeafNameFilter, positiveLeafNameFilterSize ); + } + + void CCDBManager::removeBranchObserver( const char *branchName, ICDBNode::IPropertyObserver* observer ) + { + CCDBNodeBranch *b = dynamic_cast< CCDBNodeBranch* >( _Database->getNode( ICDBNode::CTextId( std::string( branchName ) ), false ) ); + if( b == NULL ) + return; + branchObservingHandler.removeBranchObserver( b, observer ); + } + + void CCDBManager::removeBranchObserver( CCDBNodeBranch *branch, ICDBNode::IPropertyObserver* observer ) + { + if( branch == NULL ) + return; + branchObservingHandler.removeBranchObserver( branch, observer ); + } + + void CCDBManager::removeBranchObserver( const char *branchName, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer ) + { + CCDBNodeBranch *b = dynamic_cast< CCDBNodeBranch* >( _Database->getNode( ICDBNode::CTextId( std::string( branchName ) ), false ) ); + if( b == NULL ) + return; + branchObservingHandler.removeBranchObserver( b, dbPathFromThisNode, observer ); + } + + void CCDBManager::removeBranchObserver( CCDBNodeBranch *branch, const char *dbPathFromThisNode, ICDBNode::IPropertyObserver &observer ) + { + if( branch == NULL ) + return; + branchObservingHandler.removeBranchObserver( branch, dbPathFromThisNode, observer ); + } + + void CCDBManager::addFlushObserver( CCDBBranchObservingHandler::IBranchObserverCallFlushObserver *observer ) + { + if( observer == NULL ) + return; + branchObservingHandler.addFlushObserver( observer ); + } + + void CCDBManager::removeFlushObserver( CCDBBranchObservingHandler::IBranchObserverCallFlushObserver *observer ) + { + if( observer == NULL ) + return; + branchObservingHandler.removeFlushObserver( observer ); + } + + void CCDBManager::flushObserverCalls() + { + branchObservingHandler.flushObserverCalls(); + } + + void CCDBManager::resetBank( uint gc, uint bank ) + { + _Database->resetNode( gc, bankHandler.getUIDForBank( bank ) ); + } + +} diff --git a/code/nel/src/misc/co_task.cpp b/code/nel/src/misc/co_task.cpp index 1880be06c..c66941a40 100644 --- a/code/nel/src/misc/co_task.cpp +++ b/code/nel/src/misc/co_task.cpp @@ -237,12 +237,15 @@ namespace NLMISC _PImpl = new TCoTaskData(this); // _PImpl->_TaskThreadId = 0; // _PImpl->_ParentThreadId = 0; + nlunreferenced(stackSize); #else //NL_USE_THREAD_COTASK // allocate platform specific data storage _PImpl = new TCoTaskData; + nlunreferenced(stackSize); #if defined (NL_OS_WINDOWS) _PImpl->_Fiber = NULL; _PImpl->_ParentFiber = NULL; + nlunreferenced(stackSize); #elif defined(NL_OS_UNIX) // allocate the stack _PImpl->_Stack = new uint8[stackSize]; diff --git a/code/nel/src/misc/command.cpp b/code/nel/src/misc/command.cpp index 80d074386..f2b422d69 100644 --- a/code/nel/src/misc/command.cpp +++ b/code/nel/src/misc/command.cpp @@ -650,6 +650,9 @@ ICommand *CCommandRegistry::getCommand(const std::string &commandName) NLMISC_CATEGORISED_COMMAND(nel,help,"display help on a specific variable/commands or on all variables and commands", "[|]") { + nlunreferenced(rawCommandString); + nlunreferenced(quiet); + nlunreferenced(human); // nlassert (_Commands != NULL); // make sure we have a valid number of parameters diff --git a/code/nel/src/misc/common.cpp b/code/nel/src/misc/common.cpp index fb9a9c6f5..d89708c4e 100644 --- a/code/nel/src/misc/common.cpp +++ b/code/nel/src/misc/common.cpp @@ -30,6 +30,7 @@ #include "nel/misc/command.h" #include "nel/misc/path.h" +#include "nel/misc/i18n.h" using namespace std; @@ -372,6 +373,10 @@ uint32 humanReadableToBytes (const string &str) NLMISC_CATEGORISED_COMMAND(nel,btohr, "Convert a bytes number into an human readable number", "") { + nlunreferenced(rawCommandString); + nlunreferenced(quiet); + nlunreferenced(human); + if (args.size() != 1) return false; @@ -383,6 +388,10 @@ NLMISC_CATEGORISED_COMMAND(nel,btohr, "Convert a bytes number into an human read NLMISC_CATEGORISED_COMMAND(nel,hrtob, "Convert a human readable number into a bytes number", "
") { + nlunreferenced(rawCommandString); + nlunreferenced(quiet); + nlunreferenced(human); + if (args.size() != 1) return false; @@ -445,6 +454,10 @@ uint32 fromHumanReadable (const std::string &str) NLMISC_CATEGORISED_COMMAND(nel,stohr, "Convert a second number into an human readable time", "") { + nlunreferenced(rawCommandString); + nlunreferenced(quiet); + nlunreferenced(human); + if (args.size() != 1) return false; @@ -526,6 +539,31 @@ void toUpper(char *str) } } +std::string formatThousands(const std::string& s) +{ + sint i, k; + sint remaining = (sint)s.length() - 1; + static std::string separator = NLMISC::CI18N::get("uiThousandsSeparator").toUtf8(); + + // Don't add separator if the number is < 10k + if (remaining < 4) return s; + + std::string ns; + + do + { + for (i = remaining, k = 0; i >= 0 && k < 3; --i, ++k ) + { + ns = s[i] + ns; // New char is added to front of ns + if ( i > 0 && k == 2) ns = separator + ns; // j > 0 means still more digits + } + + remaining -= 3; + } + while (remaining >= 0); + + return ns; +} // // Exceptions @@ -803,6 +841,7 @@ int nlfseek64( FILE *stream, sint64 offset, int origin ) return fsetpos (stream, &pos64); #else // NL_OS_WINDOWS + // TODO: to fix for Linux and Mac OS X // This code doesn't work under windows : fseek() implementation uses a signed 32 bits offset. What ever we do, it can't seek more than 2 Go. // For the moment, i don't know if it works under linux for seek of more than 2 Go. @@ -845,6 +884,9 @@ sint64 nlftell64(FILE *stream) } else return -1; #else + nlunreferenced(stream); + + // TODO: implement for Linux and Mac OS X nlerror("Not implemented"); return -1; #endif @@ -859,9 +901,14 @@ sint64 nlftell64(FILE *stream) NLMISC_CATEGORISED_COMMAND(nel, sleep, "Freeze the service for N seconds (for debug purpose)", "") { + nlunreferenced(rawCommandString); + nlunreferenced(quiet); + nlunreferenced(human); + if(args.size() != 1) return false; - sint32 n = atoi (args[0].c_str()); + sint32 n; + fromString(args[0], n); log.displayNL ("Sleeping during %d seconds", n); @@ -871,6 +918,10 @@ NLMISC_CATEGORISED_COMMAND(nel, sleep, "Freeze the service for N seconds (for de NLMISC_CATEGORISED_COMMAND(nel, system, "Execute the command line using system() function call (wait until the end of the command)", "") { + nlunreferenced(rawCommandString); + nlunreferenced(quiet); + nlunreferenced(human); + if(args.size() != 1) return false; string cmd = args[0]; @@ -889,6 +940,10 @@ NLMISC_CATEGORISED_COMMAND(nel, system, "Execute the command line using system() NLMISC_CATEGORISED_COMMAND(nel, launchProgram, "Execute the command line using launcProgram() function call (launch in background task without waiting the end of the execution)", " ") { + nlunreferenced(rawCommandString); + nlunreferenced(quiet); + nlunreferenced(human); + if(args.size() != 2) return false; string cmd = args[0]; @@ -901,6 +956,10 @@ NLMISC_CATEGORISED_COMMAND(nel, launchProgram, "Execute the command line using l NLMISC_CATEGORISED_COMMAND(nel, killProgram, "kill a program given the pid", "") { + nlunreferenced(rawCommandString); + nlunreferenced(quiet); + nlunreferenced(human); + if(args.size() != 1) return false; uint32 pid; fromString(args[0], pid); @@ -1004,6 +1063,9 @@ bool openDoc (const char *document) } else return true; +#else + // TODO: implement for Linux and Mac OS X + nlunreferenced(document); #endif // NL_OS_WINDOWS return false; } diff --git a/code/nel/src/misc/config_file/Makefile.am b/code/nel/src/misc/config_file/Makefile.am deleted file mode 100644 index ee72fe42c..000000000 --- a/code/nel/src/misc/config_file/Makefile.am +++ /dev/null @@ -1,21 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -noinst_LTLIBRARIES = libconfig.la - -AM_YFLAGS = --defines=cf_gramatical.h -p cf - -AM_LFLAGS = -f -8 -Pcf -olex.yy.c - -EXTRA_DIST = cf_gramatical.h cf_bison.simple cf_flex.skl do.bat - -libconfig_la_SOURCES = cf_lexical.lpp \ - cf_gramatical.ypp \ - config_file.cpp - -#libconfig_la_SOURCES = config_file.cpp - -# End of Makefile.am - diff --git a/code/nel/src/misc/eid_translator.cpp b/code/nel/src/misc/eid_translator.cpp index 36ea84247..e254831c4 100644 --- a/code/nel/src/misc/eid_translator.cpp +++ b/code/nel/src/misc/eid_translator.cpp @@ -417,6 +417,17 @@ void CEntityIdTranslator::checkEntity (const CEntityId &eid, const ucstring &ent } } +void CEntityIdTranslator::removeShardFromName(ucstring& name) +{ + // The string must contain a '(' and a ')' + ucstring::size_type p0= name.find('('); + ucstring::size_type p1= name.find(')'); + if (p0 == ucstring::npos || p1 == ucstring::npos || p1 <= p0) + return; + + name = name.substr(0, p0) + name.substr(p1 + 1); +} + // this callback is call when the file is changed void cbInvalidEntityNamesFilename(const std::string &invalidEntityNamesFilename) { diff --git a/code/nel/src/misc/events.cpp b/code/nel/src/misc/events.cpp index 4d3979416..7cef02f44 100644 --- a/code/nel/src/misc/events.cpp +++ b/code/nel/src/misc/events.cpp @@ -178,7 +178,7 @@ static const CStringConversion::CPair stringTable [] = { "KeyZOOM", KeyZOOM }, { "KeyNONAME", KeyNONAME }, { "KeyPA1", KeyPA1 }, - { "KeyOEM_CLEAR", KeyOEM_CLEAR }, + { "KeyOEM_CLEAR", KeyOEM_CLEAR } }; diff --git a/code/nel/src/misc/fast_id_map.cpp b/code/nel/src/misc/fast_id_map.cpp new file mode 100644 index 000000000..f32d6edd8 --- /dev/null +++ b/code/nel/src/misc/fast_id_map.cpp @@ -0,0 +1,44 @@ +/** + * \file fast_id_map.cpp + * \brief CFastIdMap + * \date 2012-04-10 19:28GMT + * \author Jan Boon (Kaetemi) + * CFastIdMap + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#include +#include + +// STL includes + +// NeL includes +// #include + +// Project includes + +namespace NLMISC { + +void dummytoavoidthecompilerwarningfastidmap() { } + +} /* namespace NLMISC */ + +/* end of file */ diff --git a/code/nel/src/misc/p_thread.cpp b/code/nel/src/misc/p_thread.cpp index d4f1582b4..f752f4bdf 100644 --- a/code/nel/src/misc/p_thread.cpp +++ b/code/nel/src/misc/p_thread.cpp @@ -84,6 +84,17 @@ static void *ProxyFunc( void *arg ) // Run the code of the thread parent->Runnable->run(); + { + pthread_t thread_self = pthread_self(); + // Make sure the parent still cares + // If this thread was replaced with a new thread (which should not happen), + // and the IThread object has been deleted, this will likely crash. + if (parent->_ThreadHandle == thread_self) + parent->_State = CPThread::ThreadStateFinished; + else + throw EThread("Thread ended after being detached, this should not happen"); + } + // Allow some clean // pthread_exit(0); return NULL; @@ -96,7 +107,7 @@ static void *ProxyFunc( void *arg ) */ CPThread::CPThread(IRunnable *runnable, uint32 stackSize) : Runnable(runnable), - _State(0), + _State(ThreadStateNone), _StackSize(stackSize) {} @@ -106,10 +117,9 @@ CPThread::CPThread(IRunnable *runnable, uint32 stackSize) */ CPThread::~CPThread() { - if(_State == 1) - terminate(); // force the end of the thread if not already ended + terminate(); // force the end of the thread if not already ended - if(_State > 0) + if (_State != ThreadStateNone) pthread_detach(_ThreadHandle); // free allocated resources only if it was created } @@ -119,26 +129,51 @@ CPThread::~CPThread() void CPThread::start() { pthread_attr_t tattr; - pthread_t tid; int ret; - /* initialized with default attributes */ - ret = pthread_attr_init(&tattr); + if (_StackSize != 0) + { + /* initialized with default attributes */ + ret = pthread_attr_init(&tattr); - /* setting the size of the stack also */ - ret = pthread_attr_setstacksize(&tattr, _StackSize); + /* setting the size of the stack also */ + ret = pthread_attr_setstacksize(&tattr, _StackSize); + } + + bool detach_old_thread = false; + pthread_t old_thread_handle; + if (_State != ThreadStateNone) + { + if (_State == ThreadStateRunning) + { + // I don't know if this behaviour is allowed, but neither thread implementations + // check the start function, and both simply let the existing running thread for what it is... + // From now on, this is not allowed. + throw EThread("Starting a thread that is already started, existing thread will continue running, this should not happen"); + } + detach_old_thread = true; + old_thread_handle = _ThreadHandle; + } - if(pthread_create(&_ThreadHandle, _StackSize != 0 ? &tattr : 0, ProxyFunc, this) != 0) + if (pthread_create(&_ThreadHandle, _StackSize != 0 ? &tattr : NULL, ProxyFunc, this) != 0) { throw EThread("Cannot start new thread"); } - _State = 1; + _State = ThreadStateRunning; + + if (detach_old_thread) + { + // Docs don't say anything about what happens when pthread_create is called with existing handle referenced. + if (old_thread_handle == _ThreadHandle) + throw EThread("Thread handle did not change, this should not happen"); + // Don't care about old thread, free resources when it terminates. + pthread_detach(old_thread_handle); + } } bool CPThread::isRunning() { - // TODO : need a real implementation here that check thread status - return _State == 1; + return _State == ThreadStateRunning; } /* @@ -146,11 +181,11 @@ bool CPThread::isRunning() */ void CPThread::terminate() { - if(_State == 1) + if (_State == ThreadStateRunning) { // cancel only if started pthread_cancel(_ThreadHandle); - _State = 2; // set to finished + _State = ThreadStateFinished; // set to finished } } @@ -159,13 +194,24 @@ void CPThread::terminate() */ void CPThread::wait () { - if(_State == 1) + if (_State == ThreadStateRunning) { - if(pthread_join(_ThreadHandle, 0) != 0) + int error = pthread_join(_ThreadHandle, 0); + switch (error) { - throw EThread( "Cannot join with thread" ); + case 0: + break; + case EINVAL: + throw EThread("Thread is not joinable"); + case ESRCH: + throw EThread("No thread found with this id"); + case EDEADLK: + throw EThread("Deadlock detected or calling thread waits for itself"); + default: + throw EThread("Unknown thread join error"); } - _State = 2; // set to finished + if(_State != ThreadStateFinished) + throw EThread("Thread did not finish, this should not happen"); } } @@ -207,6 +253,34 @@ uint64 CPThread::getCPUMask() return cpuMask; } +void CPThread::setPriority(TThreadPriority priority) +{ + // TODO: Test this + sched_param sp; + switch (priority) + { + case ThreadPriorityHigh: + { + int minPrio = sched_get_priority_min(SCHED_FIFO); + int maxPrio = sched_get_priority_max(SCHED_FIFO); + sp.sched_priority = ((maxPrio - minPrio) / 4) + minPrio; + pthread_setschedparam(_ThreadHandle, SCHED_FIFO, &sp); + break; + } + case ThreadPriorityHighest: + { + int minPrio = sched_get_priority_min(SCHED_FIFO); + int maxPrio = sched_get_priority_max(SCHED_FIFO); + sp.sched_priority = ((maxPrio - minPrio) / 2) + minPrio; + pthread_setschedparam(_ThreadHandle, SCHED_FIFO, &sp); + break; + } + default: + sp.sched_priority = 0; + pthread_setschedparam(_ThreadHandle, SCHED_OTHER, &sp); + } +} + /* * getUserName */ diff --git a/code/nel/src/misc/rgba.cpp b/code/nel/src/misc/rgba.cpp index 844a100cf..598d8eb53 100644 --- a/code/nel/src/misc/rgba.cpp +++ b/code/nel/src/misc/rgba.cpp @@ -643,6 +643,8 @@ bool CRGBA::convertToHLS(float &h, float &l, float &s) const { h = 2.f + (b - r) / diff; } +#if defined(GCC_VERSION) && (GCC_VERSION == 40204) + // use the fix only if using the specific GCC version else if (maxV == b) { h = 4.f + (r - g) / diff; @@ -652,6 +654,12 @@ bool CRGBA::convertToHLS(float &h, float &l, float &s) const // this case is to fix a compiler bug h = (g - b) / diff; } +#else + else + { + h = 4.f + (r - g) / diff; + } +#endif h *= 60.f; // scale to [0..360] diff --git a/code/nel/src/misc/sheet_id.cpp b/code/nel/src/misc/sheet_id.cpp index 0eaad3a7a..617c994b7 100644 --- a/code/nel/src/misc/sheet_id.cpp +++ b/code/nel/src/misc/sheet_id.cpp @@ -592,7 +592,7 @@ uint32 CSheetId::typeFromFileExtension(const std::string &fileExtension) { if (!_Initialised) init(false); - unsigned i; + uint i; for (i=0;i<_FileExtensions.size();i++) if (toLower(fileExtension)==_FileExtensions[i]) return i; diff --git a/code/nel/src/misc/sstring.cpp b/code/nel/src/misc/sstring.cpp index 4c37b1f1a..ef1584361 100644 --- a/code/nel/src/misc/sstring.cpp +++ b/code/nel/src/misc/sstring.cpp @@ -37,14 +37,14 @@ namespace NLMISC return token; } - unsigned int i; + uint i; CSString result; // skip leading junk for (i=0;ipostEvent (new CEventKeyDown ((NLMISC::TKey)wParam, getKeyButton(_AltButton, _ShiftButton, _CtrlButton), true, this)); + } + // Post the message if (wParam < KeyCount) server->postEvent (new CEventKeyUp ((NLMISC::TKey)wParam, getKeyButton(_AltButton, _ShiftButton, _CtrlButton), this)); diff --git a/code/nel/src/misc/win_thread.cpp b/code/nel/src/misc/win_thread.cpp index 4192e927b..c9bfc90a1 100644 --- a/code/nel/src/misc/win_thread.cpp +++ b/code/nel/src/misc/win_thread.cpp @@ -73,6 +73,20 @@ CWinThread::CWinThread (IRunnable *runnable, uint32 stackSize) _MainThread = false; } +namespace { +class CWinCriticalSection +{ +private: + CRITICAL_SECTION cs; +public: + CWinCriticalSection() { InitializeCriticalSection(&cs); } + ~CWinCriticalSection() { DeleteCriticalSection(&cs); } + inline void enter() { EnterCriticalSection(&cs); } + inline void leave() { LeaveCriticalSection(&cs); } +}; +CWinCriticalSection s_CS; +}/* anonymous namespace */ + CWinThread::CWinThread (void* threadHandle, uint32 threadId) { // Main thread @@ -99,14 +113,11 @@ CWinThread::CWinThread (void* threadHandle, uint32 threadId) nlassert(0); // WARNING: following code has not tested! don't know if it work fo real ... // This is just a suggestion of a possible solution, should this situation one day occur ... // Ensure that this thread don't get deleted, or we could suspend the main thread - CRITICAL_SECTION cs; - InitializeCriticalSection(&cs); - EnterCriticalSection(&cs); + s_CS.enter(); // the 2 following statement must be executed atomicaly among the threads of the current process ! SuspendThread(threadHandle); _SuspendCount = ResumeThread(threadHandle); - LeaveCriticalSection(&cs); - DeleteCriticalSection(&cs); + s_CS.leave(); } } @@ -148,10 +159,10 @@ void CWinThread::resume() } } -void CWinThread::setPriority(int priority) +void CWinThread::setPriority(TThreadPriority priority) { nlassert(ThreadHandle); // 'start' was not called !! - BOOL result = SetThreadPriority(ThreadHandle, priority); + BOOL result = SetThreadPriority(ThreadHandle, (int)priority); nlassert(result); } @@ -179,6 +190,9 @@ CWinThread::~CWinThread () void CWinThread::start () { + if (isRunning()) + throw EThread("Starting a thread that is already started, existing thread will continue running, this should not happen"); + // ThreadHandle = (void *) ::CreateThread (NULL, _StackSize, ProxyFunc, this, 0, (DWORD *)&ThreadId); ThreadHandle = (void *) ::CreateThread (NULL, 0, ProxyFunc, this, 0, (DWORD *)&ThreadId); // nldebug("NLMISC: thread %x started for runnable '%x'", typeid( Runnable ).name()); diff --git a/code/nel/src/net/Makefile.am b/code/nel/src/net/Makefile.am deleted file mode 100644 index ef0d956be..000000000 --- a/code/nel/src/net/Makefile.am +++ /dev/null @@ -1,60 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -lib_LTLIBRARIES = libnelnet.la - -libnelnet_la_SOURCES = buf_client.cpp \ - buf_net_base.cpp \ - buf_server.cpp \ - buf_sock.cpp \ - callback_client.cpp \ - callback_net_base.cpp \ - callback_server.cpp \ - dummy_tcp_sock.cpp \ - inet_address.cpp \ - listen_sock.cpp \ - login_client.cpp \ - login_cookie.cpp \ - login_server.cpp \ - message.cpp \ - message_recorder.cpp \ - naming_client.cpp \ - net_displayer.cpp \ - net_log.cpp \ - service.cpp \ - sock.cpp \ - tcp_sock.cpp \ - udp_sock.cpp \ - udp_sim_sock.cpp \ - unitime.cpp \ - unified_network.cpp \ - varpath.cpp \ - transport_class.cpp \ - email.cpp \ - admin.cpp \ - stdin_monitor_thread.cpp \ - stdin_monitor_thread.h \ - module.cpp \ - module_common.cpp \ - module_gateway.cpp \ - module_manager.cpp \ - module_message.cpp \ - module_socket.cpp \ - module_gateway_transport.cpp \ - module_l5_transport.cpp \ - module_local_gateway.cpp \ - stdnet.cpp - -noinst_HEADERS = stdnet.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -libnelnet_la_LIBADD = -lc -lpthread - -libnelnet_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - - -# End of Makefile.am - diff --git a/code/nel/src/net/transport_class.cpp b/code/nel/src/net/transport_class.cpp index a5f826728..dca866916 100644 --- a/code/nel/src/net/transport_class.cpp +++ b/code/nel/src/net/transport_class.cpp @@ -77,7 +77,7 @@ string typeToString (CTransportClass::TProp type) string conv[] = { "PropUInt8", "PropUInt16", "PropUInt32", "PropUInt64", "PropSInt8", "PropSInt16", "PropSInt32", "PropSInt64", - "PropBool", "PropFloat", "PropDouble", "PropString", "PropDataSetRow", "PropSheetId", "PropUKN" }; + "PropBool", "PropFloat", "PropDouble", "PropString", "PropDataSetRow", "PropSheetId", "PropUCString", "PropUKN" }; // "PropBool", "PropFloat", "PropDouble", "PropString", "PropDataSetRow", "PropEntityId", "PropSheetId", "PropUKN" }; if (type > CTransportClass::PropUKN) @@ -352,6 +352,7 @@ void CTransportClass::init () // nlassert (PropDataSetRow < PropUKN); DummyProp[PropDataSetRow] = new CTransportClass::CRegisteredProp; // nlassert (PropEntityId < PropUKN); DummyProp[PropEntityId] = new CTransportClass::CRegisteredProp; nlassert (PropSheetId < PropUKN); DummyProp[PropSheetId] = new CTransportClass::CRegisteredProp; + nlassert (PropUCString < PropUKN); DummyProp[PropUCString] = new CTransportClass::CRegisteredProp; // we have to know when a service comes, so add callback (put the callback before all other one because we have to send this message first) CUnifiedNetwork::getInstance()->setServiceUpCallback("*", cbTCUpService, NULL, false); diff --git a/code/nel/src/pacs/Makefile.am b/code/nel/src/pacs/Makefile.am deleted file mode 100644 index 0b05a2ed6..000000000 --- a/code/nel/src/pacs/Makefile.am +++ /dev/null @@ -1,69 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -lib_LTLIBRARIES = libnelpacs.la - -libnelpacs_la_SOURCES = chain.cpp \ - chain.h \ - chain_quad.cpp \ - chain_quad.h \ - collision_callback.cpp \ - collision_callback.h \ - collision_desc.cpp \ - collision_desc.h \ - collision_mesh_build.h \ - collision_ot.cpp \ - collision_ot.h \ - collision_surface_temp.cpp \ - collision_surface_temp.h \ - edge_collide.cpp \ - edge_collide.h \ - edge_quad.cpp \ - edge_quad.h \ - exterior_mesh.cpp \ - exterior_mesh.h \ - global_retriever.cpp \ - global_retriever.h \ - local_retriever.cpp \ - local_retriever.h \ - move_cell.cpp \ - move_cell.h \ - move_container.cpp \ - move_container.h \ - move_container_inline.h \ - move_element.cpp \ - move_element.h \ - move_element_inline.h \ - move_primitive.cpp \ - move_primitive.h \ - primitive_world_image.cpp \ - primitive_world_image.h \ - primitive_block_pacs.cpp \ - primitive_block.h \ - retrievable_surface.cpp \ - retrievable_surface.h \ - retriever_bank.cpp \ - retriever_bank.h \ - retriever_instance.cpp \ - retriever_instance.h \ - stdpacs.cpp \ - surface_quad.cpp \ - surface_quad.h \ - vector_2s.cpp \ - vector_2s.h \ - build_indoor.cpp \ - build_indoor.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -noinst_HEADERS = stdpacs.h face_grid.h quad_grid.h - -libnelpacs_la_LIBADD = -lc - -libnelpacs_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - - -# End of Makefile.am - diff --git a/code/nel/src/sound/CMakeLists.txt b/code/nel/src/sound/CMakeLists.txt index 21c1de1f8..e73d0828e 100644 --- a/code/nel/src/sound/CMakeLists.txt +++ b/code/nel/src/sound/CMakeLists.txt @@ -1,8 +1,101 @@ + FILE(GLOB SRC *.cpp *.h) FILE(GLOB HEADERS ../../include/nel/sound/*.h) + +FILE(GLOB ANIMATION + sound_anim_manager.cpp ../../include/nel/sound/sound_anim_manager.h + sound_anim_marker.cpp ../../include/nel/sound/sound_anim_marker.h + sound_animation.cpp ../../include/nel/sound/sound_animation.h +) + +FILE(GLOB BACKGROUND_SOUND + background_sound.cpp ../../include/nel/sound/background_sound.h + background_sound_manager.cpp ../../include/nel/sound/background_sound_manager.h + background_source.cpp ../../include/nel/sound/background_source.h + clustered_sound.cpp ../../include/nel/sound/clustered_sound.h + context_sound.cpp ../../include/nel/sound/context_sound.h +) + +FILE(GLOB BANKS + async_file_manager_sound.cpp ../../include/nel/sound/async_file_manager_sound.h + sample_bank.cpp ../../include/nel/sound/sample_bank.h + sample_bank_manager.cpp ../../include/nel/sound/sample_bank_manager.h + sound_bank.cpp ../../include/nel/sound/sound_bank.h +) + +FILE(GLOB MIXER + audio_mixer_user.cpp ../../include/nel/sound/audio_mixer_user.h + ../../include/nel/sound/containers.h + group_controller.cpp ../../include/nel/sound/group_controller.h + group_controller_root.cpp ../../include/nel/sound/group_controller_root.h + listener_user.cpp ../../include/nel/sound/listener_user.h + mixing_track.cpp ../../include/nel/sound/mixing_track.h +) + +FILE(GLOB MUSIC + music_channel_fader.cpp ../../include/nel/sound/music_channel_fader.h + music_sound.cpp ../../include/nel/sound/music_sound.h + music_sound_manager.cpp ../../include/nel/sound/music_sound_manager.h + music_source.cpp ../../include/nel/sound/music_source.h + source_music_channel.cpp ../../include/nel/sound/source_music_channel.h +) + +FILE(GLOB SOUND + complex_sound.cpp ../../include/nel/sound/complex_sound.h + complex_source.cpp ../../include/nel/sound/complex_source.h + simple_sound.cpp ../../include/nel/sound/simple_sound.h + simple_source.cpp ../../include/nel/sound/simple_source.h + sound.cpp ../../include/nel/sound/sound.h + ../../include/nel/sound/sound_pattern.h + source_common.cpp ../../include/nel/sound/source_common.h +) + +FILE(GLOB STREAM + stream_sound.cpp ../../include/nel/sound/stream_sound.h + stream_source.cpp ../../include/nel/sound/stream_source.h +) + +FILE(GLOB STREAM_FILE + audio_decoder.cpp ../../include/nel/sound/audio_decoder.h + audio_decoder_vorbis.cpp ../../include/nel/sound/audio_decoder_vorbis.h + stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h + stream_file_source.cpp ../../include/nel/sound/stream_file_source.h +) + +FILE(GLOB USER_CLASSES + ../../include/nel/sound/u_audio_mixer.h + ../../include/nel/sound/u_group_controller.h + ../../include/nel/sound/u_listener.h + ../../include/nel/sound/u_source.h + ../../include/nel/sound/u_stream_source.h +) + +SOURCE_GROUP("" FILES ${SRC} ${HEADERS}) +SOURCE_GROUP("animation" FILES ${ANIMATION}) +SOURCE_GROUP("background_sound" FILES ${BACKGROUND_SOUND}) +SOURCE_GROUP("banks" FILES ${BANKS}) +SOURCE_GROUP("mixer" FILES ${MIXER}) +SOURCE_GROUP("music_deprecated" FILES ${MUSIC}) +SOURCE_GROUP("sound" FILES ${SOUND}) +SOURCE_GROUP("stream" FILES ${STREAM}) +SOURCE_GROUP("stream_file" FILES ${STREAM_FILE}) +SOURCE_GROUP("user_classes" FILES ${USER_CLASSES}) + + NL_TARGET_LIB(nelsound ${HEADERS} ${SRC}) + +INCLUDE_DIRECTORIES(${VORBIS_INCLUDE_DIR}) + +TARGET_LINK_LIBRARIES(nelsound ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY}) + +IF(WITH_STATIC) + # Add libogg dependency only if target is static because to libvorbisfile + TARGET_LINK_LIBRARIES(nelsound ${OGG_LIBRARY}) +ENDIF(WITH_STATIC) + + INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR}) TARGET_LINK_LIBRARIES(nelsound ${LIBXML2_LIBRARIES} nelmisc nelligo nelgeorges nel3d nelsnd_lowlevel) diff --git a/code/nel/src/sound/Makefile.am b/code/nel/src/sound/Makefile.am deleted file mode 100644 index b919e10c4..000000000 --- a/code/nel/src/sound/Makefile.am +++ /dev/null @@ -1,75 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = driver - -lib_LTLIBRARIES = libnelsnd.la - -libnelsnd_la_SOURCES = async_file_manager_sound.cpp \ - async_file_manager_sound.h \ - audio_mixer_user.cpp \ - audio_mixer_user.h \ - background_sound.cpp \ - background_sound.h \ - background_sound_manager.cpp \ - background_sound_manager.h \ - background_source.cpp \ - background_source.h \ - clustered_sound.cpp \ - clustered_sound.h \ - complex_sound.cpp \ - complex_sound.h \ - complex_source.cpp \ - complex_source.h \ - context_sound.cpp \ - context_sound.h \ - listener_user.cpp \ - listener_user.h \ - mixing_track.cpp \ - mixing_track.h \ - music_channel_fader.h \ - music_channel_fader.cpp \ - music_sound.cpp \ - music_sound.h \ - music_sound_manager.cpp \ - music_sound_manager.h \ - music_source.cpp \ - music_source.h \ - sample_bank.cpp \ - sample_bank.h \ - sample_bank_manager.cpp \ - sample_bank_manager.h \ - simple_sound.cpp \ - simple_sound.h \ - simple_source.cpp \ - simple_source.h \ - sound_animation.cpp \ - sound_anim_manager.cpp \ - sound_anim_marker.cpp \ - sound_bank.cpp \ - sound_bank.h \ - sound.cpp \ - sound.h \ - sound_pattern.h \ - source_common.cpp \ - source_common.h \ - stdsound.cpp \ - stdsound.h \ - stream_source.cpp \ - stream_source.h \ - stream_sound.cpp \ - stream_sound.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -noinst_HEADERS = stdsound.h - -libnelsnd_la_LIBADD = driver/libnelsnd_lowlevel.la - -libnelsnd_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - - -# End of Makefile.am - diff --git a/code/nel/src/sound/audio_decoder.cpp b/code/nel/src/sound/audio_decoder.cpp new file mode 100644 index 000000000..eef031417 --- /dev/null +++ b/code/nel/src/sound/audio_decoder.cpp @@ -0,0 +1,139 @@ +/** + * \file audio_decoder.cpp + * \brief IAudioDecoder + * \date 2012-04-11 09:34GMT + * \author Jan Boon (Kaetemi) + * IAudioDecoder + */ + +/* + * Copyright (C) 2008-2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#include "stdsound.h" +#include + +// STL includes + +// NeL includes +#include +#include + +// Project includes +#include + +using namespace std; +using namespace NLMISC; + +namespace NLSOUND { + +IAudioDecoder::IAudioDecoder() : _InternalStream(NULL) +{ + +} + +IAudioDecoder::~IAudioDecoder() +{ + if (_InternalStream) { delete _InternalStream; _InternalStream = NULL; } +} + +IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &filepath, bool async, bool loop) +{ + std::string lookup = CPath::lookup(filepath, false); + if (lookup.empty()) + { + nlwarning("Music file %s does not exist!", filepath.c_str()); + return NULL; + } + std::string type = CFile::getExtension(filepath); + + CIFile *ifile = new CIFile(); + ifile->setCacheFileOnOpen(!async); + ifile->allowBNPCacheFileOnOpen(!async); + ifile->open(lookup); + + IAudioDecoder *mb = createAudioDecoder(type, ifile, loop); + + if (mb) mb->_InternalStream = ifile; + else delete ifile; + + return mb; +} + +IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC::IStream *stream, bool loop) +{ + if (!stream) + { + nlwarning("Stream is NULL"); + return NULL; + } + std::string type_lower = toLower(type); + if (type_lower == "ogg") + { + return new CAudioDecoderVorbis(stream, loop); + } + else + { + nlwarning("Music file type unknown: '%s'", type_lower.c_str()); + return NULL; + } +} + +bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, std::string &title) +{ + std::string lookup = CPath::lookup(filepath, false); + if (lookup.empty()) + { + nlwarning("Music file %s does not exist!", filepath.c_str()); + return false; + } + std::string type = CFile::getExtension(filepath); + std::string type_lower = NLMISC::toLower(type); + + if (type_lower == "ogg") + { + CIFile ifile; + ifile.setCacheFileOnOpen(false); + ifile.allowBNPCacheFileOnOpen(false); + ifile.open(lookup); + return CAudioDecoderVorbis::getInfo(&ifile, artist, title); + } + else + { + nlwarning("Music file type unknown: '%s'", type_lower.c_str()); + artist.clear(); title.clear(); + return false; + } +} + +/// Get audio/container extensions that are currently supported by the nel sound library. +void IAudioDecoder::getMusicExtensions(std::vector &extensions) +{ + extensions.push_back("ogg"); + // extensions.push_back("wav"); // TODO: Easy. +} + +/// Return if a music extension is supported by the nel sound library. +bool IAudioDecoder::isMusicExtensionSupported(const std::string &extension) +{ + return (extension == "ogg"); +} + +} /* namespace NLSOUND */ + +/* end of file */ diff --git a/code/nel/src/sound/driver/music_buffer_vorbis.cpp b/code/nel/src/sound/audio_decoder_vorbis.cpp similarity index 53% rename from code/nel/src/sound/driver/music_buffer_vorbis.cpp rename to code/nel/src/sound/audio_decoder_vorbis.cpp index 9d5543c62..e0b950fc4 100644 --- a/code/nel/src/sound/driver/music_buffer_vorbis.cpp +++ b/code/nel/src/sound/audio_decoder_vorbis.cpp @@ -1,38 +1,53 @@ -// NeL - MMORPG Framework -// Copyright (C) 2010 Winch Gate Property Limited -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . +/** + * \file audio_decoder_vorbis.cpp + * \brief CAudioDecoderVorbis + * \date 2012-04-11 09:35GMT + * \author Jan Boon (Kaetemi) + * CAudioDecoderVorbis + */ -#include "stdsound_lowlevel.h" +/* + * Copyright (C) 2008-2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#include "stdsound.h" +#include + +// STL includes + +// NeL includes +#include // Project includes -#include "nel/sound/driver/music_buffer_vorbis.h" using namespace std; using namespace NLMISC; -namespace NLSOUND -{ +namespace NLSOUND { size_t vorbisReadFunc(void *ptr, size_t size, size_t nmemb, void *datasource) { - CMusicBufferVorbis *music_buffer_vorbis = (CMusicBufferVorbis *)datasource; - NLMISC::IStream *stream = music_buffer_vorbis->getStream(); + CAudioDecoderVorbis *audio_decoder_vorbis = (CAudioDecoderVorbis *)datasource; + NLMISC::IStream *stream = audio_decoder_vorbis->getStream(); nlassert(stream->isReading()); sint32 length = (sint32)(size * nmemb); - if (length > music_buffer_vorbis->getStreamSize() - stream->getPos()) - length = music_buffer_vorbis->getStreamSize() - stream->getPos(); + if (length > audio_decoder_vorbis->getStreamSize() - stream->getPos()) + length = audio_decoder_vorbis->getStreamSize() - stream->getPos(); stream->serialBuffer((uint8 *)ptr, length); return length; } @@ -45,7 +60,7 @@ int vorbisSeekFunc(void *datasource, ogg_int64_t offset, int whence) return 0; // ooookkaaaaaayyy } - CMusicBufferVorbis *music_buffer_vorbis = (CMusicBufferVorbis *)datasource; + CAudioDecoderVorbis *audio_decoder_vorbis = (CAudioDecoderVorbis *)datasource; NLMISC::IStream::TSeekOrigin origin; switch (whence) @@ -64,19 +79,19 @@ int vorbisSeekFunc(void *datasource, ogg_int64_t offset, int whence) return -1; } - if (music_buffer_vorbis->getStream()->seek(SEEK_SET ? music_buffer_vorbis->getStreamOffset() + (sint32)offset : (sint32)offset, origin)) return 0; + if (audio_decoder_vorbis->getStream()->seek(SEEK_SET ? audio_decoder_vorbis->getStreamOffset() + (sint32)offset : (sint32)offset, origin)) return 0; else return -1; } //int vorbisCloseFunc(void *datasource) //{ -// //CMusicBufferVorbis *music_buffer_vorbis = (CMusicBufferVorbis *)datasource; +// //CAudioDecoderVorbis *audio_decoder_vorbis = (CAudioDecoderVorbis *)datasource; //} long vorbisTellFunc(void *datasource) { - CMusicBufferVorbis *music_buffer_vorbis = (CMusicBufferVorbis *)datasource; - return (long)(music_buffer_vorbis->getStream()->getPos() - music_buffer_vorbis->getStreamOffset()); + CAudioDecoderVorbis *audio_decoder_vorbis = (CAudioDecoderVorbis *)datasource; + return (long)(audio_decoder_vorbis->getStream()->getPos() - audio_decoder_vorbis->getStreamOffset()); } static ov_callbacks OV_CALLBACKS_NLMISC_STREAM = { @@ -86,8 +101,8 @@ static ov_callbacks OV_CALLBACKS_NLMISC_STREAM = { (long (*)(void *)) vorbisTellFunc }; -CMusicBufferVorbis::CMusicBufferVorbis(NLMISC::IStream *stream, bool loop) -: _Stream(stream), _Loop(loop), _IsMusicEnded(false) +CAudioDecoderVorbis::CAudioDecoderVorbis(NLMISC::IStream *stream, bool loop) +: _Stream(stream), _Loop(loop), _IsMusicEnded(false), _StreamSize(0) { _StreamOffset = stream->getPos(); stream->seek(0, NLMISC::IStream::end); @@ -96,15 +111,15 @@ CMusicBufferVorbis::CMusicBufferVorbis(NLMISC::IStream *stream, bool loop) ov_open_callbacks(this, &_OggVorbisFile, NULL, 0, OV_CALLBACKS_NLMISC_STREAM); } -CMusicBufferVorbis::~CMusicBufferVorbis() +CAudioDecoderVorbis::~CAudioDecoderVorbis() { ov_clear(&_OggVorbisFile); } /// Get information on a music file (only artist and title at the moment). -bool CMusicBufferVorbis::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title) +bool CAudioDecoderVorbis::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title) { - CMusicBufferVorbis mbv(stream, false); // just opens and closes the oggvorbisfile thing :) + CAudioDecoderVorbis mbv(stream, false); // just opens and closes the oggvorbisfile thing :) vorbis_comment *vc = ov_comment(&mbv._OggVorbisFile, -1); char *title_c = vorbis_comment_query(vc, "title", 0); if (title_c) title = title_c; else title.clear(); @@ -113,29 +128,27 @@ bool CMusicBufferVorbis::getInfo(NLMISC::IStream *stream, std::string &artist, s return true; } -uint32 CMusicBufferVorbis::getRequiredBytes() +uint32 CAudioDecoderVorbis::getRequiredBytes() { return 0; // no minimum requirement of bytes to buffer out } -uint32 CMusicBufferVorbis::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) +uint32 CAudioDecoderVorbis::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) { sint current_section = 0; // ??? if (_IsMusicEnded) return 0; nlassert(minimum <= maximum); // can't have this.. uint32 bytes_read = 0; - #ifdef NL_BIG_ENDIAN sint endianness = 1; #else sint endianness = 0; #endif - do { // signed 16-bit or unsigned 8-bit little-endian samples sint br = ov_read(&_OggVorbisFile, (char *)&buffer[bytes_read], maximum - bytes_read, - endianness, // Specifies big or little endian byte packing. 0 for little endian, 1 for big endian. Typical value is 0. + endianness, // Specifies big or little endian byte packing. 0 for little endian, 1 for b ig endian. Typical value is 0. getBitsPerSample() == 8 ? 1 : 2, getBitsPerSample() == 8 ? 0 : 1, // Signed or unsigned data. 0 for unsigned, 1 for signed. Typically 1. ¤t_section); @@ -162,58 +175,53 @@ uint32 CMusicBufferVorbis::getNextBytes(uint8 *buffer, uint32 minimum, uint32 ma // error switch(br) { - case OV_HOLE: + case OV_HOLE: nlwarning("ov_read returned OV_HOLE"); break; - - case OV_EINVAL: + case OV_EINVAL: nlwarning("ov_read returned OV_EINVAL"); break; - - case OV_EBADLINK: + case OV_EBADLINK: nlwarning("ov_read returned OV_EBADLINK"); break; - - default: + default: nlwarning("ov_read returned %d", br); } - - return 0; } } while (bytes_read < minimum); return bytes_read; } -uint8 CMusicBufferVorbis::getChannels() +uint8 CAudioDecoderVorbis::getChannels() { vorbis_info *vi = ov_info(&_OggVorbisFile, -1); return (uint8)vi->channels; } -uint32 CMusicBufferVorbis::getSamplesPerSec() +uint CAudioDecoderVorbis::getSamplesPerSec() { vorbis_info *vi = ov_info(&_OggVorbisFile, -1); - return vi->rate; + return (uint)vi->rate; } -uint8 CMusicBufferVorbis::getBitsPerSample() +uint8 CAudioDecoderVorbis::getBitsPerSample() { return 16; } -bool CMusicBufferVorbis::isMusicEnded() +bool CAudioDecoderVorbis::isMusicEnded() { return _IsMusicEnded; } -float CMusicBufferVorbis::getLength() +float CAudioDecoderVorbis::getLength() { return (float)ov_time_total(&_OggVorbisFile, -1); } -uint CMusicBufferVorbis::getUncompressedSize() +void CAudioDecoderVorbis::setLooping(bool loop) { - return (uint)ov_pcm_total(&_OggVorbisFile, -1) * (getBitsPerSample() / 2) * getChannels(); + _Loop = loop; } } /* namespace NLSOUND */ diff --git a/code/nel/src/sound/audio_mixer_user.cpp b/code/nel/src/sound/audio_mixer_user.cpp index 367abd0e1..1a6c2d322 100644 --- a/code/nel/src/sound/audio_mixer_user.cpp +++ b/code/nel/src/sound/audio_mixer_user.cpp @@ -45,12 +45,15 @@ #include "nel/sound/context_sound.h" #include "nel/sound/music_source.h" #include "nel/sound/stream_source.h" +#include "nel/sound/stream_file_source.h" #include "nel/sound/simple_sound.h" #include "nel/sound/music_sound.h" #include "nel/sound/stream_sound.h" #include "nel/sound/sample_bank_manager.h" #include "nel/sound/sample_bank.h" #include "nel/sound/sound_bank.h" +#include "nel/sound/group_controller.h" +#include "nel/sound/containers.h" using namespace std; using namespace NLMISC; @@ -218,6 +221,7 @@ void CAudioMixerUser::writeProfile(std::string& out) */ out += "Sound mixer: \n"; out += "\tPlaying sources: " + toString (getPlayingSourcesCount()) + " \n"; + out += "\tPlaying simple sources: " + toString(countPlayingSimpleSources()) + " / " + toString(countSimpleSources()) + " \n"; out += "\tAvailable tracks: " + toString (getAvailableTracksCount()) + " \n"; out += "\tUsed tracks: " + toString (getUsedTracksCount()) + " \n"; // out << "\tMuted sources: " << nb << " \n"; @@ -248,6 +252,16 @@ void CAudioMixerUser::addSourceWaitingForPlay(CSourceCommon *source) _SourceWaitingForPlay.push_back(source); } +// ****************************************************************** + +void CAudioMixerUser::removeSourceWaitingForPlay(CSourceCommon *source) +{ + std::list::iterator it = find(_SourceWaitingForPlay.begin(), _SourceWaitingForPlay.end(), source); + if (it != _SourceWaitingForPlay.end()) + { + _SourceWaitingForPlay.erase(it); + } +} // ****************************************************************** @@ -256,8 +270,9 @@ void CAudioMixerUser::reset() _Leaving = true; _SourceWaitingForPlay.clear(); - - /* TODO: Stop music channels */ + + for (uint i = 0; i < _NbMusicChannelFaders; ++i) + _MusicChannelFaders[i].reset(); // Stop tracks uint i; @@ -1638,6 +1653,7 @@ void CAudioMixerUser::update() _MusicChannelFaders[i].update(); // Check all playing track and stop any terminated buffer. + std::list::size_type nbWaitingSources = _Sources.size(); for (i=0; i<_Tracks.size(); ++i) { if (!_Tracks[i]->isPlaying()) @@ -1649,13 +1665,14 @@ void CAudioMixerUser::update() } // try to play any waiting source. - if (!_SourceWaitingForPlay.empty()) + if (!_SourceWaitingForPlay.empty() && nbWaitingSources) { // check if the source still exist before trying to play it if (_Sources.find(_SourceWaitingForPlay.front()) != _Sources.end()) _SourceWaitingForPlay.front()->play(); // nldebug("Before POP Sources waiting : %u", _SourceWaitingForPlay.size()); _SourceWaitingForPlay.pop_front(); + --nbWaitingSources; // nldebug("After POP Sources waiting : %u", _SourceWaitingForPlay.size()); } } @@ -1689,7 +1706,7 @@ void CAudioMixerUser::update() // _Tracks[i]->DrvSource->setPos(source->getPos() * (1-css->PosAlpha) + css->Position*(css->PosAlpha)); _Tracks[i]->getPhysicalSource()->setPos(source->getPos() * (1-css->PosAlpha) + vpos*(css->PosAlpha)); // update the relative gain - _Tracks[i]->getPhysicalSource()->setGain(source->getRelativeGain()*source->getGain()*css->Gain); + _Tracks[i]->getPhysicalSource()->setGain(source->getFinalGain() * css->Gain); #if EAX_AVAILABLE == 1 if (_UseEax) { @@ -1826,10 +1843,14 @@ bool CAudioMixerUser::tryToLoadSampleBank(const std::string &sampleName) } } +UGroupController *CAudioMixerUser::getGroupController(const std::string &path) +{ + return static_cast(_GroupController.getGroupController(path)); +} // ****************************************************************** -USource *CAudioMixerUser::createSource( TSoundId id, bool spawn, TSpawnEndCallback cb, void *userParam, NL3D::CCluster *cluster, CSoundContext *context ) +USource *CAudioMixerUser::createSource( TSoundId id, bool spawn, TSpawnEndCallback cb, void *userParam, NL3D::CCluster *cluster, CSoundContext *context, UGroupController *groupController ) { #if NL_PROFILE_MIXER TTicks start = CTime::getPerformanceTime(); @@ -1915,7 +1936,7 @@ retrySound: } // Create source - CSimpleSource *source = new CSimpleSource( simpleSound, spawn, cb, userParam, cluster); + CSimpleSource *source = new CSimpleSource( simpleSound, spawn, cb, userParam, cluster, static_cast(groupController)); // nldebug("Mixer : source %p created", source); @@ -1939,28 +1960,35 @@ retrySound: { CStreamSound *streamSound = static_cast(id); // This is a stream thingy. - ret = new CStreamSource(streamSound, spawn, cb, userParam, cluster); + ret = new CStreamSource(streamSound, spawn, cb, userParam, cluster, static_cast(groupController)); + } + break; + case CSound::SOUND_STREAM_FILE: + { + CStreamFileSound *streamFileSound = static_cast(id); + // This is a stream file thingy. + ret = new CStreamFileSource(streamFileSound, spawn, cb, userParam, cluster, static_cast(groupController)); } break; case CSound::SOUND_COMPLEX: { CComplexSound *complexSound = static_cast(id); // This is a pattern sound. - ret = new CComplexSource(complexSound, spawn, cb, userParam, cluster); + ret = new CComplexSource(complexSound, spawn, cb, userParam, cluster, static_cast(groupController)); } break; case CSound::SOUND_BACKGROUND: { // This is a background sound. CBackgroundSound *bgSound = static_cast(id); - ret = new CBackgroundSource(bgSound, spawn, cb, userParam, cluster); + ret = new CBackgroundSource(bgSound, spawn, cb, userParam, cluster, static_cast(groupController)); } break; case CSound::SOUND_MUSIC: { // This is a background music sound CMusicSound *music_sound= static_cast(id); - ret = new CMusicSource(music_sound, spawn, cb, userParam, cluster); + ret = new CMusicSource(music_sound, spawn, cb, userParam, cluster, static_cast(groupController)); } break; case CSound::SOUND_CONTEXT: @@ -1974,7 +2002,7 @@ retrySound: CSound *sound = ctxSound->getContextSound(*context); if (sound != 0) { - ret = createSource(sound, spawn, cb, userParam, cluster); + ret = createSource(sound, spawn, cb, userParam, cluster, NULL, static_cast(groupController)); // Set the volume of the source according to the context volume if (ret != 0) { @@ -2007,9 +2035,9 @@ retrySound: // ****************************************************************** -USource *CAudioMixerUser::createSource( const NLMISC::TStringId &name, bool spawn, TSpawnEndCallback cb, void *userParam, NL3D::CCluster *cluster, CSoundContext *context) +USource *CAudioMixerUser::createSource( const NLMISC::TStringId &name, bool spawn, TSpawnEndCallback cb, void *userParam, NL3D::CCluster *cluster, CSoundContext *context, UGroupController *groupController) { - return createSource( getSoundId( name ), spawn, cb, userParam, cluster, context); + return createSource( getSoundId( name ), spawn, cb, userParam, cluster, context, groupController); } @@ -2147,6 +2175,32 @@ uint CAudioMixerUser::getPlayingSourcesCount() const return _PlayingSources; } + +// ****************************************************************** + +uint CAudioMixerUser::countPlayingSimpleSources() const +{ + uint count = 0; + for (TSourceContainer::const_iterator it(_Sources.begin()), end(_Sources.end()); it != end; ++it) + { + if ((*it)->getType() == CSourceCommon::SOURCE_SIMPLE && (*it)->isPlaying()) + ++count; + } + return count; +} + +uint CAudioMixerUser::countSimpleSources() const +{ + uint count = 0; + for (TSourceContainer::const_iterator it(_Sources.begin()), end(_Sources.end()); it != end; ++it) + { + if ((*it)->getType() == CSourceCommon::SOURCE_SIMPLE) + ++count; + } + return count; +} + + // ****************************************************************** uint CAudioMixerUser::getAvailableTracksCount() const diff --git a/code/nel/src/sound/background_source.cpp b/code/nel/src/sound/background_source.cpp index c2cb5206a..dbb14242a 100644 --- a/code/nel/src/sound/background_source.cpp +++ b/code/nel/src/sound/background_source.cpp @@ -26,8 +26,8 @@ namespace NLSOUND { -CBackgroundSource::CBackgroundSource(CBackgroundSound *backgroundSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster) -: CSourceCommon(backgroundSound, spawn, cb, cbUserParam, cluster) +CBackgroundSource::CBackgroundSource(CBackgroundSound *backgroundSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController) +: CSourceCommon(backgroundSound, spawn, cb, cbUserParam, cluster, groupController) { _BackgroundSound = backgroundSound; } @@ -119,7 +119,7 @@ void CBackgroundSource::play() for (; first != last; ++first) { TSubSource subSource; - subSource.Source = mixer->createSource(first->SoundName, false, 0, 0, _Cluster, 0); + subSource.Source = mixer->createSource(first->SoundName, false, 0, 0, _Cluster, NULL, _GroupController); if (subSource.Source != NULL) subSource.Source->setPriority(_Priority); subSource.Filter = first->Filter; diff --git a/code/nel/src/sound/complex_source.cpp b/code/nel/src/sound/complex_source.cpp index cd3d2925d..8fad61a53 100644 --- a/code/nel/src/sound/complex_source.cpp +++ b/code/nel/src/sound/complex_source.cpp @@ -25,8 +25,8 @@ using namespace NLMISC; namespace NLSOUND { -CComplexSource::CComplexSource (CComplexSound *soundPattern, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster) -: CSourceCommon(soundPattern, spawn, cb, cbUserParam, cluster), +CComplexSource::CComplexSource (CComplexSound *soundPattern, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController) +: CSourceCommon(soundPattern, spawn, cb, cbUserParam, cluster, groupController), _Source1(NULL), _Source2(NULL) { @@ -117,7 +117,7 @@ void CComplexSource::playStuf() else _FadeLength = 0; - _Source2 = mixer->createSource(sound, false, 0, 0, _Cluster); + _Source2 = mixer->createSource(sound, false, 0, 0, _Cluster, NULL, _GroupController); if (_Source2 == NULL) return; _Source2->setPriority(_Priority); @@ -155,7 +155,7 @@ void CComplexSource::playStuf() { CSound *sound = mixer->getSoundId(_PatternSound->getSound(soundSeq[_SoundSeqIndex++])); - _Source1 = mixer->createSource(sound, false, 0, 0, _Cluster); + _Source1 = mixer->createSource(sound, false, 0, 0, _Cluster, NULL, _GroupController); if (_Source1 == NULL) return; _Source1->setPriority(_Priority); @@ -202,7 +202,7 @@ void CComplexSource::playStuf() CSound *sound = mixer->getSoundId(*first); if (sound != NULL) { - USource *source = mixer->createSource(sound, false, 0, 0, _Cluster); + USource *source = mixer->createSource(sound, false, 0, 0, _Cluster, NULL, _GroupController); if (source != NULL) { source->setPriority(_Priority); @@ -512,7 +512,7 @@ void CComplexSource::onUpdate() // determine the XFade length (if next sound is too short. _FadeLength = minof(uint32(_PatternSound->getFadeLength()/_TickPerSecond), (sound2->getDuration()) / 2, (_Source1->getSound()->getDuration())/2); - _Source2 = mixer->createSource(sound2, false, 0, 0, _Cluster); + _Source2 = mixer->createSource(sound2, false, 0, 0, _Cluster, NULL, _GroupController); if (_Source2) { _Source2->setPriority(_Priority); @@ -641,7 +641,7 @@ void CComplexSource::onEvent() CSound *sound = mixer->getSoundId(_PatternSound->getSound(soundSeq[_SoundSeqIndex++])); - _Source1 = mixer->createSource(sound, false, 0, 0, _Cluster); + _Source1 = mixer->createSource(sound, false, 0, 0, _Cluster, NULL, _GroupController); if (_Source1 == NULL) { stop(); diff --git a/code/nel/src/sound/driver/CMakeLists.txt b/code/nel/src/sound/driver/CMakeLists.txt index 1a8391c41..90fbbb562 100644 --- a/code/nel/src/sound/driver/CMakeLists.txt +++ b/code/nel/src/sound/driver/CMakeLists.txt @@ -3,14 +3,7 @@ FILE(GLOB HEADERS ../../../include/nel/sound/driver/*.h) NL_TARGET_LIB(nelsnd_lowlevel ${HEADERS} ${SRC}) -INCLUDE_DIRECTORIES(${VORBIS_INCLUDE_DIR}) - -TARGET_LINK_LIBRARIES(nelsnd_lowlevel nelmisc ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY}) - -IF(WITH_STATIC) - # Add libogg dependency only if target is static because to libvorbisfile - TARGET_LINK_LIBRARIES(nelsnd_lowlevel ${OGG_LIBRARY}) -ENDIF(WITH_STATIC) +TARGET_LINK_LIBRARIES(nelsnd_lowlevel nelmisc) SET_TARGET_PROPERTIES(nelsnd_lowlevel PROPERTIES LINK_INTERFACE_LIBRARIES "") NL_DEFAULT_PROPS(nelsnd_lowlevel "NeL, Library: Sound Lowlevel") diff --git a/code/nel/src/sound/driver/Makefile.am b/code/nel/src/sound/driver/Makefile.am deleted file mode 100644 index 0e7325b29..000000000 --- a/code/nel/src/sound/driver/Makefile.am +++ /dev/null @@ -1,26 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = fmod openal dsound - -SUBDIRS = @SOUND_SUBDIRS@ - -noinst_LTLIBRARIES = libnelsnd_lowlevel.la - -libnelsnd_lowlevel_la_SOURCES = buffer.cpp \ - buffer.h \ - effect.h \ - effect.cpp \ - listener.cpp \ - listener.h \ - sound_driver.cpp \ - sound_driver.h \ - source.cpp \ - source.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -# End of Makefile.am - diff --git a/code/nel/src/sound/driver/fmod/Makefile.am b/code/nel/src/sound/driver/fmod/Makefile.am deleted file mode 100644 index 505c77173..000000000 --- a/code/nel/src/sound/driver/fmod/Makefile.am +++ /dev/null @@ -1,32 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -lib_LTLIBRARIES = libnel_drv_fmod.la - -libnel_drv_fmod_la_SOURCES = buffer_fmod.cpp \ - buffer_fmod.h \ - listener_fmod.cpp \ - listener_fmod.h \ - music_channel_fmod.cpp \ - music_channel_fmod.h \ - sound_driver_fmod.cpp \ - sound_driver_fmod.h \ - source_fmod.cpp \ - source_fmod.h \ - stdfmod.cpp \ - stdfmod.h - - -AM_CXXFLAGS = -I$(top_srcdir)/src @FMOD_CFLAGS@ - -noinst_HEADERS = stdfmod.h - -libnel_drv_fmod_la_LIBADD = @FMOD_LIBS@ - -libnel_drv_fmod_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - - -# End of Makefile.am - diff --git a/code/nel/src/sound/driver/fmod/music_channel_fmod.cpp b/code/nel/src/sound/driver/fmod/music_channel_fmod.cpp index 9c502ea1a..a64dddcfe 100644 --- a/code/nel/src/sound/driver/fmod/music_channel_fmod.cpp +++ b/code/nel/src/sound/driver/fmod/music_channel_fmod.cpp @@ -249,6 +249,12 @@ void CMusicChannelFMod::stop() _CallBackEnded = false; } +void CMusicChannelFMod::reset() +{ + // don't care + stop(); +} + /** Pause the music previously loaded and played (the Memory is not freed) */ void CMusicChannelFMod::pause() diff --git a/code/nel/src/sound/driver/fmod/music_channel_fmod.h b/code/nel/src/sound/driver/fmod/music_channel_fmod.h index d9a17fd99..e91e38a8b 100644 --- a/code/nel/src/sound/driver/fmod/music_channel_fmod.h +++ b/code/nel/src/sound/driver/fmod/music_channel_fmod.h @@ -87,6 +87,9 @@ public: /// Stop the music previously loaded and played (the Memory is also freed) virtual void stop(); + /// Makes sure any resources are freed, but keeps available for next play call + virtual void reset(); + /// Pause the music previously loaded and played (the Memory is not freed) virtual void pause(); diff --git a/code/nel/src/sound/driver/fmod/source_fmod.cpp b/code/nel/src/sound/driver/fmod/source_fmod.cpp index 2ae9a0ea1..4ef2f89d5 100644 --- a/code/nel/src/sound/driver/fmod/source_fmod.cpp +++ b/code/nel/src/sound/driver/fmod/source_fmod.cpp @@ -413,6 +413,13 @@ bool CSourceFMod::getSourceRelativeMode() const // ****************************************************************** void CSourceFMod::setMinMaxDistances( float mindist, float maxdist, bool /* deferred */ ) { + static float maxSqrt = sqrt(std::numeric_limits::max()); + if (maxdist >= maxSqrt) + { + nlwarning("SOUND_DEV (FMod): Ridiculously high max distance set on source"); + maxdist = maxSqrt; + } + _MinDist= mindist; _MaxDist= maxdist; if(_FModChannel!=-1) diff --git a/code/nel/src/sound/driver/music_buffer.cpp b/code/nel/src/sound/driver/music_buffer.cpp deleted file mode 100644 index 40088d49e..000000000 --- a/code/nel/src/sound/driver/music_buffer.cpp +++ /dev/null @@ -1,108 +0,0 @@ -// NeL - MMORPG Framework -// Copyright (C) 2010 Winch Gate Property Limited -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -#include "stdsound_lowlevel.h" - -#include "nel/sound/driver/music_buffer_vorbis.h" -#include "nel/sound/driver/music_buffer.h" - -using namespace std; -using namespace NLMISC; - -namespace NLSOUND -{ - -IMusicBuffer::IMusicBuffer() : _InternalStream(NULL) -{ - -} - -IMusicBuffer::~IMusicBuffer() -{ - if (_InternalStream) { delete _InternalStream; _InternalStream = NULL; } -} - -IMusicBuffer *IMusicBuffer::createMusicBuffer(const std::string &filepath, bool async, bool loop) -{ - string lookup = CPath::lookup(filepath, false); - if (lookup.empty()) - { - nlwarning("Music file %s does not exist!", filepath.c_str()); - return NULL; - } - string type = CFile::getExtension(filepath); - - CIFile *ifile = new CIFile(); - ifile->setAsyncLoading(async); - ifile->open(lookup); - - IMusicBuffer *mb = createMusicBuffer(type, ifile, loop); - - if (mb) mb->_InternalStream = ifile; - else delete ifile; - - return mb; -} - -IMusicBuffer *IMusicBuffer::createMusicBuffer(const std::string &type, NLMISC::IStream *stream, bool loop) -{ - if (!stream) - { - nlwarning("Stream is NULL"); - return NULL; - } - string type_lower = toLower(type); - if (type_lower == "ogg") - { - return new CMusicBufferVorbis(stream, loop); - } - else - { - nlwarning("Music file type unknown: '%s'", type_lower.c_str()); - return NULL; - } -} - -bool IMusicBuffer::getInfo(const std::string &filepath, std::string &artist, std::string &title) -{ - string lookup = CPath::lookup(filepath, false); - if (lookup.empty()) - { - nlwarning("Music file %s does not exist!", filepath.c_str()); - return false; - } - string type = CFile::getExtension(filepath); - string type_lower = toLower(type); - - if (type_lower == "ogg") - { - CIFile ifile; - ifile.setCacheFileOnOpen(false); - ifile.allowBNPCacheFileOnOpen(false); - ifile.open(lookup); - return CMusicBufferVorbis::getInfo(&ifile, artist, title); - } - else - { - nlwarning("Music file type unknown: '%s'", type_lower.c_str()); - artist.clear(); title.clear(); - return false; - } -} - -} /* namespace NLSOUND */ - -/* end of file */ diff --git a/code/nel/src/sound/driver/openal/Makefile.am b/code/nel/src/sound/driver/openal/Makefile.am deleted file mode 100644 index 559a26370..000000000 --- a/code/nel/src/sound/driver/openal/Makefile.am +++ /dev/null @@ -1,30 +0,0 @@ -# -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = driver_openal.def driver_openal.dsp driver_openal.vcproj - -lib_LTLIBRARIES = libnel_drv_openal.la - -libnel_drv_openal_la_SOURCES = buffer_al.cpp \ - buffer_al.h \ - listener_al.cpp \ - listener_al.h \ - sound_driver_al.cpp \ - sound_driver_al.h \ - source_al.cpp \ - source_al.h \ - stdopenal.h - -AM_CXXFLAGS = -I$(top_srcdir)/src @OPENAL_CFLAGS@ @OGG_CFLAGS@ @VORBIS_CFLAGS@ - -noinst_HEADERS = stdopenal.h - -libnel_drv_openal_la_LIBADD = @OPENAL_LIBS@ @OGG_LIBS@ @VORBIS_LIBS@ - -libnel_drv_openal_la_LDFLAGS = -version-info @LIBTOOL_VERSION@ - - -# End of Makefile.am - diff --git a/code/nel/src/sound/driver/openal/listener_al.cpp b/code/nel/src/sound/driver/openal/listener_al.cpp index 67eaf2478..da7e6bc31 100644 --- a/code/nel/src/sound/driver/openal/listener_al.cpp +++ b/code/nel/src/sound/driver/openal/listener_al.cpp @@ -145,9 +145,8 @@ void CListenerAL::getOrientation( NLMISC::CVector& front, NLMISC::CVector& u */ void CListenerAL::setGain( float gain ) { - CSoundDriverAL::getInstance()->setGain(gain); -// alListenerf( AL_GAIN, gain ); -// alTestError(); + alListenerf( AL_GAIN, gain ); + alTestError(); } @@ -156,15 +155,14 @@ void CListenerAL::setGain( float gain ) */ float CListenerAL::getGain() const { - return CSoundDriverAL::getInstance()->getGain(); -// ALfloat gain; -//#ifdef NL_OS_WINDOWS -// alGetListenerf( AL_GAIN, &gain ); -//#else -// alGetListenerfv( AL_GAIN, &gain ); -//#endif -// alTestError(); -// return gain; + ALfloat gain; +#ifdef NL_OS_WINDOWS + alGetListenerf( AL_GAIN, &gain ); +#else + alGetListenerfv( AL_GAIN, &gain ); +#endif + alTestError(); + return gain; } diff --git a/code/nel/src/sound/driver/openal/music_channel_al.cpp b/code/nel/src/sound/driver/openal/music_channel_al.cpp deleted file mode 100644 index b1849b33e..000000000 --- a/code/nel/src/sound/driver/openal/music_channel_al.cpp +++ /dev/null @@ -1,328 +0,0 @@ -// NeL - MMORPG Framework -// Copyright (C) 2010 Winch Gate Property Limited -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -#include "stdopenal.h" - -// Project includes -#include "sound_driver_al.h" -#include "source_al.h" -#include "buffer_al.h" -#include "music_channel_al.h" - -using namespace std; -using namespace NLMISC; - -namespace NLSOUND -{ - -CMusicChannelAL::CMusicChannelAL(CSoundDriverAL *soundDriver) -: _SoundDriver(soundDriver), _MusicBuffer(NULL), _Thread(NULL), _Buffer(NULL), _Source(NULL), _Playing(false), _Async(false), _Gain(1.0) -{ - // create a default source for music streaming - _Source = static_cast(_SoundDriver->createSource()); - _Source->setType(SourceMusic); - _Source->setStreamingBufferSize(32768); -} - -CMusicChannelAL::~CMusicChannelAL() -{ - release(); - if (_SoundDriver) { _SoundDriver->removeMusicChannel(this); _SoundDriver = NULL; } -} - -void CMusicChannelAL::release() -{ - // stop thread before deleting it - stop(); - - // delete thread - if (_Thread) - { - delete _Thread; - _Thread = NULL; - } - - // delete source - if (_Source) - { - delete _Source; - _Source = NULL; - } -} - -/// Fill IBuffer with data from IMusicBuffer -bool CMusicChannelAL::fillBuffer(IBuffer *buffer, uint length) -{ - if (!buffer || !length) - { - nlwarning("AL: No data to stream"); - return false; - } - - // fill buffer with music data - uint8 *tmp = buffer->lock(length); - if (tmp == NULL) - { - nlwarning("AL: Can't allocate %u bytes for buffer", length); - return false; - } - - uint32 size = _MusicBuffer->getNextBytes(tmp, length, length); - buffer->unlock(size); - - return size > 0; -} - -/// Use buffer format from IMusicBuffer -void CMusicChannelAL::setBufferFormat(IBuffer *buffer) -{ - if (!buffer) - { - nlwarning("AL: No buffer specified"); - return; - } - - // use the same format as music for buffers - buffer->setFormat(IBuffer::FormatPcm, _MusicBuffer->getChannels(), - _MusicBuffer->getBitsPerSample(), _MusicBuffer->getSamplesPerSec()); -} - -void CMusicChannelAL::run() -{ - bool first = true; - - // use queued buffers - do - { - // buffers to update - std::vector buffers; - - if (first) - { - // get all buffers to queue - _Source->getStreamingBuffers(buffers); - - // set format for each buffer - for(uint i = 0; i < buffers.size(); ++i) - setBufferFormat(buffers[i]); - } - else - { - // get unqueued buffers - _Source->getProcessedStreamingBuffers(buffers); - } - - // fill buffers - for(uint i = 0; i < buffers.size(); ++i) - { - if (!fillBuffer(buffers[i], _Source->getStreamingBufferSize())) - break; - - // add buffer to streaming buffers queue - _Source->submitStreamingBuffer(buffers[i]); - } - - // play the source - if (first) - { - _Source->play(); - first = false; - } - - // wait 100ms before rechecking buffers - nlSleep(100); - } - while(!_MusicBuffer->isMusicEnded() && _Playing); - - - // music finished without interruption - if (_Playing) - { - // wait until source is not playing - while(_Source->isPlaying() && _Playing) nlSleep(1000); - - _Source->stop(); - - _Playing = false; - } -} - -/// Play sync music -bool CMusicChannelAL::playSync() -{ - // use an unique buffer managed by CMusicChannelAL - _Buffer = _SoundDriver->createBuffer(); - - // set format - setBufferFormat(_Buffer); - - // fill data - fillBuffer(_Buffer, _MusicBuffer->getUncompressedSize()); - - // we don't need _MusicBuffer anymore because all is loaded into memory - if (_MusicBuffer) - { - delete _MusicBuffer; - _MusicBuffer = NULL; - } - - // delete previous queued buffers - _Source->setStreamingBuffersMax(0); - - // use this buffer as source - _Source->setStaticBuffer(_Buffer); - - // play the source - return _Source->play(); -} - -/** Play some music (.ogg etc...) - * NB: if an old music was played, it is first stop with stopMusic() - * \param filepath file path, CPath::lookup is done here - * \param async stream music from hard disk, preload in memory if false - * \param loop must be true to play the music in loop. - */ -bool CMusicChannelAL::play(const std::string &filepath, bool async, bool loop) -{ - // stop a previous music - stop(); - - // when not using async, we must load the whole file once - _MusicBuffer = IMusicBuffer::createMusicBuffer(filepath, async, async ? loop:false); - - if (_MusicBuffer) - { - _Async = async; - _Playing = true; - - _Source->setSourceRelativeMode(true); - - if (_Async) - { - // create the thread if it's not yet created - if (!_Thread) _Thread = IThread::create(this); - - if (!_Thread) - { - nlwarning("AL: Can't create a new thread"); - return false; - } - - // use 4 queued buffers - _Source->setStreamingBuffersMax(4); - - // we need to loop the source only if not async - _Source->setLooping(false); - - // start the thread - _Thread->start(); - } - else - { - // we need to loop the source only if not async - _Source->setLooping(loop); - - return playSync(); - } - } - else - { - nlwarning("AL: Can't stream file %s", filepath.c_str()); - return false; - } - - return true; -} - -/// Stop the music previously loaded and played (the Memory is also freed) -void CMusicChannelAL::stop() -{ - _Playing = false; - - _Source->stop(); - - // if not using async streaming, we manage static buffer ourself - if (!_Async && _Buffer) - { - _Source->setStaticBuffer(NULL); - delete _Buffer; - _Buffer = NULL; - } - - // wait until thread is finished - if (_Thread) - _Thread->wait(); - - if (_MusicBuffer) - { - delete _MusicBuffer; - _MusicBuffer = NULL; - } -} - -/// Pause the music previously loaded and played (the Memory is not freed) -void CMusicChannelAL::pause() -{ - _Source->pause(); -} - -/// Resume the music previously paused -void CMusicChannelAL::resume() -{ - _Source->play(); -} - -/// Return true if a song is finished. -bool CMusicChannelAL::isEnded() -{ - return !_Playing; -} - -/// Return true if the song is still loading asynchronously and hasn't started playing yet (false if not async), used to delay fading -bool CMusicChannelAL::isLoadingAsync() -{ - return _Async && _Playing && !_Source->isPlaying(); -} - -/// Return the total length (in second) of the music currently played -float CMusicChannelAL::getLength() -{ - if (_MusicBuffer) return _MusicBuffer->getLength(); - else return .0f; -} - -/** Set the music volume (if any music played). (volume value inside [0 , 1]) (default: 1) - * NB: in OpenAL driver, the volume of music IS affected by IListener::setGain() - */ -void CMusicChannelAL::setVolume(float gain) -{ - _Gain = gain; - _Source->setGain(gain); -} - -/// Update music -void CMusicChannelAL::update() -{ - // stop sync music once finished playing - if (_Playing && !_Async && !_Source->isPlaying()) - { - stop(); - } -} - -} /* namespace NLSOUND */ - -/* end of file */ diff --git a/code/nel/src/sound/driver/openal/music_channel_al.h b/code/nel/src/sound/driver/openal/music_channel_al.h deleted file mode 100644 index 157c18810..000000000 --- a/code/nel/src/sound/driver/openal/music_channel_al.h +++ /dev/null @@ -1,106 +0,0 @@ -// NeL - MMORPG Framework -// Copyright (C) 2010 Winch Gate Property Limited -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -#ifndef NLSOUND_MUSIC_CHANNEL_AL_H -#define NLSOUND_MUSIC_CHANNEL_AL_H - -#include "nel/sound/driver/music_channel.h" - -namespace NLSOUND -{ - class CSoundDriverAL; - class IMusicBuffer; - -/** - * \brief CMusicChannelAL - * \date 2010-07-27 16:56GMT - * \author Kervala - * CMusicChannelAL is an implementation of the IMusicChannel interface to run on OpenAL. - */ -class CMusicChannelAL : public IMusicChannel, public NLMISC::IRunnable -{ -protected: - // outside pointers - CSoundDriverAL* _SoundDriver; - - // pointers - IMusicBuffer* _MusicBuffer; - NLMISC::IThread* _Thread; - - IBuffer* _Buffer; - CSourceAL* _Source; - bool _Playing; - bool _Async; - - float _Gain; - - /// Fill IBuffer with data from IMusicBuffer - bool fillBuffer(IBuffer *buffer, uint length); - - /// Use buffer format from IMusicBuffer - void setBufferFormat(IBuffer *buffer); - - /// Declared in NLMISC::IRunnable interface - virtual void run(); - -public: - CMusicChannelAL(CSoundDriverAL *soundDriver); - virtual ~CMusicChannelAL(); - void release(); - - /** Play some music (.ogg etc...) - * NB: if an old music was played, it is first stop with stopMusic() - * \param filepath file path, CPath::lookup is done here - * \param async stream music from hard disk, preload in memory if false - * \param loop must be true to play the music in loop. - */ - virtual bool play(const std::string &filepath, bool async, bool loop); - - /// Stop the music previously loaded and played (the Memory is also freed) - virtual void stop(); - - /// Pause the music previously loaded and played (the Memory is not freed) - virtual void pause(); - - /// Resume the music previously paused - virtual void resume(); - - /// Return true if a song is finished. - virtual bool isEnded(); - - /// Return true if the song is still loading asynchronously and hasn't started playing yet (false if not async), used to delay fading - virtual bool isLoadingAsync(); - - /// Return the total length (in second) of the music currently played - virtual float getLength(); - - /** Set the music volume (if any music played). (volume value inside [0 , 1]) (default: 1) - * NB: in OpenAL driver, the volume of music IS affected by IListener::setGain() - */ - virtual void setVolume(float gain); - - /// Play sync music - bool playSync(); - - /// Update music - void update(); -}; /* class CMusicChannelAL */ - -} /* namespace NLSOUND */ - -#endif /* #ifndef NLSOUND_MUSIC_CHANNEL_AL_H */ - -/* end of file */ diff --git a/code/nel/src/sound/driver/openal/sound_driver_al.cpp b/code/nel/src/sound/driver/openal/sound_driver_al.cpp index e94755c63..e7eaca3f6 100644 --- a/code/nel/src/sound/driver/openal/sound_driver_al.cpp +++ b/code/nel/src/sound/driver/openal/sound_driver_al.cpp @@ -16,7 +16,6 @@ #include "stdopenal.h" #include "sound_driver_al.h" -#include "music_channel_al.h" #include "buffer_al.h" #include "listener_al.h" #include "source_al.h" @@ -174,7 +173,7 @@ uint32 NLSOUND_interfaceVersion () */ CSoundDriverAL::CSoundDriverAL(ISoundDriver::IStringMapperProvider *stringMapper) : _StringMapper(stringMapper), _AlDevice(NULL), _AlContext(NULL), -_NbExpBuffers(0), _NbExpSources(0), _RolloffFactor(1.f), _MasterGain(1.f) +_NbExpBuffers(0), _NbExpSources(0), _RolloffFactor(1.f) { alExtInit(); } @@ -184,21 +183,16 @@ _NbExpBuffers(0), _NbExpSources(0), _RolloffFactor(1.f), _MasterGain(1.f) */ CSoundDriverAL::~CSoundDriverAL() { - // Release internal resources of all remaining IMusicChannel instances - if (_MusicChannels.size()) - { - nlwarning("AL: _MusicChannels.size(): '%u'", (uint32)_MusicChannels.size()); - set::iterator it(_MusicChannels.begin()), end(_MusicChannels.end()); - for (; it != end; ++it) delete *it; - _MusicChannels.clear(); - } + // WARNING: Only internal resources are released here, + // the created instances must still be released by the user! + // Remove the allocated (but not exported) source and buffer names- // Release internal resources of all remaining ISource instances if (_Sources.size()) { nlwarning("AL: _Sources.size(): '%u'", (uint32)_Sources.size()); set::iterator it(_Sources.begin()), end(_Sources.end()); - for (; it != end; ++it) delete *it; + for (; it != end; ++it) (*it)->release(); // CSourceAL will be deleted by user _Sources.clear(); } if (!_Buffers.empty()) alDeleteBuffers(compactAliveNames(_Buffers, alIsBuffer), &*_Buffers.begin()); @@ -207,7 +201,7 @@ CSoundDriverAL::~CSoundDriverAL() { nlwarning("AL: _Effects.size(): '%u'", (uint32)_Effects.size()); set::iterator it(_Effects.begin()), end(_Effects.end()); - for (; it != end; ++it) delete *it; + for (; it != end; ++it) (*it)->release(); // CEffectAL will be deleted by user _Effects.clear(); } @@ -619,16 +613,9 @@ void CSoundDriverAL::commit3DChanges() // Sync up sources & listener 3d position. if (getOption(OptionManualRolloff)) { - set::iterator it = _Sources.begin(), iend = _Sources.end(); - while(it != iend) - { + for (std::set::iterator it(_Sources.begin()), end(_Sources.end()); it != end; ++it) (*it)->updateManualRolloff(); - ++it; - } } - - // update the music (XFade etc...) - updateMusic(); } /// Write information about the driver to the output stream. @@ -656,23 +643,6 @@ void CSoundDriverAL::displayBench(NLMISC::CLog *log) NLMISC::CHTimer::display(log, CHTimer::TotalTime); } -/** Get music info. Returns false if the song is not found or the function is not implemented. - * \param filepath path to file, CPath::lookup done by driver - * \param artist returns the song artist (empty if not available) - * \param title returns the title (empty if not available) - */ -bool CSoundDriverAL::getMusicInfo(const std::string &filepath, std::string &artist, std::string &title) -{ - // add support for additional non-standard music file types info here - return IMusicBuffer::getInfo(filepath, artist, title); -} - -void CSoundDriverAL::updateMusic() -{ - set::iterator it(_MusicChannels.begin()), end(_MusicChannels.end()); - for (; it != end; ++it) (*it)->update(); -} - /// Remove a buffer void CSoundDriverAL::removeBuffer(CBufferAL *buffer) { @@ -695,35 +665,6 @@ void CSoundDriverAL::removeEffect(CEffectAL *effect) else nlwarning("AL: removeEffect already called"); } -/// Create a music channel -IMusicChannel *CSoundDriverAL::createMusicChannel() -{ - CMusicChannelAL *music_channel = new CMusicChannelAL(this); - _MusicChannels.insert(music_channel); - return static_cast(music_channel); -} - -/// (Internal) Remove a music channel (should be called by the destructor of the music channel class). -void CSoundDriverAL::removeMusicChannel(CMusicChannelAL *musicChannel) -{ - if (_MusicChannels.find(musicChannel) != _MusicChannels.end()) _MusicChannels.erase(musicChannel); - else nlwarning("AL: removeMusicChannel already called"); -} - -/// Set the gain -void CSoundDriverAL::setGain( float gain ) -{ - clamp(gain, 0.f, 1.f); - _MasterGain= gain; - // TODO: update all sources in not using manual rollof ? -} - -/// Get the gain -float CSoundDriverAL::getGain() -{ - return _MasterGain; -} - /// Delete a buffer or a source bool CSoundDriverAL::deleteItem( ALuint name, TDeleteFunctionAL aldeletefunc, vector& names ) { diff --git a/code/nel/src/sound/driver/openal/sound_driver_al.h b/code/nel/src/sound/driver/openal/sound_driver_al.h index 50bffa379..154bc6d78 100644 --- a/code/nel/src/sound/driver/openal/sound_driver_al.h +++ b/code/nel/src/sound/driver/openal/sound_driver_al.h @@ -19,13 +19,11 @@ #include -namespace NLSOUND -{ +namespace NLSOUND { class CBufferAL; class CListenerAL; class CSourceAL; class CEffectAL; - class CMusicChannelAL; // alGenBuffers, alGenSources //typedef ALAPI ALvoid ALAPIENTRY (*TGenFunctionAL) ( ALsizei, ALuint* ); @@ -82,8 +80,6 @@ private: std::set _Sources; // Allocated effects std::set _Effects; - /// Array with the allocated music channels created by client code. - std::set _MusicChannels; // Number of exported buffers (including any deleted buffers) uint _NbExpBuffers; // Number of exported sources (including any deleted sources) @@ -101,6 +97,10 @@ public: /// Destructor virtual ~CSoundDriverAL(); + inline ALCdevice *getAlDevice() { return _AlDevice; } + inline ALCcontext *getAlContext() { return _AlContext; } + inline float getRolloffFactor() { return _RolloffFactor; } + /// Return a list of available devices for the user. The value at index 0 is empty, and is used for automatic device selection. virtual void getDevices(std::vector &devices); /// Initialize the driver with a user selected device. If device.empty(), the default or most appropriate device is used. @@ -111,73 +111,46 @@ public: /// Return if an option is enabled (including those that cannot be disabled on this driver). virtual bool getOption(TSoundOptions option); - /// Commit all the changes made to 3D settings of listener and sources - virtual void commit3DChanges(); - + /// Create a sound buffer + virtual IBuffer *createBuffer(); /// Create the listener instance virtual IListener *createListener(); - /// Create a source, destroy with delete + /// Create a source virtual ISource *createSource(); - /// Create a sound buffer, destroy with delete - virtual IBuffer *createBuffer(); /// Create a reverb effect virtual IReverbEffect *createReverbEffect(); /// Return the maximum number of sources that can created virtual uint countMaxSources(); /// Return the maximum number of effects that can be created virtual uint countMaxEffects(); - - /// Write information about the driver to the output stream. - virtual void writeProfile(std::string& /* out */); - + virtual void startBench(); virtual void endBench(); virtual void displayBench(NLMISC::CLog * /* log */); - /// Create a music channel, destroy with destroyMusicChannel. - virtual IMusicChannel *createMusicChannel(); - - /** Get music info. Returns false if the song is not found or the function is not implemented. - * \param filepath path to file, CPath::lookup done by driver - * \param artist returns the song artist (empty if not available) - * \param title returns the title (empty if not available) - */ - virtual bool getMusicInfo(const std::string &filepath, std::string &artist, std::string &title); - - /// Get audio/container extensions that are supported natively by the driver implementation. - virtual void getMusicExtensions(std::vector & /* extensions */) const { } - /// Return if a music extension is supported by the driver's music channel. - virtual bool isMusicExtensionSupported(const std::string & /* extension */) const { return false; } - - ALCdevice *getAlDevice() { return _AlDevice; } - ALCcontext *getAlContext() { return _AlContext; } - float getRolloffFactor() { return _RolloffFactor; } /// Change the rolloff factor and apply to all sources void applyRolloffFactor(float f); + /// Commit all the changes made to 3D settings of listener and sources + virtual void commit3DChanges(); + + /// Write information about the driver to the output stream. + virtual void writeProfile(std::string& /* out */); + /// Remove a buffer void removeBuffer(CBufferAL *buffer); /// Remove a source void removeSource(CSourceAL *source); /// Remove an effect void removeEffect(CEffectAL *effect); - /// (Internal) Remove music channel (should be called by the destructor of the music channel class). - void removeMusicChannel(CMusicChannelAL *musicChannel); - /** Set the gain (volume value inside [0 , 1]). (default: 1) - * 0.0 -> silence - * 0.5 -> -6dB - * 1.0 -> no attenuation - * values > 1 (amplification) not supported by most drivers - */ - void setGain( float gain ); - - /// Get the gain - float getGain(); + /// Get audio/container extensions that are supported natively by the driver implementation. + virtual void getMusicExtensions(std::vector & /* extensions */) const { } + /// Return if a music extension is supported by the driver's music channel. + virtual bool isMusicExtensionSupported(const std::string & /* extension */) const { return false; } protected: - void updateMusic(); /// Allocate nb new buffers or sources void allocateNewItems( TGenFunctionAL algenfunc, TTestFunctionAL altestfunc, @@ -195,9 +168,6 @@ protected: /// Delete a buffer or a source bool deleteItem( ALuint name, TDeleteFunctionAL aldeletefunc, std::vector& names ); - - /// Master Volume [0,1] - float _MasterGain; }; diff --git a/code/nel/src/sound/driver/openal/source_al.cpp b/code/nel/src/sound/driver/openal/source_al.cpp index fe2dcb086..dab3123b1 100644 --- a/code/nel/src/sound/driver/openal/source_al.cpp +++ b/code/nel/src/sound/driver/openal/source_al.cpp @@ -15,55 +15,38 @@ // along with this program. If not, see . #include "stdopenal.h" -#include "source_al.h" #include "sound_driver_al.h" #include "listener_al.h" #include "effect_al.h" #include "buffer_al.h" +#include "source_al.h" #include "ext_al.h" +// #define NLSOUND_DEBUG_GAIN + using namespace std; using namespace NLMISC; -namespace NLSOUND +namespace NLSOUND { + +CSourceAL::CSourceAL(CSoundDriverAL *soundDriver) : +_SoundDriver(NULL), _Buffer(NULL), _Source(AL_NONE), +_DirectFilter(AL_FILTER_NULL), _EffectFilter(AL_FILTER_NULL), +_IsPlaying(false), _IsPaused(false), _StartTime(0), _IsStreaming(false), _RelativeMode(false), +_Pos(0.0f, 0.0f, 0.0f), _Gain(NLSOUND_DEFAULT_GAIN), _Alpha(1.0), +_MinDistance(1.0f), _MaxDistance(sqrt(numeric_limits::max())), +_Effect(NULL), _Direct(true), +_DirectGain(NLSOUND_DEFAULT_DIRECT_GAIN), _EffectGain(NLSOUND_DEFAULT_EFFECT_GAIN), +_DirectFilterType(ISource::FilterLowPass), _EffectFilterType(ISource::FilterLowPass), +_DirectFilterEnabled(false), _EffectFilterEnabled(false), +_DirectFilterPassGain(NLSOUND_DEFAULT_FILTER_PASS_GAIN), _EffectFilterPassGain(NLSOUND_DEFAULT_FILTER_PASS_GAIN) { - -CSourceAL::CSourceAL(CSoundDriverAL *soundDriver):ISource(), _SoundDriver(NULL), _Source(AL_NONE), - _DirectFilter(AL_FILTER_NULL), _EffectFilter(AL_FILTER_NULL) -{ - _IsPlaying = false; - _IsPaused = false; - _StartTime = 0; - - _Type = SourceSound; - _Buffer = NULL; - _BuffersMax = 0; - _BufferSize = 32768; - - _PosRelative = false; - _Gain = NLSOUND_DEFAULT_GAIN; - _Alpha = 0.0; - _Pos = CVector::Null; - _MinDistance = 1.0f; - _MaxDistance = numeric_limits::max(); - - _Effect = NULL; - _Direct = true; - _DirectGain = NLSOUND_DEFAULT_DIRECT_GAIN; - _EffectGain = NLSOUND_DEFAULT_EFFECT_GAIN; - _DirectFilterType = ISource::FilterLowPass; - _EffectFilterType = ISource::FilterLowPass; - _DirectFilterEnabled = false; - _EffectFilterEnabled = false; - _DirectFilterPassGain = NLSOUND_DEFAULT_FILTER_PASS_GAIN; - _EffectFilterPassGain = NLSOUND_DEFAULT_FILTER_PASS_GAIN; - // create the al source alGenSources(1, &_Source); alTestError(); - + // configure rolloff - if (!soundDriver || soundDriver->getOption(ISoundDriver::OptionManualRolloff)) + if (soundDriver->getOption(ISoundDriver::OptionManualRolloff)) { alSourcef(_Source, AL_ROLLOFF_FACTOR, 0); alTestError(); @@ -73,16 +56,15 @@ CSourceAL::CSourceAL(CSoundDriverAL *soundDriver):ISource(), _SoundDriver(NULL), alSourcef(_Source, AL_ROLLOFF_FACTOR, soundDriver->getRolloffFactor()); alTestError(); } - + // create filters - if (soundDriver && soundDriver->getOption(ISoundDriver::OptionEnvironmentEffects)) + if (soundDriver->getOption(ISoundDriver::OptionEnvironmentEffects)) { alGenFilters(1, &_DirectFilter); alFilteri(_DirectFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); alFilterf(_DirectFilter, AL_LOWPASS_GAIN, NLSOUND_DEFAULT_DIRECT_GAIN); alFilterf(_DirectFilter, AL_LOWPASS_GAINHF, NLSOUND_DEFAULT_FILTER_PASS_GAIN); alTestError(); - alGenFilters(1, &_EffectFilter); alFilteri(_EffectFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); alFilterf(_EffectFilter, AL_LOWPASS_GAIN, NLSOUND_DEFAULT_EFFECT_GAIN); @@ -103,8 +85,6 @@ CSourceAL::~CSourceAL() void CSourceAL::release() { - unqueueBuffers(); - removeBuffers(); if (_Source != AL_NONE) { alDeleteSources(1, &_Source); _Source = AL_NONE; } if (_DirectFilter != AL_FILTER_NULL) { alDeleteFilters(1, &_DirectFilter); _DirectFilter = AL_FILTER_NULL; } if (_EffectFilter != AL_FILTER_NULL) { alDeleteFilters(1, &_EffectFilter); _EffectFilter = AL_FILTER_NULL; } @@ -114,39 +94,21 @@ void CSourceAL::release() /// (Internal) Update the 3d changes. void CSourceAL::updateManualRolloff() { - CVector pos = getPos(); - - // make relative to listener (if not already!) - if (!_PosRelative) - pos -= CListenerAL::getInstance()->getPos(); - - float sqrdist = pos.sqrnorm(); - float rolloff = ISource::computeManualRolloff(_Alpha, sqrdist, _MinDistance, _MaxDistance); - float volume = _Gain * rolloff; - - // apply SFX volume - if (_SoundDriver && _Type == SourceSound) - volume *= _SoundDriver->getGain(); - - // set the attenuated volume - alSourcef(_Source, AL_GAIN, volume); + CVector distanceVector = _RelativeMode ? _Pos : (_Pos - CListenerAL::getInstance()->getPos()); + float distanceSquare = distanceVector.sqrnorm(); + float rolloff = ISource::computeManualRolloff(_Alpha, distanceSquare, _MinDistance, _MaxDistance); + alSourcef(_Source, AL_GAIN, _Gain * rolloff); alTestError(); -} - -/// Set type of the source -void CSourceAL::setType(TSourceType type) -{ - _Type = type; -} - -/// Get type of the source -TSourceType CSourceAL::getType() const -{ - return _Type; +#ifdef NLSOUND_DEBUG_GAIN + ALfloat gain; + alGetSourcef(_Source, AL_GAIN, &gain); + nlwarning("Called updateManualRolloff(), physical gain is %f, configured gain is %f, distanceSquare is %f, rolloff is %f", gain, _Gain, distanceSquare, rolloff); + alTestError(); +#endif } /// Enable or disable streaming mode. Source must be stopped to call this. -void CSourceAL::setStreaming(bool /* streaming */) +void CSourceAL::setStreaming(bool streaming) { nlassert(isStopped()); @@ -154,6 +116,7 @@ void CSourceAL::setStreaming(bool /* streaming */) alSourcei(_Source, AL_BUFFER, AL_NONE); alTestError(); _Buffer = NULL; + _IsStreaming = streaming; } /* Set the buffer that will be played (no streaming) @@ -199,11 +162,10 @@ void CSourceAL::submitStreamingBuffer(IBuffer *buffer) CBufferAL *bufferAL = static_cast(buffer); ALuint bufferName = bufferAL->bufferName(); nlassert(bufferName); - - // queue the buffer alSourceQueueBuffers(_Source, 1, &bufferName); alTestError(); - + _QueuedBuffers.push(bufferAL); + // Resume playback if the internal OpenAL source stopped due to buffer underrun. ALint srcstate; alGetSourcei(_Source, AL_SOURCE_STATE, &srcstate); @@ -218,11 +180,23 @@ void CSourceAL::submitStreamingBuffer(IBuffer *buffer) /// Return the amount of buffers in the queue (playing and waiting). 3 buffers is optimal. uint CSourceAL::countStreamingBuffers() const { + // a bit ugly here, but makes a much easier/simpler implementation on both drivers + ALint buffersProcessed; + alGetSourcei(_Source, AL_BUFFERS_PROCESSED, &buffersProcessed); + while (buffersProcessed) + { + ALuint bufferName = _QueuedBuffers.front()->bufferName(); + alSourceUnqueueBuffers(_Source, 1, &bufferName); + alTestError(); + const_cast &>(_QueuedBuffers).pop(); + --buffersProcessed; + } // return how many are left in the queue - ALint buffersQueued; - alGetSourcei(_Source, AL_BUFFERS_QUEUED, &buffersQueued); - alTestError(); - return (uint)buffersQueued; + //ALint buffersQueued; + //alGetSourcei(_SourceName, AL_BUFFERS_QUEUED, &buffersQueued); + //alTestError(); + //return (uint)buffersQueued; + return (uint)_QueuedBuffers.size(); } /// Set looping on/off for future playbacks (default: off) @@ -244,28 +218,44 @@ bool CSourceAL::getLooping() const /// Play the static buffer (or stream in and play) bool CSourceAL::play() { - if ( _Buffer != NULL ) +#ifdef NLSOUND_DEBUG_GAIN + if (_IsStreaming) + { + nlwarning("Called play on a streaming source"); + } +#endif + + // Commit 3D changes before starting play + if (_SoundDriver->getOption(ISoundDriver::OptionManualRolloff)) + updateManualRolloff(); + + if (_Buffer) { // Static playing mode _IsPaused = false; alSourcePlay(_Source); - _IsPlaying = alGetError() == AL_NO_ERROR; + _IsPlaying = (alGetError() == AL_NO_ERROR); if (_IsPlaying) _StartTime = CTime::getLocalTime(); return _IsPlaying; } - else + else if (_IsStreaming) { - // TODO: Verify streaming mode? _IsPaused = false; alSourcePlay(_Source); - _IsPlaying = true; - _StartTime = CTime::getLocalTime(); - return true; + _IsPlaying = (alGetError() == AL_NO_ERROR); + if (_IsPlaying) + _StartTime = CTime::getLocalTime(); // TODO: Played time should freeze when buffering fails, and be calculated based on the number of buffers played plus passed time. This is necessary for synchronizing animation with sound. + return _IsPlaying; // Streaming mode //nlwarning("AL: Cannot play null buffer; streaming not implemented" ); //nlstop; } + else + { + nlwarning("Invalid play call, not streaming and no static buffer assigned"); + return false; + } } /// Stop playing @@ -288,8 +278,14 @@ void CSourceAL::stop() _IsPaused = false; alSourceStop(_Source); alTestError(); - - unqueueBuffers(); + // unqueue buffers + while (_QueuedBuffers.size()) + { + ALuint bufferName = _QueuedBuffers.front()->bufferName(); + alSourceUnqueueBuffers(_Source, 1, &bufferName); + _QueuedBuffers.pop(); + alTestError(); + } // Streaming mode //nlwarning("AL: Cannot stop null buffer; streaming not implemented" ); //nlstop; @@ -379,8 +375,7 @@ bool CSourceAL::isPaused() const uint32 CSourceAL::getTime() { if (!_StartTime) return 0; - - return (uint32)(CTime::getLocalTime() - _StartTime); + return (uint32)(CTime::getLocalTime() - _StartTime); } /// Set the position vector. @@ -438,27 +433,28 @@ void CSourceAL::getDirection( NLMISC::CVector& dir ) const void CSourceAL::setGain(float gain) { _Gain = std::min(std::max(gain, NLSOUND_MIN_GAIN), NLSOUND_MAX_GAIN); - - if ((_SoundDriver == NULL) || !_SoundDriver->getOption(ISoundDriver::OptionManualRolloff)) + if (!_SoundDriver->getOption(ISoundDriver::OptionManualRolloff)) { - float gain = _Gain; - - // apply SFX volume - if (_SoundDriver && _Type == SourceSound) - gain *= _SoundDriver->getGain(); - - alSourcef(_Source, AL_GAIN, gain); + alSourcef(_Source, AL_GAIN, _Gain); alTestError(); } +#ifdef NLSOUND_DEBUG_GAIN + else + { + nlwarning("Called setGain(), manual rolloff, commit deferred to play or update, physical gain is %f, configured gain is %f", gain, _Gain); + } +#endif } /// Get the gain float CSourceAL::getGain() const { - //ALfloat gain; - //alGetSourcef(_Source, AL_GAIN, &gain); - //alTestError(); - //return gain; +#ifdef NLSOUND_DEBUG_GAIN + ALfloat gain; + alGetSourcef(_Source, AL_GAIN, &gain); + nlwarning("Called getGain(), physical gain is %f, configured gain is %f", gain, _Gain); + alTestError(); +#endif return _Gain; } @@ -481,7 +477,7 @@ float CSourceAL::getPitch() const /// Set the source relative mode. If true, positions are interpreted relative to the listener position. void CSourceAL::setSourceRelativeMode( bool mode ) { - _PosRelative = mode; + _RelativeMode = mode; alSourcei(_Source, AL_SOURCE_RELATIVE, mode?AL_TRUE:AL_FALSE ); alTestError(); } @@ -489,21 +485,28 @@ void CSourceAL::setSourceRelativeMode( bool mode ) /// Get the source relative mode (3D mode only) bool CSourceAL::getSourceRelativeMode() const { - return _PosRelative; -// ALint b; -// alGetSourcei(_Source, AL_SOURCE_RELATIVE, &b ); -// alTestError(); -// return (b==AL_TRUE); + //ALint b; + //alGetSourcei(_Source, AL_SOURCE_RELATIVE, &b ); + //alTestError(); + //return (b==AL_TRUE); + return _RelativeMode; } /// Set the min and max distances (3D mode only) void CSourceAL::setMinMaxDistances( float mindist, float maxdist, bool /* deferred */) { nlassert( (mindist >= 0.0f) && (maxdist >= 0.0f) ); + + static float maxSqrt = sqrt(std::numeric_limits::max()); + if (maxdist >= maxSqrt) + { + nlwarning("SOUND_DEV (OpenAL): Ridiculously high max distance set on source"); + maxdist = maxSqrt; + } + _MinDistance = mindist; _MaxDistance = maxdist; - - if (!_SoundDriver || !_SoundDriver->getOption(ISoundDriver::OptionManualRolloff)) + if (!_SoundDriver->getOption(ISoundDriver::OptionManualRolloff)) { alSourcef(_Source, AL_REFERENCE_DISTANCE, mindist); alSourcef(_Source, AL_MAX_DISTANCE, maxdist); @@ -805,122 +808,4 @@ float CSourceAL::getEffectFilterPassGain() const return _EffectFilterPassGain; } -/// Get already processed buffers and unqueue them -void CSourceAL::getProcessedStreamingBuffers(std::vector &buffers) -{ - // get the number of processed buffers - ALint buffersProcessed; - alGetSourcei(_Source, AL_BUFFERS_PROCESSED, &buffersProcessed); - alTestError(); - - // exit if more processed buffer than allocated ones - if ((uint)buffersProcessed > _BuffersMax) return; - - // unqueue all previously processed buffers and get their name - alSourceUnqueueBuffers(_Source, buffersProcessed, &(_BuffersName[0])); - alTestError(); - - // add each processed buffer to the array - for(uint i = 0; i < (uint)buffersProcessed; ++i) - { - // if buffer is found, return it - std::map::const_iterator it = _Buffers.find(_BuffersName[i]); - if (it != _Buffers.end()) - buffers.push_back(it->second); - } -} - -/// Get all existing buffers -void CSourceAL::getStreamingBuffers(std::vector &buffers) -{ - std::map::const_iterator it = _Buffers.begin(), iend = _Buffers.end(); - while(it != iend) - { - buffers.push_back(it->second); - ++it; - } -} - -/// Unqueue all buffers -void CSourceAL::unqueueBuffers() -{ - // get count of buffers in queue - uint count = countStreamingBuffers(); - - if (count > 0) - { - // unqueue all of them - alSourceUnqueueBuffers(_Source, count, &(_BuffersName[0])); - alTestError(); - } -} - -/// Delete all allocated buffers -void CSourceAL::removeBuffers() -{ - // delete each buffer - std::map::const_iterator it = _Buffers.begin(), iend = _Buffers.end(); - while(it != iend) - { - delete it->second; - ++it; - } - - _Buffers.clear(); -} - -/// Get available streaming buffers count -uint CSourceAL::getStreamingBuffersMax() const -{ - return _BuffersMax; -} - -/// Set available streaming buffers count and allocate them -void CSourceAL::setStreamingBuffersMax(uint buffers) -{ - // remember previous value - uint oldBuffersMax = _BuffersMax; - - _BuffersMax = buffers; - - // resize the temporary buffer names array - _BuffersName.resize(buffers); - - // remove all buffers - unqueueBuffers(); - removeBuffers(); - - for(uint i = 0; i < _BuffersMax; ++i) - { - try - { - // create a new buffer - CBufferAL *buffer = static_cast(_SoundDriver->createBuffer()); - // use StorageSoftware because buffers will be reused - // deleting and recreating them is a waste of time - buffer->setStorageMode(IBuffer::StorageSoftware); - _Buffers[buffer->bufferName()] = buffer; - } - catch(const ESoundDriverGenBuf &e) - { - nlwarning("Cannot create %d buffers. openal fails after %d buffers", buffers, i); - _BuffersMax = i; - _BuffersName.resize(i); - break; - } - } -} - -/// Set the default size for streaming buffers -void CSourceAL::setStreamingBufferSize(uint size) -{ - _BufferSize = size; -} - -/// Get the default size for streaming buffers -uint CSourceAL::getStreamingBufferSize() const -{ - return _BufferSize; -} - } // NLSOUND diff --git a/code/nel/src/sound/driver/openal/source_al.h b/code/nel/src/sound/driver/openal/source_al.h index b5611b997..53c77cb7f 100644 --- a/code/nel/src/sound/driver/openal/source_al.h +++ b/code/nel/src/sound/driver/openal/source_al.h @@ -17,17 +17,14 @@ #ifndef NL_SOURCE_AL_H #define NL_SOURCE_AL_H -#include "nel/sound/driver/source.h" +#include -namespace NLSOUND -{ +namespace NLSOUND { class IBuffer; class CBufferAL; class CSoundDriverAL; class CEffectAL; - enum TSourceType { SourceSound, SourceMusic }; - /** * OpenAL sound source * @@ -50,29 +47,22 @@ private: /// Sound driver CSoundDriverAL *_SoundDriver; + /// Assigned buffer object + CBufferAL *_Buffer; + std::queue _QueuedBuffers; + /// AL Handles ALuint _Source; ALuint _DirectFilter, _EffectFilter; - - /// Assigned buffer object - CBufferAL *_Buffer; - /// Queued buffers map (uint is buffer name) - std::map _Buffers; - - /// Temporary queued buffers array - std::vector _BuffersName; - /// Max count of queued buffers allowed - uint _BuffersMax; - /// Default size of a buffer - uint _BufferSize; - - /// Position is relative to listener - bool _PosRelative; - + /// Playing status bool _IsPlaying; bool _IsPaused; - NLMISC::TTime _StartTime; + NLMISC::TTime _StartTime; + + bool _IsStreaming; + bool _RelativeMode; + NLMISC::CVector _Pos; float _Gain; double _Alpha; @@ -90,9 +80,6 @@ private: TFilter _DirectFilterType, _EffectFilterType; bool _DirectFilterEnabled, _EffectFilterEnabled; float _DirectFilterPassGain, _EffectFilterPassGain; - - /// Source type can be SourceSound or SourceMusic - TSourceType _Type; public: /// Constructor @@ -104,13 +91,8 @@ public: void release(); /// Return the OpenAL source name - ALuint getSource() const { return _Source; } - - /// Set type of the source - void setType(TSourceType type); - /// Get type of the source - TSourceType getType() const; - + inline ALuint getSource() const { return _Source; } + /// (Internal) Set the effect send for this source, NULL to disable. void setEffect(CEffectAL *effect); /// (Internal) Setup the direct send filter. @@ -275,22 +257,6 @@ public: virtual float getEffectFilterPassGain() const; //@} - /// Get already processed buffers and unqueue them - void getProcessedStreamingBuffers(std::vector &buffers); - /// Get all existing buffers - void getStreamingBuffers(std::vector &buffers); - /// Unqueue all buffers - void unqueueBuffers(); - /// Delete all allocated buffers - void removeBuffers(); - /// Get available streaming buffers count - uint getStreamingBuffersMax() const; - /// Set available streaming buffers count and allocate them - void setStreamingBuffersMax(uint max); - /// Set the default size for streaming buffers - void setStreamingBufferSize(uint size); - /// Get the default size for streaming buffers - uint getStreamingBufferSize() const; }; } // NLSOUND diff --git a/code/nel/src/sound/driver/openal/stdopenal.h b/code/nel/src/sound/driver/openal/stdopenal.h index 12960d57f..1661ef6fe 100644 --- a/code/nel/src/sound/driver/openal/stdopenal.h +++ b/code/nel/src/sound/driver/openal/stdopenal.h @@ -58,6 +58,5 @@ #include "nel/sound/driver/source.h" #include "nel/sound/driver/listener.h" #include "nel/sound/driver/effect.h" -#include "nel/sound/driver/music_buffer.h" /* end of file */ diff --git a/code/nel/src/sound/driver/source.cpp b/code/nel/src/sound/driver/source.cpp index ace98ff81..2f96d74d8 100644 --- a/code/nel/src/sound/driver/source.cpp +++ b/code/nel/src/sound/driver/source.cpp @@ -26,15 +26,12 @@ namespace NLSOUND // common method used only with OptionManualRolloff. return the volume in 1/100th DB ( = mB) modified sint32 ISource::computeManualRollOff(sint32 volumeMB, sint32 mbMin, sint32 mbMax, double alpha, float sqrdist, float distMin, float distMax) { - // root square of max float value - static float maxSqrt = sqrt(std::numeric_limits::max()); - if (sqrdist < distMin * distMin) { // no attenuation return volumeMB; } - else if ((distMax < maxSqrt) && (sqrdist > distMax * distMax)) + else if (sqrdist > distMax * distMax) { // full attenuation return mbMin; diff --git a/code/nel/src/sound/driver/xaudio2/music_channel_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/music_channel_xaudio2.cpp deleted file mode 100644 index 4110ecf41..000000000 --- a/code/nel/src/sound/driver/xaudio2/music_channel_xaudio2.cpp +++ /dev/null @@ -1,248 +0,0 @@ -// NeL - MMORPG Framework -// Copyright (C) 2010 Winch Gate Property Limited -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -#include "stdxaudio2.h" - -// Project includes -#include "sound_driver_xaudio2.h" -#include "music_channel_xaudio2.h" - -using namespace std; -using namespace NLMISC; - -namespace NLSOUND { - -CMusicChannelXAudio2::CMusicChannelXAudio2(CSoundDriverXAudio2 *soundDriver) -: _MusicBuffer(NULL), _SourceVoice(NULL), _BufferPos(0), _SoundDriver(soundDriver), _Gain(1.0) -{ - nlwarning(NLSOUND_XAUDIO2_PREFIX "Initializing CMusicChannelXAudio2"); - - stop(); -} - -CMusicChannelXAudio2::~CMusicChannelXAudio2() -{ - release(); - if (_SoundDriver) { _SoundDriver->removeMusicChannel(this); _SoundDriver = NULL; } - - nlwarning(NLSOUND_XAUDIO2_PREFIX "Destroying CMusicChannelXAudio2"); -} - -void CMusicChannelXAudio2::release() -{ - nlwarning(NLSOUND_XAUDIO2_PREFIX "Releasing CMusicChannelXAudio2"); - - stop(); -} - -/** Play some music (.ogg etc...) - * NB: if an old music was played, it is first stop with stopMusic() - * \param filepath file path, CPath::lookup is done here - * \param async stream music from hard disk, preload in memory if false - * \param loop must be true to play the music in loop. - */ -bool CMusicChannelXAudio2::play(const std::string &filepath, bool async, bool loop) -{ - // nlinfo(NLSOUND_XAUDIO2_PREFIX "play %s %u", filepath.c_str(), (uint32)loop); - - _SoundDriver->performanceIncreaseMusicPlayCounter(); - - HRESULT hr; - - stop(); - - _MusicBuffer = IMusicBuffer::createMusicBuffer(filepath, async, loop); - - if (_MusicBuffer) - { - WAVEFORMATEX wfe; - wfe.cbSize = 0; - wfe.wFormatTag = WAVE_FORMAT_PCM; // todo: getFormat(); - wfe.nChannels = _MusicBuffer->getChannels(); - wfe.wBitsPerSample = _MusicBuffer->getBitsPerSample(); - wfe.nSamplesPerSec = _MusicBuffer->getSamplesPerSec(); - wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample / 8; - wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nBlockAlign; - - XAUDIO2_VOICE_DETAILS voice_details; - _SoundDriver->getMasteringVoice()->GetVoiceDetails(&voice_details); - - // nlinfo(NLSOUND_XAUDIO2_PREFIX "Creating music voice with %u channels, %u bits per sample, %u samples per sec, " - // "on mastering voice with %u channels, %u samples per sec", - // (uint32)wfe.nChannels, (uint32)wfe.wBitsPerSample, (uint32)wfe.nSamplesPerSec, - // (uint32)voice_details.InputChannels, (uint32)voice_details.InputSampleRate); - - if (FAILED(hr = _SoundDriver->getXAudio2()->CreateSourceVoice(&_SourceVoice, &wfe, XAUDIO2_VOICE_NOPITCH, 1.0f, this, NULL, NULL))) - { - nlwarning(NLSOUND_XAUDIO2_PREFIX "FAILED CreateSourceVoice"); - stop(); return false; - } - - _SourceVoice->SetVolume(_Gain); - _SourceVoice->Start(0); - } - else - { - nlwarning(NLSOUND_XAUDIO2_PREFIX "no _MusicBuffer"); - return false; - } - - return true; -} - -/// Stop the music previously loaded and played (the Memory is also freed) -void CMusicChannelXAudio2::stop() -{ - if (_SourceVoice) { _SourceVoice->DestroyVoice(); _SourceVoice = NULL; } - if (_MusicBuffer) { delete _MusicBuffer; _MusicBuffer = NULL; } - // memset(_Buffer, 0, sizeof(_Buffer)); - _BufferPos = 0; -} - -/** Pause the music previously loaded and played (the Memory is not freed) - */ -void CMusicChannelXAudio2::pause() -{ - if (_SourceVoice) _SourceVoice->Stop(0); -} - -/// Resume the music previously paused -void CMusicChannelXAudio2::resume() -{ - if (_SourceVoice) _SourceVoice->Start(0); -} - -/// Return true if a song is finished. -bool CMusicChannelXAudio2::isEnded() -{ - if (_MusicBuffer) - { - if (!_MusicBuffer->isMusicEnded()) - return false; - } - if (_SourceVoice) - { - XAUDIO2_VOICE_STATE voice_state; - _SourceVoice->GetState(&voice_state); - if (voice_state.BuffersQueued) - { - // nldebug(NLSOUND_XAUDIO2_PREFIX "isEnded() -> voice_state.BuffersQueued, wait ..."); - return false; - } - } - // nldebug(NLSOUND_XAUDIO2_PREFIX "isEnded() -> stop()"); - stop(); - return true; -} - -/// Return true if the song is still loading asynchronously and hasn't started playing yet (false if not async), used to delay fading -bool CMusicChannelXAudio2::isLoadingAsync() -{ - return false; -} - -/// Return the total length (in second) of the music currently played -float CMusicChannelXAudio2::getLength() -{ - if (_MusicBuffer) return _MusicBuffer->getLength(); - else return .0f; -} - -/** Set the music volume (if any music played). (volume value inside [0 , 1]) (default: 1) - * NB: the volume of music is NOT affected by IListener::setGain() - */ -void CMusicChannelXAudio2::setVolume(float gain) -{ - _Gain = gain; - if (_SourceVoice) _SourceVoice->SetVolume(gain); -} - -void CMusicChannelXAudio2::OnVoiceProcessingPassStart(UINT32 BytesRequired) -{ - if (BytesRequired > 0) - { - // nlwarning(NLSOUND_XAUDIO2_PREFIX "Bytes Required: %u", BytesRequired); // byte req to not have disruption - - if (_MusicBuffer) - { - uint32 minimum = BytesRequired * 2; // give some more than required :p - if (_MusicBuffer->getRequiredBytes() > minimum) minimum = _MusicBuffer->getRequiredBytes(); - if (minimum > sizeof(_Buffer) - _BufferPos) _BufferPos = 0; - uint32 maximum = sizeof(_Buffer) - _BufferPos; - uint8 *buffer = &_Buffer[_BufferPos]; - uint32 length = _MusicBuffer->getNextBytes(buffer, minimum, maximum); - _BufferPos += length; - - if (length) - { - XAUDIO2_BUFFER xbuffer; - xbuffer.AudioBytes = length; - xbuffer.Flags = 0; - xbuffer.LoopBegin = 0; - xbuffer.LoopCount = 0; - xbuffer.LoopLength = 0; - xbuffer.pAudioData = buffer; - xbuffer.pContext = NULL; // nothing here for now - xbuffer.PlayBegin = 0; - xbuffer.PlayLength = 0; - - _SourceVoice->SubmitSourceBuffer(&xbuffer); - } - else - { - // nldebug(NLSOUND_XAUDIO2_PREFIX "!length -> delete _MusicBuffer"); - // set member var to null before deleting it to avoid crashing main thread - IMusicBuffer *music_buffer = _MusicBuffer; - _MusicBuffer = NULL; delete music_buffer; - _SourceVoice->Discontinuity(); - } - } - } -} - -void CMusicChannelXAudio2::OnVoiceProcessingPassEnd() -{ - -} - -void CMusicChannelXAudio2::OnStreamEnd() -{ - -} - -void CMusicChannelXAudio2::OnBufferStart(void * /* pBufferContext */) -{ - -} - -void CMusicChannelXAudio2::OnBufferEnd(void * /* pBufferContext */) -{ - -} - -void CMusicChannelXAudio2::OnLoopEnd(void * /* pBufferContext */) -{ - -} - -void CMusicChannelXAudio2::OnVoiceError(void * /* pBufferContext */, HRESULT /* Error */) -{ - -} - -} /* namespace NLSOUND */ - -/* end of file */ diff --git a/code/nel/src/sound/driver/xaudio2/music_channel_xaudio2.h b/code/nel/src/sound/driver/xaudio2/music_channel_xaudio2.h deleted file mode 100644 index ade13e25f..000000000 --- a/code/nel/src/sound/driver/xaudio2/music_channel_xaudio2.h +++ /dev/null @@ -1,117 +0,0 @@ -// NeL - MMORPG Framework -// Copyright (C) 2010 Winch Gate Property Limited -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -#ifndef NLSOUND_MUSIC_CHANNEL_XAUDIO2_H -#define NLSOUND_MUSIC_CHANNEL_XAUDIO2_H - -#include - -namespace NLSOUND { - class CSoundDriverXAudio2; - class IMusicBuffer; - -/** - * \brief CMusicChannelXAudio2 - * \date 2008-08-30 13:31GMT - * \author Jan Boon (Kaetemi) - * CMusicChannelXAudio2 is an implementation of the IMusicChannel interface to run on XAudio2. - * TODO: Properly decode the audio on a seperate thread. - * TODO: Change IMusicChannel to IAudioStream, and fix the interface to make more sense. - */ -class CMusicChannelXAudio2 : public IMusicChannel, IXAudio2VoiceCallback -{ -protected: - // outside pointers - CSoundDriverXAudio2 *_SoundDriver; - - // pointers - IMusicBuffer *_MusicBuffer; - IXAudio2SourceVoice *_SourceVoice; - // todo: thread for async loading of music buffer and source voice - // isasyncloading checks if thread exists - // isended checks if not async loading too - - // instances - uint8 _Buffer[64 * 1024]; // no specific reason, lol - uint32 _BufferPos; // 0 - float _Gain; - -public: - CMusicChannelXAudio2(CSoundDriverXAudio2 *soundDriver); - virtual ~CMusicChannelXAudio2(); - void release(); - -private: - // XAudio2 Callbacks - // Called just before this voice's processing pass begins. - STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired); - // Called just after this voice's processing pass ends. - STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS); - // Called when this voice has just finished playing a buffer stream - // (as marked with the XAUDIO2_END_OF_STREAM flag on the last buffer). - STDMETHOD_(void, OnStreamEnd) (THIS); - // Called when this voice is about to start processing a new buffer. - STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext); - // Called when this voice has just finished processing a buffer. - // The buffer can now be reused or destroyed. - STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext); - // Called when this voice has just reached the end position of a loop. - STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext); - // Called in the event of a critical error during voice processing, - // such as a failing XAPO or an error from the hardware XMA decoder. - // The voice may have to be destroyed and re-created to recover from - // the error. The callback arguments report which buffer was being - // processed when the error occurred, and its HRESULT code. - STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error); - -public: - /** Play some music (.ogg etc...) - * NB: if an old music was played, it is first stop with stopMusic() - * \param filepath file path, CPath::lookup is done here - * \param async stream music from hard disk, preload in memory if false - * \param loop must be true to play the music in loop. - */ - virtual bool play(const std::string &filepath, bool async, bool loop); - - /// Stop the music previously loaded and played (the Memory is also freed) - virtual void stop(); - - /// Pause the music previously loaded and played (the Memory is not freed) - virtual void pause(); - - /// Resume the music previously paused - virtual void resume(); - - /// Return true if a song is finished. - virtual bool isEnded(); - - /// Return true if the song is still loading asynchronously and hasn't started playing yet (false if not async), used to delay fading - virtual bool isLoadingAsync(); - - /// Return the total length (in second) of the music currently played - virtual float getLength(); - - /** Set the music volume (if any music played). (volume value inside [0 , 1]) (default: 1) - * NB: the volume of music is NOT affected by IListener::setGain() - */ - virtual void setVolume(float gain); -}; /* class CMusicChannelXAudio2 */ - -} /* namespace NLSOUND */ - -#endif /* #ifndef NLSOUND_MUSIC_CHANNEL_XAUDIO2_H */ - -/* end of file */ diff --git a/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.cpp index 826f9973b..7c4ea9bbf 100644 --- a/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.cpp +++ b/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.cpp @@ -19,7 +19,6 @@ // Project includes #include "listener_xaudio2.h" #include "source_xaudio2.h" -#include "music_channel_xaudio2.h" #include "effect_xaudio2.h" #include "sound_driver_xaudio2.h" @@ -132,8 +131,7 @@ CSoundDriverXAudio2::CSoundDriverXAudio2(ISoundDriver::IStringMapperProvider * / : _XAudio2(NULL), _MasteringVoice(NULL), _Listener(NULL), _SoundDriverOk(false), _CoInitOk(false), _OperationSetCounter(65536), _PerformanceCommit3DCounter(0), _PerformanceADPCMBufferSize(0), - _PerformancePCMBufferSize(0), _PerformanceMusicPlayCounter(0), - _PerformanceSourcePlayCounter(0) + _PerformancePCMBufferSize(0), _PerformanceSourcePlayCounter(0) { nlwarning(NLSOUND_XAUDIO2_PREFIX "Creating CSoundDriverXAudio2"); @@ -200,14 +198,6 @@ void CSoundDriverXAudio2::release() // WARNING: Only internal resources are released here, // the created instances must still be released by the user! - // Release internal resources of all remaining IMusicChannel instances - if (_MusicChannels.size()) - { - nlwarning(NLSOUND_XAUDIO2_PREFIX "_MusicChannels.size(): '%u'", (uint32)_MusicChannels.size()); - set::iterator it(_MusicChannels.begin()), end(_MusicChannels.end()); - for (; it != end; ++it) (*it)->release(); - _MusicChannels.clear(); - } // Release internal resources of all remaining ISource instances if (_Sources.size()) { @@ -396,14 +386,6 @@ void CSoundDriverXAudio2::destroySourceVoice(IXAudio2SourceVoice *sourceVoice) if (sourceVoice) sourceVoice->DestroyVoice(); } -/// Create a music channel -IMusicChannel *CSoundDriverXAudio2::createMusicChannel() -{ - CMusicChannelXAudio2 *music_channel = new CMusicChannelXAudio2(this); - _MusicChannels.insert(music_channel); - return static_cast(music_channel); -} - /// Create the listener instance IListener *CSoundDriverXAudio2::createListener() { @@ -481,7 +463,6 @@ void CSoundDriverXAudio2::writeProfile(std::string& out) + "\n\tPCMBufferSize: " + toString(_PerformancePCMBufferSize) + "\n\tADPCMBufferSize: " + toString(_PerformanceADPCMBufferSize) + "\n\tSourcePlayCounter: " + toString(_PerformanceSourcePlayCounter) - + "\n\tMusicPlayCounter: " + toString(_PerformanceMusicPlayCounter) + "\n\tCommit3DCounter: " + toString(_PerformanceCommit3DCounter) + "\nXAUDIO2_PERFORMANCE_DATA" + "\n\tAudioCyclesSinceLastQuery: " + toString(performance.AudioCyclesSinceLastQuery) @@ -519,17 +500,6 @@ void CSoundDriverXAudio2::displayBench(NLMISC::CLog *log) NLMISC::CHTimer::display(log, CHTimer::TotalTime); } -/** Get music info. Returns false if the song is not found or the function is not implemented. - * \param filepath path to file, CPath::lookup done by driver - * \param artist returns the song artist (empty if not available) - * \param title returns the title (empty if not available) - */ -bool CSoundDriverXAudio2::getMusicInfo(const std::string &filepath, std::string &artist, std::string &title) -{ - // add support for additional non-standard music file types info here - return IMusicBuffer::getInfo(filepath, artist, title); -} - /// Remove a buffer (should be called by the friend destructor of the buffer class) void CSoundDriverXAudio2::removeBuffer(CBufferXAudio2 *buffer) { @@ -543,13 +513,6 @@ void CSoundDriverXAudio2::removeSource(CSourceXAudio2 *source) if (_Sources.find(source) != _Sources.end()) _Sources.erase(source); else nlwarning("removeSource already called"); } - -/// (Internal) Remove a source (should be called by the destructor of the source class). -void CSoundDriverXAudio2::removeMusicChannel(CMusicChannelXAudio2 *musicChannel) -{ - if (_MusicChannels.find(musicChannel) != _MusicChannels.end()) _MusicChannels.erase(musicChannel); - else nlwarning("removeMusicChannel already called"); -} /// (Internal) Remove an effect (should be called by the destructor of the effect class) void CSoundDriverXAudio2::removeEffect(CEffectXAudio2 *effect) diff --git a/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.h b/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.h index 681d36254..bcf847a5f 100644 --- a/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.h +++ b/code/nel/src/sound/driver/xaudio2/sound_driver_xaudio2.h @@ -62,8 +62,6 @@ protected: std::set _Sources; /// Array with the allocated effects created by client code. std::set _Effects; - /// Array with the allocated music channels created by client code. - std::set _MusicChannels; /// Initialization Handle of X3DAudio. X3DAUDIO_HANDLE _X3DAudioHandle; //I /// Operation set counter @@ -73,7 +71,6 @@ protected: uint _PerformancePCMBufferSize; uint _PerformanceADPCMBufferSize; uint _PerformanceSourcePlayCounter; - uint _PerformanceMusicPlayCounter; uint _PerformanceCommit3DCounter; // user init vars @@ -108,8 +105,6 @@ public: } /// (Internal) Increase the source play counter by one. inline void performanceIncreaseSourcePlayCounter() { ++_PerformanceSourcePlayCounter; } - /// (Internal) Increase the music play counter by one. - inline void performanceIncreaseMusicPlayCounter() { ++_PerformanceMusicPlayCounter; } /// (Internal) Increase the commit 3d counter by one. inline void performanceIncreaseCommit3DCounter() { ++_PerformanceCommit3DCounter; } @@ -172,16 +167,6 @@ public: virtual void startBench(); virtual void endBench(); virtual void displayBench(NLMISC::CLog *log); - - /// Create a music channel, destroy with destroyMusicChannel. - virtual IMusicChannel *createMusicChannel(); - - /** Get music info. Returns false if the song is not found or the function is not implemented. - * \param filepath path to file, CPath::lookup done by driver - * \param artist returns the song artist (empty if not available) - * \param title returns the title (empty if not available) - */ - virtual bool getMusicInfo(const std::string &filepath, std::string &artist, std::string &title); /// Get audio/container extensions that are supported natively by the driver implementation. virtual void getMusicExtensions(std::vector & /* extensions */) const { } @@ -192,8 +177,6 @@ public: void removeBuffer(CBufferXAudio2 *buffer); /// (Internal) Remove a source (should be called by the destructor of the source class). void removeSource(CSourceXAudio2 *source); - /// (Internal) Remove a source (should be called by the destructor of the music channel class). - void removeMusicChannel(CMusicChannelXAudio2 *musicChannel); /// (Internal) Remove the listener (should be called by the destructor of the listener class) inline void removeListener(CListenerXAudio2 *listener) { nlassert(_Listener == listener); _Listener = NULL; } /// (Internal) Remove an effect (should be called by the destructor of the effect class) diff --git a/code/nel/src/sound/driver/xaudio2/source_xaudio2.cpp b/code/nel/src/sound/driver/xaudio2/source_xaudio2.cpp index 9c34beaf8..318a65a1a 100644 --- a/code/nel/src/sound/driver/xaudio2/source_xaudio2.cpp +++ b/code/nel/src/sound/driver/xaudio2/source_xaudio2.cpp @@ -42,7 +42,7 @@ _DirectFilterPassGain(NLSOUND_DEFAULT_FILTER_PASS_GAIN), _EffectFilterPassGain(N _DirectFilterLowFrequency(NLSOUND_DEFAULT_FILTER_PASS_LF), _DirectFilterHighFrequency(NLSOUND_DEFAULT_FILTER_PASS_HF), _EffectFilterLowFrequency(NLSOUND_DEFAULT_FILTER_PASS_LF), _EffectFilterHighFrequency(NLSOUND_DEFAULT_FILTER_PASS_HF), _IsPlaying(false), _IsPaused(false), _IsLooping(false), _Pitch(1.0f), -_Gain(1.0f), _MinDistance(1.0f), _MaxDistance(numeric_limits::max()), +_Gain(1.0f), _MinDistance(1.0f), _MaxDistance(sqrt(numeric_limits::max())), _AdpcmUtility(NULL), _Channels(0), _BitsPerSample(0), _BufferStreaming(false) { // nlwarning(NLSOUND_XAUDIO2_PREFIX "Inititializing CSourceXAudio2"); @@ -121,8 +121,7 @@ void CSourceXAudio2::commit3DChanges() { nlassert(_SourceVoice); - // Only mono buffers get 3d sound, multi-channel buffers go directly to the speakers (calculate rolloff too!!). - // Todo: stereo buffers calculate distance ? + // Only mono buffers get 3d sound, multi-channel buffers go directly to the speakers without any distance rolloff. if (_Channels > 1) { // _SoundDriver->getDSPSettings()->DstChannelCount = 1; @@ -494,8 +493,10 @@ bool CSourceXAudio2::initFormat(IBuffer::TBufferFormat bufferFormat, uint8 chann _SourceVoice->SetVolume(_Gain, _OperationSet); setupVoiceSends(); _SoundDriver->getXAudio2()->CommitChanges(_OperationSet); - - + + // Also commit any 3D settings that were already done + commit3DChanges(); + // test //XAUDIO2_VOICE_DETAILS voice_details; //_SourceVoice->GetVoiceDetails(&voice_details); @@ -535,7 +536,7 @@ bool CSourceXAudio2::preparePlay(IBuffer::TBufferFormat bufferFormat, uint8 chan // destroy adpcm utility (if it exists) delete _AdpcmUtility; _AdpcmUtility = NULL; // reset current stuff - _Format = (IBuffer::TBufferFormat)~0; + _Format = IBuffer::FormatNotSet; _Channels = 0; _BitsPerSample = 0; } @@ -582,6 +583,11 @@ bool CSourceXAudio2::play() { // nldebug(NLSOUND_XAUDIO2_PREFIX "play"); + // Commit 3D changes before starting play + if (_SourceVoice) + commit3DChanges(); + // else it is commit by the preparePlay > initFormat function + if (_IsPaused) { if (SUCCEEDED(_SourceVoice->Start(0))) _IsPaused = false; @@ -793,7 +799,14 @@ bool CSourceXAudio2::getSourceRelativeMode() const void CSourceXAudio2::setMinMaxDistances(float mindist, float maxdist, bool /* deferred */) { // nldebug(NLSOUND_XAUDIO2_PREFIX "setMinMaxDistances %f, %f", mindist, maxdist); - + + static float maxSqrt = sqrt(std::numeric_limits::max()); + if (maxdist >= maxSqrt) + { + nlwarning("SOUND_DEV (XAudio2): Ridiculously high max distance set on source"); + maxdist = maxSqrt; + } + _Emitter.InnerRadius = mindist; _MinDistance = mindist; _MaxDistance = maxdist; diff --git a/code/nel/src/sound/driver/xaudio2/stdxaudio2.h b/code/nel/src/sound/driver/xaudio2/stdxaudio2.h index c043f3949..1a1d766e7 100644 --- a/code/nel/src/sound/driver/xaudio2/stdxaudio2.h +++ b/code/nel/src/sound/driver/xaudio2/stdxaudio2.h @@ -57,7 +57,6 @@ #include "nel/sound/driver/listener.h" #include "nel/sound/driver/sound_driver.h" #include "nel/sound/driver/source.h" -#include "nel/sound/driver/music_buffer.h" // Defines #define NLSOUND_XAUDIO2_NAME "NeLSound XAudio2 Driver" diff --git a/code/nel/src/sound/group_controller.cpp b/code/nel/src/sound/group_controller.cpp new file mode 100644 index 000000000..d01e29252 --- /dev/null +++ b/code/nel/src/sound/group_controller.cpp @@ -0,0 +1,129 @@ +/** + * \file group_controller.cpp + * \brief CGroupController + * \date 2012-04-10 09:29GMT + * \author Jan Boon (Kaetemi) + * CGroupController + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#include "stdsound.h" +#include + +// STL includes + +// NeL includes +// #include +#include + +// Project includes + +using namespace std; +// using namespace NLMISC; + +namespace NLSOUND { + +CGroupController::CGroupController(CGroupController *parent) : + m_Parent(parent), m_Gain(1.0f), m_NbSourcesInclChild(0) +{ + +} + +CGroupController::~CGroupController() +{ + // If m_Sources is not empty, a crash is very likely. + nlassert(m_Sources.empty()); + + for (std::map::iterator it(m_Children.begin()), end(m_Children.end()); it != end; ++it) + { + delete it->second; + it->second = NULL; + } + m_Parent = NULL; +} + +void CGroupController::addSource(CSourceCommon *source) +{ + nlassert(this); + + m_Sources.insert(source); + increaseSources(); +} + +void CGroupController::removeSource(CSourceCommon *source) +{ + decreaseSources(); + m_Sources.erase(source); +} + +std::string CGroupController::getPath() // overridden by root +{ + for (std::map::iterator it(m_Parent->m_Children.begin()), end(m_Parent->m_Children.end()); it != end; ++it) + { + if (it->second == this) + { + const std::string &name = it->first; + std::string returnPath = m_Parent->getPath() + ":" + name; + return returnPath; + } + } + nlerror("Group Controller not child of parent"); + return ""; +} + +void CGroupController::calculateFinalGain() // overridden by root +{ + m_FinalGain = calculateTotalGain() * m_Parent->getFinalGain(); +} + +void CGroupController::updateSourceGain() +{ + // Dont update source gain when this controller is inactive. + if (m_NbSourcesInclChild) + { + calculateFinalGain(); + for (TSourceContainer::iterator it(m_Sources.begin()), end(m_Sources.end()); it != end; ++it) + (*it)->updateFinalGain(); + for (std::map::iterator it(m_Children.begin()), end(m_Children.end()); it != end; ++it) + (*it).second->updateSourceGain(); + } +} + +void CGroupController::increaseSources() // overridden by root +{ + ++m_NbSourcesInclChild; + m_Parent->increaseSources(); + + // Update source gain when this controller was inactive before but the parent was active before. + // Thus, when this controller was the root of inactive controllers. + if (m_NbSourcesInclChild == 1 && m_Parent->m_NbSourcesInclChild > 1) + updateSourceGain(); +} + +void CGroupController::decreaseSources() // overridden by root +{ + --m_NbSourcesInclChild; + m_Parent->decreaseSources(); +} + +} /* namespace NLSOUND */ + +/* end of file */ diff --git a/code/nel/src/sound/group_controller_root.cpp b/code/nel/src/sound/group_controller_root.cpp new file mode 100644 index 000000000..0f1c58e63 --- /dev/null +++ b/code/nel/src/sound/group_controller_root.cpp @@ -0,0 +1,127 @@ +/** + * \file group_controller_root.cpp + * \brief CGroupControllerRoot + * \date 2012-04-10 09:44GMT + * \author Jan Boon (Kaetemi) + * CGroupControllerRoot + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#include "stdsound.h" +#include + +// STL includes + +// NeL includes +// #include +#include + +// Project includes + +using namespace std; +// using namespace NLMISC; + +#define NLSOUND_GROUP_CONTROLLER_ROOT_PATH "sound" + +namespace NLSOUND { + +CGroupControllerRoot::CGroupControllerRoot() : CGroupController(NULL) +{ + +} + +CGroupControllerRoot::~CGroupControllerRoot() +{ + +} + +std::string CGroupControllerRoot::getPath() +{ + // The root node is always called sound + return NLSOUND_GROUP_CONTROLLER_ROOT_PATH; +} + +void CGroupControllerRoot::calculateFinalGain() +{ + m_FinalGain = calculateTotalGain(); +} + +void CGroupControllerRoot::increaseSources() +{ + ++m_NbSourcesInclChild; + + // Update source gain when this controller was inactive before. + if (m_NbSourcesInclChild == 1) + updateSourceGain(); +} + +void CGroupControllerRoot::decreaseSources() +{ + --m_NbSourcesInclChild; +} + +bool CGroupControllerRoot::isReservedName(const std::string &nodeName) +{ + // These node names are reserved, in case these category controllers can be integrated with CDB. + // I do not forsee any functionality for changing environment effect settings for entire categories. + // The nodeName parameter is already lowercase, see CGroupControllerRoot::getGroupController. + if (nodeName == NLSOUND_GROUP_CONTROLLER_ROOT_PATH) return true; // Root node name can only used by root. + if (nodeName == "gain") return true; + if (nodeName == "pitch") return true; + if (nodeName == "enable" || nodeName == "enabled") return true; + return false; +} + +CGroupController *CGroupControllerRoot::getGroupController(const std::string &path) +{ + std::vector pathNodes; + NLMISC::splitString(NLMISC::toLower(path), ":", pathNodes); + CGroupController *active = this; + if (pathNodes[0] != NLSOUND_GROUP_CONTROLLER_ROOT_PATH) + { + nlerror("Root node for group controller must always be 'sound', invalid group '%s' requested", path.c_str()); + } + for (std::vector::iterator it(pathNodes.begin() + 1), end(pathNodes.end()); it != end; ++it) + { + if (!(*it).empty()) + { + if (isReservedName(*it)) + { + nlerror("Attempt to use reserved node name '%s' in group controller path '%s'", (*it).c_str(), path.c_str()); + } + std::map::iterator found = active->m_Children.find(*it); + if (found == active->m_Children.end()) + { + active = new CGroupController(active); + active->m_Parent->m_Children[*it] = active; + } + else + { + active = (*found).second; + } + } + } + return active; +} + +} /* namespace NLSOUND */ + +/* end of file */ diff --git a/code/nel/src/sound/music_channel_fader.cpp b/code/nel/src/sound/music_channel_fader.cpp index ced739093..2fe9fb13c 100644 --- a/code/nel/src/sound/music_channel_fader.cpp +++ b/code/nel/src/sound/music_channel_fader.cpp @@ -20,6 +20,7 @@ // Project includes #include "nel/sound/driver/sound_driver.h" #include "nel/sound/driver/music_channel.h" +#include "nel/sound/source_music_channel.h" using namespace std; using namespace NLMISC; @@ -49,9 +50,16 @@ void CMusicChannelFader::init(ISoundDriver *soundDriver) _MusicFader[i].MusicChannel = _SoundDriver->createMusicChannel(); if (!_MusicFader[i].MusicChannel) { - release(); - nlwarning("No music channel available!"); - return; + if (_SoundDriver->getOption(ISoundDriver::OptionHasBufferStreaming)) + { + _MusicFader[i].MusicChannel = new CSourceMusicChannel(); + } + else + { + release(); + nlwarning("No music channel available!"); + return; + } } } } @@ -69,6 +77,15 @@ void CMusicChannelFader::release() } } +void CMusicChannelFader::reset() +{ + for (uint i = 0; i < _MaxMusicFader; ++i) if (_MusicFader[i].MusicChannel) + { + if (_MusicFader[i].MusicChannel) + _MusicFader[i].MusicChannel->reset(); + } +} + void CMusicChannelFader::update() { TTime current_time = CTime::getLocalTime(); diff --git a/code/nel/src/sound/music_sound.cpp b/code/nel/src/sound/music_sound.cpp index 2b192c3c6..803103dd9 100644 --- a/code/nel/src/sound/music_sound.cpp +++ b/code/nel/src/sound/music_sound.cpp @@ -20,6 +20,9 @@ #include "nel/misc/path.h" #include "nel/georges/u_form_elm.h" +#if NLSOUND_SHEET_VERSION_BUILT < 2 +# include "nel/sound/group_controller_root.h" +#endif using namespace std; using namespace NLMISC; @@ -72,6 +75,10 @@ void CMusicSound::importForm(const std::string& filename, NLGEORGES::UFormElm& root.getValueByName(_MinimumPlayTime, ".SoundType.MinimumPlayTime"); root.getValueByName(_TimeBeforeCanReplay, ".SoundType.TimeBeforeCanReplay"); +#if NLSOUND_SHEET_VERSION_BUILT < 2 + _GroupController = CGroupControllerRoot::getInstance()->getGroupController(NLSOUND_SHEET_V1_DEFAULT_SOUND_MUSIC_GROUP_CONTROLLER); +#endif + } // *************************************************************************** @@ -97,6 +104,11 @@ void CMusicSound::serial(NLMISC::IStream &s) CStringMapper::serialString(s, _FileName); s.serial(_FadeInLength, _FadeOutLength); s.serial(_MinimumPlayTime, _TimeBeforeCanReplay); + +#if NLSOUND_SHEET_VERSION_BUILT < 2 + if (s.isReading()) _GroupController = CGroupControllerRoot::getInstance()->getGroupController(NLSOUND_SHEET_V1_DEFAULT_SOUND_MUSIC_GROUP_CONTROLLER); +#endif + } // *************************************************************************** diff --git a/code/nel/src/sound/music_source.cpp b/code/nel/src/sound/music_source.cpp index a47914dde..e5595d9f4 100644 --- a/code/nel/src/sound/music_source.cpp +++ b/code/nel/src/sound/music_source.cpp @@ -26,8 +26,8 @@ namespace NLSOUND { // *************************************************************************** -CMusicSource::CMusicSource(CMusicSound *musicSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster) - : CSourceCommon(musicSound, spawn, cb, cbUserParam, cluster) +CMusicSource::CMusicSource(CMusicSound *musicSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController) + : CSourceCommon(musicSound, spawn, cb, cbUserParam, cluster, groupController) { _MusicSound= musicSound; } diff --git a/code/nel/src/sound/simple_source.cpp b/code/nel/src/sound/simple_source.cpp index fb0b7bc85..cce62cffd 100644 --- a/code/nel/src/sound/simple_source.cpp +++ b/code/nel/src/sound/simple_source.cpp @@ -28,11 +28,12 @@ using namespace NLMISC; namespace NLSOUND { -CSimpleSource::CSimpleSource(CSimpleSound *simpleSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster) - : CSourceCommon(simpleSound, spawn, cb, cbUserParam, cluster), +CSimpleSource::CSimpleSource(CSimpleSound *simpleSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController) + : CSourceCommon(simpleSound, spawn, cb, cbUserParam, cluster, groupController), _SimpleSound(simpleSound), _Track(NULL), - _PlayMuted(false) + _PlayMuted(false), + _WaitingForPlay(false) { nlassert(_SimpleSound != 0); @@ -133,6 +134,7 @@ void CSimpleSource::play() || (_RelativeMode ? getPos().sqrnorm() : (mixer->getListenPosVector() - getPos()).sqrnorm()) > _SimpleSound->getMaxDistance() * _SimpleSound->getMaxDistance()) { // The sample buffer is not available, don't play (we don't know the length) + _WaitingForPlay = false; if (_Spawn) { if (_SpawnEndCb != 0) @@ -166,7 +168,7 @@ void CSimpleSource::play() setDirection(_Direction); // because there is a workaround inside pSource->setVelocity(_Velocity); } - pSource->setGain(_Gain); + pSource->setGain(getFinalGain()); pSource->setSourceRelativeMode(_RelativeMode); pSource->setLooping(_Looping); pSource->setPitch(_Pitch); @@ -174,7 +176,14 @@ void CSimpleSource::play() // and play the sound bool play = pSource->play(); + +#ifdef NL_DEBUG nlassert(play); +#else + if (!play) + nlwarning("Failed to play physical sound source. This is a serious error"); +#endif + // nldebug("CSimpleSource %p : REAL play done", (CAudioMixerUser::IMixerEvent*)this); } else @@ -183,6 +192,7 @@ void CSimpleSource::play() { // This sound is not discardable, add it in waiting playlist mixer->addSourceWaitingForPlay(this); + _WaitingForPlay = true; return; } // there is no available track, just do a 'muted' play @@ -193,6 +203,7 @@ void CSimpleSource::play() } CSourceCommon::play(); + _WaitingForPlay = false; } /// Mixer event call when doing muted play @@ -219,6 +230,13 @@ void CSimpleSource::stop() // nldebug("CSimpleSource %p : stop", (CAudioMixerUser::IMixerEvent*)this); // nlassert(_Playing); + if (_WaitingForPlay) + { + nlassert(!_Playing); // cannot already be playing if waiting for play + CAudioMixerUser *mixer = CAudioMixerUser::instance(); + mixer->removeSourceWaitingForPlay(this); + } + if (!_Playing) return; @@ -312,36 +330,13 @@ void CSimpleSource::setDirection(const NLMISC::CVector& dir) } } - -/* Set the gain (volume value inside [0 , 1]). (default: 1) - * 0.0 -> silence - * 0.5 -> -6dB - * 1.0 -> no attenuation - * values > 1 (amplification) not supported by most drivers - */ -void CSimpleSource::setGain(float gain) +void CSimpleSource::updateFinalGain() { - CSourceCommon::setGain(gain); - // Set the gain if (hasPhysicalSource()) - { - getPhysicalSource()->setGain(gain); - } + getPhysicalSource()->setGain(getFinalGain()); } -void CSimpleSource::setRelativeGain(float gain) -{ - CSourceCommon::setRelativeGain(gain); - - // Set the gain - if (hasPhysicalSource()) - { - getPhysicalSource()->setGain(_Gain); - } -} - - /* Shift the frequency. 1.0f equals identity, each reduction of 50% equals a pitch shift * of one octave. 0 is not a legal value. */ diff --git a/code/nel/src/sound/sound.cpp b/code/nel/src/sound/sound.cpp index eed318cca..b1b644ed2 100644 --- a/code/nel/src/sound/sound.cpp +++ b/code/nel/src/sound/sound.cpp @@ -26,6 +26,10 @@ #include "nel/sound/context_sound.h" #include "nel/sound/music_sound.h" #include "nel/sound/stream_sound.h" +#include "nel/sound/stream_file_sound.h" + +#include "nel/sound/group_controller.h" +#include "nel/sound/group_controller_root.h" using namespace std; using namespace NLMISC; @@ -81,6 +85,11 @@ CSound *CSound::createSound(const std::string &filename, NLGEORGES::UFormElm& fo ret = new CStreamSound(); ret->importForm(filename, formRoot); } + else if (dfnName == "stream_file_sound.dfn") + { + ret = new CStreamFileSound(); + ret->importForm(filename, formRoot); + } else { nlassertex(false, ("SoundType unsuported : %s", dfnName.c_str())); @@ -106,7 +115,8 @@ CSound::CSound() : _Looping(false), _MinDist(1.0f), _MaxDist(1000000.0f), - _UserVarControler(CStringMapper::emptyId()) + _UserVarControler(CStringMapper::emptyId()), + _GroupController(NULL) { } @@ -134,6 +144,23 @@ void CSound::serial(NLMISC::IStream &s) std::string name = CStringMapper::unmap(_Name); s.serial(name); } + + nlassert(CGroupControllerRoot::getInstance()); // not sure +#if NLSOUND_SHEET_VERSION_BUILT < 2 + if (s.isReading()) _GroupController = static_cast(CAudioMixerUser::instance()->getGroupController(NLSOUND_SHEET_V1_DEFAULT_SOUND_GROUP_CONTROLLER)); +#else + if (s.isReading()) + { + std::string groupControllerPath; + s.serial(groupControllerPath); + _GroupController = static_cast(CAudioMixerUser::instance()->getGroupController(groupControllerPath)); + } + else + { + std::string groupControllerPath = _GroupController->getPath(); + s.serial(groupControllerPath); + } +#endif } @@ -225,6 +252,16 @@ void CSound::importForm(const std::string& filename, NLGEORGES::UFormElm& roo default: _Priority = MidPri; } + + nlassert(CGroupControllerRoot::getInstance()); // not sure +#if NLSOUND_SHEET_VERSION_BUILT < 2 + _GroupController = CGroupControllerRoot::getInstance()->getGroupController(NLSOUND_SHEET_V1_DEFAULT_SOUND_GROUP_CONTROLLER); +#else + std::string groupControllerPath; + root.getValueByName(groupControllerPath, ".GroupControllerPath"); + _GroupController = CGroupControllerRoot::getInstance()->getGroupController(groupControllerPath); +#endif + } diff --git a/code/nel/src/sound/sound_bank.cpp b/code/nel/src/sound/sound_bank.cpp index b4031b571..dd2076a72 100644 --- a/code/nel/src/sound/sound_bank.cpp +++ b/code/nel/src/sound/sound_bank.cpp @@ -23,6 +23,7 @@ #include "nel/sound/background_sound.h" #include "nel/sound/music_sound.h" #include "nel/sound/stream_sound.h" +#include "nel/sound/stream_file_sound.h" #include "nel/georges/u_form_loader.h" #include "nel/georges/u_form_elm.h" @@ -194,6 +195,9 @@ public: case CSound::SOUND_STREAM: Sound = new CStreamSound(); break; + case CSound::SOUND_STREAM_FILE: + Sound = new CStreamFileSound(); + break; default: Sound = 0; } diff --git a/code/nel/src/sound/source_common.cpp b/code/nel/src/sound/source_common.cpp index ce2b7e210..7b27deb03 100644 --- a/code/nel/src/sound/source_common.cpp +++ b/code/nel/src/sound/source_common.cpp @@ -25,7 +25,7 @@ using namespace NLMISC; namespace NLSOUND { -CSourceCommon::CSourceCommon(TSoundId id, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster) +CSourceCommon::CSourceCommon(TSoundId id, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController) : _Priority(MidPri), _Playing(false), _Looping(false), @@ -41,9 +41,11 @@ CSourceCommon::CSourceCommon(TSoundId id, bool spawn, TSpawnEndCallback cb, void _SpawnEndCb(cb), _CbUserParam(cbUserParam), _Cluster(cluster), - _UserVarControler(id->getUserVarControler()) + _UserVarControler(id->getUserVarControler()), + _GroupController(groupController ? groupController : id->getGroupController()) { CAudioMixerUser::instance()->addSource(this); + _GroupController->addSource(this); // get a local copy of the sound parameter _InitialGain = _Gain = id->getGain(); @@ -51,11 +53,11 @@ CSourceCommon::CSourceCommon(TSoundId id, bool spawn, TSpawnEndCallback cb, void _Looping = id->getLooping(); _Priority = id->getPriority(); _Direction = id->getDirectionVector(); - } CSourceCommon::~CSourceCommon() { + _GroupController->removeSource(this); CAudioMixerUser::instance()->removeSource(this); } @@ -177,6 +179,7 @@ void CSourceCommon::setGain( float gain ) { clamp(gain, 0.0f, 1.0f); _InitialGain = _Gain = gain; + updateFinalGain(); } /* Set the gain amount (value inside [0, 1]) to map between 0 and the nominal gain @@ -185,8 +188,8 @@ void CSourceCommon::setGain( float gain ) void CSourceCommon::setRelativeGain( float gain ) { clamp(gain, 0.0f, 1.0f); - _Gain = _InitialGain * gain; + updateFinalGain(); } /* diff --git a/code/nel/src/sound/source_music_channel.cpp b/code/nel/src/sound/source_music_channel.cpp new file mode 100644 index 000000000..34735f113 --- /dev/null +++ b/code/nel/src/sound/source_music_channel.cpp @@ -0,0 +1,140 @@ +/** + * \file source_music_channel.cpp + * \brief CSourceMusicChannel + * \date 2012-04-11 16:08GMT + * \author Jan Boon (Kaetemi) + * CSourceMusicChannel + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#include "stdsound.h" +#include + +// STL includes + +// NeL includes +// #include +#include + +// Project includes + +using namespace std; +// using namespace NLMISC; + +namespace NLSOUND { + +CSourceMusicChannel::CSourceMusicChannel() : m_Source(NULL), m_Gain(1.0f) +{ + +} + +CSourceMusicChannel::~CSourceMusicChannel() +{ + nlassert(!m_Source); + delete m_Source; + m_Source = NULL; +} + +bool CSourceMusicChannel::play(const std::string &filepath, bool async, bool loop) +{ + // delete previous source if any + // note that this waits for the source's thread to finish if the source was still playing + if (m_Source) + delete m_Source; + + m_Sound.setMusicFilePath(filepath, async, loop); + + m_Source = new CStreamFileSource(&m_Sound, false, NULL, NULL, NULL, NULL); + m_Source->setSourceRelativeMode(true); + m_Source->setPos(NLMISC::CVector::Null); + m_Source->setRelativeGain(m_Gain); + + m_Source->play(); + + return m_Source->isPlaying(); +} + +void CSourceMusicChannel::stop() +{ + // stop but don't delete the source, deleting source may cause waiting for thread + if (m_Source) + m_Source->stop(); +} + +void CSourceMusicChannel::reset() +{ + // forces the source to be deleted, happens when audio mixer is reset + delete m_Source; + m_Source = NULL; +} + +void CSourceMusicChannel::pause() +{ + if (m_Source) + m_Source->pause(); +} + +void CSourceMusicChannel::resume() +{ + if (m_Source) + m_Source->resume(); +} + +bool CSourceMusicChannel::isEnded() +{ + if (m_Source) + { + if (m_Source->isEnded()) + { + // we can delete the source now without worrying about thread wait + delete m_Source; + m_Source = NULL; + return true; + } + return false; + } + return true; +} + +bool CSourceMusicChannel::isLoadingAsync() +{ + if (m_Source) + return m_Source->isLoadingAsync(); + return false; +} + +float CSourceMusicChannel::getLength() +{ + if (m_Source) + return m_Source->getLength(); + return 0.0f; +} + +void CSourceMusicChannel::setVolume(float gain) +{ + m_Gain = gain; + if (m_Source) + m_Source->setRelativeGain(gain); +} + +} /* namespace NLSOUND */ + +/* end of file */ diff --git a/code/nel/src/sound/stream_file_sound.cpp b/code/nel/src/sound/stream_file_sound.cpp new file mode 100644 index 000000000..44da5a31d --- /dev/null +++ b/code/nel/src/sound/stream_file_sound.cpp @@ -0,0 +1,98 @@ +/** + * \file stream_file_sound.cpp + * \brief CStreamFileSound + * \date 2012-04-11 09:57GMT + * \author Jan Boon (Kaetemi) + * CStreamFileSound + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#include "stdsound.h" +#include + +// STL includes + +// NeL includes +// #include +#include + +// Project includes + +using namespace std; +// using namespace NLMISC; + +namespace NLSOUND { + +CStreamFileSound::CStreamFileSound() +{ + +} + +CStreamFileSound::~CStreamFileSound() +{ + +} + +void CStreamFileSound::importForm(const std::string &filename, NLGEORGES::UFormElm &root) +{ + // Call the base class + CStreamSound::importForm(filename, root); + + // Async + root.getValueByName(m_Async, ".SoundType.Async"); + + // FilePath + root.getValueByName(m_FilePath, ".SoundType.FilePath"); +} + +void CStreamFileSound::serial(NLMISC::IStream &s) +{ + CStreamSound::serial(s); + + s.serial(m_Async); + s.serial(m_FilePath); +} + +void CStreamFileSound::setMusicFilePath(const std::string &filePath, bool async, bool loop) +{ +#if !FINAL_VERSION + _Name = NLMISC::CStringMapper::map(std::string(""); +#else + _Name = NLMISC::CStringMapper::map(""); +#endif + _ConeInnerAngle = NLMISC::Pi * 2; + _ConeOuterAngle = NLMISC::Pi * 2; + _Looping = loop; + _Gain = 1.0f; + _ConeOuterGain = 1.0f; + _Direction = NLMISC::CVector(0.f, 0.f, 0.f); + _Pitch = 1.0f; + _Priority = HighestPri; + _MaxDist = 9000.0f; + _MinDist = 1000.0f; + m_Async = async; + m_FilePath = filePath; + _GroupController = CGroupControllerRoot::getInstance()->getGroupController(NLSOUND_SHEET_V1_DEFAULT_SOUND_MUSIC_GROUP_CONTROLLER); +} + +} /* namespace NLSOUND */ + +/* end of file */ diff --git a/code/nel/src/sound/stream_file_source.cpp b/code/nel/src/sound/stream_file_source.cpp new file mode 100644 index 000000000..87164a9cd --- /dev/null +++ b/code/nel/src/sound/stream_file_source.cpp @@ -0,0 +1,384 @@ +/** + * \file stream_file_source.cpp + * \brief CStreamFileSource + * \date 2012-04-11 09:57GMT + * \author Jan Boon (Kaetemi) + * CStreamFileSource + */ + +/* + * Copyright (C) 2012 by authors + * + * This file is part of RYZOM CORE. + * RYZOM CORE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * RYZOM CORE is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with RYZOM CORE. If not, see + * . + */ + +#include "stdsound.h" +#include + +// STL includes + +// NeL includes +// #include + +// Project includes +#include +#include + +using namespace std; +// using namespace NLMISC; + +// #define NLSOUND_STREAM_FILE_DEBUG + +namespace NLSOUND { + +CStreamFileSource::CStreamFileSource(CStreamFileSound *streamFileSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController) +: CStreamSource(streamFileSound, spawn, cb, cbUserParam, cluster, groupController), m_AudioDecoder(NULL), m_Paused(false) +{ + m_Thread = NLMISC::IThread::create(this); +} + +CStreamFileSource::~CStreamFileSource() +{ + stop(); + m_Thread->wait(); // thread must have stopped for delete! + delete m_Thread; + m_Thread = NULL; + delete m_AudioDecoder; + m_AudioDecoder = NULL; +} + +void CStreamFileSource::play() +{ + // note: CStreamSource will assert crash if already physically playing! + + + if (m_WaitingForPlay) + { + if (m_Thread->isRunning()) + { + if (m_NextBuffer || !m_FreeBuffers) + { +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("play waiting, play stream %s", getStreamFileSound()->getFilePath().c_str()); +#endif + CStreamSource::play(); + if (!_Playing && !m_WaitingForPlay) + { + nldebug("Stream file source playback not possible or necessary for some reason"); + } + } + else + { +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("play waiting, hop onto waiting list %s", getStreamFileSound()->getFilePath().c_str()); +#endif + m_WaitingForPlay = true; + CAudioMixerUser *mixer = CAudioMixerUser::instance(); + mixer->addSourceWaitingForPlay(this); + } + } + else + { + // thread went kaboom while not started playing yet, probably the audiodecoder cannot be started + // don't play + m_WaitingForPlay = false; + } + } + else if (!_Playing) + { +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("play go %s", getStreamFileSound()->getFilePath().c_str()); +#endif + //if (!m_WaitingForPlay) + //{ + // thread may be stopping from stop call + m_Thread->wait(); + //} + //else + //{ + // nlwarning("Already waiting for play"); + //} + if (!getStreamFileSound()->getAsync()) + { + if (!prepareDecoder()) + { + return; + } + } + // else load audiodecoder in thread + m_WaitingForPlay = true; + m_Thread->start(); + m_Thread->setPriority(NLMISC::ThreadPriorityHighest); + if (!getStreamFileSound()->getAsync()) + { + // wait until at least one buffer is ready + while (!(m_NextBuffer || !m_FreeBuffers) && m_WaitingForPlay && m_Thread->isRunning()) + { +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("wait buffer"); +#endif + NLMISC::nlSleep(100); + } + if (m_WaitingForPlay && m_Thread->isRunning()) + { + CStreamSource::play(); + if (!_Playing) + { + nlwarning("Failed to synchronously start playing a file stream source. This happens when all physical tracks are in use. Use a Highest Priority sound"); + } + } + } + else + { + CAudioMixerUser *mixer = CAudioMixerUser::instance(); + mixer->addSourceWaitingForPlay(this); + } + } + else + { + nlwarning("Already playing"); + } + + + /*if (!m_WaitingForPlay) + { + m_WaitingForPlay = true; + + m_Thread->wait(); // thread must have stopped to restart it! + + m_Thread->start(); + m_Thread->setPriority(NLMISC::ThreadPriorityHighest); + } + + CStreamSource::play();*/ +} + +void CStreamFileSource::stop() +{ +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("stop %s", getStreamFileSound()->getFilePath().c_str()); +#endif + + CStreamSource::stopInt(); + +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("stopInt ok"); +#endif + + if (_Spawn) + { + if (_SpawnEndCb != NULL) + _SpawnEndCb(this, _CbUserParam); + m_Thread->wait(); + delete this; + } + +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("stop ok"); +#endif + + // thread will check _Playing to stop +} + +bool CStreamFileSource::isPlaying() +{ +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("isPlaying"); +#endif + + return m_Thread->isRunning(); +} + +void CStreamFileSource::pause() +{ +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("pause"); +#endif + + if (!m_Paused) + { + // thread checks for this to not delete the audio decoder + m_Paused = true; + + // stop the underlying system + CStreamSource::stop(); + + // thread will check _Playing to stop + } + else + { + nlwarning("Already paused"); + } +} + +void CStreamFileSource::resume() +{ +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("resume"); +#endif + + if (m_Paused) + { + m_Thread->wait(); // thread must have stopped to restart it! + + play(); + } + else + { + nlwarning("Not paused"); + } +} + +bool CStreamFileSource::isEnded() +{ + return (!m_Thread->isRunning() && !_Playing && !m_WaitingForPlay && !m_Paused); +} + +float CStreamFileSource::getLength() +{ + return m_AudioDecoder->getLength(); +} + +bool CStreamFileSource::isLoadingAsync() +{ + return m_WaitingForPlay; +} + +bool CStreamFileSource::prepareDecoder() +{ + // creates a new decoder or keeps going with the current decoder if the stream was paused + + if (m_Paused) + { + // handle paused! + m_Paused = false; + } + else if (m_AudioDecoder) // audio decoder should normally not exist when not paused and starting the thread + { + nlwarning("CAudioDecoder already exists, possible thread race bug with pause"); + delete m_AudioDecoder; + m_AudioDecoder = NULL; + } + if (!m_AudioDecoder) + { + // load the file + m_AudioDecoder = IAudioDecoder::createAudioDecoder(getStreamFileSound()->getFilePath(), getStreamFileSound()->getAsync(), getStreamFileSound()->getLooping()); + if (!m_AudioDecoder) + { + nlwarning("Failed to create IAudioDecoder, likely invalid format"); + return false; + } + this->setFormat(m_AudioDecoder->getChannels(), m_AudioDecoder->getBitsPerSample(), (uint32)m_AudioDecoder->getSamplesPerSec()); + } + uint samples, bytes; + this->getRecommendedBufferSize(samples, bytes); + this->preAllocate(bytes * 2); + + return true; +} + +inline bool CStreamFileSource::bufferMore(uint bytes) // buffer from bytes (minimum) to bytes * 2 (maximum) +{ + uint8 *buffer = this->lock(bytes * 2); + if (buffer) + { + uint32 result = m_AudioDecoder->getNextBytes(buffer, bytes, bytes * 2); + this->unlock(result); + return true; + } + return false; +} + +void CStreamFileSource::run() +{ +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("run %s", getStreamFileSound()->getFilePath().c_str()); + uint dumpI = 0; +#endif + + bool looping = _Looping; + if (getStreamFileSound()->getAsync()) + { + if (!prepareDecoder()) + return; + } + uint samples, bytes; + this->getRecommendedBufferSize(samples, bytes); + uint32 recSleep = 40; + uint32 doSleep = 10; + while (_Playing || m_WaitingForPlay) + { + if (!m_AudioDecoder->isMusicEnded()) + { +#ifdef NLSOUND_STREAM_FILE_DEBUG + ++dumpI; + if (!(dumpI % 100)) + { + nldebug("buffer %s %s %s", _Playing ? "PLAYING" : "NP", m_WaitingForPlay ? "WAITING" : "NW", getStreamFileSound()->getFilePath().c_str()); + nldebug("gain %f", hasPhysicalSource() ? getPhysicalSource()->getGain() : -1.0f); + } +#endif + + bool newLooping = _Looping; + if (looping != newLooping) + { + m_AudioDecoder->setLooping(looping); + looping = newLooping; + } + + // reduce sleeping time if nothing was buffered + if (bufferMore(bytes)) recSleep = doSleep = this->getRecommendedSleepTime(); + else doSleep = recSleep >> 2; // /4 + NLMISC::nlSleep(doSleep); + } + else + { + // wait until done playing buffers + while (this->hasFilledBuffersAvailable() && (_Playing || m_WaitingForPlay)) + { +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("music ended, wait until done %s", getStreamFileSound()->getFilePath().c_str()); +#endif + NLMISC::nlSleep(40); + } + // stop the physical source + // if (hasPhysicalSource()) + // getPhysicalSource()->stop(); + // the audio mixer will call stop on the logical source + break; + } + } + if (m_Paused) + { + // don't delete anything + } + else + { + delete m_AudioDecoder; + m_AudioDecoder = NULL; + } + // drop buffers + m_FreeBuffers = 3; + m_NextBuffer = 0; + +#ifdef NLSOUND_STREAM_FILE_DEBUG + nldebug("run end %s", getStreamFileSound()->getFilePath().c_str()); +#endif +} + +} /* namespace NLSOUND */ + +/* end of file */ diff --git a/code/nel/src/sound/stream_sound.cpp b/code/nel/src/sound/stream_sound.cpp index 347fae987..e16158d4d 100644 --- a/code/nel/src/sound/stream_sound.cpp +++ b/code/nel/src/sound/stream_sound.cpp @@ -17,6 +17,10 @@ #include "stdsound.h" #include "nel/sound/stream_sound.h" +#if NLSOUND_SHEET_VERSION_BUILT < 2 +# include "nel/sound/group_controller_root.h" +#endif + namespace NLSOUND { CStreamSound::CStreamSound() @@ -31,14 +35,15 @@ CStreamSound::~CStreamSound() void CStreamSound::importForm(const std::string &filename, NLGEORGES::UFormElm &root) { - NLGEORGES::UFormElm *psoundType; + // cannot do this debug check because used also by CStreamFileSound + /*NLGEORGES::UFormElm *psoundType; std::string dfnName; // some basic checking. root.getNodeByName(&psoundType, ".SoundType"); nlassert(psoundType != NULL); psoundType->getDfnName(dfnName); - nlassert(dfnName == "stream_sound.dfn"); + nlassert(dfnName == "stream_sound.dfn");*/ // Call the base class CSound::importForm(filename, root); @@ -51,6 +56,11 @@ void CStreamSound::importForm(const std::string &filename, NLGEORGES::UFormElm & // Alpha root.getValueByName(m_Alpha, ".SoundType.Alpha"); + +#if NLSOUND_SHEET_VERSION_BUILT < 2 + _GroupController = CGroupControllerRoot::getInstance()->getGroupController(NLSOUND_SHEET_V1_DEFAULT_SOUND_STREAM_GROUP_CONTROLLER); +#endif + } void CStreamSound::serial(NLMISC::IStream &s) @@ -59,6 +69,11 @@ void CStreamSound::serial(NLMISC::IStream &s) s.serial(_MinDist); s.serial(m_Alpha); + +#if NLSOUND_SHEET_VERSION_BUILT < 2 + if (s.isReading()) _GroupController = CGroupControllerRoot::getInstance()->getGroupController(NLSOUND_SHEET_V1_DEFAULT_SOUND_STREAM_GROUP_CONTROLLER); +#endif + } } /* namespace NLSOUND */ diff --git a/code/nel/src/sound/stream_source.cpp b/code/nel/src/sound/stream_source.cpp index a5b167c8f..221b0529e 100644 --- a/code/nel/src/sound/stream_source.cpp +++ b/code/nel/src/sound/stream_source.cpp @@ -26,29 +26,37 @@ // using namespace std; using namespace NLMISC; +// #define NLSOUND_DEBUG_STREAM + namespace NLSOUND { -CStreamSource::CStreamSource(CStreamSound *streamSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster) - : CSourceCommon(streamSound, spawn, cb, cbUserParam, cluster), +CStreamSource::CStreamSource(CStreamSound *streamSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController) + : CSourceCommon(streamSound, spawn, cb, cbUserParam, cluster, groupController), m_StreamSound(streamSound), m_Alpha(0.0f), m_Track(NULL), m_FreeBuffers(3), m_NextBuffer(0), m_LastSize(0), - m_BytesPerSecond(0) + m_BytesPerSecond(0), + m_WaitingForPlay(false), + m_PitchInv(1.0f) { nlassert(m_StreamSound != 0); // get a local copy of the stream sound parameter m_Alpha = m_StreamSound->getAlpha();//m_Buffers + m_PitchInv = 1.0f / _Pitch; // create the three buffer objects CAudioMixerUser *mixer = CAudioMixerUser::instance(); ISoundDriver *driver = mixer->getSoundDriver(); m_Buffers[0] = driver->createBuffer(); + m_Buffers[0]->setStorageMode(IBuffer::StorageSoftware); m_Buffers[1] = driver->createBuffer(); + m_Buffers[1]->setStorageMode(IBuffer::StorageSoftware); m_Buffers[2] = driver->createBuffer(); + m_Buffers[2]->setStorageMode(IBuffer::StorageSoftware); } CStreamSource::~CStreamSource() @@ -107,6 +115,8 @@ bool CStreamSource::isPlaying() /// Set looping on/off for future playbacks (default: off) void CStreamSource::setLooping(bool l) { + CSourceCommon::setLooping(l); + //CAutoMutex autoMutex(m_BufferMutex); // //CSourceCommon::setLooping(l); @@ -146,13 +156,16 @@ void CStreamSource::play() if ((_RelativeMode ? getPos().sqrnorm() : (mixer->getListenPosVector() - getPos()).sqrnorm()) > m_StreamSound->getMaxDistance() * m_StreamSound->getMaxDistance()) { // Source is too far to play + m_WaitingForPlay = false; if (_Spawn) { if (_SpawnEndCb != NULL) _SpawnEndCb(this, _CbUserParam); delete this; } - // nldebug("CStreamSource %p : play FAILED !", (CAudioMixerUser::IMixerEvent*)this); +#ifdef NLSOUND_DEBUG_STREAM + nldebug("CStreamSource %p : play FAILED, source is too far away !", (CAudioMixerUser::IMixerEvent*)this); +#endif return; } @@ -166,24 +179,33 @@ void CStreamSource::play() ISource *pSource = getPhysicalSource(); nlassert(pSource != NULL); - for (uint i = 0; i < m_NextBuffer; ++i) + uint nbS = m_NextBuffer; + if (!m_NextBuffer && !m_FreeBuffers) nbS = 3; + for (uint i = 0; i < nbS; ++i) pSource->submitStreamingBuffer(m_Buffers[i]); // pSource->setPos( _Position, false); pSource->setPos(getVirtualPos(), false); + pSource->setMinMaxDistances(m_StreamSound->getMinDistance(), m_StreamSound->getMaxDistance(), false); if (!m_Buffers[0]->isStereo()) { - pSource->setMinMaxDistances(m_StreamSound->getMinDistance(), m_StreamSound->getMaxDistance(), false); setDirection(_Direction); // because there is a workaround inside pSource->setVelocity(_Velocity); } - pSource->setGain(_Gain); + else + { + pSource->setDirection(NLMISC::CVector::I); + pSource->setCone(float(Pi * 2), float(Pi * 2), 1.0f); + pSource->setVelocity(NLMISC::CVector::Null); + } + pSource->setGain(getFinalGain()); pSource->setSourceRelativeMode(_RelativeMode); // pSource->setLooping(_Looping); pSource->setPitch(_Pitch); pSource->setAlpha(m_Alpha); // and play the sound + nlassert(nbS); // must have buffered already! play = pSource->play(); // nldebug("CStreamSource %p : REAL play done", (CAudioMixerUser::IMixerEvent*)this); } @@ -193,11 +215,13 @@ void CStreamSource::play() { // This sound is not discardable, add it in waiting playlist mixer->addSourceWaitingForPlay(this); + m_WaitingForPlay = true; return; } else { // No source available, kill. + m_WaitingForPlay = false; if (_Spawn) { if (_SpawnEndCb != NULL) @@ -209,22 +233,67 @@ void CStreamSource::play() } if (play) + { CSourceCommon::play(); + m_WaitingForPlay = false; +#ifdef NLSOUND_DEBUG_STREAM + // Dump source info + nlwarning("--- DUMP SOURCE INFO ---"); + nlwarning(" * getLooping: %s", getPhysicalSource()->getLooping() ? "YES" : "NO"); + nlwarning(" * isPlaying: %s", getPhysicalSource()->isPlaying() ? "YES" : "NO"); + nlwarning(" * isStopped: %s", getPhysicalSource()->isStopped() ? "YES" : "NO"); + nlwarning(" * isPaused: %s", getPhysicalSource()->isPaused() ? "YES" : "NO"); + nlwarning(" * getPos: %f, %f, %f", getPhysicalSource()->getPos().x, getPhysicalSource()->getPos().y, getPhysicalSource()->getPos().z); + NLMISC::CVector v; + getPhysicalSource()->getVelocity(v); + nlwarning(" * getVelocity: %f, %f, %f", v.x, v.y, v.z); + getPhysicalSource()->getDirection(v); + nlwarning(" * getDirection: %f, %f, %f", v.x, v.y, v.z); + nlwarning(" * getGain: %f", getPhysicalSource()->getGain()); + nlwarning(" * getPitch: %f", getPhysicalSource()->getPitch()); + nlwarning(" * getSourceRelativeMode: %s", getPhysicalSource()->getSourceRelativeMode() ? "YES" : "NO"); + float a, b, c; + getPhysicalSource()->getMinMaxDistances(a, b); + nlwarning(" * getMinMaxDistances: %f, %f", a, b); + getPhysicalSource()->getCone(a, b, c); + nlwarning(" * getCone: %f, %f", a, b, c); + nlwarning(" * getDirect: %s", getPhysicalSource()->getDirect() ? "YES" : "NO"); + nlwarning(" * getDirectGain: %f", getPhysicalSource()->getDirectGain()); + nlwarning(" * isDirectFilterEnabled: %s", getPhysicalSource()->isDirectFilterEnabled() ? "YES" : "NO"); + nlwarning(" * getEffect: %s", getPhysicalSource()->getEffect() ? "YES" : "NO"); + nlwarning(" * getEffectGain: %f", getPhysicalSource()->getEffectGain()); + nlwarning(" * isEffectFilterEnabled: %s", getPhysicalSource()->isEffectFilterEnabled() ? "YES" : "NO"); +#endif + } } +#ifdef NL_DEBUG nlassert(play); +#else + if (!play) + nlwarning("Failed to play physical sound source. This is a serious error"); +#endif } -/// Stop playing -void CStreamSource::stop() +void CStreamSource::stopInt() { CAutoMutex autoMutex(m_BufferMutex); // nldebug("CStreamSource %p : stop", (CAudioMixerUser::IMixerEvent*)this); // nlassert(_Playing); + + if (m_WaitingForPlay) + { + nlassert(!_Playing); // cannot already be playing if waiting for play + CAudioMixerUser *mixer = CAudioMixerUser::instance(); + mixer->removeSourceWaitingForPlay(this); + } if (!_Playing) + { + m_WaitingForPlay = false; return; + } if (hasPhysicalSource()) releasePhysicalSource(); @@ -234,6 +303,14 @@ void CStreamSource::stop() m_FreeBuffers = 3; m_NextBuffer = 0; + m_WaitingForPlay = false; +} + +/// Stop playing +void CStreamSource::stop() +{ + stopInt(); + if (_Spawn) { if (_SpawnEndCb != NULL) @@ -294,28 +371,18 @@ void CStreamSource::setDirection(const NLMISC::CVector& dir) } } -void CStreamSource::setGain(float gain) +void CStreamSource::updateFinalGain() { CAutoMutex autoMutex(m_BufferMutex); - - CSourceCommon::setGain(gain); + if (hasPhysicalSource()) - getPhysicalSource()->setGain(gain); -} - -void CStreamSource::setRelativeGain(float gain) -{ - CAutoMutex autoMutex(m_BufferMutex); - - CSourceCommon::setRelativeGain(gain); - if (hasPhysicalSource()) - getPhysicalSource()->setGain(_Gain); + getPhysicalSource()->setGain(getFinalGain()); } void CStreamSource::setPitch(float pitch) { CAutoMutex autoMutex(m_BufferMutex); - + m_PitchInv = 1.0f / pitch; CSourceCommon::setPitch(pitch); if (hasPhysicalSource()) getPhysicalSource()->setPitch(pitch); @@ -382,7 +449,9 @@ bool CStreamSource::unlock(uint size) ++m_NextBuffer; m_NextBuffer %= 3; --m_FreeBuffers; if (hasPhysicalSource()) + { getPhysicalSource()->submitStreamingBuffer(buffer); + } m_LastSize = size; } @@ -406,8 +475,8 @@ void CStreamSource::getRecommendedBufferSize(uint &samples, uint &bytes) const uint32 CStreamSource::getRecommendedSleepTime() const { if (m_FreeBuffers > 0) return 0; - uint32 sleepTime = (uint32)((1000.0f * ((float)m_LastSize) / (float)m_BytesPerSecond) / _Pitch); - clamp(sleepTime, (uint32)0, (uint32)1000); + uint32 sleepTime = (uint32)((1000.0f * ((float)m_LastSize) / (float)m_BytesPerSecond) * m_PitchInv); + clamp(sleepTime, (uint32)0, (uint32)80); return sleepTime; } @@ -418,6 +487,19 @@ bool CStreamSource::hasFilledBuffersAvailable() const return m_FreeBuffers < 3; } +void CStreamSource::preAllocate(uint capacity) +{ + uint8 *b0 = m_Buffers[0]->lock(capacity); + memset(b0, 0, capacity); + m_Buffers[0]->unlock(capacity); + uint8 *b1 = m_Buffers[1]->lock(capacity); + memset(b1, 0, capacity); + m_Buffers[1]->unlock(capacity); + uint8 *b2 = m_Buffers[2]->lock(capacity); + memset(b2, 0, capacity); + m_Buffers[2]->unlock(capacity); +} + } /* namespace NLSOUND */ /* end of file */ diff --git a/code/nel/tools/3d/Makefile.am b/code/nel/tools/3d/Makefile.am deleted file mode 100644 index 7cde91464..000000000 --- a/code/nel/tools/3d/Makefile.am +++ /dev/null @@ -1,32 +0,0 @@ -# -# $Id: Makefile.am,v 1.2 2002/05/14 13:33:59 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -DIST_SUBDIRS = build_coarse_mesh \ - build_far_bank \ - build_smallbank \ - ig_lighter_lib \ - ig_lighter \ - panoply_maker \ - zone_lib \ - zone_dependencies \ - zone_ig_lighter \ - zone_lighter \ - zone_welder - -SUBDIRS = build_coarse_mesh \ - build_far_bank \ - build_smallbank \ - ig_lighter_lib \ - ig_lighter \ - zone_lib \ - zone_dependencies \ - zone_ig_lighter \ - zone_lighter \ - zone_welder - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/build_coarse_mesh/Makefile.am b/code/nel/tools/3d/build_coarse_mesh/Makefile.am deleted file mode 100644 index b161e1b59..000000000 --- a/code/nel/tools/3d/build_coarse_mesh/Makefile.am +++ /dev/null @@ -1,21 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = build.cfg \ - build_coarse_mesh.dsp - -bin_PROGRAMS = build_coarse_mesh - -build_coarse_mesh_SOURCES = build_coarse_mesh.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -build_coarse_mesh_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/build_far_bank/Makefile.am b/code/nel/tools/3d/build_far_bank/Makefile.am deleted file mode 100644 index 9dd0eadec..000000000 --- a/code/nel/tools/3d/build_far_bank/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = build_far_bank.dsp - -bin_PROGRAMS = build_far_bank - -build_far_bank_SOURCES = build_far_bank.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -build_far_bank_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/build_smallbank/Makefile.am b/code/nel/tools/3d/build_smallbank/Makefile.am deleted file mode 100644 index f70258cb6..000000000 --- a/code/nel/tools/3d/build_smallbank/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = build_smallbank.dsp - -bin_PROGRAMS = build_smallbank - -build_smallbank_SOURCES = build_smallbank.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -build_smallbank_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/ig_lighter/Makefile.am b/code/nel/tools/3d/ig_lighter/Makefile.am deleted file mode 100644 index ccde76fd0..000000000 --- a/code/nel/tools/3d/ig_lighter/Makefile.am +++ /dev/null @@ -1,23 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = config.cfg \ - ig_lighter.dsp - -bin_PROGRAMS = ig_lighter - -ig_lighter_SOURCES = ig_lighter.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -ig_lighter_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la \ - ../../../src/pacs/libnelpacs.la \ - ../ig_lighter_lib/libig_lighter.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/ig_lighter_lib/Makefile.am b/code/nel/tools/3d/ig_lighter_lib/Makefile.am deleted file mode 100644 index e66b9a94b..000000000 --- a/code/nel/tools/3d/ig_lighter_lib/Makefile.am +++ /dev/null @@ -1,23 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = ig_lighter_lib.dsp - -noinst_LTLIBRARIES = libig_lighter.la - -libig_lighter_la_SOURCES = ig_lighter_lib.cpp - -noinst_HEADERS = ig_lighter_lib.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -libig_lighter_la_LIBADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la \ - ../../../src/pacs/libnelpacs.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/ligo/plugin_max/script.cpp b/code/nel/tools/3d/ligo/plugin_max/script.cpp index 90050304f..b00de5fe4 100644 --- a/code/nel/tools/3d/ligo/plugin_max/script.cpp +++ b/code/nel/tools/3d/ligo/plugin_max/script.cpp @@ -29,7 +29,7 @@ # include # include # include -# include +# include #else # include # include diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/CMakeLists.txt b/code/nel/tools/3d/object_viewer_qt/src/plugins/CMakeLists.txt index 426d3aa61..467b00876 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/CMakeLists.txt +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/CMakeLists.txt @@ -7,6 +7,7 @@ ADD_SUBDIRECTORY(disp_sheet_id) ADD_SUBDIRECTORY(object_viewer) ADD_SUBDIRECTORY(georges_editor) ADD_SUBDIRECTORY(translation_manager) +ADD_SUBDIRECTORY(bnp_manager) # Note: Temporarily disabled until development continues. #ADD_SUBDIRECTORY(zone_painter) # Ryzom Specific Plugins diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/CMakeLists.txt b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/CMakeLists.txt new file mode 100644 index 000000000..7ecfd7396 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/CMakeLists.txt @@ -0,0 +1,47 @@ +INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIBXML2_INCLUDE_DIR} + ${QT_INCLUDES}) + +FILE(GLOB SRC *.cpp *.h) +SET(OVQT_EXT_SYS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/../../extension_system/iplugin.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../extension_system/iplugin_manager.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../extension_system/iplugin_spec.h) + +SET(OVQT_PLUG_BNP_MANAGER_HDR bnp_manager_plugin.h + bnp_manager_window.h + bnp_dirtree_dialog.h + bnp_filesystem_model.h + bnp_file.h + bnp_filelist_dialog.h + bnp_proxy_model.h + ) +SET(OVQT_PLUG_BNP_MANAGER_UIS bnp_dirtree_form.ui + bnp_filelist_dialog.ui + ) + +SET(OVQT_PLUGIN_BNP_MANAGER_RCS bnp_manager.qrc) + +SET(QT_USE_QTGUI TRUE) + +QT4_ADD_RESOURCES(OVQT_PLUGIN_BNP_MANAGER_RC_SRCS ${OVQT_PLUGIN_BNP_MANAGER_RCS}) +QT4_WRAP_CPP(OVQT_PLUG_BNP_MANAGER_MOC_SRC ${OVQT_PLUG_BNP_MANAGER_HDR}) +QT4_WRAP_UI(OVQT_PLUG_BNP_MANAGER_UI_HDRS ${OVQT_PLUG_BNP_MANAGER_UIS}) + +SOURCE_GROUP(QtResources FILES ${OVQT_PLUG_BNP_MANAGER_UIS} ${OVQT_PLUGIN_BNP_MANAGER_RCS}) +SOURCE_GROUP(QtGeneratedUiHdr FILES ${OVQT_PLUG_BNP_MANAGER_UI_HDRS}) +SOURCE_GROUP(QtGeneratedMocSrc FILES ${OVQT_PLUG_BNP_MANAGER_MOC_SRC}) +SOURCE_GROUP("BNP Manager Plugin" FILES ${SRC}) +SOURCE_GROUP("OVQT Extension System" FILES ${OVQT_EXT_SYS_SRC}) + +ADD_LIBRARY(ovqt_plugin_bnp_manager MODULE ${SRC} ${OVQT_PLUG_BNP_MANAGER_MOC_SRC} ${OVQT_EXT_SYS_SRC} ${OVQT_PLUGIN_BNP_MANAGER_RC_SRCS} ${OVQT_PLUG_BNP_MANAGER_UI_HDRS}) + +TARGET_LINK_LIBRARIES(ovqt_plugin_bnp_manager ovqt_plugin_core nelmisc nelgeorges ${QT_LIBRARIES}) + +NL_DEFAULT_PROPS(ovqt_plugin_bnp_manager "NeL, Tools, 3D: Object Viewer Qt Plugin: BNP Manager") +NL_ADD_RUNTIME_FLAGS(ovqt_plugin_bnp_manager) +NL_ADD_LIB_SUFFIX(ovqt_plugin_bnp_manager) + +ADD_DEFINITIONS(${LIBXML2_DEFINITIONS} -DQT_PLUGIN -DQT_SHARED ${QT_DEFINITIONS}) + +INSTALL(TARGETS ovqt_plugin_bnp_manager LIBRARY DESTINATION lib RUNTIME DESTINATION bin ARCHIVE DESTINATION lib COMPONENT tools3d) diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_dialog.cpp new file mode 100644 index 000000000..a19f4550e --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_dialog.cpp @@ -0,0 +1,93 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Project includes +#include "bnp_dirtree_dialog.h" +#include "bnp_filesystem_model.h" +#include "bnp_proxy_model.h" + +// Qt includes +#include + +// NeL includes +#include + +namespace BNPManager +{ + +CBnpDirTreeDialog::CBnpDirTreeDialog(QString bnpPath, QWidget *parent) + : QDockWidget(parent), + m_DataPath(bnpPath) +{ + // Setup the dialog + m_ui.setupUi(this); + + // Filter settings to only display files with bnp extension. + // Could be changed to display all files and react according to the extension: + // Bnp file: opened and displayed + // all other files: added to the currently opened bnp file + QStringList filter; + filter << tr("*.bnp"); + + // Setup the directory tree model + m_dirModel= new BNPFileSystemModel(); + m_proxyModel = new BNPSortProxyModel(); + m_ui.dirTree->setSortingEnabled(true); + m_dirModel->setRootPath(m_DataPath); + m_dirModel->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::AllEntries); + m_dirModel->setNameFilters(filter); + m_dirModel->setNameFilterDisables(0); + + m_proxyModel->setSourceModel(m_dirModel); + + m_ui.dirTree->setModel(m_proxyModel); + + m_ui.dirTree->setRootIndex( m_proxyModel->mapFromSource (m_dirModel->index(m_DataPath) ) ); + + // Trigger if one filename is activated + // In future drag&drop should be also possible + connect(m_ui.dirTree, SIGNAL(activated(QModelIndex)), + this, SLOT(fileSelected(QModelIndex))); +} +// *************************************************************************** +CBnpDirTreeDialog::~CBnpDirTreeDialog() +{ + +} +// *************************************************************************** +void CBnpDirTreeDialog::fileSelected(QModelIndex index) +{ + QModelIndex source = m_proxyModel->mapToSource(index); + if (source.isValid() && !m_dirModel->isDir(source)) + { + // emit the according signal to BNPManagerWindow class + Q_EMIT selectedFile(m_dirModel->fileInfo(source).filePath()); + } +} +// *************************************************************************** +void CBnpDirTreeDialog::changeFile(QString file) +{ + +} +// *************************************************************************** +void CBnpDirTreeDialog::BnpPathChanged(QString path) +{ + +} +// *************************************************************************** +} + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_dialog.h new file mode 100644 index 000000000..737085185 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_dialog.h @@ -0,0 +1,83 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef BNP_DIRTREE_DIALOG_H +#define BNP_DIRTREE_DIALOG_H + +// Qt includes +#include + +// STL includes + +// NeL includes + +// Project includes +#include "ui_bnp_dirtree_form.h" + +namespace BNPManager +{ + +class BNPFileSystemModel; +class BNPSortProxyModel; + +class CBnpDirTreeDialog : public QDockWidget +{ + Q_OBJECT +public: + + /** + * Constructor + * \param path to root directory, which should be displayed + */ + CBnpDirTreeDialog(QString bnpPath, QWidget *parent = 0); + + /** + * Destructor + */ + ~CBnpDirTreeDialog(); + + /** + * Change the root path for the dir tree view + * \param data path to the new directory + */ + void BnpPathChanged(QString); + +private: + + Ui::CBnpDirTreeDialog m_ui; + + // path ro data root directory + QString m_DataPath; + + BNPFileSystemModel *m_dirModel; + + BNPSortProxyModel *m_proxyModel; + +Q_SIGNALS: + void selectedFile(const QString); + +private Q_SLOTS: + /** + * Triggered if the user activates (double klick on windows) + * a file name in the dir tree view + * \param selected ModelIndex (filename) + */ + void fileSelected(QModelIndex index); + + void changeFile(QString file); +}; +} +#endif \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_form.ui b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_form.ui new file mode 100644 index 000000000..44b39dc54 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_dirtree_form.ui @@ -0,0 +1,61 @@ + + + CBnpDirTreeDialog + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + + 200 + 141 + + + + QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable + + + BNP Datapath + + + + + 50 + 0 + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_file.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_file.cpp new file mode 100644 index 000000000..e00f777ba --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_file.cpp @@ -0,0 +1,324 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Project includes +#include "bnp_file.h" + +// Nel includes +#include +#include +#include +#include +#include + +// Qt includes + +using namespace NLMISC; +using namespace std; + + +namespace BNPManager +{ + +PackedFile::PackedFile() +{ + m_size = 0; + m_pos = 0; +} + +NLMISC_SAFE_SINGLETON_IMPL(BNPFileHandle); + +BNPFileHandle::BNPFileHandle() +{ + m_offsetFromBeginning = 0; +} +// *************************************************************************** +BNPFileHandle::~BNPFileHandle() +{ + // Erase the list + m_packedFiles.clear(); +} +// *************************************************************************** +void BNPFileHandle::releaseInstance() +{ + if (_Instance) + { + NLMISC::INelContext::getInstance().releaseSingletonPointer("BNPFileHandle", _Instance); + delete _Instance; + _Instance = NULL; + } +} +// *************************************************************************** +void BNPFileHandle::createFile(string filePath) +{ + // Only set the filepath. Header will be created after files have been added + m_openedBNPFile = filePath; + m_packedFiles.clear(); + + nlinfo("Created file %s.", filePath.c_str() ); +} +// *************************************************************************** +bool BNPFileHandle::unpack(const string &dirName, const vector& fileList) +{ + CIFile bnp; + bnp.open(m_openedBNPFile); + + TPackedFilesList::iterator it_files = m_packedFiles.begin(); + + for (it_files; it_files != m_packedFiles.end(); it_files++) + { + // Check if the file should be unpacked or not + if (find(fileList.begin(), fileList.end(), it_files->m_name) != fileList.end()) + { + string filename = dirName + "/" + it_files->m_name; + + COFile out; + if ( out.open(filename) ) + { + bnp.seek(it_files->m_pos, IStream::begin); + uint8 *ptr = new uint8[it_files->m_size]; + bnp.serialBuffer(ptr,it_files->m_size); + out.serialBuffer(ptr,it_files->m_size); + delete [] ptr; + } + out.close(); + } + } + + bnp.close(); + return true; +} +// *************************************************************************** +// Read the header from a big file +bool BNPFileHandle::readHeader(const std::string &filePath) +{ + m_packedFiles.clear(); + + m_openedBNPFile = filePath; + + CIFile bnp; + bnp.open (filePath); + + bnp.seek(0, IStream::end); + uint32 nFileSize = bnp.getFileSize(); + bnp.seek(nFileSize-sizeof(uint32), IStream::begin); + + uint32 nOffsetFromBegining; + + bnp.serial(nOffsetFromBegining); + + if ( !bnp.seek (nOffsetFromBegining, IStream::begin) ) + { + nlwarning("Could not read offset from begining"); + bnp.close(); + return false; + } + + uint32 nNbFile; + bnp.serial(nNbFile); + + for (uint32 i = 0; i < nNbFile; ++i) + { + uint8 nStringSize; + char sName[256]; + + bnp.serial(nStringSize); + bnp.serialBuffer( (uint8*)sName, nStringSize); + sName[nStringSize] = 0; + + PackedFile tmpPackedFile; + tmpPackedFile.m_name = sName; + tmpPackedFile.m_path = m_openedBNPFile; + + bnp.serial(tmpPackedFile.m_size); + bnp.serial(tmpPackedFile.m_pos); + + m_packedFiles.push_back (tmpPackedFile); + } + + bnp.close(); + return true; +} +// *************************************************************************** +void BNPFileHandle::list(TPackedFilesList& FileList) +{ + PackedFile tmpFile; + TPackedFilesList::iterator it = m_packedFiles.begin(); + while (it != m_packedFiles.end() ) + { + tmpFile.m_name = it->m_name; + tmpFile.m_pos = it->m_pos; + tmpFile.m_size = it->m_size; + tmpFile.m_path = it->m_path; + FileList.push_back(tmpFile); + it++; + } +} +// *************************************************************************** +bool BNPFileHandle::writeHeader( const std::string &filePath, uint32 offset ) +{ + COFile bnp; + bnp.open(filePath, true); + if ( !bnp.isOpen() ) + return false; + + uint32 nNbFile = (uint32)m_packedFiles.size(); + bnp.serial(nNbFile); + + for (uint32 i = 0; i < nNbFile; ++i) + { + uint8 nStringSize = (uint8)m_packedFiles[i].m_name.size(); + bnp.serial( nStringSize ); + bnp.serialBuffer( (uint8*)m_packedFiles[i].m_name.c_str(), nStringSize ); + bnp.serial(m_packedFiles[i].m_size); + bnp.serial(m_packedFiles[i].m_pos); + } + + bnp.serial(offset); + + bnp.close(); + + return true; +} +// *************************************************************************** +void BNPFileHandle::fileNames(std::vector &fileNames) +{ + TPackedFilesList::iterator it = m_packedFiles.begin(); + while (it != m_packedFiles.end() ) + { + fileNames.push_back(it->m_name); + it++; + } +} +// *************************************************************************** +void BNPFileHandle::addFiles( const vector &filePathes) +{ + uint32 OffsetFromBegining = 0; + + // create packed files and add them to the private vector + vector::const_iterator it_vec = filePathes.begin(); + while (it_vec != filePathes.end() ) + { + PackedFile tmpFile; + tmpFile.m_name = CFile::getFilename (*it_vec); + // Leave position to 0 and set the value during the new bnp file is creating + // We need the position only for the header at the end + tmpFile.m_pos = 0; + tmpFile.m_size = CFile::getFileSize(*it_vec); + tmpFile.m_path = *it_vec; + m_packedFiles.push_back( tmpFile ); + + it_vec++; + } + + // sort packed files alphabetic + std::sort ( m_packedFiles.begin(), m_packedFiles.end(), compare ); + + // create a new temporary bnp file with extension *.tmp + TPackedFilesList::iterator it_packed = m_packedFiles.begin(); + while (it_packed != m_packedFiles.end() ) + { + append(m_openedBNPFile + ".tmp", *it_packed); + // Set now the new offset for the new header + it_packed->m_pos = OffsetFromBegining; + OffsetFromBegining += it_packed->m_size; + + it_packed++; + } + + writeHeader(m_openedBNPFile + ".tmp", OffsetFromBegining); + + // Delete any previous existing file + if (CFile::fileExists( m_openedBNPFile )) + CFile::deleteFile( m_openedBNPFile ); + string src = m_openedBNPFile + ".tmp"; + CFile::moveFile( m_openedBNPFile.c_str(), src.c_str() ); +} +// *************************************************************************** +void BNPFileHandle::deleteFiles( const vector& fileNames) +{ + vector::const_iterator it_vec; + TPackedFilesList::iterator it_packed; + uint32 OffsetFromBegining = 0; + string tmpFile = m_openedBNPFile + ".tmp"; + + // create a new temporary bnp file with extension *.tmp + it_packed = m_packedFiles.begin(); + while (it_packed != m_packedFiles.end() ) + { + // check each packed file if it should be deleted + it_vec = find (fileNames.begin(), fileNames.end(), it_packed->m_name ); + if ( it_vec != fileNames.end() ) + { + nlinfo("Deleting file %s.", it_packed->m_name.c_str() ); + it_packed = m_packedFiles.erase(it_packed); + } + else + { + append(tmpFile, *it_packed); + // Set now the new offset for the new header + it_packed->m_pos = OffsetFromBegining; + OffsetFromBegining += it_packed->m_size; + + it_packed++; + } + } + + writeHeader(tmpFile, OffsetFromBegining); + + CFile::deleteFile( m_openedBNPFile ); + string src = m_openedBNPFile + ".tmp"; + CFile::moveFile( m_openedBNPFile.c_str(), src.c_str() ); +} +// *************************************************************************** +void BNPFileHandle::append(const string &destination, const PackedFile &source) +{ + // check if the file exists and create one if not + if ( !CFile::fileExists(destination) ) + CFile::createEmptyFile( destination ); + + COFile bnpfile; + CIFile packedfile; + bnpfile.open(destination, true); + packedfile.open(source.m_path); + if ( !bnpfile.isOpen() ) return; + + + uint8 *ptr = new uint8[source.m_size]; + + // check if the source is a bnp file. + if ( nlstricmp( CFile::getExtension(source.m_path), "bnp" ) == 0 ) + { + // Jump to the file position inside the bnp + packedfile.seek(source.m_pos, IStream::begin); + } + // Read the source + packedfile.serialBuffer(ptr, source.m_size); + + // Append the data to the destination + bnpfile.serialBuffer(ptr, source.m_size); + + delete [] ptr; + + packedfile.close(); + bnpfile.close(); +} +// *************************************************************************** +bool BNPFileHandle::compare(const PackedFile &left, const PackedFile &right) +{ + return nlstricmp (left.m_name.c_str(), right.m_name.c_str()) < 0; +} +} // namespace BNPManager \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_file.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_file.h new file mode 100644 index 000000000..7695ebc2f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_file.h @@ -0,0 +1,145 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef BNP_FILE_H +#define BNP_FILE_H + +// Project includes + +// Nel includes +#include "nel/misc/types_nl.h" +#include + +// Qt includes +#include + + +namespace BNPManager +{ + +struct PackedFile +{ + PackedFile(); + std::string m_name; + uint32 m_size; + uint32 m_pos; + std::string m_path; +}; + +typedef std::vector TPackedFilesList; + +class BNPFileHandle +{ + NLMISC_SAFE_SINGLETON_DECL(BNPFileHandle) + + /** + * Private constructor + */ + BNPFileHandle(); + + /** + * Private destructor + */ + ~BNPFileHandle(); + +public: + // release memory + static void releaseInstance(); + + /*void append (const QString destFilename, const QString origFilename, uint32 sizeToRead); + void packRecurse();*/ + + /** + * Read the header from the bnp file and create a filelist + * \param filename (consisting the whole path) + */ + bool readHeader (const std::string &filePath); + + bool writeHeader (const std::string &filePath, uint32 offset); + + /** + * Append the header to a created bnp file + * \param filename (consisting the whole path) + */ + void appendHeader (const std::string &filename) {} + + /** + * Create a vector of all packed files inside the bnp file + * \param reference to the vector, which has to be filled + */ + void list (TPackedFilesList& FileList); + + /** + * Create a vector of all file names inside the bnp file + * \param reference to the vector, which has to be filled + */ + void fileNames( std::vector& fileNames ); + + /** + * Create a new bnp file + * \param string file path + */ + void createFile( std::string filePath ); + + /** + * Add files to the current aktive bnp file + * \param vector of file pathes to add + */ + void addFiles( const std::vector& filePathes ); + + /** + * Delete files from the current aktive bnp file + * \param vector of files names + */ + void deleteFiles (const std::vector& fileNames); + + /** + * Unpack the selected packed files into user defined dir + * \param directory path, where the files should be unpacked + * \param list of files, which has to be unpacked + */ + bool unpack (const std::string &dirName, const std::vector& fileList); + + /** + * Compares two filenames + * \param left: left packed file + * \param right: right packed file + * \return: TODO + */ + static bool compare(const PackedFile &left, const PackedFile &right); + +private: + + /** + * Append one file to an existing bnp file + * \param destination: the active bnp file to append the file + * \param source: the source file to pack + */ + void append( const std::string& destination, const PackedFile& source ); + + TPackedFilesList m_packedFiles; + + // currently opened and displayed bnp file + std::string m_openedBNPFile; + + // offset where the header of the bnp file begins + uint32 m_offsetFromBeginning; + +}; + +} + +#endif \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.cpp new file mode 100644 index 000000000..261175902 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.cpp @@ -0,0 +1,132 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Project includes +#include "bnp_filelist_dialog.h" +#include "bnp_file.h" + +// Qt includes +#include + +// NeL includes +#include + +using namespace std; + +namespace BNPManager +{ + +BnpFileListDialog::BnpFileListDialog(QString bnpPath, QWidget *parent) + : QDockWidget(parent), + m_DataPath(bnpPath) +{ + m_ui.setupUi(this); +} +// *************************************************************************** +BnpFileListDialog::~BnpFileListDialog() +{ + +} +// *************************************************************************** +void BnpFileListDialog::setupTable(int nbrows) +{ + // delete all old entries + m_ui.tableWidget->clear(); + + // set 2 colums: filename and size + m_ui.tableWidget->setColumnCount(2); + + // set number of rows according to the number of files in the bnp file + m_ui.tableWidget->setRowCount(nbrows); + + // only entire rows can be selected + m_ui.tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + + // set the horizontal headers + QStringList labels; + labels << tr("Filename") << tr("Size"); + m_ui.tableWidget->setHorizontalHeaderLabels(labels); + + m_ui.tableWidget->horizontalHeader()->setResizeMode(0, QHeaderView::Interactive); + m_ui.tableWidget->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch ); + m_ui.tableWidget->verticalHeader()->hide(); + + // set vertical size a little bit smaller + m_ui.tableWidget->verticalHeader()->setDefaultSectionSize(15); + m_ui.tableWidget->setShowGrid(false); + m_ui.tableWidget->setObjectName("tablewidget"); +} +// *************************************************************************** +bool BnpFileListDialog::loadTable(const QString filePath) +{ + // reference to the BNPFileHandle singletone instance + BNPFileHandle& myBNPFileHandle = BNPFileHandle::getInstance(); + // string vector of all packed files inside a bnp + TPackedFilesList filelist; + int row = 0; + + // read the header from the bnp file + if (!myBNPFileHandle.readHeader( filePath.toStdString()) ) + { + return false; + } + myBNPFileHandle.list( filelist ); + + // create table with number of rows + setupTable(filelist.size()); + + // fill the table items + TPackedFilesList::iterator it = filelist.begin(); + while (it != filelist.end() ) + { + QTableWidgetItem *nameItem = new QTableWidgetItem (it->m_name.c_str() ); + QTableWidgetItem *sizeItem = new QTableWidgetItem (tr("%1 KB").arg(it->m_size)); + m_ui.tableWidget->setItem(row, 0, nameItem); + m_ui.tableWidget->setItem(row, 1, sizeItem); + it++; + row++; + } + + // Set the file path as the widgets title + setWindowTitle(filePath); + + return true; +} +// *************************************************************************** +void BnpFileListDialog::clearTable() +{ + // create emtpy table + setupTable(0); + + setWindowTitle("BNP File List"); +} +// *************************************************************************** +void BnpFileListDialog::getSelections(TSelectionList& SelectionList) +{ + QModelIndex index; + QAbstractItemModel *model = m_ui.tableWidget->model(); + QItemSelectionModel *selection = m_ui.tableWidget->selectionModel(); + QModelIndexList indexes = selection->selectedRows(); + + Q_FOREACH(index, indexes) + { + QVariant data = model->data(index); + QString filename = data.toString(); + SelectionList.push_back( filename.toStdString() ); + } +} + +} // namespace BNPManager \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.h new file mode 100644 index 000000000..dbf007fc2 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.h @@ -0,0 +1,85 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef BNP_FILELIST_DIALOG_H +#define BNP_FILELIST_DIALOG_H + +// Qt includes +#include + +// STL includes +#include +#include + +// NeL includes + +// Project includes +#include "ui_bnp_filelist_dialog.h" + +namespace BNPManager +{ + +typedef std::vector TSelectionList; + +class BnpFileListDialog : public QDockWidget +{ + Q_OBJECT + +public: + + // Constructor + BnpFileListDialog(QString bnpPath, QWidget *parent = 0); + + // Destructor + ~BnpFileListDialog(); + + /** + * Load the bnp file and setup the table view + * \param Filename + * \return true if everything went well + */ + bool loadTable(const QString filePath); + + /** + * Set the dimension of the table + * \param number of rows + */ + void setupTable(int nbrows); + + /** + * When BNP files is closed, clear the filelist table + */ + void clearTable(); + + /** + * Fill the files selected in the table view to + * unpack them. + * \param reference to a vector of filenames. + * \return true if everything went well + */ + void getSelections(TSelectionList& SelectionList); + +private: + Ui::BnpFileListDialog m_ui; + + // common data path as root folder for the dirtree view + QString m_DataPath; + +}; + +} + +#endif diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.ui b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.ui new file mode 100644 index 000000000..9f62ff7c0 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filelist_dialog.ui @@ -0,0 +1,70 @@ + + + BnpFileListDialog + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + + 200 + 141 + + + + true + + + QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable + + + BNP File List + + + + + 50 + 0 + + + + + + + true + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + false + + + 15 + + + + + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filesystem_model.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filesystem_model.cpp new file mode 100644 index 000000000..75ef031ff --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filesystem_model.cpp @@ -0,0 +1,51 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +#include "bnp_filesystem_model.h" + +#include +#include + +namespace BNPManager +{ + +BNPFileSystemModel::BNPFileSystemModel(QObject *parent) + : QFileSystemModel(parent) +{ +} +// *************************************************************************** +BNPFileSystemModel::~BNPFileSystemModel() +{ + +} +// *************************************************************************** +int BNPFileSystemModel::columnCount(const QModelIndex &) const +{ + return 1; +} +// *************************************************************************** +QVariant BNPFileSystemModel::data(const QModelIndex& index, int role) const +{ + + if (role == Qt::DecorationRole) + { + if (isDir(index)) + return QApplication::style()->standardIcon(QStyle::SP_DirIcon); + } + return QFileSystemModel::data(index, role); +} +// *************************************************************************** +} // namespace BNPManager diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filesystem_model.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filesystem_model.h new file mode 100644 index 000000000..2ca086b39 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_filesystem_model.h @@ -0,0 +1,49 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +#ifndef BNP_FILESYSTEM_MODEL_H +#define BNP_FILESYSTEM_MODEL_H + +#include + +namespace BNPManager +{ + +class BNPFileSystemModel : public QFileSystemModel +{ + Q_OBJECT + +public: + + /** + * Constructor + */ + BNPFileSystemModel(QObject *parent = 0); + + /** + * Destructor + */ + ~BNPFileSystemModel(); + + int columnCount(const QModelIndex &) const; + + QVariant data(const QModelIndex& index, int role) const ; + +}; + +} + +#endif \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager.qrc b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager.qrc new file mode 100644 index 000000000..bf32595d6 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager.qrc @@ -0,0 +1,9 @@ + + + images/ic_nel_bnp_make.png + images/ic_nel_delete_item.png + images/ic_nel_add_item.png + images/ic_nel_export.png + images/ic_nel_reset_all.png + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_constants.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_constants.h new file mode 100644 index 000000000..609305b7e --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_constants.h @@ -0,0 +1,38 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef BNP_MANAGER_CONSTANTS_H +#define BNP_MANAGER_CONSTANTS_H + +namespace BNPManager +{ +namespace Constants +{ +//settings +const char * const BNP_MANAGER_SECTION = "BNPManager"; + +//resources +const char *const ICON_NEW = ":/images/ic_nel_new.png"; +const char *const ICON_ADD = ":/images/ic_nel_add_item.png"; +const char *const ICON_DELETE = ":/images/ic_nel_delete_item.png"; +const char *const ICON_UNPACK = ":/images/ic_nel_export.png"; +const char *const ICON_CLOSE = ":/images/ic_nel_reset_all.png"; + + +} // namespace Constants +} // namespace Plugin + +#endif diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_plugin.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_plugin.cpp new file mode 100644 index 000000000..70015773b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_plugin.cpp @@ -0,0 +1,89 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Project includes +#include "bnp_manager_plugin.h" +#include "bnp_manager_window.h" + +#include "../core/icore.h" +#include "../core/core_constants.h" +#include "../core/menu_manager.h" + +// NeL includes +#include "nel/misc/debug.h" + +// Qt includes +#include +#include + +namespace BNPManager +{ + + BNPManagerPlugin::BNPManagerPlugin() + { + } + + BNPManagerPlugin::~BNPManagerPlugin() + { + Q_FOREACH(QObject *obj, m_autoReleaseObjects) + { + m_plugMan->removeObject(obj); + } + qDeleteAll(m_autoReleaseObjects); + m_autoReleaseObjects.clear(); + } + +bool BNPManagerPlugin::initialize(ExtensionSystem::IPluginManager *pluginManager, QString *errorString) +{ + Q_UNUSED(errorString); + m_plugMan = pluginManager; + + addAutoReleasedObject(new BNPManagerContext(this)); + return true; +} + +void BNPManagerPlugin::extensionsInitialized() +{ +} + +void BNPManagerPlugin::shutdown() +{ + +} + +void BNPManagerPlugin::setNelContext(NLMISC::INelContext *nelContext) +{ +#ifdef NL_OS_WINDOWS + // Ensure that a context doesn't exist yet. + // This only applies to platforms without PIC, e.g. Windows. + nlassert(!NLMISC::INelContext::isContextInitialised()); +#endif // NL_OS_WINDOWS + m_libContext = new NLMISC::CLibraryContext(*nelContext); +} + +void BNPManagerPlugin::addAutoReleasedObject(QObject *obj) +{ + m_plugMan->addObject(obj); + m_autoReleaseObjects.prepend(obj); +} + +/*void BNPManagerContext::open() +{ + m_BnpManagerWindow->open(); +}*/ +} + +Q_EXPORT_PLUGIN(BNPManager::BNPManagerPlugin) diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_plugin.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_plugin.h new file mode 100644 index 000000000..55e2e8444 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_plugin.h @@ -0,0 +1,130 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef BNP_MANAGER_PLUGIN_H +#define BNP_MANAGER_PLUGIN_H + +// Project includes +#include "../../extension_system/iplugin.h" +#include "../core/icontext.h" +#include "bnp_manager_window.h" + +// NeL includes +#include "nel/misc/app_context.h" +#include + +// Qt includes +#include +#include + +namespace NLMISC +{ +class CLibraryContext; +} + +namespace ExtensionSystem +{ +class IPluginSpec; +} + +namespace BNPManager +{ +class m_BnpManagerWindow; + +class BNPManagerPlugin : public QObject, public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_INTERFACES(ExtensionSystem::IPlugin) + +public: + BNPManagerPlugin(); + virtual ~BNPManagerPlugin(); + + virtual bool initialize(ExtensionSystem::IPluginManager *pluginManager, QString *errorString); + virtual void extensionsInitialized(); + virtual void shutdown(); + virtual void setNelContext(NLMISC::INelContext *nelContext); + + void addAutoReleasedObject(QObject *obj); + +protected: + + NLMISC::CLibraryContext *m_libContext; + +private: + + ExtensionSystem::IPluginManager *m_plugMan; + QList m_autoReleaseObjects; +}; + +/** + * Implementation of the IContext interface + * + * \date 2011 + */ + +class BNPManagerContext : public Core::IContext +{ + Q_OBJECT + +public: + // Constructor + BNPManagerContext(QObject *parent = 0) : IContext(parent) + { + // run new manager window app + m_BnpManagerWindow = new BNPManagerWindow(); + } + + // Destructor + virtual ~BNPManagerContext() {} + + virtual QString id() const + { + return QLatin1String("BNPManagerContext"); + } + virtual QString trName() const + { + return tr("BNP Manager"); + } + virtual QIcon icon() const + { + return QIcon(":/images/ic_nel_bnp_make.png"); + } + + virtual void open() + { + m_BnpManagerWindow->open(); + } + + virtual QUndoStack *undoStack() + { + return m_BnpManagerWindow->m_undoStack; + } + + virtual QWidget *widget() + { + return m_BnpManagerWindow; + } + + BNPManagerWindow *m_BnpManagerWindow; + +}; + +} // namespace Plugin + + + +#endif // BNP_MANAGER_PLUGIN_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.cpp new file mode 100644 index 000000000..3990e70ce --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.cpp @@ -0,0 +1,444 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Project includes +#include "bnp_manager_window.h" +#include "bnp_manager_constants.h" +#include "bnp_dirtree_dialog.h" +#include "bnp_filelist_dialog.h" +#include "bnp_file.h" + +#include "../core/icore.h" +#include "../core/menu_manager.h" +#include "../core/core_constants.h" +#include "../../extension_system/iplugin_spec.h" + +// NeL includes +#include +#include + +// STL includes +#include +#include + +// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace NLMISC; + + +namespace BNPManager +{ + +BNPManagerWindow::BNPManagerWindow(QWidget *parent) + : QMainWindow(parent) +{ + // add new mainwindow for sheet dockwidgets + QTableWidget* hideWidget = new QTableWidget(0,0,this); + setCentralWidget(hideWidget); + hideWidget->hide(); + + setAcceptDrops(true); + + // Read the settings + readSettings(); + + // create main dialogs and display them + createDialogs(); + + // create actions like open, close, add etc. + createActions(); + + // create a toolbar with icons + createToolBars(); + + // this SLOT is triggered if the user activates a bnp files in the + // dirtree view + connect(m_BnpDirTreeDialog, SIGNAL(selectedFile(const QString)), + this, SLOT(loadFile(const QString))); + + // not used + m_undoStack = new QUndoStack(this); +} +// *************************************************************************** +BNPManagerWindow::~BNPManagerWindow() +{ + writeSettings(); +} +// *************************************************************************** +void BNPManagerWindow::createDialogs() +{ + // create dialog to list the contents of the specified + // bnp data file directory + m_BnpDirTreeDialog = new CBnpDirTreeDialog(tr(m_DataPath.toStdString().c_str()),this); + addDockWidget(Qt::LeftDockWidgetArea, m_BnpDirTreeDialog); + m_BnpDirTreeDialog->setVisible(true); + restoreDockWidget(m_BnpDirTreeDialog); + + // create dialog to list the packed file contents of bnp files on + // the right hand side + m_BnpFileListDialog = new BnpFileListDialog(m_DataPath,this); + addDockWidget(Qt::RightDockWidgetArea, m_BnpFileListDialog); + m_BnpFileListDialog->setVisible(true); + restoreDockWidget(m_BnpFileListDialog); +} +// *************************************************************************** +void BNPManagerWindow::createActions() +{ + // new action + m_newAction = new QAction(tr("&New..."), this); + m_newAction->setIcon(QIcon(Core::Constants::ICON_NEW)); + m_newAction->setStatusTip(tr("New file")); + connect(m_newAction, SIGNAL(triggered()), this, SLOT( newFile() )); + + // open action + m_openAction = new QAction(tr("&Open..."), this); + m_openAction->setIcon(QIcon(Core::Constants::ICON_OPEN)); + m_openAction->setStatusTip(tr("Open file")); + connect(m_openAction, SIGNAL(triggered()), this, SLOT( open() )); + + // close action + m_closeAction = new QAction(tr("&Close..."), this); + m_closeAction->setIcon(QIcon(Constants::ICON_CLOSE)); + m_closeAction->setStatusTip(tr("Close the BNP File")); + connect(m_closeAction, SIGNAL(triggered()), this, SLOT( close() )); + + // add files into the bnp file + m_addFilesAction = new QAction(tr("&Add..."), this); + m_addFilesAction->setIcon(QIcon(Constants::ICON_ADD)); + m_addFilesAction->setStatusTip(tr("Add Files to BNP")); + connect(m_addFilesAction, SIGNAL(triggered()), this, SLOT( addFiles() )); + + // delete files from the bnp file + m_deleteFilesAction = new QAction(tr("&Delete..."), this); + m_deleteFilesAction->setIcon(QIcon(Constants::ICON_DELETE)); + m_deleteFilesAction->setStatusTip(tr("Delete Files")); + connect(m_deleteFilesAction, SIGNAL(triggered()), this, SLOT( deleteFiles() )); + + // unpack selected files into user defined dir + m_unpackFilesAction = new QAction(tr("&Unpack..."), this); + m_unpackFilesAction->setIcon(QIcon(Constants::ICON_UNPACK)); + m_unpackFilesAction->setStatusTip(tr("Unpack Files")); + connect(m_unpackFilesAction, SIGNAL(triggered()), this, SLOT( unpackFiles() )); +} +// *************************************************************************** +void BNPManagerWindow::createToolBars() +{ + m_fileToolBar = addToolBar(tr("&File")); + m_fileToolBar->addAction(m_newAction); + m_fileToolBar->addAction(m_openAction); + m_fileToolBar->addAction(m_closeAction); + + m_toolsBar = addToolBar(tr("&Tools")); + m_toolsBar->addAction(m_addFilesAction); + m_toolsBar->addAction(m_deleteFilesAction); + m_toolsBar->addAction(m_unpackFilesAction); +} +// *************************************************************************** +bool BNPManagerWindow::loadFile(const QString fileName) +{ + // Store the filename for later use + m_openedBNPFile = fileName; + m_BnpFileListDialog->loadTable(fileName); + return true; +} +// *************************************************************************** +void BNPManagerWindow::newFile() +{ + // reference to the BNPFileHandle singletone instance + BNPFileHandle& myBNPFileHandle = BNPFileHandle::getInstance(); + + m_openedBNPFile = ""; + m_BnpFileListDialog->clearTable(); + + QString filePath = QFileDialog::getSaveFileName(this, tr("Create File"),QDir::currentPath(), + tr("BNP File (*.bnp)")); + + if (filePath.isEmpty() ) + return; + + if ( !filePath.endsWith(".bnp", Qt::CaseInsensitive) ) + filePath.append(".bnp"); + + m_openedBNPFile = filePath; + m_BnpFileListDialog->setWindowTitle (filePath); + + myBNPFileHandle.createFile ( filePath.toStdString() ); + +} +// *************************************************************************** +void BNPManagerWindow::open() +{ + QString fileName; + // file dialog to select with file should be opened + fileName = QFileDialog::getOpenFileName(this, + tr("Open BNP file"), tr(m_DataPath.toStdString().c_str()), tr("BNP Files (*.bnp)")); + + // Check if filename is empty + if (fileName.isNull()) + return; + + loadFile(fileName); +} +// *************************************************************************** +void BNPManagerWindow::close() +{ + m_openedBNPFile = ""; + m_BnpFileListDialog->clearTable(); +} +// *************************************************************************** +void BNPManagerWindow::addFiles() +{ + // reference to the BNPFileHandle singletone instance + BNPFileHandle& myBNPFileHandle = BNPFileHandle::getInstance(); + + // vector of all current packed filenames + vector currentFiles; + + // vector of files to add + vector addFiles; + + // open a file dialog and to add files + QStringList FileList; + + FileList = QFileDialog::getOpenFileNames(this,tr("Add Files..."), + QDir::currentPath(), tr("All Files (*.*)") ); + + // get all current filenames from the opened bnp file + myBNPFileHandle.fileNames(currentFiles); + + QStringList::iterator it_list = FileList.begin(); + while (it_list != FileList.end() ) + { + string fileName = CFile::getFilename (it_list->toStdString() ); + if ( std::find(currentFiles.begin(), currentFiles.end(), fileName ) != currentFiles.end() ) + { + // Ask the user if he wants to override the existing file + // atm only warn the user and do not override + QMessageBox::warning(this, tr("BNP Manager"), + tr("File is already in the list!"), + QMessageBox::Ok, + QMessageBox::Ok); + } + else + { + addFiles.push_back( it_list->toStdString() ); + // log it + nlinfo("Add file %s", fileName.c_str() ); + } + it_list++; + } + + if ( !addFiles.empty() ) + { + myBNPFileHandle.addFiles( addFiles ); + } + loadFile(m_openedBNPFile); +} +// *************************************************************************** +void BNPManagerWindow::addFiles( QStringList FileList ) +{ + // reference to the BNPFileHandle singletone instance + BNPFileHandle& myBNPFileHandle = BNPFileHandle::getInstance(); + + // vector of all current packed filenames + vector currentFiles; + + // vector of files to add + vector addFiles; + + // get all current filenames from the opened bnp file + myBNPFileHandle.fileNames(currentFiles); + + QStringList::iterator it_list = FileList.begin(); + while (it_list != FileList.end() ) + { + string fileName = CFile::getFilename (it_list->toStdString() ); + if ( std::find(currentFiles.begin(), currentFiles.end(), fileName ) != currentFiles.end() ) + { + // Ask the user if he wants to override the existing file + // atm only warn the user and do not override + QMessageBox::warning(this, tr("BNP Manager"), + tr("File is already in the list!"), + QMessageBox::Ok, + QMessageBox::Ok); + } + else + { + addFiles.push_back( it_list->toStdString() ); + // log it + nlinfo("Add file %s", fileName.c_str() ); + } + it_list++; + } + + if ( !addFiles.empty() ) + { + myBNPFileHandle.addFiles( addFiles ); + } + loadFile(m_openedBNPFile); +} +// *************************************************************************** +void BNPManagerWindow::deleteFiles() +{ + QFileDialog filedialog(this); + BNPFileHandle& myBNPFileHandle = BNPFileHandle::getInstance(); + vector selectedRows; + + m_BnpFileListDialog->getSelections(selectedRows); + + // Check if files were selected. If not, inform the user. + if (selectedRows.empty()) + { + QMessageBox::information(this, tr("BNP Manager"), + tr("No files selected!"), + QMessageBox::Ok, + QMessageBox::Ok); + return; + } + + myBNPFileHandle.deleteFiles(selectedRows); + loadFile(m_openedBNPFile); +} +// *************************************************************************** +void BNPManagerWindow::unpackFiles() +{ + QFileDialog filedialog(this); + BNPFileHandle& myBNPFileHandle = BNPFileHandle::getInstance(); + vector selectedrows; + + m_BnpFileListDialog->getSelections(selectedrows); + + // Check if files were selected. If not, inform the user. + // TODO: Ask the user if nothing was selected, if he wants to unpack all + // files. This is more like Winzip. + if (selectedrows.empty()) + { + QMessageBox::information(this, tr("BNP Manager"), + tr("No files selected!"), + QMessageBox::Ok, + QMessageBox::Ok); + return; + } + + QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), + tr(m_DataPath.toStdString().c_str()), + QFileDialog::ShowDirsOnly + | QFileDialog::DontResolveSymlinks); + + // If anything went wrong or the user pressed "cancel" + if ( dir.isEmpty() ) + return; + + if (myBNPFileHandle.unpack(dir.toStdString(),selectedrows)) + { + QMessageBox::information(this, tr("BNP Manager"), + tr("All files has been exported successfully."), + QMessageBox::Ok, + QMessageBox::Ok); + } +} +// *************************************************************************** +void BNPManagerWindow::readSettings() +{ + QSettings *settings = Core::ICore::instance()->settings(); + + settings->beginGroup(Core::Constants::DATA_PATH_SECTION); + m_DataPath = settings->value(Core::Constants::ASSETS_PATH, "w:/database").toString(); + settings->endGroup(); +} +// *************************************************************************** +void BNPManagerWindow::writeSettings() +{ +} + +// *************************************************************************** +void BNPManagerWindow::dragEnterEvent(QDragEnterEvent *event) +{ + // Accept only one file + // In the future a tabbed FileListDialog would accept more + if ( event->mimeData()->hasUrls() ) + event->acceptProposedAction(); +} +// *************************************************************************** +void BNPManagerWindow::dropEvent(QDropEvent *event) +{ + // reference to the BNPFileHandle singletone instance + BNPFileHandle& myBNPFileHandle = BNPFileHandle::getInstance(); + + // Excraft the local file url from the drop object and fill the table + const QMimeData *mimeData = event->mimeData(); + QList urlList = mimeData->urls(); + QString filePath; + QStringList fileList; + + if ( urlList.count() == 1 ) + { + // If it is a bnp file, open it + // If it is not a bnp file add it + + filePath = urlList.first().toLocalFile(); + if ( filePath.endsWith(".bnp", Qt::CaseInsensitive) ) + { + loadFile(filePath); + } + else + { + if ( m_openedBNPFile == "") + newFile(); + // Create a QStringList and pass it to addfiles + fileList.push_back( filePath ); + addFiles( fileList ); + // Reload current bnp + loadFile(m_openedBNPFile); + } + } + else if ( urlList.count() > 1 ) + { + // Dont accept any bnp file + QList::iterator it = urlList.begin(); + while ( it != urlList.end() ) + { + filePath = it->toLocalFile(); + if ( filePath.endsWith(".bnp") ) + { + nlwarning("Could not add a bnp file!", filePath.toStdString().c_str() ); + } + else + { + fileList.push_back( filePath ); + } + ++it; + } + if ( m_openedBNPFile == "") + newFile(); + addFiles( fileList ); + // Reload current bnp + loadFile(m_openedBNPFile); + } +} +} // namespace BNPManager diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.h new file mode 100644 index 000000000..89bd68a16 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.h @@ -0,0 +1,156 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef BNP_MANAGER_WINDOW_H +#define BNP_MANAGER_WINDOW_H + +// Project includes +//#include "ui_bnp_manager_window.h" + +// Qt includes +#include +#include +#include +#include + + +namespace BNPManager +{ + +class CBnpDirTreeDialog; +class BnpFileListDialog; +class BNPFileHandle; + +/** + * Main window class. Derived from QMainWindow and implements + * the basic layout like menue, toolbars and dialogs. + * + * \date 2011 + */ + +class BNPManagerWindow : public QMainWindow +{ + Q_OBJECT + +public: + + // Constructor + BNPManagerWindow(QWidget *parent = 0); + + //Destructor + ~BNPManagerWindow(); + + + QUndoStack *m_undoStack; + +public Q_SLOTS: + + /** + * Create a new file + * \return Filename string + */ + void newFile(); + + /** + * Open a file dialog to choose which file should be opened. + */ + void open(); + + /** + * Load a certain bnp file into the manager + * \param Filename + * \return true if everything went well + */ + bool loadFile(const QString fileName); + + /** + * close an opened bnp file and reset all views + */ + void close(); + + /** + * Add files into an opened bnp file. + * \param Filelist + */ + void addFiles(); + void addFiles( QStringList FileList ); + + /** + * Unpack the files marked in the filelist dialog into user defined + * directory. + * \param TBD + * \return true if everything went well + */ + void unpackFiles(); + + /** + * Delete marked files from the bnp file + * \param TBD + */ + void deleteFiles(); + +protected: + void dragEnterEvent (QDragEnterEvent *event); + void dropEvent(QDropEvent *event); + +private: + + /** + * Read plugin settings and set the window accordingly + */ + void readSettings(); + + /** + * Write plugin settings + */ + void writeSettings(); + + /** + * Create all plugin dialogs + */ + void createDialogs(); + + /** + * Create all plugin actions + */ + void createActions(); + + /** + * Create the plugin toolbar + */ + void createToolBars(); + + QToolBar *m_fileToolBar; + QToolBar *m_toolsBar; + + QAction *m_newAction; + QAction *m_openAction; + QAction *m_closeAction; + QAction *m_addFilesAction; + QAction *m_unpackFilesAction; + QAction *m_deleteFilesAction; + + CBnpDirTreeDialog *m_BnpDirTreeDialog; + BnpFileListDialog *m_BnpFileListDialog; + + QString m_DataPath; + QString m_openedBNPFile; + +}; /* class BNPManagerWindow */ + +} /* namespace Plugin */ + +#endif diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.ui b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.ui new file mode 100644 index 000000000..4e695f72c --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_manager_window.ui @@ -0,0 +1,50 @@ + + + BNPManagerWindow + + + + 0 + 0 + 800 + 600 + + + + BNP Manager + + + + + + + QWidget#centralwidget { + image: url(:/images/ic_nel_georges_editor.png); + } + + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_proxy_model.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_proxy_model.cpp new file mode 100644 index 000000000..d3657d13b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_proxy_model.cpp @@ -0,0 +1,56 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +// NeL includes +#include + +// project includes +#include "bnp_proxy_model.h" + +namespace BNPManager +{ + +bool BNPSortProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + if ( sourceModel()->hasChildren(left) ) + { + if ( !sourceModel()->hasChildren(right) ) + { + return true; + } + else + { + QString leftString = sourceModel()->data( left ).toString(); + QString rightString = sourceModel()->data( right ).toString(); + return QString::localeAwareCompare(leftString, rightString) < 0; + } + } + else + { + if ( sourceModel()->hasChildren(right) ) + return false; + else + { + QString leftString = sourceModel()->data( left ).toString(); + QString rightString = sourceModel()->data( right ).toString(); + return QString::localeAwareCompare(leftString, rightString) < 0; + } + } +} + +} /* namespace Plugin */ + +/* end of file */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_proxy_model.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_proxy_model.h new file mode 100644 index 000000000..ed2da5966 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/bnp_proxy_model.h @@ -0,0 +1,44 @@ +// Object Viewer Qt - BNP Manager Plugin - MMORPG Framework +// Copyright (C) 2011 Roland Winklmeier +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +#ifndef BNP_PROXY_MODEL_H +#define BNP_PROXY_MODEL_H + +// Qt includes +#include + +namespace BNPManager +{ + + class BNPSortProxyModel : public QSortFilterProxyModel + { + + public: + BNPSortProxyModel(QObject *parent = 0): QSortFilterProxyModel(parent) + { + } + ~BNPSortProxyModel() + { + } + + protected: + virtual bool lessThan ( const QModelIndex & left, const QModelIndex & right ) const; + + };/* class BNPSortProxyModel */ + +} // BNPManager + +#endif // BNP_PROXY_MODEL_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_add_item.png b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_add_item.png new file mode 100644 index 000000000..bde338f78 Binary files /dev/null and b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_add_item.png differ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_bnp_make.png b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_bnp_make.png new file mode 100644 index 000000000..19b749b1d Binary files /dev/null and b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_bnp_make.png differ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_delete_item.png b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_delete_item.png new file mode 100644 index 000000000..a5a1787d5 Binary files /dev/null and b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_delete_item.png differ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_export.png b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_export.png new file mode 100644 index 000000000..9fc71c09d Binary files /dev/null and b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_export.png differ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_new.png b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_new.png new file mode 100644 index 000000000..d45dcb467 Binary files /dev/null and b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_new.png differ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_reset_all.png b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_reset_all.png new file mode 100644 index 000000000..c67287408 Binary files /dev/null and b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/images/ic_nel_reset_all.png differ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/ovqt_plugin_bnp_manager.xml b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/ovqt_plugin_bnp_manager.xml new file mode 100644 index 000000000..43cea2022 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/bnp_manager/ovqt_plugin_bnp_manager.xml @@ -0,0 +1,10 @@ + + ovqt_plugin_bnp_manager + BNPManager + 0.1 + Krolock + Edit BNP Files + + + + \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/context_manager.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/context_manager.cpp index 68e28429d..d82cdb63b 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/context_manager.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/context_manager.cpp @@ -78,6 +78,18 @@ Core::IContext *ContextManager::context(const QString &id) const return 0; } +void ContextManager::registerUndoStack(QUndoStack *stack) +{ + nlassert(stack); + d->m_mainWindow->undoGroup()->addStack(stack); +} + +void ContextManager::unregisterUndoStack(QUndoStack *stack) +{ + nlassert(stack); + d->m_mainWindow->undoGroup()->removeStack(stack); +} + void ContextManager::activateContext(const QString &id) { const int index = indexOf(id); @@ -85,6 +97,11 @@ void ContextManager::activateContext(const QString &id) d->m_tabWidget->setCurrentIndex(index); } +void ContextManager::updateCurrentContext() +{ + d->m_mainWindow->updateContext(currentContext()); +} + void ContextManager::objectAdded(QObject *obj) { IContext *context = qobject_cast(obj); diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/context_manager.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/context_manager.h index 7a3658fff..8151648e7 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/context_manager.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/context_manager.h @@ -26,6 +26,7 @@ QT_BEGIN_NAMESPACE class QTabWidget; +class QUndoStack; QT_END_NAMESPACE namespace Core @@ -45,12 +46,17 @@ public: Core::IContext *currentContext() const; Core::IContext *context(const QString &id) const; + // temporary solution for multiple undo stacks per context + void registerUndoStack(QUndoStack *stack); + void unregisterUndoStack(QUndoStack *stack); + Q_SIGNALS: // the default argument '=0' is important for connects without the oldContext argument. void currentContextChanged(Core::IContext *context, Core::IContext *oldContext = 0); public Q_SLOTS: void activateContext(const QString &id); + void updateCurrentContext(); private Q_SLOTS: void objectAdded(QObject *obj); diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/core_constants.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/core_constants.h index 6eb7d41f4..18f3690a0 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/core_constants.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/core_constants.h @@ -92,7 +92,7 @@ const char *const SEARCH_PATHS = "SearchPaths"; const char *const RECURSIVE_SEARCH_PATHS = "RecursiveSearchPathes"; const char *const LEVELDESIGN_PATH = "LevelDesignPath"; const char *const PRIMITIVES_PATH = "PrimitivesPath"; -const char * const ASSETS_PATH = "AssetsPath"; +const char *const ASSETS_PATH = "AssetsPath"; const char *const LIGOCONFIG_FILE = "LigoConfigFile"; const char *const REMAP_EXTENSIONS = "RemapExtensions"; diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/main_window.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/main_window.cpp index f00075b21..6e409e75d 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/main_window.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/main_window.cpp @@ -128,6 +128,11 @@ QSettings *MainWindow::settings() const return m_settings; } +QUndoGroup *MainWindow::undoGroup() const +{ + return m_undoGroup; +} + ExtensionSystem::IPluginManager *MainWindow::pluginManager() const { return m_pluginManager; @@ -135,12 +140,16 @@ ExtensionSystem::IPluginManager *MainWindow::pluginManager() const void MainWindow::addContextObject(IContext *context) { - m_undoGroup->addStack(context->undoStack()); + QUndoStack *stack = context->undoStack(); + if (stack) + m_undoGroup->addStack(stack); } void MainWindow::removeContextObject(IContext *context) { - m_undoGroup->removeStack(context->undoStack()); + QUndoStack *stack = context->undoStack(); + if (stack) + m_undoGroup->removeStack(stack); } void MainWindow::open() diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/main_window.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/main_window.h index ae0d0522c..a75126823 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/core/main_window.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/core/main_window.h @@ -52,6 +52,7 @@ public: MenuManager *menuManager() const; ContextManager *contextManager() const; QSettings *settings() const; + QUndoGroup *undoGroup() const; ExtensionSystem::IPluginManager *pluginManager() const; @@ -62,6 +63,7 @@ public Q_SLOTS: bool showOptionsDialog(const QString &group = QString(), const QString &page = QString(), QWidget *parent = 0); + void updateContext(Core::IContext *context); private Q_SLOTS: void open(); @@ -77,7 +79,6 @@ private Q_SLOTS: void gotoPos(); void setFullScreen(bool enabled); void about(); - void updateContext(Core::IContext *context); protected: virtual void closeEvent(QCloseEvent *event); diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/expandable_headerview.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/expandable_headerview.cpp index 7ae482824..2967be435 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/expandable_headerview.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/expandable_headerview.cpp @@ -22,7 +22,7 @@ #include #include -namespace Plugin +namespace GeorgesQt { ExpandableHeaderView::ExpandableHeaderView(Qt::Orientation orientation, QWidget * parent) diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/expandable_headerview.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/expandable_headerview.h index 596bf5ba1..f7f26eafc 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/expandable_headerview.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/expandable_headerview.h @@ -20,7 +20,7 @@ // Qt includes #include -namespace Plugin +namespace GeorgesQt { class ExpandableHeaderView : public QHeaderView { diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formdelegate.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formdelegate.cpp index 4d04bc6b5..be358d065 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formdelegate.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formdelegate.cpp @@ -37,7 +37,7 @@ #include "georgesform_proxy_model.h" #include "formitem.h" -namespace Plugin +namespace GeorgesQt { FormDelegate::FormDelegate(QObject *parent) @@ -275,4 +275,4 @@ namespace Plugin QRect r = option.rect; editor->setGeometry(r); } -} /* namespace Plugin */ +} /* namespace GeorgesQt */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formdelegate.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formdelegate.h index a309da1fc..486308536 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formdelegate.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formdelegate.h @@ -19,7 +19,7 @@ #include -namespace Plugin +namespace GeorgesQt { class FormDelegate : public QStyledItemDelegate diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formitem.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formitem.cpp index 205e18a52..68f29064d 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formitem.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formitem.cpp @@ -23,7 +23,7 @@ #include #include -namespace Plugin +namespace GeorgesQt { CFormItem::CFormItem(NLGEORGES::UFormElm* elm, const QList &data, CFormItem *parent, diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formitem.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formitem.h index b85b12275..80efce5d5 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formitem.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/formitem.h @@ -24,7 +24,7 @@ #include #include -namespace Plugin +namespace GeorgesQt { class CFormItem diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges.cpp index b93f93ec4..50e007d90 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges.cpp @@ -28,7 +28,7 @@ using namespace NLGEORGES; -namespace Plugin +namespace GeorgesQt { CGeorges::CGeorges(): FormLoader(0) @@ -61,4 +61,4 @@ namespace Plugin return type; } -} /* namespace Plugin */ +} /* namespace GeorgesQt */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges.h index eb9a6b7da..6d7455d3b 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges.h @@ -38,7 +38,7 @@ namespace NLGEORGES using namespace NLGEORGES; -namespace Plugin +namespace GeorgesQt { /** @@ -64,6 +64,6 @@ namespace Plugin };/* class CGeorges */ -} /* namespace Plugin */ +} /* namespace GeorgesQt */ #endif // GEORGES_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_dialog.cpp index de7a105ab..163ec7877 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_dialog.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_dialog.cpp @@ -23,7 +23,7 @@ // NeL includes -namespace Plugin +namespace GeorgesQt { CGeorgesDirTreeDialog::CGeorgesDirTreeDialog(QString ldPath, QWidget *parent) @@ -109,4 +109,4 @@ void CGeorgesDirTreeDialog::ldPathChanged(QString path) } } -} /* namespace NLQT */ \ No newline at end of file +} /* namespace GeorgesQt */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_dialog.h index fa783bca0..dc717617e 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_dialog.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_dialog.h @@ -28,7 +28,7 @@ #include "ui_georges_dirtree_form.h" #include "georges_filesystem_model.h" -namespace Plugin +namespace GeorgesQt { class CGeorgesDirTreeDialog: public QDockWidget @@ -57,6 +57,6 @@ private Q_SLOTS: }; /* CGEorgesDirTreeDialog */ -} /* namespace NLQT */ +} /* namespace GeorgesQt */ #endif // GEORGES_DIRTREE_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_form.ui b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_form.ui index 4a429af1f..e14857e89 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_form.ui +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_dirtree_form.ui @@ -44,6 +44,9 @@ 0 + + true + diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_constants.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_constants.h index bb9fe3306..cccede9fc 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_constants.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_constants.h @@ -17,7 +17,7 @@ #ifndef GEORGES_EDITOR_CONSTANTS_H #define GEORGES_EDITOR_CONSTANTS_H -namespace Plugin +namespace GeorgesQt { namespace Constants { @@ -26,6 +26,6 @@ const char * const GEORGES_EDITOR_SECTION = "GeorgesEditor"; } // namespace Constants -} // namespace Plugin +} // namespace GeorgesQt #endif // GEORGES_EDITOR_CONSTANTS_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_form.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_form.cpp index 252d7fd7e..a9666c8db 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_form.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_form.cpp @@ -33,7 +33,7 @@ #include #include -namespace Plugin +namespace GeorgesQt { GeorgesEditorForm::GeorgesEditorForm(QWidget *parent) @@ -112,24 +112,9 @@ namespace Plugin void GeorgesEditorForm::open() { - /*qDebug() << "GeorgesEditorForm::open()"; - if (!m_dockedWidgets.size()) - { - m_dockedWidgets.append(new CGeorgesTreeViewDialog(m_mainDock)); - m_mainDock->addDockWidget(Qt::RightDockWidgetArea, m_dockedWidgets.last()); - } - else - { - m_dockedWidgets.append(new CGeorgesTreeViewDialog(m_mainDock)); - Q_ASSERT(m_dockedWidgets.size() > 1); - m_mainDock->tabifyDockWidget(m_dockedWidgets.at(m_dockedWidgets.size() - 2), m_dockedWidgets.last()); - }*/ - - // TODO: FileDialog & loadFile(); - //m_mainDock->addDockWidget(Qt::TopDockWidgetArea, new CGeorgesTreeViewDialog(m_mainDock, true)); - //m_mainDock->addDockWidget(Qt::LeftDockWidgetArea, new CGeorgesTreeViewDialog(m_mainDock, true)); - //QString fileName = QFileDialog::getOpenFileName(); - //loadFile(fileName); + QString fileName = QFileDialog::getOpenFileName(this, tr("Open Form")); + if(!fileName.isNull()) + loadFile(fileName); } void GeorgesEditorForm::newFile() @@ -191,6 +176,7 @@ namespace Plugin if (!m_dockedWidgets.size()) { CGeorgesTreeViewDialog *dock = new CGeorgesTreeViewDialog(m_mainDock); + dock->setUndoStack(m_undoStack); m_lastActiveDock = dock; m_dockedWidgets.append(dock); @@ -212,6 +198,7 @@ namespace Plugin } } CGeorgesTreeViewDialog *dock = new CGeorgesTreeViewDialog(m_mainDock); + dock->setUndoStack(m_undoStack); m_dockedWidgets.append(dock); connect(m_dockedWidgets.last(), SIGNAL(closing()), @@ -284,4 +271,4 @@ namespace Plugin } } } -} /* namespace Plugin */ +} /* namespace GeorgesQt */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_form.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_form.h index 6b270ca3d..c991f8b9f 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_form.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_form.h @@ -23,7 +23,7 @@ // Qt includes #include -namespace Plugin +namespace GeorgesQt { class CGeorgesDirTreeDialog; @@ -70,6 +70,6 @@ private: CGeorgesTreeViewDialog *m_lastActiveDock; }; /* class GeorgesEditorForm */ -} /* namespace Plugin */ +} /* namespace GeorgesQt */ #endif // GEORGES_EDITOR_FORM_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_plugin.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_plugin.cpp index 199bc20ca..7dd0be5cd 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_plugin.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_plugin.cpp @@ -27,7 +27,7 @@ // Qt includes #include -namespace Plugin +namespace GeorgesQt { GeorgesEditorPlugin::~GeorgesEditorPlugin() @@ -97,4 +97,4 @@ QWidget *GeorgesEditorContext::widget() } -Q_EXPORT_PLUGIN(Plugin::GeorgesEditorPlugin) \ No newline at end of file +Q_EXPORT_PLUGIN(GeorgesQt::GeorgesEditorPlugin) \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_plugin.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_plugin.h index fabdd600c..e21d8c57a 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_plugin.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_editor_plugin.h @@ -38,7 +38,7 @@ namespace ExtensionSystem class IPluginSpec; } -namespace Plugin +namespace GeorgesQt { class GeorgesEditorForm; class GeorgesEditorPlugin : public QObject, public ExtensionSystem::IPlugin @@ -93,6 +93,6 @@ public: GeorgesEditorForm *m_georgesEditorForm; }; -} // namespace Plugin +} // namespace GeorgesQt #endif // LANDSCAPE_EDITOR_PLUGIN_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_filesystem_model.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_filesystem_model.cpp index 4e021f681..7f405fb75 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_filesystem_model.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_filesystem_model.cpp @@ -19,7 +19,7 @@ #include #include -namespace Plugin +namespace GeorgesQt { CGeorgesFileSystemModel::CGeorgesFileSystemModel(QString ldPath, QObject *parent) @@ -159,6 +159,6 @@ void CGeorgesFileSystemModel::checkLDPath() // } // return QSortFilterProxyModel::data(index, role); //} -} /* namespace NLQT */ +} /* namespace GeorgesQt */ /* end of file */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_filesystem_model.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_filesystem_model.h index 03eb5ecc2..6c7a26055 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_filesystem_model.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_filesystem_model.h @@ -20,7 +20,7 @@ #include #include -namespace Plugin +namespace GeorgesQt { class CGeorgesFileSystemModel : public QFileSystemModel @@ -74,6 +74,6 @@ private Q_SLOTS: // bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; //}; -} /* namespace NLQT */ +} /* namespace GeorgesQt */ #endif // GEORGES_FILESYSTEM_MODEL_H \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_dialog.cpp index 13b0dab03..dccb5f07b 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_dialog.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_dialog.cpp @@ -21,12 +21,20 @@ #include #include #include +#include // NeL includes #include #include #include +#include #include +#include +#include + +// OVQT Includes +#include "../core/icore.h" +#include "../core/core_constants.h" // Project includes #include "georges.h" @@ -39,7 +47,7 @@ using namespace NLMISC; using namespace NLGEORGES; -namespace Plugin +namespace GeorgesQt { CGeorgesTreeViewDialog::CGeorgesTreeViewDialog(QWidget *parent /*= 0*/) @@ -50,6 +58,11 @@ namespace Plugin m_georges = new CGeorges; loadedForm = ""; + // Set the default sheet dir dir to the level design path. + m_lastSheetDir = "."; + QSettings *settings = Core::ICore::instance()->settings(); + m_lastSheetDir = settings->value(Core::Constants::LEVELDESIGN_PATH, "l:/leveldesign").toString(); + settings->endGroup(); m_ui.setupUi(this); m_header = new ExpandableHeaderView(Qt::Horizontal, m_ui.treeView); @@ -65,6 +78,10 @@ namespace Plugin FormDelegate *formdelegate = new FormDelegate(this); m_ui.treeView->setItemDelegateForColumn(1, formdelegate); + // Set up custom context menu. + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showContextMenu(const QPoint&))); + connect(m_ui.treeView, SIGNAL(doubleClicked (QModelIndex)), this, SLOT(doubleClicked (QModelIndex))); connect(m_ui.checkBoxParent, SIGNAL(toggled(bool)), @@ -203,9 +220,34 @@ namespace Plugin } } - void CGeorgesTreeViewDialog::addParentForm(CForm *form) + void CGeorgesTreeViewDialog::addParentForm(QString parentFormNm) { - //((CForm*)_form)->insertParent(((CForm*)_form)->getParentCount(), form->getFilename().c_str(), form); + // Try to load the form + NLGEORGES::UForm *uParentForm = m_georges->loadForm(parentFormNm.toStdString()); + NLGEORGES::CForm *parentForm = dynamic_cast(uParentForm); + NLGEORGES::CForm *mainForm = static_cast(m_form); + + CGeorgesFormProxyModel * proxyModel = dynamic_cast(m_ui.treeView->model()); + CGeorgesFormModel *model = dynamic_cast(proxyModel->sourceModel()); + + if(parentForm) + { + if (mainForm != parentForm) + { + // Check it is the same dfn + if (parentForm->Elements.FormDfn == mainForm->Elements.FormDfn) + { + // This is the parent form selector + if(!mainForm->insertParent(mainForm->getParentCount(),parentFormNm.toStdString().c_str(), parentForm)) + nlwarning("Failed to add parent form: %s", parentFormNm.toStdString().c_str()); + else + { + nlinfo("Successfullyadded parent form: %s", parentFormNm.toStdString().c_str()); + model->addParentForm(parentFormNm); + } + } + } + } } void CGeorgesTreeViewDialog::modifiedFile( ) @@ -385,4 +427,121 @@ namespace Plugin } } -} /* namespace NLQT */ + void CGeorgesTreeViewDialog::showContextMenu(const QPoint &pos) + { + QMenu contextMenu; + QMenu *structContext = NULL; + QPoint globalPos = this->mapToGlobal(pos); + + // Fisrt we're going to see if we've right clicked on a new item and select it. + QModelIndex &index = this->m_ui.treeView->currentIndex(); + + if(!index.isValid()) + return; + + CGeorgesFormProxyModel * mp = dynamic_cast(m_ui.treeView->model()); + CGeorgesFormModel *m = dynamic_cast(mp->sourceModel()); + QModelIndex sourceIndex = mp->mapToSource(index); + + if (m) + { + + CFormItem *item = m->getItem(sourceIndex); + + // Right click on the "parents" item + if (item->data(0) == "parents") + contextMenu.addAction("Add parent..."); + // Right click on a parent item + else if(item->parent() && item->parent()->data(0) == "parents") + { + contextMenu.addAction("Add parent..."); + contextMenu.addAction("Remove parent"); + } + else if(item->getFormElm()->isArray()) + contextMenu.addAction("Add array entry..."); + else if(item->getFormElm()->isStruct()) + { + QMenu *structContext = new QMenu("Add struct element...", this); + contextMenu.addMenu(structContext); + + NLGEORGES::UFormDfn *defn = item->getFormElm()->getStructDfn(); + if(defn) + { + for(uint defnNum=0; defnNum < defn->getNumEntry(); defnNum++) + { + std::string entryName; + std::string dummy; + UFormElm::TWhereIsValue *whereV = new UFormElm::TWhereIsValue; + bool result = defn->getEntryName(defnNum, entryName); + bool result2 = item->getFormElm()->getValueByName(dummy, entryName.c_str(), NLGEORGES::UFormElm::Eval, whereV); + + + if(result2 && *whereV != UFormElm::ValueForm) + { + structContext->addAction(entryName.c_str()); + } + delete whereV; + } + } + } + else if(item->getFormElm()->isAtom() && item->valueFrom() == NLGEORGES::UFormElm::ValueForm) + contextMenu.addAction("Revert to parent/default..."); + + QAction *selectedItem = contextMenu.exec(globalPos); + if(selectedItem) + { + if(selectedItem->text() == "Add parent...") + { + // Get the file extension of the form so we can build a dialog pattern. + QString file = m_form->getFilename().c_str(); + file = file.remove(0,file.indexOf(".")+1); + QString filePattern = "Parent Sheets (*."+file+")"; + + nlinfo("parent defn name '%s'", file.toStdString().c_str()); + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select parent sheets..."), m_lastSheetDir, filePattern); + if(!fileNames.isEmpty()) + { + Q_FOREACH(QString fileToParent, fileNames) + { + // Get just the filename. Georges doesn't want the path. + QFileInfo pathInfo( fileToParent ); + QString tmpFileName( pathInfo.fileName() ); + + nlinfo("requesting to add parent form '%s'", tmpFileName.toStdString().c_str()); + + // Call to add the form and load it into the Georges form. + addParentForm(tmpFileName); + + // Save the file lookup path for future dialog boxes. + m_lastSheetDir = pathInfo.absolutePath(); + } + } + m_ui.treeView->expandAll(); + } + else if(selectedItem->text() == "Remove parent") + { + NLGEORGES::CForm *form = static_cast(m_form); + QString parentFileName = item->data(0).toString(); + + for(uint num = 0; num < form->getParentCount(); num++) + { + QString curParentName = form->getParent(num)->getFilename().c_str(); + if(parentFileName == curParentName) + { + form->removeParent(num); + m->removeParentForm(parentFileName); + break; + } + } + + m_ui.treeView->expandAll(); + } + + } // if selected context menu item is valid. + } // if 'm' model valid. + + if(structContext) + delete structContext; + } + +} /* namespace GeorgesQt */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_dialog.h index 4992c9b23..a7ff4c375 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_dialog.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_dialog.h @@ -22,6 +22,9 @@ // Qt includes #include +#include +#include + // STL includes @@ -37,7 +40,7 @@ namespace NLGEORGES using namespace NLGEORGES; -namespace Plugin +namespace GeorgesQt { class CGeorges; @@ -54,12 +57,16 @@ namespace Plugin void setModified(bool m) {m_modified = m;} CForm* getFormByName(const QString); - void addParentForm(CForm *form); + void addParentForm(QString parentFormNm); void write ( ); QTabWidget* tabWidget() { return m_ui.treeViewTabWidget; } + void setUndoStack(QUndoStack *stack) { + m_undoStack = stack; + } + QString loadedForm; protected: @@ -75,6 +82,7 @@ namespace Plugin void loadFormIntoDialog(CForm *form = 0); void modifiedFile( ); void checkVisibility(bool); + void showContextMenu(const QPoint &pos); private Q_SLOTS: void doubleClicked ( const QModelIndex & index ); @@ -87,10 +95,15 @@ namespace Plugin UForm *m_form; CGeorges *m_georges; + QUndoStack *m_undoStack; + + /// Contains a record of the last directory a sheet file dialog was opened for. + QString m_lastSheetDir; + bool m_modified; }; /* CGeorgesTreeViewDialog */ -} /* namespace NLQT */ +} /* namespace GeorgesQt */ #endif // GEORGES_TREEVIEWER_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_form.ui b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_form.ui index 183b16118..8d53bfdd6 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_form.ui +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georges_treeview_form.ui @@ -63,6 +63,9 @@ 0 + + true + diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_model.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_model.cpp index f6b9c441b..ca016e052 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_model.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_model.cpp @@ -40,16 +40,16 @@ using namespace NLGEORGES; -namespace Plugin +namespace GeorgesQt { CGeorgesFormModel::CGeorgesFormModel(UFormElm *rootElm, QMap< QString, QStringList> deps, QString comment, QStringList parents, bool *expanded, QObject *parent) : QAbstractItemModel(parent) { - QList rootData; - rootData << "Value" << "Data" << "Extra";// << "Type"; + + m_rootData << "Value" << "Data" << "Extra";// << "Type"; m_rootElm = rootElm; - m_rootItem = new CFormItem(m_rootElm, rootData); + m_rootItem = new CFormItem(m_rootElm, m_rootData); m_dependencies = deps; m_comments = comment; m_parents = parents; @@ -669,6 +669,27 @@ namespace Plugin Q_EMIT layoutAboutToBeChanged(); Q_EMIT layoutChanged(); } -} /* namespace Plugin */ + + void CGeorgesFormModel::addParentForm(QString parentForm) + { + beginResetModel(); + m_parents.push_back(parentForm); + delete m_rootItem; + m_rootItem = new CFormItem(m_rootElm, m_rootData); + setupModelData(); + endResetModel(); + } + + void CGeorgesFormModel::removeParentForm(QString parentForm) + { + beginResetModel(); + m_parents.removeOne(parentForm); + + delete m_rootItem; + m_rootItem = new CFormItem(m_rootElm, m_rootData); + setupModelData(); + endResetModel(); + } +} /* namespace GeorgesQt */ /* end of file */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_model.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_model.h index 0d8ce6e69..05fadc498 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_model.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_model.h @@ -29,7 +29,7 @@ namespace NLGEORGES { class UFormElm; } -namespace Plugin +namespace GeorgesQt { class CFormItem; @@ -56,6 +56,9 @@ namespace Plugin bool showDefaults() { return m_showDefaults;} void setShowParents( bool show ); void setShowDefaults( bool show ); + void addParentForm(QString parentForm); + void removeParentForm(QString parentForm); + NLGEORGES::UFormElm *getRootForm() { return m_rootElm; } private: void setupModelData(); @@ -64,6 +67,7 @@ namespace Plugin CFormItem* m_rootItem; NLGEORGES::UFormElm* m_rootElm; + QList m_rootData; QMap< QString, QStringList> m_dependencies; QString m_comments; QStringList m_parents; @@ -71,10 +75,10 @@ namespace Plugin bool m_showParents; bool m_showDefaults; - bool *m_expanded; + bool *m_expanded; };/* class CGeorgesFormModel */ -} /* namespace Plugin */ +} /* namespace GeorgesQt */ #endif // GEORGESFORM_MODEL_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_proxy_model.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_proxy_model.cpp index ae3720a57..a99997925 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_proxy_model.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_proxy_model.cpp @@ -23,7 +23,7 @@ #include "georgesform_proxy_model.h" #include "georgesform_model.h" -namespace Plugin +namespace GeorgesQt { bool CGeorgesFormProxyModel::filterAcceptsRow(int sourceRow, @@ -76,6 +76,6 @@ namespace Plugin { return QSortFilterProxyModel::filterAcceptsColumn(sourceRow, sourceParent); } -} /* namespace Plugin */ +} /* namespace GeorgesQt */ /* end of file */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_proxy_model.h b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_proxy_model.h index 570913dab..73c384a99 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_proxy_model.h +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/georges_editor/georgesform_proxy_model.h @@ -20,7 +20,7 @@ // Qt includes #include -namespace Plugin +namespace GeorgesQt { class CGeorgesFormProxyModel : public QSortFilterProxyModel @@ -40,6 +40,6 @@ namespace Plugin };/* class CGeorgesFormProxyModel */ -} /* namespace NLQT */ +} /* namespace GeorgesQt */ #endif // GEORGESFORM_PROXY_MODEL_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/plugins/mission_compiler/mission_compiler_main_window.cpp b/code/nel/tools/3d/object_viewer_qt/src/plugins/mission_compiler/mission_compiler_main_window.cpp index 416a418f1..df909f31a 100644 --- a/code/nel/tools/3d/object_viewer_qt/src/plugins/mission_compiler/mission_compiler_main_window.cpp +++ b/code/nel/tools/3d/object_viewer_qt/src/plugins/mission_compiler/mission_compiler_main_window.cpp @@ -23,7 +23,7 @@ #include #include -#include +#include #include #include @@ -81,7 +81,7 @@ MissionCompilerMainWindow::MissionCompilerMainWindow(QWidget *parent) : connect(Core::ICore::instance(), SIGNAL(changeSettings()), this, SLOT(handleChangedSettings())); // Set the default data dir to the primitives path. - QSettings *settings = Core::ICore::instance()->settings(); + QSettings *settings = Core::ICore::instance()->settings(); settings->beginGroup(Core::Constants::DATA_PATH_SECTION); m_lastDir = settings->value(Core::Constants::PRIMITIVES_PATH).toString(); ui->dataDirEdit->setText(m_lastDir); @@ -381,7 +381,7 @@ bool MissionCompilerMainWindow::parsePrimForMissions(NLLIGO::IPrimitive const *p { std::string value; // if the node is a mission parse it - if (prim->getPropertyByName("class",value) && !stricmp(value.c_str(),"mission") ) + if (prim->getPropertyByName("class",value) && !NLMISC::stricmp(value.c_str(),"mission") ) { std::string name; prim->getPropertyByName("name",name); @@ -460,20 +460,20 @@ void MissionCompilerMainWindow::loadConfig() { } void MissionCompilerMainWindow::saveConfig() { - QSettings *settings = Core::ICore::instance()->settings(); - settings->beginGroup(MISSION_COMPILER_SECTION); - + QSettings *settings = Core::ICore::instance()->settings(); + settings->beginGroup(MISSION_COMPILER_SECTION); + QStringList servers; for(int row = 0; row < ui->publishServersList->count(); row++) { QListWidgetItem *item = ui->publishServersList->item(row); if(item->checkState() == Qt::Checked) servers << item->text(); - } - - settings->setValue(SETTING_PUBLISH_SERVER_CHECKS, servers); - - settings->endGroup(); + } + + settings->setValue(SETTING_PUBLISH_SERVER_CHECKS, servers); + + settings->endGroup(); settings->sync(); } diff --git a/code/nel/tools/3d/panoply_maker/Makefile.am b/code/nel/tools/3d/panoply_maker/Makefile.am deleted file mode 100644 index d821ed109..000000000 --- a/code/nel/tools/3d/panoply_maker/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = panoply.cfg \ - panoply_maker.dsp - -bin_PROGRAMS = panoply_maker - -panoply_maker_SOURCES = color_modifier.cpp hls_bank_texture_info.cpp \ - panoply_maker.cpp - -noinst_HEADERS = color_mask.h \ - color_modifier.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -panoply_maker_LDADD = ../../../src/misc/libnelmisc.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/plugin_max/nel_export/std_afx.h b/code/nel/tools/3d/plugin_max/nel_export/std_afx.h index 099f2f882..7d0ef0b2a 100644 --- a/code/nel/tools/3d/plugin_max/nel_export/std_afx.h +++ b/code/nel/tools/3d/plugin_max/nel_export/std_afx.h @@ -36,7 +36,7 @@ # include # include # include -# include +# include #else # include # include diff --git a/code/nel/tools/3d/plugin_max/nel_patch_converter/script.cpp b/code/nel/tools/3d/plugin_max/nel_patch_converter/script.cpp index 1dfdc97f4..8433e5001 100644 --- a/code/nel/tools/3d/plugin_max/nel_patch_converter/script.cpp +++ b/code/nel/tools/3d/plugin_max/nel_patch_converter/script.cpp @@ -33,7 +33,7 @@ # include # include # include -# include +# include #else # include # include diff --git a/code/nel/tools/3d/tile_edit_qt/tile_listwidgetitem.h b/code/nel/tools/3d/tile_edit_qt/tile_listwidgetitem.h index b12be6437..4cf6ee1ae 100644 --- a/code/nel/tools/3d/tile_edit_qt/tile_listwidgetitem.h +++ b/code/nel/tools/3d/tile_edit_qt/tile_listwidgetitem.h @@ -21,17 +21,17 @@ #include "ui_tile_widget_qt.h" class CTile_ListWidgetItem : public QListWidgetItem - { - public: - CTile_ListWidgetItem ( QListWidget * parent, int type = Type ):QListWidgetItem(parent,type){}; +{ +public: + CTile_ListWidgetItem ( QListWidget * parent, int type = Type ):QListWidgetItem(parent,type){} CTile_ListWidgetItem(QWidget *parent = 0); void initWidget(const QPixmap&, const QString&); - private: - Ui::TileWidget ui; +private: + Ui::TileWidget ui; // Qpixmap tilePixmap; // QString tileLabel; - }; +}; #endif \ No newline at end of file diff --git a/code/nel/tools/3d/zone_dependencies/Makefile.am b/code/nel/tools/3d/zone_dependencies/Makefile.am deleted file mode 100644 index 6563d9011..000000000 --- a/code/nel/tools/3d/zone_dependencies/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = zone_dependencies.dsp - -bin_PROGRAMS = zone_dependencies - -zone_dependencies_SOURCES = zone_dependencies.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -zone_dependencies_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la \ - ../../../src/georges/libnelgeorges.la \ - ../zone_lib/libzone.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/zone_ig_lighter/Makefile.am b/code/nel/tools/3d/zone_ig_lighter/Makefile.am deleted file mode 100644 index 79d13da15..000000000 --- a/code/nel/tools/3d/zone_ig_lighter/Makefile.am +++ /dev/null @@ -1,21 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = zone_ig_lighter.dsp - -bin_PROGRAMS = zone_ig_lighter - -zone_ig_lighter_SOURCES = zone_ig_lighter.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -zone_ig_lighter_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la \ - ../zone_lib/libzone.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/zone_lib/Makefile.am b/code/nel/tools/3d/zone_lib/Makefile.am deleted file mode 100644 index e3e6927a2..000000000 --- a/code/nel/tools/3d/zone_lib/Makefile.am +++ /dev/null @@ -1,20 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -noinst_LTLIBRARIES = libzone.la - -libzone_la_SOURCES = zone_utility.cpp - -noinst_HEADERS = zone_utility.h - -AM_CXXFLAGS = -I$(top_srcdir)/src - -libzone_la_LIBADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/zone_lighter/Makefile.am b/code/nel/tools/3d/zone_lighter/Makefile.am deleted file mode 100644 index 3d70c71b4..000000000 --- a/code/nel/tools/3d/zone_lighter/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:57 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = zone_lighter.dsp - -bin_PROGRAMS = zone_lighter - -zone_lighter_SOURCES = zone_lighter.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -zone_lighter_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la \ - ../../../src/georges/libnelgeorges.la \ - ../zone_lib/libzone.la - - -# End of Makefile.am - diff --git a/code/nel/tools/3d/zone_welder/Makefile.am b/code/nel/tools/3d/zone_welder/Makefile.am deleted file mode 100644 index c405a7fa5..000000000 --- a/code/nel/tools/3d/zone_welder/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -# -# $Id: Makefile.am,v 1.1 2002/05/13 15:44:58 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = zwelder.cfg \ - zone_welder.dsp - -bin_PROGRAMS = zone_welder - -zone_welder_SOURCES = internal_weld.cpp zone_welder.cpp - -AM_CXXFLAGS = -I$(top_srcdir)/src - -zone_welder_LDADD = ../../../src/misc/libnelmisc.la \ - ../../../src/3d/libnel3d.la \ - ../zone_lib/libzone.la - - -# End of Makefile.am - diff --git a/code/nel/tools/Makefile.am b/code/nel/tools/Makefile.am deleted file mode 100644 index 1dba72e96..000000000 --- a/code/nel/tools/Makefile.am +++ /dev/null @@ -1,11 +0,0 @@ -# -# $Id: Makefile.am,v 1.2 2002/05/14 13:33:59 valignat Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -SUBDIRS = 3d misc pacs - - -# End of Makefile.am - diff --git a/code/nel/tools/georges/georges2csv/georges2csv.cpp b/code/nel/tools/georges/georges2csv/georges2csv.cpp index e10d06bf5..da89cc9bc 100644 --- a/code/nel/tools/georges/georges2csv/georges2csv.cpp +++ b/code/nel/tools/georges/georges2csv/georges2csv.cpp @@ -289,7 +289,7 @@ void addQuotesRoundString (std::string &valueString) std::string hold=valueString; valueString.erase(); valueString='\"'; - for (unsigned i=0;i -#ifdef $macro - yo_header -#endif], - have_header="yes", - have_header="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_header" = "yes" - then - AC_MSG_RESULT(yes) - else - if test "$is_mandatory" = "yes" - then - AC_MSG_ERROR([$chk_message_obj must be installed (http://www.nevrax.org).]) - else - AC_MSG_RESULT(no) - fi - fi -fi - - -]) - - -# ========================================================================= -# MY_NEL_LIB_CHK : NeL library checking macros - -AC_DEFUN([MY_NEL_LIB_CHK], -[ AC_REQUIRE_CPP() - -chk_message_obj="$1" -nel_test_lib="$2" -is_mandatory="$3" - -if test $is_mandatory = "yes" -then - - AC_CHECK_LIB($nel_test_lib, main,,[AC_MSG_ERROR([$chk_message_obj must be installed (http://www.nevrax.org).])]) -fi -]) - - -# ========================================================================= -# AM_PATH_NEL : NeL checking macros -AC_DEFUN([AM_PATH_NEL], -[ AC_REQUIRE_CPP() - -AC_ARG_WITH( nel, - [ --with-nel= path to the NeL install files directory. - e.g. /usr/local/nel]) - -AC_ARG_WITH( nel-include, - [ --with-nel-include= - path to the NeL header files directory. - e.g. /usr/local/nel/include]) - -AC_ARG_WITH( nel-lib, - [ --with-nel-lib= - path to the NeL library files directory. - e.g. /usr/local/nel/lib]) - - -nelmisc_is_mandatory="$1" -nelnet_is_mandatory="$2" -nel3d_is_mandatory="$3" -nelpacs_is_mandatory="$4" -nelsound_is_mandatory="$5" -nelai_is_mandatory="$6" -nelgeorges_is_mandatory="$7" - -# Check for nel-config -AC_PATH_PROG(NEL_CONFIG, nel-config, no) - -# -# Configure options (--with-nel*) have precendence -# over nel-config only set variables if they are not -# specified -# -if test "$NEL_CONFIG" != "no" -then - if test -z "$with_nel" -a -z "$with_nel_include" - then - CXXFLAGS="$CXXFLAGS `nel-config --cflags`" - fi - - if test -z "$with_nel" -a -z "$with_nel_lib" - then - LDFLAGS="`nel-config --ldflags` $LDFLAGS" - fi -fi - -# -# Set nel_libraries and nel_includes according to -# user specification (--with-nel*) if any. -# --with-nel-include and --with-nel-lib have precendence -# over --with-nel -# -if test "$with_nel" = "no" -then - # The user explicitly disabled the use of the NeL - AC_MSG_ERROR([NeL is mandatory: do not specify --without-nel]) -else - if test "$with_nel" -a "$with_nel" != "yes" - then - nel_includes="$with_nel/include" - nel_libraries="$with_nel/lib" - fi -fi - -if test "$with_nel_include" -then - nel_includes="$with_nel_include" -fi - -if test "$with_nel_lib" -then - nel_libraries="$with_nel_lib" -fi - -# -# Set compilation variables -# -if test "$nel_includes" -then - CXXFLAGS="$CXXFLAGS -I$nel_includes" -fi - -if test "$nel_libraries" -then - LDFLAGS="-L$nel_libraries $LDFLAGS" -fi - -# -# Collect headers information and bark if missing and -# mandatory -# - -MY_NEL_HEADER_CHK([NeL Misc], [nel/misc/types_nl.h], [NL_TYPES_H], $nelmisc_is_mandatory) -MY_NEL_HEADER_CHK([NeL Network], [nel/net/sock.h], [NL_SOCK_H], $nelnet_is_mandatory) -MY_NEL_HEADER_CHK([NeL 3D], [nel/3d/u_camera.h], [NL_U_CAMERA_H], $nel3d_is_mandatory) -MY_NEL_HEADER_CHK([NeL PACS], [nel/pacs/u_global_position.h], [NL_U_GLOBAL_POSITION_H], $nelpacs_is_mandatory) -MY_NEL_HEADER_CHK([NeL Sound], [nel/sound/u_source.h], [NL_U_SOURCE_H], $nelsound_is_mandatory) -MY_NEL_HEADER_CHK([NeL AI], [nel/ai/nl_ai.h], [_IA_NEL_H], $nelai_is_mandatory) -MY_NEL_HEADER_CHK([NeL Georges], [nel/georges/common.h], [NLGEORGES_COMMON_H], $nelgeorges_is_mandatory) - -# -# Collect libraries information and bark if missing and -# mandatory -# - -MY_NEL_LIB_CHK([NeL Misc], [nelmisc], $nelmisc_is_mandatory) -MY_NEL_LIB_CHK([NeL Network], [nelnet], $nelnet_is_mandatory) -MY_NEL_LIB_CHK([NeL 3D], [nel3d], $nel3d_is_mandatory) -MY_NEL_LIB_CHK([NeL PACS], [nelpacs], $nelpacs_is_mandatory) -MY_NEL_LIB_CHK([NeL Sound], [nelsnd], $nelsound_is_mandatory) -MY_NEL_LIB_CHK([NeL AI], [nelai], $nelai_is_mandatory) -MY_NEL_LIB_CHK([NeL Georges], [nelgeorges], $nelgeorges_is_mandatory) - -]) - -# ========================================================================= -# AM_PATH_OPENGL : OpenGL checking macros - -AC_DEFUN([AM_PATH_OPENGL], -[ AC_MSG_CHECKING(for OpenGL headers and GL Version >= 1.2) - -is_mandatory="$1" - -AC_REQUIRE_CPP() - -AC_ARG_WITH( opengl, - [ --with-opengl= path to the OpenGL install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( opengl-include, - [ --with-opengl-include= - path to the OpenGL header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( opengl-lib, - [ --with-opengl-lib= - path to the OpenGL library files directory. - e.g. /usr/local/lib]) - -opengl_lib="GL" - -if test "$with_opengl" -then - opengl_includes="$with_opengl/include" - opengl_libraries="$with_opengl/lib" -fi - -if test "$with_opengl_include" -then - opengl_includes="$with_opengl_include" -fi - -if test "$with_opengl_lib" -then - opengl_libraries="$with_opengl_lib" -fi - -# Set OPENGL_CFLAGS -if test "$opengl_includes" -then - OPENGL_CFLAGS="-I$opengl_includes" -fi - -# Set OPENGL_LIBS -if test "$opengl_libraries" -then - OPENGL_LIBS="-L$opengl_libraries" -fi -OPENGL_LIBS="$OPENGL_LIBS -l$opengl_lib" - -# Test the headers -_CPPFLAGS="$CPPFLAGS" - -CPPFLAGS="$CXXFLAGS $OPENGL_CFLAGS" - -AC_EGREP_CPP( yo_opengl, -[#include -#if defined(GL_VERSION_1_2) - yo_opengl -#endif], - have_opengl_headers="yes", - have_opengl_headers="no" ) - -if test "$have_opengl_headers" = "yes" -then - if test "$opengl_includes" - then - AC_MSG_RESULT([$opengl_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Checking the GLEXT version >= 7 -AC_MSG_CHECKING(for and GLEXT version >= 7) - -AC_EGREP_CPP( yo_glext_version, -[#include -#ifdef GL_GLEXT_VERSION -#if GL_GLEXT_VERSION >= 7 - yo_glext_version -#endif -#endif], - have_glext="yes", - have_glext="no" ) - -if test "$have_glext" = "yes" -then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT([no, can be downloaded from http://oss.sgi.com/projects/ogl-sample/ABI/]) -fi - -# Test the libraries -AC_MSG_CHECKING(for OpenGL libraries) - -CPPFLAGS="$CXXFLAGS $OPENGL_LIBS" - -AC_TRY_LINK( , , have_opengl_libraries="yes", have_opengl_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_opengl_libraries" = "yes" -then - if test "$opengl_libraries" - then - AC_MSG_RESULT([$opengl_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -opengl_libraries="$opengl_libraries" - -if test "$have_opengl_headers" = "yes" \ - -a "$have_glext" = "yes" \ - -a "$have_opengl_libraries" = "yes" -then - have_opengl="yes" -else - have_opengl="no" -fi - -if test "$have_opengl" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([OpenGL >= 1.2 must be installed (http://www.mesa3d.org)]) -fi - -AC_SUBST(OPENGL_CFLAGS) -AC_SUBST(OPENGL_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_FREETYPE : FreeType checking macros - -AC_DEFUN([AM_PATH_FREETYPE], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -AC_ARG_WITH( freetype, - [ --with-freetype= path to the FreeType install files directory. - e.g. /usr/local/freetype]) - -AC_ARG_WITH( freetype-include, - [ --with-freetype-include= - path to the FreeType header files directory. - e.g. /usr/local/freetype/include]) - -AC_ARG_WITH( freetype-lib, - [ --with-freetype-lib= - path to the FreeType library files directory. - e.g. /usr/local/freetype/lib]) - -freetype_lib="freetype" - - -AC_PATH_PROG(FREETYPE_CONFIG, freetype-config, no) - -if test "$FREETYPE_CONFIG" = "no" -then - have_freetype_config="no" -else - FREETYPE_CFLAGS=`freetype-config --cflags` - FREETYPE_LIBS=`freetype-config --libs` - have_freetype_config="yes" -fi - -if test "$with_freetype" -then - freetype_includes="$with_freetype/include" - freetype_libraries="$with_freetype/lib" -fi - -if test "$with_freetype_include" -then - freetype_includes="$with_freetype_include" -fi - -if test "$with_freetype_lib" -then - freetype_libraries="$with_freetype_lib" -fi - -if test "$freetype_includes" -then - FREETYPE_CFLAGS="-I$freetype_includes" -fi - -# Checking the FreeType 2 instalation -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS=" $FREETYPE_CFLAGS $CXXFLAGS" - -AC_MSG_CHECKING(for FreeType version = 2) - -AC_EGREP_CPP( yo_freetype2, -[#include -#if FREETYPE_MAJOR == 2 - yo_freetype2 -#endif], - have_freetype2="yes", - have_freetype2="no") - -if test "$have_freetype2" = "yes" -then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for FreeType libraries) - -if test $freetype_libraries -then - FREETYPE_LIBS="-L$freetype_libraries -l$freetype_lib" -fi - -CPPFLAGS="$FREETYPE_LIBS $CXXFLAGS" - -AC_TRY_LINK( , , have_freetype_libraries="yes", have_freetype_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_freetype_libraries" = "yes" -then - if test "$freetype_libraries" - then - AC_MSG_RESULT([$freetype_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -if test "$have_freetype2" = "yes" && test "$have_freetype_libraries" = "yes" -then - have_freetype="yes" -else - have_freetype="no" -fi - -if test "$have_freetype" = "no" && test "$is_mandatory" = "yes" -then - AC_MSG_ERROR([FreeType 2 must be installed (http://freetype.sourceforge.net)]) -fi - -AC_SUBST(FREETYPE_CFLAGS) -AC_SUBST(FREETYPE_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_XF86VIDMODE : XF86VidMode checking macros - -AC_DEFUN([AM_PATH_XF86VIDMODE], -[ AC_MSG_CHECKING(for XF86VidMode extension) - -AC_REQUIRE_CPP() - -AC_ARG_WITH( xf86vidmode-lib, - [ --with-xf86vidmode-lib= - path to the XF86VidMode library. - e.g. /usr/X11R6/lib] ) - -xf86vidmode_lib="Xxf86vm" - -if test "$with_xf86vidmode_lib" = no -then - # The user explicitly disabled the use of XF86VidMode - have_xf86vidmode="disabled" - AC_MSG_RESULT(disabled) -else - if test "$with_xf86vidmode_lib" - then - xf86vidmode_libraries="$with_xf86vidmode_lib" - fi - - XF86VIDMODE_CFLAGS="-DXF86VIDMODE" -fi - -if test -z "$have_xf86vidmode" -# -a "$with_xf86vidmode_lib" -then - if test "$xf86vidmode_libraries" - then - XF86VIDMODE_LIBS="-L$xf86vidmode_libraries" - fi - - XF86VIDMODE_LIBS="$XF86VIDMODE_LIBS -l$xf86vidmode_lib" - - _CPPFLAGS="$CPPFLAGS" - - CPPFLAGS="$CXXFLAGS $XF86VIDMODE_LIBS" - - AC_TRY_LINK( , , have_xf86vidmode_libraries="yes", have_xf86vidmode_libraries="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_xf86vidmode_libraries" = "yes" - then - have_xf86vidmode="yes" - if test "$xf86vidmode_libraries" - then - AC_MSG_RESULT($xf86vidmode_libraries) - else - AC_MSG_RESULT(yes) - fi - else - have_xf86vidmode="no" - AC_MSG_RESULT(no, no fullscreen support available.) - fi - - xf86vidmode_libraries="$xf86vidmode_libraries" - -fi - -AC_SUBST(XF86VIDMODE_CFLAGS) -AC_SUBST(XF86VIDMODE_LIBS) - -]) - - -# ========================================================================= -# AM_PATH_OPENAL : OpenAL checking macros - -AC_DEFUN([AM_PATH_OPENAL], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the OpenAL files location -AC_ARG_WITH( openal, - [ --with-openal= path to the OpenAL install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( openal-include, - [ --with-openal-include= - path to the OpenAL header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( openal-lib, - [ --with-openal-lib= - path to the OpenAL library files directory. - e.g. /usr/local/lib]) - -openal_lib="openal" -alut_lib="alut" - -if test $with_openal -then - openal_includes="$with_openal/include" - openal_libraries="$with_openal/lib" -fi - -if test "$with_openal_include" -then - openal_includes="$with_openal_include" -fi - -if test "$with_openal_lib" -then - openal_libraries="$with_openal_lib" -fi - - -# Set OPENAL_CFLAGS -if test "$openal_includes" -then - OPENAL_CFLAGS="-I$openal_includes" -fi - -# Set OPENAL_LIBS -if test "$openal_libraries" -then - OPENAL_LIBS="-L$openal_libraries" -fi -OPENAL_LIBS="$OPENAL_LIBS -l$openal_lib -l$alut_lib" - -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS="$CXXFLAGS $OPENAL_CFLAGS" - -AC_MSG_CHECKING(for OpenAL headers) -AC_EGREP_CPP( yo_openal, -[#include -#include -#ifdef AL_VERSION - yo_openal -#endif], - have_openal_headers="yes", - have_openal_headers="no" ) - -if test "$have_openal_headers" = "yes" -then - if test "$openal_includes" - then - AC_MSG_RESULT([$openal_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for OpenAL libraries) - -CPPFLAGS="$CXXFLAGS $OPENAL_LIBS" - -AC_TRY_LINK( , , have_openal_libraries="yes", have_openal_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_openal_libraries" = "yes" -then - if test "$openal_libraries" - then - AC_MSG_RESULT([$openal_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -openal_libraries="$openal_libraries" - -if test "$have_openal_headers" = "yes" \ - && test "$have_openal_libraries" = "yes" -then - have_openal="yes" -else - have_openal="no" -fi - -if test "$have_openal" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([OpenAL is needed to compile NeL (http://www.openal.org).]) -fi - -AC_SUBST(OPENAL_CFLAGS) -AC_SUBST(OPENAL_LIBS) -AC_SUBST([have_openal]) - -]) - - -# ========================================================================= -# AM_PATH_PYTHON : Python checking macros - -AC_DEFUN([AM_PATH_PYTHON], -[ python_version_required="$1" - -is_mandatory="$2" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the Python files location -AC_ARG_WITH( python, - [ --with-python= path to the Python prefix installation directory. - e.g. /usr/local], - [ PYTHON_PREFIX=$with_python ] -) - -AC_ARG_WITH( python-version, - [ --with-python-version= - Python version to use, e.g. 1.5], - [ PYTHON_VERSION=$with_python_version ] -) - -if test ! "$PYTHON_PREFIX" = "" -then - PATH="$PYTHON_PREFIX/bin:$PATH" -fi - -if test ! "$PYTHON_VERSION" = "" -then - PYTHON_EXEC="python$PYTHON_VERSION" -else - PYTHON_EXEC="python python2.1 python2.0 python1.5" -fi - -AC_PATH_PROGS(PYTHON, $PYTHON_EXEC, no, $PATH) - -if test "$PYTHON" != "no" -then - PYTHON_PREFIX=`$PYTHON -c 'import sys; print "%s" % (sys.prefix)'` - PYTHON_VERSION=`$PYTHON -c 'import sys; print "%s" % (sys.version[[:3]])'` - - is_python_version_enough=`expr $python_version_required \<= $PYTHON_VERSION` -fi - - -if test "$PYTHON" = "no" || test "$is_python_version_enough" != "1" -then - - if test "$is_mandatory" = "yes" - then - AC_MSG_ERROR([Python $python_version_required must be installed (http://www.python.org)]) - else - have_python="no" - fi - -else - - python_includes="$PYTHON_PREFIX/include/python$PYTHON_VERSION" - python_libraries="$PYTHON_PREFIX/lib/python$PYTHON_VERSION/config" - python_lib="python$PYTHON_VERSION" - - PYTHON_CFLAGS="-I$python_includes" - PYTHON_LIBS="-L$python_libraries -l$python_lib" - - _CPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CXXFLAGS ${PYTHON_CFLAGS}" - - # Test the headers - AC_MSG_CHECKING(for Python headers) - - AC_EGREP_CPP( yo_python, - [#include - yo_python - ], - have_python_headers="yes", - have_python_headers="no" ) - - if test "$have_python_headers" = "yes" - then - AC_MSG_RESULT([$python_includes]) - else - AC_MSG_RESULT(no) - fi - - # Test the libraries - AC_MSG_CHECKING(for Python libraries) - - CPPFLAGS="$CXXFLAGS $PYTHON_CFLAGS" - - AC_TRY_LINK( , , have_python_libraries="yes", have_python_libraries="no") - - CPPFLAGS="$_CPPFLAGS" - - if test "$have_python_libraries" = "yes" - then - if test "$python_libraries" - then - AC_MSG_RESULT([$python_libraries]) - else - AC_MSG_RESULT(yes) - fi - else - AC_MSG_RESULT(no) - fi - - if test "$have_python_headers" = "yes" \ - && test "$have_python_libraries" = "yes" - then - have_python="yes" - else - have_python="no" - fi - - if test "$have_python" = "no" -a "$is_mandatory" = "yes" - then - AC_MSG_ERROR([Python is needed to compile NeL (http://www.python.org).]) - fi - - AC_SUBST(PYTHON_CFLAGS) - AC_SUBST(PYTHON_LIBS) - -fi - -]) - -# ========================================================================= -# AM_PATH_MYSQL : MySQL library - -# AM_PATH_MYSQL([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) -# Test for MYSQL, and define MYSQL_CFLAGS and MYSQL_LIBS -# -AC_DEFUN([AM_PATH_MYSQL], -[# -# Get the cflags and libraries from the mysql_config script -# -AC_ARG_WITH(mysql-prefix,[ --with-mysql-prefix=PFX Prefix where MYSQL is installed (optional)], - mysql_prefix="$withval", mysql_prefix="") -AC_ARG_WITH(mysql-exec-prefix,[ --with-mysql-exec-prefix=PFX Exec prefix where MYSQL is installed (optional)], - mysql_exec_prefix="$withval", mysql_exec_prefix="") -AC_ARG_ENABLE(mysqltest, [ --disable-mysqltest Do not try to compile and run a test MYSQL program], - , enable_mysqltest=yes) - - if test x$mysql_exec_prefix != x ; then - mysql_args="$mysql_args --exec-prefix=$mysql_exec_prefix" - if test x${MYSQL_CONFIG+set} != xset ; then - MYSQL_CONFIG=$mysql_exec_prefix/bin/mysql_config - fi - fi - if test x$mysql_prefix != x ; then - mysql_args="$mysql_args --prefix=$mysql_prefix" - if test x${MYSQL_CONFIG+set} != xset ; then - MYSQL_CONFIG=$mysql_prefix/bin/mysql_config - fi - fi - - AC_REQUIRE([AC_CANONICAL_TARGET]) - AC_PATH_PROG(MYSQL_CONFIG, mysql_config, no) - min_mysql_version=ifelse([$1], ,0.11.0,$1) - AC_MSG_CHECKING(for MYSQL - version >= $min_mysql_version) - no_mysql="" - if test "$MYSQL_CONFIG" = "no" ; then - no_mysql=yes - else - MYSQL_CFLAGS=`$MYSQL_CONFIG $mysqlconf_args --cflags | sed -e "s/'//g"` - MYSQL_LIBS=`$MYSQL_CONFIG $mysqlconf_args --libs | sed -e "s/'//g"` - - mysql_major_version=`$MYSQL_CONFIG $mysql_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` - mysql_minor_version=`$MYSQL_CONFIG $mysql_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` - mysql_micro_version=`$MYSQL_CONFIG $mysql_config_args --version | \ - sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` - if test "x$enable_mysqltest" = "xyes" ; then - ac_save_CFLAGS="$CFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $MYSQL_CFLAGS" - LIBS="$LIBS $MYSQL_LIBS" -# -# Now check if the installed MYSQL is sufficiently new. (Also sanity -# checks the results of mysql_config to some extent -# - rm -f conf.mysqltest - AC_TRY_RUN([ -#include -#include -#include -#include - -char* -my_strdup (char *str) -{ - char *new_str; - - if (str) - { - new_str = (char *)malloc ((strlen (str) + 1) * sizeof(char)); - strcpy (new_str, str); - } - else - new_str = NULL; - - return new_str; -} - -int main (int argc, char *argv[]) -{ - int major, minor, micro; - char *tmp_version; - - /* This hangs on some systems (?) - system ("touch conf.mysqltest"); - */ - { FILE *fp = fopen("conf.mysqltest", "a"); if ( fp ) fclose(fp); } - - /* HP/UX 9 (%@#!) writes to sscanf strings */ - tmp_version = my_strdup("$min_mysql_version"); - if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { - printf("%s, bad version string\n", "$min_mysql_version"); - exit(1); - } - - if (($mysql_major_version > major) || - (($mysql_major_version == major) && ($mysql_minor_version > minor)) || - (($mysql_major_version == major) && ($mysql_minor_version == minor) && ($mysql_micro_version >= micro))) - { - return 0; - } - else - { - printf("\n*** 'mysql_config --version' returned %d.%d.%d, but the minimum version\n", $mysql_major_version, $mysql_minor_version, $mysql_micro_version); - printf("*** of MYSQL required is %d.%d.%d. If mysql_config is correct, then it is\n", major, minor, micro); - printf("*** best to upgrade to the required version.\n"); - printf("*** If mysql_config was wrong, set the environment variable MYSQL_CONFIG\n"); - printf("*** to point to the correct copy of mysql_config, and remove the file\n"); - printf("*** config.cache before re-running configure\n"); - return 1; - } -} - -],, no_mysql=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - fi - if test "x$no_mysql" = x ; then - AC_MSG_RESULT(yes) - ifelse([$2], , :, [$2]) - else - AC_MSG_RESULT(no) - if test "$MYSQL_CONFIG" = "no" ; then - echo "*** The mysql_config script installed by MYSQL could not be found" - echo "*** If MYSQL was installed in PREFIX, make sure PREFIX/bin is in" - echo "*** your path, or set the MYSQL_CONFIG environment variable to the" - echo "*** full path to mysql_config." - else - if test -f conf.mysqltest ; then - : - else - echo "*** Could not run MYSQL test program, checking why..." - CFLAGS="$CFLAGS $MYSQL_CFLAGS" - LIBS="$LIBS $MYSQL_LIBS" - AC_TRY_LINK([ -#include -#include - -int main(int argc, char *argv[]) -{ return 0; } -#undef main -#define main K_and_R_C_main -], [ return 0; ], - [ echo "*** The test program compiled, but did not run. This usually means" - echo "*** that the run-time linker is not finding MYSQL or finding the wrong" - echo "*** version of MYSQL. If it is not finding MYSQL, you'll need to set your" - echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" - echo "*** to the installed location Also, make sure you have run ldconfig if that" - echo "*** is required on your system" - echo "***" - echo "*** If you have an old version installed, it is best to remove it, although" - echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], - [ echo "*** The test program failed to compile or link. See the file config.log for the" - echo "*** exact error that occured. This usually means MYSQL was incorrectly installed" - echo "*** or that you have moved MYSQL since it was installed. In the latter case, you" - echo "*** may want to edit the mysql_config script: $MYSQL_CONFIG" ]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - fi - MYSQL_CFLAGS="" - MYSQL_LIBS="" - ifelse([$3], , :, [$3]) - fi - AC_SUBST(MYSQL_CFLAGS) - AC_SUBST(MYSQL_LIBS) - rm -f conf.mysqltest -]) - -# ========================================================================= -# AM_PATH_FMOD : FMOD checking macros - -AC_DEFUN([AM_PATH_FMOD], -[ is_mandatory="$1" - -AC_REQUIRE_CPP() - -# Get from the user option the path to the FMOD files location -AC_ARG_WITH( fmod, - [ --with-fmod= path to the FMOD install files directory. - e.g. /usr/local]) - -AC_ARG_WITH( fmod-include, - [ --with-fmod-include= - path to the FMOD header files directory. - e.g. /usr/local/include]) - -AC_ARG_WITH( fmod-lib, - [ --with-fmod-lib= - path to the FMOD library files directory. - e.g. /usr/local/lib]) - -fmod_lib="fmod" - -if test $with_fmod -then - fmod_includes="$with_fmod/include" - fmod_libraries="$with_fmod/lib" -fi - -if test "$with_fmod_include" -then - fmod_includes="$with_fmod_include" -fi - -if test "$with_fmod_lib" -then - fmod_libraries="$with_fmod_lib" -fi - - -# Set FMOD_CFLAGS -if test "$fmod_includes" -then - FMOD_CFLAGS="-I$fmod_includes" -fi - -# Set FMOD_LIBS -if test "$fmod_libraries" -then - FMOD_LIBS="-L$fmod_libraries" -fi -FMOD_LIBS="$FMOD_LIBS -l$fmod_lib" - -_CPPFLAGS="$CPPFLAGS" -CPPFLAGS="$CXXFLAGS $FMOD_CFLAGS" - -AC_MSG_CHECKING(for FMOD headers) -AC_EGREP_CPP( yo_fmod, -[#include -#ifdef FMOD_VERSION - yo_fmod -#endif], - have_fmod_headers="yes", - have_fmod_headers="no" ) - -if test "$have_fmod_headers" = "yes" -then - if test "$fmod_includes" - then - AC_MSG_RESULT([$fmod_includes]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -# Test the libraries -AC_MSG_CHECKING(for FMOD libraries) - -CPPFLAGS="$CXXFLAGS $FMOD_LIBS" - -AC_TRY_LINK( , , have_fmod_libraries="yes", have_fmod_libraries="no") - -CPPFLAGS="$_CPPFLAGS" - -if test "$have_fmod_libraries" = "yes" -then - if test "$fmod_libraries" - then - AC_MSG_RESULT([$fmod_libraries]) - else - AC_MSG_RESULT(yes) - fi -else - AC_MSG_RESULT(no) -fi - -fmod_libraries="$fmod_libraries" - -if test "$have_fmod_headers" = "yes" \ - && test "$have_fmod_libraries" = "yes" -then - have_fmod="yes" -else - have_fmod="no" -fi - -if test "$have_fmod" = "no" -a "$is_mandatory" = "yes" -then - AC_MSG_ERROR([FMOD is needed to compile NeL (http://www.fmod.org).]) -fi - -AC_SUBST(FMOD_CFLAGS) -AC_SUBST(FMOD_LIBS) -AC_SUBST([have_fmod]) - -]) - -# ========================================================================= -# End of file - diff --git a/code/nelns/admin_executor_service/Makefile.am b/code/nelns/admin_executor_service/Makefile.am deleted file mode 100644 index aafee52b1..000000000 --- a/code/nelns/admin_executor_service/Makefile.am +++ /dev/null @@ -1,25 +0,0 @@ -# -# $Id: Makefile.am,v 1.8 2004/03/02 09:10:50 cado Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = admin_executor_service.cfg \ - admin_executor_service.dsp \ - admin_executor_service_8.vcproj \ - admin_executor_service.vcproj \ - log_report.h \ - common.cfg - -sbin_PROGRAMS = admin_executor_service - -admin_executor_servicedir = ${pkgsysconfdir} -admin_executor_service_DATA = admin_executor_service.cfg common.cfg - -AM_CXXFLAGS = -DNELNS_CONFIG="\"${pkgsysconfdir}\"" -DNELNS_STATE="\"${pkglocalstatedir}\"" -DNELNS_LOGS="\"${logdir}\"" - -admin_executor_service_SOURCES = admin_executor_service.cpp \ - log_report.cpp - -# End of Makefile.am - diff --git a/code/nelns/admin_service/Makefile.am b/code/nelns/admin_service/Makefile.am deleted file mode 100644 index 0513d968c..000000000 --- a/code/nelns/admin_service/Makefile.am +++ /dev/null @@ -1,24 +0,0 @@ -# -# $Id: Makefile.am,v 1.7 2002/11/13 16:59:55 coutelas Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = common.cfg \ - admin_service.cfg \ - admin_service.dsp \ - admin_service_8.vcproj \ - admin_service.vcproj - -sbin_PROGRAMS = admin_service - -admin_servicedir = ${pkgsysconfdir} -admin_service_DATA = admin_service.cfg common.cfg -admin_service_LDADD = @MYSQL_LDFLAGS@ - -AM_CXXFLAGS = -DNELNS_CONFIG="\"${pkgsysconfdir}\"" -DNELNS_STATE="\"${pkglocalstatedir}\"" -DNELNS_LOGS="\"${logdir}\"" @MYSQL_CFLAGS@ - -admin_service_SOURCES = admin_service.cpp connection_web.cpp - -# End of Makefile.am - diff --git a/code/nelns/autogen.sh b/code/nelns/autogen.sh deleted file mode 100755 index 426af8b39..000000000 --- a/code/nelns/autogen.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - - -WANT_AUTOMAKE="1.6" - -echo "Creating macros..." && \ -aclocal -I automacros/ && \ -echo "Creating library tools..." && \ -libtoolize --force && \ -echo "Creating header templates..." && \ -autoheader && \ -echo "Creating Makefile templates..." && \ -automake --gnu --add-missing && \ -echo "Creating 'configure'..." && \ -autoconf && \ -echo -e "\nRun: ./configure; make; make install\n" diff --git a/code/nelns/automacros/mysql.m4 b/code/nelns/automacros/mysql.m4 deleted file mode 100644 index 4d5adf3cd..000000000 --- a/code/nelns/automacros/mysql.m4 +++ /dev/null @@ -1,101 +0,0 @@ -AC_DEFUN([AX_LIB_MYSQL], -[ - AC_ARG_WITH([mysql], - AC_HELP_STRING([--with-mysql=@<:@ARG@:>@], - [use MySQL client library @<:@default=yes@:>@, optionally specify path to mysql_config] - ), - [ - if test "$withval" = "no"; then - want_mysql="no" - elif test "$withval" = "yes"; then - want_mysql="yes" - else - want_mysql="yes" - MYSQL_CONFIG="$withval" - fi - ], - [want_mysql="yes"] - ) - - MYSQL_CFLAGS="" - MYSQL_LDFLAGS="" - MYSQL_VERSION="" - - dnl - dnl Check MySQL libraries (libpq) - dnl - - if test "$want_mysql" = "yes"; then - - if test -z "$MYSQL_CONFIG" -o test; then - AC_PATH_PROG([MYSQL_CONFIG], [mysql_config], [no]) - fi - - if test "$MYSQL_CONFIG" != "no"; then - AC_MSG_CHECKING([for MySQL libraries]) - - MYSQL_CFLAGS="`$MYSQL_CONFIG --cflags`" - MYSQL_LDFLAGS="`$MYSQL_CONFIG --libs`" - - MYSQL_VERSION=`$MYSQL_CONFIG --version` - - AC_DEFINE([HAVE_MYSQL], [1], - [Define to 1 if MySQL libraries are available]) - - found_mysql="yes" - AC_MSG_RESULT([yes]) - else - found_mysql="no" - AC_MSG_RESULT([no]) - fi - fi - - dnl - dnl Check if required version of MySQL is available - dnl - - - mysql_version_req=ifelse([$1], [], [], [$1]) - - if test "$found_mysql" = "yes" -a -n "$mysql_version_req"; then - - AC_MSG_CHECKING([if MySQL version is >= $mysql_version_req]) - - dnl Decompose required version string of MySQL - dnl and calculate its number representation - mysql_version_req_major=`expr $mysql_version_req : '\([[0-9]]*\)'` - mysql_version_req_minor=`expr $mysql_version_req : '[[0-9]]*\.\([[0-9]]*\)'` - mysql_version_req_micro=`expr $mysql_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` - if test "x$mysql_version_req_micro" = "x"; then - mysql_version_req_micro="0" - fi - - mysql_version_req_number=`expr $mysql_version_req_major \* 1000000 \ - \+ $mysql_version_req_minor \* 1000 \ - \+ $mysql_version_req_micro` - - dnl Decompose version string of installed MySQL - dnl and calculate its number representation - mysql_version_major=`expr $MYSQL_VERSION : '\([[0-9]]*\)'` - mysql_version_minor=`expr $MYSQL_VERSION : '[[0-9]]*\.\([[0-9]]*\)'` - mysql_version_micro=`expr $MYSQL_VERSION : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` - if test "x$mysql_version_micro" = "x"; then - mysql_version_micro="0" - fi - - mysql_version_number=`expr $mysql_version_major \* 1000000 \ - \+ $mysql_version_minor \* 1000 \ - \+ $mysql_version_micro` - - mysql_version_check=`expr $mysql_version_number \>\= $mysql_version_req_number` - if test "$mysql_version_check" = "1"; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no]) - fi - fi - - AC_SUBST([MYSQL_VERSION]) - AC_SUBST([MYSQL_CFLAGS]) - AC_SUBST([MYSQL_LDFLAGS]) -]) diff --git a/code/nelns/configure.ac b/code/nelns/configure.ac deleted file mode 100644 index ef7f65029..000000000 --- a/code/nelns/configure.ac +++ /dev/null @@ -1,186 +0,0 @@ -# ==================================================================== -# Configuration script for NeLNS -# ==================================================================== -# -# $Id: configure.ac,v 1.4 2005/01/07 18:29:56 distrib Exp $ -# - -# ==================================================================== -# Process this file with autoconf to produce a configure script. -# ==================================================================== - -# If you want to change the version, must must change AC_INIT -# *and* AC_SUBST(LIBTOOL_VERSION) - -AC_PREREQ(2.57) -AC_INIT([nelns],[0.5.0],[nel-all@nevrax.org]) - -AC_CANONICAL_TARGET - -AM_INIT_AUTOMAKE([tar-ustar]) - -AM_CONFIG_HEADER(config.h) - -AC_SUBST(LIBTOOL_VERSION, [0:5:0]) - -# ==================================================================== -# Checks for programs. -# ==================================================================== - - -AC_PROG_CXX -AC_PROG_CPP -AC_PROG_INSTALL -AC_PROG_LN_S -AC_PROG_MAKE_SET -AC_PROG_LIBTOOL -AM_PROG_LIBTOOL -AM_SANITY_CHECK - -AC_SYS_LARGEFILE - -AM_MAINTAINER_MODE - -# The following hack should ensure that configure doesn't add optimizing -# or debugging flags to CFLAGS or CXXFLAGS -CFLAGS="$CFLAGS " -CXXFLAGS="$CXXFLAGS " - -# Template needed to generate the config.h.in -#AH_TEMPLATE([NELNS_CONFIG],[Configuration files directory path]) -#AH_TEMPLATE([NELNS_LOGS],[Log files directory path]) -#AH_TEMPLATE([NELNS_STATE],[Local state files directory path]) -AH_TEMPLATE([HAVE_MYSQL],[Define to 1 if mysql was found]) - - -AX_LIB_MYSQL([3.23.40]) -#dnl AM_PATH_MYSQL(3.23.40,,AC_MSG_ERROR([MySQL 3.23.40 or higher must be installed])) - -AC_DEFINE(HAVE_MYSQL) - -CFLAGS="$CFLAGS $MYSQL_CFLAGS" -CXXFLAGS="$CXXFLAGS $MYSQL_CFLAGS" - -# prevent using GLIBC2.4 stuffs -CFLAGS="$CFLAGS -fno-stack-protector" -CXXFLAGS="$CXXFLAGS -fno-stack-protector" - -LIBS="$LIBS -lrt $MYSQL_LIBS" - - -# ==================================================================== -# Configure Settings -# ==================================================================== - -AC_LANG([C++]) - - -# ==================================================================== -# Debug/optimized compilation mode -# ==================================================================== - -AM_NEL_DEBUG - -# Set the gcc specific warning level -if test "X$CC" = "Xgcc"; -then - if test "$with_debug" != "no" - then - # - # When debugging variables are declared for the sole purpose of - # inspecting their content with a debugger. They are not used - # in the code itself and this is legitimate, hence the -Wno-unused - # - CXXFLAGS="$CXXFLAGS -Wall -Wno-multichar -Wno-unused" - else - CXXFLAGS="$CXXFLAGS -Wall -Wno-multichar" - fi -fi - -# ==================================================================== -# Checks for typedefs, structures, and compiler characteristics. -# ==================================================================== - -# Add the define _REENTRANT for a correct use of the threads -if test "X$CC" = "Xgcc"; -then - CXXFLAGS="$CXXFLAGS -D_REENTRANT" -fi - - -# ==================================================================== -# Checks for header files. -# ==================================================================== - -AC_HEADER_STDC([]) -AC_CHECK_HEADERS(fcntl.h unistd.h) - - -# ==================================================================== -# Checks for libraries. -# ==================================================================== - -AC_CHECK_LIB(crypt, crypt) - - -# ==================================================================== -# NeL - -# misc net 3d pacs sound ai georges -AM_PATH_NEL("yes", "yes", "no", "no", "no", "no", "no") - -# ==================================================================== -# Checks for library functions. -# ==================================================================== - -# ==================================================================== -# nelns configuration and logs file location -# ==================================================================== - -test "x$prefix" = xNONE && prefix=$ac_default_prefix - -AC_ARG_WITH(sysconfdir, - [ --with-sysconfdir[=DIR] - use DIR instead of the default sysconfdir/nelns], - [pkgsysconfdir=$withval], - [pkgsysconfdir='${sysconfdir}/nelns']) -AC_SUBST(pkgsysconfdir) - -#eval eval NELNS_CONFIG=\"$pkgsysconfdir/\" -#AC_DEFINE_UNQUOTED(NELNS_CONFIG, "$NELNS_CONFIG") - -AC_ARG_WITH(localstatedir, - [ --with-localstatedir[=DIR] - use DIR instead of the default localstatedir/game/nelns], - [pkglocalstatedir=$withval], - [pkglocalstatedir=${localstatedir}/game/nelns]) -AC_SUBST(pkglocalstatedir) - -#eval eval NELNS_STATE=\"$pkglocalstatedir/\" -#AC_DEFINE_UNQUOTED(NELNS_STATE, "$NELNS_STATE") - -AC_ARG_WITH(logdir, - [ --with-logdir[=DIR] - use DIR instead of the default localstatedir/log/nelns], - [logdir=$withval], - [logdir='${localstatedir}/log/nelns']) -AC_SUBST(logdir) - -#eval eval NELNS_LOGS=\"$logdir/\" -#AC_DEFINE_UNQUOTED(NELNS_LOGS, "$NELNS_LOGS") - -# ==================================================================== -# Output files to generate. -# ==================================================================== - -AC_CONFIG_FILES([Makefile \ - login_service/Makefile \ - naming_service/Makefile \ - admin_executor_service/Makefile \ - admin_service/Makefile \ - welcome_service/Makefile \ -]) -AC_OUTPUT - - -# End of configure.ac diff --git a/code/nelns/login_service/Makefile.am b/code/nelns/login_service/Makefile.am deleted file mode 100644 index 15a9eae6c..000000000 --- a/code/nelns/login_service/Makefile.am +++ /dev/null @@ -1,33 +0,0 @@ -# -# $Id: Makefile.am,v 1.8 2002-09-16 14:50:07 lecroart Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = login_service.cfg \ - login_service.dsp \ - login_service.vcproj \ - login_service_8.vcproj \ - common.cfg - -sbin_PROGRAMS = login_service - -login_servicedir = ${pkgsysconfdir} -login_service_DATA = login_service.cfg common.cfg -login_service_LDADD = @MYSQL_LDFLAGS@ - -AM_CXXFLAGS = -DNELNS_CONFIG="\"${pkgsysconfdir}\"" -DNELNS_STATE="\"${pkglocalstatedir}\"" -DNELNS_LOGS="\"${logdir}\"" @MYSQL_CFLAGS@ - -login_service_SOURCES = mysql_helper.cpp \ - mysql_helper.h \ - connection_client.cpp \ - connection_client.h \ - connection_web.cpp \ - connection_web.h \ - connection_ws.cpp \ - connection_ws.h \ - login_service.cpp \ - login_service.h - -# End of Makefile.am - diff --git a/code/nelns/login_service/mysql_helper.h b/code/nelns/login_service/mysql_helper.h index cc81f4647..7f6893b4a 100644 --- a/code/nelns/login_service/mysql_helper.h +++ b/code/nelns/login_service/mysql_helper.h @@ -70,7 +70,7 @@ public: private: //we don't want user to do a copy - CMysqlResult(const CMysqlResult &mysqlResult){}; + CMysqlResult(const CMysqlResult &mysqlResult){} MYSQL_RES *Result; }; diff --git a/code/nelns/login_system/nel_launcher_windows_ext/BarTabsWnd.h b/code/nelns/login_system/nel_launcher_windows_ext/BarTabsWnd.h index eefb93653..2437ae854 100644 --- a/code/nelns/login_system/nel_launcher_windows_ext/BarTabsWnd.h +++ b/code/nelns/login_system/nel_launcher_windows_ext/BarTabsWnd.h @@ -14,9 +14,9 @@ class CTabsObserver { public: - CTabsObserver() {}; + CTabsObserver() {} - virtual void OnTab(int iTab) {}; + virtual void OnTab(int iTab) {} }; class CBarTabsWnd : public CWnd diff --git a/code/nelns/naming_service/Makefile.am b/code/nelns/naming_service/Makefile.am deleted file mode 100644 index 95e510f7f..000000000 --- a/code/nelns/naming_service/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -# -# $Id: Makefile.am,v 1.5 2002/06/12 10:22:28 lecroart Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = naming_service.cfg \ - naming_service.dsp \ - naming_service.vcproj \ - naming_service_8.vcproj \ - common.cfg - -sbin_PROGRAMS = naming_service - -naming_servicedir = ${pkgsysconfdir} -naming_service_DATA = naming_service.cfg common.cfg - -AM_CXXFLAGS = -DNELNS_CONFIG="\"${pkgsysconfdir}\"" -DNELNS_STATE="\"${pkglocalstatedir}\"" -DNELNS_LOGS="\"${logdir}\"" - -naming_service_SOURCES = naming_service.cpp - -# End of Makefile.am diff --git a/code/nelns/welcome_service/Makefile.am b/code/nelns/welcome_service/Makefile.am deleted file mode 100644 index 35b7d6eb5..000000000 --- a/code/nelns/welcome_service/Makefile.am +++ /dev/null @@ -1,23 +0,0 @@ -# -# $Id: Makefile.am,v 1.7 2006/05/31 12:14:16 boucher Exp $ -# - -MAINTAINERCLEANFILES = Makefile.in - -EXTRA_DIST = welcome_service.cfg \ - welcome_service.dsp \ - welcome_service.vcproj \ - welcome_service_8.vcproj \ - common.cfg - -sbin_PROGRAMS = welcome_service - -welcome_servicedir = ${pkgsysconfdir} -welcome_service_DATA = welcome_service.cfg common.cfg - -AM_CXXFLAGS = -DNELNS_CONFIG="\"${pkgsysconfdir}\"" -DNELNS_STATE="\"${pkglocalstatedir}\"" -DNELNS_LOGS="\"${logdir}\"" - -welcome_service_SOURCES = welcome_service.cpp \ - welcome_service_itf.cpp - -# End of Makefile.am diff --git a/code/revision.h.in b/code/revision.h.in new file mode 100644 index 000000000..6c5e9b8b1 --- /dev/null +++ b/code/revision.h.in @@ -0,0 +1,7 @@ +#ifndef REVISION_H +#define REVISION_H + +#cmakedefine REVISION "${REVISION}" +#cmakedefine BUILD_DATE "${BUILD_DATE}" + +#endif diff --git a/code/ryzom/CMakeLists.txt b/code/ryzom/CMakeLists.txt index 4202738c1..eb3d1c642 100644 --- a/code/ryzom/CMakeLists.txt +++ b/code/ryzom/CMakeLists.txt @@ -1,18 +1,7 @@ #----------------------------------------------------------------------------- #Platform specifics -IF(WITH_LUA51) - FIND_PACKAGE(Lua51 REQUIRED) -ELSE(WITH_LUA51) - FIND_PACKAGE(Lua50 REQUIRED) -ENDIF(WITH_LUA51) -FIND_PACKAGE(Luabind REQUIRED) -FIND_PACKAGE(CURL REQUIRED) -FIND_PACKAGE(Libwww REQUIRED) FIND_PACKAGE(ZLIB) -IF(NOT WIN32 AND NOT APPLE) - FIND_PACKAGE(X11) -ENDIF(NOT WIN32 AND NOT APPLE) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/common/src ) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/common ) @@ -30,6 +19,15 @@ ENDIF(WITH_STATIC) ADD_SUBDIRECTORY(common) IF(WITH_RYZOM_CLIENT) + IF(WITH_LUA51) + FIND_PACKAGE(Lua51 REQUIRED) + ELSE(WITH_LUA51) + FIND_PACKAGE(Lua50 REQUIRED) + ENDIF(WITH_LUA51) + FIND_PACKAGE(Luabind REQUIRED) + FIND_PACKAGE(CURL REQUIRED) + FIND_PACKAGE(Libwww REQUIRED) + ADD_SUBDIRECTORY(client) ENDIF(WITH_RYZOM_CLIENT) diff --git a/code/ryzom/Makefile b/code/ryzom/Makefile deleted file mode 100644 index 2a1850c35..000000000 --- a/code/ryzom/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -MAKE_NIGHT_ARGS=-j2 -MAKE_ALL_ARGS=-j2 -CXX=g++ -CC=g++ -BIN_DIR=$(RYZOM_PATH)/tools/scripts/linux - -night: - +make $(MAKE_NIGHT_ARGS) -k -C common night CXX=$(CXX) - +make $(MAKE_NIGHT_ARGS) -k -C server night CXX=$(CXX) - -all: - +make $(MAKE_ALL_ARGS) -C common all - +make $(MAKE_ALL_ARGS) -C server all - -mono: - +make -C common all - +make -C server all - -clean: - +make -C common clean - +make -C server clean - -distclean: - +make -C common distclean - +make -C server distclean - -cleansheets: - +make -C common cleansheets - +make -C server cleansheets - -update: - +make -C common update - +make -C server update - -touch: - +make -C common touch - +make -C server touch diff --git a/code/ryzom/Rules.mk b/code/ryzom/Rules.mk deleted file mode 100644 index 67bc3bc42..000000000 --- a/code/ryzom/Rules.mk +++ /dev/null @@ -1,64 +0,0 @@ -############################################################################# -# A few basic default rules and intrinsic rules - -# Load objects dependencies -ifeq (Dependencies.mk,$(wildcard Dependencies.mk)) -include Dependencies.mk -check-deps: - @echo - @echo Dependencies found [OK] - @echo -else -check-deps: - @echo - @echo "No dependencies found [ERROR]" - @echo "You should try 'make update' first" - @echo - @exit 1 -endif - -# Start off by over-riding the default build rules with our own intrinsics -.SUFFIXES: -.SUFFIXES: .cpp .o -.cpp.o: - $(CXX) -c $(CXXFLAGS) $< -o $@ - -# remove object files and core (if any) -clean: - find . -name "core*" -exec $(RM) {} \; - find . -name "*.o" -exec $(RM) {} \; - find . -name "*~" -exec $(RM) {} \; - find . -name "Dependencies.mk" -exec $(RM) {} \; - find . -name "Objects.mk" -exec $(RM) {} \; - -cleansheets: - find . -name "*.packed_sheets" -exec $(RM) {} \; - -# remove object files, core dump, and executable (if any) -distclean: - $(MAKE) clean - $(RM) $(TARGETS) - $(RM) $(TARGETS)_debug - -# make the thing again from scratch -again: - $(MAKE) distclean - $(MAKE) $(TARGETS) - -#UPDATE_OBJS=`cat $(DSP_TARGET) | grep SOURCE | sed -e 's/\r$$//' | grep "\.cpp$$" | cut -d\\\\ -f3- | tr '\n' ' ' | sed -e 's/=/..\\\\/g' | tr '\n' ' ' | sed -e 's/\\\\/\\//g' | sed -e 's/\.cpp /\.o /g'` - -#UPDATE_SRCS=`cat $(DSP_TARGET) | grep SOURCE | sed -e 's/\r$$//' | grep "\.cpp$$" | cut -d\\\\ -f3- | tr '\n' ' ' | sed -e 's/=/..\\\\/g' | tr '\n' ' ' | sed -e 's/\\\\/\\//g'` - -UPDATE_OBJS=`cat $(DSP_TARGET) | grep RelativePath | sed -e 's/\\"\r$$//' | grep "\.cpp$$" | cut -d\\\\ -f2- | tr '\n' ' ' | sed -e 's/\\\\/\\//g' | sed -e 's/\.cpp /\.o /g'` -UPDATE_SRCS=`cat $(DSP_TARGET) | grep RelativePath | sed -e 's/\\"\r$$//' | grep "\.cpp$$" | cut -d\\\\ -f2- | tr '\n' ' ' | sed -e 's/\\\\/\\//g'` - -dep: update - -update: -# ../gen_compile_flags.sh > RyzomCompilerFlags.mk - $(RYZOM_PATH)/gen_deps.sh $(CXX) $(CXXFLAGS) -- $(UPDATE_SRCS) > Dependencies.mk - echo "OBJS=$(UPDATE_OBJS)" > Objects.mk - -touch: - $(RM) $(TARGETS) - $(RM) $(TARGETS)_debug diff --git a/code/ryzom/Variables.mk b/code/ryzom/Variables.mk deleted file mode 100644 index a952922f0..000000000 --- a/code/ryzom/Variables.mk +++ /dev/null @@ -1,33 +0,0 @@ -############################################################################# -# Setting up the global compiler settings... - -# The names of the executables -CXX = c++ -RM = rm -f -MAKE = make - -DBG = off - -ifeq (RyzomCompilerFlags.mk,$(wildcard RyzomCompilerFlags.mk)) -include RyzomCompilerFlags.mk -endif - -FLAGS_CMN = -g -pipe -fno-stack-protector -fno-strict-aliasing -Wall -D_REENTRANT -D_GNU_SOURCE -DFINAL_VERSION=1 -LD_FLAGS_CMN = -rdynamic - -FLAGS_DBG_on = -O0 -finline-functions -DNL_DEBUG -FLAGS_DBG_off = -O3 -funroll-loops -DNL_RELEASE -DIR_DBG_on = debug -DIR_DBG_off = release - -PACK_SHEETS_FLAGS = -A$RYZOM_PATH/server -L$RYZOM_PATH/server -C$RYZOM_PATH//server/sheet_pack_cfg -Q --nons - - -NEL_PATH = $(RYZOM_PATH)/../../code/install/$(DIR_DBG_$(DBG)) - -NEL_INCLUDE = $(RYZOM_PATH)/../../code/nel/include -RYZOM_COMMON_SRC = $(RYZOM_PATH)/common/src - -ifeq (Objects.mk,$(wildcard Objects.mk)) -include Objects.mk -endif diff --git a/code/ryzom/client/Makefile b/code/ryzom/client/Makefile deleted file mode 100644 index 11e58485a..000000000 --- a/code/ryzom/client/Makefile +++ /dev/null @@ -1,18 +0,0 @@ - -all: - +make $(MAKE_ALL_ARGS) -C src all - -mono: - +make -C src all - -clean: - +make -C src clean - -cleansheets: - +make -C src cleansheets - -update: - +make -C src update - -touch: - +make -C src touch diff --git a/code/ryzom/client/client_default.cfg b/code/ryzom/client/client_default.cfg index 14885fa01..997699ca8 100644 --- a/code/ryzom/client/client_default.cfg +++ b/code/ryzom/client/client_default.cfg @@ -354,8 +354,8 @@ SystemInfoColors = // NEW System Info Categories "SYS", "255 255 255 255 normal", // Default system messages -"BC", "255 255 255 255 center", // Broadcast messages -"TAGBC", "255 255 255 255 center", // Taged broadcast messages : color should remain white as some word are tagged +"BC", "255 255 255 255 centeraround", // Broadcast messages +"TAGBC", "255 255 255 255 centeraround", // Taged broadcast messages : color should remain white as some word are tagged "XP", "255 255 64 255 over", // XP Gain "SP", "255 255 64 255 over", // SP Gain "TTL", "255 255 64 255 over", // Title @@ -443,23 +443,24 @@ R2EDReloadFiles = { XMLInterfaceFiles = { "config.xml", "widgets.xml", + "webig_widgets.xml", "player.xml", "inventory.xml", "interaction.xml", - "phrase.xml", + "phrase.xml", "harvest.xml", "macros.xml", "info_player.xml", "outpost.xml", "guild.xml", "taskbar.xml", - "game_config.xml", + "game_config.xml", "game_context_menu.xml", "player_trade.xml", "bot_chat_v4.xml", "compass.xml", "map.xml", - "hierarchy.xml", + "hierarchy.xml", "reset.xml", "actions.xml", "help.xml", @@ -559,6 +560,15 @@ HelpPages = "de=http://forums.ryzom.com/forum/showthread.php?t=29131" }; +WebIgMainDomain = "atys.ryzom.com"; + +WebIgTrustedDomains = +{ + "atys.ryzom.com" +}; + +PatchletUrl = "http://atys.ryzom.com/start/app_patchlet.php?patch=preload"; + SelectedSlot = 0; BuildName = "RELEASE_HEAD"; diff --git a/code/ryzom/client/client_default.cfg.in b/code/ryzom/client/client_default.cfg.in index 8ae0117a5..41c3dd1de 100644 --- a/code/ryzom/client/client_default.cfg.in +++ b/code/ryzom/client/client_default.cfg.in @@ -80,6 +80,9 @@ XMLOutGameInterfaceFiles = { "out_v2_keys.xml", }; +TexturesInterface = "texture_interfaces_v3"; +TexturesInterfaceDXTC = "texture_interfaces_dxtc"; + // The ligo primitive class file LigoPrimitiveClass = "world_editor_classes.xml"; @@ -352,8 +355,8 @@ SystemInfoColors = // NEW System Info Categories "SYS", "255 255 255 255 normal", // Default system messages -"BC", "255 255 255 255 center", // Broadcast messages -"TAGBC", "255 255 255 255 center", // Taged broadcast messages : color should remain white as some word are tagged +"BC", "255 255 255 255 centeraround", // Broadcast messages +"TAGBC", "255 255 255 255 centeraround", // Taged broadcast messages : color should remain white as some word are tagged "XP", "255 255 64 255 over", // XP Gain "SP", "255 255 64 255 over", // SP Gain "TTL", "255 255 64 255 over", // Title @@ -441,23 +444,24 @@ R2EDReloadFiles = { XMLInterfaceFiles = { "config.xml", "widgets.xml", + "webig_widgets.xml", "player.xml", "inventory.xml", "interaction.xml", - "phrase.xml", + "phrase.xml", "harvest.xml", "macros.xml", "info_player.xml", "outpost.xml", "guild.xml", "taskbar.xml", - "game_config.xml", + "game_config.xml", "game_context_menu.xml", "player_trade.xml", "bot_chat_v4.xml", "compass.xml", "map.xml", - "hierarchy.xml", + "hierarchy.xml", "reset.xml", "actions.xml", "help.xml", @@ -557,6 +561,15 @@ HelpPages = "de=http://forums.ryzom.com/forum/showthread.php?t=29131" }; +WebIgMainDomain = "atys.ryzom.com"; + +WebIgTrustedDomains = +{ + "atys.ryzom.com" +}; + +PatchletUrl = "http://atys.ryzom.com/start/app_patchlet.php?patch=preload"; + SelectedSlot = 0; BuildName = "RELEASE_HEAD"; diff --git a/code/ryzom/client/data/gamedev/adds/interfaces/new_texture_interfaces_dxtc.tga b/code/ryzom/client/data/gamedev/adds/interfaces/new_texture_interfaces_dxtc.tga index bbeeab27b..91e87e2e7 100644 Binary files a/code/ryzom/client/data/gamedev/adds/interfaces/new_texture_interfaces_dxtc.tga and b/code/ryzom/client/data/gamedev/adds/interfaces/new_texture_interfaces_dxtc.tga differ diff --git a/code/ryzom/client/data/gamedev/adds/interfaces/new_texture_interfaces_dxtc.txt b/code/ryzom/client/data/gamedev/adds/interfaces/new_texture_interfaces_dxtc.txt index ec1afb449..f35989e33 100644 --- a/code/ryzom/client/data/gamedev/adds/interfaces/new_texture_interfaces_dxtc.txt +++ b/code/ryzom/client/data/gamedev/adds/interfaces/new_texture_interfaces_dxtc.txt @@ -18,305 +18,305 @@ BK_goo.tga 0.156250000000 0.078125000000 0.195312500000 0.117187500000 bk_guild.tga 0.195312500000 0.078125000000 0.234375000000 0.117187500000 bk_horde.tga 0.000000000000 0.117187500000 0.039062500000 0.156250000000 bk_kami.tga 0.039062500000 0.117187500000 0.078125000000 0.156250000000 -bk_karavan.tga 0.078125000000 0.117187500000 0.117187500000 0.156250000000 -BK_matis.tga 0.117187500000 0.117187500000 0.156250000000 0.156250000000 -bk_mission.tga 0.156250000000 0.117187500000 0.195312500000 0.156250000000 -bk_mission2.tga 0.195312500000 0.117187500000 0.234375000000 0.156250000000 -BK_outpost.tga 0.000000000000 0.156250000000 0.039062500000 0.195312500000 -BK_primes.tga 0.039062500000 0.156250000000 0.078125000000 0.195312500000 -bk_service.tga 0.078125000000 0.156250000000 0.117187500000 0.195312500000 -bk_training.tga 0.117187500000 0.156250000000 0.156250000000 0.195312500000 -BK_tryker.tga 0.156250000000 0.156250000000 0.195312500000 0.195312500000 -BK_zorai.tga 0.195312500000 0.156250000000 0.234375000000 0.195312500000 -charge.tga 0.000000000000 0.195312500000 0.039062500000 0.234375000000 -clef.tga 0.039062500000 0.195312500000 0.078125000000 0.234375000000 -conso_branche.tga 0.078125000000 0.195312500000 0.117187500000 0.234375000000 -conso_branche_mask.tga 0.117187500000 0.195312500000 0.156250000000 0.234375000000 -conso_fleur.tga 0.156250000000 0.195312500000 0.195312500000 0.234375000000 -conso_fleur_mask.tga 0.195312500000 0.195312500000 0.234375000000 0.234375000000 -conso_grappe.tga 0.242187500000 0.000000000000 0.281250000000 0.039062500000 -conso_grappe_mask.tga 0.281250000000 0.000000000000 0.320312500000 0.039062500000 -conso_nectar.tga 0.320312500000 0.000000000000 0.359375000000 0.039062500000 -conso_nectar_mask.tga 0.359375000000 0.000000000000 0.398437500000 0.039062500000 -construction.tga 0.398437500000 0.000000000000 0.437500000000 0.039062500000 -cristal_ammo.tga 0.437500000000 0.000000000000 0.476562500000 0.039062500000 -cristal_spell.tga 0.238281250000 0.039062500000 0.277343750000 0.078125000000 -ge_mission_outpost_townhall.tga 0.277343750000 0.039062500000 0.316406250000 0.078125000000 -ico_amande.tga 0.316406250000 0.039062500000 0.355468750000 0.078125000000 -ico_cataliseur_xp.tga 0.355468750000 0.039062500000 0.394531250000 0.078125000000 -ico_consommable_over.tga 0.394531250000 0.039062500000 0.433593750000 0.078125000000 -ico_fleur_carac_1.tga 0.433593750000 0.039062500000 0.472656250000 0.078125000000 -ico_fleur_carac_1_mask.tga 0.234375000000 0.078125000000 0.273437500000 0.117187500000 -ico_fleur_carac_2.tga 0.273437500000 0.078125000000 0.312500000000 0.117187500000 -ico_fleur_carac_2_mask.tga 0.312500000000 0.078125000000 0.351562500000 0.117187500000 -ico_fleur_carac_3.tga 0.351562500000 0.078125000000 0.390625000000 0.117187500000 -ico_fleur_carac_3_mask.tga 0.390625000000 0.078125000000 0.429687500000 0.117187500000 -ico_foreuse.tga 0.429687500000 0.078125000000 0.468750000000 0.117187500000 -ico_haircolor.tga 0.234375000000 0.117187500000 0.273437500000 0.156250000000 -ico_haircut.tga 0.273437500000 0.117187500000 0.312500000000 0.156250000000 -ico_mission_art_fyros.tga 0.312500000000 0.117187500000 0.351562500000 0.156250000000 -ico_mission_art_matis.tga 0.351562500000 0.117187500000 0.390625000000 0.156250000000 -ico_mission_art_tryker.tga 0.390625000000 0.117187500000 0.429687500000 0.156250000000 -ico_mission_art_zorai.tga 0.429687500000 0.117187500000 0.468750000000 0.156250000000 -ico_mission_barrel.tga 0.234375000000 0.156250000000 0.273437500000 0.195312500000 -ico_mission_bottle.tga 0.273437500000 0.156250000000 0.312500000000 0.195312500000 -ico_mission_casket.tga 0.312500000000 0.156250000000 0.351562500000 0.195312500000 -ico_mission_medicine.tga 0.351562500000 0.156250000000 0.390625000000 0.195312500000 -ico_mission_message.tga 0.390625000000 0.156250000000 0.429687500000 0.195312500000 -ico_mission_package.tga 0.429687500000 0.156250000000 0.468750000000 0.195312500000 -ico_mission_pot.tga 0.234375000000 0.195312500000 0.273437500000 0.234375000000 -ico_mission_purse.tga 0.273437500000 0.195312500000 0.312500000000 0.234375000000 -ico_noix.tga 0.312500000000 0.195312500000 0.351562500000 0.234375000000 -ico_racine.tga 0.351562500000 0.195312500000 0.390625000000 0.234375000000 -ico_spores.tga 0.390625000000 0.195312500000 0.429687500000 0.234375000000 -ico_task_craft.tga 0.429687500000 0.195312500000 0.468750000000 0.234375000000 -ico_task_done.tga 0.000000000000 0.234375000000 0.039062500000 0.273437500000 -ico_task_failed.tga 0.039062500000 0.234375000000 0.078125000000 0.273437500000 -ico_task_fight.tga 0.078125000000 0.234375000000 0.117187500000 0.273437500000 -ico_task_forage.tga 0.117187500000 0.234375000000 0.156250000000 0.273437500000 -ico_task_generic.tga 0.156250000000 0.234375000000 0.195312500000 0.273437500000 -ico_task_generic_quart.tga 0.195312500000 0.234375000000 0.234375000000 0.273437500000 -ico_task_guild.tga 0.234375000000 0.234375000000 0.273437500000 0.273437500000 -ico_task_rite.tga 0.273437500000 0.234375000000 0.312500000000 0.273437500000 -ico_task_travel.tga 0.312500000000 0.234375000000 0.351562500000 0.273437500000 -ico_tatoo.tga 0.351562500000 0.234375000000 0.390625000000 0.273437500000 -ico_tourbe.tga 0.390625000000 0.234375000000 0.429687500000 0.273437500000 -improved_tool.tga 0.429687500000 0.234375000000 0.468750000000 0.273437500000 -item_default.tga 0.000000000000 0.273437500000 0.039062500000 0.312500000000 -item_plan_over.tga 0.039062500000 0.273437500000 0.078125000000 0.312500000000 -lucky_flower.tga 0.078125000000 0.273437500000 0.117187500000 0.312500000000 -mektoub_pack.tga 0.117187500000 0.273437500000 0.156250000000 0.312500000000 -mektoub_steed.tga 0.156250000000 0.273437500000 0.195312500000 0.312500000000 -mg_glove.tga 0.195312500000 0.273437500000 0.234375000000 0.312500000000 -mission_icon_0.tga 0.234375000000 0.273437500000 0.273437500000 0.312500000000 -mission_icon_1.tga 0.273437500000 0.273437500000 0.312500000000 0.312500000000 -mission_icon_2.tga 0.312500000000 0.273437500000 0.351562500000 0.312500000000 -mission_icon_3.tga 0.351562500000 0.273437500000 0.390625000000 0.312500000000 -mp_amber.tga 0.390625000000 0.273437500000 0.429687500000 0.312500000000 -mp_bark.tga 0.429687500000 0.273437500000 0.468750000000 0.312500000000 -mp_batiment_brique.tga 0.000000000000 0.312500000000 0.039062500000 0.351562500000 -mp_batiment_colonne.tga 0.039062500000 0.312500000000 0.078125000000 0.351562500000 -mp_batiment_colonne_justice.tga 0.078125000000 0.312500000000 0.117187500000 0.351562500000 -mp_batiment_comble.tga 0.117187500000 0.312500000000 0.156250000000 0.351562500000 -mp_batiment_noyau_maduk.tga 0.156250000000 0.312500000000 0.195312500000 0.351562500000 -mp_batiment_ornement.tga 0.195312500000 0.312500000000 0.234375000000 0.351562500000 -mp_batiment_revetement.tga 0.234375000000 0.312500000000 0.273437500000 0.351562500000 -mp_batiment_socle.tga 0.273437500000 0.312500000000 0.312500000000 0.351562500000 -mp_batiment_statue.tga 0.312500000000 0.312500000000 0.351562500000 0.351562500000 -mp_beak.tga 0.351562500000 0.312500000000 0.390625000000 0.351562500000 -mp_blood.tga 0.390625000000 0.312500000000 0.429687500000 0.351562500000 -mp_bone.tga 0.429687500000 0.312500000000 0.468750000000 0.351562500000 -mp_bud.tga 0.000000000000 0.351562500000 0.039062500000 0.390625000000 -mp_buterfly_blue.tga 0.039062500000 0.351562500000 0.078125000000 0.390625000000 -mp_buterfly_cocoon.tga 0.078125000000 0.351562500000 0.117187500000 0.390625000000 -mp_cereal.tga 0.117187500000 0.351562500000 0.156250000000 0.390625000000 -mp_claw.tga 0.156250000000 0.351562500000 0.195312500000 0.390625000000 -mp_dandelion.tga 0.195312500000 0.351562500000 0.234375000000 0.390625000000 -mp_dust.tga 0.234375000000 0.351562500000 0.273437500000 0.390625000000 -mp_egg.tga 0.273437500000 0.351562500000 0.312500000000 0.390625000000 -mp_eyes.tga 0.312500000000 0.351562500000 0.351562500000 0.390625000000 -mp_fang.tga 0.351562500000 0.351562500000 0.390625000000 0.390625000000 -mp_fiber.tga 0.390625000000 0.351562500000 0.429687500000 0.390625000000 -mp_filament.tga 0.429687500000 0.351562500000 0.468750000000 0.390625000000 -mp_firefly_abdomen.tga 0.000000000000 0.390625000000 0.039062500000 0.429687500000 -mp_fish_scale.tga 0.039062500000 0.390625000000 0.078125000000 0.429687500000 -mp_flowers.tga 0.078125000000 0.390625000000 0.117187500000 0.429687500000 -mp_fresh_loose_soil.tga 0.117187500000 0.390625000000 0.156250000000 0.429687500000 -mp_fruit.tga 0.156250000000 0.390625000000 0.195312500000 0.429687500000 -mp_generic.tga 0.195312500000 0.390625000000 0.234375000000 0.429687500000 -mp_generic_colorize.tga 0.234375000000 0.390625000000 0.273437500000 0.429687500000 -mp_gomme.tga 0.273437500000 0.390625000000 0.312500000000 0.429687500000 -mp_goo_residue.tga 0.312500000000 0.390625000000 0.351562500000 0.429687500000 -mp_hairs.tga 0.351562500000 0.390625000000 0.390625000000 0.429687500000 -mp_hoof.tga 0.390625000000 0.390625000000 0.429687500000 0.429687500000 -mp_horn.tga 0.429687500000 0.390625000000 0.468750000000 0.429687500000 -mp_horney.tga 0.000000000000 0.429687500000 0.039062500000 0.468750000000 -mp_insect_fossil.tga 0.039062500000 0.429687500000 0.078125000000 0.468750000000 -mp_kitinshell.tga 0.078125000000 0.429687500000 0.117187500000 0.468750000000 -mp_kitin_flesh.tga 0.117187500000 0.429687500000 0.156250000000 0.468750000000 -mp_kitin_secretion.tga 0.156250000000 0.429687500000 0.195312500000 0.468750000000 -mp_larva.tga 0.195312500000 0.429687500000 0.234375000000 0.468750000000 -mp_leaf.tga 0.234375000000 0.429687500000 0.273437500000 0.468750000000 -mp_leather.tga 0.273437500000 0.429687500000 0.312500000000 0.468750000000 -mp_liane.tga 0.312500000000 0.429687500000 0.351562500000 0.468750000000 -mp_lichen.tga 0.351562500000 0.429687500000 0.390625000000 0.468750000000 -mp_ligament.tga 0.390625000000 0.429687500000 0.429687500000 0.468750000000 -mp_mandible.tga 0.429687500000 0.429687500000 0.468750000000 0.468750000000 -mp_meat.tga 0.476562500000 0.000000000000 0.515625000000 0.039062500000 -mp_moss.tga 0.515625000000 0.000000000000 0.554687500000 0.039062500000 -mp_mushroom.tga 0.554687500000 0.000000000000 0.593750000000 0.039062500000 -mp_nail.tga 0.593750000000 0.000000000000 0.632812500000 0.039062500000 -mp_oil.tga 0.632812500000 0.000000000000 0.671875000000 0.039062500000 -mp_parasite.tga 0.671875000000 0.000000000000 0.710937500000 0.039062500000 -mp_pearl.tga 0.710937500000 0.000000000000 0.750000000000 0.039062500000 -mp_pelvis.tga 0.750000000000 0.000000000000 0.789062500000 0.039062500000 -mp_pigment.tga 0.789062500000 0.000000000000 0.828125000000 0.039062500000 -mp_pistil.tga 0.828125000000 0.000000000000 0.867187500000 0.039062500000 -mp_plant_fossil.tga 0.867187500000 0.000000000000 0.906250000000 0.039062500000 -mp_pollen.tga 0.906250000000 0.000000000000 0.945312500000 0.039062500000 -mp_resin.tga 0.945312500000 0.000000000000 0.984375000000 0.039062500000 -mp_ronce.tga 0.472656250000 0.039062500000 0.511718750000 0.078125000000 -mp_rostrum.tga 0.511718750000 0.039062500000 0.550781250000 0.078125000000 -mp_sap.tga 0.550781250000 0.039062500000 0.589843750000 0.078125000000 -mp_sawdust.tga 0.589843750000 0.039062500000 0.628906250000 0.078125000000 -mp_seed.tga 0.628906250000 0.039062500000 0.667968750000 0.078125000000 -mp_shell.tga 0.667968750000 0.039062500000 0.707031250000 0.078125000000 -mp_silk_worm.tga 0.707031250000 0.039062500000 0.746093750000 0.078125000000 -mp_skin.tga 0.746093750000 0.039062500000 0.785156250000 0.078125000000 -mp_skull.tga 0.785156250000 0.039062500000 0.824218750000 0.078125000000 -mp_spiders_web.tga 0.824218750000 0.039062500000 0.863281250000 0.078125000000 -mp_spine.tga 0.863281250000 0.039062500000 0.902343750000 0.078125000000 -mp_stem.tga 0.902343750000 0.039062500000 0.941406250000 0.078125000000 -mp_sting.tga 0.941406250000 0.039062500000 0.980468750000 0.078125000000 -mp_straw.tga 0.468750000000 0.078125000000 0.507812500000 0.117187500000 -mp_suc.tga 0.507812500000 0.078125000000 0.546875000000 0.117187500000 -mp_tail.tga 0.546875000000 0.078125000000 0.585937500000 0.117187500000 -mp_tooth.tga 0.585937500000 0.078125000000 0.625000000000 0.117187500000 -mp_trunk.tga 0.625000000000 0.078125000000 0.664062500000 0.117187500000 -mp_whiskers.tga 0.664062500000 0.078125000000 0.703125000000 0.117187500000 -mp_wing.tga 0.703125000000 0.078125000000 0.742187500000 0.117187500000 -mp_wood.tga 0.742187500000 0.078125000000 0.781250000000 0.117187500000 -mp_wood_node.tga 0.781250000000 0.078125000000 0.820312500000 0.117187500000 -MW_2h_axe.tga 0.820312500000 0.078125000000 0.859375000000 0.117187500000 -MW_2h_lance.tga 0.859375000000 0.078125000000 0.898437500000 0.117187500000 -MW_2h_mace.tga 0.898437500000 0.078125000000 0.937500000000 0.117187500000 -MW_2h_sword.tga 0.937500000000 0.078125000000 0.976562500000 0.117187500000 -MW_axe.tga 0.468750000000 0.117187500000 0.507812500000 0.156250000000 -MW_dagger.tga 0.507812500000 0.117187500000 0.546875000000 0.156250000000 -MW_lance.tga 0.546875000000 0.117187500000 0.585937500000 0.156250000000 -MW_mace.tga 0.585937500000 0.117187500000 0.625000000000 0.156250000000 -MW_staff.tga 0.625000000000 0.117187500000 0.664062500000 0.156250000000 -MW_sword.tga 0.664062500000 0.117187500000 0.703125000000 0.156250000000 -PA_anklet.tga 0.703125000000 0.117187500000 0.742187500000 0.156250000000 -PA_bracelet.tga 0.742187500000 0.117187500000 0.781250000000 0.156250000000 -PA_diadem.tga 0.781250000000 0.117187500000 0.820312500000 0.156250000000 -PA_earring.tga 0.820312500000 0.117187500000 0.859375000000 0.156250000000 -PA_pendant.tga 0.859375000000 0.117187500000 0.898437500000 0.156250000000 -PA_ring.tga 0.898437500000 0.117187500000 0.937500000000 0.156250000000 -protect_amber.tga 0.937500000000 0.117187500000 0.976562500000 0.156250000000 -pvp_aura.tga 0.468750000000 0.156250000000 0.507812500000 0.195312500000 -pvp_aura_mask.tga 0.507812500000 0.156250000000 0.546875000000 0.195312500000 -pvp_boost.tga 0.546875000000 0.156250000000 0.585937500000 0.195312500000 -pvp_boost_mask.tga 0.585937500000 0.156250000000 0.625000000000 0.195312500000 -pw_4.tga 0.625000000000 0.156250000000 0.664062500000 0.195312500000 -pw_5.tga 0.664062500000 0.156250000000 0.703125000000 0.195312500000 -pw_6.tga 0.703125000000 0.156250000000 0.742187500000 0.195312500000 -pw_7.tga 0.742187500000 0.156250000000 0.781250000000 0.195312500000 -PW_heavy.tga 0.781250000000 0.156250000000 0.820312500000 0.195312500000 -PW_light.tga 0.820312500000 0.156250000000 0.859375000000 0.195312500000 -PW_medium.tga 0.859375000000 0.156250000000 0.898437500000 0.195312500000 -quest_coeur.tga 0.898437500000 0.156250000000 0.937500000000 0.195312500000 -quest_foie.tga 0.937500000000 0.156250000000 0.976562500000 0.195312500000 -quest_jeton.tga 0.468750000000 0.195312500000 0.507812500000 0.234375000000 -quest_langue.tga 0.507812500000 0.195312500000 0.546875000000 0.234375000000 -quest_louche.tga 0.546875000000 0.195312500000 0.585937500000 0.234375000000 -quest_oreille.tga 0.585937500000 0.195312500000 0.625000000000 0.234375000000 -quest_patte.tga 0.625000000000 0.195312500000 0.664062500000 0.234375000000 -quest_poils.tga 0.664062500000 0.195312500000 0.703125000000 0.234375000000 -quest_queue.tga 0.703125000000 0.195312500000 0.742187500000 0.234375000000 -quest_ticket.tga 0.742187500000 0.195312500000 0.781250000000 0.234375000000 -AM_logo.tga 0.781250000000 0.195312500000 0.820312500000 0.234375000000 -AR_armpad.tga 0.820312500000 0.195312500000 0.859375000000 0.234375000000 -ar_armpad_mask.tga 0.859375000000 0.195312500000 0.898437500000 0.234375000000 -requirement.tga 0.898437500000 0.195312500000 0.937500000000 0.234375000000 -rm_f.tga 0.937500000000 0.195312500000 0.976562500000 0.234375000000 -rm_f_upgrade.tga 0.468750000000 0.234375000000 0.507812500000 0.273437500000 -rm_h.tga 0.507812500000 0.234375000000 0.546875000000 0.273437500000 -rm_h_upgrade.tga 0.546875000000 0.234375000000 0.585937500000 0.273437500000 -rm_m.tga 0.585937500000 0.234375000000 0.625000000000 0.273437500000 -rm_m_upgrade.tga 0.625000000000 0.234375000000 0.664062500000 0.273437500000 -rm_r.tga 0.664062500000 0.234375000000 0.703125000000 0.273437500000 -rm_r_upgrade.tga 0.703125000000 0.234375000000 0.742187500000 0.273437500000 -rpjobitem_200_a.tga 0.742187500000 0.234375000000 0.781250000000 0.273437500000 -rpjobitem_200_b.tga 0.781250000000 0.234375000000 0.820312500000 0.273437500000 -rpjobitem_200_c.tga 0.820312500000 0.234375000000 0.859375000000 0.273437500000 -rpjobitem_201_a.tga 0.859375000000 0.234375000000 0.898437500000 0.273437500000 -rpjobitem_201_b.tga 0.898437500000 0.234375000000 0.937500000000 0.273437500000 -rpjobitem_201_c.tga 0.937500000000 0.234375000000 0.976562500000 0.273437500000 -rpjobitem_202_a.tga 0.468750000000 0.273437500000 0.507812500000 0.312500000000 -rpjobitem_202_b.tga 0.507812500000 0.273437500000 0.546875000000 0.312500000000 -rpjobitem_202_c.tga 0.546875000000 0.273437500000 0.585937500000 0.312500000000 -rpjobitem_203_a.tga 0.585937500000 0.273437500000 0.625000000000 0.312500000000 -rpjobitem_203_b.tga 0.625000000000 0.273437500000 0.664062500000 0.312500000000 -rpjobitem_203_c.tga 0.664062500000 0.273437500000 0.703125000000 0.312500000000 -rpjobitem_204_a.tga 0.703125000000 0.273437500000 0.742187500000 0.312500000000 -rpjobitem_204_b.tga 0.742187500000 0.273437500000 0.781250000000 0.312500000000 -rpjobitem_204_c.tga 0.781250000000 0.273437500000 0.820312500000 0.312500000000 -rpjobitem_205_a.tga 0.820312500000 0.273437500000 0.859375000000 0.312500000000 -rpjobitem_205_b.tga 0.859375000000 0.273437500000 0.898437500000 0.312500000000 -rpjobitem_205_c.tga 0.898437500000 0.273437500000 0.937500000000 0.312500000000 -rpjobitem_206_a.tga 0.937500000000 0.273437500000 0.976562500000 0.312500000000 -rpjobitem_206_b.tga 0.468750000000 0.312500000000 0.507812500000 0.351562500000 -rpjobitem_206_c.tga 0.507812500000 0.312500000000 0.546875000000 0.351562500000 -rpjobitem_207_a.tga 0.546875000000 0.312500000000 0.585937500000 0.351562500000 -rpjobitem_207_b.tga 0.585937500000 0.312500000000 0.625000000000 0.351562500000 -rpjobitem_207_c.tga 0.625000000000 0.312500000000 0.664062500000 0.351562500000 -rpjobitem_certifications.tga 0.664062500000 0.312500000000 0.703125000000 0.351562500000 -rpjob_200.tga 0.703125000000 0.312500000000 0.742187500000 0.351562500000 -rpjob_201.tga 0.742187500000 0.312500000000 0.781250000000 0.351562500000 -rpjob_202.tga 0.781250000000 0.312500000000 0.820312500000 0.351562500000 -rpjob_203.tga 0.820312500000 0.312500000000 0.859375000000 0.351562500000 -rpjob_204.tga 0.859375000000 0.312500000000 0.898437500000 0.351562500000 -rpjob_205.tga 0.898437500000 0.312500000000 0.937500000000 0.351562500000 -rpjob_206.tga 0.937500000000 0.312500000000 0.976562500000 0.351562500000 -rpjob_207.tga 0.468750000000 0.351562500000 0.507812500000 0.390625000000 -rpjob_advanced.tga 0.507812500000 0.351562500000 0.546875000000 0.390625000000 -rpjob_elementary.tga 0.546875000000 0.351562500000 0.585937500000 0.390625000000 -rpjob_roleplay.tga 0.585937500000 0.351562500000 0.625000000000 0.390625000000 -rpjob_task.tga 0.625000000000 0.351562500000 0.664062500000 0.390625000000 -rpjob_task_certificats.tga 0.664062500000 0.351562500000 0.703125000000 0.390625000000 -rpjob_task_convert.tga 0.703125000000 0.351562500000 0.742187500000 0.390625000000 -rpjob_task_elementary.tga 0.742187500000 0.351562500000 0.781250000000 0.390625000000 -rpjob_task_generic.tga 0.781250000000 0.351562500000 0.820312500000 0.390625000000 -rpjob_task_upgrade.tga 0.820312500000 0.351562500000 0.859375000000 0.390625000000 -RW_autolaunch.tga 0.859375000000 0.351562500000 0.898437500000 0.390625000000 -RW_bowgun.tga 0.898437500000 0.351562500000 0.937500000000 0.390625000000 -RW_grenade.tga 0.937500000000 0.351562500000 0.976562500000 0.390625000000 -RW_harpoongun.tga 0.468750000000 0.390625000000 0.507812500000 0.429687500000 -RW_launcher.tga 0.507812500000 0.390625000000 0.546875000000 0.429687500000 -RW_pistol.tga 0.546875000000 0.390625000000 0.585937500000 0.429687500000 -RW_pistolarc.tga 0.585937500000 0.390625000000 0.625000000000 0.429687500000 -RW_rifle.tga 0.625000000000 0.390625000000 0.664062500000 0.429687500000 -SH_buckler.tga 0.664062500000 0.390625000000 0.703125000000 0.429687500000 -SH_large_shield.tga 0.703125000000 0.390625000000 0.742187500000 0.429687500000 -spe_beast.tga 0.742187500000 0.390625000000 0.781250000000 0.429687500000 -spe_com.tga 0.781250000000 0.390625000000 0.820312500000 0.429687500000 -spe_inventory.tga 0.820312500000 0.390625000000 0.859375000000 0.429687500000 -spe_labs.tga 0.859375000000 0.390625000000 0.898437500000 0.429687500000 -spe_memory.tga 0.898437500000 0.390625000000 0.937500000000 0.429687500000 -spe_options.tga 0.937500000000 0.390625000000 0.976562500000 0.429687500000 -spe_status.tga 0.468750000000 0.429687500000 0.507812500000 0.468750000000 -stimulating_water.tga 0.507812500000 0.429687500000 0.546875000000 0.468750000000 -tetekitin.tga 0.546875000000 0.429687500000 0.585937500000 0.468750000000 -to_ammo.tga 0.585937500000 0.429687500000 0.625000000000 0.468750000000 -to_armor.tga 0.625000000000 0.429687500000 0.664062500000 0.468750000000 -to_cooking_pot.tga 0.664062500000 0.429687500000 0.703125000000 0.468750000000 -to_fishing_rod.tga 0.703125000000 0.429687500000 0.742187500000 0.468750000000 -to_forage.tga 0.742187500000 0.429687500000 0.781250000000 0.468750000000 -to_hammer.tga 0.781250000000 0.429687500000 0.820312500000 0.468750000000 -to_jewelry_hammer.tga 0.820312500000 0.429687500000 0.859375000000 0.468750000000 -to_jewels.tga 0.859375000000 0.429687500000 0.898437500000 0.468750000000 -to_leathercutter.tga 0.898437500000 0.429687500000 0.937500000000 0.468750000000 -to_melee.tga 0.937500000000 0.429687500000 0.976562500000 0.468750000000 -to_needle.tga 0.000000000000 0.468750000000 0.039062500000 0.507812500000 -to_pestle.tga 0.039062500000 0.468750000000 0.078125000000 0.507812500000 -to_range.tga 0.078125000000 0.468750000000 0.117187500000 0.507812500000 -to_searake.tga 0.117187500000 0.468750000000 0.156250000000 0.507812500000 -to_spade.tga 0.156250000000 0.468750000000 0.195312500000 0.507812500000 -to_stick.tga 0.195312500000 0.468750000000 0.234375000000 0.507812500000 -to_tunneling_knife.tga 0.234375000000 0.468750000000 0.273437500000 0.507812500000 -to_whip.tga 0.273437500000 0.468750000000 0.312500000000 0.507812500000 -to_wrench.tga 0.312500000000 0.468750000000 0.351562500000 0.507812500000 -TP_caravane.tga 0.351562500000 0.468750000000 0.390625000000 0.507812500000 -TP_kami.tga 0.390625000000 0.468750000000 0.429687500000 0.507812500000 -W_AM_logo.tga 0.429687500000 0.468750000000 0.468750000000 0.507812500000 -w_pa_anklet.tga 0.468750000000 0.468750000000 0.507812500000 0.507812500000 -w_pa_bracelet.tga 0.507812500000 0.468750000000 0.546875000000 0.507812500000 -w_pa_diadem.tga 0.546875000000 0.468750000000 0.585937500000 0.507812500000 -w_pa_earring.tga 0.585937500000 0.468750000000 0.625000000000 0.507812500000 -w_pa_pendant.tga 0.625000000000 0.468750000000 0.664062500000 0.507812500000 -w_pa_ring.tga 0.664062500000 0.468750000000 0.703125000000 0.507812500000 -xp_cat_green.tga 0.703125000000 0.468750000000 0.742187500000 0.507812500000 +BK_matis.tga 0.078125000000 0.117187500000 0.117187500000 0.156250000000 +bk_mission.tga 0.117187500000 0.117187500000 0.156250000000 0.156250000000 +bk_mission2.tga 0.156250000000 0.117187500000 0.195312500000 0.156250000000 +BK_outpost.tga 0.195312500000 0.117187500000 0.234375000000 0.156250000000 +BK_primes.tga 0.000000000000 0.156250000000 0.039062500000 0.195312500000 +bk_service.tga 0.039062500000 0.156250000000 0.078125000000 0.195312500000 +bk_training.tga 0.078125000000 0.156250000000 0.117187500000 0.195312500000 +BK_tryker.tga 0.117187500000 0.156250000000 0.156250000000 0.195312500000 +BK_zorai.tga 0.156250000000 0.156250000000 0.195312500000 0.195312500000 +charge.tga 0.195312500000 0.156250000000 0.234375000000 0.195312500000 +clef.tga 0.000000000000 0.195312500000 0.039062500000 0.234375000000 +conso_branche.tga 0.039062500000 0.195312500000 0.078125000000 0.234375000000 +conso_branche_mask.tga 0.078125000000 0.195312500000 0.117187500000 0.234375000000 +conso_fleur.tga 0.117187500000 0.195312500000 0.156250000000 0.234375000000 +conso_fleur_mask.tga 0.156250000000 0.195312500000 0.195312500000 0.234375000000 +conso_grappe.tga 0.195312500000 0.195312500000 0.234375000000 0.234375000000 +conso_nectar.tga 0.242187500000 0.000000000000 0.281250000000 0.039062500000 +conso_nectar_mask.tga 0.281250000000 0.000000000000 0.320312500000 0.039062500000 +construction.tga 0.320312500000 0.000000000000 0.359375000000 0.039062500000 +cristal_ammo.tga 0.359375000000 0.000000000000 0.398437500000 0.039062500000 +cristal_spell.tga 0.398437500000 0.000000000000 0.437500000000 0.039062500000 +ico_haircolor.tga 0.437500000000 0.000000000000 0.476562500000 0.039062500000 +ico_haircut.tga 0.238281250000 0.039062500000 0.277343750000 0.078125000000 +bk_karavan.tga 0.277343750000 0.039062500000 0.316406250000 0.078125000000 +conso_grappe_mask.tga 0.316406250000 0.039062500000 0.355468750000 0.078125000000 +ico_foreuse.tga 0.355468750000 0.039062500000 0.394531250000 0.078125000000 +ico_noix.tga 0.394531250000 0.039062500000 0.433593750000 0.078125000000 +ico_spores.tga 0.433593750000 0.039062500000 0.472656250000 0.078125000000 +mektoub_pack.tga 0.234375000000 0.078125000000 0.273437500000 0.117187500000 +mp_beak.tga 0.273437500000 0.078125000000 0.312500000000 0.117187500000 +mp_fresh_loose_soil.tga 0.312500000000 0.078125000000 0.351562500000 0.117187500000 +mp_lichen.tga 0.351562500000 0.078125000000 0.390625000000 0.117187500000 +mp_sawdust.tga 0.390625000000 0.078125000000 0.429687500000 0.117187500000 +MW_2h_axe.tga 0.429687500000 0.078125000000 0.468750000000 0.117187500000 +PA_bracelet.tga 0.234375000000 0.117187500000 0.273437500000 0.156250000000 +pvp_aura_mask.tga 0.273437500000 0.117187500000 0.312500000000 0.156250000000 +quest_queue.tga 0.312500000000 0.117187500000 0.351562500000 0.156250000000 +rpjobitem_201_b.tga 0.351562500000 0.117187500000 0.390625000000 0.156250000000 +rpjobitem_207_a.tga 0.390625000000 0.117187500000 0.429687500000 0.156250000000 +rpjob_task_elementary.tga 0.429687500000 0.117187500000 0.468750000000 0.156250000000 +spe_options.tga 0.234375000000 0.156250000000 0.273437500000 0.195312500000 +to_range.tga 0.273437500000 0.156250000000 0.312500000000 0.195312500000 +w_pa_anklet.tga 0.312500000000 0.156250000000 0.351562500000 0.195312500000 +ico_task_craft.tga 0.351562500000 0.156250000000 0.390625000000 0.195312500000 +ico_task_done.tga 0.390625000000 0.156250000000 0.429687500000 0.195312500000 +ico_task_failed.tga 0.429687500000 0.156250000000 0.468750000000 0.195312500000 +ico_task_fight.tga 0.234375000000 0.195312500000 0.273437500000 0.234375000000 +ico_task_forage.tga 0.273437500000 0.195312500000 0.312500000000 0.234375000000 +ico_task_generic.tga 0.312500000000 0.195312500000 0.351562500000 0.234375000000 +ico_task_generic_quart.tga 0.351562500000 0.195312500000 0.390625000000 0.234375000000 +ico_task_guild.tga 0.390625000000 0.195312500000 0.429687500000 0.234375000000 +ico_task_rite.tga 0.429687500000 0.195312500000 0.468750000000 0.234375000000 +ico_task_travel.tga 0.000000000000 0.234375000000 0.039062500000 0.273437500000 +ico_tatoo.tga 0.039062500000 0.234375000000 0.078125000000 0.273437500000 +mektoub_steed.tga 0.078125000000 0.234375000000 0.117187500000 0.273437500000 +mg_glove.tga 0.117187500000 0.234375000000 0.156250000000 0.273437500000 +mission_icon_0.tga 0.156250000000 0.234375000000 0.195312500000 0.273437500000 +mission_icon_1.tga 0.195312500000 0.234375000000 0.234375000000 0.273437500000 +mission_icon_2.tga 0.234375000000 0.234375000000 0.273437500000 0.273437500000 +mission_icon_3.tga 0.273437500000 0.234375000000 0.312500000000 0.273437500000 +mp_amber.tga 0.312500000000 0.234375000000 0.351562500000 0.273437500000 +mp_bark.tga 0.351562500000 0.234375000000 0.390625000000 0.273437500000 +mp_batiment_brique.tga 0.390625000000 0.234375000000 0.429687500000 0.273437500000 +mp_batiment_colonne.tga 0.429687500000 0.234375000000 0.468750000000 0.273437500000 +mp_batiment_colonne_justice.tga 0.000000000000 0.273437500000 0.039062500000 0.312500000000 +mp_batiment_comble.tga 0.039062500000 0.273437500000 0.078125000000 0.312500000000 +mp_batiment_noyau_maduk.tga 0.078125000000 0.273437500000 0.117187500000 0.312500000000 +mp_batiment_ornement.tga 0.117187500000 0.273437500000 0.156250000000 0.312500000000 +mp_batiment_revetement.tga 0.156250000000 0.273437500000 0.195312500000 0.312500000000 +mp_batiment_socle.tga 0.195312500000 0.273437500000 0.234375000000 0.312500000000 +mp_batiment_statue.tga 0.234375000000 0.273437500000 0.273437500000 0.312500000000 +mp_blood.tga 0.273437500000 0.273437500000 0.312500000000 0.312500000000 +mp_bone.tga 0.312500000000 0.273437500000 0.351562500000 0.312500000000 +mp_bud.tga 0.351562500000 0.273437500000 0.390625000000 0.312500000000 +mp_buterfly_blue.tga 0.390625000000 0.273437500000 0.429687500000 0.312500000000 +mp_buterfly_cocoon.tga 0.429687500000 0.273437500000 0.468750000000 0.312500000000 +mp_cereal.tga 0.000000000000 0.312500000000 0.039062500000 0.351562500000 +mp_claw.tga 0.039062500000 0.312500000000 0.078125000000 0.351562500000 +mp_dandelion.tga 0.078125000000 0.312500000000 0.117187500000 0.351562500000 +mp_dust.tga 0.117187500000 0.312500000000 0.156250000000 0.351562500000 +mp_egg.tga 0.156250000000 0.312500000000 0.195312500000 0.351562500000 +mp_eyes.tga 0.195312500000 0.312500000000 0.234375000000 0.351562500000 +mp_fang.tga 0.234375000000 0.312500000000 0.273437500000 0.351562500000 +mp_fiber.tga 0.273437500000 0.312500000000 0.312500000000 0.351562500000 +mp_filament.tga 0.312500000000 0.312500000000 0.351562500000 0.351562500000 +mp_firefly_abdomen.tga 0.351562500000 0.312500000000 0.390625000000 0.351562500000 +mp_fish_scale.tga 0.390625000000 0.312500000000 0.429687500000 0.351562500000 +mp_flowers.tga 0.429687500000 0.312500000000 0.468750000000 0.351562500000 +mp_fruit.tga 0.000000000000 0.351562500000 0.039062500000 0.390625000000 +mp_generic.tga 0.039062500000 0.351562500000 0.078125000000 0.390625000000 +mp_generic_colorize.tga 0.078125000000 0.351562500000 0.117187500000 0.390625000000 +mp_gomme.tga 0.117187500000 0.351562500000 0.156250000000 0.390625000000 +mp_goo_residue.tga 0.156250000000 0.351562500000 0.195312500000 0.390625000000 +mp_hairs.tga 0.195312500000 0.351562500000 0.234375000000 0.390625000000 +mp_hoof.tga 0.234375000000 0.351562500000 0.273437500000 0.390625000000 +mp_horn.tga 0.273437500000 0.351562500000 0.312500000000 0.390625000000 +mp_horney.tga 0.312500000000 0.351562500000 0.351562500000 0.390625000000 +mp_insect_fossil.tga 0.351562500000 0.351562500000 0.390625000000 0.390625000000 +mp_kitinshell.tga 0.390625000000 0.351562500000 0.429687500000 0.390625000000 +mp_kitin_flesh.tga 0.429687500000 0.351562500000 0.468750000000 0.390625000000 +mp_kitin_secretion.tga 0.000000000000 0.390625000000 0.039062500000 0.429687500000 +mp_larva.tga 0.039062500000 0.390625000000 0.078125000000 0.429687500000 +mp_leaf.tga 0.078125000000 0.390625000000 0.117187500000 0.429687500000 +mp_leather.tga 0.117187500000 0.390625000000 0.156250000000 0.429687500000 +mp_liane.tga 0.156250000000 0.390625000000 0.195312500000 0.429687500000 +mp_ligament.tga 0.195312500000 0.390625000000 0.234375000000 0.429687500000 +mp_mandible.tga 0.234375000000 0.390625000000 0.273437500000 0.429687500000 +mp_meat.tga 0.273437500000 0.390625000000 0.312500000000 0.429687500000 +mp_moss.tga 0.312500000000 0.390625000000 0.351562500000 0.429687500000 +mp_mushroom.tga 0.351562500000 0.390625000000 0.390625000000 0.429687500000 +mp_nail.tga 0.390625000000 0.390625000000 0.429687500000 0.429687500000 +mp_oil.tga 0.429687500000 0.390625000000 0.468750000000 0.429687500000 +mp_parasite.tga 0.000000000000 0.429687500000 0.039062500000 0.468750000000 +mp_pearl.tga 0.039062500000 0.429687500000 0.078125000000 0.468750000000 +mp_pelvis.tga 0.078125000000 0.429687500000 0.117187500000 0.468750000000 +mp_pigment.tga 0.117187500000 0.429687500000 0.156250000000 0.468750000000 +mp_pistil.tga 0.156250000000 0.429687500000 0.195312500000 0.468750000000 +mp_plant_fossil.tga 0.195312500000 0.429687500000 0.234375000000 0.468750000000 +mp_pollen.tga 0.234375000000 0.429687500000 0.273437500000 0.468750000000 +mp_resin.tga 0.273437500000 0.429687500000 0.312500000000 0.468750000000 +mp_ronce.tga 0.312500000000 0.429687500000 0.351562500000 0.468750000000 +mp_rostrum.tga 0.351562500000 0.429687500000 0.390625000000 0.468750000000 +mp_sap.tga 0.390625000000 0.429687500000 0.429687500000 0.468750000000 +mp_seed.tga 0.429687500000 0.429687500000 0.468750000000 0.468750000000 +mp_shell.tga 0.476562500000 0.000000000000 0.515625000000 0.039062500000 +mp_silk_worm.tga 0.515625000000 0.000000000000 0.554687500000 0.039062500000 +mp_skin.tga 0.554687500000 0.000000000000 0.593750000000 0.039062500000 +mp_skull.tga 0.593750000000 0.000000000000 0.632812500000 0.039062500000 +mp_spiders_web.tga 0.632812500000 0.000000000000 0.671875000000 0.039062500000 +mp_spine.tga 0.671875000000 0.000000000000 0.710937500000 0.039062500000 +mp_stem.tga 0.710937500000 0.000000000000 0.750000000000 0.039062500000 +mp_sting.tga 0.750000000000 0.000000000000 0.789062500000 0.039062500000 +mp_straw.tga 0.789062500000 0.000000000000 0.828125000000 0.039062500000 +mp_suc.tga 0.828125000000 0.000000000000 0.867187500000 0.039062500000 +mp_tail.tga 0.867187500000 0.000000000000 0.906250000000 0.039062500000 +mp_tooth.tga 0.906250000000 0.000000000000 0.945312500000 0.039062500000 +mp_trunk.tga 0.945312500000 0.000000000000 0.984375000000 0.039062500000 +mp_whiskers.tga 0.472656250000 0.039062500000 0.511718750000 0.078125000000 +mp_wing.tga 0.511718750000 0.039062500000 0.550781250000 0.078125000000 +mp_wood.tga 0.550781250000 0.039062500000 0.589843750000 0.078125000000 +mp_wood_node.tga 0.589843750000 0.039062500000 0.628906250000 0.078125000000 +MW_2h_lance.tga 0.628906250000 0.039062500000 0.667968750000 0.078125000000 +MW_2h_mace.tga 0.667968750000 0.039062500000 0.707031250000 0.078125000000 +MW_2h_sword.tga 0.707031250000 0.039062500000 0.746093750000 0.078125000000 +MW_axe.tga 0.746093750000 0.039062500000 0.785156250000 0.078125000000 +MW_dagger.tga 0.785156250000 0.039062500000 0.824218750000 0.078125000000 +MW_lance.tga 0.824218750000 0.039062500000 0.863281250000 0.078125000000 +MW_mace.tga 0.863281250000 0.039062500000 0.902343750000 0.078125000000 +MW_staff.tga 0.902343750000 0.039062500000 0.941406250000 0.078125000000 +MW_sword.tga 0.941406250000 0.039062500000 0.980468750000 0.078125000000 +PA_anklet.tga 0.468750000000 0.078125000000 0.507812500000 0.117187500000 +pvp_boost.tga 0.507812500000 0.078125000000 0.546875000000 0.117187500000 +pvp_boost_mask.tga 0.546875000000 0.078125000000 0.585937500000 0.117187500000 +pw_4.tga 0.585937500000 0.078125000000 0.625000000000 0.117187500000 +pw_5.tga 0.625000000000 0.078125000000 0.664062500000 0.117187500000 +pw_6.tga 0.664062500000 0.078125000000 0.703125000000 0.117187500000 +pw_7.tga 0.703125000000 0.078125000000 0.742187500000 0.117187500000 +PW_heavy.tga 0.742187500000 0.078125000000 0.781250000000 0.117187500000 +PW_light.tga 0.781250000000 0.078125000000 0.820312500000 0.117187500000 +PW_medium.tga 0.820312500000 0.078125000000 0.859375000000 0.117187500000 +quest_coeur.tga 0.859375000000 0.078125000000 0.898437500000 0.117187500000 +quest_foie.tga 0.898437500000 0.078125000000 0.937500000000 0.117187500000 +quest_jeton.tga 0.937500000000 0.078125000000 0.976562500000 0.117187500000 +quest_langue.tga 0.468750000000 0.117187500000 0.507812500000 0.156250000000 +quest_louche.tga 0.507812500000 0.117187500000 0.546875000000 0.156250000000 +quest_oreille.tga 0.546875000000 0.117187500000 0.585937500000 0.156250000000 +quest_patte.tga 0.585937500000 0.117187500000 0.625000000000 0.156250000000 +quest_poils.tga 0.625000000000 0.117187500000 0.664062500000 0.156250000000 +quest_ticket.tga 0.664062500000 0.117187500000 0.703125000000 0.156250000000 +AM_logo.tga 0.703125000000 0.117187500000 0.742187500000 0.156250000000 +AR_armpad.tga 0.742187500000 0.117187500000 0.781250000000 0.156250000000 +ar_armpad_mask.tga 0.781250000000 0.117187500000 0.820312500000 0.156250000000 +requirement.tga 0.820312500000 0.117187500000 0.859375000000 0.156250000000 +rm_f.tga 0.859375000000 0.117187500000 0.898437500000 0.156250000000 +rm_f_upgrade.tga 0.898437500000 0.117187500000 0.937500000000 0.156250000000 +rm_h.tga 0.937500000000 0.117187500000 0.976562500000 0.156250000000 +rm_h_upgrade.tga 0.468750000000 0.156250000000 0.507812500000 0.195312500000 +rm_m.tga 0.507812500000 0.156250000000 0.546875000000 0.195312500000 +rm_m_upgrade.tga 0.546875000000 0.156250000000 0.585937500000 0.195312500000 +rm_r.tga 0.585937500000 0.156250000000 0.625000000000 0.195312500000 +rm_r_upgrade.tga 0.625000000000 0.156250000000 0.664062500000 0.195312500000 +rpjobitem_200_a.tga 0.664062500000 0.156250000000 0.703125000000 0.195312500000 +rpjobitem_200_b.tga 0.703125000000 0.156250000000 0.742187500000 0.195312500000 +rpjobitem_200_c.tga 0.742187500000 0.156250000000 0.781250000000 0.195312500000 +rpjobitem_201_a.tga 0.781250000000 0.156250000000 0.820312500000 0.195312500000 +rpjobitem_201_c.tga 0.820312500000 0.156250000000 0.859375000000 0.195312500000 +rpjobitem_202_a.tga 0.859375000000 0.156250000000 0.898437500000 0.195312500000 +rpjobitem_202_b.tga 0.898437500000 0.156250000000 0.937500000000 0.195312500000 +rpjobitem_202_c.tga 0.937500000000 0.156250000000 0.976562500000 0.195312500000 +rpjobitem_203_a.tga 0.468750000000 0.195312500000 0.507812500000 0.234375000000 +rpjobitem_203_b.tga 0.507812500000 0.195312500000 0.546875000000 0.234375000000 +rpjobitem_203_c.tga 0.546875000000 0.195312500000 0.585937500000 0.234375000000 +rpjobitem_204_a.tga 0.585937500000 0.195312500000 0.625000000000 0.234375000000 +rpjobitem_204_b.tga 0.625000000000 0.195312500000 0.664062500000 0.234375000000 +rpjobitem_204_c.tga 0.664062500000 0.195312500000 0.703125000000 0.234375000000 +rpjobitem_205_a.tga 0.703125000000 0.195312500000 0.742187500000 0.234375000000 +rpjobitem_205_b.tga 0.742187500000 0.195312500000 0.781250000000 0.234375000000 +rpjobitem_205_c.tga 0.781250000000 0.195312500000 0.820312500000 0.234375000000 +rpjobitem_206_a.tga 0.820312500000 0.195312500000 0.859375000000 0.234375000000 +rpjobitem_206_b.tga 0.859375000000 0.195312500000 0.898437500000 0.234375000000 +rpjobitem_206_c.tga 0.898437500000 0.195312500000 0.937500000000 0.234375000000 +rpjobitem_207_b.tga 0.937500000000 0.195312500000 0.976562500000 0.234375000000 +rpjobitem_207_c.tga 0.468750000000 0.234375000000 0.507812500000 0.273437500000 +rpjobitem_certifications.tga 0.507812500000 0.234375000000 0.546875000000 0.273437500000 +rpjob_200.tga 0.546875000000 0.234375000000 0.585937500000 0.273437500000 +rpjob_201.tga 0.585937500000 0.234375000000 0.625000000000 0.273437500000 +rpjob_202.tga 0.625000000000 0.234375000000 0.664062500000 0.273437500000 +rpjob_203.tga 0.664062500000 0.234375000000 0.703125000000 0.273437500000 +rpjob_204.tga 0.703125000000 0.234375000000 0.742187500000 0.273437500000 +rpjob_205.tga 0.742187500000 0.234375000000 0.781250000000 0.273437500000 +rpjob_206.tga 0.781250000000 0.234375000000 0.820312500000 0.273437500000 +rpjob_207.tga 0.820312500000 0.234375000000 0.859375000000 0.273437500000 +rpjob_advanced.tga 0.859375000000 0.234375000000 0.898437500000 0.273437500000 +rpjob_elementary.tga 0.898437500000 0.234375000000 0.937500000000 0.273437500000 +rpjob_roleplay.tga 0.937500000000 0.234375000000 0.976562500000 0.273437500000 +rpjob_task.tga 0.468750000000 0.273437500000 0.507812500000 0.312500000000 +rpjob_task_certificats.tga 0.507812500000 0.273437500000 0.546875000000 0.312500000000 +rpjob_task_convert.tga 0.546875000000 0.273437500000 0.585937500000 0.312500000000 +rpjob_task_generic.tga 0.585937500000 0.273437500000 0.625000000000 0.312500000000 +rpjob_task_upgrade.tga 0.625000000000 0.273437500000 0.664062500000 0.312500000000 +RW_autolaunch.tga 0.664062500000 0.273437500000 0.703125000000 0.312500000000 +RW_bowgun.tga 0.703125000000 0.273437500000 0.742187500000 0.312500000000 +RW_grenade.tga 0.742187500000 0.273437500000 0.781250000000 0.312500000000 +RW_harpoongun.tga 0.781250000000 0.273437500000 0.820312500000 0.312500000000 +RW_launcher.tga 0.820312500000 0.273437500000 0.859375000000 0.312500000000 +RW_pistol.tga 0.859375000000 0.273437500000 0.898437500000 0.312500000000 +RW_pistolarc.tga 0.898437500000 0.273437500000 0.937500000000 0.312500000000 +RW_rifle.tga 0.937500000000 0.273437500000 0.976562500000 0.312500000000 +SH_buckler.tga 0.468750000000 0.312500000000 0.507812500000 0.351562500000 +SH_large_shield.tga 0.507812500000 0.312500000000 0.546875000000 0.351562500000 +spe_beast.tga 0.546875000000 0.312500000000 0.585937500000 0.351562500000 +spe_com.tga 0.585937500000 0.312500000000 0.625000000000 0.351562500000 +spe_inventory.tga 0.625000000000 0.312500000000 0.664062500000 0.351562500000 +spe_labs.tga 0.664062500000 0.312500000000 0.703125000000 0.351562500000 +spe_memory.tga 0.703125000000 0.312500000000 0.742187500000 0.351562500000 +spe_status.tga 0.742187500000 0.312500000000 0.781250000000 0.351562500000 +stimulating_water.tga 0.781250000000 0.312500000000 0.820312500000 0.351562500000 +ico_cataliseur_xp.tga 0.820312500000 0.312500000000 0.859375000000 0.351562500000 +ico_consommable_over.tga 0.859375000000 0.312500000000 0.898437500000 0.351562500000 +ico_fleur_carac_1.tga 0.898437500000 0.312500000000 0.937500000000 0.351562500000 +ico_fleur_carac_1_mask.tga 0.937500000000 0.312500000000 0.976562500000 0.351562500000 +ico_fleur_carac_2.tga 0.468750000000 0.351562500000 0.507812500000 0.390625000000 +ico_fleur_carac_2_mask.tga 0.507812500000 0.351562500000 0.546875000000 0.390625000000 +ico_fleur_carac_3.tga 0.546875000000 0.351562500000 0.585937500000 0.390625000000 +ico_fleur_carac_3_mask.tga 0.585937500000 0.351562500000 0.625000000000 0.390625000000 +ico_mission_art_fyros.tga 0.625000000000 0.351562500000 0.664062500000 0.390625000000 +ico_mission_art_matis.tga 0.664062500000 0.351562500000 0.703125000000 0.390625000000 +ico_mission_art_tryker.tga 0.703125000000 0.351562500000 0.742187500000 0.390625000000 +ico_mission_art_zorai.tga 0.742187500000 0.351562500000 0.781250000000 0.390625000000 +ico_mission_barrel.tga 0.781250000000 0.351562500000 0.820312500000 0.390625000000 +ico_mission_bottle.tga 0.820312500000 0.351562500000 0.859375000000 0.390625000000 +ico_mission_casket.tga 0.859375000000 0.351562500000 0.898437500000 0.390625000000 +ico_mission_medicine.tga 0.898437500000 0.351562500000 0.937500000000 0.390625000000 +ico_mission_message.tga 0.937500000000 0.351562500000 0.976562500000 0.390625000000 +ico_mission_package.tga 0.468750000000 0.390625000000 0.507812500000 0.429687500000 +ico_mission_pot.tga 0.507812500000 0.390625000000 0.546875000000 0.429687500000 +ico_mission_purse.tga 0.546875000000 0.390625000000 0.585937500000 0.429687500000 +ico_racine.tga 0.585937500000 0.390625000000 0.625000000000 0.429687500000 +ge_mission_outpost_townhall.tga 0.625000000000 0.390625000000 0.664062500000 0.429687500000 +ico_amande.tga 0.664062500000 0.390625000000 0.703125000000 0.429687500000 +to_searake.tga 0.703125000000 0.390625000000 0.742187500000 0.429687500000 +to_spade.tga 0.742187500000 0.390625000000 0.781250000000 0.429687500000 +to_stick.tga 0.781250000000 0.390625000000 0.820312500000 0.429687500000 +to_tunneling_knife.tga 0.820312500000 0.390625000000 0.859375000000 0.429687500000 +to_whip.tga 0.859375000000 0.390625000000 0.898437500000 0.429687500000 +to_wrench.tga 0.898437500000 0.390625000000 0.937500000000 0.429687500000 +TP_caravane.tga 0.937500000000 0.390625000000 0.976562500000 0.429687500000 +TP_kami.tga 0.468750000000 0.429687500000 0.507812500000 0.468750000000 +tetekitin.tga 0.507812500000 0.429687500000 0.546875000000 0.468750000000 +to_ammo.tga 0.546875000000 0.429687500000 0.585937500000 0.468750000000 +to_armor.tga 0.585937500000 0.429687500000 0.625000000000 0.468750000000 +to_cooking_pot.tga 0.625000000000 0.429687500000 0.664062500000 0.468750000000 +to_fishing_rod.tga 0.664062500000 0.429687500000 0.703125000000 0.468750000000 +to_forage.tga 0.703125000000 0.429687500000 0.742187500000 0.468750000000 +to_hammer.tga 0.742187500000 0.429687500000 0.781250000000 0.468750000000 +to_jewelry_hammer.tga 0.781250000000 0.429687500000 0.820312500000 0.468750000000 +to_jewels.tga 0.820312500000 0.429687500000 0.859375000000 0.468750000000 +to_leathercutter.tga 0.859375000000 0.429687500000 0.898437500000 0.468750000000 +to_melee.tga 0.898437500000 0.429687500000 0.937500000000 0.468750000000 +to_needle.tga 0.937500000000 0.429687500000 0.976562500000 0.468750000000 +to_pestle.tga 0.000000000000 0.468750000000 0.039062500000 0.507812500000 +ico_tourbe.tga 0.039062500000 0.468750000000 0.078125000000 0.507812500000 +improved_tool.tga 0.078125000000 0.468750000000 0.117187500000 0.507812500000 +item_default.tga 0.117187500000 0.468750000000 0.156250000000 0.507812500000 +item_plan_over.tga 0.156250000000 0.468750000000 0.195312500000 0.507812500000 +lucky_flower.tga 0.195312500000 0.468750000000 0.234375000000 0.507812500000 +W_AM_logo.tga 0.234375000000 0.468750000000 0.273437500000 0.507812500000 +w_pa_bracelet.tga 0.273437500000 0.468750000000 0.312500000000 0.507812500000 +w_pa_diadem.tga 0.312500000000 0.468750000000 0.351562500000 0.507812500000 +w_pa_earring.tga 0.351562500000 0.468750000000 0.390625000000 0.507812500000 +w_pa_pendant.tga 0.390625000000 0.468750000000 0.429687500000 0.507812500000 +w_pa_ring.tga 0.429687500000 0.468750000000 0.468750000000 0.507812500000 +xp_cat_green.tga 0.468750000000 0.468750000000 0.507812500000 0.507812500000 +PA_diadem.tga 0.507812500000 0.468750000000 0.546875000000 0.507812500000 +PA_earring.tga 0.546875000000 0.468750000000 0.585937500000 0.507812500000 +PA_pendant.tga 0.585937500000 0.468750000000 0.625000000000 0.507812500000 +PA_ring.tga 0.625000000000 0.468750000000 0.664062500000 0.507812500000 +protect_amber.tga 0.664062500000 0.468750000000 0.703125000000 0.507812500000 +pvp_aura.tga 0.703125000000 0.468750000000 0.742187500000 0.507812500000 asc_exit.tga 0.742187500000 0.468750000000 0.773437500000 0.500000000000 asc_rolemastercraft.tga 0.773437500000 0.468750000000 0.804687500000 0.500000000000 asc_rolemasterfight.tga 0.804687500000 0.468750000000 0.835937500000 0.500000000000 @@ -324,340 +324,350 @@ asc_rolemasterharvest.tga 0.835937500000 0.468750000000 0.867187500000 0.5000000 asc_rolemastermagic.tga 0.867187500000 0.468750000000 0.898437500000 0.500000000000 asc_unknown.tga 0.898437500000 0.468750000000 0.929687500000 0.500000000000 mail.tga 0.929687500000 0.468750000000 0.960937500000 0.492187500000 -mp_back_curative.tga 0.976562500000 0.078125000000 1.000000000000 0.101562500000 -mp_back_offensive.tga 0.976562500000 0.101562500000 1.000000000000 0.125000000000 -mp_back_selfonly.tga 0.976562500000 0.125000000000 1.000000000000 0.148437500000 -building_state_24x24.tga 0.976562500000 0.148437500000 1.000000000000 0.171875000000 -ico_ammo_bullet.tga 0.976562500000 0.171875000000 1.000000000000 0.195312500000 -ico_ammo_jacket.tga 0.976562500000 0.195312500000 1.000000000000 0.218750000000 -ico_angle.tga 0.976562500000 0.218750000000 1.000000000000 0.242187500000 -ico_anti_magic_shield.tga 0.976562500000 0.242187500000 1.000000000000 0.265625000000 -ico_armor.tga 0.976562500000 0.265625000000 1.000000000000 0.289062500000 -ico_armor_clip.tga 0.976562500000 0.289062500000 1.000000000000 0.312500000000 -ico_armor_heavy.tga 0.976562500000 0.312500000000 1.000000000000 0.335937500000 -ico_armor_kitin.tga 0.976562500000 0.335937500000 1.000000000000 0.359375000000 -ico_armor_light.tga 0.976562500000 0.359375000000 1.000000000000 0.382812500000 -ico_armor_medium.tga 0.976562500000 0.382812500000 1.000000000000 0.406250000000 -ico_armor_penalty.tga 0.976562500000 0.406250000000 1.000000000000 0.429687500000 -ico_armor_shell.tga 0.976562500000 0.429687500000 1.000000000000 0.453125000000 -ico_atys.tga 0.976562500000 0.453125000000 1.000000000000 0.476562500000 -ico_atysian.tga 0.960937500000 0.476562500000 0.984375000000 0.500000000000 -ico_balance_hp.tga 0.929687500000 0.492187500000 0.953125000000 0.515625000000 -ico_barrel.tga 0.742187500000 0.500000000000 0.765625000000 0.523437500000 -ico_bash.tga 0.765625000000 0.500000000000 0.789062500000 0.523437500000 -ico_berserk.tga 0.789062500000 0.500000000000 0.812500000000 0.523437500000 -ico_blade.tga 0.812500000000 0.500000000000 0.835937500000 0.523437500000 -ico_bleeding.tga 0.835937500000 0.500000000000 0.859375000000 0.523437500000 -ico_blind.tga 0.859375000000 0.500000000000 0.882812500000 0.523437500000 -ico_blunt.tga 0.882812500000 0.500000000000 0.906250000000 0.523437500000 -ico_bomb.tga 0.906250000000 0.500000000000 0.929687500000 0.523437500000 -cb_main_nue.tga 0.953125000000 0.500000000000 0.976562500000 0.523437500000 -ico_celestial.tga 0.976562500000 0.500000000000 1.000000000000 0.523437500000 -ico_circular_attack.tga 0.000000000000 0.507812500000 0.023437500000 0.531250000000 -ico_clothes.tga 0.023437500000 0.507812500000 0.046875000000 0.531250000000 -ico_cold.tga 0.046875000000 0.507812500000 0.070312500000 0.531250000000 -ico_concentration.tga 0.070312500000 0.507812500000 0.093750000000 0.531250000000 -BK_matis_brick.tga 0.093750000000 0.507812500000 0.117187500000 0.531250000000 -ico_constitution.tga 0.117187500000 0.507812500000 0.140625000000 0.531250000000 -ico_counterweight.tga 0.140625000000 0.507812500000 0.164062500000 0.531250000000 -ico_craft_buff.tga 0.164062500000 0.507812500000 0.187500000000 0.531250000000 -ico_create_sapload.tga 0.187500000000 0.507812500000 0.210937500000 0.531250000000 -ico_curse.tga 0.210937500000 0.507812500000 0.234375000000 0.531250000000 -ico_debuff.tga 0.234375000000 0.507812500000 0.257812500000 0.531250000000 -ico_debuff_resist.tga 0.257812500000 0.507812500000 0.281250000000 0.531250000000 -ico_debuff_skill.tga 0.281250000000 0.507812500000 0.304687500000 0.531250000000 -ico_desert.tga 0.304687500000 0.507812500000 0.328125000000 0.531250000000 -ico_dexterity.tga 0.328125000000 0.507812500000 0.351562500000 0.531250000000 -ico_disarm.tga 0.351562500000 0.507812500000 0.375000000000 0.531250000000 -ico_dodge.tga 0.375000000000 0.507812500000 0.398437500000 0.531250000000 -ico_dot.tga 0.398437500000 0.507812500000 0.421875000000 0.531250000000 -ico_durability.tga 0.421875000000 0.507812500000 0.445312500000 0.531250000000 -ico_electric.tga 0.445312500000 0.507812500000 0.468750000000 0.531250000000 -ico_explosif.tga 0.468750000000 0.507812500000 0.492187500000 0.531250000000 -ico_extracting.tga 0.492187500000 0.507812500000 0.515625000000 0.531250000000 -ico_fear.tga 0.515625000000 0.507812500000 0.539062500000 0.531250000000 -ico_feint.tga 0.539062500000 0.507812500000 0.562500000000 0.531250000000 -ico_fire.tga 0.562500000000 0.507812500000 0.585937500000 0.531250000000 -ico_firing_pin.tga 0.585937500000 0.507812500000 0.609375000000 0.531250000000 -ch_back.tga 0.609375000000 0.507812500000 0.632812500000 0.531250000000 -BK_generic_brick.tga 0.632812500000 0.507812500000 0.656250000000 0.531250000000 -mp_over_link.tga 0.656250000000 0.507812500000 0.679687500000 0.531250000000 -bk_aura.tga 0.679687500000 0.507812500000 0.703125000000 0.531250000000 -bk_conso.tga 0.703125000000 0.507812500000 0.726562500000 0.531250000000 -bk_outpost_brick.tga 0.929687500000 0.515625000000 0.953125000000 0.539062500000 -bk_power.tga 0.726562500000 0.523437500000 0.750000000000 0.546875000000 -ico_focus.tga 0.750000000000 0.523437500000 0.773437500000 0.546875000000 -ico_forage_buff.tga 0.773437500000 0.523437500000 0.796875000000 0.546875000000 -ico_forbid_item.tga 0.796875000000 0.523437500000 0.820312500000 0.546875000000 -ico_forest.tga 0.820312500000 0.523437500000 0.843750000000 0.546875000000 -2h_over.tga 0.843750000000 0.523437500000 0.867187500000 0.546875000000 -ico_gardening.tga 0.867187500000 0.523437500000 0.890625000000 0.546875000000 -ico_gentle.tga 0.890625000000 0.523437500000 0.914062500000 0.546875000000 -ico_goo.tga 0.953125000000 0.523437500000 0.976562500000 0.546875000000 -ico_gripp.tga 0.976562500000 0.523437500000 1.000000000000 0.546875000000 -1h_over.tga 0.000000000000 0.531250000000 0.023437500000 0.554687500000 -BK_fyros_brick.tga 0.023437500000 0.531250000000 0.046875000000 0.554687500000 -ico_hammer.tga 0.046875000000 0.531250000000 0.070312500000 0.554687500000 -ico_harmful.tga 0.070312500000 0.531250000000 0.093750000000 0.554687500000 -ico_hatred.tga 0.093750000000 0.531250000000 0.117187500000 0.554687500000 -ico_heal.tga 0.117187500000 0.531250000000 0.140625000000 0.554687500000 -ico_hit_rate.tga 0.140625000000 0.531250000000 0.164062500000 0.554687500000 -ico_incapacity.tga 0.164062500000 0.531250000000 0.187500000000 0.554687500000 -ico_intelligence.tga 0.187500000000 0.531250000000 0.210937500000 0.554687500000 -ico_interrupt.tga 0.210937500000 0.531250000000 0.234375000000 0.554687500000 -ico_invulnerability.tga 0.234375000000 0.531250000000 0.257812500000 0.554687500000 -ico_jewel_stone.tga 0.257812500000 0.531250000000 0.281250000000 0.554687500000 -ico_jewel_stone_support.tga 0.281250000000 0.531250000000 0.304687500000 0.554687500000 -ico_jungle.tga 0.304687500000 0.531250000000 0.328125000000 0.554687500000 -ico_lacustre.tga 0.328125000000 0.531250000000 0.351562500000 0.554687500000 -ico_landmark_bonus.tga 0.351562500000 0.531250000000 0.375000000000 0.554687500000 -ico_level.tga 0.375000000000 0.531250000000 0.398437500000 0.554687500000 -ico_lining.tga 0.398437500000 0.531250000000 0.421875000000 0.554687500000 -ico_location.tga 0.421875000000 0.531250000000 0.445312500000 0.554687500000 -ico_madness.tga 0.445312500000 0.531250000000 0.468750000000 0.554687500000 -ico_magic.tga 0.468750000000 0.531250000000 0.492187500000 0.554687500000 -ico_magic_action_buff.tga 0.492187500000 0.531250000000 0.515625000000 0.554687500000 -ico_magic_focus.tga 0.515625000000 0.531250000000 0.539062500000 0.554687500000 -ico_magic_target_buff.tga 0.539062500000 0.531250000000 0.562500000000 0.554687500000 -ico_melee_action_buff.tga 0.562500000000 0.531250000000 0.585937500000 0.554687500000 -ico_melee_target_buff.tga 0.585937500000 0.531250000000 0.609375000000 0.554687500000 -ico_mental.tga 0.609375000000 0.531250000000 0.632812500000 0.554687500000 -no_action.tga 0.632812500000 0.531250000000 0.656250000000 0.554687500000 -op_back.tga 0.656250000000 0.531250000000 0.679687500000 0.554687500000 -op_over_break.tga 0.679687500000 0.531250000000 0.703125000000 0.554687500000 -op_over_less.tga 0.703125000000 0.531250000000 0.726562500000 0.554687500000 -op_over_more.tga 0.914062500000 0.539062500000 0.937500000000 0.562500000000 -ico_metabolism.tga 0.726562500000 0.546875000000 0.750000000000 0.570312500000 -pa_back.tga 0.750000000000 0.546875000000 0.773437500000 0.570312500000 -ico_mezz.tga 0.773437500000 0.546875000000 0.796875000000 0.570312500000 -ico_misfortune.tga 0.796875000000 0.546875000000 0.820312500000 0.570312500000 -BK_magie_noire_brick.tga 0.820312500000 0.546875000000 0.843750000000 0.570312500000 -pa_over_break.tga 0.843750000000 0.546875000000 0.867187500000 0.570312500000 -pa_over_less.tga 0.867187500000 0.546875000000 0.890625000000 0.570312500000 -pa_over_more.tga 0.890625000000 0.546875000000 0.914062500000 0.570312500000 -BK_tryker_brick.tga 0.937500000000 0.546875000000 0.960937500000 0.570312500000 -cp_back.tga 0.960937500000 0.546875000000 0.984375000000 0.570312500000 -cp_over_break.tga 0.000000000000 0.554687500000 0.023437500000 0.578125000000 -pvp_ally_0.tga 0.023437500000 0.554687500000 0.046875000000 0.578125000000 -pvp_ally_1.tga 0.046875000000 0.554687500000 0.070312500000 0.578125000000 -pvp_ally_2.tga 0.070312500000 0.554687500000 0.093750000000 0.578125000000 -pvp_ally_3.tga 0.093750000000 0.554687500000 0.117187500000 0.578125000000 -pvp_ally_4.tga 0.117187500000 0.554687500000 0.140625000000 0.578125000000 -pvp_ally_6.tga 0.140625000000 0.554687500000 0.164062500000 0.578125000000 -pvp_ally_primas.tga 0.164062500000 0.554687500000 0.187500000000 0.578125000000 -pvp_ally_ranger.tga 0.187500000000 0.554687500000 0.210937500000 0.578125000000 -cp_over_less.tga 0.210937500000 0.554687500000 0.234375000000 0.578125000000 -cp_over_more.tga 0.234375000000 0.554687500000 0.257812500000 0.578125000000 -cp_over_opening.tga 0.257812500000 0.554687500000 0.281250000000 0.578125000000 -cp_over_opening_2.tga 0.281250000000 0.554687500000 0.304687500000 0.578125000000 -pvp_enemy_0.tga 0.304687500000 0.554687500000 0.328125000000 0.578125000000 -pvp_enemy_1.tga 0.328125000000 0.554687500000 0.351562500000 0.578125000000 -pvp_enemy_2.tga 0.351562500000 0.554687500000 0.375000000000 0.578125000000 -pvp_enemy_3.tga 0.375000000000 0.554687500000 0.398437500000 0.578125000000 -pvp_enemy_4.tga 0.398437500000 0.554687500000 0.421875000000 0.578125000000 -pvp_enemy_6.tga 0.421875000000 0.554687500000 0.445312500000 0.578125000000 -pvp_enemy_marauder.tga 0.445312500000 0.554687500000 0.468750000000 0.578125000000 -pvp_enemy_trytonist.tga 0.468750000000 0.554687500000 0.492187500000 0.578125000000 -bg_downloader.tga 0.492187500000 0.554687500000 0.515625000000 0.578125000000 -BK_zorai_brick.tga 0.515625000000 0.554687500000 0.539062500000 0.578125000000 -ef_back.tga 0.539062500000 0.554687500000 0.562500000000 0.578125000000 -ef_over_break.tga 0.562500000000 0.554687500000 0.585937500000 0.578125000000 -ico_move.tga 0.585937500000 0.554687500000 0.609375000000 0.578125000000 -ico_multiple_spots.tga 0.609375000000 0.554687500000 0.632812500000 0.578125000000 -ico_multi_fight.tga 0.632812500000 0.554687500000 0.656250000000 0.578125000000 -ef_over_less.tga 0.656250000000 0.554687500000 0.679687500000 0.578125000000 -ico_opening_hit.tga 0.679687500000 0.554687500000 0.703125000000 0.578125000000 -ico_over_autumn.tga 0.703125000000 0.554687500000 0.726562500000 0.578125000000 -ico_over_degenerated.tga 0.914062500000 0.562500000000 0.937500000000 0.585937500000 -ico_over_fauna.tga 0.726562500000 0.570312500000 0.750000000000 0.593750000000 -ico_over_flora.tga 0.750000000000 0.570312500000 0.773437500000 0.593750000000 -ico_over_hit_arms.tga 0.773437500000 0.570312500000 0.796875000000 0.593750000000 -ico_over_hit_chest.tga 0.796875000000 0.570312500000 0.820312500000 0.593750000000 -ico_over_hit_feet.tga 0.820312500000 0.570312500000 0.843750000000 0.593750000000 -ico_over_hit_feet_hands.tga 0.843750000000 0.570312500000 0.867187500000 0.593750000000 -ico_over_hit_feet_head.tga 0.867187500000 0.570312500000 0.890625000000 0.593750000000 -ico_over_hit_feet_x2.tga 0.890625000000 0.570312500000 0.914062500000 0.593750000000 -ico_over_hit_feint_x3.tga 0.937500000000 0.570312500000 0.960937500000 0.593750000000 -ico_over_hit_hands.tga 0.960937500000 0.570312500000 0.984375000000 0.593750000000 -ico_over_hit_hands_chest.tga 0.000000000000 0.578125000000 0.023437500000 0.601562500000 -ico_over_hit_hands_head.tga 0.023437500000 0.578125000000 0.046875000000 0.601562500000 -ico_over_hit_head.tga 0.046875000000 0.578125000000 0.070312500000 0.601562500000 -ico_over_hit_head_x3.tga 0.070312500000 0.578125000000 0.093750000000 0.601562500000 -ico_over_hit_legs.tga 0.093750000000 0.578125000000 0.117187500000 0.601562500000 -ico_over_homin.tga 0.117187500000 0.578125000000 0.140625000000 0.601562500000 -ico_over_kitin.tga 0.140625000000 0.578125000000 0.164062500000 0.601562500000 -ico_over_magic.tga 0.164062500000 0.578125000000 0.187500000000 0.601562500000 -ico_over_melee.tga 0.187500000000 0.578125000000 0.210937500000 0.601562500000 -ico_over_racial.tga 0.210937500000 0.578125000000 0.234375000000 0.601562500000 -ico_over_range.tga 0.234375000000 0.578125000000 0.257812500000 0.601562500000 -ico_over_special.tga 0.257812500000 0.578125000000 0.281250000000 0.601562500000 -ico_over_spring.tga 0.281250000000 0.578125000000 0.304687500000 0.601562500000 -ico_over_summer.tga 0.304687500000 0.578125000000 0.328125000000 0.601562500000 -ico_over_winter.tga 0.328125000000 0.578125000000 0.351562500000 0.601562500000 -ico_parry.tga 0.351562500000 0.578125000000 0.375000000000 0.601562500000 -ico_piercing.tga 0.375000000000 0.578125000000 0.398437500000 0.601562500000 -ico_pointe.tga 0.398437500000 0.578125000000 0.421875000000 0.601562500000 -ico_poison.tga 0.421875000000 0.578125000000 0.445312500000 0.601562500000 -ico_power.tga 0.445312500000 0.578125000000 0.468750000000 0.601562500000 -ico_preservation.tga 0.468750000000 0.578125000000 0.492187500000 0.601562500000 -ico_primal.tga 0.492187500000 0.578125000000 0.515625000000 0.601562500000 -ico_prime_roots.tga 0.515625000000 0.578125000000 0.539062500000 0.601562500000 -ico_private.tga 0.539062500000 0.578125000000 0.562500000000 0.601562500000 -ico_prospecting.tga 0.562500000000 0.578125000000 0.585937500000 0.601562500000 -ico_quality.tga 0.585937500000 0.578125000000 0.609375000000 0.601562500000 -ef_over_more.tga 0.609375000000 0.578125000000 0.632812500000 0.601562500000 -ico_range.tga 0.632812500000 0.578125000000 0.656250000000 0.601562500000 -ico_range_action_buff.tga 0.656250000000 0.578125000000 0.679687500000 0.601562500000 -ico_range_target_buff.tga 0.679687500000 0.578125000000 0.703125000000 0.601562500000 -ico_ricochet.tga 0.703125000000 0.578125000000 0.726562500000 0.601562500000 -ico_root.tga 0.914062500000 0.585937500000 0.937500000000 0.609375000000 -ico_rot.tga 0.726562500000 0.593750000000 0.750000000000 0.617187500000 -ico_safe.tga 0.750000000000 0.593750000000 0.773437500000 0.617187500000 -ico_sap.tga 0.773437500000 0.593750000000 0.796875000000 0.617187500000 -ico_self_damage.tga 0.796875000000 0.593750000000 0.820312500000 0.617187500000 -ico_shaft.tga 0.820312500000 0.593750000000 0.843750000000 0.617187500000 -ico_shielding.tga 0.843750000000 0.593750000000 0.867187500000 0.617187500000 -ico_shield_buff.tga 0.867187500000 0.593750000000 0.890625000000 0.617187500000 -ico_shield_up.tga 0.890625000000 0.593750000000 0.914062500000 0.617187500000 -ico_shockwave.tga 0.937500000000 0.593750000000 0.960937500000 0.617187500000 -ico_sickness.tga 0.960937500000 0.593750000000 0.984375000000 0.617187500000 -ico_slashing.tga 0.000000000000 0.601562500000 0.023437500000 0.625000000000 -ico_slow.tga 0.023437500000 0.601562500000 0.046875000000 0.625000000000 -ico_soft_spot.tga 0.046875000000 0.601562500000 0.070312500000 0.625000000000 -ico_source_time.tga 0.070312500000 0.601562500000 0.093750000000 0.625000000000 -ico_speed.tga 0.093750000000 0.601562500000 0.117187500000 0.625000000000 -ico_speeding_up.tga 0.117187500000 0.601562500000 0.140625000000 0.625000000000 -ico_spell_break.tga 0.140625000000 0.601562500000 0.164062500000 0.625000000000 -fo_back.tga 0.164062500000 0.601562500000 0.187500000000 0.625000000000 -ico_spray.tga 0.187500000000 0.601562500000 0.210937500000 0.625000000000 -ico_spying.tga 0.210937500000 0.601562500000 0.234375000000 0.625000000000 -ico_stamina.tga 0.234375000000 0.601562500000 0.257812500000 0.625000000000 -ico_strength.tga 0.257812500000 0.601562500000 0.281250000000 0.625000000000 -ico_stuffing.tga 0.281250000000 0.601562500000 0.304687500000 0.625000000000 -ico_stunn.tga 0.304687500000 0.601562500000 0.328125000000 0.625000000000 -fo_over.tga 0.328125000000 0.601562500000 0.351562500000 0.625000000000 -fp_ammo.tga 0.351562500000 0.601562500000 0.375000000000 0.625000000000 -fp_armor.tga 0.375000000000 0.601562500000 0.398437500000 0.625000000000 -fp_building.tga 0.398437500000 0.601562500000 0.421875000000 0.625000000000 -fp_jewel.tga 0.421875000000 0.601562500000 0.445312500000 0.625000000000 -fp_melee.tga 0.445312500000 0.601562500000 0.468750000000 0.625000000000 -fp_over.tga 0.468750000000 0.601562500000 0.492187500000 0.625000000000 -fp_range.tga 0.492187500000 0.601562500000 0.515625000000 0.625000000000 -fp_shield.tga 0.515625000000 0.601562500000 0.539062500000 0.625000000000 -fp_tools.tga 0.539062500000 0.601562500000 0.562500000000 0.625000000000 -brick_default.tga 0.562500000000 0.601562500000 0.585937500000 0.625000000000 -ico_taunt.tga 0.585937500000 0.601562500000 0.609375000000 0.625000000000 -tb_action_attack.tga 0.609375000000 0.601562500000 0.632812500000 0.625000000000 -tb_action_config.tga 0.632812500000 0.601562500000 0.656250000000 0.625000000000 -tb_action_disband.tga 0.656250000000 0.601562500000 0.679687500000 0.625000000000 -tb_action_disengage.tga 0.679687500000 0.601562500000 0.703125000000 0.625000000000 -tb_action_extract.tga 0.703125000000 0.601562500000 0.726562500000 0.625000000000 -tb_action_invite.tga 0.914062500000 0.609375000000 0.937500000000 0.632812500000 -tb_action_kick.tga 0.726562500000 0.617187500000 0.750000000000 0.640625000000 -tb_action_move.tga 0.750000000000 0.617187500000 0.773437500000 0.640625000000 -tb_action_run.tga 0.773437500000 0.617187500000 0.796875000000 0.640625000000 -tb_action_sit.tga 0.796875000000 0.617187500000 0.820312500000 0.640625000000 -tb_action_stand.tga 0.820312500000 0.617187500000 0.843750000000 0.640625000000 -tb_action_stop.tga 0.843750000000 0.617187500000 0.867187500000 0.640625000000 -tb_action_talk.tga 0.867187500000 0.617187500000 0.890625000000 0.640625000000 -tb_action_walk.tga 0.890625000000 0.617187500000 0.914062500000 0.640625000000 -tb_animals.tga 0.937500000000 0.617187500000 0.960937500000 0.640625000000 -tb_config.tga 0.960937500000 0.617187500000 0.984375000000 0.640625000000 -tb_connection.tga 0.000000000000 0.625000000000 0.023437500000 0.648437500000 -tb_contacts.tga 0.023437500000 0.625000000000 0.046875000000 0.648437500000 -tb_desk_1.tga 0.046875000000 0.625000000000 0.070312500000 0.648437500000 -tb_desk_2.tga 0.070312500000 0.625000000000 0.093750000000 0.648437500000 -tb_desk_3.tga 0.093750000000 0.625000000000 0.117187500000 0.648437500000 -tb_desk_4.tga 0.117187500000 0.625000000000 0.140625000000 0.648437500000 -tb_faction.tga 0.140625000000 0.625000000000 0.164062500000 0.648437500000 -tb_forum.tga 0.164062500000 0.625000000000 0.187500000000 0.648437500000 -tb_guild.tga 0.187500000000 0.625000000000 0.210937500000 0.648437500000 -TB_help2.tga 0.210937500000 0.625000000000 0.234375000000 0.648437500000 -tb_keys.tga 0.234375000000 0.625000000000 0.257812500000 0.648437500000 -tb_macros.tga 0.257812500000 0.625000000000 0.281250000000 0.648437500000 -tb_mail.tga 0.281250000000 0.625000000000 0.304687500000 0.648437500000 -tb_mode_dodge.tga 0.304687500000 0.625000000000 0.328125000000 0.648437500000 -tb_mode_parry.tga 0.328125000000 0.625000000000 0.351562500000 0.648437500000 -tb_over.tga 0.351562500000 0.625000000000 0.375000000000 0.648437500000 -tb_support.tga 0.375000000000 0.625000000000 0.398437500000 0.648437500000 -tb_team.tga 0.398437500000 0.625000000000 0.421875000000 0.648437500000 -tb_windows.tga 0.421875000000 0.625000000000 0.445312500000 0.648437500000 -ico_time.tga 0.445312500000 0.625000000000 0.468750000000 0.648437500000 -ico_time_bonus.tga 0.468750000000 0.625000000000 0.492187500000 0.648437500000 -ico_absorb_damage.tga 0.492187500000 0.625000000000 0.515625000000 0.648437500000 -ico_trigger.tga 0.515625000000 0.625000000000 0.539062500000 0.648437500000 -ico_umbrella.tga 0.539062500000 0.625000000000 0.562500000000 0.648437500000 -ico_use_enchantement.tga 0.562500000000 0.625000000000 0.585937500000 0.648437500000 -ico_vampire.tga 0.585937500000 0.625000000000 0.609375000000 0.648437500000 -ico_visibility.tga 0.609375000000 0.625000000000 0.632812500000 0.648437500000 -ico_war_cry.tga 0.632812500000 0.625000000000 0.656250000000 0.648437500000 -ico_weight.tga 0.656250000000 0.625000000000 0.679687500000 0.648437500000 -ico_wellbalanced.tga 0.679687500000 0.625000000000 0.703125000000 0.648437500000 -ico_will.tga 0.703125000000 0.625000000000 0.726562500000 0.648437500000 -ico_windding.tga 0.914062500000 0.632812500000 0.937500000000 0.656250000000 -ico_wisdom.tga 0.726562500000 0.640625000000 0.750000000000 0.664062500000 -ico_accurate.tga 0.750000000000 0.640625000000 0.773437500000 0.664062500000 -ico_acid.tga 0.773437500000 0.640625000000 0.796875000000 0.664062500000 -ico_aim.tga 0.796875000000 0.640625000000 0.820312500000 0.664062500000 -ico_aim_bird_wings.tga 0.820312500000 0.640625000000 0.843750000000 0.664062500000 -ico_aim_flying_kitin_abdomen.tga 0.843750000000 0.640625000000 0.867187500000 0.664062500000 -ico_aim_homin_arms.tga 0.867187500000 0.640625000000 0.890625000000 0.664062500000 -ico_aim_homin_chest.tga 0.890625000000 0.640625000000 0.914062500000 0.664062500000 -mf_back.tga 0.937500000000 0.640625000000 0.960937500000 0.664062500000 -us_back_0.tga 0.960937500000 0.640625000000 0.984375000000 0.664062500000 -us_back_1.tga 0.000000000000 0.648437500000 0.023437500000 0.671875000000 -us_back_2.tga 0.023437500000 0.648437500000 0.046875000000 0.671875000000 -us_back_3.tga 0.046875000000 0.648437500000 0.070312500000 0.671875000000 -us_back_4.tga 0.070312500000 0.648437500000 0.093750000000 0.671875000000 -us_back_5.tga 0.093750000000 0.648437500000 0.117187500000 0.671875000000 -us_back_6.tga 0.117187500000 0.648437500000 0.140625000000 0.671875000000 -us_back_7.tga 0.140625000000 0.648437500000 0.164062500000 0.671875000000 -us_back_8.tga 0.164062500000 0.648437500000 0.187500000000 0.671875000000 -us_back_9.tga 0.187500000000 0.648437500000 0.210937500000 0.671875000000 -us_ico_0.tga 0.210937500000 0.648437500000 0.234375000000 0.671875000000 -us_ico_1.tga 0.234375000000 0.648437500000 0.257812500000 0.671875000000 -us_ico_2.tga 0.257812500000 0.648437500000 0.281250000000 0.671875000000 -us_ico_3.tga 0.281250000000 0.648437500000 0.304687500000 0.671875000000 -us_ico_4.tga 0.304687500000 0.648437500000 0.328125000000 0.671875000000 -us_ico_5.tga 0.328125000000 0.648437500000 0.351562500000 0.671875000000 -us_ico_6.tga 0.351562500000 0.648437500000 0.375000000000 0.671875000000 -us_ico_7.tga 0.375000000000 0.648437500000 0.398437500000 0.671875000000 -us_ico_8.tga 0.398437500000 0.648437500000 0.421875000000 0.671875000000 -us_ico_9.tga 0.421875000000 0.648437500000 0.445312500000 0.671875000000 -us_over_0.tga 0.445312500000 0.648437500000 0.468750000000 0.671875000000 -us_over_1.tga 0.468750000000 0.648437500000 0.492187500000 0.671875000000 -us_over_2.tga 0.492187500000 0.648437500000 0.515625000000 0.671875000000 -us_over_3.tga 0.515625000000 0.648437500000 0.539062500000 0.671875000000 -us_over_4.tga 0.539062500000 0.648437500000 0.562500000000 0.671875000000 -mf_over.tga 0.562500000000 0.648437500000 0.585937500000 0.671875000000 -ico_aim_homin_feet.tga 0.585937500000 0.648437500000 0.609375000000 0.671875000000 -ico_aim_homin_feint.tga 0.609375000000 0.648437500000 0.632812500000 0.671875000000 -ico_aim_homin_hands.tga 0.632812500000 0.648437500000 0.656250000000 0.671875000000 -ico_aim_homin_head.tga 0.656250000000 0.648437500000 0.679687500000 0.671875000000 -ico_aim_homin_legs.tga 0.679687500000 0.648437500000 0.703125000000 0.671875000000 -mp3.tga 0.703125000000 0.648437500000 0.726562500000 0.671875000000 -W_slot_shortcut_id0.tga 0.914062500000 0.656250000000 0.937500000000 0.679687500000 -W_slot_shortcut_id1.tga 0.726562500000 0.664062500000 0.750000000000 0.687500000000 -W_slot_shortcut_id2.tga 0.750000000000 0.664062500000 0.773437500000 0.687500000000 -W_slot_shortcut_id3.tga 0.773437500000 0.664062500000 0.796875000000 0.687500000000 -W_slot_shortcut_id4.tga 0.796875000000 0.664062500000 0.820312500000 0.687500000000 -W_slot_shortcut_id5.tga 0.820312500000 0.664062500000 0.843750000000 0.687500000000 -W_slot_shortcut_id6.tga 0.843750000000 0.664062500000 0.867187500000 0.687500000000 -W_slot_shortcut_id7.tga 0.867187500000 0.664062500000 0.890625000000 0.687500000000 -W_slot_shortcut_id8.tga 0.890625000000 0.664062500000 0.914062500000 0.687500000000 -W_slot_shortcut_id9.tga 0.937500000000 0.664062500000 0.960937500000 0.687500000000 -w_slot_shortcut_shift_id0.tga 0.960937500000 0.664062500000 0.984375000000 0.687500000000 -w_slot_shortcut_shift_id1.tga 0.000000000000 0.671875000000 0.023437500000 0.695312500000 -w_slot_shortcut_shift_id2.tga 0.023437500000 0.671875000000 0.046875000000 0.695312500000 -w_slot_shortcut_shift_id3.tga 0.046875000000 0.671875000000 0.070312500000 0.695312500000 -w_slot_shortcut_shift_id4.tga 0.070312500000 0.671875000000 0.093750000000 0.695312500000 -w_slot_shortcut_shift_id5.tga 0.093750000000 0.671875000000 0.117187500000 0.695312500000 -w_slot_shortcut_shift_id6.tga 0.117187500000 0.671875000000 0.140625000000 0.695312500000 -w_slot_shortcut_shift_id7.tga 0.140625000000 0.671875000000 0.164062500000 0.695312500000 -w_slot_shortcut_shift_id8.tga 0.164062500000 0.671875000000 0.187500000000 0.695312500000 -w_slot_shortcut_shift_id9.tga 0.187500000000 0.671875000000 0.210937500000 0.695312500000 -ico_aim_kitin_head.tga 0.210937500000 0.671875000000 0.234375000000 0.695312500000 -ico_source_knowledge.tga 0.234375000000 0.671875000000 0.255859375000 0.695312500000 +ico_taunt.tga 0.976562500000 0.078125000000 1.000000000000 0.101562500000 +BK_generic_brick.tga 0.976562500000 0.101562500000 1.000000000000 0.125000000000 +bk_aura.tga 0.976562500000 0.125000000000 1.000000000000 0.148437500000 +bk_outpost_brick.tga 0.976562500000 0.148437500000 1.000000000000 0.171875000000 +bk_power.tga 0.976562500000 0.171875000000 1.000000000000 0.195312500000 +no_action.tga 0.976562500000 0.195312500000 1.000000000000 0.218750000000 +no_pvp.tga 0.976562500000 0.218750000000 1.000000000000 0.242187500000 +op_back.tga 0.976562500000 0.242187500000 1.000000000000 0.265625000000 +op_over_break.tga 0.976562500000 0.265625000000 1.000000000000 0.289062500000 +op_over_less.tga 0.976562500000 0.289062500000 1.000000000000 0.312500000000 +op_over_more.tga 0.976562500000 0.312500000000 1.000000000000 0.335937500000 +bk_conso.tga 0.976562500000 0.335937500000 1.000000000000 0.359375000000 +pa_back.tga 0.976562500000 0.359375000000 1.000000000000 0.382812500000 +2h_over.tga 0.976562500000 0.382812500000 1.000000000000 0.406250000000 +1h_over.tga 0.976562500000 0.406250000000 1.000000000000 0.429687500000 +pvp_duel.tga 0.976562500000 0.429687500000 1.000000000000 0.453125000000 +pvp_enemy_0.tga 0.976562500000 0.453125000000 1.000000000000 0.476562500000 +pvp_enemy_1.tga 0.960937500000 0.476562500000 0.984375000000 0.500000000000 +pvp_enemy_2.tga 0.929687500000 0.492187500000 0.953125000000 0.515625000000 +pvp_enemy_3.tga 0.742187500000 0.500000000000 0.765625000000 0.523437500000 +pvp_enemy_4.tga 0.765625000000 0.500000000000 0.789062500000 0.523437500000 +pvp_enemy_6.tga 0.789062500000 0.500000000000 0.812500000000 0.523437500000 +pvp_enemy_flag.tga 0.812500000000 0.500000000000 0.835937500000 0.523437500000 +pvp_enemy_marauder.tga 0.835937500000 0.500000000000 0.859375000000 0.523437500000 +pvp_enemy_tag.tga 0.859375000000 0.500000000000 0.882812500000 0.523437500000 +tb_action_attack.tga 0.882812500000 0.500000000000 0.906250000000 0.523437500000 +tb_action_config.tga 0.906250000000 0.500000000000 0.929687500000 0.523437500000 +tb_action_disband.tga 0.953125000000 0.500000000000 0.976562500000 0.523437500000 +tb_action_disengage.tga 0.976562500000 0.500000000000 1.000000000000 0.523437500000 +tb_action_extract.tga 0.000000000000 0.507812500000 0.023437500000 0.531250000000 +tb_action_invite.tga 0.023437500000 0.507812500000 0.046875000000 0.531250000000 +tb_action_kick.tga 0.046875000000 0.507812500000 0.070312500000 0.531250000000 +tb_action_move.tga 0.070312500000 0.507812500000 0.093750000000 0.531250000000 +tb_action_run.tga 0.093750000000 0.507812500000 0.117187500000 0.531250000000 +tb_action_sit.tga 0.117187500000 0.507812500000 0.140625000000 0.531250000000 +tb_action_stand.tga 0.140625000000 0.507812500000 0.164062500000 0.531250000000 +tb_action_stop.tga 0.164062500000 0.507812500000 0.187500000000 0.531250000000 +tb_action_talk.tga 0.187500000000 0.507812500000 0.210937500000 0.531250000000 +tb_action_walk.tga 0.210937500000 0.507812500000 0.234375000000 0.531250000000 +ico_armor_penalty.tga 0.234375000000 0.507812500000 0.257812500000 0.531250000000 +ico_armor_shell.tga 0.257812500000 0.507812500000 0.281250000000 0.531250000000 +ico_atys.tga 0.281250000000 0.507812500000 0.304687500000 0.531250000000 +ico_atysian.tga 0.304687500000 0.507812500000 0.328125000000 0.531250000000 +ico_balance_hp.tga 0.328125000000 0.507812500000 0.351562500000 0.531250000000 +ico_barrel.tga 0.351562500000 0.507812500000 0.375000000000 0.531250000000 +ico_bash.tga 0.375000000000 0.507812500000 0.398437500000 0.531250000000 +ico_berserk.tga 0.398437500000 0.507812500000 0.421875000000 0.531250000000 +ico_blade.tga 0.421875000000 0.507812500000 0.445312500000 0.531250000000 +ico_bleeding.tga 0.445312500000 0.507812500000 0.468750000000 0.531250000000 +ico_blind.tga 0.468750000000 0.507812500000 0.492187500000 0.531250000000 +ico_blunt.tga 0.492187500000 0.507812500000 0.515625000000 0.531250000000 +ico_bomb.tga 0.515625000000 0.507812500000 0.539062500000 0.531250000000 +pvp_enemy_trytonist.tga 0.539062500000 0.507812500000 0.562500000000 0.531250000000 +ico_celestial.tga 0.562500000000 0.507812500000 0.585937500000 0.531250000000 +ico_circular_attack.tga 0.585937500000 0.507812500000 0.609375000000 0.531250000000 +ico_cold.tga 0.609375000000 0.507812500000 0.632812500000 0.531250000000 +ico_concentration.tga 0.632812500000 0.507812500000 0.656250000000 0.531250000000 +pvp_flag.tga 0.656250000000 0.507812500000 0.679687500000 0.531250000000 +ico_constitution.tga 0.679687500000 0.507812500000 0.703125000000 0.531250000000 +ico_counterweight.tga 0.703125000000 0.507812500000 0.726562500000 0.531250000000 +ico_craft_buff.tga 0.929687500000 0.515625000000 0.953125000000 0.539062500000 +ico_create_sapload.tga 0.726562500000 0.523437500000 0.750000000000 0.546875000000 +ico_curse.tga 0.750000000000 0.523437500000 0.773437500000 0.546875000000 +ico_debuff.tga 0.773437500000 0.523437500000 0.796875000000 0.546875000000 +ico_debuff_resist.tga 0.796875000000 0.523437500000 0.820312500000 0.546875000000 +ico_debuff_skill.tga 0.820312500000 0.523437500000 0.843750000000 0.546875000000 +ico_desert.tga 0.843750000000 0.523437500000 0.867187500000 0.546875000000 +ico_dexterity.tga 0.867187500000 0.523437500000 0.890625000000 0.546875000000 +ico_disarm.tga 0.890625000000 0.523437500000 0.914062500000 0.546875000000 +ico_dodge.tga 0.953125000000 0.523437500000 0.976562500000 0.546875000000 +ico_dot.tga 0.976562500000 0.523437500000 1.000000000000 0.546875000000 +ico_electric.tga 0.000000000000 0.531250000000 0.023437500000 0.554687500000 +ico_explosif.tga 0.023437500000 0.531250000000 0.046875000000 0.554687500000 +ico_extracting.tga 0.046875000000 0.531250000000 0.070312500000 0.554687500000 +ico_fear.tga 0.070312500000 0.531250000000 0.093750000000 0.554687500000 +ico_feint.tga 0.093750000000 0.531250000000 0.117187500000 0.554687500000 +ico_fire.tga 0.117187500000 0.531250000000 0.140625000000 0.554687500000 +ico_firing_pin.tga 0.140625000000 0.531250000000 0.164062500000 0.554687500000 +pvp_neutral.tga 0.164062500000 0.531250000000 0.187500000000 0.554687500000 +pvp_tag.tga 0.187500000000 0.531250000000 0.210937500000 0.554687500000 +BK_magie_noire_brick.tga 0.210937500000 0.531250000000 0.234375000000 0.554687500000 +cp_back.tga 0.234375000000 0.531250000000 0.257812500000 0.554687500000 +cp_over_break.tga 0.257812500000 0.531250000000 0.281250000000 0.554687500000 +cp_over_less.tga 0.281250000000 0.531250000000 0.304687500000 0.554687500000 +ico_focus.tga 0.304687500000 0.531250000000 0.328125000000 0.554687500000 +ico_forage_buff.tga 0.328125000000 0.531250000000 0.351562500000 0.554687500000 +ico_forbid_item.tga 0.351562500000 0.531250000000 0.375000000000 0.554687500000 +ico_forest.tga 0.375000000000 0.531250000000 0.398437500000 0.554687500000 +ico_jungle.tga 0.398437500000 0.531250000000 0.421875000000 0.554687500000 +ico_lacustre.tga 0.421875000000 0.531250000000 0.445312500000 0.554687500000 +ico_landmark_bonus.tga 0.445312500000 0.531250000000 0.468750000000 0.554687500000 +ico_level.tga 0.468750000000 0.531250000000 0.492187500000 0.554687500000 +ico_lining.tga 0.492187500000 0.531250000000 0.515625000000 0.554687500000 +ico_location.tga 0.515625000000 0.531250000000 0.539062500000 0.554687500000 +ico_madness.tga 0.539062500000 0.531250000000 0.562500000000 0.554687500000 +ico_magic.tga 0.562500000000 0.531250000000 0.585937500000 0.554687500000 +ico_magic_action_buff.tga 0.585937500000 0.531250000000 0.609375000000 0.554687500000 +ico_magic_focus.tga 0.609375000000 0.531250000000 0.632812500000 0.554687500000 +ico_magic_target_buff.tga 0.632812500000 0.531250000000 0.656250000000 0.554687500000 +ico_melee_action_buff.tga 0.656250000000 0.531250000000 0.679687500000 0.554687500000 +ico_melee_target_buff.tga 0.679687500000 0.531250000000 0.703125000000 0.554687500000 +ico_mental.tga 0.703125000000 0.531250000000 0.726562500000 0.554687500000 +ico_metabolism.tga 0.914062500000 0.539062500000 0.937500000000 0.562500000000 +ico_mezz.tga 0.726562500000 0.546875000000 0.750000000000 0.570312500000 +cp_over_more.tga 0.750000000000 0.546875000000 0.773437500000 0.570312500000 +cp_over_opening.tga 0.773437500000 0.546875000000 0.796875000000 0.570312500000 +tb_animals.tga 0.796875000000 0.546875000000 0.820312500000 0.570312500000 +cp_over_opening_2.tga 0.820312500000 0.546875000000 0.843750000000 0.570312500000 +us_back_9.tga 0.843750000000 0.546875000000 0.867187500000 0.570312500000 +BK_tryker_brick.tga 0.867187500000 0.546875000000 0.890625000000 0.570312500000 +ico_spray.tga 0.890625000000 0.546875000000 0.914062500000 0.570312500000 +ico_spying.tga 0.937500000000 0.546875000000 0.960937500000 0.570312500000 +ico_stamina.tga 0.960937500000 0.546875000000 0.984375000000 0.570312500000 +ico_strength.tga 0.000000000000 0.554687500000 0.023437500000 0.578125000000 +ico_stuffing.tga 0.023437500000 0.554687500000 0.046875000000 0.578125000000 +ico_stunn.tga 0.046875000000 0.554687500000 0.070312500000 0.578125000000 +ico_move.tga 0.070312500000 0.554687500000 0.093750000000 0.578125000000 +ico_multiple_spots.tga 0.093750000000 0.554687500000 0.117187500000 0.578125000000 +ico_multi_fight.tga 0.117187500000 0.554687500000 0.140625000000 0.578125000000 +ico_opening_hit.tga 0.140625000000 0.554687500000 0.164062500000 0.578125000000 +ico_over_autumn.tga 0.164062500000 0.554687500000 0.187500000000 0.578125000000 +ico_over_degenerated.tga 0.187500000000 0.554687500000 0.210937500000 0.578125000000 +ico_over_fauna.tga 0.210937500000 0.554687500000 0.234375000000 0.578125000000 +ico_over_flora.tga 0.234375000000 0.554687500000 0.257812500000 0.578125000000 +ico_over_hit_arms.tga 0.257812500000 0.554687500000 0.281250000000 0.578125000000 +ico_over_hit_chest.tga 0.281250000000 0.554687500000 0.304687500000 0.578125000000 +ico_over_hit_feet.tga 0.304687500000 0.554687500000 0.328125000000 0.578125000000 +ico_over_hit_feet_hands.tga 0.328125000000 0.554687500000 0.351562500000 0.578125000000 +ico_over_hit_feet_head.tga 0.351562500000 0.554687500000 0.375000000000 0.578125000000 +ico_over_hit_feet_x2.tga 0.375000000000 0.554687500000 0.398437500000 0.578125000000 +ico_over_hit_feint_x3.tga 0.398437500000 0.554687500000 0.421875000000 0.578125000000 +ico_over_hit_hands.tga 0.421875000000 0.554687500000 0.445312500000 0.578125000000 +ico_over_hit_hands_chest.tga 0.445312500000 0.554687500000 0.468750000000 0.578125000000 +ico_over_hit_hands_head.tga 0.468750000000 0.554687500000 0.492187500000 0.578125000000 +ico_over_hit_head.tga 0.492187500000 0.554687500000 0.515625000000 0.578125000000 +ico_over_hit_legs.tga 0.515625000000 0.554687500000 0.539062500000 0.578125000000 +ico_over_homin.tga 0.539062500000 0.554687500000 0.562500000000 0.578125000000 +ico_over_kitin.tga 0.562500000000 0.554687500000 0.585937500000 0.578125000000 +ico_over_magic.tga 0.585937500000 0.554687500000 0.609375000000 0.578125000000 +ico_over_melee.tga 0.609375000000 0.554687500000 0.632812500000 0.578125000000 +ico_over_racial.tga 0.632812500000 0.554687500000 0.656250000000 0.578125000000 +ico_over_range.tga 0.656250000000 0.554687500000 0.679687500000 0.578125000000 +ico_over_special.tga 0.679687500000 0.554687500000 0.703125000000 0.578125000000 +ico_over_spring.tga 0.703125000000 0.554687500000 0.726562500000 0.578125000000 +ico_over_summer.tga 0.914062500000 0.562500000000 0.937500000000 0.585937500000 +ico_over_winter.tga 0.726562500000 0.570312500000 0.750000000000 0.593750000000 +ico_parry.tga 0.750000000000 0.570312500000 0.773437500000 0.593750000000 +ico_piercing.tga 0.773437500000 0.570312500000 0.796875000000 0.593750000000 +ico_pointe.tga 0.796875000000 0.570312500000 0.820312500000 0.593750000000 +ico_poison.tga 0.820312500000 0.570312500000 0.843750000000 0.593750000000 +ico_power.tga 0.843750000000 0.570312500000 0.867187500000 0.593750000000 +ico_preservation.tga 0.867187500000 0.570312500000 0.890625000000 0.593750000000 +ico_prime_roots.tga 0.890625000000 0.570312500000 0.914062500000 0.593750000000 +ico_private.tga 0.937500000000 0.570312500000 0.960937500000 0.593750000000 +ico_prospecting.tga 0.960937500000 0.570312500000 0.984375000000 0.593750000000 +ico_quality.tga 0.000000000000 0.578125000000 0.023437500000 0.601562500000 +BK_fyros_brick.tga 0.023437500000 0.578125000000 0.046875000000 0.601562500000 +ico_range.tga 0.046875000000 0.578125000000 0.070312500000 0.601562500000 +ico_range_action_buff.tga 0.070312500000 0.578125000000 0.093750000000 0.601562500000 +ico_range_target_buff.tga 0.093750000000 0.578125000000 0.117187500000 0.601562500000 +ico_ricochet.tga 0.117187500000 0.578125000000 0.140625000000 0.601562500000 +ico_root.tga 0.140625000000 0.578125000000 0.164062500000 0.601562500000 +ico_rot.tga 0.164062500000 0.578125000000 0.187500000000 0.601562500000 +ico_safe.tga 0.187500000000 0.578125000000 0.210937500000 0.601562500000 +ico_sap.tga 0.210937500000 0.578125000000 0.234375000000 0.601562500000 +ico_self_damage.tga 0.234375000000 0.578125000000 0.257812500000 0.601562500000 +ico_shaft.tga 0.257812500000 0.578125000000 0.281250000000 0.601562500000 +ico_shielding.tga 0.281250000000 0.578125000000 0.304687500000 0.601562500000 +ico_shield_buff.tga 0.304687500000 0.578125000000 0.328125000000 0.601562500000 +ico_shield_up.tga 0.328125000000 0.578125000000 0.351562500000 0.601562500000 +ico_shockwave.tga 0.351562500000 0.578125000000 0.375000000000 0.601562500000 +ico_sickness.tga 0.375000000000 0.578125000000 0.398437500000 0.601562500000 +ico_slashing.tga 0.398437500000 0.578125000000 0.421875000000 0.601562500000 +ico_slow.tga 0.421875000000 0.578125000000 0.445312500000 0.601562500000 +ico_soft_spot.tga 0.445312500000 0.578125000000 0.468750000000 0.601562500000 +ico_source_time.tga 0.468750000000 0.578125000000 0.492187500000 0.601562500000 +ico_speed.tga 0.492187500000 0.578125000000 0.515625000000 0.601562500000 +ico_speeding_up.tga 0.515625000000 0.578125000000 0.539062500000 0.601562500000 +ico_spell_break.tga 0.539062500000 0.578125000000 0.562500000000 0.601562500000 +fp_armor.tga 0.562500000000 0.578125000000 0.585937500000 0.601562500000 +fp_building.tga 0.585937500000 0.578125000000 0.609375000000 0.601562500000 +fp_jewel.tga 0.609375000000 0.578125000000 0.632812500000 0.601562500000 +fp_melee.tga 0.632812500000 0.578125000000 0.656250000000 0.601562500000 +fp_over.tga 0.656250000000 0.578125000000 0.679687500000 0.601562500000 +fp_range.tga 0.679687500000 0.578125000000 0.703125000000 0.601562500000 +fp_shield.tga 0.703125000000 0.578125000000 0.726562500000 0.601562500000 +fp_tools.tga 0.914062500000 0.585937500000 0.937500000000 0.609375000000 +ef_back.tga 0.726562500000 0.593750000000 0.750000000000 0.617187500000 +ico_absorb_damage.tga 0.750000000000 0.593750000000 0.773437500000 0.617187500000 +ico_accurate.tga 0.773437500000 0.593750000000 0.796875000000 0.617187500000 +ico_acid.tga 0.796875000000 0.593750000000 0.820312500000 0.617187500000 +ico_aim.tga 0.820312500000 0.593750000000 0.843750000000 0.617187500000 +ico_aim_bird_wings.tga 0.843750000000 0.593750000000 0.867187500000 0.617187500000 +ico_aim_flying_kitin_abdomen.tga 0.867187500000 0.593750000000 0.890625000000 0.617187500000 +ico_aim_homin_arms.tga 0.890625000000 0.593750000000 0.914062500000 0.617187500000 +ico_aim_homin_chest.tga 0.937500000000 0.593750000000 0.960937500000 0.617187500000 +ico_aim_homin_feet.tga 0.960937500000 0.593750000000 0.984375000000 0.617187500000 +ico_aim_homin_feint.tga 0.000000000000 0.601562500000 0.023437500000 0.625000000000 +ico_aim_homin_hands.tga 0.023437500000 0.601562500000 0.046875000000 0.625000000000 +ico_aim_homin_head.tga 0.046875000000 0.601562500000 0.070312500000 0.625000000000 +ico_aim_homin_legs.tga 0.070312500000 0.601562500000 0.093750000000 0.625000000000 +ico_aim_kitin_head.tga 0.093750000000 0.601562500000 0.117187500000 0.625000000000 +ef_over_break.tga 0.117187500000 0.601562500000 0.140625000000 0.625000000000 +ico_ammo_bullet.tga 0.140625000000 0.601562500000 0.164062500000 0.625000000000 +ico_ammo_jacket.tga 0.164062500000 0.601562500000 0.187500000000 0.625000000000 +ico_angle.tga 0.187500000000 0.601562500000 0.210937500000 0.625000000000 +ico_anti_magic_shield.tga 0.210937500000 0.601562500000 0.234375000000 0.625000000000 +ico_armor.tga 0.234375000000 0.601562500000 0.257812500000 0.625000000000 +ico_armor_clip.tga 0.257812500000 0.601562500000 0.281250000000 0.625000000000 +ico_armor_heavy.tga 0.281250000000 0.601562500000 0.304687500000 0.625000000000 +ico_armor_kitin.tga 0.304687500000 0.601562500000 0.328125000000 0.625000000000 +ico_armor_light.tga 0.328125000000 0.601562500000 0.351562500000 0.625000000000 +ef_over_less.tga 0.351562500000 0.601562500000 0.375000000000 0.625000000000 +ef_over_more.tga 0.375000000000 0.601562500000 0.398437500000 0.625000000000 +fo_back.tga 0.398437500000 0.601562500000 0.421875000000 0.625000000000 +fo_over.tga 0.421875000000 0.601562500000 0.445312500000 0.625000000000 +ico_gardening.tga 0.445312500000 0.601562500000 0.468750000000 0.625000000000 +ico_gentle.tga 0.468750000000 0.601562500000 0.492187500000 0.625000000000 +mp_over_link.tga 0.492187500000 0.601562500000 0.515625000000 0.625000000000 +ico_goo.tga 0.515625000000 0.601562500000 0.539062500000 0.625000000000 +us_back_0.tga 0.539062500000 0.601562500000 0.562500000000 0.625000000000 +us_back_1.tga 0.562500000000 0.601562500000 0.585937500000 0.625000000000 +us_back_2.tga 0.585937500000 0.601562500000 0.609375000000 0.625000000000 +us_back_3.tga 0.609375000000 0.601562500000 0.632812500000 0.625000000000 +us_back_4.tga 0.632812500000 0.601562500000 0.656250000000 0.625000000000 +us_back_5.tga 0.656250000000 0.601562500000 0.679687500000 0.625000000000 +us_back_6.tga 0.679687500000 0.601562500000 0.703125000000 0.625000000000 +us_back_7.tga 0.703125000000 0.601562500000 0.726562500000 0.625000000000 +us_back_8.tga 0.914062500000 0.609375000000 0.937500000000 0.632812500000 +tb_config.tga 0.726562500000 0.617187500000 0.750000000000 0.640625000000 +tb_connection.tga 0.750000000000 0.617187500000 0.773437500000 0.640625000000 +tb_contacts.tga 0.773437500000 0.617187500000 0.796875000000 0.640625000000 +tb_desk_1.tga 0.796875000000 0.617187500000 0.820312500000 0.640625000000 +tb_desk_2.tga 0.820312500000 0.617187500000 0.843750000000 0.640625000000 +tb_desk_3.tga 0.843750000000 0.617187500000 0.867187500000 0.640625000000 +tb_desk_4.tga 0.867187500000 0.617187500000 0.890625000000 0.640625000000 +tb_faction.tga 0.890625000000 0.617187500000 0.914062500000 0.640625000000 +tb_forum.tga 0.937500000000 0.617187500000 0.960937500000 0.640625000000 +tb_guild.tga 0.960937500000 0.617187500000 0.984375000000 0.640625000000 +TB_help2.tga 0.000000000000 0.625000000000 0.023437500000 0.648437500000 +tb_keys.tga 0.023437500000 0.625000000000 0.046875000000 0.648437500000 +tb_macros.tga 0.046875000000 0.625000000000 0.070312500000 0.648437500000 +tb_mail.tga 0.070312500000 0.625000000000 0.093750000000 0.648437500000 +tb_mode_dodge.tga 0.093750000000 0.625000000000 0.117187500000 0.648437500000 +tb_mode_parry.tga 0.117187500000 0.625000000000 0.140625000000 0.648437500000 +tb_over.tga 0.140625000000 0.625000000000 0.164062500000 0.648437500000 +tb_support.tga 0.164062500000 0.625000000000 0.187500000000 0.648437500000 +tb_team.tga 0.187500000000 0.625000000000 0.210937500000 0.648437500000 +tb_windows.tga 0.210937500000 0.625000000000 0.234375000000 0.648437500000 +ico_gripp.tga 0.234375000000 0.625000000000 0.257812500000 0.648437500000 +BK_zorai_brick.tga 0.257812500000 0.625000000000 0.281250000000 0.648437500000 +mf_back.tga 0.281250000000 0.625000000000 0.304687500000 0.648437500000 +mf_over.tga 0.304687500000 0.625000000000 0.328125000000 0.648437500000 +brick_default.tga 0.328125000000 0.625000000000 0.351562500000 0.648437500000 +ico_hammer.tga 0.351562500000 0.625000000000 0.375000000000 0.648437500000 +ico_harmful.tga 0.375000000000 0.625000000000 0.398437500000 0.648437500000 +ico_hatred.tga 0.398437500000 0.625000000000 0.421875000000 0.648437500000 +ico_heal.tga 0.421875000000 0.625000000000 0.445312500000 0.648437500000 +mp3.tga 0.445312500000 0.625000000000 0.468750000000 0.648437500000 +ico_hit_rate.tga 0.468750000000 0.625000000000 0.492187500000 0.648437500000 +mp_back_curative.tga 0.492187500000 0.625000000000 0.515625000000 0.648437500000 +mp_back_offensive.tga 0.515625000000 0.625000000000 0.539062500000 0.648437500000 +ico_time.tga 0.539062500000 0.625000000000 0.562500000000 0.648437500000 +ico_time_bonus.tga 0.562500000000 0.625000000000 0.585937500000 0.648437500000 +mp_back_selfonly.tga 0.585937500000 0.625000000000 0.609375000000 0.648437500000 +ico_trigger.tga 0.609375000000 0.625000000000 0.632812500000 0.648437500000 +ico_umbrella.tga 0.632812500000 0.625000000000 0.656250000000 0.648437500000 +ico_use_enchantement.tga 0.656250000000 0.625000000000 0.679687500000 0.648437500000 +ico_vampire.tga 0.679687500000 0.625000000000 0.703125000000 0.648437500000 +ico_visibility.tga 0.703125000000 0.625000000000 0.726562500000 0.648437500000 +ico_war_cry.tga 0.914062500000 0.632812500000 0.937500000000 0.656250000000 +ico_weight.tga 0.726562500000 0.640625000000 0.750000000000 0.664062500000 +ico_wellbalanced.tga 0.750000000000 0.640625000000 0.773437500000 0.664062500000 +ico_will.tga 0.773437500000 0.640625000000 0.796875000000 0.664062500000 +ico_windding.tga 0.796875000000 0.640625000000 0.820312500000 0.664062500000 +ico_wisdom.tga 0.820312500000 0.640625000000 0.843750000000 0.664062500000 +ico_incapacity.tga 0.843750000000 0.640625000000 0.867187500000 0.664062500000 +ico_intelligence.tga 0.867187500000 0.640625000000 0.890625000000 0.664062500000 +ico_interrupt.tga 0.890625000000 0.640625000000 0.914062500000 0.664062500000 +ico_invulnerability.tga 0.937500000000 0.640625000000 0.960937500000 0.664062500000 +ico_jewel_stone.tga 0.960937500000 0.640625000000 0.984375000000 0.664062500000 +us_ico_0.tga 0.000000000000 0.648437500000 0.023437500000 0.671875000000 +us_ico_1.tga 0.023437500000 0.648437500000 0.046875000000 0.671875000000 +us_ico_2.tga 0.046875000000 0.648437500000 0.070312500000 0.671875000000 +us_ico_3.tga 0.070312500000 0.648437500000 0.093750000000 0.671875000000 +us_ico_4.tga 0.093750000000 0.648437500000 0.117187500000 0.671875000000 +us_ico_5.tga 0.117187500000 0.648437500000 0.140625000000 0.671875000000 +us_ico_6.tga 0.140625000000 0.648437500000 0.164062500000 0.671875000000 +us_ico_7.tga 0.164062500000 0.648437500000 0.187500000000 0.671875000000 +us_ico_8.tga 0.187500000000 0.648437500000 0.210937500000 0.671875000000 +us_ico_9.tga 0.210937500000 0.648437500000 0.234375000000 0.671875000000 +us_over_0.tga 0.234375000000 0.648437500000 0.257812500000 0.671875000000 +us_over_1.tga 0.257812500000 0.648437500000 0.281250000000 0.671875000000 +us_over_2.tga 0.281250000000 0.648437500000 0.304687500000 0.671875000000 +us_over_3.tga 0.304687500000 0.648437500000 0.328125000000 0.671875000000 +us_over_4.tga 0.328125000000 0.648437500000 0.351562500000 0.671875000000 +building_state_24x24.tga 0.351562500000 0.648437500000 0.375000000000 0.671875000000 +cb_main_nue.tga 0.375000000000 0.648437500000 0.398437500000 0.671875000000 +fp_ammo.tga 0.398437500000 0.648437500000 0.421875000000 0.671875000000 +ico_armor_medium.tga 0.421875000000 0.648437500000 0.445312500000 0.671875000000 +ico_clothes.tga 0.445312500000 0.648437500000 0.468750000000 0.671875000000 +ico_durability.tga 0.468750000000 0.648437500000 0.492187500000 0.671875000000 +W_slot_shortcut_id0.tga 0.492187500000 0.648437500000 0.515625000000 0.671875000000 +W_slot_shortcut_id1.tga 0.515625000000 0.648437500000 0.539062500000 0.671875000000 +W_slot_shortcut_id2.tga 0.539062500000 0.648437500000 0.562500000000 0.671875000000 +W_slot_shortcut_id3.tga 0.562500000000 0.648437500000 0.585937500000 0.671875000000 +W_slot_shortcut_id4.tga 0.585937500000 0.648437500000 0.609375000000 0.671875000000 +W_slot_shortcut_id5.tga 0.609375000000 0.648437500000 0.632812500000 0.671875000000 +W_slot_shortcut_id6.tga 0.632812500000 0.648437500000 0.656250000000 0.671875000000 +W_slot_shortcut_id7.tga 0.656250000000 0.648437500000 0.679687500000 0.671875000000 +W_slot_shortcut_id8.tga 0.679687500000 0.648437500000 0.703125000000 0.671875000000 +W_slot_shortcut_id9.tga 0.703125000000 0.648437500000 0.726562500000 0.671875000000 +w_slot_shortcut_shift_id0.tga 0.914062500000 0.656250000000 0.937500000000 0.679687500000 +w_slot_shortcut_shift_id1.tga 0.726562500000 0.664062500000 0.750000000000 0.687500000000 +w_slot_shortcut_shift_id2.tga 0.750000000000 0.664062500000 0.773437500000 0.687500000000 +w_slot_shortcut_shift_id3.tga 0.773437500000 0.664062500000 0.796875000000 0.687500000000 +w_slot_shortcut_shift_id4.tga 0.796875000000 0.664062500000 0.820312500000 0.687500000000 +w_slot_shortcut_shift_id5.tga 0.820312500000 0.664062500000 0.843750000000 0.687500000000 +w_slot_shortcut_shift_id6.tga 0.843750000000 0.664062500000 0.867187500000 0.687500000000 +w_slot_shortcut_shift_id7.tga 0.867187500000 0.664062500000 0.890625000000 0.687500000000 +w_slot_shortcut_shift_id8.tga 0.890625000000 0.664062500000 0.914062500000 0.687500000000 +w_slot_shortcut_shift_id9.tga 0.937500000000 0.664062500000 0.960937500000 0.687500000000 +BK_matis_brick.tga 0.960937500000 0.664062500000 0.984375000000 0.687500000000 +ico_jewel_stone_support.tga 0.000000000000 0.671875000000 0.023437500000 0.695312500000 +ico_misfortune.tga 0.023437500000 0.671875000000 0.046875000000 0.695312500000 +pa_over_break.tga 0.046875000000 0.671875000000 0.070312500000 0.695312500000 +pa_over_less.tga 0.070312500000 0.671875000000 0.093750000000 0.695312500000 +pa_over_more.tga 0.093750000000 0.671875000000 0.117187500000 0.695312500000 +ch_back.tga 0.117187500000 0.671875000000 0.140625000000 0.695312500000 +ico_over_hit_head_x3.tga 0.140625000000 0.671875000000 0.164062500000 0.695312500000 +ico_primal.tga 0.164062500000 0.671875000000 0.187500000000 0.695312500000 +pvp_ally_0.tga 0.187500000000 0.671875000000 0.210937500000 0.695312500000 +pvp_ally_1.tga 0.210937500000 0.671875000000 0.234375000000 0.695312500000 +pvp_ally_2.tga 0.234375000000 0.671875000000 0.257812500000 0.695312500000 +pvp_ally_3.tga 0.257812500000 0.671875000000 0.281250000000 0.695312500000 +pvp_ally_4.tga 0.281250000000 0.671875000000 0.304687500000 0.695312500000 +pvp_ally_6.tga 0.304687500000 0.671875000000 0.328125000000 0.695312500000 +pvp_ally_flag.tga 0.328125000000 0.671875000000 0.351562500000 0.695312500000 +pvp_ally_primas.tga 0.351562500000 0.671875000000 0.375000000000 0.695312500000 +pvp_ally_ranger.tga 0.375000000000 0.671875000000 0.398437500000 0.695312500000 +pvp_ally_tag.tga 0.398437500000 0.671875000000 0.421875000000 0.695312500000 +bg_downloader.tga 0.421875000000 0.671875000000 0.445312500000 0.695312500000 +ico_source_knowledge.tga 0.445312500000 0.671875000000 0.466796875000 0.695312500000 +filter_tp.tga 0.468750000000 0.671875000000 0.492187500000 0.687500000000 small_task_done.tga 0.984375000000 0.000000000000 1.000000000000 0.015625000000 small_task_failed.tga 0.984375000000 0.015625000000 1.000000000000 0.031250000000 small_task_fight.tga 0.984375000000 0.031250000000 1.000000000000 0.046875000000 @@ -669,6 +679,6 @@ small_task_travel.tga 0.914062500000 0.523437500000 0.929687500000 0.53906250000 small_task_craft.tga 0.984375000000 0.546875000000 1.000000000000 0.562500000000 num_slash.tga 0.984375000000 0.562500000000 0.996093750000 0.576171875000 W_leader.tga 0.984375000000 0.578125000000 0.997070312500 0.589843750000 -tb_mode.tga 0.984375000000 0.589843750000 0.996093750000 0.601562500000 -profile.tga 0.984375000000 0.601562500000 0.996093750000 0.613281250000 -w_major.tga 0.984375000000 0.613281250000 0.996093750000 0.625000000000 +w_major.tga 0.984375000000 0.589843750000 0.996093750000 0.601562500000 +tb_mode.tga 0.984375000000 0.601562500000 0.996093750000 0.613281250000 +profile.tga 0.984375000000 0.613281250000 0.996093750000 0.625000000000 diff --git a/code/ryzom/client/data/gamedev/adds/sfx/marauder_teleporter.ps b/code/ryzom/client/data/gamedev/adds/sfx/marauder_teleporter.ps new file mode 100644 index 000000000..cf6a508a9 Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/sfx/marauder_teleporter.ps differ diff --git a/code/ryzom/client/data/gamedev/adds/shapes/GE_HOF_caster_pvp_pantabottes.shape b/code/ryzom/client/data/gamedev/adds/shapes/GE_HOF_caster_pvp_pantabottes.shape new file mode 100644 index 000000000..713018df8 Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/shapes/GE_HOF_caster_pvp_pantabottes.shape differ diff --git a/code/ryzom/client/data/gamedev/adds/shapes/GE_HOM_caster_pvp_pantabottes.shape b/code/ryzom/client/data/gamedev/adds/shapes/GE_HOM_caster_pvp_pantabottes.shape new file mode 100644 index 000000000..b3fb8f6ca Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/shapes/GE_HOM_caster_pvp_pantabottes.shape differ diff --git a/code/ryzom/client/data/gamedev/adds/shapes/GE_pvp_big_shield.shape b/code/ryzom/client/data/gamedev/adds/shapes/GE_pvp_big_shield.shape new file mode 100644 index 000000000..c72f5304e Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/shapes/GE_pvp_big_shield.shape differ diff --git a/code/ryzom/client/data/gamedev/adds/shapes/tp_diamand.shape b/code/ryzom/client/data/gamedev/adds/shapes/tp_diamand.shape new file mode 100644 index 000000000..147ff3abb Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/shapes/tp_diamand.shape differ diff --git a/code/ryzom/client/data/gamedev/adds/shapes/tp_socle.shape b/code/ryzom/client/data/gamedev/adds/shapes/tp_socle.shape new file mode 100644 index 000000000..4a3eea639 Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/shapes/tp_socle.shape differ diff --git a/code/ryzom/client/data/gamedev/adds/textures/event_refday_yber.tga b/code/ryzom/client/data/gamedev/adds/textures/event_refday_yber.tga new file mode 100644 index 000000000..b8e05a831 Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/textures/event_refday_yber.tga differ diff --git a/code/ryzom/client/data/gamedev/adds/textures/gn_pvp_dress.tga b/code/ryzom/client/data/gamedev/adds/textures/gn_pvp_dress.tga new file mode 100644 index 000000000..026711a84 Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/textures/gn_pvp_dress.tga differ diff --git a/code/ryzom/client/data/gamedev/adds/textures/gn_pvp_dress_hof.tga b/code/ryzom/client/data/gamedev/adds/textures/gn_pvp_dress_hof.tga new file mode 100644 index 000000000..83f18a9b3 Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/textures/gn_pvp_dress_hof.tga differ diff --git a/code/ryzom/client/data/gamedev/adds/textures/ul_mission_hall_of_fame.dds b/code/ryzom/client/data/gamedev/adds/textures/ul_mission_hall_of_fame.dds new file mode 100644 index 000000000..03f9e0442 Binary files /dev/null and b/code/ryzom/client/data/gamedev/adds/textures/ul_mission_hall_of_fame.dds differ diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml b/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml index ca013f242..f0455028a 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml @@ -78,9 +78,18 @@ - + + + + + + + + + + diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/config.xml b/code/ryzom/client/data/gamedev/interfaces_v3/config.xml index 1d593ef06..9fb0dd958 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/config.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/config.xml @@ -258,6 +258,9 @@ + @@ -291,6 +294,9 @@ + @@ -327,6 +333,9 @@ + @@ -2797,6 +2806,9 @@ This MUST follow the Enum MISSION_DESC::TIconId + - + + + + + + + + @@ -3045,6 +3081,9 @@ This MUST follow the Enum MISSION_DESC::TIconId + @@ -3066,6 +3105,9 @@ This MUST follow the Enum MISSION_DESC::TIconId + @@ -3087,6 +3129,9 @@ This MUST follow the Enum MISSION_DESC::TIconId + @@ -3108,6 +3153,9 @@ This MUST follow the Enum MISSION_DESC::TIconId + @@ -3129,6 +3177,9 @@ This MUST follow the Enum MISSION_DESC::TIconId + @@ -3151,6 +3202,9 @@ This MUST follow the Enum MISSION_DESC::TIconId + @@ -3173,6 +3227,9 @@ This MUST follow the Enum MISSION_DESC::TIconId + + + diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/encyclopedia.xml b/code/ryzom/client/data/gamedev/interfaces_v3/encyclopedia.xml index b2db7ab26..30f2c34a0 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/encyclopedia.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/encyclopedia.xml @@ -11,7 +11,7 @@ - + + +

+ + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/js/default.ctp b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/js/default.ctp new file mode 100644 index 000000000..d94dc903a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/js/default.ctp @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/rss/default.ctp b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/rss/default.ctp new file mode 100644 index 000000000..94067f2bf --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/rss/default.ctp @@ -0,0 +1,17 @@ +header(); + +if (!isset($channel)) { + $channel = array(); +} +if (!isset($channel['title'])) { + $channel['title'] = $title_for_layout; +} + +echo $rss->document( + $rss->channel( + array(), $channel, $content_for_layout + ) +); + +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/xml/default.ctp b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/xml/default.ctp new file mode 100644 index 000000000..566ca2158 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/layouts/xml/default.ctp @@ -0,0 +1,2 @@ +header(); ?> + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/pages/empty b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/pages/empty new file mode 100644 index 000000000..e69de29bb diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/scaffolds/empty b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/views/scaffolds/empty new file mode 100644 index 000000000..e69de29bb diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/.htaccess b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/.htaccess new file mode 100644 index 000000000..f9d8b938b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/.htaccess @@ -0,0 +1,6 @@ + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/css.php b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/css.php new file mode 100644 index 000000000..1f04005de --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/css.php @@ -0,0 +1,97 @@ +compress($data); + $ratio = 100 - (round(strlen($output) / strlen($data), 3) * 100); + $output = " /* file: $name, ratio: $ratio% */ " . $output; + return $output; + } + +/** + * Write CSS cache + * + * @param unknown_type $path + * @param unknown_type $content + * @return unknown + */ + function write_css_cache($path, $content) { + if (!is_dir(dirname($path))) { + mkdir(dirname($path)); + } + $cache = new File($path); + return $cache->write($content); + } + + if (preg_match('|\.\.|', $url) || !preg_match('|^ccss/(.+)$|i', $url, $regs)) { + exit('Wrong file name.'); + } + + $filename = 'css/' . $regs[1]; + $filepath = CSS . $regs[1]; + $cachepath = CACHE . 'css' . DS . str_replace(array('/','\\'), '-', $regs[1]); + + if (!file_exists($filepath)) { + exit('Wrong file name.'); + } + + if (file_exists($cachepath)) { + $templateModified = filemtime($filepath); + $cacheModified = filemtime($cachepath); + + if ($templateModified > $cacheModified) { + $output = make_clean_css($filepath, $filename); + write_css_cache($cachepath, $output); + } else { + $output = file_get_contents($cachepath); + } + } else { + $output = make_clean_css($filepath, $filename); + write_css_cache($cachepath, $output); + $templateModified = time(); + } + + header("Date: " . date("D, j M Y G:i:s ", $templateModified) . 'GMT'); + header("Content-Type: text/css"); + header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT"); + header("Cache-Control: max-age=86400, must-revalidate"); // HTTP/1.1 + header("Pragma: cache"); // HTTP/1.0 + print $output; diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/css/cake.generic.css b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/css/cake.generic.css new file mode 100644 index 000000000..7f978afc0 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/css/cake.generic.css @@ -0,0 +1,535 @@ +/** + * + * Generic CSS for CakePHP + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.app.webroot.css + * @since CakePHP(tm) + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +* { + margin:0; + padding:0; +} + +/** General Style Info **/ +body { + background: #003d4c; + color: #fff; + font-family:'lucida grande',verdana,helvetica,arial,sans-serif; + font-size:90%; + margin: 0; +} +a { + color: #003d4c; + text-decoration: underline; + font-weight: bold; +} +a:hover { + color: #367889; + text-decoration:none; +} +a img { + border:none; +} +h1, h2, h3, h4 { + font-weight: normal; + margin-bottom:0.5em; +} +h1 { + background:#fff; + color: #003d4c; + font-size: 100%; +} +h2 { + background:#fff; + color: #e32; + font-family:'Gill Sans','lucida grande', helvetica, arial, sans-serif; + font-size: 190%; +} +h3 { + color: #993; + font-family:'Gill Sans','lucida grande', helvetica, arial, sans-serif; + font-size: 165%; +} +h4 { + color: #993; + font-weight: normal; +} +ul, li { + margin: 0 12px; +} + +/** Layout **/ +#container { + text-align: left; +} + +#header{ + padding: 10px 20px; +} +#header h1 { + line-height:20px; + background: #003d4c url('../img/cake.icon.png') no-repeat left; + color: #fff; + padding: 0px 30px; +} +#header h1 a { + color: #fff; + background: #003d4c; + font-weight: normal; + text-decoration: none; +} +#header h1 a:hover { + color: #fff; + background: #003d4c; + text-decoration: underline; +} +#content{ + background: #fff; + clear: both; + color: #333; + padding: 10px 20px 40px 20px; + overflow: auto; +} +#footer { + clear: both; + padding: 6px 10px; + text-align: right; +} + +/** containers **/ +div.form, +div.index, +div.view { + float:right; + width:76%; + border-left:1px solid #666; + padding:10px 2%; +} +div.actions { + float:left; + width:16%; + padding:10px 1.5%; +} +div.actions h3 { + padding-top:0; + color:#777; +} + + +/** Tables **/ +table { + background: #fff; + border-right:0; + clear: both; + color: #333; + margin-bottom: 10px; + width: 100%; +} +th { + border:0; + border-bottom:2px solid #555; + text-align: left; + padding:4px; +} +th a { + display: block; + padding: 2px 4px; + text-decoration: none; +} +th a.asc:after { + content: ' ⇣'; +} +th a.desc:after { + content: ' ⇡'; +} +table tr td { + background: #fff; + padding: 6px; + text-align: left; + vertical-align: top; + border-bottom:1px solid #ddd; +} +table tr:nth-child(2n) td { + background: #f5f5f5; +} +table .altrow td { + background: #f5f5f5; +} +td.actions { + text-align: center; + white-space: nowrap; +} +table td.actions a { + margin: 0px 6px; + padding:2px 5px; +} +.cake-sql-log table { + background: #f4f4f4; +} +.cake-sql-log td { + padding: 4px 8px; + text-align: left; + font-family: Monaco, Consolas, "Courier New", monospaced; +} +.cake-sql-log caption { + color:#fff; +} + +/** Paging **/ +div.paging { + background:#fff; + color: #ccc; + margin-top: 1em; + clear:both; +} +div.paging span.disabled { + color: #ddd; + display: inline; +} +div.paging span.current { + color: #c73e14; +} +div.paging span a { +} + +/** Scaffold View **/ +dl { + line-height: 2em; + margin: 0em 0em; + width: 60%; +} +dl .altrow { + background: #f4f4f4; +} +dt { + font-weight: bold; + padding-left: 4px; + vertical-align: top; +} +dd { + margin-left: 10em; + margin-top: -2em; + vertical-align: top; +} + +/** Forms **/ +form { + clear: both; + margin-right: 20px; + padding: 0; + width: 95%; +} +fieldset { + border: 1px solid #ccc; + margin-bottom: 1em; + padding: 16px 20px; +} +fieldset legend { + background:#fff; + color: #e32; + font-size: 160%; + font-weight: bold; +} +fieldset fieldset { + margin-top: 0px; + margin-bottom: 20px; + padding: 16px 10px; +} +fieldset fieldset legend { + font-size: 120%; + font-weight: normal; +} +fieldset fieldset div { + clear: left; + margin: 0 20px; +} +form div { + clear: both; + margin-bottom: 1em; + padding: .5em; + vertical-align: text-top; +} +form .input { + color: #444; +} +form .required { + font-weight: bold; +} +form .required label:after { + color: #e32; + content: '*'; + display:inline; +} +form div.submit { + border: 0; + clear: both; + margin-top: 10px; +} +label { + display: block; + font-size: 110%; + margin-bottom:3px; +} +input, textarea { + clear: both; + font-size: 140%; + font-family: "frutiger linotype", "lucida grande", "verdana", sans-serif; + padding: 1%; + width:98%; +} +select { + clear: both; + font-size: 120%; + vertical-align: text-bottom; +} +select[multiple=multiple] { + width: 100%; +} +option { + font-size: 120%; + padding: 0 3px; +} +input[type=checkbox] { + clear: left; + float: left; + margin: 0px 6px 7px 2px; + width: auto; +} +div.checkbox label { + display: inline; +} +input[type=radio] { + float:left; + width:auto; + margin: 0 3px 7px 0; +} +div.radio label { + margin: 0 0 6px 20px; +} +input[type=submit] { + display: inline; + font-size: 110%; + width: auto; +} +form .submit input[type=submit] { + background:#62af56; + background: -webkit-gradient(linear, left top, left bottom, from(#a8ea9c), to(#62af56)); + background-image: -moz-linear-gradient(top, #a8ea9c, #62af56); + border-color: #2d6324; + color: #000; + text-shadow: #8cee7c 0px 1px 0px; +} +form .submit input[type=submit]:hover { + background:#4ca83d; + background: -webkit-gradient(linear, left top, left bottom, from(#85e573), to(#4ca83d)); + background-image: -moz-linear-gradient(top, #85e573, #4ca83d); +} + +/** Notices and Errors **/ +div.message { + clear: both; + color: #fff; + font-size: 140%; + font-weight: bold; + margin: 0 0 1em 0; + background: #c73e14; + padding: 5px; +} +div.error-message { + clear: both; + color: #fff; + font-weight: bold; + background: #c73e14; +} +p.error { + background-color: #e32; + color: #fff; + font-family: Courier, monospace; + font-size: 120%; + line-height: 140%; + padding: 0.8em; + margin: 1em 0; +} +p.error em { + color: #000; + font-weight: normal; + line-height: 140%; +} +.notice { + background: #ffcc00; + color: #000; + display: block; + font-family: Courier, monospace; + font-size: 120%; + line-height: 140%; + padding: 0.8em; + margin: 1em 0; +} +.success { + background: green; + color: #fff; +} + +/** Actions **/ +div.actions ul { + margin: 0; + padding: 0; +} +div.actions li { + margin:0 0 0.5em 0; + list-style-type: none; + white-space: nowrap; + padding: 0; +} +div.actions ul li a { + font-weight: normal; + display: block; + clear: both; +} +div.actions ul li a:hover { + text-decoration: underline; +} + +input[type=submit], +div.actions ul li a, +td.actions a { + font-weight:normal; + padding: 4px 8px; + background:#e6e49f; + background: -webkit-gradient(linear, left top, left bottom, from(#f1f1d4), to(#e6e49f)); + background-image: -moz-linear-gradient(top, #f1f1d4, #e6e49f); + color:#333; + border:1px solid #aaac62; + -webkit-border-radius:8px; + -moz-border-radius:8px; + border-radius:8px; + text-decoration:none; + text-shadow: #fff 0px 1px 0px; + min-width: 0; +} +input[type=submit]:hover, +div.actions ul li a:hover, +td.actions a:hover { + background: #f0f09a; + background: -webkit-gradient(linear, left top, left bottom, from(#f7f7e1), to(#eeeca9)); +} + +/** Related **/ +div.related { + clear: both; + display: block; +} + +/** Debugging **/ +pre { + color: #000; + background: #f0f0f0; + padding: 1em; +} +pre.cake-debug { + background: #ffcc00; + font-size: 120%; + line-height: 140%; + margin-top: 1em; + overflow: auto; + position: relative; +} +div.cake-stack-trace { + background: #fff; + color: #333; + margin: 0px; + padding: 6px; + font-size: 120%; + line-height: 140%; + overflow: auto; + position: relative; +} +div.cake-code-dump pre { + position: relative; + overflow: auto; +} +div.cake-stack-trace pre, div.cake-code-dump pre { + color: #000; + background-color: #F0F0F0; + margin: 0px; + padding: 1em; + overflow: auto; +} +div.cake-code-dump pre, div.cake-code-dump pre code { + clear: both; + font-size: 12px; + line-height: 15px; + margin: 4px 2px; + padding: 4px; + overflow: auto; +} +div.cake-code-dump span.code-highlight { + background-color: #ff0; + padding: 4px; +} +div.code-coverage-results div.code-line { + padding-left:5px; + display:block; + margin-left:10px; +} +div.code-coverage-results div.uncovered span.content { + background:#ecc; +} +div.code-coverage-results div.covered span.content { + background:#cec; +} +div.code-coverage-results div.ignored span.content { + color:#aaa; +} +div.code-coverage-results span.line-num { + color:#666; + display:block; + float:left; + width:20px; + text-align:right; + margin-right:5px; +} +div.code-coverage-results span.line-num strong { + color:#666; +} +div.code-coverage-results div.start { + border:1px solid #aaa; + border-width:1px 1px 0px 1px; + margin-top:30px; + padding-top:5px; +} +div.code-coverage-results div.end { + border:1px solid #aaa; + border-width:0px 1px 1px 1px; + margin-bottom:30px; + padding-bottom:5px; +} +div.code-coverage-results div.realstart { + margin-top:0px; +} +div.code-coverage-results p.note { + color:#bbb; + padding:5px; + margin:5px 0 10px; + font-size:10px; +} +div.code-coverage-results span.result-bad { + color: #a00; +} +div.code-coverage-results span.result-ok { + color: #fa0; +} +div.code-coverage-results span.result-good { + color: #0a0; +} diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/favicon.ico b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/favicon.ico new file mode 100644 index 000000000..b36e81f2f Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/favicon.ico differ diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/img/cake.icon.png b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/img/cake.icon.png new file mode 100644 index 000000000..394fa42d5 Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/img/cake.icon.png differ diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/img/cake.power.gif b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/img/cake.power.gif new file mode 100644 index 000000000..8f8d570a2 Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/img/cake.power.gif differ diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/index.php b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/index.php new file mode 100644 index 000000000..396775b73 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/index.php @@ -0,0 +1,84 @@ +dispatch(); + } diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/js/empty b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/js/empty new file mode 100644 index 000000000..e69de29bb diff --git a/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/test.php b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/test.php new file mode 100644 index 000000000..cfbf85145 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/console/templates/skel/webroot/test.php @@ -0,0 +1,94 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.cake.tests.libs + * @since CakePHP(tm) v 1.2.0.4433 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +set_time_limit(0); +ini_set('display_errors', 1); +/** + * Use the DS to separate the directories in other defines + */ + if (!defined('DS')) { + define('DS', DIRECTORY_SEPARATOR); + } +/** + * These defines should only be edited if you have cake installed in + * a directory layout other than the way it is distributed. + * When using custom settings be sure to use the DS and do not add a trailing DS. + */ + +/** + * The full path to the directory which holds "app", WITHOUT a trailing DS. + * + */ + if (!defined('ROOT')) { + define('ROOT', dirname(dirname(dirname(__FILE__)))); + } +/** + * The actual directory name for the "app". + * + */ + if (!defined('APP_DIR')) { + define('APP_DIR', basename(dirname(dirname(__FILE__)))); + } +/** + * The absolute path to the "cake" directory, WITHOUT a trailing DS. + * + */ + if (!defined('CAKE_CORE_INCLUDE_PATH')) { + define('CAKE_CORE_INCLUDE_PATH', ROOT); + } + +/** + * Editing below this line should not be necessary. + * Change at your own risk. + * + */ +if (!defined('WEBROOT_DIR')) { + define('WEBROOT_DIR', basename(dirname(__FILE__))); +} +if (!defined('WWW_ROOT')) { + define('WWW_ROOT', dirname(__FILE__) . DS); +} +if (!defined('CORE_PATH')) { + if (function_exists('ini_set') && ini_set('include_path', CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ROOT . DS . APP_DIR . DS . PATH_SEPARATOR . ini_get('include_path'))) { + define('APP_PATH', null); + define('CORE_PATH', null); + } else { + define('APP_PATH', ROOT . DS . APP_DIR . DS); + define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS); + } +} +if (!include(CORE_PATH . 'cake' . DS . 'bootstrap.php')) { + trigger_error("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.", E_USER_ERROR); +} + +$corePath = App::core('cake'); +if (isset($corePath[0])) { + define('TEST_CAKE_CORE_INCLUDE_PATH', rtrim($corePath[0], DS) . DS); +} else { + define('TEST_CAKE_CORE_INCLUDE_PATH', CAKE_CORE_INCLUDE_PATH); +} + +if (Configure::read('debug') < 1) { + die(__('Debug setting does not allow access to this url.', true)); +} + +require_once CAKE_TESTS_LIB . 'cake_test_suite_dispatcher.php'; + +$Dispatcher = new CakeTestSuiteDispatcher(); +$Dispatcher->dispatch(); diff --git a/code/ryzom/tools/server/www/webtt/cake/dispatcher.php b/code/ryzom/tools/server/www/webtt/cake/dispatcher.php new file mode 100644 index 000000000..d1e79c4d3 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/dispatcher.php @@ -0,0 +1,663 @@ +dispatch($url); + } + } + +/** + * Dispatches and invokes given URL, handing over control to the involved controllers, and then renders the + * results (if autoRender is set). + * + * If no controller of given name can be found, invoke() shows error messages in + * the form of Missing Controllers information. It does the same with Actions (methods of Controllers are called + * Actions). + * + * @param string $url URL information to work on + * @param array $additionalParams Settings array ("bare", "return") which is melded with the GET and POST params + * @return boolean Success + * @access public + */ + function dispatch($url = null, $additionalParams = array()) { + if ($this->base === false) { + $this->base = $this->baseUrl(); + } + + if (is_array($url)) { + $url = $this->__extractParams($url, $additionalParams); + } else { + if ($url) { + $_GET['url'] = $url; + } + $url = $this->getUrl(); + $this->params = array_merge($this->parseParams($url), $additionalParams); + } + $this->here = $this->base . '/' . $url; + + if ($this->asset($url) || $this->cached($url)) { + return; + } + $controller =& $this->__getController(); + + if (!is_object($controller)) { + Router::setRequestInfo(array($this->params, array('base' => $this->base, 'webroot' => $this->webroot))); + return $this->cakeError('missingController', array(array( + 'className' => Inflector::camelize($this->params['controller']) . 'Controller', + 'webroot' => $this->webroot, + 'url' => $url, + 'base' => $this->base + ))); + } + $privateAction = $this->params['action'][0] === '_'; + $prefixes = Router::prefixes(); + + if (!empty($prefixes)) { + if (isset($this->params['prefix'])) { + $this->params['action'] = $this->params['prefix'] . '_' . $this->params['action']; + } elseif (strpos($this->params['action'], '_') > 0) { + list($prefix, $action) = explode('_', $this->params['action']); + $privateAction = in_array($prefix, $prefixes); + } + } + + Router::setRequestInfo(array( + $this->params, array('base' => $this->base, 'here' => $this->here, 'webroot' => $this->webroot) + )); + + if ($privateAction) { + return $this->cakeError('privateAction', array(array( + 'className' => Inflector::camelize($this->params['controller'] . "Controller"), + 'action' => $this->params['action'], + 'webroot' => $this->webroot, + 'url' => $url, + 'base' => $this->base + ))); + } + $controller->base = $this->base; + $controller->here = $this->here; + $controller->webroot = $this->webroot; + $controller->plugin = isset($this->params['plugin']) ? $this->params['plugin'] : null; + $controller->params =& $this->params; + $controller->action =& $this->params['action']; + $controller->passedArgs = array_merge($this->params['pass'], $this->params['named']); + + if (!empty($this->params['data'])) { + $controller->data =& $this->params['data']; + } else { + $controller->data = null; + } + if (isset($this->params['return']) && $this->params['return'] == 1) { + $controller->autoRender = false; + } + if (!empty($this->params['bare'])) { + $controller->autoLayout = false; + } + return $this->_invoke($controller, $this->params); + } + +/** + * Initializes the components and models a controller will be using. + * Triggers the controller action, and invokes the rendering if Controller::$autoRender is true and echo's the output. + * Otherwise the return value of the controller action are returned. + * + * @param object $controller Controller to invoke + * @param array $params Parameters with at least the 'action' to invoke + * @param boolean $missingAction Set to true if missing action should be rendered, false otherwise + * @return string Output as sent by controller + * @access protected + */ + function _invoke(&$controller, $params) { + $controller->constructClasses(); + $controller->startupProcess(); + + $methods = array_flip($controller->methods); + + if (!isset($methods[strtolower($params['action'])])) { + if ($controller->scaffold !== false) { + App::import('Controller', 'Scaffold', false); + return new Scaffold($controller, $params); + } + return $this->cakeError('missingAction', array(array( + 'className' => Inflector::camelize($params['controller']."Controller"), + 'action' => $params['action'], + 'webroot' => $this->webroot, + 'url' => $this->here, + 'base' => $this->base + ))); + } + $output = call_user_func_array(array(&$controller, $params['action']), $params['pass']); + + if ($controller->autoRender) { + $controller->output = $controller->render(); + } elseif (empty($controller->output)) { + $controller->output = $output; + } + $controller->shutdownProcess(); + + if (isset($params['return'])) { + return $controller->output; + } + echo($controller->output); + } + +/** + * Sets the params when $url is passed as an array to Object::requestAction(); + * Merges the $url and $additionalParams and creates a string url. + * + * @param array $url Array or request parameters + * @param array $additionalParams Array of additional parameters. + * @return string $url The generated url string. + * @access private + */ + function __extractParams($url, $additionalParams = array()) { + $defaults = array('pass' => array(), 'named' => array(), 'form' => array()); + $params = array_merge($defaults, $url, $additionalParams); + $this->params = $params; + + $params += array('base' => false, 'url' => array()); + return ltrim(Router::reverse($params), '/'); + } + +/** + * Returns array of GET and POST parameters. GET parameters are taken from given URL. + * + * @param string $fromUrl URL to mine for parameter information. + * @return array Parameters found in POST and GET. + * @access public + */ + function parseParams($fromUrl) { + $params = array(); + + if (isset($_POST)) { + $params['form'] = $_POST; + if (ini_get('magic_quotes_gpc') === '1') { + $params['form'] = stripslashes_deep($params['form']); + } + if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $params['form']['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE'); + } + if (isset($params['form']['_method'])) { + if (!empty($_SERVER)) { + $_SERVER['REQUEST_METHOD'] = $params['form']['_method']; + } else { + $_ENV['REQUEST_METHOD'] = $params['form']['_method']; + } + unset($params['form']['_method']); + } + } + $namedExpressions = Router::getNamedExpressions(); + extract($namedExpressions); + include CONFIGS . 'routes.php'; + $params = array_merge(array('controller' => '', 'action' => ''), Router::parse($fromUrl), $params); + + if (empty($params['action'])) { + $params['action'] = 'index'; + } + if (isset($params['form']['data'])) { + $params['data'] = $params['form']['data']; + unset($params['form']['data']); + } + if (isset($_GET)) { + if (ini_get('magic_quotes_gpc') === '1') { + $url = stripslashes_deep($_GET); + } else { + $url = $_GET; + } + if (isset($params['url'])) { + $params['url'] = array_merge($params['url'], $url); + } else { + $params['url'] = $url; + } + } + + foreach ($_FILES as $name => $data) { + if ($name != 'data') { + $params['form'][$name] = $data; + } + } + + if (isset($_FILES['data'])) { + foreach ($_FILES['data'] as $key => $data) { + foreach ($data as $model => $fields) { + if (is_array($fields)) { + foreach ($fields as $field => $value) { + if (is_array($value)) { + foreach ($value as $k => $v) { + $params['data'][$model][$field][$k][$key] = $v; + } + } else { + $params['data'][$model][$field][$key] = $value; + } + } + } else { + $params['data'][$model][$key] = $fields; + } + } + } + } + return $params; + } + +/** + * Returns a base URL and sets the proper webroot + * + * @return string Base URL + * @access public + */ + function baseUrl() { + $dir = $webroot = null; + $config = Configure::read('App'); + extract($config); + + if (!$base) { + $base = $this->base; + } + if ($base !== false) { + $this->webroot = $base . '/'; + return $this->base = $base; + } + if (!$baseUrl) { + $replace = array('<', '>', '*', '\'', '"'); + $base = str_replace($replace, '', dirname(env('PHP_SELF'))); + + if ($webroot === 'webroot' && $webroot === basename($base)) { + $base = dirname($base); + } + if ($dir === 'app' && $dir === basename($base)) { + $base = dirname($base); + } + + if ($base === DS || $base === '.') { + $base = ''; + } + + $this->webroot = $base .'/'; + return $base; + } + + $file = '/' . basename($baseUrl); + $base = dirname($baseUrl); + + if ($base === DS || $base === '.') { + $base = ''; + } + $this->webroot = $base . '/'; + + $docRoot = realpath(env('DOCUMENT_ROOT')); + $docRootContainsWebroot = strpos($docRoot, $dir . '/' . $webroot); + + if (!empty($base) || !$docRootContainsWebroot) { + if (strpos($this->webroot, $dir) === false) { + $this->webroot .= $dir . '/' ; + } + if (strpos($this->webroot, $webroot) === false) { + $this->webroot .= $webroot . '/'; + } + } + return $base . $file; + } + +/** + * Get controller to use, either plugin controller or application controller + * + * @param array $params Array of parameters + * @return mixed name of controller if not loaded, or object if loaded + * @access private + */ + function &__getController() { + $controller = false; + $ctrlClass = $this->__loadController($this->params); + if (!$ctrlClass) { + return $controller; + } + $ctrlClass .= 'Controller'; + if (class_exists($ctrlClass)) { + $controller =& new $ctrlClass(); + } + return $controller; + } + +/** + * Load controller and return controller classname + * + * @param array $params Array of parameters + * @return string|bool Name of controller class name + * @access private + */ + function __loadController($params) { + $pluginName = $pluginPath = $controller = null; + if (!empty($params['plugin'])) { + $pluginName = $controller = Inflector::camelize($params['plugin']); + $pluginPath = $pluginName . '.'; + } + if (!empty($params['controller'])) { + $controller = Inflector::camelize($params['controller']); + } + if ($pluginPath . $controller) { + if (App::import('Controller', $pluginPath . $controller)) { + return $controller; + } + } + return false; + } + +/** + * Returns the REQUEST_URI from the server environment, or, failing that, + * constructs a new one, using the PHP_SELF constant and other variables. + * + * @return string URI + * @access public + */ + function uri() { + foreach (array('HTTP_X_REWRITE_URL', 'REQUEST_URI', 'argv') as $var) { + if ($uri = env($var)) { + if ($var == 'argv') { + $uri = $uri[0]; + } + break; + } + } + $base = preg_replace('/^\//', '', '' . Configure::read('App.baseUrl')); + + if ($base) { + $uri = preg_replace('/^(?:\/)?(?:' . preg_quote($base, '/') . ')?(?:url=)?/', '', $uri); + } + if (PHP_SAPI == 'isapi') { + $uri = preg_replace('/^(?:\/)?(?:\/)?(?:\?)?(?:url=)?/', '', $uri); + } + if (!empty($uri)) { + if (key($_GET) && strpos(key($_GET), '?') !== false) { + unset($_GET[key($_GET)]); + } + $uri = explode('?', $uri, 2); + + if (isset($uri[1])) { + parse_str($uri[1], $_GET); + } + $uri = $uri[0]; + } else { + $uri = env('QUERY_STRING'); + } + if (is_string($uri) && strpos($uri, 'index.php') !== false) { + list(, $uri) = explode('index.php', $uri, 2); + } + if (empty($uri) || $uri == '/' || $uri == '//') { + return ''; + } + return str_replace('//', '/', '/' . $uri); + } + +/** + * Returns and sets the $_GET[url] derived from the REQUEST_URI + * + * @param string $uri Request URI + * @param string $base Base path + * @return string URL + * @access public + */ + function getUrl($uri = null, $base = null) { + if (empty($_GET['url'])) { + if ($uri == null) { + $uri = $this->uri(); + } + if ($base == null) { + $base = $this->base; + } + $url = null; + $tmpUri = preg_replace('/^(?:\?)?(?:\/)?/', '', $uri); + $baseDir = preg_replace('/^\//', '', dirname($base)) . '/'; + + if ($tmpUri === '/' || $tmpUri == $baseDir || $tmpUri == $base) { + $url = $_GET['url'] = '/'; + } else { + if ($base && strpos($uri, $base) === 0) { + $elements = explode($base, $uri, 2); + } elseif (preg_match('/^[\/\?\/|\/\?|\?\/]/', $uri)) { + $elements = array(1 => preg_replace('/^[\/\?\/|\/\?|\?\/]/', '', $uri)); + } else { + $elements = array(); + } + + if (!empty($elements[1])) { + $_GET['url'] = $elements[1]; + $url = $elements[1]; + } else { + $url = $_GET['url'] = '/'; + } + + if (strpos($url, '/') === 0 && $url != '/') { + $url = $_GET['url'] = substr($url, 1); + } + } + } else { + $url = $_GET['url']; + + } + if ($url{0} == '/') { + $url = substr($url, 1); + } + return $url; + } + +/** + * Outputs cached dispatch view cache + * + * @param string $url Requested URL + * @access public + */ + function cached($url) { + if (Configure::read('Cache.check') === true) { + $path = $this->here; + if ($this->here == '/') { + $path = 'home'; + } + $path = strtolower(Inflector::slug($path)); + + $filename = CACHE . 'views' . DS . $path . '.php'; + + if (!file_exists($filename)) { + $filename = CACHE . 'views' . DS . $path . '_index.php'; + } + + if (file_exists($filename)) { + if (!class_exists('View')) { + App::import('View', 'View', false); + } + $controller = null; + $view =& new View($controller); + $return = $view->renderCache($filename, getMicrotime()); + if (!$return) { + ClassRegistry::removeObject('view'); + } + return $return; + } + } + return false; + } + +/** + * Checks if a requested asset exists and sends it to the browser + * + * @param $url string $url Requested URL + * @return boolean True on success if the asset file was found and sent + * @access public + */ + function asset($url) { + if (strpos($url, '..') !== false || strpos($url, '.') === false) { + return false; + } + $filters = Configure::read('Asset.filter'); + $isCss = ( + strpos($url, 'ccss/') === 0 || + preg_match('#^(theme/([^/]+)/ccss/)|(([^/]+)(?_stop(); + } elseif ($isCss) { + include WWW_ROOT . DS . $filters['css']; + $this->_stop(); + } elseif ($isJs) { + include WWW_ROOT . DS . $filters['js']; + $this->_stop(); + } + $controller = null; + $ext = array_pop(explode('.', $url)); + $parts = explode('/', $url); + $assetFile = null; + + if ($parts[0] === 'theme') { + $themeName = $parts[1]; + unset($parts[0], $parts[1]); + $fileFragment = implode(DS, $parts); + $path = App::themePath($themeName) . 'webroot' . DS; + if (file_exists($path . $fileFragment)) { + $assetFile = $path . $fileFragment; + } + } else { + $plugin = $parts[0]; + unset($parts[0]); + $fileFragment = implode(DS, $parts); + $pluginWebroot = App::pluginPath($plugin) . 'webroot' . DS; + if (file_exists($pluginWebroot . $fileFragment)) { + $assetFile = $pluginWebroot . $fileFragment; + } + } + + if ($assetFile !== null) { + $this->_deliverAsset($assetFile, $ext); + return true; + } + return false; + } + +/** + * Sends an asset file to the client + * + * @param string $assetFile Path to the asset file in the file system + * @param string $ext The extension of the file to determine its mime type + * @return void + * @access protected + */ + function _deliverAsset($assetFile, $ext) { + $ob = @ini_get("zlib.output_compression") !== '1' && extension_loaded("zlib") && (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false); + $compressionEnabled = $ob && Configure::read('Asset.compress'); + if ($compressionEnabled) { + ob_start(); + ob_start('ob_gzhandler'); + } + + App::import('View', 'Media'); + $controller = null; + $Media = new MediaView($controller); + if (isset($Media->mimeType[$ext])) { + $contentType = $Media->mimeType[$ext]; + } else { + $contentType = 'application/octet-stream'; + $agent = env('HTTP_USER_AGENT'); + if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { + $contentType = 'application/octetstream'; + } + } + + header("Date: " . date("D, j M Y G:i:s ", filemtime($assetFile)) . 'GMT'); + header('Content-type: ' . $contentType); + header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT"); + header("Cache-Control: cache"); + header("Pragma: cache"); + + if ($ext === 'css' || $ext === 'js') { + include($assetFile); + } else { + if ($compressionEnabled) { + ob_clean(); + } + readfile($assetFile); + } + + if ($compressionEnabled) { + ob_end_flush(); + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/cache.php b/code/ryzom/tools/server/www/webtt/cake/libs/cache.php new file mode 100644 index 000000000..4616a46d2 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/cache.php @@ -0,0 +1,702 @@ + 'File', 'path' => TMP));` + * + * To get the settings for a configuration, and set it as the currently selected configuration + * + * `Cache::config('default');` + * + * The following keys are used in core cache engines: + * + * - `duration` Specify how long items in this cache configuration last. + * - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace + * with either another cache config or annother application. + * - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable + * cache::gc from ever being called automatically. + * - `servers' Used by memcache. Give the address of the memcached servers to use. + * - `compress` Used by memcache. Enables memcache's compressed format. + * - `serialize` Used by FileCache. Should cache objects be serialized first. + * - `path` Used by FileCache. Path to where cachefiles should be saved. + * - `lock` Used by FileCache. Should files be locked before writing to them? + * - `user` Used by Xcache. Username for XCache + * - `password` Used by Xcache. Password for XCache + * + * @see app/config/core.php for configuration settings + * @param string $name Name of the configuration + * @param array $settings Optional associative array of settings passed to the engine + * @return array(engine, settings) on success, false on failure + * @access public + * @static + */ + function config($name = null, $settings = array()) { + $self =& Cache::getInstance(); + if (is_array($name)) { + $settings = $name; + } + + if ($name === null || !is_string($name)) { + $name = $self->__name; + } + + $current = array(); + if (isset($self->__config[$name])) { + $current = $self->__config[$name]; + } + + if (!empty($settings)) { + $self->__config[$name] = array_merge($current, $settings); + } + + if (empty($self->__config[$name]['engine'])) { + return false; + } + + $engine = $self->__config[$name]['engine']; + $self->__name = $name; + + if (!isset($self->_engines[$name])) { + $self->_buildEngine($name); + $settings = $self->__config[$name] = $self->settings($name); + } elseif ($settings = $self->set($self->__config[$name])) { + $self->__config[$name] = $settings; + } + return compact('engine', 'settings'); + } + +/** + * Finds and builds the instance of the required engine class. + * + * @param string $name Name of the config array that needs an engine instance built + * @return void + * @access protected + */ + function _buildEngine($name) { + $config = $this->__config[$name]; + + list($plugin, $class) = pluginSplit($config['engine']); + $cacheClass = $class . 'Engine'; + if (!class_exists($cacheClass) && $this->__loadEngine($class, $plugin) === false) { + return false; + } + $cacheClass = $class . 'Engine'; + $this->_engines[$name] =& new $cacheClass(); + if ($this->_engines[$name]->init($config)) { + if ($this->_engines[$name]->settings['probability'] && time() % $this->_engines[$name]->settings['probability'] === 0) { + $this->_engines[$name]->gc(); + } + return true; + } + return false; + } + +/** + * Returns an array containing the currently configured Cache settings. + * + * @return array Array of configured Cache config names. + */ + function configured() { + $self =& Cache::getInstance(); + return array_keys($self->__config); + } + +/** + * Drops a cache engine. Deletes the cache configuration information + * If the deleted configuration is the last configuration using an certain engine, + * the Engine instance is also unset. + * + * @param string $name A currently configured cache config you wish to remove. + * @return boolen success of the removal, returns false when the config does not exist. + */ + function drop($name) { + $self =& Cache::getInstance(); + if (!isset($self->__config[$name])) { + return false; + } + unset($self->__config[$name]); + unset($self->_engines[$name]); + return true; + } + +/** + * Tries to find and include a file for a cache engine and returns object instance + * + * @param $name Name of the engine (without 'Engine') + * @return mixed $engine object or null + * @access private + */ + function __loadEngine($name, $plugin = null) { + if ($plugin) { + return App::import('Lib', $plugin . '.cache' . DS . $name, false); + } else { + $core = App::core(); + $path = $core['libs'][0] . 'cache' . DS . strtolower($name) . '.php'; + if (file_exists($path)) { + require $path; + return true; + } + return App::import('Lib', 'cache' . DS . $name, false); + } + } + +/** + * Temporarily change settings to current config options. if no params are passed, resets settings if needed + * Cache::write() will reset the configuration changes made + * + * @param mixed $settings Optional string for simple name-value pair or array + * @param string $value Optional for a simple name-value pair + * @return array Array of settings. + * @access public + * @static + */ + function set($settings = array(), $value = null) { + $self =& Cache::getInstance(); + if (!isset($self->__config[$self->__name]) || !isset($self->_engines[$self->__name])) { + return false; + } + $name = $self->__name; + if (!empty($settings)) { + $self->__reset = true; + } + + if ($self->__reset === true) { + if (empty($settings)) { + $self->__reset = false; + $settings = $self->__config[$name]; + } else { + if (is_string($settings) && $value !== null) { + $settings = array($settings => $value); + } + $settings = array_merge($self->__config[$name], $settings); + if (isset($settings['duration']) && !is_numeric($settings['duration'])) { + $settings['duration'] = strtotime($settings['duration']) - time(); + } + } + $self->_engines[$name]->settings = $settings; + } + return $self->settings($name); + } + +/** + * Garbage collection + * + * Permanently remove all expired and deleted data + * + * @return void + * @access public + * @static + */ + function gc() { + $self =& Cache::getInstance(); + $self->_engines[$self->__name]->gc(); + } + +/** + * Write data for key into cache. Will automatically use the currently + * active cache configuration. To set the currently active configuration use + * Cache::config() + * + * ### Usage: + * + * Writing to the active cache config: + * + * `Cache::write('cached_data', $data);` + * + * Writing to a specific cache config: + * + * `Cache::write('cached_data', $data, 'long_term');` + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached - anything except a resource + * @param string $config Optional string configuration name to write to. + * @return boolean True if the data was successfully cached, false on failure + * @access public + * @static + */ + function write($key, $value, $config = null) { + $self =& Cache::getInstance(); + + if (!$config) { + $config = $self->__name; + } + $settings = $self->settings($config); + + if (empty($settings)) { + return null; + } + if (!$self->isInitialized($config)) { + return false; + } + $key = $self->_engines[$config]->key($key); + + if (!$key || is_resource($value)) { + return false; + } + + $success = $self->_engines[$config]->write($settings['prefix'] . $key, $value, $settings['duration']); + $self->set(); + return $success; + } + +/** + * Read a key from the cache. Will automatically use the currently + * active cache configuration. To set the currently active configuration use + * Cache::config() + * + * ### Usage: + * + * Reading from the active cache configuration. + * + * `Cache::read('my_data');` + * + * Reading from a specific cache configuration. + * + * `Cache::read('my_data', 'long_term');` + * + * @param string $key Identifier for the data + * @param string $config optional name of the configuration to use. + * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it + * @access public + * @static + */ + function read($key, $config = null) { + $self =& Cache::getInstance(); + + if (!$config) { + $config = $self->__name; + } + $settings = $self->settings($config); + + if (empty($settings)) { + return null; + } + if (!$self->isInitialized($config)) { + return false; + } + $key = $self->_engines[$config]->key($key); + if (!$key) { + return false; + } + $success = $self->_engines[$config]->read($settings['prefix'] . $key); + + if ($config !== null && $config !== $self->__name) { + $self->set(); + } + return $success; + } + +/** + * Increment a number under the key and return incremented value. + * + * @param string $key Identifier for the data + * @param integer $offset How much to add + * @param string $config Optional string configuration name. If not specified the current + * default config will be used. + * @return mixed new value, or false if the data doesn't exist, is not integer, + * or if there was an error fetching it. + * @access public + */ + function increment($key, $offset = 1, $config = null) { + $self =& Cache::getInstance(); + + if (!$config) { + $config = $self->__name; + } + $settings = $self->settings($config); + + if (empty($settings)) { + return null; + } + if (!$self->isInitialized($config)) { + return false; + } + $key = $self->_engines[$config]->key($key); + + if (!$key || !is_integer($offset) || $offset < 0) { + return false; + } + $success = $self->_engines[$config]->increment($settings['prefix'] . $key, $offset); + $self->set(); + return $success; + } +/** + * Decrement a number under the key and return decremented value. + * + * @param string $key Identifier for the data + * @param integer $offset How much to substract + * @param string $config Optional string configuration name, if not specified the current + * default config will be used. + * @return mixed new value, or false if the data doesn't exist, is not integer, + * or if there was an error fetching it + * @access public + */ + function decrement($key, $offset = 1, $config = null) { + $self =& Cache::getInstance(); + + if (!$config) { + $config = $self->__name; + } + $settings = $self->settings($config); + + if (empty($settings)) { + return null; + } + if (!$self->isInitialized($config)) { + return false; + } + $key = $self->_engines[$config]->key($key); + + if (!$key || !is_integer($offset) || $offset < 0) { + return false; + } + $success = $self->_engines[$config]->decrement($settings['prefix'] . $key, $offset); + $self->set(); + return $success; + } +/** + * Delete a key from the cache. Will automatically use the currently + * active cache configuration. To set the currently active configuration use + * Cache::config() + * + * ### Usage: + * + * Deleting from the active cache configuration. + * + * `Cache::delete('my_data');` + * + * Deleting from a specific cache configuration. + * + * `Cache::delete('my_data', 'long_term');` + * + * @param string $key Identifier for the data + * @param string $config name of the configuration to use + * @return boolean True if the value was succesfully deleted, false if it didn't exist or couldn't be removed + * @access public + * @static + */ + function delete($key, $config = null) { + $self =& Cache::getInstance(); + if (!$config) { + $config = $self->__name; + } + $settings = $self->settings($config); + + if (empty($settings)) { + return null; + } + if (!$self->isInitialized($config)) { + return false; + } + $key = $self->_engines[$config]->key($key); + if (!$key) { + return false; + } + + $success = $self->_engines[$config]->delete($settings['prefix'] . $key); + $self->set(); + return $success; + } + +/** + * Delete all keys from the cache. + * + * @param boolean $check if true will check expiration, otherwise delete all + * @param string $config name of the configuration to use + * @return boolean True if the cache was succesfully cleared, false otherwise + * @access public + * @static + */ + function clear($check = false, $config = null) { + $self =& Cache::getInstance(); + if (!$config) { + $config = $self->__name; + } + $settings = $self->settings($config); + + if (empty($settings)) { + return null; + } + + if (!$self->isInitialized($config)) { + return false; + } + $success = $self->_engines[$config]->clear($check); + $self->set(); + return $success; + } + +/** + * Check if Cache has initialized a working config for the given name. + * + * @param string $engine Name of the engine + * @param string $config Name of the configuration setting + * @return bool Whether or not the config name has been initialized. + * @access public + * @static + */ + function isInitialized($name = null) { + if (Configure::read('Cache.disable')) { + return false; + } + $self =& Cache::getInstance(); + if (!$name && isset($self->__config[$self->__name])) { + $name = $self->__name; + } + return isset($self->_engines[$name]); + } + +/** + * Return the settings for current cache engine. If no name is supplied the settings + * for the 'active default' configuration will be returned. To set the 'active default' + * configuration use `Cache::config()` + * + * @param string $engine Name of the configuration to get settings for. + * @return array list of settings for this engine + * @see Cache::config() + * @access public + * @static + */ + function settings($name = null) { + $self =& Cache::getInstance(); + if (!$name && isset($self->__config[$self->__name])) { + $name = $self->__name; + } + if (!empty($self->_engines[$name])) { + return $self->_engines[$name]->settings(); + } + return array(); + } + +/** + * Write the session when session data is persisted with cache. + * + * @return void + * @access public + */ + function __destruct() { + if (Configure::read('Session.save') == 'cache' && function_exists('session_write_close')) { + session_write_close(); + } + } +} + +/** + * Storage engine for CakePHP caching + * + * @package cake + * @subpackage cake.cake.libs + */ +class CacheEngine { + +/** + * Settings of current engine instance + * + * @var int + * @access public + */ + var $settings = array(); + +/** + * Initialize the cache engine + * + * Called automatically by the cache frontend + * + * @param array $params Associative array of parameters for the engine + * @return boolean True if the engine has been succesfully initialized, false if not + * @access public + */ + function init($settings = array()) { + $this->settings = array_merge( + array('prefix' => 'cake_', 'duration'=> 3600, 'probability'=> 100), + $this->settings, + $settings + ); + if (!is_numeric($this->settings['duration'])) { + $this->settings['duration'] = strtotime($this->settings['duration']) - time(); + } + return true; + } + +/** + * Garbage collection + * + * Permanently remove all expired and deleted data + * + * @access public + */ + function gc() { + } + +/** + * Write value for a key into cache + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param mixed $duration How long to cache the data, in seconds + * @return boolean True if the data was succesfully cached, false on failure + * @access public + */ + function write($key, &$value, $duration) { + trigger_error(sprintf(__('Method write() not implemented in %s', true), get_class($this)), E_USER_ERROR); + } + +/** + * Read a key from the cache + * + * @param string $key Identifier for the data + * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it + * @access public + */ + function read($key) { + trigger_error(sprintf(__('Method read() not implemented in %s', true), get_class($this)), E_USER_ERROR); + } + +/** + * Increment a number under the key and return incremented value + * + * @param string $key Identifier for the data + * @param integer $offset How much to add + * @return New incremented value, false otherwise + * @access public + */ + function increment($key, $offset = 1) { + trigger_error(sprintf(__('Method increment() not implemented in %s', true), get_class($this)), E_USER_ERROR); + } +/** + * Decrement a number under the key and return decremented value + * + * @param string $key Identifier for the data + * @param integer $value How much to substract + * @return New incremented value, false otherwise + * @access public + */ + function decrement($key, $offset = 1) { + trigger_error(sprintf(__('Method decrement() not implemented in %s', true), get_class($this)), E_USER_ERROR); + } +/** + * Delete a key from the cache + * + * @param string $key Identifier for the data + * @return boolean True if the value was succesfully deleted, false if it didn't exist or couldn't be removed + * @access public + */ + function delete($key) { + } + +/** + * Delete all keys from the cache + * + * @param boolean $check if true will check expiration, otherwise delete all + * @return boolean True if the cache was succesfully cleared, false otherwise + * @access public + */ + function clear($check) { + } + +/** + * Cache Engine settings + * + * @return array settings + * @access public + */ + function settings() { + return $this->settings; + } + +/** + * Generates a safe key for use with cache engine storage engines. + * + * @param string $key the key passed over + * @return mixed string $key or false + * @access public + */ + function key($key) { + if (empty($key)) { + return false; + } + $key = Inflector::underscore(str_replace(array(DS, '/', '.'), '_', strval($key))); + return $key; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/cache/apc.php b/code/ryzom/tools/server/www/webtt/cake/libs/cache/apc.php new file mode 100644 index 000000000..92a458d22 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/cache/apc.php @@ -0,0 +1,123 @@ + 'Apc', 'prefix' => Inflector::slug(APP_DIR) . '_'), $settings)); + return function_exists('apc_cache_info'); + } + +/** + * Write data for key into cache + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param integer $duration How long to cache the data, in seconds + * @return boolean True if the data was succesfully cached, false on failure + * @access public + */ + function write($key, &$value, $duration) { + $expires = time() + $duration; + apc_store($key.'_expires', $expires, $duration); + return apc_store($key, $value, $duration); + } + +/** + * Read a key from the cache + * + * @param string $key Identifier for the data + * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it + * @access public + */ + function read($key) { + $time = time(); + $cachetime = intval(apc_fetch($key.'_expires')); + if ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime) { + return false; + } + return apc_fetch($key); + } + +/** + * Increments the value of an integer cached key + * + * @param string $key Identifier for the data + * @param integer $offset How much to increment + * @param integer $duration How long to cache the data, in seconds + * @return New incremented value, false otherwise + * @access public + */ + function increment($key, $offset = 1) { + return apc_inc($key, $offset); + } + +/** + * Decrements the value of an integer cached key + * + * @param string $key Identifier for the data + * @param integer $offset How much to substract + * @param integer $duration How long to cache the data, in seconds + * @return New decremented value, false otherwise + * @access public + */ + function decrement($key, $offset = 1) { + return apc_dec($key, $offset); + } + +/** + * Delete a key from the cache + * + * @param string $key Identifier for the data + * @return boolean True if the value was succesfully deleted, false if it didn't exist or couldn't be removed + * @access public + */ + function delete($key) { + return apc_delete($key); + } + +/** + * Delete all keys from the cache + * + * @return boolean True if the cache was succesfully cleared, false otherwise + * @access public + */ + function clear() { + return apc_clear_cache('user'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/cache/file.php b/code/ryzom/tools/server/www/webtt/cake/libs/cache/file.php new file mode 100644 index 000000000..55a678100 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/cache/file.php @@ -0,0 +1,272 @@ + CACHE + * - prefix = string prefix for filename, default => cake_ + * - lock = enable file locking on write, default => false + * - serialize = serialize the data, default => true + * + * @var array + * @see CacheEngine::__defaults + * @access public + */ + var $settings = array(); + +/** + * True unless FileEngine::__active(); fails + * + * @var boolean + * @access protected + */ + var $_init = true; + +/** + * Initialize the Cache Engine + * + * Called automatically by the cache frontend + * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array()); + * + * @param array $setting array of setting for the engine + * @return boolean True if the engine has been successfully initialized, false if not + * @access public + */ + function init($settings = array()) { + parent::init(array_merge( + array( + 'engine' => 'File', 'path' => CACHE, 'prefix'=> 'cake_', 'lock'=> false, + 'serialize'=> true, 'isWindows' => false + ), + $settings + )); + if (!isset($this->_File)) { + $this->_File =& new File($this->settings['path'] . DS . 'cake'); + } + + if (DIRECTORY_SEPARATOR === '\\') { + $this->settings['isWindows'] = true; + } + + $path = $this->_File->Folder->cd($this->settings['path']); + if ($path) { + $this->settings['path'] = $path; + } + return $this->__active(); + } + +/** + * Garbage collection. Permanently remove all expired and deleted data + * + * @return boolean True if garbage collection was succesful, false on failure + * @access public + */ + function gc() { + return $this->clear(true); + } + +/** + * Write data for key into cache + * + * @param string $key Identifier for the data + * @param mixed $data Data to be cached + * @param mixed $duration How long to cache the data, in seconds + * @return boolean True if the data was succesfully cached, false on failure + * @access public + */ + function write($key, &$data, $duration) { + if ($data === '' || !$this->_init) { + return false; + } + + if ($this->_setKey($key) === false) { + return false; + } + + $lineBreak = "\n"; + + if ($this->settings['isWindows']) { + $lineBreak = "\r\n"; + } + + if (!empty($this->settings['serialize'])) { + if ($this->settings['isWindows']) { + $data = str_replace('\\', '\\\\\\\\', serialize($data)); + } else { + $data = serialize($data); + } + } + + if ($this->settings['lock']) { + $this->_File->lock = true; + } + $expires = time() + $duration; + $contents = $expires . $lineBreak . $data . $lineBreak; + $success = $this->_File->write($contents); + $this->_File->close(); + return $success; + } + +/** + * Read a key from the cache + * + * @param string $key Identifier for the data + * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it + * @access public + */ + function read($key) { + if ($this->_setKey($key) === false || !$this->_init || !$this->_File->exists()) { + return false; + } + if ($this->settings['lock']) { + $this->_File->lock = true; + } + $time = time(); + $cachetime = intval($this->_File->read(11)); + + if ($cachetime !== false && ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime)) { + $this->_File->close(); + return false; + } + $data = $this->_File->read(true); + + if ($data !== '' && !empty($this->settings['serialize'])) { + if ($this->settings['isWindows']) { + $data = str_replace('\\\\\\\\', '\\', $data); + } + $data = unserialize((string)$data); + } + $this->_File->close(); + return $data; + } + +/** + * Delete a key from the cache + * + * @param string $key Identifier for the data + * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed + * @access public + */ + function delete($key) { + if ($this->_setKey($key) === false || !$this->_init) { + return false; + } + return $this->_File->delete(); + } + +/** + * Delete all values from the cache + * + * @param boolean $check Optional - only delete expired cache items + * @return boolean True if the cache was succesfully cleared, false otherwise + * @access public + */ + function clear($check) { + if (!$this->_init) { + return false; + } + $dir = dir($this->settings['path']); + if ($check) { + $now = time(); + $threshold = $now - $this->settings['duration']; + } + $prefixLength = strlen($this->settings['prefix']); + while (($entry = $dir->read()) !== false) { + if (substr($entry, 0, $prefixLength) !== $this->settings['prefix']) { + continue; + } + if ($this->_setKey($entry) === false) { + continue; + } + if ($check) { + $mtime = $this->_File->lastChange(); + + if ($mtime === false || $mtime > $threshold) { + continue; + } + + $expires = $this->_File->read(11); + $this->_File->close(); + + if ($expires > $now) { + continue; + } + } + $this->_File->delete(); + } + $dir->close(); + return true; + } + +/** + * Get absolute file for a given key + * + * @param string $key The key + * @return mixed Absolute cache file for the given key or false if erroneous + * @access private + */ + function _setKey($key) { + $this->_File->Folder->cd($this->settings['path']); + if ($key !== $this->_File->name) { + $this->_File->name = $key; + $this->_File->path = null; + } + if (!$this->_File->Folder->inPath($this->_File->pwd(), true)) { + return false; + } + } + +/** + * Determine is cache directory is writable + * + * @return boolean + * @access private + */ + function __active() { + if ($this->_init && !is_writable($this->settings['path'])) { + $this->_init = false; + trigger_error(sprintf(__('%s is not writable', true), $this->settings['path']), E_USER_WARNING); + return false; + } + return true; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/cache/memcache.php b/code/ryzom/tools/server/www/webtt/cake/libs/cache/memcache.php new file mode 100644 index 000000000..a1bcc5c55 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/cache/memcache.php @@ -0,0 +1,220 @@ + 127.0.0.1. If an + * array MemcacheEngine will use them as a pool. + * - compress = boolean, default => false + * + * @var array + * @access public + */ + var $settings = array(); + +/** + * Initialize the Cache Engine + * + * Called automatically by the cache frontend + * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array()); + * + * @param array $setting array of setting for the engine + * @return boolean True if the engine has been successfully initialized, false if not + * @access public + */ + function init($settings = array()) { + if (!class_exists('Memcache')) { + return false; + } + parent::init(array_merge(array( + 'engine'=> 'Memcache', + 'prefix' => Inflector::slug(APP_DIR) . '_', + 'servers' => array('127.0.0.1'), + 'compress'=> false, + 'persistent' => true + ), $settings) + ); + + if ($this->settings['compress']) { + $this->settings['compress'] = MEMCACHE_COMPRESSED; + } + if (!is_array($this->settings['servers'])) { + $this->settings['servers'] = array($this->settings['servers']); + } + if (!isset($this->__Memcache)) { + $return = false; + $this->__Memcache =& new Memcache(); + foreach ($this->settings['servers'] as $server) { + list($host, $port) = $this->_parseServerString($server); + if ($this->__Memcache->addServer($host, $port, $this->settings['persistent'])) { + $return = true; + } + } + return $return; + } + return true; + } + +/** + * Parses the server address into the host/port. Handles both IPv6 and IPv4 + * addresses + * + * @param string $server The server address string. + * @return array Array containing host, port + */ + function _parseServerString($server) { + if (substr($server, 0, 1) == '[') { + $position = strpos($server, ']:'); + if ($position !== false) { + $position++; + } + } else { + $position = strpos($server, ':'); + } + $port = 11211; + $host = $server; + if ($position !== false) { + $host = substr($server, 0, $position); + $port = substr($server, $position + 1); + } + return array($host, $port); + } + +/** + * Write data for key into cache. When using memcache as your cache engine + * remember that the Memcache pecl extension does not support cache expiry times greater + * than 30 days in the future. Any duration greater than 30 days will be treated as never expiring. + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param integer $duration How long to cache the data, in seconds + * @return boolean True if the data was succesfully cached, false on failure + * @see http://php.net/manual/en/memcache.set.php + * @access public + */ + function write($key, &$value, $duration) { + if ($duration > 30 * DAY) { + $duration = 0; + } + return $this->__Memcache->set($key, $value, $this->settings['compress'], $duration); + } + +/** + * Read a key from the cache + * + * @param string $key Identifier for the data + * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it + * @access public + */ + function read($key) { + return $this->__Memcache->get($key); + } + +/** + * Increments the value of an integer cached key + * + * @param string $key Identifier for the data + * @param integer $offset How much to increment + * @param integer $duration How long to cache the data, in seconds + * @return New incremented value, false otherwise + * @access public + */ + function increment($key, $offset = 1) { + if ($this->settings['compress']) { + trigger_error(sprintf(__('Method increment() not implemented for compressed cache in %s', true), get_class($this)), E_USER_ERROR); + } + return $this->__Memcache->increment($key, $offset); + } + +/** + * Decrements the value of an integer cached key + * + * @param string $key Identifier for the data + * @param integer $offset How much to substract + * @param integer $duration How long to cache the data, in seconds + * @return New decremented value, false otherwise + * @access public + */ + function decrement($key, $offset = 1) { + if ($this->settings['compress']) { + trigger_error(sprintf(__('Method decrement() not implemented for compressed cache in %s', true), get_class($this)), E_USER_ERROR); + } + return $this->__Memcache->decrement($key, $offset); + } + +/** + * Delete a key from the cache + * + * @param string $key Identifier for the data + * @return boolean True if the value was succesfully deleted, false if it didn't exist or couldn't be removed + * @access public + */ + function delete($key) { + return $this->__Memcache->delete($key); + } + +/** + * Delete all keys from the cache + * + * @return boolean True if the cache was succesfully cleared, false otherwise + * @access public + */ + function clear() { + return $this->__Memcache->flush(); + } + +/** + * Connects to a server in connection pool + * + * @param string $host host ip address or name + * @param integer $port Server port + * @return boolean True if memcache server was connected + * @access public + */ + function connect($host, $port = 11211) { + if ($this->__Memcache->getServerStatus($host, $port) === 0) { + if ($this->__Memcache->connect($host, $port)) { + return true; + } + return false; + } + return true; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/cache/xcache.php b/code/ryzom/tools/server/www/webtt/cake/libs/cache/xcache.php new file mode 100644 index 000000000..db93b8dfd --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/cache/xcache.php @@ -0,0 +1,183 @@ + 'Xcache', 'prefix' => Inflector::slug(APP_DIR) . '_', 'PHP_AUTH_USER' => 'user', 'PHP_AUTH_PW' => 'password' + ), $settings) + ); + return function_exists('xcache_info'); + } + +/** + * Write data for key into cache + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param integer $duration How long to cache the data, in seconds + * @return boolean True if the data was succesfully cached, false on failure + * @access public + */ + function write($key, &$value, $duration) { + $expires = time() + $duration; + xcache_set($key . '_expires', $expires, $duration); + return xcache_set($key, $value, $duration); + } + +/** + * Read a key from the cache + * + * @param string $key Identifier for the data + * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it + * @access public + */ + function read($key) { + if (xcache_isset($key)) { + $time = time(); + $cachetime = intval(xcache_get($key . '_expires')); + if ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime) { + return false; + } + return xcache_get($key); + } + return false; + } + +/** + * Increments the value of an integer cached key + * If the cache key is not an integer it will be treated as 0 + * + * @param string $key Identifier for the data + * @param integer $offset How much to increment + * @param integer $duration How long to cache the data, in seconds + * @return New incremented value, false otherwise + * @access public + */ + function increment($key, $offset = 1) { + return xcache_inc($key, $offset); + } + +/** + * Decrements the value of an integer cached key. + * If the cache key is not an integer it will be treated as 0 + * + * @param string $key Identifier for the data + * @param integer $offset How much to substract + * @param integer $duration How long to cache the data, in seconds + * @return New decremented value, false otherwise + * @access public + */ + function decrement($key, $offset = 1) { + return xcache_dec($key, $offset); + } +/** + * Delete a key from the cache + * + * @param string $key Identifier for the data + * @return boolean True if the value was succesfully deleted, false if it didn't exist or couldn't be removed + * @access public + */ + function delete($key) { + return xcache_unset($key); + } + +/** + * Delete all keys from the cache + * + * @return boolean True if the cache was succesfully cleared, false otherwise + * @access public + */ + function clear() { + $this->__auth(); + $max = xcache_count(XC_TYPE_VAR); + for ($i = 0; $i < $max; $i++) { + xcache_clear_cache(XC_TYPE_VAR, $i); + } + $this->__auth(true); + return true; + } + +/** + * Populates and reverses $_SERVER authentication values + * Makes necessary changes (and reverting them back) in $_SERVER + * + * This has to be done because xcache_clear_cache() needs to pass Basic Http Auth + * (see xcache.admin configuration settings) + * + * @param boolean Revert changes + * @access private + */ + function __auth($reverse = false) { + static $backup = array(); + $keys = array('PHP_AUTH_USER' => 'user', 'PHP_AUTH_PW' => 'password'); + foreach ($keys as $key => $setting) { + if ($reverse) { + if (isset($backup[$key])) { + $_SERVER[$key] = $backup[$key]; + unset($backup[$key]); + } else { + unset($_SERVER[$key]); + } + } else { + $value = env($key); + if (!empty($value)) { + $backup[$key] = $value; + } + if (!empty($this->settings[$setting])) { + $_SERVER[$key] = $this->settings[$setting]; + } else if (!empty($this->settings[$key])) { + $_SERVER[$key] = $this->settings[$key]; + } else { + $_SERVER[$key] = $value; + } + } + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/cake_log.php b/code/ryzom/tools/server/www/webtt/cake/libs/cake_log.php new file mode 100644 index 000000000..1d92ad854 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/cake_log.php @@ -0,0 +1,292 @@ + 'FileLog', + * 'path' => '/var/logs/my_app/' + * )); + * }}} + * + * Will configure a FileLog instance to use the specified path. All options that are not `engine` + * are passed onto the logging adapter, and handled there. Any class can be configured as a logging + * adapter as long as it implements a `write` method with the following signature. + * + * `write($type, $message)` + * + * For an explaination of these parameters, see CakeLog::write() + * + * @param string $key The keyname for this logger, used to remove the logger later. + * @param array $config Array of configuration information for the logger + * @return boolean success of configuration. + * @static + */ + function config($key, $config) { + if (empty($config['engine'])) { + trigger_error(__('Missing logger classname', true), E_USER_WARNING); + return false; + } + $self =& CakeLog::getInstance(); + $className = $self->_getLogger($config['engine']); + if (!$className) { + return false; + } + unset($config['engine']); + $self->_streams[$key] = new $className($config); + return true; + } + +/** + * Attempts to import a logger class from the various paths it could be on. + * Checks that the logger class implements a write method as well. + * + * @param string $loggerName the plugin.className of the logger class you want to build. + * @return mixed boolean false on any failures, string of classname to use if search was successful. + * @access protected + */ + function _getLogger($loggerName) { + list($plugin, $loggerName) = pluginSplit($loggerName); + + if ($plugin) { + App::import('Lib', $plugin . '.log/' . $loggerName); + } else { + if (!App::import('Lib', 'log/' . $loggerName)) { + App::import('Core', 'log/' . $loggerName); + } + } + if (!class_exists($loggerName)) { + trigger_error(sprintf(__('Could not load logger class %s', true), $loggerName), E_USER_WARNING); + return false; + } + if (!is_callable(array($loggerName, 'write'))) { + trigger_error( + sprintf(__('logger class %s does not implement a write method.', true), $loggerName), + E_USER_WARNING + ); + return false; + } + return $loggerName; + } + +/** + * Returns the keynames of the currently active streams + * + * @return array Array of configured log streams. + * @access public + * @static + */ + function configured() { + $self =& CakeLog::getInstance(); + return array_keys($self->_streams); + } + +/** + * Removes a stream from the active streams. Once a stream has been removed + * it will no longer have messages sent to it. + * + * @param string $keyname Key name of a configured stream to remove. + * @return void + * @access public + * @static + */ + function drop($streamName) { + $self =& CakeLog::getInstance(); + unset($self->_streams[$streamName]); + } + +/** + * Configures the automatic/default stream a FileLog. + * + * @return void + * @access protected + */ + function _autoConfig() { + if (!class_exists('FileLog')) { + App::import('Core', 'log/FileLog'); + } + $this->_streams['default'] =& new FileLog(array('path' => LOGS)); + } + +/** + * Writes the given message and type to all of the configured log adapters. + * Configured adapters are passed both the $type and $message variables. $type + * is one of the following strings/values. + * + * ### Types: + * + * - `LOG_WARNING` => 'warning', + * - `LOG_NOTICE` => 'notice', + * - `LOG_INFO` => 'info', + * - `LOG_DEBUG` => 'debug', + * - `LOG_ERR` => 'error', + * - `LOG_ERROR` => 'error' + * + * ### Usage: + * + * Write a message to the 'warning' log: + * + * `CakeLog::write('warning', 'Stuff is broken here');` + * + * @param string $type Type of message being written + * @param string $message Message content to log + * @return boolean Success + * @access public + * @static + */ + function write($type, $message) { + if (!defined('LOG_ERROR')) { + define('LOG_ERROR', 2); + } + if (!defined('LOG_ERR')) { + define('LOG_ERR', LOG_ERROR); + } + $levels = array( + LOG_WARNING => 'warning', + LOG_NOTICE => 'notice', + LOG_INFO => 'info', + LOG_DEBUG => 'debug', + LOG_ERR => 'error', + LOG_ERROR => 'error' + ); + + if (is_int($type) && isset($levels[$type])) { + $type = $levels[$type]; + } + $self =& CakeLog::getInstance(); + if (empty($self->_streams)) { + $self->_autoConfig(); + } + $keys = array_keys($self->_streams); + foreach ($keys as $key) { + $logger =& $self->_streams[$key]; + $logger->write($type, $message); + } + return true; + } + +/** + * An error_handler that will log errors to file using CakeLog::write(); + * You can control how verbose and what type of errors this error_handler will + * catch using `Configure::write('log', $value)`. See core.php for more information. + * + * + * @param integer $code Code of error + * @param string $description Error description + * @param string $file File on which error occurred + * @param integer $line Line that triggered the error + * @param array $context Context + * @return void + */ + function handleError($code, $description, $file = null, $line = null, $context = null) { + if ($code === 2048 || $code === 8192 || error_reporting() === 0) { + return; + } + switch ($code) { + case E_PARSE: + case E_ERROR: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + $error = 'Fatal Error'; + $level = LOG_ERROR; + break; + case E_WARNING: + case E_USER_WARNING: + case E_COMPILE_WARNING: + case E_RECOVERABLE_ERROR: + $error = 'Warning'; + $level = LOG_WARNING; + break; + case E_NOTICE: + case E_USER_NOTICE: + $error = 'Notice'; + $level = LOG_NOTICE; + break; + default: + return; + break; + } + $message = $error . ' (' . $code . '): ' . $description . ' in [' . $file . ', line ' . $line . ']'; + CakeLog::write($level, $message); + } +} + +if (!defined('DISABLE_DEFAULT_ERROR_HANDLING')) { + $cakeLog =& CakeLog::getInstance(); + set_error_handler(array($cakeLog, 'handleError')); +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/cake_session.php b/code/ryzom/tools/server/www/webtt/cake/libs/cake_session.php new file mode 100644 index 000000000..395f68536 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/cake_session.php @@ -0,0 +1,794 @@ +time = time(); + + if (Configure::read('Session.checkAgent') === true || Configure::read('Session.checkAgent') === null) { + if (env('HTTP_USER_AGENT') != null) { + $this->_userAgent = md5(env('HTTP_USER_AGENT') . Configure::read('Security.salt')); + } + } + if (Configure::read('Session.save') === 'database') { + $modelName = Configure::read('Session.model'); + $database = Configure::read('Session.database'); + $table = Configure::read('Session.table'); + + if (empty($database)) { + $database = 'default'; + } + $settings = array( + 'class' => 'Session', + 'alias' => 'Session', + 'table' => 'cake_sessions', + 'ds' => $database + ); + if (!empty($modelName)) { + $settings['class'] = $modelName; + } + if (!empty($table)) { + $settings['table'] = $table; + } + ClassRegistry::init($settings); + } + if ($start === true) { + if (!empty($base)) { + $this->path = $base; + if (strpos($base, 'index.php') !== false) { + $this->path = str_replace('index.php', '', $base); + } + if (strpos($base, '?') !== false) { + $this->path = str_replace('?', '', $base); + } + } + $this->host = env('HTTP_HOST'); + + if (strpos($this->host, ':') !== false) { + $this->host = substr($this->host, 0, strpos($this->host, ':')); + } + } + if (isset($_SESSION) || $start === true) { + $this->sessionTime = $this->time + (Security::inactiveMins() * Configure::read('Session.timeout')); + $this->security = Configure::read('Security.level'); + } + parent::__construct(); + } + +/** + * Starts the Session. + * + * @return boolean True if session was started + * @access public + */ + function start() { + if ($this->started()) { + return true; + } + if (function_exists('session_write_close')) { + session_write_close(); + } + $this->__initSession(); + $this->__startSession(); + return $this->started(); + } + +/** + * Determine if Session has been started. + * + * @access public + * @return boolean True if session has been started. + */ + function started() { + if (isset($_SESSION) && session_id()) { + return true; + } + return false; + } + +/** + * Returns true if given variable is set in session. + * + * @param string $name Variable name to check for + * @return boolean True if variable is there + * @access public + */ + function check($name) { + if (empty($name)) { + return false; + } + $result = Set::classicExtract($_SESSION, $name); + return isset($result); + } + +/** + * Returns the Session id + * + * @param id $name string + * @return string Session id + * @access public + */ + function id($id = null) { + if ($id) { + $this->id = $id; + session_id($this->id); + } + if ($this->started()) { + return session_id(); + } else { + return $this->id; + } + } + +/** + * Removes a variable from session. + * + * @param string $name Session variable to remove + * @return boolean Success + * @access public + */ + function delete($name) { + if ($this->check($name)) { + if (in_array($name, $this->watchKeys)) { + trigger_error(sprintf(__('Deleting session key {%s}', true), $name), E_USER_NOTICE); + } + $this->__overwrite($_SESSION, Set::remove($_SESSION, $name)); + return ($this->check($name) == false); + } + $this->__setError(2, sprintf(__("%s doesn't exist", true), $name)); + return false; + } + +/** + * Used to write new data to _SESSION, since PHP doesn't like us setting the _SESSION var itself + * + * @param array $old Set of old variables => values + * @param array $new New set of variable => value + * @access private + */ + function __overwrite(&$old, $new) { + if (!empty($old)) { + foreach ($old as $key => $var) { + if (!isset($new[$key])) { + unset($old[$key]); + } + } + } + foreach ($new as $key => $var) { + $old[$key] = $var; + } + } + +/** + * Return error description for given error number. + * + * @param integer $errorNumber Error to set + * @return string Error as string + * @access private + */ + function __error($errorNumber) { + if (!is_array($this->error) || !array_key_exists($errorNumber, $this->error)) { + return false; + } else { + return $this->error[$errorNumber]; + } + } + +/** + * Returns last occurred error as a string, if any. + * + * @return mixed Error description as a string, or false. + * @access public + */ + function error() { + if ($this->lastError) { + return $this->__error($this->lastError); + } else { + return false; + } + } + +/** + * Returns true if session is valid. + * + * @return boolean Success + * @access public + */ + function valid() { + if ($this->read('Config')) { + if ((Configure::read('Session.checkAgent') === false || $this->_userAgent == $this->read('Config.userAgent')) && $this->time <= $this->read('Config.time')) { + if ($this->error === false) { + $this->valid = true; + } + } else { + $this->valid = false; + $this->__setError(1, 'Session Highjacking Attempted !!!'); + } + } + return $this->valid; + } + +/** + * Returns given session variable, or all of them, if no parameters given. + * + * @param mixed $name The name of the session variable (or a path as sent to Set.extract) + * @return mixed The value of the session variable + * @access public + */ + function read($name = null) { + if (is_null($name)) { + return $this->__returnSessionVars(); + } + if (empty($name)) { + return false; + } + $result = Set::classicExtract($_SESSION, $name); + + if (!is_null($result)) { + return $result; + } + $this->__setError(2, "$name doesn't exist"); + return null; + } + +/** + * Returns all session variables. + * + * @return mixed Full $_SESSION array, or false on error. + * @access private + */ + function __returnSessionVars() { + if (!empty($_SESSION)) { + return $_SESSION; + } + $this->__setError(2, "No Session vars set"); + return false; + } + +/** + * Tells Session to write a notification when a certain session path or subpath is written to + * + * @param mixed $var The variable path to watch + * @return void + * @access public + */ + function watch($var) { + if (empty($var)) { + return false; + } + if (!in_array($var, $this->watchKeys, true)) { + $this->watchKeys[] = $var; + } + } + +/** + * Tells Session to stop watching a given key path + * + * @param mixed $var The variable path to watch + * @return void + * @access public + */ + function ignore($var) { + if (!in_array($var, $this->watchKeys)) { + return; + } + foreach ($this->watchKeys as $i => $key) { + if ($key == $var) { + unset($this->watchKeys[$i]); + $this->watchKeys = array_values($this->watchKeys); + return; + } + } + } + +/** + * Writes value to given session variable name. + * + * @param mixed $name Name of variable + * @param string $value Value to write + * @return boolean True if the write was successful, false if the write failed + * @access public + */ + function write($name, $value) { + if (empty($name)) { + return false; + } + if (in_array($name, $this->watchKeys)) { + trigger_error(sprintf(__('Writing session key {%s}: %s', true), $name, Debugger::exportVar($value)), E_USER_NOTICE); + } + $this->__overwrite($_SESSION, Set::insert($_SESSION, $name, $value)); + return (Set::classicExtract($_SESSION, $name) === $value); + } + +/** + * Helper method to destroy invalid sessions. + * + * @return void + * @access public + */ + function destroy() { + if ($this->started()) { + session_destroy(); + } + $_SESSION = null; + $this->__construct($this->path); + $this->start(); + $this->renew(); + $this->_checkValid(); + } + +/** + * Helper method to initialize a session, based on Cake core settings. + * + * @access private + */ + function __initSession() { + $iniSet = function_exists('ini_set'); + if ($iniSet && env('HTTPS')) { + ini_set('session.cookie_secure', 1); + } + if ($iniSet && ($this->security === 'high' || $this->security === 'medium')) { + ini_set('session.referer_check', $this->host); + } + + if ($this->security == 'high') { + $this->cookieLifeTime = 0; + } else { + $this->cookieLifeTime = Configure::read('Session.timeout') * (Security::inactiveMins() * 60); + } + + switch (Configure::read('Session.save')) { + case 'cake': + if (empty($_SESSION)) { + if ($iniSet) { + ini_set('session.use_trans_sid', 0); + ini_set('url_rewriter.tags', ''); + ini_set('session.serialize_handler', 'php'); + ini_set('session.use_cookies', 1); + ini_set('session.name', Configure::read('Session.cookie')); + ini_set('session.cookie_lifetime', $this->cookieLifeTime); + ini_set('session.cookie_path', $this->path); + ini_set('session.auto_start', 0); + ini_set('session.save_path', TMP . 'sessions'); + } + } + break; + case 'database': + if (empty($_SESSION)) { + if (Configure::read('Session.model') === null) { + trigger_error(__("You must set the all Configure::write('Session.*') in core.php to use database storage"), E_USER_WARNING); + $this->_stop(); + } + if ($iniSet) { + ini_set('session.use_trans_sid', 0); + ini_set('url_rewriter.tags', ''); + ini_set('session.save_handler', 'user'); + ini_set('session.serialize_handler', 'php'); + ini_set('session.use_cookies', 1); + ini_set('session.name', Configure::read('Session.cookie')); + ini_set('session.cookie_lifetime', $this->cookieLifeTime); + ini_set('session.cookie_path', $this->path); + ini_set('session.auto_start', 0); + } + } + session_set_save_handler( + array('CakeSession','__open'), + array('CakeSession', '__close'), + array('CakeSession', '__read'), + array('CakeSession', '__write'), + array('CakeSession', '__destroy'), + array('CakeSession', '__gc') + ); + break; + case 'php': + if (empty($_SESSION)) { + if ($iniSet) { + ini_set('session.use_trans_sid', 0); + ini_set('session.name', Configure::read('Session.cookie')); + ini_set('session.cookie_lifetime', $this->cookieLifeTime); + ini_set('session.cookie_path', $this->path); + } + } + break; + case 'cache': + if (empty($_SESSION)) { + if (!class_exists('Cache')) { + require LIBS . 'cache.php'; + } + if ($iniSet) { + ini_set('session.use_trans_sid', 0); + ini_set('url_rewriter.tags', ''); + ini_set('session.save_handler', 'user'); + ini_set('session.use_cookies', 1); + ini_set('session.name', Configure::read('Session.cookie')); + ini_set('session.cookie_lifetime', $this->cookieLifeTime); + ini_set('session.cookie_path', $this->path); + } + } + session_set_save_handler( + array('CakeSession','__open'), + array('CakeSession', '__close'), + array('Cache', 'read'), + array('Cache', 'write'), + array('Cache', 'delete'), + array('Cache', 'gc') + ); + break; + default: + $config = CONFIGS . Configure::read('Session.save') . '.php'; + + if (is_file($config)) { + require($config); + } + break; + } + } + +/** + * Helper method to start a session + * + * @access private + */ + function __startSession() { + if (headers_sent()) { + if (empty($_SESSION)) { + $_SESSION = array(); + } + return true; + } elseif (!isset($_SESSION)) { + session_cache_limiter ("must-revalidate"); + session_start(); + header ('P3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"'); + return true; + } else { + session_start(); + return true; + } + } + +/** + * Helper method to create a new session. + * + * @return void + * @access protected + */ + function _checkValid() { + if ($this->read('Config')) { + if ((Configure::read('Session.checkAgent') === false || $this->_userAgent == $this->read('Config.userAgent')) && $this->time <= $this->read('Config.time')) { + $time = $this->read('Config.time'); + $this->write('Config.time', $this->sessionTime); + if (Configure::read('Security.level') === 'high') { + $check = $this->read('Config.timeout'); + $check -= 1; + $this->write('Config.timeout', $check); + + if (time() > ($time - (Security::inactiveMins() * Configure::read('Session.timeout')) + 2) || $check < 1) { + $this->renew(); + $this->write('Config.timeout', 10); + } + } + $this->valid = true; + } else { + $this->destroy(); + $this->valid = false; + $this->__setError(1, 'Session Highjacking Attempted !!!'); + } + } else { + $this->write('Config.userAgent', $this->_userAgent); + $this->write('Config.time', $this->sessionTime); + $this->write('Config.timeout', 10); + $this->valid = true; + $this->__setError(1, 'Session is valid'); + } + } + +/** + * Helper method to restart a session. + * + * @return void + * @access private + */ + function __regenerateId() { + $oldSessionId = session_id(); + if ($oldSessionId) { + if (session_id() != ''|| isset($_COOKIE[session_name()])) { + setcookie(Configure::read('Session.cookie'), '', time() - 42000, $this->path); + } + session_regenerate_id(true); + if (PHP_VERSION < 5.1) { + $sessionPath = session_save_path(); + if (empty($sessionPath)) { + $sessionPath = '/tmp'; + } + $newSessid = session_id(); + + if (function_exists('session_write_close')) { + session_write_close(); + } + $this->__initSession(); + session_id($oldSessionId); + session_start(); + session_destroy(); + $file = $sessionPath . DS . 'sess_' . $oldSessionId; + @unlink($file); + $this->__initSession(); + session_id($newSessid); + session_start(); + } + } + } + +/** + * Restarts this session. + * + * @access public + */ + function renew() { + $this->__regenerateId(); + } + +/** + * Helper method to set an internal error message. + * + * @param integer $errorNumber Number of the error + * @param string $errorMessage Description of the error + * @return void + * @access private + */ + function __setError($errorNumber, $errorMessage) { + if ($this->error === false) { + $this->error = array(); + } + $this->error[$errorNumber] = $errorMessage; + $this->lastError = $errorNumber; + } + +/** + * Method called on open of a database session. + * + * @return boolean Success + * @access private + */ + function __open() { + return true; + } + +/** + * Method called on close of a database session. + * + * @return boolean Success + * @access private + */ + function __close() { + $probability = mt_rand(1, 150); + if ($probability <= 3) { + switch (Configure::read('Session.save')) { + case 'cache': + Cache::gc(); + break; + default: + CakeSession::__gc(); + break; + } + } + return true; + } + +/** + * Method used to read from a database session. + * + * @param mixed $id The key of the value to read + * @return mixed The value of the key or false if it does not exist + * @access private + */ + function __read($id) { + $model =& ClassRegistry::getObject('Session'); + + $row = $model->find('first', array( + 'conditions' => array($model->primaryKey => $id) + )); + + if (empty($row[$model->alias]['data'])) { + return false; + } + + return $row[$model->alias]['data']; + } + +/** + * Helper function called on write for database sessions. + * + * @param integer $id ID that uniquely identifies session in database + * @param mixed $data The value of the data to be saved. + * @return boolean True for successful write, false otherwise. + * @access private + */ + function __write($id, $data) { + if (!$id) { + return false; + } + $expires = time() + Configure::read('Session.timeout') * Security::inactiveMins(); + $model =& ClassRegistry::getObject('Session'); + $return = $model->save(array($model->primaryKey => $id) + compact('data', 'expires')); + return $return; + } + +/** + * Method called on the destruction of a database session. + * + * @param integer $id ID that uniquely identifies session in database + * @return boolean True for successful delete, false otherwise. + * @access private + */ + function __destroy($id) { + $model =& ClassRegistry::getObject('Session'); + $return = $model->delete($id); + + return $return; + } + +/** + * Helper function called on gc for database sessions. + * + * @param integer $expires Timestamp (defaults to current time) + * @return boolean Success + * @access private + */ + function __gc($expires = null) { + $model =& ClassRegistry::getObject('Session'); + + if (!$expires) { + $expires = time(); + } + + $return = $model->deleteAll(array($model->alias . ".expires <" => $expires), false, false); + return $return; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/cake_socket.php b/code/ryzom/tools/server/www/webtt/cake/libs/cake_socket.php new file mode 100644 index 000000000..00eff6e3c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/cake_socket.php @@ -0,0 +1,310 @@ + false, + 'host' => 'localhost', + 'protocol' => 'tcp', + 'port' => 80, + 'timeout' => 30 + ); + +/** + * Configuration settings for the socket connection + * + * @var array + * @access public + */ + var $config = array(); + +/** + * Reference to socket connection resource + * + * @var resource + * @access public + */ + var $connection = null; + +/** + * This boolean contains the current state of the CakeSocket class + * + * @var boolean + * @access public + */ + var $connected = false; + +/** + * This variable contains an array with the last error number (num) and string (str) + * + * @var array + * @access public + */ + var $lastError = array(); + +/** + * Constructor. + * + * @param array $config Socket configuration, which will be merged with the base configuration + * @see CakeSocket::$_baseConfig + */ + function __construct($config = array()) { + parent::__construct(); + + $this->config = array_merge($this->_baseConfig, $config); + if (!is_numeric($this->config['protocol'])) { + $this->config['protocol'] = getprotobyname($this->config['protocol']); + } + } + +/** + * Connect the socket to the given host and port. + * + * @return boolean Success + * @access public + */ + function connect() { + if ($this->connection != null) { + $this->disconnect(); + } + + $scheme = null; + if (isset($this->config['request']) && $this->config['request']['uri']['scheme'] == 'https') { + $scheme = 'ssl://'; + } + + if ($this->config['persistent'] == true) { + $tmp = null; + $this->connection = @pfsockopen($scheme.$this->config['host'], $this->config['port'], $errNum, $errStr, $this->config['timeout']); + } else { + $this->connection = @fsockopen($scheme.$this->config['host'], $this->config['port'], $errNum, $errStr, $this->config['timeout']); + } + + if (!empty($errNum) || !empty($errStr)) { + $this->setLastError($errNum, $errStr); + } + + $this->connected = is_resource($this->connection); + if ($this->connected) { + stream_set_timeout($this->connection, $this->config['timeout']); + } + return $this->connected; + } + +/** + * Get the host name of the current connection. + * + * @return string Host name + * @access public + */ + function host() { + if (Validation::ip($this->config['host'])) { + return gethostbyaddr($this->config['host']); + } else { + return gethostbyaddr($this->address()); + } + } + +/** + * Get the IP address of the current connection. + * + * @return string IP address + * @access public + */ + function address() { + if (Validation::ip($this->config['host'])) { + return $this->config['host']; + } else { + return gethostbyname($this->config['host']); + } + } + +/** + * Get all IP addresses associated with the current connection. + * + * @return array IP addresses + * @access public + */ + function addresses() { + if (Validation::ip($this->config['host'])) { + return array($this->config['host']); + } else { + return gethostbynamel($this->config['host']); + } + } + +/** + * Get the last error as a string. + * + * @return string Last error + * @access public + */ + function lastError() { + if (!empty($this->lastError)) { + return $this->lastError['num'] . ': ' . $this->lastError['str']; + } else { + return null; + } + } + +/** + * Set the last error. + * + * @param integer $errNum Error code + * @param string $errStr Error string + * @access public + */ + function setLastError($errNum, $errStr) { + $this->lastError = array('num' => $errNum, 'str' => $errStr); + } + +/** + * Write data to the socket. + * + * @param string $data The data to write to the socket + * @return boolean Success + * @access public + */ + function write($data) { + if (!$this->connected) { + if (!$this->connect()) { + return false; + } + } + $totalBytes = strlen($data); + for ($written = 0, $rv = 0; $written < $totalBytes; $written += $rv) { + $rv = fwrite($this->connection, substr($data, $written)); + if ($rv === false || $rv === 0) { + return $written; + } + } + return $written; + } + +/** + * Read data from the socket. Returns false if no data is available or no connection could be + * established. + * + * @param integer $length Optional buffer length to read; defaults to 1024 + * @return mixed Socket data + * @access public + */ + function read($length = 1024) { + if (!$this->connected) { + if (!$this->connect()) { + return false; + } + } + + if (!feof($this->connection)) { + $buffer = fread($this->connection, $length); + $info = stream_get_meta_data($this->connection); + if ($info['timed_out']) { + $this->setLastError(E_WARNING, __('Connection timed out', true)); + return false; + } + return $buffer; + } else { + return false; + } + } + +/** + * Abort socket operation. + * + * @return boolean Success + * @access public + */ + function abort() { + } + +/** + * Disconnect the socket from the current connection. + * + * @return boolean Success + * @access public + */ + function disconnect() { + if (!is_resource($this->connection)) { + $this->connected = false; + return true; + } + $this->connected = !fclose($this->connection); + + if (!$this->connected) { + $this->connection = null; + } + return !$this->connected; + } + +/** + * Destructor, used to disconnect from current connection. + * + * @access private + */ + function __destruct() { + $this->disconnect(); + } + +/** + * Resets the state of this Socket instance to it's initial state (before Object::__construct got executed) + * + * @return boolean True on success + * @access public + */ + function reset($state = null) { + if (empty($state)) { + static $initalState = array(); + if (empty($initalState)) { + $initalState = get_class_vars(__CLASS__); + } + $state = $initalState; + } + + foreach ($state as $property => $value) { + $this->{$property} = $value; + } + return true; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/class_registry.php b/code/ryzom/tools/server/www/webtt/cake/libs/class_registry.php new file mode 100644 index 000000000..0ab7f08b3 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/class_registry.php @@ -0,0 +1,365 @@ + 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'Model');``` + * + * Model Classes can accept optional ```array('id' => $id, 'table' => $table, 'ds' => $ds, 'alias' => $alias);``` + * + * When $class is a numeric keyed array, multiple class instances will be stored in the registry, + * no instance of the object will be returned + * {{{ + * array( + * array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'Model'), + * array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'Model'), + * array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'Model') + * ); + * }}} + * @param mixed $class as a string or a single key => value array instance will be created, + * stored in the registry and returned. + * @param string $type Only model is accepted as a valid value for $type. + * @return object instance of ClassName + * @access public + * @static + */ + function &init($class, $type = null) { + $_this =& ClassRegistry::getInstance(); + $id = $false = false; + $true = true; + + if (!$type) { + $type = 'Model'; + } + + if (is_array($class)) { + $objects = $class; + if (!isset($class[0])) { + $objects = array($class); + } + } else { + $objects = array(array('class' => $class)); + } + $defaults = isset($_this->__config[$type]) ? $_this->__config[$type] : array(); + $count = count($objects); + + foreach ($objects as $key => $settings) { + if (is_array($settings)) { + $pluginPath = null; + $settings = array_merge($defaults, $settings); + $class = $settings['class']; + + list($plugin, $class) = pluginSplit($class); + if ($plugin) { + $pluginPath = $plugin . '.'; + } + + if (empty($settings['alias'])) { + $settings['alias'] = $class; + } + $alias = $settings['alias']; + + if ($model =& $_this->__duplicate($alias, $class)) { + $_this->map($alias, $class); + return $model; + } + + if (class_exists($class) || App::import($type, $pluginPath . $class)) { + ${$class} =& new $class($settings); + } elseif ($type === 'Model') { + if ($plugin && class_exists($plugin . 'AppModel')) { + $appModel = $plugin . 'AppModel'; + } else { + $appModel = 'AppModel'; + } + $settings['name'] = $class; + ${$class} =& new $appModel($settings); + } + + if (!isset(${$class})) { + trigger_error(sprintf(__('(ClassRegistry::init() could not create instance of %1$s class %2$s ', true), $class, $type), E_USER_WARNING); + return $false; + } + + if ($type !== 'Model') { + $_this->addObject($alias, ${$class}); + } else { + $_this->map($alias, $class); + } + } elseif (is_numeric($settings)) { + trigger_error(__('(ClassRegistry::init() Attempted to create instance of a class with a numeric name', true), E_USER_WARNING); + return $false; + } + } + + if ($count > 1) { + return $true; + } + return ${$class}; + } + +/** + * Add $object to the registry, associating it with the name $key. + * + * @param string $key Key for the object in registry + * @param mixed $object Object to store + * @return boolean True if the object was written, false if $key already exists + * @access public + * @static + */ + function addObject($key, &$object) { + $_this =& ClassRegistry::getInstance(); + $key = Inflector::underscore($key); + if (!isset($_this->__objects[$key])) { + $_this->__objects[$key] =& $object; + return true; + } + return false; + } + +/** + * Remove object which corresponds to given key. + * + * @param string $key Key of object to remove from registry + * @return void + * @access public + * @static + */ + function removeObject($key) { + $_this =& ClassRegistry::getInstance(); + $key = Inflector::underscore($key); + if (isset($_this->__objects[$key])) { + unset($_this->__objects[$key]); + } + } + +/** + * Returns true if given key is present in the ClassRegistry. + * + * @param string $key Key to look for + * @return boolean true if key exists in registry, false otherwise + * @access public + * @static + */ + function isKeySet($key) { + $_this =& ClassRegistry::getInstance(); + $key = Inflector::underscore($key); + if (isset($_this->__objects[$key])) { + return true; + } elseif (isset($_this->__map[$key])) { + return true; + } + return false; + } + +/** + * Get all keys from the registry. + * + * @return array Set of keys stored in registry + * @access public + * @static + */ + function keys() { + $_this =& ClassRegistry::getInstance(); + return array_keys($_this->__objects); + } + +/** + * Return object which corresponds to given key. + * + * @param string $key Key of object to look for + * @return mixed Object stored in registry or boolean false if the object does not exist. + * @access public + * @static + */ + function &getObject($key) { + $_this =& ClassRegistry::getInstance(); + $key = Inflector::underscore($key); + $return = false; + if (isset($_this->__objects[$key])) { + $return =& $_this->__objects[$key]; + } else { + $key = $_this->__getMap($key); + if (isset($_this->__objects[$key])) { + $return =& $_this->__objects[$key]; + } + } + return $return; + } + +/** + * Sets the default constructor parameter for an object type + * + * @param string $type Type of object. If this parameter is omitted, defaults to "Model" + * @param array $param The parameter that will be passed to object constructors when objects + * of $type are created + * @return mixed Void if $param is being set. Otherwise, if only $type is passed, returns + * the previously-set value of $param, or null if not set. + * @access public + * @static + */ + function config($type, $param = array()) { + $_this =& ClassRegistry::getInstance(); + + if (empty($param) && is_array($type)) { + $param = $type; + $type = 'Model'; + } elseif (is_null($param)) { + unset($_this->__config[$type]); + } elseif (empty($param) && is_string($type)) { + return isset($_this->__config[$type]) ? $_this->__config[$type] : null; + } + $_this->__config[$type] = $param; + } + +/** + * Checks to see if $alias is a duplicate $class Object + * + * @param string $alias + * @param string $class + * @return boolean + * @access private + * @static + */ + function &__duplicate($alias, $class) { + $duplicate = false; + if ($this->isKeySet($alias)) { + $model =& $this->getObject($alias); + if (is_object($model) && (is_a($model, $class) || $model->alias === $class)) { + $duplicate =& $model; + } + unset($model); + } + return $duplicate; + } + +/** + * Add a key name pair to the registry to map name to class in the registry. + * + * @param string $key Key to include in map + * @param string $name Key that is being mapped + * @access public + * @static + */ + function map($key, $name) { + $_this =& ClassRegistry::getInstance(); + $key = Inflector::underscore($key); + $name = Inflector::underscore($name); + if (!isset($_this->__map[$key])) { + $_this->__map[$key] = $name; + } + } + +/** + * Get all keys from the map in the registry. + * + * @return array Keys of registry's map + * @access public + * @static + */ + function mapKeys() { + $_this =& ClassRegistry::getInstance(); + return array_keys($_this->__map); + } + +/** + * Return the name of a class in the registry. + * + * @param string $key Key to find in map + * @return string Mapped value + * @access private + * @static + */ + function __getMap($key) { + if (isset($this->__map[$key])) { + return $this->__map[$key]; + } + } + +/** + * Flushes all objects from the ClassRegistry. + * + * @return void + * @access public + * @static + */ + function flush() { + $_this =& ClassRegistry::getInstance(); + $_this->__objects = array(); + $_this->__map = array(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/configure.php b/code/ryzom/tools/server/www/webtt/cake/libs/configure.php new file mode 100644 index 000000000..e3e8e7835 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/configure.php @@ -0,0 +1,1320 @@ +__loadBootstrap($boot); + } + return $instance[0]; + } + +/** + * Used to store a dynamic variable in the Configure instance. + * + * Usage: + * {{{ + * Configure::write('One.key1', 'value of the Configure::One[key1]'); + * Configure::write(array('One.key1' => 'value of the Configure::One[key1]')); + * Configure::write('One', array( + * 'key1' => 'value of the Configure::One[key1]', + * 'key2' => 'value of the Configure::One[key2]' + * ); + * + * Configure::write(array( + * 'One.key1' => 'value of the Configure::One[key1]', + * 'One.key2' => 'value of the Configure::One[key2]' + * )); + * }}} + * + * @link http://book.cakephp.org/view/926/write + * @param array $config Name of var to write + * @param mixed $value Value to set for var + * @return boolean True if write was successful + * @access public + */ + function write($config, $value = null) { + $_this =& Configure::getInstance(); + + if (!is_array($config)) { + $config = array($config => $value); + } + + foreach ($config as $name => $value) { + if (strpos($name, '.') === false) { + $_this->{$name} = $value; + } else { + $names = explode('.', $name, 4); + switch (count($names)) { + case 2: + $_this->{$names[0]}[$names[1]] = $value; + break; + case 3: + $_this->{$names[0]}[$names[1]][$names[2]] = $value; + break; + case 4: + $names = explode('.', $name, 2); + if (!isset($_this->{$names[0]})) { + $_this->{$names[0]} = array(); + } + $_this->{$names[0]} = Set::insert($_this->{$names[0]}, $names[1], $value); + break; + } + } + } + + if (isset($config['debug']) || isset($config['log'])) { + $reporting = 0; + if ($_this->debug) { + if (!class_exists('Debugger')) { + require LIBS . 'debugger.php'; + } + $reporting = E_ALL & ~E_DEPRECATED; + if (function_exists('ini_set')) { + ini_set('display_errors', 1); + } + } elseif (function_exists('ini_set')) { + ini_set('display_errors', 0); + } + + if (isset($_this->log) && $_this->log) { + if (!class_exists('CakeLog')) { + require LIBS . 'cake_log.php'; + } + if (is_integer($_this->log) && !$_this->debug) { + $reporting = $_this->log; + } else { + $reporting = E_ALL & ~E_DEPRECATED; + } + } + error_reporting($reporting); + } + return true; + } + +/** + * Used to read information stored in the Configure instance. + * + * Usage: + * {{{ + * Configure::read('Name'); will return all values for Name + * Configure::read('Name.key'); will return only the value of Configure::Name[key] + * }}} + * + * @link http://book.cakephp.org/view/927/read + * @param string $var Variable to obtain. Use '.' to access array elements. + * @return string value of Configure::$var + * @access public + */ + function read($var = 'debug') { + $_this =& Configure::getInstance(); + + if ($var === 'debug') { + return $_this->debug; + } + + if (strpos($var, '.') !== false) { + $names = explode('.', $var, 3); + $var = $names[0]; + } + if (!isset($_this->{$var})) { + return null; + } + if (!isset($names[1])) { + return $_this->{$var}; + } + switch (count($names)) { + case 2: + if (isset($_this->{$var}[$names[1]])) { + return $_this->{$var}[$names[1]]; + } + break; + case 3: + if (isset($_this->{$var}[$names[1]][$names[2]])) { + return $_this->{$var}[$names[1]][$names[2]]; + } + if (!isset($_this->{$var}[$names[1]])) { + return null; + } + return Set::classicExtract($_this->{$var}[$names[1]], $names[2]); + break; + } + return null; + } + +/** + * Used to delete a variable from the Configure instance. + * + * Usage: + * {{{ + * Configure::delete('Name'); will delete the entire Configure::Name + * Configure::delete('Name.key'); will delete only the Configure::Name[key] + * }}} + * + * @link http://book.cakephp.org/view/928/delete + * @param string $var the var to be deleted + * @return void + * @access public + */ + function delete($var = null) { + $_this =& Configure::getInstance(); + + if (strpos($var, '.') === false) { + unset($_this->{$var}); + return; + } + + $names = explode('.', $var, 2); + $_this->{$names[0]} = Set::remove($_this->{$names[0]}, $names[1]); + } + +/** + * Loads a file from app/config/configure_file.php. + * Config file variables should be formated like: + * `$config['name'] = 'value';` + * These will be used to create dynamic Configure vars. load() is also used to + * load stored config files created with Configure::store() + * + * - To load config files from app/config use `Configure::load('configure_file');`. + * - To load config files from a plugin `Configure::load('plugin.configure_file');`. + * + * @link http://book.cakephp.org/view/929/load + * @param string $fileName name of file to load, extension must be .php and only the name + * should be used, not the extenstion + * @return mixed false if file not found, void if load successful + * @access public + */ + function load($fileName) { + $found = $plugin = $pluginPath = false; + list($plugin, $fileName) = pluginSplit($fileName); + if ($plugin) { + $pluginPath = App::pluginPath($plugin); + } + $pos = strpos($fileName, '..'); + + if ($pos === false) { + if ($pluginPath && file_exists($pluginPath . 'config' . DS . $fileName . '.php')) { + include($pluginPath . 'config' . DS . $fileName . '.php'); + $found = true; + } elseif (file_exists(CONFIGS . $fileName . '.php')) { + include(CONFIGS . $fileName . '.php'); + $found = true; + } elseif (file_exists(CACHE . 'persistent' . DS . $fileName . '.php')) { + include(CACHE . 'persistent' . DS . $fileName . '.php'); + $found = true; + } else { + foreach (App::core('cake') as $key => $path) { + if (file_exists($path . DS . 'config' . DS . $fileName . '.php')) { + include($path . DS . 'config' . DS . $fileName . '.php'); + $found = true; + break; + } + } + } + } + + if (!$found) { + return false; + } + + if (!isset($config)) { + trigger_error(sprintf(__('Configure::load() - no variable $config found in %s.php', true), $fileName), E_USER_WARNING); + return false; + } + return Configure::write($config); + } + +/** + * Used to determine the current version of CakePHP. + * + * Usage `Configure::version();` + * + * @link http://book.cakephp.org/view/930/version + * @return string Current version of CakePHP + * @access public + */ + function version() { + $_this =& Configure::getInstance(); + + if (!isset($_this->Cake['version'])) { + require(CORE_PATH . 'cake' . DS . 'config' . DS . 'config.php'); + $_this->write($config); + } + return $_this->Cake['version']; + } + +/** + * Used to write a config file to disk. + * + * {{{ + * Configure::store('Model', 'class_paths', array('Users' => array( + * 'path' => 'users', 'plugin' => true + * ))); + * }}} + * + * @param string $type Type of config file to write, ex: Models, Controllers, Helpers, Components + * @param string $name file name. + * @param array $data array of values to store. + * @return void + * @access public + */ + function store($type, $name, $data = array()) { + $write = true; + $content = ''; + + foreach ($data as $key => $value) { + $content .= "\$config['$type']['$key'] = " . var_export($value, true) . ";\n"; + } + if (is_null($type)) { + $write = false; + } + Configure::__writeConfig($content, $name, $write); + } + +/** + * Creates a cached version of a configuration file. + * Appends values passed from Configure::store() to the cached file + * + * @param string $content Content to write on file + * @param string $name Name to use for cache file + * @param boolean $write true if content should be written, false otherwise + * @return void + * @access private + */ + function __writeConfig($content, $name, $write = true) { + $file = CACHE . 'persistent' . DS . $name . '.php'; + + if (Configure::read() > 0) { + $expires = "+10 seconds"; + } else { + $expires = "+999 days"; + } + $cache = cache('persistent' . DS . $name . '.php', null, $expires); + + if ($cache === null) { + cache('persistent' . DS . $name . '.php', "writable()) { + $fileClass->append($content); + } + } + } + +/** + * @deprecated + * @see App::objects() + */ + function listObjects($type, $path = null, $cache = true) { + return App::objects($type, $path, $cache); + } + +/** + * @deprecated + * @see App::core() + */ + function corePaths($type = null) { + return App::core($type); + } + +/** + * @deprecated + * @see App::build() + */ + function buildPaths($paths) { + return App::build($paths); + } + +/** + * Loads app/config/bootstrap.php. + * If the alternative paths are set in this file + * they will be added to the paths vars. + * + * @param boolean $boot Load application bootstrap (if true) + * @return void + * @access private + */ + function __loadBootstrap($boot) { + if ($boot) { + Configure::write('App', array('base' => false, 'baseUrl' => false, 'dir' => APP_DIR, 'webroot' => WEBROOT_DIR, 'www_root' => WWW_ROOT)); + + if (!include(CONFIGS . 'core.php')) { + trigger_error(sprintf(__("Can't find application core file. Please create %score.php, and make sure it is readable by PHP.", true), CONFIGS), E_USER_ERROR); + } + + if (Configure::read('Cache.disable') !== true) { + $cache = Cache::config('default'); + + if (empty($cache['settings'])) { + trigger_error(__('Cache not configured properly. Please check Cache::config(); in APP/config/core.php', true), E_USER_WARNING); + $cache = Cache::config('default', array('engine' => 'File')); + } + $path = $prefix = $duration = null; + + if (!empty($cache['settings']['path'])) { + $path = realpath($cache['settings']['path']); + } else { + $prefix = $cache['settings']['prefix']; + } + + if (Configure::read() >= 1) { + $duration = '+10 seconds'; + } else { + $duration = '+999 days'; + } + + if (Cache::config('_cake_core_') === false) { + Cache::config('_cake_core_', array_merge((array)$cache['settings'], array( + 'prefix' => $prefix . 'cake_core_', 'path' => $path . DS . 'persistent' . DS, + 'serialize' => true, 'duration' => $duration + ))); + } + + if (Cache::config('_cake_model_') === false) { + Cache::config('_cake_model_', array_merge((array)$cache['settings'], array( + 'prefix' => $prefix . 'cake_model_', 'path' => $path . DS . 'models' . DS, + 'serialize' => true, 'duration' => $duration + ))); + } + Cache::config('default'); + } + App::build(); + if (!include(CONFIGS . 'bootstrap.php')) { + trigger_error(sprintf(__("Can't find application bootstrap file. Please create %sbootstrap.php, and make sure it is readable by PHP.", true), CONFIGS), E_USER_ERROR); + } + } + } +} + +/** + * Class/file loader and path management. + * + * @link http://book.cakephp.org/view/933/The-App-Class + * @since CakePHP(tm) v 1.2.0.6001 + * @package cake + * @subpackage cake.cake.libs + */ +class App extends Object { + +/** + * List of object types and their properties + * + * @var array + * @access public + */ + var $types = array( + 'class' => array('suffix' => '.php', 'extends' => null, 'core' => true), + 'file' => array('suffix' => '.php', 'extends' => null, 'core' => true), + 'model' => array('suffix' => '.php', 'extends' => 'AppModel', 'core' => false), + 'behavior' => array('suffix' => '.php', 'extends' => 'ModelBehavior', 'core' => true), + 'controller' => array('suffix' => '_controller.php', 'extends' => 'AppController', 'core' => true), + 'component' => array('suffix' => '.php', 'extends' => null, 'core' => true), + 'lib' => array('suffix' => '.php', 'extends' => null, 'core' => true), + 'view' => array('suffix' => '.php', 'extends' => null, 'core' => true), + 'helper' => array('suffix' => '.php', 'extends' => 'AppHelper', 'core' => true), + 'vendor' => array('suffix' => '', 'extends' => null, 'core' => true), + 'shell' => array('suffix' => '.php', 'extends' => 'Shell', 'core' => true), + 'plugin' => array('suffix' => '', 'extends' => null, 'core' => true) + ); + +/** + * List of additional path(s) where model files reside. + * + * @var array + * @access public + */ + var $models = array(); + +/** + * List of additional path(s) where behavior files reside. + * + * @var array + * @access public + */ + var $behaviors = array(); + +/** + * List of additional path(s) where controller files reside. + * + * @var array + * @access public + */ + var $controllers = array(); + +/** + * List of additional path(s) where component files reside. + * + * @var array + * @access public + */ + var $components = array(); + +/** + * List of additional path(s) where datasource files reside. + * + * @var array + * @access public + */ + var $datasources = array(); + +/** + * List of additional path(s) where libs files reside. + * + * @var array + * @access public + */ + var $libs = array(); +/** + * List of additional path(s) where view files reside. + * + * @var array + * @access public + */ + var $views = array(); + +/** + * List of additional path(s) where helper files reside. + * + * @var array + * @access public + */ + var $helpers = array(); + +/** + * List of additional path(s) where plugins reside. + * + * @var array + * @access public + */ + var $plugins = array(); + +/** + * List of additional path(s) where vendor packages reside. + * + * @var array + * @access public + */ + var $vendors = array(); + +/** + * List of additional path(s) where locale files reside. + * + * @var array + * @access public + */ + var $locales = array(); + +/** + * List of additional path(s) where console shell files reside. + * + * @var array + * @access public + */ + var $shells = array(); + +/** + * Paths to search for files. + * + * @var array + * @access public + */ + var $search = array(); + +/** + * Whether or not to return the file that is loaded. + * + * @var boolean + * @access public + */ + var $return = false; + +/** + * Holds key/value pairs of $type => file path. + * + * @var array + * @access private + */ + var $__map = array(); + +/** + * Holds paths for deep searching of files. + * + * @var array + * @access private + */ + var $__paths = array(); + +/** + * Holds loaded files. + * + * @var array + * @access private + */ + var $__loaded = array(); + +/** + * Holds and key => value array of object types. + * + * @var array + * @access private + */ + var $__objects = array(); + +/** + * Used to read information stored path + * + * Usage: + * + * `App::path('models'); will return all paths for models` + * + * @param string $type type of path + * @return string array + * @access public + */ + function path($type) { + $_this =& App::getInstance(); + if (!isset($_this->{$type})) { + return array(); + } + return $_this->{$type}; + } + +/** + * Build path references. Merges the supplied $paths + * with the base paths and the default core paths. + * + * @param array $paths paths defines in config/bootstrap.php + * @param boolean $reset true will set paths, false merges paths [default] false + * @return void + * @access public + */ + function build($paths = array(), $reset = false) { + $_this =& App::getInstance(); + $defaults = array( + 'models' => array(MODELS), + 'behaviors' => array(BEHAVIORS), + 'datasources' => array(MODELS . 'datasources'), + 'controllers' => array(CONTROLLERS), + 'components' => array(COMPONENTS), + 'libs' => array(APPLIBS), + 'views' => array(VIEWS), + 'helpers' => array(HELPERS), + 'locales' => array(APP . 'locale' . DS), + 'shells' => array(APP . 'vendors' . DS . 'shells' . DS, VENDORS . 'shells' . DS), + 'vendors' => array(APP . 'vendors' . DS, VENDORS), + 'plugins' => array(APP . 'plugins' . DS) + ); + + if ($reset == true) { + foreach ($paths as $type => $new) { + $_this->{$type} = (array)$new; + } + return $paths; + } + + $core = $_this->core(); + $app = array('models' => true, 'controllers' => true, 'helpers' => true); + + foreach ($defaults as $type => $default) { + $merge = array(); + + if (isset($app[$type])) { + $merge = array(APP); + } + if (isset($core[$type])) { + $merge = array_merge($merge, (array)$core[$type]); + } + + if (empty($_this->{$type}) || empty($paths)) { + $_this->{$type} = $default; + } + + if (!empty($paths[$type])) { + $path = array_flip(array_flip(array_merge( + (array)$paths[$type], $_this->{$type}, $merge + ))); + $_this->{$type} = array_values($path); + } else { + $path = array_flip(array_flip(array_merge($_this->{$type}, $merge))); + $_this->{$type} = array_values($path); + } + } + } + +/** + * Get the path that a plugin is on. Searches through the defined plugin paths. + * + * @param string $plugin CamelCased/lower_cased plugin name to find the path of. + * @return string full path to the plugin. + */ + function pluginPath($plugin) { + $_this =& App::getInstance(); + $pluginDir = Inflector::underscore($plugin); + for ($i = 0, $length = count($_this->plugins); $i < $length; $i++) { + if (is_dir($_this->plugins[$i] . $pluginDir)) { + return $_this->plugins[$i] . $pluginDir . DS ; + } + } + return $_this->plugins[0] . $pluginDir . DS; + } + +/** + * Find the path that a theme is on. Search through the defined theme paths. + * + * @param string $theme lower_cased theme name to find the path of. + * @return string full path to the theme. + */ + function themePath($theme) { + $_this =& App::getInstance(); + $themeDir = 'themed' . DS . Inflector::underscore($theme); + for ($i = 0, $length = count($_this->views); $i < $length; $i++) { + if (is_dir($_this->views[$i] . $themeDir)) { + return $_this->views[$i] . $themeDir . DS ; + } + } + return $_this->views[0] . $themeDir . DS; + } + +/** + * Returns a key/value list of all paths where core libs are found. + * Passing $type only returns the values for a given value of $key. + * + * @param string $type valid values are: 'model', 'behavior', 'controller', 'component', + * 'view', 'helper', 'datasource', 'libs', and 'cake' + * @return array numeric keyed array of core lib paths + * @access public + */ + function core($type = null) { + static $paths = false; + if ($paths === false) { + $paths = Cache::read('core_paths', '_cake_core_'); + } + if (!$paths) { + $paths = array(); + $libs = dirname(__FILE__) . DS; + $cake = dirname($libs) . DS; + $path = dirname($cake) . DS; + + $paths['cake'][] = $cake; + $paths['libs'][] = $libs; + $paths['models'][] = $libs . 'model' . DS; + $paths['datasources'][] = $libs . 'model' . DS . 'datasources' . DS; + $paths['behaviors'][] = $libs . 'model' . DS . 'behaviors' . DS; + $paths['controllers'][] = $libs . 'controller' . DS; + $paths['components'][] = $libs . 'controller' . DS . 'components' . DS; + $paths['views'][] = $libs . 'view' . DS; + $paths['helpers'][] = $libs . 'view' . DS . 'helpers' . DS; + $paths['plugins'][] = $path . 'plugins' . DS; + $paths['vendors'][] = $path . 'vendors' . DS; + $paths['shells'][] = $cake . 'console' . DS . 'libs' . DS; + + Cache::write('core_paths', array_filter($paths), '_cake_core_'); + } + if ($type && isset($paths[$type])) { + return $paths[$type]; + } + return $paths; + } + +/** + * Returns an array of objects of the given type. + * + * Example usage: + * + * `App::objects('plugin');` returns `array('DebugKit', 'Blog', 'User');` + * + * @param string $type Type of object, i.e. 'model', 'controller', 'helper', or 'plugin' + * @param mixed $path Optional Scan only the path given. If null, paths for the chosen + * type will be used. + * @param boolean $cache Set to false to rescan objects of the chosen type. Defaults to true. + * @return mixed Either false on incorrect / miss. Or an array of found objects. + * @access public + */ + function objects($type, $path = null, $cache = true) { + $objects = array(); + $extension = false; + $name = $type; + + if ($type === 'file' && !$path) { + return false; + } elseif ($type === 'file') { + $extension = true; + $name = $type . str_replace(DS, '', $path); + } + $_this =& App::getInstance(); + + if (empty($_this->__objects) && $cache === true) { + $_this->__objects = Cache::read('object_map', '_cake_core_'); + } + + if (!isset($_this->__objects[$name]) || $cache !== true) { + $types = $_this->types; + + if (!isset($types[$type])) { + return false; + } + $objects = array(); + + if (empty($path)) { + $path = $_this->{"{$type}s"}; + if (isset($types[$type]['core']) && $types[$type]['core'] === false) { + array_pop($path); + } + } + $items = array(); + + foreach ((array)$path as $dir) { + if ($dir != APP) { + $items = $_this->__list($dir, $types[$type]['suffix'], $extension); + $objects = array_merge($items, array_diff($objects, $items)); + } + } + + if ($type !== 'file') { + foreach ($objects as $key => $value) { + $objects[$key] = Inflector::camelize($value); + } + } + + if ($cache === true) { + $_this->__resetCache(true); + } + $_this->__objects[$name] = $objects; + } + + return $_this->__objects[$name]; + } + +/** + * Finds classes based on $name or specific file(s) to search. Calling App::import() will + * not construct any classes contained in the files. It will only find and require() the file. + * + * @link http://book.cakephp.org/view/934/Using-App-import + * @param mixed $type The type of Class if passed as a string, or all params can be passed as + * an single array to $type, + * @param string $name Name of the Class or a unique name for the file + * @param mixed $parent boolean true if Class Parent should be searched, accepts key => value + * array('parent' => $parent ,'file' => $file, 'search' => $search, 'ext' => '$ext'); + * $ext allows setting the extension of the file name + * based on Inflector::underscore($name) . ".$ext"; + * @param array $search paths to search for files, array('path 1', 'path 2', 'path 3'); + * @param string $file full name of the file to search for including extension + * @param boolean $return, return the loaded file, the file must have a return + * statement in it to work: return $variable; + * @return boolean true if Class is already in memory or if file is found and loaded, false if not + * @access public + */ + function import($type = null, $name = null, $parent = true, $search = array(), $file = null, $return = false) { + $plugin = $directory = null; + + if (is_array($type)) { + extract($type, EXTR_OVERWRITE); + } + + if (is_array($parent)) { + extract($parent, EXTR_OVERWRITE); + } + + if ($name === null && $file === null) { + $name = $type; + $type = 'Core'; + } elseif ($name === null) { + $type = 'File'; + } + + if (is_array($name)) { + foreach ($name as $class) { + $tempType = $type; + $plugin = null; + + if (strpos($class, '.') !== false) { + $value = explode('.', $class); + $count = count($value); + + if ($count > 2) { + $tempType = $value[0]; + $plugin = $value[1] . '.'; + $class = $value[2]; + } elseif ($count === 2 && ($type === 'Core' || $type === 'File')) { + $tempType = $value[0]; + $class = $value[1]; + } else { + $plugin = $value[0] . '.'; + $class = $value[1]; + } + } + + if (!App::import($tempType, $plugin . $class, $parent)) { + return false; + } + } + return true; + } + + if ($name != null && strpos($name, '.') !== false) { + list($plugin, $name) = explode('.', $name); + $plugin = Inflector::camelize($plugin); + } + $_this =& App::getInstance(); + $_this->return = $return; + + if (isset($ext)) { + $file = Inflector::underscore($name) . ".{$ext}"; + } + $ext = $_this->__settings($type, $plugin, $parent); + if ($name != null && !class_exists($name . $ext['class'])) { + if ($load = $_this->__mapped($name . $ext['class'], $type, $plugin)) { + if ($_this->__load($load)) { + $_this->__overload($type, $name . $ext['class'], $parent); + + if ($_this->return) { + return include($load); + } + return true; + } else { + $_this->__remove($name . $ext['class'], $type, $plugin); + $_this->__resetCache(true); + } + } + if (!empty($search)) { + $_this->search = $search; + } elseif ($plugin) { + $_this->search = $_this->__paths('plugin'); + } else { + $_this->search = $_this->__paths($type); + } + $find = $file; + + if ($find === null) { + $find = Inflector::underscore($name . $ext['suffix']).'.php'; + + if ($plugin) { + $paths = $_this->search; + foreach ($paths as $key => $value) { + $_this->search[$key] = $value . $ext['path']; + } + } + } + + if (strtolower($type) !== 'vendor' && empty($search) && $_this->__load($file)) { + $directory = false; + } else { + $file = $find; + $directory = $_this->__find($find, true); + } + + if ($directory !== null) { + $_this->__resetCache(true); + $_this->__map($directory . $file, $name . $ext['class'], $type, $plugin); + $_this->__overload($type, $name . $ext['class'], $parent); + + if ($_this->return) { + return include($directory . $file); + } + return true; + } + return false; + } + return true; + } + +/** + * Returns a single instance of App. + * + * @return object + * @access public + */ + function &getInstance() { + static $instance = array(); + if (!$instance) { + $instance[0] =& new App(); + $instance[0]->__map = (array)Cache::read('file_map', '_cake_core_'); + } + return $instance[0]; + } + +/** + * Locates the $file in $__paths, searches recursively. + * + * @param string $file full file name + * @param boolean $recursive search $__paths recursively + * @return mixed boolean on fail, $file directory path on success + * @access private + */ + function __find($file, $recursive = true) { + static $appPath = false; + + if (empty($this->search)) { + return null; + } elseif (is_string($this->search)) { + $this->search = array($this->search); + } + + if (empty($this->__paths)) { + $this->__paths = Cache::read('dir_map', '_cake_core_'); + } + + foreach ($this->search as $path) { + if ($appPath === false) { + $appPath = rtrim(APP, DS); + } + $path = rtrim($path, DS); + + if ($path === $appPath) { + $recursive = false; + } + if ($recursive === false) { + if ($this->__load($path . DS . $file)) { + return $path . DS; + } + continue; + } + + if (!isset($this->__paths[$path])) { + if (!class_exists('Folder')) { + require LIBS . 'folder.php'; + } + $Folder =& new Folder(); + $directories = $Folder->tree($path, array('.svn', '.git', 'CVS', 'tests', 'templates'), 'dir'); + sort($directories); + $this->__paths[$path] = $directories; + } + + foreach ($this->__paths[$path] as $directory) { + if ($this->__load($directory . DS . $file)) { + return $directory . DS; + } + } + } + return null; + } + +/** + * Attempts to load $file. + * + * @param string $file full path to file including file name + * @return boolean + * @access private + */ + function __load($file) { + if (empty($file)) { + return false; + } + if (!$this->return && isset($this->__loaded[$file])) { + return true; + } + if (file_exists($file)) { + if (!$this->return) { + require($file); + $this->__loaded[$file] = true; + } + return true; + } + return false; + } + +/** + * Maps the $name to the $file. + * + * @param string $file full path to file + * @param string $name unique name for this map + * @param string $type type object being mapped + * @param string $plugin camelized if object is from a plugin, the name of the plugin + * @return void + * @access private + */ + function __map($file, $name, $type, $plugin) { + if ($plugin) { + $this->__map['Plugin'][$plugin][$type][$name] = $file; + } else { + $this->__map[$type][$name] = $file; + } + } + +/** + * Returns a file's complete path. + * + * @param string $name unique name + * @param string $type type object + * @param string $plugin camelized if object is from a plugin, the name of the plugin + * @return mixed, file path if found, false otherwise + * @access private + */ + function __mapped($name, $type, $plugin) { + if ($plugin) { + if (isset($this->__map['Plugin'][$plugin][$type]) && isset($this->__map['Plugin'][$plugin][$type][$name])) { + return $this->__map['Plugin'][$plugin][$type][$name]; + } + return false; + } + + if (isset($this->__map[$type]) && isset($this->__map[$type][$name])) { + return $this->__map[$type][$name]; + } + return false; + } + +/** + * Used to overload objects as needed. + * + * @param string $type Model or Helper + * @param string $name Class name to overload + * @access private + */ + function __overload($type, $name, $parent) { + if (($type === 'Model' || $type === 'Helper') && $parent !== false) { + Overloadable::overload($name); + } + } + +/** + * Loads parent classes based on $type. + * Returns a prefix or suffix needed for loading files. + * + * @param string $type type of object + * @param string $plugin camelized name of plugin + * @param boolean $parent false will not attempt to load parent + * @return array + * @access private + */ + function __settings($type, $plugin, $parent) { + if (!$parent) { + return array('class' => null, 'suffix' => null, 'path' => null); + } + + if ($plugin) { + $pluginPath = Inflector::underscore($plugin); + } + $path = null; + $load = strtolower($type); + + switch ($load) { + case 'model': + if (!class_exists('Model')) { + require LIBS . 'model' . DS . 'model.php'; + } + if (!class_exists('AppModel')) { + App::import($type, 'AppModel', false); + } + if ($plugin) { + if (!class_exists($plugin . 'AppModel')) { + App::import($type, $plugin . '.' . $plugin . 'AppModel', false, array(), $pluginPath . DS . $pluginPath . '_app_model.php'); + } + $path = $pluginPath . DS . 'models' . DS; + } + return array('class' => null, 'suffix' => null, 'path' => $path); + break; + case 'behavior': + if ($plugin) { + $path = $pluginPath . DS . 'models' . DS . 'behaviors' . DS; + } + return array('class' => $type, 'suffix' => null, 'path' => $path); + break; + case 'datasource': + if ($plugin) { + $path = $pluginPath . DS . 'models' . DS . 'datasources' . DS; + } + return array('class' => $type, 'suffix' => null, 'path' => $path); + case 'controller': + App::import($type, 'AppController', false); + if ($plugin) { + App::import($type, $plugin . '.' . $plugin . 'AppController', false, array(), $pluginPath . DS . $pluginPath . '_app_controller.php'); + $path = $pluginPath . DS . 'controllers' . DS; + } + return array('class' => $type, 'suffix' => $type, 'path' => $path); + break; + case 'component': + if ($plugin) { + $path = $pluginPath . DS . 'controllers' . DS . 'components' . DS; + } + return array('class' => $type, 'suffix' => null, 'path' => $path); + break; + case 'lib': + if ($plugin) { + $path = $pluginPath . DS . 'libs' . DS; + } + return array('class' => null, 'suffix' => null, 'path' => $path); + break; + case 'view': + if ($plugin) { + $path = $pluginPath . DS . 'views' . DS; + } + return array('class' => $type, 'suffix' => null, 'path' => $path); + break; + case 'helper': + if (!class_exists('AppHelper')) { + App::import($type, 'AppHelper', false); + } + if ($plugin) { + $path = $pluginPath . DS . 'views' . DS . 'helpers' . DS; + } + return array('class' => $type, 'suffix' => null, 'path' => $path); + break; + case 'vendor': + if ($plugin) { + $path = $pluginPath . DS . 'vendors' . DS; + } + return array('class' => null, 'suffix' => null, 'path' => $path); + break; + default: + $type = $suffix = $path = null; + break; + } + return array('class' => null, 'suffix' => null, 'path' => null); + } + +/** + * Returns default search paths. + * + * @param string $type type of object to be searched + * @return array list of paths + * @access private + */ + function __paths($type) { + $type = strtolower($type); + $paths = array(); + + if ($type === 'core') { + return App::core('libs'); + } + if (isset($this->{$type . 's'})) { + return $this->{$type . 's'}; + } + return $paths; + } + +/** + * Removes file location from map if the file has been deleted. + * + * @param string $name name of object + * @param string $type type of object + * @param string $plugin camelized name of plugin + * @return void + * @access private + */ + function __remove($name, $type, $plugin) { + if ($plugin) { + unset($this->__map['Plugin'][$plugin][$type][$name]); + } else { + unset($this->__map[$type][$name]); + } + } + +/** + * Returns an array of filenames of PHP files in the given directory. + * + * @param string $path Path to scan for files + * @param string $suffix if false, return only directories. if string, match and return files + * @return array List of directories or files in directory + * @access private + */ + function __list($path, $suffix = false, $extension = false) { + if (!class_exists('Folder')) { + require LIBS . 'folder.php'; + } + $items = array(); + $Folder =& new Folder($path); + $contents = $Folder->read(false, true); + + if (is_array($contents)) { + if (!$suffix) { + return $contents[0]; + } else { + foreach ($contents[1] as $item) { + if (substr($item, - strlen($suffix)) === $suffix) { + if ($extension) { + $items[] = $item; + } else { + $items[] = substr($item, 0, strlen($item) - strlen($suffix)); + } + } + } + } + } + return $items; + } + +/** + * Determines if $__maps, $__objects and $__paths cache should be reset. + * + * @param boolean $reset + * @return boolean + * @access private + */ + function __resetCache($reset = null) { + static $cache = array(); + if (!$cache && $reset === true) { + $cache = true; + } + return $cache; + } + +/** + * Object destructor. + * + * Writes cache file if changes have been made to the $__map or $__paths + * + * @return void + * @access private + */ + function __destruct() { + if ($this->__resetCache() === true) { + $core = App::core('cake'); + unset($this->__paths[rtrim($core[0], DS)]); + Cache::write('dir_map', array_filter($this->__paths), '_cake_core_'); + Cache::write('file_map', array_filter($this->__map), '_cake_core_'); + Cache::write('object_map', $this->__objects, '_cake_core_'); + } + } +} \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/app_controller.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/app_controller.php new file mode 100644 index 000000000..073c6405e --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/app_controller.php @@ -0,0 +1,36 @@ + null, 'name' => null, 'base' => null); + +/** + * List of loaded components. + * + * @var object + * @access protected + */ + var $_loaded = array(); + +/** + * List of components attached directly to the controller, which callbacks + * should be executed on. + * + * @var object + * @access protected + */ + var $_primary = array(); + +/** + * Settings for loaded components. + * + * @var array + * @access private + */ + var $__settings = array(); + +/** + * Used to initialize the components for current controller. + * + * @param object $controller Controller with components to load + * @return void + * @access public + */ + function init(&$controller) { + if (!is_array($controller->components)) { + return; + } + $this->__controllerVars = array( + 'plugin' => $controller->plugin, 'name' => $controller->name, + 'base' => $controller->base + ); + + $this->_loadComponents($controller); + } + +/** + * Called before the Controller::beforeFilter(). + * + * @param object $controller Controller with components to initialize + * @return void + * @access public + * @link http://book.cakephp.org/view/998/MVC-Class-Access-Within-Components + */ + function initialize(&$controller) { + foreach (array_keys($this->_loaded) as $name) { + $component =& $this->_loaded[$name]; + + if (method_exists($component,'initialize') && $component->enabled === true) { + $settings = array(); + if (isset($this->__settings[$name])) { + $settings = $this->__settings[$name]; + } + $component->initialize($controller, $settings); + } + } + } + +/** + * Called after the Controller::beforeFilter() and before the controller action + * + * @param object $controller Controller with components to startup + * @return void + * @access public + * @link http://book.cakephp.org/view/998/MVC-Class-Access-Within-Components + * @deprecated See Component::triggerCallback() + */ + function startup(&$controller) { + $this->triggerCallback('startup', $controller); + } + +/** + * Called after the Controller::beforeRender(), after the view class is loaded, and before the + * Controller::render() + * + * @param object $controller Controller with components to beforeRender + * @return void + * @access public + * @deprecated See Component::triggerCallback() + */ + function beforeRender(&$controller) { + $this->triggerCallback('beforeRender', $controller); + } + +/** + * Called before Controller::redirect(). + * + * @param object $controller Controller with components to beforeRedirect + * @return void + * @access public + */ + function beforeRedirect(&$controller, $url, $status = null, $exit = true) { + $response = array(); + + foreach ($this->_primary as $name) { + $component =& $this->_loaded[$name]; + + if ($component->enabled === true && method_exists($component, 'beforeRedirect')) { + $resp = $component->beforeRedirect($controller, $url, $status, $exit); + if ($resp === false) { + return false; + } + $response[] = $resp; + } + } + return $response; + } + +/** + * Called after Controller::render() and before the output is printed to the browser. + * + * @param object $controller Controller with components to shutdown + * @return void + * @access public + * @deprecated See Component::triggerCallback() + */ + function shutdown(&$controller) { + $this->triggerCallback('shutdown', $controller); + } + +/** + * Trigger a callback on all primary components. Will fire $callback on all components + * that have such a method. You can implement and fire custom callbacks in addition to the + * standard ones. + * + * example use, from inside a controller: + * + * `$this->Component->triggerCallback('beforeFilter', $this);` + * + * will trigger the beforeFilter callback on all components that have implemented one. You + * can trigger any method in this fashion. + * + * @param Controller $controller Controller instance + * @param string $callback Callback to trigger. + * @return void + * @access public + */ + function triggerCallback($callback, &$controller) { + foreach ($this->_primary as $name) { + $component =& $this->_loaded[$name]; + if (method_exists($component, $callback) && $component->enabled === true) { + $component->{$callback}($controller); + } + } + } + +/** + * Loads components used by this component. + * + * @param object $object Object with a Components array + * @param object $parent the parent of the current object + * @return void + * @access protected + */ + function _loadComponents(&$object, $parent = null) { + $base = $this->__controllerVars['base']; + $normal = Set::normalize($object->components); + foreach ((array)$normal as $component => $config) { + $plugin = isset($this->__controllerVars['plugin']) ? $this->__controllerVars['plugin'] . '.' : null; + list($plugin, $component) = pluginSplit($component, true, $plugin); + $componentCn = $component . 'Component'; + + if (!class_exists($componentCn)) { + if (is_null($plugin) || !App::import('Component', $plugin . $component)) { + if (!App::import('Component', $component)) { + $this->cakeError('missingComponentFile', array(array( + 'className' => $this->__controllerVars['name'], + 'component' => $component, + 'file' => Inflector::underscore($component) . '.php', + 'base' => $base, + 'code' => 500 + ))); + return false; + } + } + + if (!class_exists($componentCn)) { + $this->cakeError('missingComponentClass', array(array( + 'className' => $this->__controllerVars['name'], + 'component' => $component, + 'file' => Inflector::underscore($component) . '.php', + 'base' => $base, + 'code' => 500 + ))); + return false; + } + } + + if ($parent === null) { + $this->_primary[] = $component; + } + + if (isset($this->_loaded[$component])) { + $object->{$component} =& $this->_loaded[$component]; + + if (!empty($config) && isset($this->__settings[$component])) { + $this->__settings[$component] = array_merge($this->__settings[$component], $config); + } elseif (!empty($config)) { + $this->__settings[$component] = $config; + } + } else { + if ($componentCn === 'SessionComponent') { + $object->{$component} =& new $componentCn($base); + } else { + if (PHP5) { + $object->{$component} = new $componentCn(); + } else { + $object->{$component} =& new $componentCn(); + } + } + $object->{$component}->enabled = true; + $this->_loaded[$component] =& $object->{$component}; + if (!empty($config)) { + $this->__settings[$component] = $config; + } + } + + if (isset($object->{$component}->components) && is_array($object->{$component}->components) && (!isset($object->{$component}->{$parent}))) { + $this->_loadComponents($object->{$component}, $component); + } + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/acl.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/acl.php new file mode 100644 index 000000000..2e5407739 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/acl.php @@ -0,0 +1,638 @@ +_Instance =& new $name(); + $this->_Instance->initialize($this); + } + +/** + * Startup is not used + * + * @param object $controller Controller using this component + * @return boolean Proceed with component usage (true), or fail (false) + * @access public + */ + function startup(&$controller) { + return true; + } + +/** + * Empty class defintion, to be overridden in subclasses. + * + * @access protected + */ + function _initACL() { + } + +/** + * Pass-thru function for ACL check instance. Check methods + * are used to check whether or not an ARO can access an ACO + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $action Action (defaults to *) + * @return boolean Success + * @access public + */ + function check($aro, $aco, $action = "*") { + return $this->_Instance->check($aro, $aco, $action); + } + +/** + * Pass-thru function for ACL allow instance. Allow methods + * are used to grant an ARO access to an ACO. + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $action Action (defaults to *) + * @return boolean Success + * @access public + */ + function allow($aro, $aco, $action = "*") { + return $this->_Instance->allow($aro, $aco, $action); + } + +/** + * Pass-thru function for ACL deny instance. Deny methods + * are used to remove permission from an ARO to access an ACO. + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $action Action (defaults to *) + * @return boolean Success + * @access public + */ + function deny($aro, $aco, $action = "*") { + return $this->_Instance->deny($aro, $aco, $action); + } + +/** + * Pass-thru function for ACL inherit instance. Inherit methods + * modify the permission for an ARO to be that of its parent object. + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $action Action (defaults to *) + * @return boolean Success + * @access public + */ + function inherit($aro, $aco, $action = "*") { + return $this->_Instance->inherit($aro, $aco, $action); + } + +/** + * Pass-thru function for ACL grant instance. An alias for AclComponent::allow() + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $action Action (defaults to *) + * @return boolean Success + * @access public + */ + function grant($aro, $aco, $action = "*") { + return $this->_Instance->grant($aro, $aco, $action); + } + +/** + * Pass-thru function for ACL grant instance. An alias for AclComponent::deny() + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $action Action (defaults to *) + * @return boolean Success + * @access public + */ + function revoke($aro, $aco, $action = "*") { + return $this->_Instance->revoke($aro, $aco, $action); + } +} + +/** + * Access Control List abstract class. Not to be instantiated. + * Subclasses of this class are used by AclComponent to perform ACL checks in Cake. + * + * @package cake + * @subpackage cake.cake.libs.controller.components + * @abstract + */ +class AclBase extends Object { + +/** + * This class should never be instantiated, just subclassed. + * + */ + function __construct() { + if (strcasecmp(get_class($this), "AclBase") == 0 || !is_subclass_of($this, "AclBase")) { + trigger_error(__("[acl_base] The AclBase class constructor has been called, or the class was instantiated. This class must remain abstract. Please refer to the Cake docs for ACL configuration.", true), E_USER_ERROR); + return NULL; + } + } + +/** + * Empty method to be overridden in subclasses + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $action Action (defaults to *) + * @access public + */ + function check($aro, $aco, $action = "*") { + } + +/** + * Empty method to be overridden in subclasses + * + * @param object $component Component + * @access public + */ + function initialize(&$component) { + } +} + +/** + * DbAcl implements an ACL control system in the database. ARO's and ACO's are + * structured into trees and a linking table is used to define permissions. You + * can install the schema for DbAcl with the Schema Shell. + * + * `$aco` and `$aro` parameters can be slash delimited paths to tree nodes. + * + * eg. `controllers/Users/edit` + * + * Would point to a tree structure like + * + * {{{ + * controllers + * Users + * edit + * }}} + * + * @package cake + * @subpackage cake.cake.libs.model + */ +class DbAcl extends AclBase { + +/** + * Constructor + * + */ + function __construct() { + parent::__construct(); + if (!class_exists('AclNode')) { + require LIBS . 'model' . DS . 'db_acl.php'; + } + $this->Aro =& ClassRegistry::init(array('class' => 'Aro', 'alias' => 'Aro')); + $this->Aco =& ClassRegistry::init(array('class' => 'Aco', 'alias' => 'Aco')); + } + +/** + * Initializes the containing component and sets the Aro/Aco objects to it. + * + * @param AclComponent $component + * @return void + * @access public + */ + function initialize(&$component) { + $component->Aro =& $this->Aro; + $component->Aco =& $this->Aco; + } + +/** + * Checks if the given $aro has access to action $action in $aco + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $action Action (defaults to *) + * @return boolean Success (true if ARO has access to action in ACO, false otherwise) + * @access public + * @link http://book.cakephp.org/view/1249/Checking-Permissions-The-ACL-Component + */ + function check($aro, $aco, $action = "*") { + if ($aro == null || $aco == null) { + return false; + } + + $permKeys = $this->_getAcoKeys($this->Aro->Permission->schema()); + $aroPath = $this->Aro->node($aro); + $acoPath = $this->Aco->node($aco); + + if (empty($aroPath) || empty($acoPath)) { + trigger_error(__("DbAcl::check() - Failed ARO/ACO node lookup in permissions check. Node references:\nAro: ", true) . print_r($aro, true) . "\nAco: " . print_r($aco, true), E_USER_WARNING); + return false; + } + + if ($acoPath == null || $acoPath == array()) { + trigger_error(__("DbAcl::check() - Failed ACO node lookup in permissions check. Node references:\nAro: ", true) . print_r($aro, true) . "\nAco: " . print_r($aco, true), E_USER_WARNING); + return false; + } + + $aroNode = $aroPath[0]; + $acoNode = $acoPath[0]; + + if ($action != '*' && !in_array('_' . $action, $permKeys)) { + trigger_error(sprintf(__("ACO permissions key %s does not exist in DbAcl::check()", true), $action), E_USER_NOTICE); + return false; + } + + $inherited = array(); + $acoIDs = Set::extract($acoPath, '{n}.' . $this->Aco->alias . '.id'); + + $count = count($aroPath); + for ($i = 0 ; $i < $count; $i++) { + $permAlias = $this->Aro->Permission->alias; + + $perms = $this->Aro->Permission->find('all', array( + 'conditions' => array( + "{$permAlias}.aro_id" => $aroPath[$i][$this->Aro->alias]['id'], + "{$permAlias}.aco_id" => $acoIDs + ), + 'order' => array($this->Aco->alias . '.lft' => 'desc'), + 'recursive' => 0 + )); + + if (empty($perms)) { + continue; + } else { + $perms = Set::extract($perms, '{n}.' . $this->Aro->Permission->alias); + foreach ($perms as $perm) { + if ($action == '*') { + + foreach ($permKeys as $key) { + if (!empty($perm)) { + if ($perm[$key] == -1) { + return false; + } elseif ($perm[$key] == 1) { + $inherited[$key] = 1; + } + } + } + + if (count($inherited) === count($permKeys)) { + return true; + } + } else { + switch ($perm['_' . $action]) { + case -1: + return false; + case 0: + continue; + break; + case 1: + return true; + break; + } + } + } + } + } + return false; + } + +/** + * Allow $aro to have access to action $actions in $aco + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $actions Action (defaults to *) + * @param integer $value Value to indicate access type (1 to give access, -1 to deny, 0 to inherit) + * @return boolean Success + * @access public + * @link http://book.cakephp.org/view/1248/Assigning-Permissions + */ + function allow($aro, $aco, $actions = "*", $value = 1) { + $perms = $this->getAclLink($aro, $aco); + $permKeys = $this->_getAcoKeys($this->Aro->Permission->schema()); + $save = array(); + + if ($perms == false) { + trigger_error(__('DbAcl::allow() - Invalid node', true), E_USER_WARNING); + return false; + } + if (isset($perms[0])) { + $save = $perms[0][$this->Aro->Permission->alias]; + } + + if ($actions == "*") { + $permKeys = $this->_getAcoKeys($this->Aro->Permission->schema()); + $save = array_combine($permKeys, array_pad(array(), count($permKeys), $value)); + } else { + if (!is_array($actions)) { + $actions = array('_' . $actions); + } + if (is_array($actions)) { + foreach ($actions as $action) { + if ($action{0} != '_') { + $action = '_' . $action; + } + if (in_array($action, $permKeys)) { + $save[$action] = $value; + } + } + } + } + list($save['aro_id'], $save['aco_id']) = array($perms['aro'], $perms['aco']); + + if ($perms['link'] != null && !empty($perms['link'])) { + $save['id'] = $perms['link'][0][$this->Aro->Permission->alias]['id']; + } else { + unset($save['id']); + $this->Aro->Permission->id = null; + } + return ($this->Aro->Permission->save($save) !== false); + } + +/** + * Deny access for $aro to action $action in $aco + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $actions Action (defaults to *) + * @return boolean Success + * @access public + * @link http://book.cakephp.org/view/1248/Assigning-Permissions + */ + function deny($aro, $aco, $action = "*") { + return $this->allow($aro, $aco, $action, -1); + } + +/** + * Let access for $aro to action $action in $aco be inherited + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $actions Action (defaults to *) + * @return boolean Success + * @access public + */ + function inherit($aro, $aco, $action = "*") { + return $this->allow($aro, $aco, $action, 0); + } + +/** + * Allow $aro to have access to action $actions in $aco + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $actions Action (defaults to *) + * @return boolean Success + * @see allow() + * @access public + */ + function grant($aro, $aco, $action = "*") { + return $this->allow($aro, $aco, $action); + } + +/** + * Deny access for $aro to action $action in $aco + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @param string $actions Action (defaults to *) + * @return boolean Success + * @see deny() + * @access public + */ + function revoke($aro, $aco, $action = "*") { + return $this->deny($aro, $aco, $action); + } + +/** + * Get an array of access-control links between the given Aro and Aco + * + * @param string $aro ARO The requesting object identifier. + * @param string $aco ACO The controlled object identifier. + * @return array Indexed array with: 'aro', 'aco' and 'link' + * @access public + */ + function getAclLink($aro, $aco) { + $obj = array(); + $obj['Aro'] = $this->Aro->node($aro); + $obj['Aco'] = $this->Aco->node($aco); + + if (empty($obj['Aro']) || empty($obj['Aco'])) { + return false; + } + + return array( + 'aro' => Set::extract($obj, 'Aro.0.'.$this->Aro->alias.'.id'), + 'aco' => Set::extract($obj, 'Aco.0.'.$this->Aco->alias.'.id'), + 'link' => $this->Aro->Permission->find('all', array('conditions' => array( + $this->Aro->Permission->alias . '.aro_id' => Set::extract($obj, 'Aro.0.'.$this->Aro->alias.'.id'), + $this->Aro->Permission->alias . '.aco_id' => Set::extract($obj, 'Aco.0.'.$this->Aco->alias.'.id') + ))) + ); + } + +/** + * Get the keys used in an ACO + * + * @param array $keys Permission model info + * @return array ACO keys + * @access protected + */ + function _getAcoKeys($keys) { + $newKeys = array(); + $keys = array_keys($keys); + foreach ($keys as $key) { + if (!in_array($key, array('id', 'aro_id', 'aco_id'))) { + $newKeys[] = $key; + } + } + return $newKeys; + } +} + +/** + * IniAcl implements an access control system using an INI file. An example + * of the ini file used can be found in /config/acl.ini.php. + * + * @package cake + * @subpackage cake.cake.libs.model.iniacl + */ +class IniAcl extends AclBase { + +/** + * Array with configuration, parsed from ini file + * + * @var array + * @access public + */ + var $config = null; + +/** + * The constructor must be overridden, as AclBase is abstract. + * + */ + function __construct() { + } + +/** + * Main ACL check function. Checks to see if the ARO (access request object) has access to the + * ACO (access control object).Looks at the acl.ini.php file for permissions + * (see instructions in /config/acl.ini.php). + * + * @param string $aro ARO + * @param string $aco ACO + * @param string $aco_action Action + * @return boolean Success + * @access public + */ + function check($aro, $aco, $aco_action = null) { + if ($this->config == null) { + $this->config = $this->readConfigFile(CONFIGS . 'acl.ini.php'); + } + $aclConfig = $this->config; + + if (isset($aclConfig[$aro]['deny'])) { + $userDenies = $this->arrayTrim(explode(",", $aclConfig[$aro]['deny'])); + + if (array_search($aco, $userDenies)) { + return false; + } + } + + if (isset($aclConfig[$aro]['allow'])) { + $userAllows = $this->arrayTrim(explode(",", $aclConfig[$aro]['allow'])); + + if (array_search($aco, $userAllows)) { + return true; + } + } + + if (isset($aclConfig[$aro]['groups'])) { + $userGroups = $this->arrayTrim(explode(",", $aclConfig[$aro]['groups'])); + + foreach ($userGroups as $group) { + if (array_key_exists($group, $aclConfig)) { + if (isset($aclConfig[$group]['deny'])) { + $groupDenies=$this->arrayTrim(explode(",", $aclConfig[$group]['deny'])); + + if (array_search($aco, $groupDenies)) { + return false; + } + } + + if (isset($aclConfig[$group]['allow'])) { + $groupAllows = $this->arrayTrim(explode(",", $aclConfig[$group]['allow'])); + + if (array_search($aco, $groupAllows)) { + return true; + } + } + } + } + } + return false; + } + +/** + * Parses an INI file and returns an array that reflects the INI file's section structure. Double-quote friendly. + * + * @param string $fileName File + * @return array INI section structure + * @access public + */ + function readConfigFile($fileName) { + $fileLineArray = file($fileName); + + foreach ($fileLineArray as $fileLine) { + $dataLine = trim($fileLine); + $firstChar = substr($dataLine, 0, 1); + + if ($firstChar != ';' && $dataLine != '') { + if ($firstChar == '[' && substr($dataLine, -1, 1) == ']') { + $sectionName = preg_replace('/[\[\]]/', '', $dataLine); + } else { + $delimiter = strpos($dataLine, '='); + + if ($delimiter > 0) { + $key = strtolower(trim(substr($dataLine, 0, $delimiter))); + $value = trim(substr($dataLine, $delimiter + 1)); + + if (substr($value, 0, 1) == '"' && substr($value, -1) == '"') { + $value = substr($value, 1, -1); + } + + $iniSetting[$sectionName][$key]=stripcslashes($value); + } else { + if (!isset($sectionName)) { + $sectionName = ''; + } + + $iniSetting[$sectionName][strtolower(trim($dataLine))]=''; + } + } + } + } + + return $iniSetting; + } + +/** + * Removes trailing spaces on all array elements (to prepare for searching) + * + * @param array $array Array to trim + * @return array Trimmed array + * @access public + */ + function arrayTrim($array) { + foreach ($array as $key => $value) { + $array[$key] = trim($value); + } + array_unshift($array, ""); + return $array; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/auth.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/auth.php new file mode 100644 index 000000000..b619bd814 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/auth.php @@ -0,0 +1,959 @@ + 'name'); will validate mapActions against model $name::isAuthorized(user, controller, mapAction) + * 'object' will validate Controller::action against object::isAuthorized(user, controller, action) + * + * @var mixed + * @access public + * @link http://book.cakephp.org/view/1275/authorize + */ + var $authorize = false; + +/** + * The name of an optional view element to render when an Ajax request is made + * with an invalid or expired session + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1277/ajaxLogin + */ + var $ajaxLogin = null; + +/** + * The name of the element used for SessionComponent::setFlash + * + * @var string + * @access public + */ + var $flashElement = 'default'; + +/** + * The name of the model that represents users which will be authenticated. Defaults to 'User'. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1266/userModel + */ + var $userModel = 'User'; + +/** + * Additional query conditions to use when looking up and authenticating users, + * i.e. array('User.is_active' => 1). + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1268/userScope + */ + var $userScope = array(); + +/** + * Allows you to specify non-default login name and password fields used in + * $userModel, i.e. array('username' => 'login_name', 'password' => 'passwd'). + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1267/fields + */ + var $fields = array('username' => 'username', 'password' => 'password'); + +/** + * The session key name where the record of the current user is stored. If + * unspecified, it will be "Auth.{$userModel name}". + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1276/sessionKey + */ + var $sessionKey = null; + +/** + * If using action-based access control, this defines how the paths to action + * ACO nodes is computed. If, for example, all controller nodes are nested + * under an ACO node named 'Controllers', $actionPath should be set to + * "Controllers/". + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1279/actionPath + */ + var $actionPath = null; + +/** + * A URL (defined as a string or array) to the controller action that handles + * logins. + * + * @var mixed + * @access public + * @link http://book.cakephp.org/view/1269/loginAction + */ + var $loginAction = null; + +/** + * Normally, if a user is redirected to the $loginAction page, the location they + * were redirected from will be stored in the session so that they can be + * redirected back after a successful login. If this session value is not + * set, the user will be redirected to the page specified in $loginRedirect. + * + * @var mixed + * @access public + * @link http://book.cakephp.org/view/1270/loginRedirect + */ + var $loginRedirect = null; + +/** + * The default action to redirect to after the user is logged out. While AuthComponent does + * not handle post-logout redirection, a redirect URL will be returned from AuthComponent::logout(). + * Defaults to AuthComponent::$loginAction. + * + * @var mixed + * @access public + * @see AuthComponent::$loginAction + * @see AuthComponent::logout() + * @link http://book.cakephp.org/view/1271/logoutRedirect + */ + var $logoutRedirect = null; + +/** + * The name of model or model object, or any other object has an isAuthorized method. + * + * @var string + * @access public + */ + var $object = null; + +/** + * Error to display when user login fails. For security purposes, only one error is used for all + * login failures, so as not to expose information on why the login failed. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1272/loginError + */ + var $loginError = null; + +/** + * Error to display when user attempts to access an object or action to which they do not have + * acccess. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1273/authError + */ + var $authError = null; + +/** + * Determines whether AuthComponent will automatically redirect and exit if login is successful. + * + * @var boolean + * @access public + * @link http://book.cakephp.org/view/1274/autoRedirect + */ + var $autoRedirect = true; + +/** + * Controller actions for which user validation is not required. + * + * @var array + * @access public + * @see AuthComponent::allow() + * @link http://book.cakephp.org/view/1251/Setting-Auth-Component-Variables + */ + var $allowedActions = array(); + +/** + * Maps actions to CRUD operations. Used for controller-based validation ($validate = 'controller'). + * + * @var array + * @access public + * @see AuthComponent::mapActions() + */ + var $actionMap = array( + 'index' => 'read', + 'add' => 'create', + 'edit' => 'update', + 'view' => 'read', + 'remove' => 'delete' + ); + +/** + * Form data from Controller::$data + * + * @var array + * @access public + */ + var $data = array(); + +/** + * Parameter data from Controller::$params + * + * @var array + * @access public + */ + var $params = array(); + +/** + * Method list for bound controller + * + * @var array + * @access protected + */ + var $_methods = array(); + +/** + * Initializes AuthComponent for use in the controller + * + * @param object $controller A reference to the instantiating controller object + * @return void + * @access public + */ + function initialize(&$controller, $settings = array()) { + $this->params = $controller->params; + $crud = array('create', 'read', 'update', 'delete'); + $this->actionMap = array_merge($this->actionMap, array_combine($crud, $crud)); + $this->_methods = $controller->methods; + + $prefixes = Router::prefixes(); + if (!empty($prefixes)) { + foreach ($prefixes as $prefix) { + $this->actionMap = array_merge($this->actionMap, array( + $prefix . '_index' => 'read', + $prefix . '_add' => 'create', + $prefix . '_edit' => 'update', + $prefix . '_view' => 'read', + $prefix . '_remove' => 'delete', + $prefix . '_create' => 'create', + $prefix . '_read' => 'read', + $prefix . '_update' => 'update', + $prefix . '_delete' => 'delete' + )); + } + } + $this->_set($settings); + if (Configure::read() > 0) { + App::import('Debugger'); + Debugger::checkSecurityKeys(); + } + } + +/** + * Main execution method. Handles redirecting of invalid users, and processing + * of login form data. + * + * @param object $controller A reference to the instantiating controller object + * @return boolean + * @access public + */ + function startup(&$controller) { + $isErrorOrTests = ( + strtolower($controller->name) == 'cakeerror' || + (strtolower($controller->name) == 'tests' && Configure::read() > 0) + ); + if ($isErrorOrTests) { + return true; + } + + $methods = array_flip($controller->methods); + $action = strtolower($controller->params['action']); + $isMissingAction = ( + $controller->scaffold === false && + !isset($methods[$action]) + ); + + if ($isMissingAction) { + return true; + } + + if (!$this->__setDefaults()) { + return false; + } + + $this->data = $controller->data = $this->hashPasswords($controller->data); + $url = ''; + + if (isset($controller->params['url']['url'])) { + $url = $controller->params['url']['url']; + } + $url = Router::normalize($url); + $loginAction = Router::normalize($this->loginAction); + + $allowedActions = array_map('strtolower', $this->allowedActions); + $isAllowed = ( + $this->allowedActions == array('*') || + in_array($action, $allowedActions) + ); + + if ($loginAction != $url && $isAllowed) { + return true; + } + + if ($loginAction == $url) { + $model =& $this->getModel(); + if (empty($controller->data) || !isset($controller->data[$model->alias])) { + if (!$this->Session->check('Auth.redirect') && !$this->loginRedirect && env('HTTP_REFERER')) { + $this->Session->write('Auth.redirect', $controller->referer(null, true)); + } + return false; + } + + $isValid = !empty($controller->data[$model->alias][$this->fields['username']]) && + !empty($controller->data[$model->alias][$this->fields['password']]); + + if ($isValid) { + $username = $controller->data[$model->alias][$this->fields['username']]; + $password = $controller->data[$model->alias][$this->fields['password']]; + + $data = array( + $model->alias . '.' . $this->fields['username'] => $username, + $model->alias . '.' . $this->fields['password'] => $password + ); + + if ($this->login($data)) { + if ($this->autoRedirect) { + $controller->redirect($this->redirect(), null, true); + } + return true; + } + } + + $this->Session->setFlash($this->loginError, $this->flashElement, array(), 'auth'); + $controller->data[$model->alias][$this->fields['password']] = null; + return false; + } else { + $user = $this->user(); + if (!$user) { + if (!$this->RequestHandler->isAjax()) { + $this->Session->setFlash($this->authError, $this->flashElement, array(), 'auth'); + if (!empty($controller->params['url']) && count($controller->params['url']) >= 2) { + $query = $controller->params['url']; + unset($query['url'], $query['ext']); + $url .= Router::queryString($query, array()); + } + $this->Session->write('Auth.redirect', $url); + $controller->redirect($loginAction); + return false; + } elseif (!empty($this->ajaxLogin)) { + $controller->viewPath = 'elements'; + echo $controller->render($this->ajaxLogin, $this->RequestHandler->ajaxLayout); + $this->_stop(); + return false; + } else { + $controller->redirect(null, 403); + } + } + } + + if (!$this->authorize) { + return true; + } + + extract($this->__authType()); + switch ($type) { + case 'controller': + $this->object =& $controller; + break; + case 'crud': + case 'actions': + if (isset($controller->Acl)) { + $this->Acl =& $controller->Acl; + } else { + trigger_error(__('Could not find AclComponent. Please include Acl in Controller::$components.', true), E_USER_WARNING); + } + break; + case 'model': + if (!isset($object)) { + $hasModel = ( + isset($controller->{$controller->modelClass}) && + is_object($controller->{$controller->modelClass}) + ); + $isUses = ( + !empty($controller->uses) && isset($controller->{$controller->uses[0]}) && + is_object($controller->{$controller->uses[0]}) + ); + + if ($hasModel) { + $object = $controller->modelClass; + } elseif ($isUses) { + $object = $controller->uses[0]; + } + } + $type = array('model' => $object); + break; + } + + if ($this->isAuthorized($type, null, $user)) { + return true; + } + + $this->Session->setFlash($this->authError, $this->flashElement, array(), 'auth'); + $controller->redirect($controller->referer(), null, true); + return false; + } + +/** + * Attempts to introspect the correct values for object properties including + * $userModel and $sessionKey. + * + * @param object $controller A reference to the instantiating controller object + * @return boolean + * @access private + */ + function __setDefaults() { + if (empty($this->userModel)) { + trigger_error(__("Could not find \$userModel. Please set AuthComponent::\$userModel in beforeFilter().", true), E_USER_WARNING); + return false; + } + list($plugin, $model) = pluginSplit($this->userModel); + $defaults = array( + 'loginAction' => array( + 'controller' => Inflector::underscore(Inflector::pluralize($model)), + 'action' => 'login', + 'plugin' => Inflector::underscore($plugin), + ), + 'sessionKey' => 'Auth.' . $model, + 'logoutRedirect' => $this->loginAction, + 'loginError' => __('Login failed. Invalid username or password.', true), + 'authError' => __('You are not authorized to access that location.', true) + ); + foreach ($defaults as $key => $value) { + if (empty($this->{$key})) { + $this->{$key} = $value; + } + } + return true; + } + +/** + * Determines whether the given user is authorized to perform an action. The type of + * authorization used is based on the value of AuthComponent::$authorize or the + * passed $type param. + * + * Types: + * 'controller' will validate against Controller::isAuthorized() if controller instance is + * passed in $object + * 'actions' will validate Controller::action against an AclComponent::check() + * 'crud' will validate mapActions against an AclComponent::check() + * array('model'=> 'name'); will validate mapActions against model + * $name::isAuthorized(user, controller, mapAction) + * 'object' will validate Controller::action against + * object::isAuthorized(user, controller, action) + * + * @param string $type Type of authorization + * @param mixed $object object, model object, or model name + * @param mixed $user The user to check the authorization of + * @return boolean True if $user is authorized, otherwise false + * @access public + */ + function isAuthorized($type = null, $object = null, $user = null) { + if (empty($user) && !$this->user()) { + return false; + } elseif (empty($user)) { + $user = $this->user(); + } + + extract($this->__authType($type)); + + if (!$object) { + $object = $this->object; + } + + $valid = false; + switch ($type) { + case 'controller': + $valid = $object->isAuthorized(); + break; + case 'actions': + $valid = $this->Acl->check($user, $this->action()); + break; + case 'crud': + if (!isset($this->actionMap[$this->params['action']])) { + trigger_error( + sprintf(__('Auth::startup() - Attempted access of un-mapped action "%1$s" in controller "%2$s"', true), $this->params['action'], $this->params['controller']), + E_USER_WARNING + ); + } else { + $valid = $this->Acl->check( + $user, + $this->action(':controller'), + $this->actionMap[$this->params['action']] + ); + } + break; + case 'model': + $action = $this->params['action']; + if (isset($this->actionMap[$action])) { + $action = $this->actionMap[$action]; + } + if (is_string($object)) { + $object = $this->getModel($object); + } + case 'object': + if (!isset($action)) { + $action = $this->action(':action'); + } + if (empty($object)) { + trigger_error(sprintf(__('Could not find %s. Set AuthComponent::$object in beforeFilter() or pass a valid object', true), get_class($object)), E_USER_WARNING); + return; + } + if (method_exists($object, 'isAuthorized')) { + $valid = $object->isAuthorized($user, $this->action(':controller'), $action); + } elseif ($object) { + trigger_error(sprintf(__('%s::isAuthorized() is not defined.', true), get_class($object)), E_USER_WARNING); + } + break; + case null: + case false: + return true; + break; + default: + trigger_error(__('Auth::isAuthorized() - $authorize is set to an incorrect value. Allowed settings are: "actions", "crud", "model" or null.', true), E_USER_WARNING); + break; + } + return $valid; + } + +/** + * Get authorization type + * + * @param string $auth Type of authorization + * @return array Associative array with: type, object + * @access private + */ + function __authType($auth = null) { + if ($auth == null) { + $auth = $this->authorize; + } + $object = null; + if (is_array($auth)) { + $type = key($auth); + $object = $auth[$type]; + } else { + $type = $auth; + return compact('type'); + } + return compact('type', 'object'); + } + +/** + * Takes a list of actions in the current controller for which authentication is not required, or + * no parameters to allow all actions. + * + * @param mixed $action Controller action name or array of actions + * @param string $action Controller action name + * @param string ... etc. + * @return void + * @access public + * @link http://book.cakephp.org/view/1257/allow + */ + function allow() { + $args = func_get_args(); + if (empty($args) || $args == array('*')) { + $this->allowedActions = $this->_methods; + } else { + if (isset($args[0]) && is_array($args[0])) { + $args = $args[0]; + } + $this->allowedActions = array_merge($this->allowedActions, array_map('strtolower', $args)); + } + } + +/** + * Removes items from the list of allowed actions. + * + * @param mixed $action Controller action name or array of actions + * @param string $action Controller action name + * @param string ... etc. + * @return void + * @see AuthComponent::allow() + * @access public + * @link http://book.cakephp.org/view/1258/deny + */ + function deny() { + $args = func_get_args(); + if (isset($args[0]) && is_array($args[0])) { + $args = $args[0]; + } + foreach ($args as $arg) { + $i = array_search(strtolower($arg), $this->allowedActions); + if (is_int($i)) { + unset($this->allowedActions[$i]); + } + } + $this->allowedActions = array_values($this->allowedActions); + } + +/** + * Maps action names to CRUD operations. Used for controller-based authentication. + * + * @param array $map Actions to map + * @return void + * @access public + * @link http://book.cakephp.org/view/1260/mapActions + */ + function mapActions($map = array()) { + $crud = array('create', 'read', 'update', 'delete'); + foreach ($map as $action => $type) { + if (in_array($action, $crud) && is_array($type)) { + foreach ($type as $typedAction) { + $this->actionMap[$typedAction] = $action; + } + } else { + $this->actionMap[$action] = $type; + } + } + } + +/** + * Manually log-in a user with the given parameter data. The $data provided can be any data + * structure used to identify a user in AuthComponent::identify(). If $data is empty or not + * specified, POST data from Controller::$data will be used automatically. + * + * After (if) login is successful, the user record is written to the session key specified in + * AuthComponent::$sessionKey. + * + * @param mixed $data User object + * @return boolean True on login success, false on failure + * @access public + * @link http://book.cakephp.org/view/1261/login + */ + function login($data = null) { + $this->__setDefaults(); + $this->_loggedIn = false; + + if (empty($data)) { + $data = $this->data; + } + + if ($user = $this->identify($data)) { + $this->Session->write($this->sessionKey, $user); + $this->_loggedIn = true; + } + return $this->_loggedIn; + } + +/** + * Logs a user out, and returns the login action to redirect to. + * + * @param mixed $url Optional URL to redirect the user to after logout + * @return string AuthComponent::$loginAction + * @see AuthComponent::$loginAction + * @access public + * @link http://book.cakephp.org/view/1262/logout + */ + function logout() { + $this->__setDefaults(); + $this->Session->delete($this->sessionKey); + $this->Session->delete('Auth.redirect'); + $this->_loggedIn = false; + return Router::normalize($this->logoutRedirect); + } + +/** + * Get the current user from the session. + * + * @param string $key field to retrive. Leave null to get entire User record + * @return mixed User record. or null if no user is logged in. + * @access public + * @link http://book.cakephp.org/view/1264/user + */ + function user($key = null) { + $this->__setDefaults(); + if (!$this->Session->check($this->sessionKey)) { + return null; + } + + if ($key == null) { + $model =& $this->getModel(); + return array($model->alias => $this->Session->read($this->sessionKey)); + } else { + $user = $this->Session->read($this->sessionKey); + if (isset($user[$key])) { + return $user[$key]; + } + return null; + } + } + +/** + * If no parameter is passed, gets the authentication redirect URL. + * + * @param mixed $url Optional URL to write as the login redirect URL. + * @return string Redirect URL + * @access public + */ + function redirect($url = null) { + if (!is_null($url)) { + $redir = $url; + $this->Session->write('Auth.redirect', $redir); + } elseif ($this->Session->check('Auth.redirect')) { + $redir = $this->Session->read('Auth.redirect'); + $this->Session->delete('Auth.redirect'); + + if (Router::normalize($redir) == Router::normalize($this->loginAction)) { + $redir = $this->loginRedirect; + } + } else { + $redir = $this->loginRedirect; + } + return Router::normalize($redir); + } + +/** + * Validates a user against an abstract object. + * + * @param mixed $object The object to validate the user against. + * @param mixed $user Optional. The identity of the user to be validated. + * Uses the current user session if none specified. For + * valid forms of identifying users, see + * AuthComponent::identify(). + * @param string $action Optional. The action to validate against. + * @see AuthComponent::identify() + * @return boolean True if the user validates, false otherwise. + * @access public + */ + function validate($object, $user = null, $action = null) { + if (empty($user)) { + $user = $this->user(); + } + if (empty($user)) { + return false; + } + return $this->Acl->check($user, $object, $action); + } + +/** + * Returns the path to the ACO node bound to a controller/action. + * + * @param string $action Optional. The controller/action path to validate the + * user against. The current request action is used if + * none is specified. + * @return boolean ACO node path + * @access public + * @link http://book.cakephp.org/view/1256/action + */ + function action($action = ':plugin/:controller/:action') { + $plugin = empty($this->params['plugin']) ? null : Inflector::camelize($this->params['plugin']) . '/'; + return str_replace( + array(':controller', ':action', ':plugin/'), + array(Inflector::camelize($this->params['controller']), $this->params['action'], $plugin), + $this->actionPath . $action + ); + } + +/** + * Returns a reference to the model object specified, and attempts + * to load it if it is not found. + * + * @param string $name Model name (defaults to AuthComponent::$userModel) + * @return object A reference to a model object + * @access public + */ + function &getModel($name = null) { + $model = null; + if (!$name) { + $name = $this->userModel; + } + + if (PHP5) { + $model = ClassRegistry::init($name); + } else { + $model =& ClassRegistry::init($name); + } + + if (empty($model)) { + trigger_error(__('Auth::getModel() - Model is not set or could not be found', true), E_USER_WARNING); + return null; + } + + return $model; + } + +/** + * Identifies a user based on specific criteria. + * + * @param mixed $user Optional. The identity of the user to be validated. + * Uses the current user session if none specified. + * @param array $conditions Optional. Additional conditions to a find. + * @return array User record data, or null, if the user could not be identified. + * @access public + */ + function identify($user = null, $conditions = null) { + if ($conditions === false) { + $conditions = array(); + } elseif (is_array($conditions)) { + $conditions = array_merge((array)$this->userScope, $conditions); + } else { + $conditions = $this->userScope; + } + $model =& $this->getModel(); + if (empty($user)) { + $user = $this->user(); + if (empty($user)) { + return null; + } + } elseif (is_object($user) && is_a($user, 'Model')) { + if (!$user->exists()) { + return null; + } + $user = $user->read(); + $user = $user[$model->alias]; + } elseif (is_array($user) && isset($user[$model->alias])) { + $user = $user[$model->alias]; + } + + if (is_array($user) && (isset($user[$this->fields['username']]) || isset($user[$model->alias . '.' . $this->fields['username']]))) { + if (isset($user[$this->fields['username']]) && !empty($user[$this->fields['username']]) && !empty($user[$this->fields['password']])) { + if (trim($user[$this->fields['username']]) == '=' || trim($user[$this->fields['password']]) == '=') { + return false; + } + $find = array( + $model->alias.'.'.$this->fields['username'] => $user[$this->fields['username']], + $model->alias.'.'.$this->fields['password'] => $user[$this->fields['password']] + ); + } elseif (isset($user[$model->alias . '.' . $this->fields['username']]) && !empty($user[$model->alias . '.' . $this->fields['username']])) { + if (trim($user[$model->alias . '.' . $this->fields['username']]) == '=' || trim($user[$model->alias . '.' . $this->fields['password']]) == '=') { + return false; + } + $find = array( + $model->alias.'.'.$this->fields['username'] => $user[$model->alias . '.' . $this->fields['username']], + $model->alias.'.'.$this->fields['password'] => $user[$model->alias . '.' . $this->fields['password']] + ); + } else { + return false; + } + $data = $model->find('first', array( + 'conditions' => array_merge($find, $conditions), + 'recursive' => 0 + )); + if (empty($data) || empty($data[$model->alias])) { + return null; + } + } elseif (!empty($user) && is_string($user)) { + $data = $model->find('first', array( + 'conditions' => array_merge(array($model->escapeField() => $user), $conditions), + )); + if (empty($data) || empty($data[$model->alias])) { + return null; + } + } + + if (!empty($data)) { + if (!empty($data[$model->alias][$this->fields['password']])) { + unset($data[$model->alias][$this->fields['password']]); + } + return $data[$model->alias]; + } + return null; + } + +/** + * Hash any passwords found in $data using $userModel and $fields['password'] + * + * @param array $data Set of data to look for passwords + * @return array Data with passwords hashed + * @access public + * @link http://book.cakephp.org/view/1259/hashPasswords + */ + function hashPasswords($data) { + if (is_object($this->authenticate) && method_exists($this->authenticate, 'hashPasswords')) { + return $this->authenticate->hashPasswords($data); + } + + if (is_array($data)) { + $model =& $this->getModel(); + + if(isset($data[$model->alias])) { + if (isset($data[$model->alias][$this->fields['username']]) && isset($data[$model->alias][$this->fields['password']])) { + $data[$model->alias][$this->fields['password']] = $this->password($data[$model->alias][$this->fields['password']]); + } + } + } + return $data; + } + +/** + * Hash a password with the application's salt value (as defined with Configure::write('Security.salt'); + * + * @param string $password Password to hash + * @return string Hashed password + * @access public + * @link http://book.cakephp.org/view/1263/password + */ + function password($password) { + return Security::hash($password, null, true); + } + +/** + * Component shutdown. If user is logged in, wipe out redirect. + * + * @param object $controller Instantiating controller + * @access public + */ + function shutdown(&$controller) { + if ($this->_loggedIn) { + $this->Session->delete('Auth.redirect'); + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/cookie.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/cookie.php new file mode 100644 index 000000000..b2ca100df --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/cookie.php @@ -0,0 +1,480 @@ +Cookie->name = 'CookieName'; + * + * @var string + * @access public + */ + var $name = 'CakeCookie'; + +/** + * The time a cookie will remain valid. + * + * Can be either integer Unix timestamp or a date string. + * + * Overridden with the controller beforeFilter(); + * $this->Cookie->time = '5 Days'; + * + * @var mixed + * @access public + */ + var $time = null; + +/** + * Cookie path. + * + * Overridden with the controller beforeFilter(); + * $this->Cookie->path = '/'; + * + * The path on the server in which the cookie will be available on. + * If var $cookiePath is set to '/foo/', the cookie will only be available + * within the /foo/ directory and all sub-directories such as /foo/bar/ of domain. + * The default value is the entire domain. + * + * @var string + * @access public + */ + var $path = '/'; + +/** + * Domain path. + * + * The domain that the cookie is available. + * + * Overridden with the controller beforeFilter(); + * $this->Cookie->domain = '.example.com'; + * + * To make the cookie available on all subdomains of example.com. + * Set $this->Cookie->domain = '.example.com'; in your controller beforeFilter + * + * @var string + * @access public + */ + var $domain = ''; + +/** + * Secure HTTPS only cookie. + * + * Overridden with the controller beforeFilter(); + * $this->Cookie->secure = true; + * + * Indicates that the cookie should only be transmitted over a secure HTTPS connection. + * When set to true, the cookie will only be set if a secure connection exists. + * + * @var boolean + * @access public + */ + var $secure = false; + +/** + * Encryption key. + * + * Overridden with the controller beforeFilter(); + * $this->Cookie->key = 'SomeRandomString'; + * + * @var string + * @access protected + */ + var $key = null; + +/** + * Values stored in the cookie. + * + * Accessed in the controller using $this->Cookie->read('Name.key'); + * + * @see CookieComponent::read(); + * @var string + * @access private + */ + var $__values = array(); + +/** + * Type of encryption to use. + * + * Currently only one method is available + * Defaults to Security::cipher(); + * + * @var string + * @access private + * @todo add additional encryption methods + */ + var $__type = 'cipher'; + +/** + * Used to reset cookie time if $expire is passed to CookieComponent::write() + * + * @var string + * @access private + */ + var $__reset = null; + +/** + * Expire time of the cookie + * + * This is controlled by CookieComponent::time; + * + * @var string + * @access private + */ + var $__expires = 0; + +/** + * Main execution method. + * + * @param object $controller A reference to the instantiating controller object + * @access public + */ + function initialize(&$controller, $settings) { + $this->key = Configure::read('Security.salt'); + $this->_set($settings); + if (isset($this->time)) { + $this->__expire($this->time); + } + } + +/** + * Start CookieComponent for use in the controller + * + * @access public + */ + function startup() { + $this->__expire($this->time); + + if (isset($_COOKIE[$this->name])) { + $this->__values = $this->__decrypt($_COOKIE[$this->name]); + } + } + +/** + * Write a value to the $_COOKIE[$key]; + * + * Optional [Name.], required key, optional $value, optional $encrypt, optional $expires + * $this->Cookie->write('[Name.]key, $value); + * + * By default all values are encrypted. + * You must pass $encrypt false to store values in clear test + * + * You must use this method before any output is sent to the browser. + * Failure to do so will result in header already sent errors. + * + * @param mixed $key Key for the value + * @param mixed $value Value + * @param boolean $encrypt Set to true to encrypt value, false otherwise + * @param string $expires Can be either Unix timestamp, or date string + * @access public + */ + function write($key, $value = null, $encrypt = true, $expires = null) { + if (is_null($encrypt)) { + $encrypt = true; + } + $this->__encrypted = $encrypt; + $this->__expire($expires); + + if (!is_array($key)) { + $key = array($key => $value); + } + + foreach ($key as $name => $value) { + if (strpos($name, '.') === false) { + $this->__values[$name] = $value; + $this->__write("[$name]", $value); + + } else { + $names = explode('.', $name, 2); + if (!isset($this->__values[$names[0]])) { + $this->__values[$names[0]] = array(); + } + $this->__values[$names[0]] = Set::insert($this->__values[$names[0]], $names[1], $value); + $this->__write('[' . implode('][', $names) . ']', $value); + } + } + $this->__encrypted = true; + } + +/** + * Read the value of the $_COOKIE[$key]; + * + * Optional [Name.], required key + * $this->Cookie->read(Name.key); + * + * @param mixed $key Key of the value to be obtained. If none specified, obtain map key => values + * @return string or null, value for specified key + * @access public + */ + function read($key = null) { + if (empty($this->__values) && isset($_COOKIE[$this->name])) { + $this->__values = $this->__decrypt($_COOKIE[$this->name]); + } + + if (is_null($key)) { + return $this->__values; + } + + if (strpos($key, '.') !== false) { + $names = explode('.', $key, 2); + $key = $names[0]; + } + if (!isset($this->__values[$key])) { + return null; + } + + if (!empty($names[1])) { + return Set::extract($this->__values[$key], $names[1]); + } + return $this->__values[$key]; + } + +/** + * Delete a cookie value + * + * Optional [Name.], required key + * $this->Cookie->read('Name.key); + * + * You must use this method before any output is sent to the browser. + * Failure to do so will result in header already sent errors. + * + * @param string $key Key of the value to be deleted + * @return void + * @access public + */ + function delete($key) { + if (empty($this->__values)) { + $this->read(); + } + if (strpos($key, '.') === false) { + if (isset($this->__values[$key]) && is_array($this->__values[$key])) { + foreach ($this->__values[$key] as $idx => $val) { + $this->__delete("[$key][$idx]"); + } + } else { + $this->__delete("[$key]"); + } + unset($this->__values[$key]); + return; + } + $names = explode('.', $key, 2); + $this->__values[$names[0]] = Set::remove($this->__values[$names[0]], $names[1]); + $this->__delete('[' . implode('][', $names) . ']'); + } + +/** + * Destroy current cookie + * + * You must use this method before any output is sent to the browser. + * Failure to do so will result in header already sent errors. + * + * @return void + * @access public + */ + function destroy() { + if (isset($_COOKIE[$this->name])) { + $this->__values = $this->__decrypt($_COOKIE[$this->name]); + } + + foreach ($this->__values as $name => $value) { + if (is_array($value)) { + foreach ($value as $key => $val) { + unset($this->__values[$name][$key]); + $this->__delete("[$name][$key]"); + } + } + unset($this->__values[$name]); + $this->__delete("[$name]"); + } + } + +/** + * Will allow overriding default encryption method. + * + * @param string $type Encryption method + * @access public + * @todo NOT IMPLEMENTED + */ + function type($type = 'cipher') { + $this->__type = 'cipher'; + } + +/** + * Set the expire time for a session variable. + * + * Creates a new expire time for a session variable. + * $expire can be either integer Unix timestamp or a date string. + * + * Used by write() + * CookieComponent::write(string, string, boolean, 8400); + * CookieComponent::write(string, string, boolean, '5 Days'); + * + * @param mixed $expires Can be either Unix timestamp, or date string + * @return int Unix timestamp + * @access private + */ + function __expire($expires = null) { + $now = time(); + if (is_null($expires)) { + return $this->__expires; + } + $this->__reset = $this->__expires; + + if ($expires == 0) { + return $this->__expires = 0; + } + + if (is_integer($expires) || is_numeric($expires)) { + return $this->__expires = $now + intval($expires); + } + return $this->__expires = strtotime($expires, $now); + } + +/** + * Set cookie + * + * @param string $name Name for cookie + * @param string $value Value for cookie + * @access private + */ + function __write($name, $value) { + setcookie($this->name . $name, $this->__encrypt($value), $this->__expires, $this->path, $this->domain, $this->secure); + + if (!is_null($this->__reset)) { + $this->__expires = $this->__reset; + $this->__reset = null; + } + } + +/** + * Sets a cookie expire time to remove cookie value + * + * @param string $name Name of cookie + * @access private + */ + function __delete($name) { + setcookie($this->name . $name, '', time() - 42000, $this->path, $this->domain, $this->secure); + } + +/** + * Encrypts $value using var $type method in Security class + * + * @param string $value Value to encrypt + * @return string encrypted string + * @access private + */ + function __encrypt($value) { + if (is_array($value)) { + $value = $this->__implode($value); + } + + if ($this->__encrypted === true) { + $type = $this->__type; + $value = "Q2FrZQ==." .base64_encode(Security::$type($value, $this->key)); + } + return $value; + } + +/** + * Decrypts $value using var $type method in Security class + * + * @param array $values Values to decrypt + * @return string decrypted string + * @access private + */ + function __decrypt($values) { + $decrypted = array(); + $type = $this->__type; + + foreach ((array)$values as $name => $value) { + if (is_array($value)) { + foreach ($value as $key => $val) { + $pos = strpos($val, 'Q2FrZQ==.'); + $decrypted[$name][$key] = $this->__explode($val); + + if ($pos !== false) { + $val = substr($val, 8); + $decrypted[$name][$key] = $this->__explode(Security::$type(base64_decode($val), $this->key)); + } + } + } else { + $pos = strpos($value, 'Q2FrZQ==.'); + $decrypted[$name] = $this->__explode($value); + + if ($pos !== false) { + $value = substr($value, 8); + $decrypted[$name] = $this->__explode(Security::$type(base64_decode($value), $this->key)); + } + } + } + return $decrypted; + } + +/** + * Implode method to keep keys are multidimensional arrays + * + * @param array $array Map of key and values + * @return string String in the form key1|value1,key2|value2 + * @access private + */ + function __implode($array) { + $string = ''; + foreach ($array as $key => $value) { + $string .= ',' . $key . '|' . $value; + } + return substr($string, 1); + } + +/** + * Explode method to return array from string set in CookieComponent::__implode() + * + * @param string $string String in the form key1|value1,key2|value2 + * @return array Map of key and values + * @access private + */ + function __explode($string) { + $array = array(); + foreach (explode(',', $string) as $pair) { + $key = explode('|', $pair); + if (!isset($key[1])) { + return $key[0]; + } + $array[$key[0]] = $key[1]; + } + return $array; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/email.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/email.php new file mode 100755 index 000000000..43ee594b7 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/email.php @@ -0,0 +1,1004 @@ +Controller =& $controller; + if (Configure::read('App.encoding') !== null) { + $this->charset = Configure::read('App.encoding'); + } + $this->_set($settings); + } + +/** + * Startup component + * + * @param object $controller Instantiating controller + * @access public + */ + function startup(&$controller) {} + +/** + * Send an email using the specified content, template and layout + * + * @param mixed $content Either an array of text lines, or a string with contents + * If you are rendering a template this variable will be sent to the templates as `$content` + * @param string $template Template to use when sending email + * @param string $layout Layout to use to enclose email body + * @return boolean Success + * @access public + */ + function send($content = null, $template = null, $layout = null) { + $this->_createHeader(); + + if ($template) { + $this->template = $template; + } + + if ($layout) { + $this->layout = $layout; + } + + if (is_array($content)) { + $content = implode("\n", $content) . "\n"; + } + + $this->htmlMessage = $this->textMessage = null; + if ($content) { + if ($this->sendAs === 'html') { + $this->htmlMessage = $content; + } elseif ($this->sendAs === 'text') { + $this->textMessage = $content; + } else { + $this->htmlMessage = $this->textMessage = $content; + } + } + + if ($this->sendAs === 'text') { + $message = $this->_wrap($content); + } else { + $message = $this->_wrap($content, 998); + } + + if ($this->template === null) { + $message = $this->_formatMessage($message); + } else { + $message = $this->_render($message); + } + + $message[] = ''; + $this->__message = $message; + + if (!empty($this->attachments)) { + $this->_attachFiles(); + } + + if (!is_null($this->__boundary)) { + $this->__message[] = ''; + $this->__message[] = '--' . $this->__boundary . '--'; + $this->__message[] = ''; + } + + + $_method = '_' . $this->delivery; + $sent = $this->$_method(); + + $this->__header = array(); + $this->__message = array(); + + return $sent; + } + +/** + * Reset all EmailComponent internal variables to be able to send out a new email. + * + * @access public + * @link http://book.cakephp.org/view/1285/Sending-Multiple-Emails-in-a-loop + */ + function reset() { + $this->template = null; + $this->to = array(); + $this->from = null; + $this->replyTo = null; + $this->return = null; + $this->cc = array(); + $this->bcc = array(); + $this->subject = null; + $this->additionalParams = null; + $this->date = null; + $this->smtpError = null; + $this->attachments = array(); + $this->htmlMessage = null; + $this->textMessage = null; + $this->messageId = true; + $this->__header = array(); + $this->__boundary = null; + $this->__message = array(); + } + +/** + * Render the contents using the current layout and template. + * + * @param string $content Content to render + * @return array Email ready to be sent + * @access private + */ + function _render($content) { + $viewClass = $this->Controller->view; + + if ($viewClass != 'View') { + list($plugin, $viewClass) = pluginSplit($viewClass); + $viewClass = $viewClass . 'View'; + App::import('View', $this->Controller->view); + } + + $View = new $viewClass($this->Controller); + $View->layout = $this->layout; + $msg = array(); + + $content = implode("\n", $content); + + if ($this->sendAs === 'both') { + $htmlContent = $content; + if (!empty($this->attachments)) { + $msg[] = '--' . $this->__boundary; + $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->__boundary . '"'; + $msg[] = ''; + } + $msg[] = '--alt-' . $this->__boundary; + $msg[] = 'Content-Type: text/plain; charset=' . $this->charset; + $msg[] = 'Content-Transfer-Encoding: 7bit'; + $msg[] = ''; + + $content = $View->element('email' . DS . 'text' . DS . $this->template, array('content' => $content), true); + $View->layoutPath = 'email' . DS . 'text'; + $content = explode("\n", $this->textMessage = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content))); + + $msg = array_merge($msg, $content); + + $msg[] = ''; + $msg[] = '--alt-' . $this->__boundary; + $msg[] = 'Content-Type: text/html; charset=' . $this->charset; + $msg[] = 'Content-Transfer-Encoding: 7bit'; + $msg[] = ''; + + $htmlContent = $View->element('email' . DS . 'html' . DS . $this->template, array('content' => $htmlContent), true); + $View->layoutPath = 'email' . DS . 'html'; + $htmlContent = explode("\n", $this->htmlMessage = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($htmlContent))); + $msg = array_merge($msg, $htmlContent); + $msg[] = ''; + $msg[] = '--alt-' . $this->__boundary . '--'; + $msg[] = ''; + + ClassRegistry::removeObject('view'); + return $msg; + } + + if (!empty($this->attachments)) { + if ($this->sendAs === 'html') { + $msg[] = ''; + $msg[] = '--' . $this->__boundary; + $msg[] = 'Content-Type: text/html; charset=' . $this->charset; + $msg[] = 'Content-Transfer-Encoding: 7bit'; + $msg[] = ''; + } else { + $msg[] = '--' . $this->__boundary; + $msg[] = 'Content-Type: text/plain; charset=' . $this->charset; + $msg[] = 'Content-Transfer-Encoding: 7bit'; + $msg[] = ''; + } + } + + $content = $View->element('email' . DS . $this->sendAs . DS . $this->template, array('content' => $content), true); + $View->layoutPath = 'email' . DS . $this->sendAs; + $content = explode("\n", $rendered = str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content))); + + if ($this->sendAs === 'html') { + $this->htmlMessage = $rendered; + } else { + $this->textMessage = $rendered; + } + + $msg = array_merge($msg, $content); + ClassRegistry::removeObject('view'); + + return $msg; + } + +/** + * Create unique boundary identifier + * + * @access private + */ + function _createboundary() { + $this->__boundary = md5(uniqid(time())); + } + +/** + * Sets headers for the message + * + * @access public + * @param array Associative array containing headers to be set. + */ + function header($headers) { + foreach ($headers as $header => $value) { + $this->__header[] = sprintf('%s: %s', trim($header), trim($value)); + } + } +/** + * Create emails headers including (but not limited to) from email address, reply to, + * bcc and cc. + * + * @access private + */ + function _createHeader() { + $headers = array(); + + if ($this->delivery == 'smtp') { + $headers['To'] = implode(', ', array_map(array($this, '_formatAddress'), (array)$this->to)); + } + $headers['From'] = $this->_formatAddress($this->from); + + if (!empty($this->replyTo)) { + $headers['Reply-To'] = $this->_formatAddress($this->replyTo); + } + if (!empty($this->return)) { + $headers['Return-Path'] = $this->_formatAddress($this->return); + } + if (!empty($this->readReceipt)) { + $headers['Disposition-Notification-To'] = $this->_formatAddress($this->readReceipt); + } + + if (!empty($this->cc)) { + $headers['Cc'] = implode(', ', array_map(array($this, '_formatAddress'), (array)$this->cc)); + } + + if (!empty($this->bcc) && $this->delivery != 'smtp') { + $headers['Bcc'] = implode(', ', array_map(array($this, '_formatAddress'), (array)$this->bcc)); + } + if ($this->delivery == 'smtp') { + $headers['Subject'] = $this->_encode($this->subject); + } + + if ($this->messageId !== false) { + if ($this->messageId === true) { + $headers['Message-ID'] = '<' . String::UUID() . '@' . env('HTTP_HOST') . '>'; + } else { + $headers['Message-ID'] = $this->messageId; + } + } + + $date = $this->date; + if ($date == false) { + $date = date(DATE_RFC2822); + } + $headers['Date'] = $date; + + $headers['X-Mailer'] = $this->xMailer; + + if (!empty($this->headers)) { + foreach ($this->headers as $key => $val) { + $headers['X-' . $key] = $val; + } + } + + if (!empty($this->attachments)) { + $this->_createBoundary(); + $headers['MIME-Version'] = '1.0'; + $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->__boundary . '"'; + $headers[] = 'This part of the E-mail should never be seen. If'; + $headers[] = 'you are reading this, consider upgrading your e-mail'; + $headers[] = 'client to a MIME-compatible client.'; + } elseif ($this->sendAs === 'text') { + $headers['Content-Type'] = 'text/plain; charset=' . $this->charset; + } elseif ($this->sendAs === 'html') { + $headers['Content-Type'] = 'text/html; charset=' . $this->charset; + } elseif ($this->sendAs === 'both') { + $headers['Content-Type'] = 'multipart/alternative; boundary="alt-' . $this->__boundary . '"'; + } + + $headers['Content-Transfer-Encoding'] = '7bit'; + + $this->header($headers); + } + +/** + * Format the message by seeing if it has attachments. + * + * @param string $message Message to format + * @access private + */ + function _formatMessage($message) { + if (!empty($this->attachments)) { + $prefix = array('--' . $this->__boundary); + if ($this->sendAs === 'text') { + $prefix[] = 'Content-Type: text/plain; charset=' . $this->charset; + } elseif ($this->sendAs === 'html') { + $prefix[] = 'Content-Type: text/html; charset=' . $this->charset; + } elseif ($this->sendAs === 'both') { + $prefix[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->__boundary . '"'; + } + $prefix[] = 'Content-Transfer-Encoding: 7bit'; + $prefix[] = ''; + $message = array_merge($prefix, $message); + } + return $message; + } + +/** + * Attach files by adding file contents inside boundaries. + * + * @access private + * @TODO: modify to use the core File class? + */ + function _attachFiles() { + $files = array(); + foreach ($this->attachments as $filename => $attachment) { + $file = $this->_findFiles($attachment); + if (!empty($file)) { + if (is_int($filename)) { + $filename = basename($file); + } + $files[$filename] = $file; + } + } + + foreach ($files as $filename => $file) { + $handle = fopen($file, 'rb'); + $data = fread($handle, filesize($file)); + $data = chunk_split(base64_encode($data)) ; + fclose($handle); + + $this->__message[] = '--' . $this->__boundary; + $this->__message[] = 'Content-Type: application/octet-stream'; + $this->__message[] = 'Content-Transfer-Encoding: base64'; + $this->__message[] = 'Content-Disposition: attachment; filename="' . basename($filename) . '"'; + $this->__message[] = ''; + $this->__message[] = $data; + $this->__message[] = ''; + } + } + +/** + * Find the specified attachment in the list of file paths + * + * @param string $attachment Attachment file name to find + * @return string Path to located file + * @access private + */ + function _findFiles($attachment) { + if (file_exists($attachment)) { + return $attachment; + } + foreach ($this->filePaths as $path) { + if (file_exists($path . DS . $attachment)) { + $file = $path . DS . $attachment; + return $file; + } + } + return null; + } + +/** + * Wrap the message using EmailComponent::$lineLength + * + * @param string $message Message to wrap + * @param integer $lineLength Max length of line + * @return array Wrapped message + * @access protected + */ + function _wrap($message, $lineLength = null) { + $message = $this->_strip($message, true); + $message = str_replace(array("\r\n","\r"), "\n", $message); + $lines = explode("\n", $message); + $formatted = array(); + + if ($this->_lineLength !== null) { + trigger_error(__('_lineLength cannot be accessed please use lineLength', true), E_USER_WARNING); + $this->lineLength = $this->_lineLength; + } + + if (!$lineLength) { + $lineLength = $this->lineLength; + } + + foreach ($lines as $line) { + if (substr($line, 0, 1) == '.') { + $line = '.' . $line; + } + $formatted = array_merge($formatted, explode("\n", wordwrap($line, $lineLength, "\n", true))); + } + $formatted[] = ''; + return $formatted; + } + +/** + * Encode the specified string using the current charset + * + * @param string $subject String to encode + * @return string Encoded string + * @access private + */ + function _encode($subject) { + $subject = $this->_strip($subject); + + $nl = "\r\n"; + if ($this->delivery == 'mail') { + $nl = ''; + } + $internalEncoding = function_exists('mb_internal_encoding'); + if ($internalEncoding) { + $restore = mb_internal_encoding(); + mb_internal_encoding($this->charset); + } + $return = mb_encode_mimeheader($subject, $this->charset, 'B', $nl); + if ($internalEncoding) { + mb_internal_encoding($restore); + } + return $return; + } + +/** + * Format a string as an email address + * + * @param string $string String representing an email address + * @return string Email address suitable for email headers or smtp pipe + * @access private + */ + function _formatAddress($string, $smtp = false) { + $hasAlias = preg_match('/((.*))?\s?<(.+)>/', $string, $matches); + if ($smtp && $hasAlias) { + return $this->_strip('<' . $matches[3] . '>'); + } elseif ($smtp) { + return $this->_strip('<' . $string . '>'); + } + + if ($hasAlias && !empty($matches[2])) { + return $this->_encode($matches[2]) . $this->_strip(' <' . $matches[3] . '>'); + } + return $this->_strip($string); + } + +/** + * Remove certain elements (such as bcc:, to:, %0a) from given value. + * Helps prevent header injection / mainipulation on user content. + * + * @param string $value Value to strip + * @param boolean $message Set to true to indicate main message content + * @return string Stripped value + * @access private + */ + function _strip($value, $message = false) { + $search = '%0a|%0d|Content-(?:Type|Transfer-Encoding)\:'; + $search .= '|charset\=|mime-version\:|multipart/mixed|(?:[^a-z]to|b?cc)\:.*'; + + if ($message !== true) { + $search .= '|\r|\n'; + } + $search = '#(?:' . $search . ')#i'; + while (preg_match($search, $value)) { + $value = preg_replace($search, '', $value); + } + return $value; + } + +/** + * Wrapper for PHP mail function used for sending out emails + * + * @return bool Success + * @access private + */ + function _mail() { + $header = implode($this->lineFeed, $this->__header); + $message = implode($this->lineFeed, $this->__message); + if (is_array($this->to)) { + $to = implode(', ', array_map(array($this, '_formatAddress'), $this->to)); + } else { + $to = $this->to; + } + if (ini_get('safe_mode')) { + return @mail($to, $this->_encode($this->subject), $message, $header); + } + return @mail($to, $this->_encode($this->subject), $message, $header, $this->additionalParams); + } + + +/** + * Helper method to get socket, overridden in tests + * + * @param array $config Config data for the socket. + * @return void + * @access protected + */ + function _getSocket($config) { + $this->__smtpConnection =& new CakeSocket($config); + } + +/** + * Sends out email via SMTP + * + * @return bool Success + * @access private + */ + function _smtp() { + App::import('Core', array('CakeSocket')); + + $defaults = array( + 'host' => 'localhost', + 'port' => 25, + 'protocol' => 'smtp', + 'timeout' => 30 + ); + $this->smtpOptions = array_merge($defaults, $this->smtpOptions); + $this->_getSocket($this->smtpOptions); + + if (!$this->__smtpConnection->connect()) { + $this->smtpError = $this->__smtpConnection->lastError(); + return false; + } elseif (!$this->_smtpSend(null, '220')) { + return false; + } + + $httpHost = env('HTTP_HOST'); + + if (isset($this->smtpOptions['client'])) { + $host = $this->smtpOptions['client']; + } elseif (!empty($httpHost)) { + list($host) = explode(':', $httpHost); + } else { + $host = 'localhost'; + } + + if (!$this->_smtpSend("EHLO {$host}", '250') && !$this->_smtpSend("HELO {$host}", '250')) { + return false; + } + + if (isset($this->smtpOptions['username']) && isset($this->smtpOptions['password'])) { + $authRequired = $this->_smtpSend('AUTH LOGIN', '334|503'); + if ($authRequired == '334') { + if (!$this->_smtpSend(base64_encode($this->smtpOptions['username']), '334')) { + return false; + } + if (!$this->_smtpSend(base64_encode($this->smtpOptions['password']), '235')) { + return false; + } + } elseif ($authRequired != '503') { + return false; + } + } + + if (!$this->_smtpSend('MAIL FROM: ' . $this->_formatAddress($this->from, true))) { + return false; + } + + if (!is_array($this->to)) { + $tos = array_map('trim', explode(',', $this->to)); + } else { + $tos = $this->to; + } + foreach ($tos as $to) { + if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($to, true))) { + return false; + } + } + + foreach ($this->cc as $cc) { + if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($cc, true))) { + return false; + } + } + foreach ($this->bcc as $bcc) { + if (!$this->_smtpSend('RCPT TO: ' . $this->_formatAddress($bcc, true))) { + return false; + } + } + + if (!$this->_smtpSend('DATA', '354')) { + return false; + } + + $header = implode("\r\n", $this->__header); + $message = implode("\r\n", $this->__message); + if (!$this->_smtpSend($header . "\r\n\r\n" . $message . "\r\n\r\n\r\n.")) { + return false; + } + $this->_smtpSend('QUIT', false); + + $this->__smtpConnection->disconnect(); + return true; + } + +/** + * Protected method for sending data to SMTP connection + * + * @param string $data data to be sent to SMTP server + * @param mixed $checkCode code to check for in server response, false to skip + * @return bool Success + * @access protected + */ + function _smtpSend($data, $checkCode = '250') { + if (!is_null($data)) { + $this->__smtpConnection->write($data . "\r\n"); + } + while ($checkCode !== false) { + $response = ''; + $startTime = time(); + while (substr($response, -2) !== "\r\n" && ((time() - $startTime) < $this->smtpOptions['timeout'])) { + $response .= $this->__smtpConnection->read(); + } + if (substr($response, -2) !== "\r\n") { + $this->smtpError = 'timeout'; + return false; + } + $response = end(explode("\r\n", rtrim($response, "\r\n"))); + + if (preg_match('/^(' . $checkCode . ')(.)/', $response, $code)) { + if ($code[2] === '-') { + continue; + } + return $code[1]; + } + $this->smtpError = $response; + return false; + } + return true; + } + +/** + * Set as controller flash message a debug message showing current settings in component + * + * @return boolean Success + * @access private + */ + function _debug() { + $nl = "\n"; + $header = implode($nl, $this->__header); + $message = implode($nl, $this->__message); + $fm = '
';
+
+		if (is_array($this->to)) {
+			$to = implode(', ', array_map(array($this, '_formatAddress'), $this->to));
+		} else {
+			$to = $this->to;
+		}
+		$fm .= sprintf('%s %s%s', 'To:', $to, $nl);
+		$fm .= sprintf('%s %s%s', 'From:', $this->from, $nl);
+		$fm .= sprintf('%s %s%s', 'Subject:', $this->_encode($this->subject), $nl);
+		$fm .= sprintf('%s%3$s%3$s%s', 'Header:', $header, $nl);
+		$fm .= sprintf('%s%3$s%3$s%s', 'Parameters:', $this->additionalParams, $nl);
+		$fm .= sprintf('%s%3$s%3$s%s', 'Message:', $message, $nl);
+		$fm .= '
'; + + if (isset($this->Controller->Session)) { + $this->Controller->Session->setFlash($fm, 'default', null, 'email'); + return true; + } + return $fm; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/request_handler.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/request_handler.php new file mode 100644 index 000000000..ae33ab27d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/request_handler.php @@ -0,0 +1,827 @@ + 'text/javascript', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'css' => 'text/css', + 'html' => array('text/html', '*/*'), + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'csv' => array('application/vnd.ms-excel', 'text/plain'), + 'form' => 'application/x-www-form-urlencoded', + 'file' => 'multipart/form-data', + 'xhtml' => array('application/xhtml+xml', 'application/xhtml', 'text/xhtml'), + 'xhtml-mobile' => 'application/vnd.wap.xhtml+xml', + 'xml' => array('application/xml', 'text/xml'), + 'rss' => 'application/rss+xml', + 'atom' => 'application/atom+xml', + 'amf' => 'application/x-amf', + 'wap' => array( + 'text/vnd.wap.wml', + 'text/vnd.wap.wmlscript', + 'image/vnd.wap.wbmp' + ), + 'wml' => 'text/vnd.wap.wml', + 'wmlscript' => 'text/vnd.wap.wmlscript', + 'wbmp' => 'image/vnd.wap.wbmp', + 'pdf' => 'application/pdf', + 'zip' => 'application/x-zip', + 'tar' => 'application/x-tar' + ); + +/** + * List of regular expressions for matching mobile device's user agent string + * + * @var array + * @access public + */ + var $mobileUA = array( + 'Android', + 'AvantGo', + 'BlackBerry', + 'DoCoMo', + 'iPod', + 'iPhone', + 'J2ME', + 'MIDP', + 'NetFront', + 'Nokia', + 'Opera Mini', + 'PalmOS', + 'PalmSource', + 'portalmmm', + 'Plucker', + 'ReqwirelessWeb', + 'SonyEricsson', + 'Symbian', + 'UP\.Browser', + 'webOS', + 'Windows CE', + 'Xiino' + ); + +/** + * Content-types accepted by the client. If extension parsing is enabled in the + * Router, and an extension is detected, the corresponding content-type will be + * used as the overriding primary content-type accepted. + * + * @var array + * @access private + * @see Router::parseExtensions() + */ + var $__acceptTypes = array(); + +/** + * The template to use when rendering the given content type. + * + * @var string + * @access private + */ + var $__renderType = null; + +/** + * Contains the file extension parsed out by the Router + * + * @var string + * @access public + * @see Router::parseExtensions() + */ + var $ext = null; + +/** + * Flag set when MIME types have been initialized + * + * @var boolean + * @access private + * @see RequestHandler::__initializeTypes() + */ + var $__typesInitialized = false; + +/** + * Constructor. Parses the accepted content types accepted by the client using HTTP_ACCEPT + * + */ + function __construct() { + $this->__acceptTypes = explode(',', env('HTTP_ACCEPT')); + + foreach ($this->__acceptTypes as $i => $type) { + if (strpos($type, ';')) { + $type = explode(';', $type); + $this->__acceptTypes[$i] = $type[0]; + } + } + parent::__construct(); + } + +/** + * Initializes the component, gets a reference to Controller::$parameters, and + * checks to see if a file extension has been parsed by the Router. If yes, the + * corresponding content-type is pushed onto the list of accepted content-types + * as the first item. + * + * @param object $controller A reference to the controller + * @param array $settings Array of settings to _set(). + * @return void + * @see Router::parseExtensions() + * @access public + */ + function initialize(&$controller, $settings = array()) { + if (isset($controller->params['url']['ext'])) { + $this->ext = $controller->params['url']['ext']; + } + $this->params = $controller->params; + $this->_set($settings); + } + +/** + * The startup method of the RequestHandler enables several automatic behaviors + * related to the detection of certain properties of the HTTP request, including: + * + * - Disabling layout rendering for Ajax requests (based on the HTTP_X_REQUESTED_WITH header) + * - If Router::parseExtensions() is enabled, the layout and template type are + * switched based on the parsed extension. For example, if controller/action.xml + * is requested, the view path becomes app/views/controller/xml/action.ctp. + * - If a helper with the same name as the extension exists, it is added to the controller. + * - If the extension is of a type that RequestHandler understands, it will set that + * Content-type in the response header. + * - If the XML data is POSTed, the data is parsed into an XML object, which is assigned + * to the $data property of the controller, which can then be saved to a model object. + * + * @param object $controller A reference to the controller + * @return void + * @access public + */ + function startup(&$controller) { + if (!$this->enabled) { + return; + } + + $this->__initializeTypes(); + $controller->params['isAjax'] = $this->isAjax(); + $isRecognized = ( + !in_array($this->ext, array('html', 'htm')) && + in_array($this->ext, array_keys($this->__requestContent)) + ); + + if (!empty($this->ext) && $isRecognized) { + $this->renderAs($controller, $this->ext); + } elseif ($this->isAjax()) { + $this->renderAs($controller, 'ajax'); + } elseif (empty($this->ext) || in_array($this->ext, array('html', 'htm'))) { + $this->respondAs('html', array('charset' => Configure::read('App.encoding'))); + } + + if ($this->requestedWith('xml')) { + if (!class_exists('XmlNode')) { + App::import('Core', 'Xml'); + } + $xml = new Xml(trim(file_get_contents('php://input'))); + + if (count($xml->children) == 1 && is_object($dataNode = $xml->child('data'))) { + $controller->data = $dataNode->toArray(); + } else { + $controller->data = $xml->toArray(); + } + } + } + +/** + * Handles (fakes) redirects for Ajax requests using requestAction() + * + * @param object $controller A reference to the controller + * @param mixed $url A string or array containing the redirect location + * @param mixed HTTP Status for redirect + * @access public + */ + function beforeRedirect(&$controller, $url, $status = null) { + if (!$this->isAjax()) { + return; + } + foreach ($_POST as $key => $val) { + unset($_POST[$key]); + } + if (is_array($url)) { + $url = Router::url($url + array('base' => false)); + } + if (!empty($status)) { + $statusCode = $controller->httpCodes($status); + $code = key($statusCode); + $msg = $statusCode[$code]; + $controller->header("HTTP/1.1 {$code} {$msg}"); + } + echo $this->requestAction($url, array('return', 'bare' => false)); + $this->_stop(); + } + +/** + * Returns true if the current HTTP request is Ajax, false otherwise + * + * @return boolean True if call is Ajax + * @access public + */ + function isAjax() { + return env('HTTP_X_REQUESTED_WITH') === "XMLHttpRequest"; + } + +/** + * Returns true if the current HTTP request is coming from a Flash-based client + * + * @return boolean True if call is from Flash + * @access public + */ + function isFlash() { + return (preg_match('/^(Shockwave|Adobe) Flash/', env('HTTP_USER_AGENT')) == 1); + } + +/** + * Returns true if the current request is over HTTPS, false otherwise. + * + * @return bool True if call is over HTTPS + * @access public + */ + function isSSL() { + return env('HTTPS'); + } + +/** + * Returns true if the current call accepts an XML response, false otherwise + * + * @return boolean True if client accepts an XML response + * @access public + */ + function isXml() { + return $this->prefers('xml'); + } + +/** + * Returns true if the current call accepts an RSS response, false otherwise + * + * @return boolean True if client accepts an RSS response + * @access public + */ + function isRss() { + return $this->prefers('rss'); + } + +/** + * Returns true if the current call accepts an Atom response, false otherwise + * + * @return boolean True if client accepts an RSS response + * @access public + */ + function isAtom() { + return $this->prefers('atom'); + } + +/** + * Returns true if user agent string matches a mobile web browser, or if the + * client accepts WAP content. + * + * @return boolean True if user agent is a mobile web browser + * @access public + * @deprecated Use of constant REQUEST_MOBILE_UA is deprecated and will be removed in future versions + */ + function isMobile() { + if (defined('REQUEST_MOBILE_UA')) { + $regex = '/' . REQUEST_MOBILE_UA . '/i'; + } else { + $regex = '/' . implode('|', $this->mobileUA) . '/i'; + } + + if (preg_match($regex, env('HTTP_USER_AGENT')) || $this->accepts('wap')) { + return true; + } + return false; + } + +/** + * Returns true if the client accepts WAP content + * + * @return bool + * @access public + */ + function isWap() { + return $this->prefers('wap'); + } + +/** + * Returns true if the current call a POST request + * + * @return boolean True if call is a POST + * @access public + */ + function isPost() { + return (strtolower(env('REQUEST_METHOD')) == 'post'); + } + +/** + * Returns true if the current call a PUT request + * + * @return boolean True if call is a PUT + * @access public + */ + function isPut() { + return (strtolower(env('REQUEST_METHOD')) == 'put'); + } + +/** + * Returns true if the current call a GET request + * + * @return boolean True if call is a GET + * @access public + */ + function isGet() { + return (strtolower(env('REQUEST_METHOD')) == 'get'); + } + +/** + * Returns true if the current call a DELETE request + * + * @return boolean True if call is a DELETE + * @access public + */ + function isDelete() { + return (strtolower(env('REQUEST_METHOD')) == 'delete'); + } + +/** + * Gets Prototype version if call is Ajax, otherwise empty string. + * The Prototype library sets a special "Prototype version" HTTP header. + * + * @return string Prototype version of component making Ajax call + * @access public + */ + function getAjaxVersion() { + if (env('HTTP_X_PROTOTYPE_VERSION') != null) { + return env('HTTP_X_PROTOTYPE_VERSION'); + } + return false; + } + +/** + * Adds/sets the Content-type(s) for the given name. This method allows + * content-types to be mapped to friendly aliases (or extensions), which allows + * RequestHandler to automatically respond to requests of that type in the + * startup method. + * + * @param string $name The name of the Content-type, i.e. "html", "xml", "css" + * @param mixed $type The Content-type or array of Content-types assigned to the name, + * i.e. "text/html", or "application/xml" + * @return void + * @access public + */ + function setContent($name, $type = null) { + if (is_array($name)) { + $this->__requestContent = array_merge($this->__requestContent, $name); + return; + } + $this->__requestContent[$name] = $type; + } + +/** + * Gets the server name from which this request was referred + * + * @return string Server address + * @access public + */ + function getReferer() { + if (env('HTTP_HOST') != null) { + $sessHost = env('HTTP_HOST'); + } + + if (env('HTTP_X_FORWARDED_HOST') != null) { + $sessHost = env('HTTP_X_FORWARDED_HOST'); + } + return trim(preg_replace('/(?:\:.*)/', '', $sessHost)); + } + +/** + * Gets remote client IP + * + * @return string Client IP address + * @access public + */ + function getClientIP($safe = true) { + if (!$safe && env('HTTP_X_FORWARDED_FOR') != null) { + $ipaddr = preg_replace('/(?:,.*)/', '', env('HTTP_X_FORWARDED_FOR')); + } else { + if (env('HTTP_CLIENT_IP') != null) { + $ipaddr = env('HTTP_CLIENT_IP'); + } else { + $ipaddr = env('REMOTE_ADDR'); + } + } + + if (env('HTTP_CLIENTADDRESS') != null) { + $tmpipaddr = env('HTTP_CLIENTADDRESS'); + + if (!empty($tmpipaddr)) { + $ipaddr = preg_replace('/(?:,.*)/', '', $tmpipaddr); + } + } + return trim($ipaddr); + } + +/** + * Determines which content types the client accepts. Acceptance is based on + * the file extension parsed by the Router (if present), and by the HTTP_ACCEPT + * header. + * + * @param mixed $type Can be null (or no parameter), a string type name, or an + * array of types + * @return mixed If null or no parameter is passed, returns an array of content + * types the client accepts. If a string is passed, returns true + * if the client accepts it. If an array is passed, returns true + * if the client accepts one or more elements in the array. + * @access public + * @see RequestHandlerComponent::setContent() + */ + function accepts($type = null) { + $this->__initializeTypes(); + + if ($type == null) { + return $this->mapType($this->__acceptTypes); + + } elseif (is_array($type)) { + foreach ($type as $t) { + if ($this->accepts($t) == true) { + return true; + } + } + return false; + } elseif (is_string($type)) { + + if (!isset($this->__requestContent[$type])) { + return false; + } + + $content = $this->__requestContent[$type]; + + if (is_array($content)) { + foreach ($content as $c) { + if (in_array($c, $this->__acceptTypes)) { + return true; + } + } + } else { + if (in_array($content, $this->__acceptTypes)) { + return true; + } + } + } + } + +/** + * Determines the content type of the data the client has sent (i.e. in a POST request) + * + * @param mixed $type Can be null (or no parameter), a string type name, or an array of types + * @return mixed + * @access public + */ + function requestedWith($type = null) { + if (!$this->isPost() && !$this->isPut()) { + return null; + } + + list($contentType) = explode(';', env('CONTENT_TYPE')); + if ($type == null) { + return $this->mapType($contentType); + } elseif (is_array($type)) { + foreach ($type as $t) { + if ($this->requestedWith($t)) { + return $this->mapType($t); + } + } + return false; + } elseif (is_string($type)) { + return ($type == $this->mapType($contentType)); + } + } + +/** + * Determines which content-types the client prefers. If no parameters are given, + * the content-type that the client most likely prefers is returned. If $type is + * an array, the first item in the array that the client accepts is returned. + * Preference is determined primarily by the file extension parsed by the Router + * if provided, and secondarily by the list of content-types provided in + * HTTP_ACCEPT. + * + * @param mixed $type An optional array of 'friendly' content-type names, i.e. + * 'html', 'xml', 'js', etc. + * @return mixed If $type is null or not provided, the first content-type in the + * list, based on preference, is returned. + * @access public + * @see RequestHandlerComponent::setContent() + */ + function prefers($type = null) { + $this->__initializeTypes(); + $accept = $this->accepts(); + + if ($type == null) { + if (empty($this->ext)) { + if (is_array($accept)) { + return $accept[0]; + } + return $accept; + } + return $this->ext; + } + + $types = $type; + if (is_string($type)) { + $types = array($type); + } + + if (count($types) === 1) { + if (!empty($this->ext)) { + return ($types[0] == $this->ext); + } + return ($types[0] == $accept[0]); + } + $accepts = array(); + + foreach ($types as $type) { + if (in_array($type, $accept)) { + $accepts[] = $type; + } + } + + if (count($accepts) === 0) { + return false; + } elseif (count($types) === 1) { + return ($types[0] === $accepts[0]); + } elseif (count($accepts) === 1) { + return $accepts[0]; + } + + $acceptedTypes = array(); + foreach ($this->__acceptTypes as $type) { + $acceptedTypes[] = $this->mapType($type); + } + $accepts = array_intersect($acceptedTypes, $accepts); + return $accepts[0]; + } + +/** + * Sets the layout and template paths for the content type defined by $type. + * + * @param object $controller A reference to a controller object + * @param string $type Type of response to send (e.g: 'ajax') + * @return void + * @access public + * @see RequestHandlerComponent::setContent() + * @see RequestHandlerComponent::respondAs() + */ + function renderAs(&$controller, $type) { + $this->__initializeTypes(); + $options = array('charset' => 'UTF-8'); + + if (Configure::read('App.encoding') !== null) { + $options = array('charset' => Configure::read('App.encoding')); + } + + if ($type == 'ajax') { + $controller->layout = $this->ajaxLayout; + return $this->respondAs('html', $options); + } + $controller->ext = '.ctp'; + + if (empty($this->__renderType)) { + $controller->viewPath .= DS . $type; + } else { + $remove = preg_replace("/([\/\\\\]{$this->__renderType})$/", DS . $type, $controller->viewPath); + $controller->viewPath = $remove; + } + $this->__renderType = $type; + $controller->layoutPath = $type; + + if (isset($this->__requestContent[$type])) { + $this->respondAs($type, $options); + } + + $helper = ucfirst($type); + $isAdded = ( + in_array($helper, $controller->helpers) || + array_key_exists($helper, $controller->helpers) + ); + + if (!$isAdded) { + if (App::import('Helper', $helper)) { + $controller->helpers[] = $helper; + } + } + } + +/** + * Sets the response header based on type map index name. If DEBUG is greater than 2, the header + * is not set. + * + * @param mixed $type Friendly type name, i.e. 'html' or 'xml', or a full content-type, + * like 'application/x-shockwave'. + * @param array $options If $type is a friendly type name that is associated with + * more than one type of content, $index is used to select which content-type to use. + * @return boolean Returns false if the friendly type name given in $type does + * not exist in the type map, or if the Content-type header has + * already been set by this method. + * @access public + * @see RequestHandlerComponent::setContent() + */ + function respondAs($type, $options = array()) { + $this->__initializeTypes(); + if (!array_key_exists($type, $this->__requestContent) && strpos($type, '/') === false) { + return false; + } + $defaults = array('index' => 0, 'charset' => null, 'attachment' => false); + $options = array_merge($defaults, $options); + + if (strpos($type, '/') === false && isset($this->__requestContent[$type])) { + $cType = null; + if (is_array($this->__requestContent[$type]) && isset($this->__requestContent[$type][$options['index']])) { + $cType = $this->__requestContent[$type][$options['index']]; + } elseif (is_array($this->__requestContent[$type]) && isset($this->__requestContent[$type][0])) { + $cType = $this->__requestContent[$type][0]; + } elseif (isset($this->__requestContent[$type])) { + $cType = $this->__requestContent[$type]; + } else { + return false; + } + + if (is_array($cType)) { + if ($this->prefers($cType)) { + $cType = $this->prefers($cType); + } else { + $cType = $cType[0]; + } + } + } else { + $cType = $type; + } + + if ($cType != null) { + $header = 'Content-type: ' . $cType; + + if (!empty($options['charset'])) { + $header .= '; charset=' . $options['charset']; + } + if (!empty($options['attachment'])) { + $this->_header("Content-Disposition: attachment; filename=\"{$options['attachment']}\""); + } + if (Configure::read() < 2 && !defined('CAKEPHP_SHELL') && empty($this->params['requested'])) { + $this->_header($header); + } + $this->__responseTypeSet = $cType; + return true; + } + return false; + } + +/** + * Wrapper for header() so calls can be easily tested. + * + * @param string $header The header to be sent. + * @return void + * @access protected + */ + function _header($header) { + header($header); + } + +/** + * Returns the current response type (Content-type header), or null if none has been set + * + * @return mixed A string content type alias, or raw content type if no alias map exists, + * otherwise null + * @access public + */ + function responseType() { + if ($this->__responseTypeSet == null) { + return null; + } + return $this->mapType($this->__responseTypeSet); + } + +/** + * Maps a content-type back to an alias + * + * @param mixed $type Content type + * @return mixed Alias + * @access public + */ + function mapType($ctype) { + if (is_array($ctype)) { + $out = array(); + foreach ($ctype as $t) { + $out[] = $this->mapType($t); + } + return $out; + } else { + $keys = array_keys($this->__requestContent); + $count = count($keys); + + for ($i = 0; $i < $count; $i++) { + $name = $keys[$i]; + $type = $this->__requestContent[$name]; + + if (is_array($type) && in_array($ctype, $type)) { + return $name; + } elseif (!is_array($type) && $type == $ctype) { + return $name; + } + } + return $ctype; + } + } + +/** + * Initializes MIME types + * + * @return void + * @access private + */ + function __initializeTypes() { + if ($this->__typesInitialized) { + return; + } + if (isset($this->__requestContent[$this->ext])) { + $content = $this->__requestContent[$this->ext]; + if (is_array($content)) { + $content = $content[0]; + } + array_unshift($this->__acceptTypes, $content); + } + $this->__typesInitialized = true; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/security.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/security.php new file mode 100644 index 000000000..0de6c6ae4 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/security.php @@ -0,0 +1,747 @@ + '', 'prompt' => null); + +/** + * An associative array of usernames/passwords used for HTTP-authenticated logins. + * + * @var array + * @access public + * @see SecurityComponent::requireLogin() + */ + var $loginUsers = array(); + +/** + * Controllers from which actions of the current controller are allowed to receive + * requests. + * + * @var array + * @access public + * @see SecurityComponent::requireAuth() + */ + var $allowedControllers = array(); + +/** + * Actions from which actions of the current controller are allowed to receive + * requests. + * + * @var array + * @access public + * @see SecurityComponent::requireAuth() + */ + var $allowedActions = array(); + +/** + * Form fields to disable + * + * @var array + * @access public + */ + var $disabledFields = array(); + +/** + * Whether to validate POST data. Set to false to disable for data coming from 3rd party + * services, etc. + * + * @var boolean + * @access public + */ + var $validatePost = true; + +/** + * Other components used by the Security component + * + * @var array + * @access public + */ + var $components = array('RequestHandler', 'Session'); + +/** + * Holds the current action of the controller + * + * @var string + */ + var $_action = null; + +/** + * Initialize the SecurityComponent + * + * @param object $controller Controller instance for the request + * @param array $settings Settings to set to the component + * @return void + * @access public + */ + function initialize(&$controller, $settings = array()) { + $this->_set($settings); + } + +/** + * Component startup. All security checking happens here. + * + * @param object $controller Instantiating controller + * @return void + * @access public + */ + function startup(&$controller) { + $this->_action = strtolower($controller->action); + $this->_methodsRequired($controller); + $this->_secureRequired($controller); + $this->_authRequired($controller); + $this->_loginRequired($controller); + + $isPost = ($this->RequestHandler->isPost() || $this->RequestHandler->isPut()); + $isRequestAction = ( + !isset($controller->params['requested']) || + $controller->params['requested'] != 1 + ); + + if ($isPost && $isRequestAction && $this->validatePost) { + if ($this->_validatePost($controller) === false) { + if (!$this->blackHole($controller, 'auth')) { + return null; + } + } + } + $this->_generateToken($controller); + } + +/** + * Sets the actions that require a POST request, or empty for all actions + * + * @return void + * @access public + * @link http://book.cakephp.org/view/1299/requirePost + */ + function requirePost() { + $args = func_get_args(); + $this->_requireMethod('Post', $args); + } + +/** + * Sets the actions that require a GET request, or empty for all actions + * + * @return void + * @access public + */ + function requireGet() { + $args = func_get_args(); + $this->_requireMethod('Get', $args); + } + +/** + * Sets the actions that require a PUT request, or empty for all actions + * + * @return void + * @access public + */ + function requirePut() { + $args = func_get_args(); + $this->_requireMethod('Put', $args); + } + +/** + * Sets the actions that require a DELETE request, or empty for all actions + * + * @return void + * @access public + */ + function requireDelete() { + $args = func_get_args(); + $this->_requireMethod('Delete', $args); + } + +/** + * Sets the actions that require a request that is SSL-secured, or empty for all actions + * + * @return void + * @access public + * @link http://book.cakephp.org/view/1300/requireSecure + */ + function requireSecure() { + $args = func_get_args(); + $this->_requireMethod('Secure', $args); + } + +/** + * Sets the actions that require an authenticated request, or empty for all actions + * + * @return void + * @access public + * @link http://book.cakephp.org/view/1301/requireAuth + */ + function requireAuth() { + $args = func_get_args(); + $this->_requireMethod('Auth', $args); + } + +/** + * Sets the actions that require an HTTP-authenticated request, or empty for all actions + * + * @return void + * @access public + * @link http://book.cakephp.org/view/1302/requireLogin + */ + function requireLogin() { + $args = func_get_args(); + $base = $this->loginOptions; + + foreach ($args as $i => $arg) { + if (is_array($arg)) { + $this->loginOptions = $arg; + unset($args[$i]); + } + } + $this->loginOptions = array_merge($base, $this->loginOptions); + $this->_requireMethod('Login', $args); + + if (isset($this->loginOptions['users'])) { + $this->loginUsers =& $this->loginOptions['users']; + } + } + +/** + * Attempts to validate the login credentials for an HTTP-authenticated request + * + * @param string $type Either 'basic', 'digest', or null. If null/empty, will try both. + * @return mixed If successful, returns an array with login name and password, otherwise null. + * @access public + * @link http://book.cakephp.org/view/1303/loginCredentials-string-type + */ + function loginCredentials($type = null) { + switch (strtolower($type)) { + case 'basic': + $login = array('username' => env('PHP_AUTH_USER'), 'password' => env('PHP_AUTH_PW')); + if (!empty($login['username'])) { + return $login; + } + break; + case 'digest': + default: + $digest = null; + + if (version_compare(PHP_VERSION, '5.1') != -1) { + $digest = env('PHP_AUTH_DIGEST'); + } elseif (function_exists('apache_request_headers')) { + $headers = apache_request_headers(); + if (isset($headers['Authorization']) && !empty($headers['Authorization']) && substr($headers['Authorization'], 0, 7) == 'Digest ') { + $digest = substr($headers['Authorization'], 7); + } + } else { + // Server doesn't support digest-auth headers + trigger_error(__('SecurityComponent::loginCredentials() - Server does not support digest authentication', true), E_USER_WARNING); + } + + if (!empty($digest)) { + return $this->parseDigestAuthData($digest); + } + break; + } + return null; + } + +/** + * Generates the text of an HTTP-authentication request header from an array of options. + * + * @param array $options Set of options for header + * @return string HTTP-authentication request header + * @access public + * @link http://book.cakephp.org/view/1304/loginRequest-array-options + */ + function loginRequest($options = array()) { + $options = array_merge($this->loginOptions, $options); + $this->_setLoginDefaults($options); + $auth = 'WWW-Authenticate: ' . ucfirst($options['type']); + $out = array('realm="' . $options['realm'] . '"'); + + if (strtolower($options['type']) == 'digest') { + $out[] = 'qop="auth"'; + $out[] = 'nonce="' . uniqid("") . '"'; + $out[] = 'opaque="' . md5($options['realm']) . '"'; + } + + return $auth . ' ' . implode(',', $out); + } + +/** + * Parses an HTTP digest authentication response, and returns an array of the data, or null on failure. + * + * @param string $digest Digest authentication response + * @return array Digest authentication parameters + * @access public + * @link http://book.cakephp.org/view/1305/parseDigestAuthData-string-digest + */ + function parseDigestAuthData($digest) { + if (substr($digest, 0, 7) == 'Digest ') { + $digest = substr($digest, 7); + } + $keys = array(); + $match = array(); + $req = array('nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1); + preg_match_all('/(\w+)=([\'"]?)([a-zA-Z0-9@=.\/_-]+)\2/', $digest, $match, PREG_SET_ORDER); + + foreach ($match as $i) { + $keys[$i[1]] = $i[3]; + unset($req[$i[1]]); + } + + if (empty($req)) { + return $keys; + } + return null; + } + +/** + * Generates a hash to be compared with an HTTP digest-authenticated response + * + * @param array $data HTTP digest response data, as parsed by SecurityComponent::parseDigestAuthData() + * @return string Digest authentication hash + * @access public + * @see SecurityComponent::parseDigestAuthData() + * @link http://book.cakephp.org/view/1306/generateDigestResponseHash-array-data + */ + function generateDigestResponseHash($data) { + return md5( + md5($data['username'] . ':' . $this->loginOptions['realm'] . ':' . $this->loginUsers[$data['username']]) . + ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . + md5(env('REQUEST_METHOD') . ':' . $data['uri']) + ); + } + +/** + * Black-hole an invalid request with a 404 error or custom callback. If SecurityComponent::$blackHoleCallback + * is specified, it will use this callback by executing the method indicated in $error + * + * @param object $controller Instantiating controller + * @param string $error Error method + * @return mixed If specified, controller blackHoleCallback's response, or no return otherwise + * @access public + * @see SecurityComponent::$blackHoleCallback + * @link http://book.cakephp.org/view/1307/blackHole-object-controller-string-error + */ + function blackHole(&$controller, $error = '') { + if ($this->blackHoleCallback == null) { + $code = 404; + if ($error == 'login') { + $code = 401; + $controller->header($this->loginRequest()); + } + $controller->redirect(null, $code, true); + } else { + return $this->_callback($controller, $this->blackHoleCallback, array($error)); + } + } + +/** + * Sets the actions that require a $method HTTP request, or empty for all actions + * + * @param string $method The HTTP method to assign controller actions to + * @param array $actions Controller actions to set the required HTTP method to. + * @return void + * @access protected + */ + function _requireMethod($method, $actions = array()) { + if (isset($actions[0]) && is_array($actions[0])) { + $actions = $actions[0]; + } + $this->{'require' . $method} = (empty($actions)) ? array('*'): $actions; + } + +/** + * Check if HTTP methods are required + * + * @param object $controller Instantiating controller + * @return bool true if $method is required + * @access protected + */ + function _methodsRequired(&$controller) { + foreach (array('Post', 'Get', 'Put', 'Delete') as $method) { + $property = 'require' . $method; + if (is_array($this->$property) && !empty($this->$property)) { + $require = array_map('strtolower', $this->$property); + + if (in_array($this->_action, $require) || $this->$property == array('*')) { + if (!$this->RequestHandler->{'is' . $method}()) { + if (!$this->blackHole($controller, strtolower($method))) { + return null; + } + } + } + } + } + return true; + } + +/** + * Check if access requires secure connection + * + * @param object $controller Instantiating controller + * @return bool true if secure connection required + * @access protected + */ + function _secureRequired(&$controller) { + if (is_array($this->requireSecure) && !empty($this->requireSecure)) { + $requireSecure = array_map('strtolower', $this->requireSecure); + + if (in_array($this->_action, $requireSecure) || $this->requireSecure == array('*')) { + if (!$this->RequestHandler->isSSL()) { + if (!$this->blackHole($controller, 'secure')) { + return null; + } + } + } + } + return true; + } + +/** + * Check if authentication is required + * + * @param object $controller Instantiating controller + * @return bool true if authentication required + * @access protected + */ + function _authRequired(&$controller) { + if (is_array($this->requireAuth) && !empty($this->requireAuth) && !empty($controller->data)) { + $requireAuth = array_map('strtolower', $this->requireAuth); + + if (in_array($this->_action, $requireAuth) || $this->requireAuth == array('*')) { + if (!isset($controller->data['_Token'] )) { + if (!$this->blackHole($controller, 'auth')) { + return null; + } + } + + if ($this->Session->check('_Token')) { + $tData = unserialize($this->Session->read('_Token')); + + if (!empty($tData['allowedControllers']) && !in_array($controller->params['controller'], $tData['allowedControllers']) || !empty($tData['allowedActions']) && !in_array($controller->params['action'], $tData['allowedActions'])) { + if (!$this->blackHole($controller, 'auth')) { + return null; + } + } + } else { + if (!$this->blackHole($controller, 'auth')) { + return null; + } + } + } + } + return true; + } + +/** + * Check if login is required + * + * @param object $controller Instantiating controller + * @return bool true if login is required + * @access protected + */ + function _loginRequired(&$controller) { + if (is_array($this->requireLogin) && !empty($this->requireLogin)) { + $requireLogin = array_map('strtolower', $this->requireLogin); + + if (in_array($this->_action, $requireLogin) || $this->requireLogin == array('*')) { + $login = $this->loginCredentials($this->loginOptions['type']); + + if ($login == null) { + $controller->header($this->loginRequest()); + + if (!empty($this->loginOptions['prompt'])) { + $this->_callback($controller, $this->loginOptions['prompt']); + } else { + $this->blackHole($controller, 'login'); + } + } else { + if (isset($this->loginOptions['login'])) { + $this->_callback($controller, $this->loginOptions['login'], array($login)); + } else { + if (strtolower($this->loginOptions['type']) == 'digest') { + if ($login && isset($this->loginUsers[$login['username']])) { + if ($login['response'] == $this->generateDigestResponseHash($login)) { + return true; + } + } + $this->blackHole($controller, 'login'); + } else { + if ( + !(in_array($login['username'], array_keys($this->loginUsers)) && + $this->loginUsers[$login['username']] == $login['password']) + ) { + $this->blackHole($controller, 'login'); + } + } + } + } + } + } + return true; + } + +/** + * Validate submitted form + * + * @param object $controller Instantiating controller + * @return bool true if submitted form is valid + * @access protected + */ + function _validatePost(&$controller) { + if (empty($controller->data)) { + return true; + } + $data = $controller->data; + + if (!isset($data['_Token']) || !isset($data['_Token']['fields']) || !isset($data['_Token']['key'])) { + return false; + } + $token = $data['_Token']['key']; + + if ($this->Session->check('_Token')) { + $tokenData = unserialize($this->Session->read('_Token')); + + if ($tokenData['expires'] < time() || $tokenData['key'] !== $token) { + return false; + } + } + + $locked = null; + $check = $controller->data; + $token = urldecode($check['_Token']['fields']); + + if (strpos($token, ':')) { + list($token, $locked) = explode(':', $token, 2); + } + unset($check['_Token']); + + $locked = explode('|', $locked); + + $lockedFields = array(); + $fields = Set::flatten($check); + $fieldList = array_keys($fields); + $multi = array(); + + foreach ($fieldList as $i => $key) { + if (preg_match('/\.\d+$/', $key)) { + $multi[$i] = preg_replace('/\.\d+$/', '', $key); + unset($fieldList[$i]); + } + } + if (!empty($multi)) { + $fieldList += array_unique($multi); + } + + foreach ($fieldList as $i => $key) { + $isDisabled = false; + $isLocked = (is_array($locked) && in_array($key, $locked)); + + if (!empty($this->disabledFields)) { + foreach ((array)$this->disabledFields as $disabled) { + $disabled = explode('.', $disabled); + $field = array_values(array_intersect(explode('.', $key), $disabled)); + $isDisabled = ($field === $disabled); + if ($isDisabled) { + break; + } + } + } + + if ($isDisabled || $isLocked) { + unset($fieldList[$i]); + if ($isLocked) { + $lockedFields[$key] = $fields[$key]; + } + } + } + sort($fieldList, SORT_STRING); + ksort($lockedFields, SORT_STRING); + + $fieldList += $lockedFields; + $check = Security::hash(serialize($fieldList) . Configure::read('Security.salt')); + return ($token === $check); + } + +/** + * Add authentication key for new form posts + * + * @param object $controller Instantiating controller + * @return bool Success + * @access protected + */ + function _generateToken(&$controller) { + if (isset($controller->params['requested']) && $controller->params['requested'] === 1) { + if ($this->Session->check('_Token')) { + $tokenData = unserialize($this->Session->read('_Token')); + $controller->params['_Token'] = $tokenData; + } + return false; + } + $authKey = Security::generateAuthKey(); + $expires = strtotime('+' . Security::inactiveMins() . ' minutes'); + $token = array( + 'key' => $authKey, + 'expires' => $expires, + 'allowedControllers' => $this->allowedControllers, + 'allowedActions' => $this->allowedActions, + 'disabledFields' => $this->disabledFields + ); + + if (!isset($controller->data)) { + $controller->data = array(); + } + + if ($this->Session->check('_Token')) { + $tokenData = unserialize($this->Session->read('_Token')); + $valid = ( + isset($tokenData['expires']) && + $tokenData['expires'] > time() && + isset($tokenData['key']) + ); + + if ($valid) { + $token['key'] = $tokenData['key']; + } + } + $controller->params['_Token'] = $token; + $this->Session->write('_Token', serialize($token)); + return true; + } + +/** + * Sets the default login options for an HTTP-authenticated request + * + * @param array $options Default login options + * @return void + * @access protected + */ + function _setLoginDefaults(&$options) { + $options = array_merge(array( + 'type' => 'basic', + 'realm' => env('SERVER_NAME'), + 'qop' => 'auth', + 'nonce' => String::uuid() + ), array_filter($options)); + $options = array_merge(array('opaque' => md5($options['realm'])), $options); + } + +/** + * Calls a controller callback method + * + * @param object $controller Controller to run callback on + * @param string $method Method to execute + * @param array $params Parameters to send to method + * @return mixed Controller callback method's response + * @access protected + */ + function _callback(&$controller, $method, $params = array()) { + if (is_callable(array($controller, $method))) { + return call_user_func_array(array(&$controller, $method), empty($params) ? null : $params); + } else { + return null; + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/session.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/session.php new file mode 100644 index 000000000..60af59b38 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/components/session.php @@ -0,0 +1,295 @@ +__active = false; + } + } + +/** + * Startup method. + * + * @param object $controller Instantiating controller + * @return void + * @access public + */ + function startup(&$controller) { + if ($this->started() === false && $this->__active === true) { + $this->__start(); + } + } + +/** + * Starts Session on if 'Session.start' is set to false in core.php + * + * @param string $base The base path for the Session + * @return void + * @access public + */ + function activate($base = null) { + if ($this->__active === true) { + return; + } + parent::__construct($base); + $this->__active = true; + } + +/** + * Used to write a value to a session key. + * + * In your controller: $this->Session->write('Controller.sessKey', 'session value'); + * + * @param string $name The name of the key your are setting in the session. + * This should be in a Controller.key format for better organizing + * @param string $value The value you want to store in a session. + * @return boolean Success + * @access public + * @link http://book.cakephp.org/view/1312/write + */ + function write($name, $value = null) { + if ($this->__active === true) { + $this->__start(); + if (is_array($name)) { + foreach ($name as $key => $value) { + if (parent::write($key, $value) === false) { + return false; + } + } + return true; + } + if (parent::write($name, $value) === false) { + return false; + } + return true; + } + return false; + } + +/** + * Used to read a session values for a key or return values for all keys. + * + * In your controller: $this->Session->read('Controller.sessKey'); + * Calling the method without a param will return all session vars + * + * @param string $name the name of the session key you want to read + * @return mixed value from the session vars + * @access public + * @link http://book.cakephp.org/view/1314/read + */ + function read($name = null) { + if ($this->__active === true) { + $this->__start(); + return parent::read($name); + } + return false; + } + +/** + * Wrapper for SessionComponent::del(); + * + * In your controller: $this->Session->delete('Controller.sessKey'); + * + * @param string $name the name of the session key you want to delete + * @return boolean true is session variable is set and can be deleted, false is variable was not set. + * @access public + * @link http://book.cakephp.org/view/1316/delete + */ + function delete($name) { + if ($this->__active === true) { + $this->__start(); + return parent::delete($name); + } + return false; + } + +/** + * Used to check if a session variable is set + * + * In your controller: $this->Session->check('Controller.sessKey'); + * + * @param string $name the name of the session key you want to check + * @return boolean true is session variable is set, false if not + * @access public + * @link http://book.cakephp.org/view/1315/check + */ + function check($name) { + if ($this->__active === true) { + $this->__start(); + return parent::check($name); + } + return false; + } + +/** + * Used to determine the last error in a session. + * + * In your controller: $this->Session->error(); + * + * @return string Last session error + * @access public + * @link http://book.cakephp.org/view/1318/error + */ + function error() { + if ($this->__active === true) { + $this->__start(); + return parent::error(); + } + return false; + } + +/** + * Used to set a session variable that can be used to output messages in the view. + * + * In your controller: $this->Session->setFlash('This has been saved'); + * + * Additional params below can be passed to customize the output, or the Message.[key] + * + * @param string $message Message to be flashed + * @param string $element Element to wrap flash message in. + * @param array $params Parameters to be sent to layout as view variables + * @param string $key Message key, default is 'flash' + * @access public + * @link http://book.cakephp.org/view/1313/setFlash + */ + function setFlash($message, $element = 'default', $params = array(), $key = 'flash') { + if ($this->__active === true) { + $this->__start(); + $this->write('Message.' . $key, compact('message', 'element', 'params')); + } + } + +/** + * Used to renew a session id + * + * In your controller: $this->Session->renew(); + * + * @return void + * @access public + */ + function renew() { + if ($this->__active === true) { + $this->__start(); + parent::renew(); + } + } + +/** + * Used to check for a valid session. + * + * In your controller: $this->Session->valid(); + * + * @return boolean true is session is valid, false is session is invalid + * @access public + */ + function valid() { + if ($this->__active === true) { + $this->__start(); + return parent::valid(); + } + return false; + } + +/** + * Used to destroy sessions + * + * In your controller: $this->Session->destroy(); + * + * @return void + * @access public + * @link http://book.cakephp.org/view/1317/destroy + */ + function destroy() { + if ($this->__active === true) { + $this->__start(); + parent::destroy(); + } + } + +/** + * Returns Session id + * + * If $id is passed in a beforeFilter, the Session will be started + * with the specified id + * + * @param $id string + * @return string + * @access public + */ + function id($id = null) { + return parent::id($id); + } + +/** + * Starts Session if SessionComponent is used in Controller::beforeFilter(), + * or is called from + * + * @return boolean + * @access private + */ + function __start() { + if ($this->started() === false) { + if (!$this->id() && parent::start()) { + parent::_checkValid(); + } else { + parent::start(); + } + } + return $this->started(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/controller.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/controller.php new file mode 100644 index 000000000..d0cad823b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/controller.php @@ -0,0 +1,1321 @@ +data['ModelName']['fieldName']` pattern. + * + * @var array + * @access public + */ + var $data = array(); + +/** + * Holds pagination defaults for controller actions. The keys that can be included + * in this array are: 'conditions', 'fields', 'order', 'limit', 'page', and 'recursive', + * similar to the keys in the second parameter of Model::find(). + * + * Pagination defaults can also be supplied in a model-by-model basis by using + * the name of the model as a key for a pagination array: + * + * {{{ + * var $paginate = array( + * 'Post' => array(...), + * 'Comment' => array(...) + * ); + * }}} + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1231/Pagination + */ + var $paginate = array('limit' => 20, 'page' => 1); + +/** + * The name of the views subfolder containing views for this controller. + * + * @var string + * @access public + */ + var $viewPath = null; + +/** + * The name of the layouts subfolder containing layouts for this controller. + * + * @var string + * @access public + */ + var $layoutPath = null; + +/** + * Contains variables to be handed to the view. + * + * @var array + * @access public + */ + var $viewVars = array(); + +/** + * An array containing the class names of the models this controller uses. + * + * @var array Array of model objects. + * @access public + */ + var $modelNames = array(); + +/** + * Base URL path. + * + * @var string + * @access public + */ + var $base = null; + +/** + * The name of the layout file to render the view inside of. The name specified + * is the filename of the layout in /app/views/layouts without the .ctp + * extension. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/962/Page-related-Attributes-layout-and-pageTitle + */ + var $layout = 'default'; + +/** + * Set to true to automatically render the view + * after action logic. + * + * @var boolean + * @access public + */ + var $autoRender = true; + +/** + * Set to true to automatically render the layout around views. + * + * @var boolean + * @access public + */ + var $autoLayout = true; + +/** + * Instance of Component used to handle callbacks. + * + * @var string + * @access public + */ + var $Component = null; + +/** + * Array containing the names of components this controller uses. Component names + * should not contain the "Component" portion of the classname. + * + * Example: `var $components = array('Session', 'RequestHandler', 'Acl');` + * + * @var array + * @access public + * @link http://book.cakephp.org/view/961/components-helpers-and-uses + */ + var $components = array('Session'); + +/** + * The name of the View class this controller sends output to. + * + * @var string + * @access public + */ + var $view = 'View'; + +/** + * File extension for view templates. Defaults to Cake's conventional ".ctp". + * + * @var string + * @access public + */ + var $ext = '.ctp'; + +/** + * The output of the requested action. Contains either a variable + * returned from the action, or the data of the rendered view; + * You can use this var in child controllers' afterFilter() callbacks to alter output. + * + * @var string + * @access public + */ + var $output = null; + +/** + * Automatically set to the name of a plugin. + * + * @var string + * @access public + */ + var $plugin = null; + +/** + * Used to define methods a controller that will be cached. To cache a + * single action, the value is set to an array containing keys that match + * action names and values that denote cache expiration times (in seconds). + * + * Example: + * + * {{{ + * var $cacheAction = array( + * 'view/23/' => 21600, + * 'recalled/' => 86400 + * ); + * }}} + * + * $cacheAction can also be set to a strtotime() compatible string. This + * marks all the actions in the controller for view caching. + * + * @var mixed + * @access public + * @link http://book.cakephp.org/view/1380/Caching-in-the-Controller + */ + var $cacheAction = false; + +/** + * Used to create cached instances of models a controller uses. + * When set to true, all models related to the controller will be cached. + * This can increase performance in many cases. + * + * @var boolean + * @access public + */ + var $persistModel = false; + +/** + * Holds all params passed and named. + * + * @var mixed + * @access public + */ + var $passedArgs = array(); + +/** + * Triggers Scaffolding + * + * @var mixed + * @access public + * @link http://book.cakephp.org/view/1103/Scaffolding + */ + var $scaffold = false; + +/** + * Holds current methods of the controller + * + * @var array + * @access public + * @link + */ + var $methods = array(); + +/** + * This controller's primary model class name, the Inflector::classify()'ed version of + * the controller's $name property. + * + * Example: For a controller named 'Comments', the modelClass would be 'Comment' + * + * @var string + * @access public + */ + var $modelClass = null; + +/** + * This controller's model key name, an underscored version of the controller's $modelClass property. + * + * Example: For a controller named 'ArticleComments', the modelKey would be 'article_comment' + * + * @var string + * @access public + */ + var $modelKey = null; + +/** + * Holds any validation errors produced by the last call of the validateErrors() method/ + * + * @var array Validation errors, or false if none + * @access public + */ + var $validationErrors = null; + +/** + * Contains a list of the HTTP codes that CakePHP recognizes. These may be + * queried and/or modified through Controller::httpCodes(), which is also + * tasked with their lazy-loading. + * + * @var array Associative array of HTTP codes and their associated messages. + * @access private + */ + var $__httpCodes = null; + +/** + * Constructor. + * + */ + function __construct() { + if ($this->name === null) { + $r = null; + if (!preg_match('/(.*)Controller/i', get_class($this), $r)) { + __("Controller::__construct() : Can not get or parse my own class name, exiting."); + $this->_stop(); + } + $this->name = $r[1]; + } + + if ($this->viewPath == null) { + $this->viewPath = Inflector::underscore($this->name); + } + $this->modelClass = Inflector::classify($this->name); + $this->modelKey = Inflector::underscore($this->modelClass); + $this->Component =& new Component(); + + $childMethods = get_class_methods($this); + $parentMethods = get_class_methods('Controller'); + + foreach ($childMethods as $key => $value) { + $childMethods[$key] = strtolower($value); + } + + foreach ($parentMethods as $key => $value) { + $parentMethods[$key] = strtolower($value); + } + $this->methods = array_diff($childMethods, $parentMethods); + parent::__construct(); + } + +/** + * Merge components, helpers, and uses vars from AppController and PluginAppController. + * + * @return void + * @access protected + */ + function __mergeVars() { + $pluginName = Inflector::camelize($this->plugin); + $pluginController = $pluginName . 'AppController'; + + if (is_subclass_of($this, 'AppController') || is_subclass_of($this, $pluginController)) { + $appVars = get_class_vars('AppController'); + $uses = $appVars['uses']; + $merge = array('components', 'helpers'); + $plugin = null; + + if (!empty($this->plugin)) { + $plugin = $pluginName . '.'; + if (!is_subclass_of($this, $pluginController)) { + $pluginController = null; + } + } else { + $pluginController = null; + } + + if ($uses == $this->uses && !empty($this->uses)) { + if (!in_array($plugin . $this->modelClass, $this->uses)) { + array_unshift($this->uses, $plugin . $this->modelClass); + } elseif ($this->uses[0] !== $plugin . $this->modelClass) { + $this->uses = array_flip($this->uses); + unset($this->uses[$plugin . $this->modelClass]); + $this->uses = array_flip($this->uses); + array_unshift($this->uses, $plugin . $this->modelClass); + } + } elseif ($this->uses !== null || $this->uses !== false) { + $merge[] = 'uses'; + } + + foreach ($merge as $var) { + if (!empty($appVars[$var]) && is_array($this->{$var})) { + if ($var !== 'uses') { + $normal = Set::normalize($this->{$var}); + $app = Set::normalize($appVars[$var]); + if ($app !== $normal) { + $this->{$var} = Set::merge($app, $normal); + } + } else { + $this->{$var} = array_merge($this->{$var}, array_diff($appVars[$var], $this->{$var})); + } + } + } + } + + if ($pluginController && $pluginName != null) { + $appVars = get_class_vars($pluginController); + $uses = $appVars['uses']; + $merge = array('components', 'helpers'); + + if ($this->uses !== null || $this->uses !== false) { + $merge[] = 'uses'; + } + + foreach ($merge as $var) { + if (isset($appVars[$var]) && !empty($appVars[$var]) && is_array($this->{$var})) { + if ($var !== 'uses') { + $normal = Set::normalize($this->{$var}); + $app = Set::normalize($appVars[$var]); + if ($app !== $normal) { + $this->{$var} = Set::merge($app, $normal); + } + } else { + $this->{$var} = array_merge($this->{$var}, array_diff($appVars[$var], $this->{$var})); + } + } + } + } + } + +/** + * Loads Model classes based on the uses property + * see Controller::loadModel(); for more info. + * Loads Components and prepares them for initialization. + * + * @return mixed true if models found and instance created, or cakeError if models not found. + * @access public + * @see Controller::loadModel() + * @link http://book.cakephp.org/view/977/Controller-Methods#constructClasses-986 + */ + function constructClasses() { + $this->__mergeVars(); + $this->Component->init($this); + + if ($this->uses !== null || ($this->uses !== array())) { + if (empty($this->passedArgs) || !isset($this->passedArgs['0'])) { + $id = false; + } else { + $id = $this->passedArgs['0']; + } + + if ($this->uses === false) { + $this->loadModel($this->modelClass, $id); + } elseif ($this->uses) { + $uses = is_array($this->uses) ? $this->uses : array($this->uses); + $modelClassName = $uses[0]; + if (strpos($uses[0], '.') !== false) { + list($plugin, $modelClassName) = explode('.', $uses[0]); + } + $this->modelClass = $modelClassName; + foreach ($uses as $modelClass) { + $this->loadModel($modelClass); + } + } + } + return true; + } + +/** + * Perform the startup process for this controller. + * Fire the Component and Controller callbacks in the correct order. + * + * - Initializes components, which fires their `initialize` callback + * - Calls the controller `beforeFilter`. + * - triggers Component `startup` methods. + * + * @return void + * @access public + */ + function startupProcess() { + $this->Component->initialize($this); + $this->beforeFilter(); + $this->Component->triggerCallback('startup', $this); + } + +/** + * Perform the various shutdown processes for this controller. + * Fire the Component and Controller callbacks in the correct order. + * + * - triggers the component `shutdown` callback. + * - calls the Controller's `afterFilter` method. + * + * @return void + * @access public + */ + function shutdownProcess() { + $this->Component->triggerCallback('shutdown', $this); + $this->afterFilter(); + } + +/** + * Queries & sets valid HTTP response codes & messages. + * + * @param mixed $code If $code is an integer, then the corresponding code/message is + * returned if it exists, null if it does not exist. If $code is an array, + * then the 'code' and 'message' keys of each nested array are added to the default + * HTTP codes. Example: + * + * httpCodes(404); // returns array(404 => 'Not Found') + * + * httpCodes(array( + * 701 => 'Unicorn Moved', + * 800 => 'Unexpected Minotaur' + * )); // sets these new values, and returns true + * + * @return mixed Associative array of the HTTP codes as keys, and the message + * strings as values, or null of the given $code does not exist. + */ + function httpCodes($code = null) { + if (empty($this->__httpCodes)) { + $this->__httpCodes = array( + 100 => 'Continue', 101 => 'Switching Protocols', + 200 => 'OK', 201 => 'Created', 202 => 'Accepted', + 203 => 'Non-Authoritative Information', 204 => 'No Content', + 205 => 'Reset Content', 206 => 'Partial Content', + 300 => 'Multiple Choices', 301 => 'Moved Permanently', + 302 => 'Found', 303 => 'See Other', + 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', + 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', + 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', + 411 => 'Length Required', 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', 500 => 'Internal Server Error', + 501 => 'Not Implemented', 502 => 'Bad Gateway', + 503 => 'Service Unavailable', 504 => 'Gateway Time-out' + ); + } + + if (empty($code)) { + return $this->__httpCodes; + } + + if (is_array($code)) { + $this->__httpCodes = $code + $this->__httpCodes; + return true; + } + + if (!isset($this->__httpCodes[$code])) { + return null; + } + return array($code => $this->__httpCodes[$code]); + } + +/** + * Loads and instantiates models required by this controller. + * If Controller::$persistModel; is true, controller will cache model instances on first request, + * additional request will used cached models. + * If the model is non existent, it will throw a missing database table error, as Cake generates + * dynamic models for the time being. + * + * @param string $modelClass Name of model class to load + * @param mixed $id Initial ID the instanced model class should have + * @return mixed true when single model found and instance created, error returned if model not found. + * @access public + */ + function loadModel($modelClass = null, $id = null) { + if ($modelClass === null) { + $modelClass = $this->modelClass; + } + $cached = false; + $object = null; + $plugin = null; + if ($this->uses === false) { + if ($this->plugin) { + $plugin = $this->plugin . '.'; + } + } + list($plugin, $modelClass) = pluginSplit($modelClass, true, $plugin); + + if ($this->persistModel === true) { + $cached = $this->_persist($modelClass, null, $object); + } + + if (($cached === false)) { + $this->modelNames[] = $modelClass; + + if (!PHP5) { + $this->{$modelClass} =& ClassRegistry::init(array( + 'class' => $plugin . $modelClass, 'alias' => $modelClass, 'id' => $id + )); + } else { + $this->{$modelClass} = ClassRegistry::init(array( + 'class' => $plugin . $modelClass, 'alias' => $modelClass, 'id' => $id + )); + } + + if (!$this->{$modelClass}) { + return $this->cakeError('missingModel', array(array( + 'className' => $modelClass, 'webroot' => '', 'base' => $this->base + ))); + } + + if ($this->persistModel === true) { + $this->_persist($modelClass, true, $this->{$modelClass}); + $registry =& ClassRegistry::getInstance(); + $this->_persist($modelClass . 'registry', true, $registry->__objects, 'registry'); + } + } else { + $this->_persist($modelClass . 'registry', true, $object, 'registry'); + $this->_persist($modelClass, true, $object); + $this->modelNames[] = $modelClass; + } + + return true; + } + +/** + * Redirects to given $url, after turning off $this->autoRender. + * Script execution is halted after the redirect. + * + * @param mixed $url A string or array-based URL pointing to another location within the app, + * or an absolute URL + * @param integer $status Optional HTTP status code (eg: 404) + * @param boolean $exit If true, exit() will be called after the redirect + * @return mixed void if $exit = false. Terminates script if $exit = true + * @access public + * @link http://book.cakephp.org/view/982/redirect + */ + function redirect($url, $status = null, $exit = true) { + $this->autoRender = false; + + if (is_array($status)) { + extract($status, EXTR_OVERWRITE); + } + $response = $this->Component->beforeRedirect($this, $url, $status, $exit); + + if ($response === false) { + return; + } + if (is_array($response)) { + foreach ($response as $resp) { + if (is_array($resp) && isset($resp['url'])) { + extract($resp, EXTR_OVERWRITE); + } elseif ($resp !== null) { + $url = $resp; + } + } + } + + if (function_exists('session_write_close')) { + session_write_close(); + } + + if (!empty($status)) { + $codes = $this->httpCodes(); + + if (is_string($status)) { + $codes = array_flip($codes); + } + + if (isset($codes[$status])) { + $code = $msg = $codes[$status]; + if (is_numeric($status)) { + $code = $status; + } + if (is_string($status)) { + $msg = $status; + } + $status = "HTTP/1.1 {$code} {$msg}"; + + } else { + $status = null; + } + $this->header($status); + } + + if ($url !== null) { + $this->header('Location: ' . Router::url($url, true)); + } + + if (!empty($status) && ($status >= 300 && $status < 400)) { + $this->header($status); + } + + if ($exit) { + $this->_stop(); + } + } + +/** + * Convenience and object wrapper method for header(). Useful when doing tests and + * asserting that particular headers have been set. + * + * @param string $status The header message that is being set. + * @return void + * @access public + */ + function header($status) { + header($status); + } + +/** + * Saves a variable for use inside a view template. + * + * @param mixed $one A string or an array of data. + * @param mixed $two Value in case $one is a string (which then works as the key). + * Unused if $one is an associative array, otherwise serves as the values to $one's keys. + * @return void + * @access public + * @link http://book.cakephp.org/view/979/set + */ + function set($one, $two = null) { + $data = array(); + + if (is_array($one)) { + if (is_array($two)) { + $data = array_combine($one, $two); + } else { + $data = $one; + } + } else { + $data = array($one => $two); + } + $this->viewVars = $data + $this->viewVars; + } + +/** + * Internally redirects one action to another. Does not perform another HTTP request unlike Controller::redirect() + * + * Examples: + * + * {{{ + * setAction('another_action'); + * setAction('action_with_parameters', $parameter1); + * }}} + * + * @param string $action The new action to be 'redirected' to + * @param mixed Any other parameters passed to this method will be passed as + * parameters to the new action. + * @return mixed Returns the return value of the called action + * @access public + */ + function setAction($action) { + $this->action = $action; + $args = func_get_args(); + unset($args[0]); + return call_user_func_array(array(&$this, $action), $args); + } + +/** + * Controller callback to tie into Auth component. + * Only called when AuthComponent::$authorize is set to 'controller'. + * + * @return bool true if authorized, false otherwise + * @access public + * @link http://book.cakephp.org/view/1275/authorize + */ + function isAuthorized() { + trigger_error(sprintf( + __('%sController::isAuthorized() is not defined.', true), $this->name + ), E_USER_WARNING); + return false; + } + +/** + * Returns number of errors in a submitted FORM. + * + * @return integer Number of errors + * @access public + */ + function validate() { + $args = func_get_args(); + $errors = call_user_func_array(array(&$this, 'validateErrors'), $args); + + if ($errors === false) { + return 0; + } + return count($errors); + } + +/** + * Validates models passed by parameters. Example: + * + * `$errors = $this->validateErrors($this->Article, $this->User);` + * + * @param mixed A list of models as a variable argument + * @return array Validation errors, or false if none + * @access public + */ + function validateErrors() { + $objects = func_get_args(); + + if (empty($objects)) { + return false; + } + + $errors = array(); + foreach ($objects as $object) { + if (isset($this->{$object->alias})) { + $object =& $this->{$object->alias}; + } + $object->set($object->data); + $errors = array_merge($errors, (array)$object->invalidFields()); + } + + return $this->validationErrors = (!empty($errors) ? $errors : false); + } + +/** + * Instantiates the correct view class, hands it its data, and uses it to render the view output. + * + * @param string $action Action name to render + * @param string $layout Layout to use + * @param string $file File to use for rendering + * @return string Full output string of view contents + * @access public + * @link http://book.cakephp.org/view/980/render + */ + function render($action = null, $layout = null, $file = null) { + $this->beforeRender(); + $this->Component->triggerCallback('beforeRender', $this); + + $viewClass = $this->view; + if ($this->view != 'View') { + list($plugin, $viewClass) = pluginSplit($viewClass); + $viewClass = $viewClass . 'View'; + App::import('View', $this->view); + } + + $this->params['models'] = $this->modelNames; + + if (Configure::read() > 2) { + $this->set('cakeDebug', $this); + } + + $View =& new $viewClass($this); + + if (!empty($this->modelNames)) { + $models = array(); + foreach ($this->modelNames as $currentModel) { + if (isset($this->$currentModel) && is_a($this->$currentModel, 'Model')) { + $models[] = Inflector::underscore($currentModel); + } + $isValidModel = ( + isset($this->$currentModel) && is_a($this->$currentModel, 'Model') && + !empty($this->$currentModel->validationErrors) + ); + if ($isValidModel) { + $View->validationErrors[Inflector::camelize($currentModel)] =& + $this->$currentModel->validationErrors; + } + } + $models = array_diff(ClassRegistry::keys(), $models); + foreach ($models as $currentModel) { + if (ClassRegistry::isKeySet($currentModel)) { + $currentObject =& ClassRegistry::getObject($currentModel); + if (is_a($currentObject, 'Model') && !empty($currentObject->validationErrors)) { + $View->validationErrors[Inflector::camelize($currentModel)] =& + $currentObject->validationErrors; + } + } + } + } + + $this->autoRender = false; + $this->output .= $View->render($action, $layout, $file); + + return $this->output; + } + +/** + * Returns the referring URL for this request. + * + * @param string $default Default URL to use if HTTP_REFERER cannot be read from headers + * @param boolean $local If true, restrict referring URLs to local server + * @return string Referring URL + * @access public + * @link http://book.cakephp.org/view/987/referer + */ + function referer($default = null, $local = false) { + $ref = env('HTTP_REFERER'); + if (!empty($ref) && defined('FULL_BASE_URL')) { + $base = FULL_BASE_URL . $this->webroot; + if (strpos($ref, $base) === 0) { + $return = substr($ref, strlen($base)); + if ($return[0] != '/') { + $return = '/'.$return; + } + return $return; + } elseif (!$local) { + return $ref; + } + } + + if ($default != null) { + $url = Router::url($default, true); + return $url; + } + return '/'; + } + +/** + * Forces the user's browser not to cache the results of the current request. + * + * @return void + * @access public + * @link http://book.cakephp.org/view/988/disableCache + */ + function disableCache() { + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + } + +/** + * Shows a message to the user for $pause seconds, then redirects to $url. + * Uses flash.ctp as the default layout for the message. + * Does not work if the current debug level is higher than 0. + * + * @param string $message Message to display to the user + * @param mixed $url Relative string or array-based URL to redirect to after the time expires + * @param integer $pause Time to show the message + * @param string $layout Layout you want to use, defaults to 'flash' + * @return void Renders flash layout + * @access public + * @link http://book.cakephp.org/view/983/flash + */ + function flash($message, $url, $pause = 1, $layout = 'flash') { + $this->autoRender = false; + $this->set('url', Router::url($url)); + $this->set('message', $message); + $this->set('pause', $pause); + $this->set('page_title', $message); + $this->render(false, $layout); + } + +/** + * Converts POST'ed form data to a model conditions array, suitable for use in a Model::find() call. + * + * @param array $data POST'ed data organized by model and field + * @param mixed $op A string containing an SQL comparison operator, or an array matching operators + * to fields + * @param string $bool SQL boolean operator: AND, OR, XOR, etc. + * @param boolean $exclusive If true, and $op is an array, fields not included in $op will not be + * included in the returned conditions + * @return array An array of model conditions + * @access public + * @link http://book.cakephp.org/view/989/postConditions + */ + function postConditions($data = array(), $op = null, $bool = 'AND', $exclusive = false) { + if (!is_array($data) || empty($data)) { + if (!empty($this->data)) { + $data = $this->data; + } else { + return null; + } + } + $cond = array(); + + if ($op === null) { + $op = ''; + } + + $arrayOp = is_array($op); + foreach ($data as $model => $fields) { + foreach ($fields as $field => $value) { + $key = $model.'.'.$field; + $fieldOp = $op; + if ($arrayOp) { + if (array_key_exists($key, $op)) { + $fieldOp = $op[$key]; + } elseif (array_key_exists($field, $op)) { + $fieldOp = $op[$field]; + } else { + $fieldOp = false; + } + } + if ($exclusive && $fieldOp === false) { + continue; + } + $fieldOp = strtoupper(trim($fieldOp)); + if ($fieldOp === 'LIKE') { + $key = $key.' LIKE'; + $value = '%'.$value.'%'; + } elseif ($fieldOp && $fieldOp != '=') { + $key = $key.' '.$fieldOp; + } + $cond[$key] = $value; + } + } + if ($bool != null && strtoupper($bool) != 'AND') { + $cond = array($bool => $cond); + } + return $cond; + } + +/** + * Handles automatic pagination of model records. + * + * @param mixed $object Model to paginate (e.g: model instance, or 'Model', or 'Model.InnerModel') + * @param mixed $scope Conditions to use while paginating + * @param array $whitelist List of allowed options for paging + * @return array Model query results + * @access public + * @link http://book.cakephp.org/view/1232/Controller-Setup + */ + function paginate($object = null, $scope = array(), $whitelist = array()) { + if (is_array($object)) { + $whitelist = $scope; + $scope = $object; + $object = null; + } + $assoc = null; + + if (is_string($object)) { + $assoc = null; + if (strpos($object, '.') !== false) { + list($object, $assoc) = pluginSplit($object); + } + + if ($assoc && isset($this->{$object}->{$assoc})) { + $object =& $this->{$object}->{$assoc}; + } elseif ( + $assoc && isset($this->{$this->modelClass}) && + isset($this->{$this->modelClass}->{$assoc} + )) { + $object =& $this->{$this->modelClass}->{$assoc}; + } elseif (isset($this->{$object})) { + $object =& $this->{$object}; + } elseif ( + isset($this->{$this->modelClass}) && isset($this->{$this->modelClass}->{$object} + )) { + $object =& $this->{$this->modelClass}->{$object}; + } + } elseif (empty($object) || $object === null) { + if (isset($this->{$this->modelClass})) { + $object =& $this->{$this->modelClass}; + } else { + $className = null; + $name = $this->uses[0]; + if (strpos($this->uses[0], '.') !== false) { + list($name, $className) = explode('.', $this->uses[0]); + } + if ($className) { + $object =& $this->{$className}; + } else { + $object =& $this->{$name}; + } + } + } + + if (!is_object($object)) { + trigger_error(sprintf( + __('Controller::paginate() - can\'t find model %1$s in controller %2$sController', + true + ), $object, $this->name + ), E_USER_WARNING); + return array(); + } + $options = array_merge($this->params, $this->params['url'], $this->passedArgs); + + if (isset($this->paginate[$object->alias])) { + $defaults = $this->paginate[$object->alias]; + } else { + $defaults = $this->paginate; + } + + if (isset($options['show'])) { + $options['limit'] = $options['show']; + } + + if (isset($options['sort'])) { + $direction = null; + if (isset($options['direction'])) { + $direction = strtolower($options['direction']); + } + if ($direction != 'asc' && $direction != 'desc') { + $direction = 'asc'; + } + $options['order'] = array($options['sort'] => $direction); + } + + if (!empty($options['order']) && is_array($options['order'])) { + $alias = $object->alias ; + $key = $field = key($options['order']); + + if (strpos($key, '.') !== false) { + list($alias, $field) = explode('.', $key); + } + $value = $options['order'][$key]; + unset($options['order'][$key]); + + if ($object->hasField($field)) { + $options['order'][$alias . '.' . $field] = $value; + } elseif ($object->hasField($field, true)) { + $options['order'][$field] = $value; + } elseif (isset($object->{$alias}) && $object->{$alias}->hasField($field)) { + $options['order'][$alias . '.' . $field] = $value; + } + } + $vars = array('fields', 'order', 'limit', 'page', 'recursive'); + $keys = array_keys($options); + $count = count($keys); + + for ($i = 0; $i < $count; $i++) { + if (!in_array($keys[$i], $vars, true)) { + unset($options[$keys[$i]]); + } + if (empty($whitelist) && ($keys[$i] === 'fields' || $keys[$i] === 'recursive')) { + unset($options[$keys[$i]]); + } elseif (!empty($whitelist) && !in_array($keys[$i], $whitelist)) { + unset($options[$keys[$i]]); + } + } + $conditions = $fields = $order = $limit = $page = $recursive = null; + + if (!isset($defaults['conditions'])) { + $defaults['conditions'] = array(); + } + + $type = 'all'; + + if (isset($defaults[0])) { + $type = $defaults[0]; + unset($defaults[0]); + } + + $options = array_merge(array('page' => 1, 'limit' => 20), $defaults, $options); + $options['limit'] = (int) $options['limit']; + if (empty($options['limit']) || $options['limit'] < 1) { + $options['limit'] = 1; + } + + extract($options); + + if (is_array($scope) && !empty($scope)) { + $conditions = array_merge($conditions, $scope); + } elseif (is_string($scope)) { + $conditions = array($conditions, $scope); + } + if ($recursive === null) { + $recursive = $object->recursive; + } + + $extra = array_diff_key($defaults, compact( + 'conditions', 'fields', 'order', 'limit', 'page', 'recursive' + )); + if ($type !== 'all') { + $extra['type'] = $type; + } + + if (method_exists($object, 'paginateCount')) { + $count = $object->paginateCount($conditions, $recursive, $extra); + } else { + $parameters = compact('conditions'); + if ($recursive != $object->recursive) { + $parameters['recursive'] = $recursive; + } + $count = $object->find('count', array_merge($parameters, $extra)); +// var_dump(array_merge($parameters, $extra)); + } + $pageCount = intval(ceil($count / $limit)); + + if ($page === 'last' || $page >= $pageCount) { + $options['page'] = $page = $pageCount; + } elseif (intval($page) < 1) { + $options['page'] = $page = 1; + } + $page = $options['page'] = (integer)$page; + + if (method_exists($object, 'paginate')) { + $results = $object->paginate( + $conditions, $fields, $order, $limit, $page, $recursive, $extra + ); + } else { + $parameters = compact('conditions', 'fields', 'order', 'limit', 'page'); + if ($recursive != $object->recursive) { + $parameters['recursive'] = $recursive; + } + $results = $object->find($type, array_merge($parameters, $extra)); + } + $paging = array( + 'page' => $page, + 'current' => count($results), + 'count' => $count, + 'prevPage' => ($page > 1), + 'nextPage' => ($count > ($page * $limit)), + 'pageCount' => $pageCount, + 'defaults' => array_merge(array('limit' => 20, 'step' => 1), $defaults), + 'options' => $options + ); + $this->params['paging'][$object->alias] = $paging; + + if (!in_array('Paginator', $this->helpers) && !array_key_exists('Paginator', $this->helpers)) { + $this->helpers[] = 'Paginator'; + } + return $results; + } + +/** + * Called before the controller action. + * + * @access public + * @link http://book.cakephp.org/view/984/Callbacks + */ + function beforeFilter() { + } + +/** + * Called after the controller action is run, but before the view is rendered. + * + * @access public + * @link http://book.cakephp.org/view/984/Callbacks + */ + function beforeRender() { + } + +/** + * Called after the controller action is run and rendered. + * + * @access public + * @link http://book.cakephp.org/view/984/Callbacks + */ + function afterFilter() { + } + +/** + * This method should be overridden in child classes. + * + * @param string $method name of method called example index, edit, etc. + * @return boolean Success + * @access protected + * @link http://book.cakephp.org/view/984/Callbacks + */ + function _beforeScaffold($method) { + return true; + } + +/** + * This method should be overridden in child classes. + * + * @param string $method name of method called either edit or update. + * @return boolean Success + * @access protected + * @link http://book.cakephp.org/view/984/Callbacks + */ + function _afterScaffoldSave($method) { + return true; + } + +/** + * This method should be overridden in child classes. + * + * @param string $method name of method called either edit or update. + * @return boolean Success + * @access protected + * @link http://book.cakephp.org/view/984/Callbacks + */ + function _afterScaffoldSaveError($method) { + return true; + } + +/** + * This method should be overridden in child classes. + * If not it will render a scaffold error. + * Method MUST return true in child classes + * + * @param string $method name of method called example index, edit, etc. + * @return boolean Success + * @access protected + * @link http://book.cakephp.org/view/984/Callbacks + */ + function _scaffoldError($method) { + return false; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/pages_controller.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/pages_controller.php new file mode 100644 index 000000000..6dcc2a3ec --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/pages_controller.php @@ -0,0 +1,85 @@ +redirect('/'); + } + $page = $subpage = $title_for_layout = null; + + if (!empty($path[0])) { + $page = $path[0]; + } + if (!empty($path[1])) { + $subpage = $path[1]; + } + if (!empty($path[$count - 1])) { + $title_for_layout = Inflector::humanize($path[$count - 1]); + } + $this->set(compact('page', 'subpage', 'title_for_layout')); + $this->render(implode('/', $path)); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/controller/scaffold.php b/code/ryzom/tools/server/www/webtt/cake/libs/controller/scaffold.php new file mode 100644 index 000000000..27b7632c8 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/controller/scaffold.php @@ -0,0 +1,615 @@ +controller =& $controller; + + $count = count($this->__passedVars); + for ($j = 0; $j < $count; $j++) { + $var = $this->__passedVars[$j]; + $this->{$var} = $controller->{$var}; + } + + $this->redirect = array('action' => 'index'); + + $this->modelClass = $controller->modelClass; + $this->modelKey = $controller->modelKey; + + if (!is_object($this->controller->{$this->modelClass})) { + return $this->cakeError('missingModel', array(array( + 'className' => $this->modelClass, 'webroot' => '', 'base' => $controller->base + ))); + } + + $this->ScaffoldModel =& $this->controller->{$this->modelClass}; + $this->scaffoldTitle = Inflector::humanize($this->viewPath); + $this->scaffoldActions = $controller->scaffold; + $title_for_layout = __('Scaffold :: ', true) . Inflector::humanize($this->action) . ' :: ' . $this->scaffoldTitle; + $modelClass = $this->controller->modelClass; + $primaryKey = $this->ScaffoldModel->primaryKey; + $displayField = $this->ScaffoldModel->displayField; + $singularVar = Inflector::variable($modelClass); + $pluralVar = Inflector::variable($this->controller->name); + $singularHumanName = Inflector::humanize(Inflector::underscore($modelClass)); + $pluralHumanName = Inflector::humanize(Inflector::underscore($this->controller->name)); + $scaffoldFields = array_keys($this->ScaffoldModel->schema()); + $associations = $this->__associations(); + + $this->controller->set(compact( + 'title_for_layout', 'modelClass', 'primaryKey', 'displayField', 'singularVar', 'pluralVar', + 'singularHumanName', 'pluralHumanName', 'scaffoldFields', 'associations' + )); + + if ($this->controller->view) { + $this->controller->view = 'Scaffold'; + } + $this->_validSession = ( + isset($this->controller->Session) && $this->controller->Session->valid() != false + ); + $this->__scaffold($params); + } + +/** + * Outputs the content of a scaffold method passing it through the Controller::afterFilter() + * + * @return void + * @access protected + */ + function _output() { + $this->controller->afterFilter(); + echo($this->controller->output); + } + +/** + * Renders a view action of scaffolded model. + * + * @param array $params Parameters for scaffolding + * @return mixed A rendered view of a row from Models database table + * @access private + */ + function __scaffoldView($params) { + if ($this->controller->_beforeScaffold('view')) { + + $message = sprintf(__("No id set for %s::view()", true), Inflector::humanize($this->modelKey)); + if (isset($params['pass'][0])) { + $this->ScaffoldModel->id = $params['pass'][0]; + } elseif ($this->_validSession) { + $this->controller->Session->setFlash($message); + $this->controller->redirect($this->redirect); + } else { + return $this->controller->flash($message, '/' . Inflector::underscore($this->controller->viewPath)); + } + $this->ScaffoldModel->recursive = 1; + $this->controller->data = $this->ScaffoldModel->read(); + $this->controller->set( + Inflector::variable($this->controller->modelClass), $this->controller->data + ); + $this->controller->render($this->action, $this->layout); + $this->_output(); + } elseif ($this->controller->_scaffoldError('view') === false) { + return $this->__scaffoldError(); + } + } + +/** + * Renders index action of scaffolded model. + * + * @param array $params Parameters for scaffolding + * @return mixed A rendered view listing rows from Models database table + * @access private + */ + function __scaffoldIndex($params) { + if ($this->controller->_beforeScaffold('index')) { + $this->ScaffoldModel->recursive = 0; + $this->controller->set( + Inflector::variable($this->controller->name), $this->controller->paginate() + ); + $this->controller->render($this->action, $this->layout); + $this->_output(); + } elseif ($this->controller->_scaffoldError('index') === false) { + return $this->__scaffoldError(); + } + } + +/** + * Renders an add or edit action for scaffolded model. + * + * @param string $action Action (add or edit) + * @return mixed A rendered view with a form to edit or add a record in the Models database table + * @access private + */ + function __scaffoldForm($action = 'edit') { + $this->controller->viewVars['scaffoldFields'] = array_merge( + $this->controller->viewVars['scaffoldFields'], + array_keys($this->ScaffoldModel->hasAndBelongsToMany) + ); + $this->controller->render($action, $this->layout); + $this->_output(); + } + +/** + * Saves or updates the scaffolded model. + * + * @param array $params Parameters for scaffolding + * @param string $action add or edt + * @return mixed Success on save/update, add/edit form if data is empty or error if save or update fails + * @access private + */ + function __scaffoldSave($params = array(), $action = 'edit') { + $formAction = 'edit'; + $success = __('updated', true); + if ($action === 'add') { + $formAction = 'add'; + $success = __('saved', true); + } + + if ($this->controller->_beforeScaffold($action)) { + if ($action == 'edit') { + if (isset($params['pass'][0])) { + $this->ScaffoldModel->id = $params['pass'][0]; + } + + if (!$this->ScaffoldModel->exists()) { + $message = sprintf(__("Invalid id for %s::edit()", true), Inflector::humanize($this->modelKey)); + if ($this->_validSession) { + $this->controller->Session->setFlash($message); + $this->controller->redirect($this->redirect); + } else { + $this->controller->flash($message, $this->redirect); + $this->_output(); + } + } + } + + if (!empty($this->controller->data)) { + if ($action == 'create') { + $this->ScaffoldModel->create(); + } + + if ($this->ScaffoldModel->save($this->controller->data)) { + if ($this->controller->_afterScaffoldSave($action)) { + $message = sprintf( + __('The %1$s has been %2$s', true), + Inflector::humanize($this->modelKey), + $success + ); + if ($this->_validSession) { + $this->controller->Session->setFlash($message); + $this->controller->redirect($this->redirect); + } else { + $this->controller->flash($message, $this->redirect); + return $this->_output(); + } + } else { + return $this->controller->_afterScaffoldSaveError($action); + } + } else { + if ($this->_validSession) { + $this->controller->Session->setFlash(__('Please correct errors below.', true)); + } + } + } + + if (empty($this->controller->data)) { + if ($this->ScaffoldModel->id) { + $this->controller->data = $this->ScaffoldModel->read(); + } else { + $this->controller->data = $this->ScaffoldModel->create(); + } + } + + foreach ($this->ScaffoldModel->belongsTo as $assocName => $assocData) { + $varName = Inflector::variable(Inflector::pluralize( + preg_replace('/(?:_id)$/', '', $assocData['foreignKey']) + )); + $this->controller->set($varName, $this->ScaffoldModel->{$assocName}->find('list')); + } + foreach ($this->ScaffoldModel->hasAndBelongsToMany as $assocName => $assocData) { + $varName = Inflector::variable(Inflector::pluralize($assocName)); + $this->controller->set($varName, $this->ScaffoldModel->{$assocName}->find('list')); + } + + return $this->__scaffoldForm($formAction); + } elseif ($this->controller->_scaffoldError($action) === false) { + return $this->__scaffoldError(); + } + } + +/** + * Performs a delete on given scaffolded Model. + * + * @param array $params Parameters for scaffolding + * @return mixed Success on delete, error if delete fails + * @access private + */ + function __scaffoldDelete($params = array()) { + if ($this->controller->_beforeScaffold('delete')) { + $message = sprintf( + __("No id set for %s::delete()", true), + Inflector::humanize($this->modelKey) + ); + if (isset($params['pass'][0])) { + $id = $params['pass'][0]; + } elseif ($this->_validSession) { + $this->controller->Session->setFlash($message); + $this->controller->redirect($this->redirect); + } else { + $this->controller->flash($message, $this->redirect); + return $this->_output(); + } + + if ($this->ScaffoldModel->delete($id)) { + $message = sprintf( + __('The %1$s with id: %2$d has been deleted.', true), + Inflector::humanize($this->modelClass), $id + ); + if ($this->_validSession) { + $this->controller->Session->setFlash($message); + $this->controller->redirect($this->redirect); + } else { + $this->controller->flash($message, $this->redirect); + return $this->_output(); + } + } else { + $message = sprintf( + __('There was an error deleting the %1$s with id: %2$d', true), + Inflector::humanize($this->modelClass), $id + ); + if ($this->_validSession) { + $this->controller->Session->setFlash($message); + $this->controller->redirect($this->redirect); + } else { + $this->controller->flash($message, $this->redirect); + return $this->_output(); + } + } + } elseif ($this->controller->_scaffoldError('delete') === false) { + return $this->__scaffoldError(); + } + } + +/** + * Show a scaffold error + * + * @return mixed A rendered view showing the error + * @access private + */ + function __scaffoldError() { + return $this->controller->render('error', $this->layout); + $this->_output(); + } + +/** + * When methods are now present in a controller + * scaffoldView is used to call default Scaffold methods if: + * `var $scaffold;` is placed in the controller's class definition. + * + * @param array $params Parameters for scaffolding + * @return mixed A rendered view of scaffold action, or showing the error + * @access private + */ + function __scaffold($params) { + $db = &ConnectionManager::getDataSource($this->ScaffoldModel->useDbConfig); + $prefixes = Configure::read('Routing.prefixes'); + $scaffoldPrefix = $this->scaffoldActions; + + if (isset($db)) { + if (empty($this->scaffoldActions)) { + $this->scaffoldActions = array( + 'index', 'list', 'view', 'add', 'create', 'edit', 'update', 'delete' + ); + } elseif (!empty($prefixes) && in_array($scaffoldPrefix, $prefixes)) { + $this->scaffoldActions = array( + $scaffoldPrefix . '_index', + $scaffoldPrefix . '_list', + $scaffoldPrefix . '_view', + $scaffoldPrefix . '_add', + $scaffoldPrefix . '_create', + $scaffoldPrefix . '_edit', + $scaffoldPrefix . '_update', + $scaffoldPrefix . '_delete' + ); + } + + if (in_array($params['action'], $this->scaffoldActions)) { + if (!empty($prefixes)) { + $params['action'] = str_replace($scaffoldPrefix . '_', '', $params['action']); + } + switch ($params['action']) { + case 'index': + case 'list': + $this->__scaffoldIndex($params); + break; + case 'view': + $this->__scaffoldView($params); + break; + case 'add': + case 'create': + $this->__scaffoldSave($params, 'add'); + break; + case 'edit': + case 'update': + $this->__scaffoldSave($params, 'edit'); + break; + case 'delete': + $this->__scaffoldDelete($params); + break; + } + } else { + return $this->cakeError('missingAction', array(array( + 'className' => $this->controller->name . "Controller", + 'base' => $this->controller->base, + 'action' => $this->action, + 'webroot' => $this->controller->webroot + ))); + } + } else { + return $this->cakeError('missingDatabase', array(array( + 'webroot' => $this->controller->webroot + ))); + } + } + +/** + * Returns associations for controllers models. + * + * @return array Associations for model + * @access private + */ + function __associations() { + $keys = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'); + $associations = array(); + + foreach ($keys as $key => $type) { + foreach ($this->ScaffoldModel->{$type} as $assocKey => $assocData) { + $associations[$type][$assocKey]['primaryKey'] = + $this->ScaffoldModel->{$assocKey}->primaryKey; + + $associations[$type][$assocKey]['displayField'] = + $this->ScaffoldModel->{$assocKey}->displayField; + + $associations[$type][$assocKey]['foreignKey'] = + $assocData['foreignKey']; + + $associations[$type][$assocKey]['controller'] = + Inflector::pluralize(Inflector::underscore($assocData['className'])); + + if ($type == 'hasAndBelongsToMany') { + $associations[$type][$assocKey]['with'] = $assocData['with']; + } + } + } + return $associations; + } +} + +/** + * Scaffold View. + * + * @package cake + * @subpackage cake.cake.libs.controller +*/ +if (!class_exists('ThemeView')) { + App::import('View', 'Theme'); +} + +/** + * ScaffoldView provides specific view file loading features for scaffolded views. + * + * @package cake.libs.view + */ +class ScaffoldView extends ThemeView { + +/** + * Override _getViewFileName Appends special scaffolding views in. + * + * @param string $name name of the view file to get. + * @return string action + * @access protected + */ + function _getViewFileName($name = null) { + if ($name === null) { + $name = $this->action; + } + $name = Inflector::underscore($name); + $prefixes = Configure::read('Routing.prefixes'); + + if (!empty($prefixes)) { + foreach ($prefixes as $prefix) { + if (strpos($name, $prefix . '_') !== false) { + $name = substr($name, strlen($prefix) + 1); + break; + } + } + } + + if ($name === 'add') { + $name = 'edit'; + } + + $scaffoldAction = 'scaffold.' . $name; + + if (!is_null($this->subDir)) { + $subDir = strtolower($this->subDir) . DS; + } else { + $subDir = null; + } + + $names[] = $this->viewPath . DS . $subDir . $scaffoldAction; + $names[] = 'scaffolds' . DS . $subDir . $name; + + $paths = $this->_paths($this->plugin); + $exts = array($this->ext); + if ($this->ext !== '.ctp') { + array_push($exts, '.ctp'); + } + foreach ($exts as $ext) { + foreach ($paths as $path) { + foreach ($names as $name) { + if (file_exists($path . $name . $ext)) { + return $path . $name . $ext; + } + } + } + } + + if ($name === 'scaffolds' . DS . $subDir . 'error') { + return LIBS . 'view' . DS . 'errors' . DS . 'scaffold_error.ctp'; + } + + return $this->_missingView($paths[0] . $name . $this->ext, 'missingView'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/debugger.php b/code/ryzom/tools/server/www/webtt/cake/libs/debugger.php new file mode 100644 index 000000000..b66c7366c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/debugger.php @@ -0,0 +1,708 @@ + array( + 'trace' => '{:reference} - {:path}, line {:line}', + 'error' => "{:error} ({:code}): {:description} in [{:file}, line {:line}]" + ), + 'js' => array( + 'error' => '', + 'info' => '', + 'trace' => '
{:trace}
', + 'code' => '', + 'context' => '', + 'links' => array() + ), + 'html' => array( + 'trace' => '
Trace 

{:trace}

', + 'context' => '
Context 

{:context}

' + ), + 'txt' => array( + 'error' => "{:error}: {:code} :: {:description} on line {:line} of {:path}\n{:info}", + 'context' => "Context:\n{:context}\n", + 'trace' => "Trace:\n{:trace}\n", + 'code' => '', + 'info' => '' + ), + 'base' => array( + 'traceLine' => '{:reference} - {:path}, line {:line}' + ) + ); + +/** + * Holds current output data when outputFormat is false. + * + * @var string + * @access private + */ + var $_data = array(); + +/** + * Constructor. + * + */ + function __construct() { + $docRef = ini_get('docref_root'); + + if (empty($docRef) && function_exists('ini_set')) { + ini_set('docref_root', 'http://php.net/'); + } + if (!defined('E_RECOVERABLE_ERROR')) { + define('E_RECOVERABLE_ERROR', 4096); + } + if (!defined('E_DEPRECATED')) { + define('E_DEPRECATED', 8192); + } + + $e = '
';
+		$e .= '{:error} ({:code}): {:description} ';
+		$e .= '[{:path}, line {:line}]';
+
+		$e .= '';
+		$e .= '
'; + $this->_templates['js']['error'] = $e; + + $t = ''; + $this->_templates['js']['info'] = $t; + + $links = array(); + $link = 'Code'; + $links['code'] = $link; + + $link = 'Context'; + $links['context'] = $link; + + $links['help'] = 'Help'; + $this->_templates['js']['links'] = $links; + + $this->_templates['js']['context'] = '
_templates['js']['context'] .= 'style="display: none;">{:context}
'; + + $this->_templates['js']['code'] = '
_templates['js']['code'] .= 'style="display: none;">
{:code}
'; + + $e = '
{:error} ({:code}) : {:description} ';
+		$e .= '[{:path}, line {:line}]
'; + $this->_templates['html']['error'] = $e; + + $this->_templates['html']['context'] = '
Context ';
+		$this->_templates['html']['context'] .= '

{:context}

'; + } + +/** + * Returns a reference to the Debugger singleton object instance. + * + * @return object + * @access public + * @static + */ + function &getInstance($class = null) { + static $instance = array(); + if (!empty($class)) { + if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) { + $instance[0] = & new $class(); + if (Configure::read() > 0) { + Configure::version(); // Make sure the core config is loaded + $instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath'); + } + } + } + + if (!$instance) { + $instance[0] =& new Debugger(); + if (Configure::read() > 0) { + Configure::version(); // Make sure the core config is loaded + $instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath'); + } + } + return $instance[0]; + } + +/** + * Formats and outputs the contents of the supplied variable. + * + * @param $var mixed the variable to dump + * @return void + * @see Debugger::exportVar() + * @access public + * @static + * @link http://book.cakephp.org/view/1191/Using-the-Debugger-Class +*/ + function dump($var) { + $_this =& Debugger::getInstance(); + pr($_this->exportVar($var)); + } + +/** + * Creates an entry in the log file. The log entry will contain a stack trace from where it was called. + * as well as export the variable using exportVar. By default the log is written to the debug log. + * + * @param $var mixed Variable or content to log + * @param $level int type of log to use. Defaults to LOG_DEBUG + * @return void + * @static + * @link http://book.cakephp.org/view/1191/Using-the-Debugger-Class + */ + function log($var, $level = LOG_DEBUG) { + $_this =& Debugger::getInstance(); + $source = $_this->trace(array('start' => 1)) . "\n"; + CakeLog::write($level, "\n" . $source . $_this->exportVar($var)); + } + +/** + * Overrides PHP's default error handling. + * + * @param integer $code Code of error + * @param string $description Error description + * @param string $file File on which error occurred + * @param integer $line Line that triggered the error + * @param array $context Context + * @return boolean true if error was handled + * @access public + */ + function handleError($code, $description, $file = null, $line = null, $context = null) { + if (error_reporting() == 0 || $code === 2048 || $code === 8192) { + return; + } + + $_this =& Debugger::getInstance(); + + if (empty($file)) { + $file = '[internal]'; + } + if (empty($line)) { + $line = '??'; + } + $path = $_this->trimPath($file); + + $info = compact('code', 'description', 'file', 'line'); + if (!in_array($info, $_this->errors)) { + $_this->errors[] = $info; + } else { + return; + } + + switch ($code) { + case E_PARSE: + case E_ERROR: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + $error = 'Fatal Error'; + $level = LOG_ERROR; + break; + case E_WARNING: + case E_USER_WARNING: + case E_COMPILE_WARNING: + case E_RECOVERABLE_ERROR: + $error = 'Warning'; + $level = LOG_WARNING; + break; + case E_NOTICE: + case E_USER_NOTICE: + $error = 'Notice'; + $level = LOG_NOTICE; + break; + default: + return; + break; + } + + $helpCode = null; + if (!empty($_this->helpPath) && preg_match('/.*\[([0-9]+)\]$/', $description, $codes)) { + if (isset($codes[1])) { + $helpID = $codes[1]; + $description = trim(preg_replace('/\[[0-9]+\]$/', '', $description)); + } + } + + $data = compact( + 'level', 'error', 'code', 'helpID', 'description', 'file', 'path', 'line', 'context' + ); + echo $_this->_output($data); + + if (Configure::read('log')) { + $tpl = $_this->_templates['log']['error']; + $options = array('before' => '{:', 'after' => '}'); + CakeLog::write($level, String::insert($tpl, $data, $options)); + } + + if ($error == 'Fatal Error') { + exit(); + } + return true; + } + +/** + * Outputs a stack trace based on the supplied options. + * + * ### Options + * + * - `depth` - The number of stack frames to return. Defaults to 999 + * - `format` - The format you want the return. Defaults to the currently selected format. If + * format is 'array' or 'points' the return will be an array. + * - `args` - Should arguments for functions be shown? If true, the arguments for each method call + * will be displayed. + * - `start` - The stack frame to start generating a trace from. Defaults to 0 + * + * @param array $options Format for outputting stack trace + * @return mixed Formatted stack trace + * @access public + * @static + * @link http://book.cakephp.org/view/1191/Using-the-Debugger-Class + */ + function trace($options = array()) { + $_this =& Debugger::getInstance(); + $defaults = array( + 'depth' => 999, + 'format' => $_this->_outputFormat, + 'args' => false, + 'start' => 0, + 'scope' => null, + 'exclude' => null + ); + $options += $defaults; + + $backtrace = debug_backtrace(); + $count = count($backtrace); + $back = array(); + + $_trace = array( + 'line' => '??', + 'file' => '[internal]', + 'class' => null, + 'function' => '[main]' + ); + + for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) { + $trace = array_merge(array('file' => '[internal]', 'line' => '??'), $backtrace[$i]); + + if (isset($backtrace[$i + 1])) { + $next = array_merge($_trace, $backtrace[$i + 1]); + $reference = $next['function']; + + if (!empty($next['class'])) { + $reference = $next['class'] . '::' . $reference . '('; + if ($options['args'] && isset($next['args'])) { + $args = array(); + foreach ($next['args'] as $arg) { + $args[] = Debugger::exportVar($arg); + } + $reference .= join(', ', $args); + } + $reference .= ')'; + } + } else { + $reference = '[main]'; + } + if (in_array($reference, array('call_user_func_array', 'trigger_error'))) { + continue; + } + if ($options['format'] == 'points' && $trace['file'] != '[internal]') { + $back[] = array('file' => $trace['file'], 'line' => $trace['line']); + } elseif ($options['format'] == 'array') { + $back[] = $trace; + } else { + if (isset($_this->_templates[$options['format']]['traceLine'])) { + $tpl = $_this->_templates[$options['format']]['traceLine']; + } else { + $tpl = $_this->_templates['base']['traceLine']; + } + $trace['path'] = Debugger::trimPath($trace['file']); + $trace['reference'] = $reference; + unset($trace['object'], $trace['args']); + $back[] = String::insert($tpl, $trace, array('before' => '{:', 'after' => '}')); + } + } + + if ($options['format'] == 'array' || $options['format'] == 'points') { + return $back; + } + return implode("\n", $back); + } + +/** + * Shortens file paths by replacing the application base path with 'APP', and the CakePHP core + * path with 'CORE'. + * + * @param string $path Path to shorten + * @return string Normalized path + * @access public + * @static + */ + function trimPath($path) { + if (!defined('CAKE_CORE_INCLUDE_PATH') || !defined('APP')) { + return $path; + } + + if (strpos($path, APP) === 0) { + return str_replace(APP, 'APP' . DS, $path); + } elseif (strpos($path, CAKE_CORE_INCLUDE_PATH) === 0) { + return str_replace(CAKE_CORE_INCLUDE_PATH, 'CORE', $path); + } elseif (strpos($path, ROOT) === 0) { + return str_replace(ROOT, 'ROOT', $path); + } + $corePaths = App::core('cake'); + + foreach ($corePaths as $corePath) { + if (strpos($path, $corePath) === 0) { + return str_replace($corePath, 'CORE' .DS . 'cake' .DS, $path); + } + } + return $path; + } + +/** + * Grabs an excerpt from a file and highlights a given line of code + * + * @param string $file Absolute path to a PHP file + * @param integer $line Line number to highlight + * @param integer $context Number of lines of context to extract above and below $line + * @return array Set of lines highlighted + * @access public + * @static + * @link http://book.cakephp.org/view/1191/Using-the-Debugger-Class + */ + function excerpt($file, $line, $context = 2) { + $data = $lines = array(); + if (!file_exists($file)) { + return array(); + } + $data = @explode("\n", file_get_contents($file)); + + if (empty($data) || !isset($data[$line])) { + return; + } + for ($i = $line - ($context + 1); $i < $line + $context; $i++) { + if (!isset($data[$i])) { + continue; + } + $string = str_replace(array("\r\n", "\n"), "", highlight_string($data[$i], true)); + if ($i == $line) { + $lines[] = '' . $string . ''; + } else { + $lines[] = $string; + } + } + return $lines; + } + +/** + * Converts a variable to a string for debug output. + * + * @param string $var Variable to convert + * @return string Variable as a formatted string + * @access public + * @static + * @link http://book.cakephp.org/view/1191/Using-the-Debugger-Class + */ + function exportVar($var, $recursion = 0) { + $_this =& Debugger::getInstance(); + switch (strtolower(gettype($var))) { + case 'boolean': + return ($var) ? 'true' : 'false'; + break; + case 'integer': + case 'double': + return $var; + break; + case 'string': + if (trim($var) == "") { + return '""'; + } + return '"' . h($var) . '"'; + break; + case 'object': + return get_class($var) . "\n" . $_this->__object($var); + case 'array': + $out = "array("; + $vars = array(); + foreach ($var as $key => $val) { + if ($recursion >= 0) { + if (is_numeric($key)) { + $vars[] = "\n\t" . $_this->exportVar($val, $recursion - 1); + } else { + $vars[] = "\n\t" .$_this->exportVar($key, $recursion - 1) + . ' => ' . $_this->exportVar($val, $recursion - 1); + } + } + } + $n = null; + if (!empty($vars)) { + $n = "\n"; + } + return $out . implode(",", $vars) . "{$n})"; + break; + case 'resource': + return strtolower(gettype($var)); + break; + case 'null': + return 'null'; + break; + } + } + +/** + * Handles object to string conversion. + * + * @param string $var Object to convert + * @return string + * @access private + * @see Debugger::exportVar() + */ + function __object($var) { + $out = array(); + + if (is_object($var)) { + $className = get_class($var); + $objectVars = get_object_vars($var); + + foreach ($objectVars as $key => $value) { + if (is_object($value)) { + $value = get_class($value) . ' object'; + } elseif (is_array($value)) { + $value = 'array'; + } elseif ($value === null) { + $value = 'NULL'; + } elseif (in_array(gettype($value), array('boolean', 'integer', 'double', 'string', 'array', 'resource'))) { + $value = Debugger::exportVar($value); + } + $out[] = "$className::$$key = " . $value; + } + } + return implode("\n", $out); + } + +/** + * Switches output format, updates format strings + * + * @param string $format Format to use, including 'js' for JavaScript-enhanced HTML, 'html' for + * straight HTML output, or 'txt' for unformatted text. + * @param array $strings Template strings to be used for the output format. + * @access protected + */ + function output($format = null, $strings = array()) { + $_this =& Debugger::getInstance(); + $data = null; + + if (is_null($format)) { + return $_this->_outputFormat; + } + + if (!empty($strings)) { + if (isset($_this->_templates[$format])) { + if (isset($strings['links'])) { + $_this->_templates[$format]['links'] = array_merge( + $_this->_templates[$format]['links'], + $strings['links'] + ); + unset($strings['links']); + } + $_this->_templates[$format] = array_merge($_this->_templates[$format], $strings); + } else { + $_this->_templates[$format] = $strings; + } + return $_this->_templates[$format]; + } + + if ($format === true && !empty($_this->_data)) { + $data = $_this->_data; + $_this->_data = array(); + $format = false; + } + $_this->_outputFormat = $format; + + return $data; + } + +/** + * Renders error messages + * + * @param array $data Data about the current error + * @access private + */ + function _output($data = array()) { + $defaults = array( + 'level' => 0, + 'error' => 0, + 'code' => 0, + 'helpID' => null, + 'description' => '', + 'file' => '', + 'line' => 0, + 'context' => array() + ); + $data += $defaults; + + $files = $this->trace(array('start' => 2, 'format' => 'points')); + $code = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1); + $trace = $this->trace(array('start' => 2, 'depth' => '20')); + $insertOpts = array('before' => '{:', 'after' => '}'); + $context = array(); + $links = array(); + $info = ''; + + foreach ((array)$data['context'] as $var => $value) { + $context[] = "\${$var}\t=\t" . $this->exportVar($value, 1); + } + + switch ($this->_outputFormat) { + case false: + $this->_data[] = compact('context', 'trace') + $data; + return; + case 'log': + $this->log(compact('context', 'trace') + $data); + return; + } + + if (empty($this->_outputFormat) || !isset($this->_templates[$this->_outputFormat])) { + $this->_outputFormat = 'js'; + } + + $data['id'] = 'cakeErr' . count($this->errors); + $tpl = array_merge($this->_templates['base'], $this->_templates[$this->_outputFormat]); + $insert = array('context' => join("\n", $context), 'helpPath' => $this->helpPath) + $data; + + $detect = array('help' => 'helpID', 'context' => 'context'); + + if (isset($tpl['links'])) { + foreach ($tpl['links'] as $key => $val) { + if (isset($detect[$key]) && empty($insert[$detect[$key]])) { + continue; + } + $links[$key] = String::insert($val, $insert, $insertOpts); + } + } + + foreach (array('code', 'context', 'trace') as $key) { + if (empty($$key) || !isset($tpl[$key])) { + continue; + } + if (is_array($$key)) { + $$key = join("\n", $$key); + } + $info .= String::insert($tpl[$key], compact($key) + $insert, $insertOpts); + } + $links = join(' | ', $links); + unset($data['context']); + + echo String::insert($tpl['error'], compact('links', 'info') + $data, $insertOpts); + } + +/** + * Verifies that the application's salt and cipher seed value has been changed from the default value. + * + * @access public + * @static + */ + function checkSecurityKeys() { + if (Configure::read('Security.salt') == 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') { + trigger_error(__('Please change the value of \'Security.salt\' in app/config/core.php to a salt value specific to your application', true), E_USER_NOTICE); + } + + if (Configure::read('Security.cipherSeed') === '76859309657453542496749683645') { + trigger_error(__('Please change the value of \'Security.cipherSeed\' in app/config/core.php to a numeric (digits only) seed value specific to your application', true), E_USER_NOTICE); + } + } + +/** + * Invokes the given debugger object as the current error handler, taking over control from the + * previous handler in a stack-like hierarchy. + * + * @param object $debugger A reference to the Debugger object + * @access public + * @static + * @link http://book.cakephp.org/view/1191/Using-the-Debugger-Class + */ + function invoke(&$debugger) { + set_error_handler(array(&$debugger, 'handleError')); + } +} + +if (!defined('DISABLE_DEFAULT_ERROR_HANDLING')) { + Debugger::invoke(Debugger::getInstance()); +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/error.php b/code/ryzom/tools/server/www/webtt/cake/libs/error.php new file mode 100644 index 000000000..e8d7a0d6a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/error.php @@ -0,0 +1,462 @@ +_set(Router::getPaths()); + $this->params = Router::getParams(); + $this->constructClasses(); + $this->Component->initialize($this); + $this->_set(array('cacheAction' => false, 'viewPath' => 'errors')); + } +} + +/** + * Error Handler. + * + * Captures and handles all cakeError() calls. + * Displays helpful framework errors when debug > 1. + * When debug < 1 cakeError() will render 404 or 500 errors. + * + * @package cake + * @subpackage cake.cake.libs + */ +class ErrorHandler extends Object { + +/** + * Controller instance. + * + * @var Controller + * @access public + */ + var $controller = null; + +/** + * Class constructor. + * + * @param string $method Method producing the error + * @param array $messages Error messages + */ + function __construct($method, $messages) { + App::import('Core', 'Sanitize'); + static $__previousError = null; + + if ($__previousError != array($method, $messages)) { + $__previousError = array($method, $messages); + $this->controller =& new CakeErrorController(); + } else { + $this->controller =& new Controller(); + $this->controller->viewPath = 'errors'; + } + $options = array('escape' => false); + $messages = Sanitize::clean($messages, $options); + + if (!isset($messages[0])) { + $messages = array($messages); + } + + if (method_exists($this->controller, 'apperror')) { + return $this->controller->appError($method, $messages); + } + + if (!in_array(strtolower($method), array_map('strtolower', get_class_methods($this)))) { + $method = 'error'; + } + + if ($method !== 'error') { + if (Configure::read('debug') == 0) { + $parentClass = get_parent_class($this); + if (strtolower($parentClass) != 'errorhandler') { + $method = 'error404'; + } + $parentMethods = array_map('strtolower', get_class_methods($parentClass)); + if (in_array(strtolower($method), $parentMethods)) { + $method = 'error404'; + } + if (isset($messages[0]['code']) && $messages[0]['code'] == 500) { + $method = 'error500'; + } + } + } + $this->dispatchMethod($method, $messages); + $this->_stop(); + } + +/** + * Displays an error page (e.g. 404 Not found). + * + * @param array $params Parameters for controller + * @access public + */ + function error($params) { + extract($params, EXTR_OVERWRITE); + $this->controller->set(array( + 'code' => $code, + 'name' => $name, + 'message' => $message, + 'title' => $code . ' ' . $name + )); + $this->_outputMessage('error404'); + } + +/** + * Convenience method to display a 404 page. + * + * @param array $params Parameters for controller + * @access public + */ + function error404($params) { + extract($params, EXTR_OVERWRITE); + + if (!isset($url)) { + $url = $this->controller->here; + } + $url = Router::normalize($url); + $this->controller->header("HTTP/1.0 404 Not Found"); + $this->controller->set(array( + 'code' => '404', + 'name' => __('Not Found', true), + 'message' => h($url), + 'base' => $this->controller->base + )); + $this->_outputMessage('error404'); + } + +/** + * Convenience method to display a 500 page. + * + * @param array $params Parameters for controller + * @access public + */ + function error500($params) { + extract($params, EXTR_OVERWRITE); + + if (!isset($url)) { + $url = $this->controller->here; + } + $url = Router::normalize($url); + $this->controller->header("HTTP/1.0 500 Internal Server Error"); + $this->controller->set(array( + 'code' => '500', + 'name' => __('An Internal Error Has Occurred', true), + 'message' => h($url), + 'base' => $this->controller->base + )); + $this->_outputMessage('error500'); + } +/** + * Renders the Missing Controller web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingController($params) { + extract($params, EXTR_OVERWRITE); + + $controllerName = str_replace('Controller', '', $className); + $this->controller->set(array( + 'controller' => $className, + 'controllerName' => $controllerName, + 'title' => __('Missing Controller', true) + )); + $this->_outputMessage('missingController'); + } + +/** + * Renders the Missing Action web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingAction($params) { + extract($params, EXTR_OVERWRITE); + + $controllerName = str_replace('Controller', '', $className); + $this->controller->set(array( + 'controller' => $className, + 'controllerName' => $controllerName, + 'action' => $action, + 'title' => __('Missing Method in Controller', true) + )); + $this->_outputMessage('missingAction'); + } + +/** + * Renders the Private Action web page. + * + * @param array $params Parameters for controller + * @access public + */ + function privateAction($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'controller' => $className, + 'action' => $action, + 'title' => __('Trying to access private method in class', true) + )); + $this->_outputMessage('privateAction'); + } + +/** + * Renders the Missing Table web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingTable($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->header("HTTP/1.0 500 Internal Server Error"); + $this->controller->set(array( + 'code' => '500', + 'model' => $className, + 'table' => $table, + 'title' => __('Missing Database Table', true) + )); + $this->_outputMessage('missingTable'); + } + +/** + * Renders the Missing Database web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingDatabase($params = array()) { + $this->controller->header("HTTP/1.0 500 Internal Server Error"); + $this->controller->set(array( + 'code' => '500', + 'title' => __('Scaffold Missing Database Connection', true) + )); + $this->_outputMessage('missingScaffolddb'); + } + +/** + * Renders the Missing View web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingView($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'controller' => $className, + 'action' => $action, + 'file' => $file, + 'title' => __('Missing View', true) + )); + $this->_outputMessage('missingView'); + } + +/** + * Renders the Missing Layout web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingLayout($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->layout = 'default'; + $this->controller->set(array( + 'file' => $file, + 'title' => __('Missing Layout', true) + )); + $this->_outputMessage('missingLayout'); + } + +/** + * Renders the Database Connection web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingConnection($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->header("HTTP/1.0 500 Internal Server Error"); + $this->controller->set(array( + 'code' => '500', + 'model' => $className, + 'title' => __('Missing Database Connection', true) + )); + $this->_outputMessage('missingConnection'); + } + +/** + * Renders the Missing Helper file web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingHelperFile($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'helperClass' => Inflector::camelize($helper) . "Helper", + 'file' => $file, + 'title' => __('Missing Helper File', true) + )); + $this->_outputMessage('missingHelperFile'); + } + +/** + * Renders the Missing Helper class web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingHelperClass($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'helperClass' => Inflector::camelize($helper) . "Helper", + 'file' => $file, + 'title' => __('Missing Helper Class', true) + )); + $this->_outputMessage('missingHelperClass'); + } + +/** + * Renders the Missing Behavior file web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingBehaviorFile($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'behaviorClass' => Inflector::camelize($behavior) . "Behavior", + 'file' => $file, + 'title' => __('Missing Behavior File', true) + )); + $this->_outputMessage('missingBehaviorFile'); + } + +/** + * Renders the Missing Behavior class web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingBehaviorClass($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'behaviorClass' => Inflector::camelize($behavior) . "Behavior", + 'file' => $file, + 'title' => __('Missing Behavior Class', true) + )); + $this->_outputMessage('missingBehaviorClass'); + } + +/** + * Renders the Missing Component file web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingComponentFile($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'controller' => $className, + 'component' => $component, + 'file' => $file, + 'title' => __('Missing Component File', true) + )); + $this->_outputMessage('missingComponentFile'); + } + +/** + * Renders the Missing Component class web page. + * + * @param array $params Parameters for controller + * @access public + */ + function missingComponentClass($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'controller' => $className, + 'component' => $component, + 'file' => $file, + 'title' => __('Missing Component Class', true) + )); + $this->_outputMessage('missingComponentClass'); + } + +/** + * Renders the Missing Model class web page. + * + * @param unknown_type $params Parameters for controller + * @access public + */ + function missingModel($params) { + extract($params, EXTR_OVERWRITE); + + $this->controller->set(array( + 'model' => $className, + 'title' => __('Missing Model', true) + )); + $this->_outputMessage('missingModel'); + } + +/** + * Output message + * + * @access protected + */ + function _outputMessage($template) { + $this->controller->render($template); + $this->controller->afterFilter(); + echo $this->controller->output; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/file.php b/code/ryzom/tools/server/www/webtt/cake/libs/file.php new file mode 100644 index 000000000..86755a513 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/file.php @@ -0,0 +1,542 @@ +Folder =& new Folder(dirname($path), $create, $mode); + if (!is_dir($path)) { + $this->name = basename($path); + } + $this->pwd(); + $create && !$this->exists() && $this->safe($path) && $this->create(); + } + +/** + * Closes the current file if it is opened + * + */ + function __destruct() { + $this->close(); + } + +/** + * Creates the File. + * + * @return boolean Success + * @access public + */ + function create() { + $dir = $this->Folder->pwd(); + if (is_dir($dir) && is_writable($dir) && !$this->exists()) { + $old = umask(0); + if (touch($this->path)) { + umask($old); + return true; + } + } + return false; + } + +/** + * Opens the current file with a given $mode + * + * @param string $mode A valid 'fopen' mode string (r|w|a ...) + * @param boolean $force If true then the file will be re-opened even if its already opened, otherwise it won't + * @return boolean True on success, false on failure + * @access public + */ + function open($mode = 'r', $force = false) { + if (!$force && is_resource($this->handle)) { + return true; + } + clearstatcache(); + if ($this->exists() === false) { + if ($this->create() === false) { + return false; + } + } + + $this->handle = fopen($this->path, $mode); + if (is_resource($this->handle)) { + return true; + } + return false; + } + +/** + * Return the contents of this File as a string. + * + * @param string $bytes where to start + * @param string $mode A `fread` compatible mode. + * @param boolean $force If true then the file will be re-opened even if its already opened, otherwise it won't + * @return mixed string on success, false on failure + * @access public + */ + function read($bytes = false, $mode = 'rb', $force = false) { + if ($bytes === false && $this->lock === null) { + return file_get_contents($this->path); + } + if ($this->open($mode, $force) === false) { + return false; + } + if ($this->lock !== null && flock($this->handle, LOCK_SH) === false) { + return false; + } + if (is_int($bytes)) { + return fread($this->handle, $bytes); + } + + $data = ''; + while (!feof($this->handle)) { + $data .= fgets($this->handle, 4096); + } + + if ($this->lock !== null) { + flock($this->handle, LOCK_UN); + } + if ($bytes === false) { + $this->close(); + } + return trim($data); + } + +/** + * Sets or gets the offset for the currently opened file. + * + * @param mixed $offset The $offset in bytes to seek. If set to false then the current offset is returned. + * @param integer $seek PHP Constant SEEK_SET | SEEK_CUR | SEEK_END determining what the $offset is relative to + * @return mixed True on success, false on failure (set mode), false on failure or integer offset on success (get mode) + * @access public + */ + function offset($offset = false, $seek = SEEK_SET) { + if ($offset === false) { + if (is_resource($this->handle)) { + return ftell($this->handle); + } + } elseif ($this->open() === true) { + return fseek($this->handle, $offset, $seek) === 0; + } + return false; + } + +/** + * Prepares a ascii string for writing. Converts line endings to the + * correct terminator for the current platform. If windows "\r\n" will be used + * all other platforms will use "\n" + * + * @param string $data Data to prepare for writing. + * @return string The with converted line endings. + * @access public + */ + function prepare($data, $forceWindows = false) { + $lineBreak = "\n"; + if (DIRECTORY_SEPARATOR == '\\' || $forceWindows === true) { + $lineBreak = "\r\n"; + } + return strtr($data, array("\r\n" => $lineBreak, "\n" => $lineBreak, "\r" => $lineBreak)); + } + +/** + * Write given data to this File. + * + * @param string $data Data to write to this File. + * @param string $mode Mode of writing. {@link http://php.net/fwrite See fwrite()}. + * @param string $force force the file to open + * @return boolean Success + * @access public + */ + function write($data, $mode = 'w', $force = false) { + $success = false; + if ($this->open($mode, $force) === true) { + if ($this->lock !== null) { + if (flock($this->handle, LOCK_EX) === false) { + return false; + } + } + + if (fwrite($this->handle, $data) !== false) { + $success = true; + } + if ($this->lock !== null) { + flock($this->handle, LOCK_UN); + } + } + return $success; + } + +/** + * Append given data string to this File. + * + * @param string $data Data to write + * @param string $force force the file to open + * @return boolean Success + * @access public + */ + function append($data, $force = false) { + return $this->write($data, 'a', $force); + } + +/** + * Closes the current file if it is opened. + * + * @return boolean True if closing was successful or file was already closed, otherwise false + * @access public + */ + function close() { + if (!is_resource($this->handle)) { + return true; + } + return fclose($this->handle); + } + +/** + * Deletes the File. + * + * @return boolean Success + * @access public + */ + function delete() { + clearstatcache(); + if ($this->exists()) { + return unlink($this->path); + } + return false; + } + +/** + * Returns the File info. + * + * @return string The File extension + * @access public + */ + function info() { + if ($this->info == null) { + $this->info = pathinfo($this->path); + } + if (!isset($this->info['filename'])) { + $this->info['filename'] = $this->name(); + } + return $this->info; + } + +/** + * Returns the File extension. + * + * @return string The File extension + * @access public + */ + function ext() { + if ($this->info == null) { + $this->info(); + } + if (isset($this->info['extension'])) { + return $this->info['extension']; + } + return false; + } + +/** + * Returns the File name without extension. + * + * @return string The File name without extension. + * @access public + */ + function name() { + if ($this->info == null) { + $this->info(); + } + if (isset($this->info['extension'])) { + return basename($this->name, '.'.$this->info['extension']); + } elseif ($this->name) { + return $this->name; + } + return false; + } + +/** + * makes filename safe for saving + * + * @param string $name The name of the file to make safe if different from $this->name + * @param strin $ext The name of the extension to make safe if different from $this->ext + * @return string $ext the extension of the file + * @access public + */ + function safe($name = null, $ext = null) { + if (!$name) { + $name = $this->name; + } + if (!$ext) { + $ext = $this->ext(); + } + return preg_replace( "/(?:[^\w\.-]+)/", "_", basename($name, $ext)); + } + +/** + * Get md5 Checksum of file with previous check of Filesize + * + * @param mixed $maxsize in MB or true to force + * @return string md5 Checksum {@link http://php.net/md5_file See md5_file()} + * @access public + */ + function md5($maxsize = 5) { + if ($maxsize === true) { + return md5_file($this->path); + } + + $size = $this->size(); + if ($size && $size < ($maxsize * 1024) * 1024) { + return md5_file($this->path); + } + + return false; + } + +/** + * Returns the full path of the File. + * + * @return string Full path to file + * @access public + */ + function pwd() { + if (is_null($this->path)) { + $this->path = $this->Folder->slashTerm($this->Folder->pwd()) . $this->name; + } + return $this->path; + } + +/** + * Returns true if the File exists. + * + * @return boolean true if it exists, false otherwise + * @access public + */ + function exists() { + return (file_exists($this->path) && is_file($this->path)); + } + +/** + * Returns the "chmod" (permissions) of the File. + * + * @return string Permissions for the file + * @access public + */ + function perms() { + if ($this->exists()) { + return substr(sprintf('%o', fileperms($this->path)), -4); + } + return false; + } + +/** + * Returns the Filesize + * + * @return integer size of the file in bytes, or false in case of an error + * @access public + */ + function size() { + if ($this->exists()) { + return filesize($this->path); + } + return false; + } + +/** + * Returns true if the File is writable. + * + * @return boolean true if its writable, false otherwise + * @access public + */ + function writable() { + return is_writable($this->path); + } + +/** + * Returns true if the File is executable. + * + * @return boolean true if its executable, false otherwise + * @access public + */ + function executable() { + return is_executable($this->path); + } + +/** + * Returns true if the File is readable. + * + * @return boolean true if file is readable, false otherwise + * @access public + */ + function readable() { + return is_readable($this->path); + } + +/** + * Returns the File's owner. + * + * @return integer the Fileowner + * @access public + */ + function owner() { + if ($this->exists()) { + return fileowner($this->path); + } + return false; + } + +/** + * Returns the File's group. + * + * @return integer the Filegroup + * @access public + */ + function group() { + if ($this->exists()) { + return filegroup($this->path); + } + return false; + } + +/** + * Returns last access time. + * + * @return integer timestamp Timestamp of last access time + * @access public + */ + function lastAccess() { + if ($this->exists()) { + return fileatime($this->path); + } + return false; + } + +/** + * Returns last modified time. + * + * @return integer timestamp Timestamp of last modification + * @access public + */ + function lastChange() { + if ($this->exists()) { + return filemtime($this->path); + } + return false; + } + +/** + * Returns the current folder. + * + * @return Folder Current folder + * @access public + */ + function &Folder() { + return $this->Folder; + } + +/** + * Copy the File to $dest + * + * @param string $dest destination for the copy + * @param boolean $overwrite Overwrite $dest if exists + * @return boolean Succes + * @access public + */ + function copy($dest, $overwrite = true) { + if (!$this->exists() || is_file($dest) && !$overwrite) { + return false; + } + return copy($this->path, $dest); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/folder.php b/code/ryzom/tools/server/www/webtt/cake/libs/folder.php new file mode 100644 index 000000000..97de64e8c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/folder.php @@ -0,0 +1,787 @@ +mode = $mode; + } + + if (!file_exists($path) && $create === true) { + $this->create($path, $this->mode); + } + if (!Folder::isAbsolute($path)) { + $path = realpath($path); + } + if (!empty($path)) { + $this->cd($path); + } + } + +/** + * Return current path. + * + * @return string Current path + * @access public + */ + function pwd() { + return $this->path; + } + +/** + * Change directory to $path. + * + * @param string $path Path to the directory to change to + * @return string The new path. Returns false on failure + * @access public + */ + function cd($path) { + $path = $this->realpath($path); + if (is_dir($path)) { + return $this->path = $path; + } + return false; + } + +/** + * Returns an array of the contents of the current directory. + * The returned array holds two arrays: One of directories and one of files. + * + * @param boolean $sort Whether you want the results sorted, set this and the sort property + * to false to get unsorted results. + * @param mixed $exceptions Either an array or boolean true will not grab dot files + * @param boolean $fullPath True returns the full path + * @return mixed Contents of current directory as an array, an empty array on failure + * @access public + */ + function read($sort = true, $exceptions = false, $fullPath = false) { + $dirs = $files = array(); + + if (!$this->pwd()) { + return array($dirs, $files); + } + if (is_array($exceptions)) { + $exceptions = array_flip($exceptions); + } + $skipHidden = isset($exceptions['.']) || $exceptions === true; + + if (false === ($dir = @opendir($this->path))) { + return array($dirs, $files); + } + + while (false !== ($item = readdir($dir))) { + if ($item === '.' || $item === '..' || ($skipHidden && $item[0] === '.') || isset($exceptions[$item])) { + continue; + } + + $path = Folder::addPathElement($this->path, $item); + if (is_dir($path)) { + $dirs[] = $fullPath ? $path : $item; + } else { + $files[] = $fullPath ? $path : $item; + } + } + + if ($sort || $this->sort) { + sort($dirs); + sort($files); + } + + closedir($dir); + return array($dirs, $files); + } + +/** + * Returns an array of all matching files in current directory. + * + * @param string $pattern Preg_match pattern (Defaults to: .*) + * @param boolean $sort Whether results should be sorted. + * @return array Files that match given pattern + * @access public + */ + function find($regexpPattern = '.*', $sort = false) { + list($dirs, $files) = $this->read($sort); + return array_values(preg_grep('/^' . $regexpPattern . '$/i', $files)); ; + } + +/** + * Returns an array of all matching files in and below current directory. + * + * @param string $pattern Preg_match pattern (Defaults to: .*) + * @param boolean $sort Whether results should be sorted. + * @return array Files matching $pattern + * @access public + */ + function findRecursive($pattern = '.*', $sort = false) { + if (!$this->pwd()) { + return array(); + } + $startsOn = $this->path; + $out = $this->_findRecursive($pattern, $sort); + $this->cd($startsOn); + return $out; + } + +/** + * Private helper function for findRecursive. + * + * @param string $pattern Pattern to match against + * @param boolean $sort Whether results should be sorted. + * @return array Files matching pattern + * @access private + */ + function _findRecursive($pattern, $sort = false) { + list($dirs, $files) = $this->read($sort); + $found = array(); + + foreach ($files as $file) { + if (preg_match('/^' . $pattern . '$/i', $file)) { + $found[] = Folder::addPathElement($this->path, $file); + } + } + $start = $this->path; + + foreach ($dirs as $dir) { + $this->cd(Folder::addPathElement($start, $dir)); + $found = array_merge($found, $this->findRecursive($pattern, $sort)); + } + return $found; + } + +/** + * Returns true if given $path is a Windows path. + * + * @param string $path Path to check + * @return boolean true if windows path, false otherwise + * @access public + * @static + */ + function isWindowsPath($path) { + return (preg_match('/^[A-Z]:\\\\/i', $path) || substr($path, 0, 2) == '\\\\'); + } + +/** + * Returns true if given $path is an absolute path. + * + * @param string $path Path to check + * @return bool true if path is absolute. + * @access public + * @static + */ + function isAbsolute($path) { + return !empty($path) && ($path[0] === '/' || preg_match('/^[A-Z]:\\\\/i', $path) || substr($path, 0, 2) == '\\\\'); + } + +/** + * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.) + * + * @param string $path Path to check + * @return string Set of slashes ("\\" or "/") + * @access public + * @static + */ + function normalizePath($path) { + return Folder::correctSlashFor($path); + } + +/** + * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.) + * + * @param string $path Path to check + * @return string Set of slashes ("\\" or "/") + * @access public + * @static + */ + function correctSlashFor($path) { + return (Folder::isWindowsPath($path)) ? '\\' : '/'; + } + +/** + * Returns $path with added terminating slash (corrected for Windows or other OS). + * + * @param string $path Path to check + * @return string Path with ending slash + * @access public + * @static + */ + function slashTerm($path) { + if (Folder::isSlashTerm($path)) { + return $path; + } + return $path . Folder::correctSlashFor($path); + } + +/** + * Returns $path with $element added, with correct slash in-between. + * + * @param string $path Path + * @param string $element Element to and at end of path + * @return string Combined path + * @access public + * @static + */ + function addPathElement($path, $element) { + return rtrim($path, DS) . DS . $element; + } + +/** + * Returns true if the File is in a given CakePath. + * + * @param string $path The path to check. + * @return bool + * @access public + */ + function inCakePath($path = '') { + $dir = substr(Folder::slashTerm(ROOT), 0, -1); + $newdir = $dir . $path; + + return $this->inPath($newdir); + } + +/** + * Returns true if the File is in given path. + * + * @param string $path The path to check that the current pwd() resides with in. + * @param boolean $reverse + * @return bool + * @access public + */ + function inPath($path = '', $reverse = false) { + $dir = Folder::slashTerm($path); + $current = Folder::slashTerm($this->pwd()); + + if (!$reverse) { + $return = preg_match('/^(.*)' . preg_quote($dir, '/') . '(.*)/', $current); + } else { + $return = preg_match('/^(.*)' . preg_quote($current, '/') . '(.*)/', $dir); + } + return (bool)$return; + } + +/** + * Change the mode on a directory structure recursively. This includes changing the mode on files as well. + * + * @param string $path The path to chmod + * @param integer $mode octal value 0755 + * @param boolean $recursive chmod recursively, set to false to only change the current directory. + * @param array $exceptions array of files, directories to skip + * @return boolean Returns TRUE on success, FALSE on failure + * @access public + */ + function chmod($path, $mode = false, $recursive = true, $exceptions = array()) { + if (!$mode) { + $mode = $this->mode; + } + + if ($recursive === false && is_dir($path)) { + if (@chmod($path, intval($mode, 8))) { + $this->__messages[] = sprintf(__('%s changed to %s', true), $path, $mode); + return true; + } + + $this->__errors[] = sprintf(__('%s NOT changed to %s', true), $path, $mode); + return false; + } + + if (is_dir($path)) { + $paths = $this->tree($path); + + foreach ($paths as $type) { + foreach ($type as $key => $fullpath) { + $check = explode(DS, $fullpath); + $count = count($check); + + if (in_array($check[$count - 1], $exceptions)) { + continue; + } + + if (@chmod($fullpath, intval($mode, 8))) { + $this->__messages[] = sprintf(__('%s changed to %s', true), $fullpath, $mode); + } else { + $this->__errors[] = sprintf(__('%s NOT changed to %s', true), $fullpath, $mode); + } + } + } + + if (empty($this->__errors)) { + return true; + } + } + return false; + } + +/** + * Returns an array of nested directories and files in each directory + * + * @param string $path the directory path to build the tree from + * @param mixed $exceptions Array of files to exclude, defaults to excluding hidden files. + * @param string $type either file or dir. null returns both files and directories + * @return mixed array of nested directories and files in each directory + * @access public + */ + function tree($path, $exceptions = true, $type = null) { + $original = $this->path; + $path = rtrim($path, DS); + if (!$this->cd($path)) { + if ($type === null) { + return array(array(), array()); + } + return array(); + } + $this->__files = array(); + $this->__directories = array($this->realpath($path)); + $directories = array(); + + if ($exceptions === false) { + $exceptions = true; + } + while (!empty($this->__directories)) { + $dir = array_pop($this->__directories); + $this->__tree($dir, $exceptions); + $directories[] = $dir; + } + + if ($type === null) { + return array($directories, $this->__files); + } + if ($type === 'dir') { + return $directories; + } + $this->cd($original); + + return $this->__files; + } + +/** + * Private method to list directories and files in each directory + * + * @param string $path The Path to read. + * @param mixed $exceptions Array of files to exclude from the read that will be performed. + * @access private + */ + function __tree($path, $exceptions) { + $this->path = $path; + list($dirs, $files) = $this->read(false, $exceptions, true); + $this->__directories = array_merge($this->__directories, $dirs); + $this->__files = array_merge($this->__files, $files); + } + +/** + * Create a directory structure recursively. Can be used to create + * deep path structures like `/foo/bar/baz/shoe/horn` + * + * @param string $pathname The directory structure to create + * @param integer $mode octal value 0755 + * @return boolean Returns TRUE on success, FALSE on failure + * @access public + */ + function create($pathname, $mode = false) { + if (is_dir($pathname) || empty($pathname)) { + return true; + } + + if (!$mode) { + $mode = $this->mode; + } + + if (is_file($pathname)) { + $this->__errors[] = sprintf(__('%s is a file', true), $pathname); + return false; + } + $pathname = rtrim($pathname, DS); + $nextPathname = substr($pathname, 0, strrpos($pathname, DS)); + + if ($this->create($nextPathname, $mode)) { + if (!file_exists($pathname)) { + $old = umask(0); + if (mkdir($pathname, $mode)) { + umask($old); + $this->__messages[] = sprintf(__('%s created', true), $pathname); + return true; + } else { + umask($old); + $this->__errors[] = sprintf(__('%s NOT created', true), $pathname); + return false; + } + } + } + return false; + } + +/** + * Returns the size in bytes of this Folder and its contents. + * + * @param string $directory Path to directory + * @return int size in bytes of current folder + * @access public + */ + function dirsize() { + $size = 0; + $directory = Folder::slashTerm($this->path); + $stack = array($directory); + $count = count($stack); + for ($i = 0, $j = $count; $i < $j; ++$i) { + if (is_file($stack[$i])) { + $size += filesize($stack[$i]); + } elseif (is_dir($stack[$i])) { + $dir = dir($stack[$i]); + if ($dir) { + while (false !== ($entry = $dir->read())) { + if ($entry === '.' || $entry === '..') { + continue; + } + $add = $stack[$i] . $entry; + + if (is_dir($stack[$i] . $entry)) { + $add = Folder::slashTerm($add); + } + $stack[] = $add; + } + $dir->close(); + } + } + $j = count($stack); + } + return $size; + } + +/** + * Recursively Remove directories if the system allows. + * + * @param string $path Path of directory to delete + * @return boolean Success + * @access public + */ + function delete($path = null) { + if (!$path) { + $path = $this->pwd(); + } + if (!$path) { + return null; + } + $path = Folder::slashTerm($path); + if (is_dir($path) === true) { + $normalFiles = glob($path . '*'); + $hiddenFiles = glob($path . '\.?*'); + + $normalFiles = $normalFiles ? $normalFiles : array(); + $hiddenFiles = $hiddenFiles ? $hiddenFiles : array(); + + $files = array_merge($normalFiles, $hiddenFiles); + if (is_array($files)) { + foreach ($files as $file) { + if (preg_match('/(\.|\.\.)$/', $file)) { + continue; + } + if (is_file($file) === true) { + if (@unlink($file)) { + $this->__messages[] = sprintf(__('%s removed', true), $file); + } else { + $this->__errors[] = sprintf(__('%s NOT removed', true), $file); + } + } elseif (is_dir($file) === true && $this->delete($file) === false) { + return false; + } + } + } + $path = substr($path, 0, strlen($path) - 1); + if (rmdir($path) === false) { + $this->__errors[] = sprintf(__('%s NOT removed', true), $path); + return false; + } else { + $this->__messages[] = sprintf(__('%s removed', true), $path); + } + } + return true; + } + +/** + * Recursive directory copy. + * + * ### Options + * + * - `to` The directory to copy to. + * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd(). + * - `chmod` The mode to copy the files/directories with. + * - `skip` Files/directories to skip. + * + * @param mixed $options Either an array of options (see above) or a string of the destination directory. + * @return bool Success + * @access public + */ + function copy($options = array()) { + if (!$this->pwd()) { + return false; + } + $to = null; + if (is_string($options)) { + $to = $options; + $options = array(); + } + $options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options); + + $fromDir = $options['from']; + $toDir = $options['to']; + $mode = $options['mode']; + + if (!$this->cd($fromDir)) { + $this->__errors[] = sprintf(__('%s not found', true), $fromDir); + return false; + } + + if (!is_dir($toDir)) { + $this->create($toDir, $mode); + } + + if (!is_writable($toDir)) { + $this->__errors[] = sprintf(__('%s not writable', true), $toDir); + return false; + } + + $exceptions = array_merge(array('.', '..', '.svn'), $options['skip']); + if ($handle = @opendir($fromDir)) { + while (false !== ($item = readdir($handle))) { + if (!in_array($item, $exceptions)) { + $from = Folder::addPathElement($fromDir, $item); + $to = Folder::addPathElement($toDir, $item); + if (is_file($from)) { + if (copy($from, $to)) { + chmod($to, intval($mode, 8)); + touch($to, filemtime($from)); + $this->__messages[] = sprintf(__('%s copied to %s', true), $from, $to); + } else { + $this->__errors[] = sprintf(__('%s NOT copied to %s', true), $from, $to); + } + } + + if (is_dir($from) && !file_exists($to)) { + $old = umask(0); + if (mkdir($to, $mode)) { + umask($old); + $old = umask(0); + chmod($to, $mode); + umask($old); + $this->__messages[] = sprintf(__('%s created', true), $to); + $options = array_merge($options, array('to'=> $to, 'from'=> $from)); + $this->copy($options); + } else { + $this->__errors[] = sprintf(__('%s not created', true), $to); + } + } + } + } + closedir($handle); + } else { + return false; + } + + if (!empty($this->__errors)) { + return false; + } + return true; + } + +/** + * Recursive directory move. + * + * ### Options + * + * - `to` The directory to copy to. + * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd(). + * - `chmod` The mode to copy the files/directories with. + * - `skip` Files/directories to skip. + * + * @param array $options (to, from, chmod, skip) + * @return boolean Success + * @access public + */ + function move($options) { + $to = null; + if (is_string($options)) { + $to = $options; + $options = (array)$options; + } + $options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options); + + if ($this->copy($options)) { + if ($this->delete($options['from'])) { + return $this->cd($options['to']); + } + } + return false; + } + +/** + * get messages from latest method + * + * @return array + * @access public + */ + function messages() { + return $this->__messages; + } + +/** + * get error from latest method + * + * @return array + * @access public + */ + function errors() { + return $this->__errors; + } + +/** + * Get the real path (taking ".." and such into account) + * + * @param string $path Path to resolve + * @return string The resolved path + */ + function realpath($path) { + $path = str_replace('/', DS, trim($path)); + if (strpos($path, '..') === false) { + if (!Folder::isAbsolute($path)) { + $path = Folder::addPathElement($this->path, $path); + } + return $path; + } + $parts = explode(DS, $path); + $newparts = array(); + $newpath = ''; + if ($path[0] === DS) { + $newpath = DS; + } + + while (($part = array_shift($parts)) !== NULL) { + if ($part === '.' || $part === '') { + continue; + } + if ($part === '..') { + if (!empty($newparts)) { + array_pop($newparts); + continue; + } else { + return false; + } + } + $newparts[] = $part; + } + $newpath .= implode(DS, $newparts); + + return Folder::slashTerm($newpath); + } + +/** + * Returns true if given $path ends in a slash (i.e. is slash-terminated). + * + * @param string $path Path to check + * @return boolean true if path ends with slash, false otherwise + * @access public + * @static + */ + function isSlashTerm($path) { + $lastChar = $path[strlen($path) - 1]; + return $lastChar === '/' || $lastChar === '\\'; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/http_socket.php b/code/ryzom/tools/server/www/webtt/cake/libs/http_socket.php new file mode 100644 index 000000000..a01776cb3 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/http_socket.php @@ -0,0 +1,1081 @@ + 'GET', + 'uri' => array( + 'scheme' => 'http', + 'host' => null, + 'port' => 80, + 'user' => null, + 'pass' => null, + 'path' => null, + 'query' => null, + 'fragment' => null + ), + 'auth' => array( + 'method' => 'Basic', + 'user' => null, + 'pass' => null + ), + 'version' => '1.1', + 'body' => '', + 'line' => null, + 'header' => array( + 'Connection' => 'close', + 'User-Agent' => 'CakePHP' + ), + 'raw' => null, + 'cookies' => array() + ); + +/** +* The default structure for storing the response +* +* @var array +* @access public +*/ + var $response = array( + 'raw' => array( + 'status-line' => null, + 'header' => null, + 'body' => null, + 'response' => null + ), + 'status' => array( + 'http-version' => null, + 'code' => null, + 'reason-phrase' => null + ), + 'header' => array(), + 'body' => '', + 'cookies' => array() + ); + +/** + * Default configuration settings for the HttpSocket + * + * @var array + * @access public + */ + var $config = array( + 'persistent' => false, + 'host' => 'localhost', + 'protocol' => 'tcp', + 'port' => 80, + 'timeout' => 30, + 'request' => array( + 'uri' => array( + 'scheme' => 'http', + 'host' => 'localhost', + 'port' => 80 + ), + 'auth' => array( + 'method' => 'Basic', + 'user' => null, + 'pass' => null + ), + 'cookies' => array() + ) + ); + +/** + * String that represents a line break. + * + * @var string + * @access public + */ + var $lineBreak = "\r\n"; + +/** + * Build an HTTP Socket using the specified configuration. + * + * You can use a url string to set the url and use default configurations for + * all other options: + * + * `$http =& new HttpSocket('http://cakephp.org/');` + * + * Or use an array to configure multiple options: + * + * {{{ + * $http =& new HttpSocket(array( + * 'host' => 'cakephp.org', + * 'timeout' => 20 + * )); + * }}} + * + * See HttpSocket::$config for options that can be used. + * + * @param mixed $config Configuration information, either a string url or an array of options. + * @access public + */ + function __construct($config = array()) { + if (is_string($config)) { + $this->_configUri($config); + } elseif (is_array($config)) { + if (isset($config['request']['uri']) && is_string($config['request']['uri'])) { + $this->_configUri($config['request']['uri']); + unset($config['request']['uri']); + } + $this->config = Set::merge($this->config, $config); + } + parent::__construct($this->config); + } + +/** + * Issue the specified request. HttpSocket::get() and HttpSocket::post() wrap this + * method and provide a more granular interface. + * + * @param mixed $request Either an URI string, or an array defining host/uri + * @return mixed false on error, request body on success + * @access public + */ + function request($request = array()) { + $this->reset(false); + + if (is_string($request)) { + $request = array('uri' => $request); + } elseif (!is_array($request)) { + return false; + } + + if (!isset($request['uri'])) { + $request['uri'] = null; + } + $uri = $this->_parseUri($request['uri']); + $hadAuth = false; + if (is_array($uri) && array_key_exists('user', $uri)) { + $hadAuth = true; + } + if (!isset($uri['host'])) { + $host = $this->config['host']; + } + if (isset($request['host'])) { + $host = $request['host']; + unset($request['host']); + } + $request['uri'] = $this->url($request['uri']); + $request['uri'] = $this->_parseUri($request['uri'], true); + $this->request = Set::merge($this->request, $this->config['request'], $request); + + if (!$hadAuth && !empty($this->config['request']['auth']['user'])) { + $this->request['uri']['user'] = $this->config['request']['auth']['user']; + $this->request['uri']['pass'] = $this->config['request']['auth']['pass']; + } + $this->_configUri($this->request['uri']); + + if (isset($host)) { + $this->config['host'] = $host; + } + $cookies = null; + + if (is_array($this->request['header'])) { + $this->request['header'] = $this->_parseHeader($this->request['header']); + if (!empty($this->request['cookies'])) { + $cookies = $this->buildCookies($this->request['cookies']); + } + $Host = $this->request['uri']['host']; + $schema = ''; + $port = 0; + if (isset($this->request['uri']['schema'])) { + $schema = $this->request['uri']['schema']; + } + if (isset($this->request['uri']['port'])) { + $port = $this->request['uri']['port']; + } + if ( + ($schema === 'http' && $port != 80) || + ($schema === 'https' && $port != 443) || + ($port != 80 && $port != 443) + ) { + $Host .= ':' . $port; + } + $this->request['header'] = array_merge(compact('Host'), $this->request['header']); + } + + if (isset($this->request['auth']['user']) && isset($this->request['auth']['pass'])) { + $this->request['header']['Authorization'] = $this->request['auth']['method'] . " " . base64_encode($this->request['auth']['user'] . ":" . $this->request['auth']['pass']); + } + if (isset($this->request['uri']['user']) && isset($this->request['uri']['pass'])) { + $this->request['header']['Authorization'] = $this->request['auth']['method'] . " " . base64_encode($this->request['uri']['user'] . ":" . $this->request['uri']['pass']); + } + + if (is_array($this->request['body'])) { + $this->request['body'] = $this->_httpSerialize($this->request['body']); + } + + if (!empty($this->request['body']) && !isset($this->request['header']['Content-Type'])) { + $this->request['header']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (!empty($this->request['body']) && !isset($this->request['header']['Content-Length'])) { + $this->request['header']['Content-Length'] = strlen($this->request['body']); + } + + $connectionType = null; + if (isset($this->request['header']['Connection'])) { + $connectionType = $this->request['header']['Connection']; + } + $this->request['header'] = $this->_buildHeader($this->request['header']) . $cookies; + + if (empty($this->request['line'])) { + $this->request['line'] = $this->_buildRequestLine($this->request); + } + + if ($this->quirksMode === false && $this->request['line'] === false) { + return $this->response = false; + } + + if ($this->request['line'] !== false) { + $this->request['raw'] = $this->request['line']; + } + + if ($this->request['header'] !== false) { + $this->request['raw'] .= $this->request['header']; + } + + $this->request['raw'] .= "\r\n"; + $this->request['raw'] .= $this->request['body']; + $this->write($this->request['raw']); + + $response = null; + while ($data = $this->read()) { + $response .= $data; + } + + if ($connectionType == 'close') { + $this->disconnect(); + } + + $this->response = $this->_parseResponse($response); + if (!empty($this->response['cookies'])) { + $this->config['request']['cookies'] = array_merge($this->config['request']['cookies'], $this->response['cookies']); + } + + return $this->response['body']; + } + +/** + * Issues a GET request to the specified URI, query, and request. + * + * Using a string uri and an array of query string parameters: + * + * `$response = $http->get('http://google.com/search', array('q' => 'cakephp', 'client' => 'safari'));` + * + * Would do a GET request to `http://google.com/search?q=cakephp&client=safari` + * + * You could express the same thing using a uri array and query string parameters: + * + * {{{ + * $response = $http->get( + * array('host' => 'google.com', 'path' => '/search'), + * array('q' => 'cakephp', 'client' => 'safari') + * ); + * }}} + * + * @param mixed $uri URI to request. Either a string uri, or a uri array, see HttpSocket::_parseUri() + * @param array $query Querystring parameters to append to URI + * @param array $request An indexed array with indexes such as 'method' or uri + * @return mixed Result of request, either false on failure or the response to the request. + * @access public + */ + function get($uri = null, $query = array(), $request = array()) { + if (!empty($query)) { + $uri = $this->_parseUri($uri); + if (isset($uri['query'])) { + $uri['query'] = array_merge($uri['query'], $query); + } else { + $uri['query'] = $query; + } + $uri = $this->_buildUri($uri); + } + + $request = Set::merge(array('method' => 'GET', 'uri' => $uri), $request); + return $this->request($request); + } + +/** + * Issues a POST request to the specified URI, query, and request. + * + * `post()` can be used to post simple data arrays to a url: + * + * {{{ + * $response = $http->post('http://example.com', array( + * 'username' => 'batman', + * 'password' => 'bruce_w4yne' + * )); + * }}} + * + * @param mixed $uri URI to request. See HttpSocket::_parseUri() + * @param array $data Array of POST data keys and values. + * @param array $request An indexed array with indexes such as 'method' or uri + * @return mixed Result of request, either false on failure or the response to the request. + * @access public + */ + function post($uri = null, $data = array(), $request = array()) { + $request = Set::merge(array('method' => 'POST', 'uri' => $uri, 'body' => $data), $request); + return $this->request($request); + } + +/** + * Issues a PUT request to the specified URI, query, and request. + * + * @param mixed $uri URI to request, See HttpSocket::_parseUri() + * @param array $data Array of PUT data keys and values. + * @param array $request An indexed array with indexes such as 'method' or uri + * @return mixed Result of request + * @access public + */ + function put($uri = null, $data = array(), $request = array()) { + $request = Set::merge(array('method' => 'PUT', 'uri' => $uri, 'body' => $data), $request); + return $this->request($request); + } + +/** + * Issues a DELETE request to the specified URI, query, and request. + * + * @param mixed $uri URI to request (see {@link _parseUri()}) + * @param array $data Query to append to URI + * @param array $request An indexed array with indexes such as 'method' or uri + * @return mixed Result of request + * @access public + */ + function delete($uri = null, $data = array(), $request = array()) { + $request = Set::merge(array('method' => 'DELETE', 'uri' => $uri, 'body' => $data), $request); + return $this->request($request); + } + +/** + * Normalizes urls into a $uriTemplate. If no template is provided + * a default one will be used. Will generate the url using the + * current config information. + * + * ### Usage: + * + * After configuring part of the request parameters, you can use url() to generate + * urls. + * + * {{{ + * $http->configUri('http://www.cakephp.org'); + * $url = $http->url('/search?q=bar'); + * }}} + * + * Would return `http://www.cakephp.org/search?q=bar` + * + * url() can also be used with custom templates: + * + * `$url = $http->url('http://www.cakephp/search?q=socket', '/%path?%query');` + * + * Would return `/search?q=socket`. + * + * @param mixed $url Either a string or array of url options to create a url with. + * @param string $uriTemplate A template string to use for url formatting. + * @return mixed Either false on failure or a string containing the composed url. + * @access public + */ + function url($url = null, $uriTemplate = null) { + if (is_null($url)) { + $url = '/'; + } + if (is_string($url)) { + if ($url{0} == '/') { + $url = $this->config['request']['uri']['host'].':'.$this->config['request']['uri']['port'] . $url; + } + if (!preg_match('/^.+:\/\/|\*|^\//', $url)) { + $url = $this->config['request']['uri']['scheme'].'://'.$url; + } + } elseif (!is_array($url) && !empty($url)) { + return false; + } + + $base = array_merge($this->config['request']['uri'], array('scheme' => array('http', 'https'), 'port' => array(80, 443))); + $url = $this->_parseUri($url, $base); + + if (empty($url)) { + $url = $this->config['request']['uri']; + } + + if (!empty($uriTemplate)) { + return $this->_buildUri($url, $uriTemplate); + } + return $this->_buildUri($url); + } + +/** + * Parses the given message and breaks it down in parts. + * + * @param string $message Message to parse + * @return array Parsed message (with indexed elements such as raw, status, header, body) + * @access protected + */ + function _parseResponse($message) { + if (is_array($message)) { + return $message; + } elseif (!is_string($message)) { + return false; + } + + static $responseTemplate; + + if (empty($responseTemplate)) { + $classVars = get_class_vars(__CLASS__); + $responseTemplate = $classVars['response']; + } + + $response = $responseTemplate; + + if (!preg_match("/^(.+\r\n)(.*)(?<=\r\n)\r\n/Us", $message, $match)) { + return false; + } + + list($null, $response['raw']['status-line'], $response['raw']['header']) = $match; + $response['raw']['response'] = $message; + $response['raw']['body'] = substr($message, strlen($match[0])); + + if (preg_match("/(.+) ([0-9]{3}) (.+)\r\n/DU", $response['raw']['status-line'], $match)) { + $response['status']['http-version'] = $match[1]; + $response['status']['code'] = (int)$match[2]; + $response['status']['reason-phrase'] = $match[3]; + } + + $response['header'] = $this->_parseHeader($response['raw']['header']); + $transferEncoding = null; + if (isset($response['header']['Transfer-Encoding'])) { + $transferEncoding = $response['header']['Transfer-Encoding']; + } + $decoded = $this->_decodeBody($response['raw']['body'], $transferEncoding); + $response['body'] = $decoded['body']; + + if (!empty($decoded['header'])) { + $response['header'] = $this->_parseHeader($this->_buildHeader($response['header']).$this->_buildHeader($decoded['header'])); + } + + if (!empty($response['header'])) { + $response['cookies'] = $this->parseCookies($response['header']); + } + + foreach ($response['raw'] as $field => $val) { + if ($val === '') { + $response['raw'][$field] = null; + } + } + + return $response; + } + +/** + * Generic function to decode a $body with a given $encoding. Returns either an array with the keys + * 'body' and 'header' or false on failure. + * + * @param string $body A string continaing the body to decode. + * @param mixed $encoding Can be false in case no encoding is being used, or a string representing the encoding. + * @return mixed Array of response headers and body or false. + * @access protected + */ + function _decodeBody($body, $encoding = 'chunked') { + if (!is_string($body)) { + return false; + } + if (empty($encoding)) { + return array('body' => $body, 'header' => false); + } + $decodeMethod = '_decode'.Inflector::camelize(str_replace('-', '_', $encoding)).'Body'; + + if (!is_callable(array(&$this, $decodeMethod))) { + if (!$this->quirksMode) { + trigger_error(sprintf(__('HttpSocket::_decodeBody - Unknown encoding: %s. Activate quirks mode to surpress error.', true), h($encoding)), E_USER_WARNING); + } + return array('body' => $body, 'header' => false); + } + return $this->{$decodeMethod}($body); + } + +/** + * Decodes a chunked message $body and returns either an array with the keys 'body' and 'header' or false as + * a result. + * + * @param string $body A string continaing the chunked body to decode. + * @return mixed Array of response headers and body or false. + * @access protected + */ + function _decodeChunkedBody($body) { + if (!is_string($body)) { + return false; + } + + $decodedBody = null; + $chunkLength = null; + + while ($chunkLength !== 0) { + if (!preg_match("/^([0-9a-f]+) *(?:;(.+)=(.+))?\r\n/iU", $body, $match)) { + if (!$this->quirksMode) { + trigger_error(__('HttpSocket::_decodeChunkedBody - Could not parse malformed chunk. Activate quirks mode to do this.', true), E_USER_WARNING); + return false; + } + break; + } + + $chunkSize = 0; + $hexLength = 0; + $chunkExtensionName = ''; + $chunkExtensionValue = ''; + if (isset($match[0])) { + $chunkSize = $match[0]; + } + if (isset($match[1])) { + $hexLength = $match[1]; + } + if (isset($match[2])) { + $chunkExtensionName = $match[2]; + } + if (isset($match[3])) { + $chunkExtensionValue = $match[3]; + } + + $body = substr($body, strlen($chunkSize)); + $chunkLength = hexdec($hexLength); + $chunk = substr($body, 0, $chunkLength); + if (!empty($chunkExtensionName)) { + /** + * @todo See if there are popular chunk extensions we should implement + */ + } + $decodedBody .= $chunk; + if ($chunkLength !== 0) { + $body = substr($body, $chunkLength+strlen("\r\n")); + } + } + + $entityHeader = false; + if (!empty($body)) { + $entityHeader = $this->_parseHeader($body); + } + return array('body' => $decodedBody, 'header' => $entityHeader); + } + +/** + * Parses and sets the specified URI into current request configuration. + * + * @param mixed $uri URI, See HttpSocket::_parseUri() + * @return array Current configuration settings + * @access protected + */ + function _configUri($uri = null) { + if (empty($uri)) { + return false; + } + + if (is_array($uri)) { + $uri = $this->_parseUri($uri); + } else { + $uri = $this->_parseUri($uri, true); + } + + if (!isset($uri['host'])) { + return false; + } + $config = array( + 'request' => array( + 'uri' => array_intersect_key($uri, $this->config['request']['uri']), + 'auth' => array_intersect_key($uri, $this->config['request']['auth']) + ) + ); + $this->config = Set::merge($this->config, $config); + $this->config = Set::merge($this->config, array_intersect_key($this->config['request']['uri'], $this->config)); + return $this->config; + } + +/** + * Takes a $uri array and turns it into a fully qualified URL string + * + * @param mixed $uri Either A $uri array, or a request string. Will use $this->config if left empty. + * @param string $uriTemplate The Uri template/format to use. + * @return mixed A fully qualified URL formated according to $uriTemplate, or false on failure + * @access protected + */ + function _buildUri($uri = array(), $uriTemplate = '%scheme://%user:%pass@%host:%port/%path?%query#%fragment') { + if (is_string($uri)) { + $uri = array('host' => $uri); + } + $uri = $this->_parseUri($uri, true); + + if (!is_array($uri) || empty($uri)) { + return false; + } + + $uri['path'] = preg_replace('/^\//', null, $uri['path']); + $uri['query'] = $this->_httpSerialize($uri['query']); + $stripIfEmpty = array( + 'query' => '?%query', + 'fragment' => '#%fragment', + 'user' => '%user:%pass@', + 'host' => '%host:%port/' + ); + + foreach ($stripIfEmpty as $key => $strip) { + if (empty($uri[$key])) { + $uriTemplate = str_replace($strip, null, $uriTemplate); + } + } + + $defaultPorts = array('http' => 80, 'https' => 443); + if (array_key_exists($uri['scheme'], $defaultPorts) && $defaultPorts[$uri['scheme']] == $uri['port']) { + $uriTemplate = str_replace(':%port', null, $uriTemplate); + } + foreach ($uri as $property => $value) { + $uriTemplate = str_replace('%'.$property, $value, $uriTemplate); + } + + if ($uriTemplate === '/*') { + $uriTemplate = '*'; + } + return $uriTemplate; + } + +/** + * Parses the given URI and breaks it down into pieces as an indexed array with elements + * such as 'scheme', 'port', 'query'. + * + * @param string $uri URI to parse + * @param mixed $base If true use default URI config, otherwise indexed array to set 'scheme', 'host', 'port', etc. + * @return array Parsed URI + * @access protected + */ + function _parseUri($uri = null, $base = array()) { + $uriBase = array( + 'scheme' => array('http', 'https'), + 'host' => null, + 'port' => array(80, 443), + 'user' => null, + 'pass' => null, + 'path' => '/', + 'query' => null, + 'fragment' => null + ); + + if (is_string($uri)) { + $uri = parse_url($uri); + } + if (!is_array($uri) || empty($uri)) { + return false; + } + if ($base === true) { + $base = $uriBase; + } + + if (isset($base['port'], $base['scheme']) && is_array($base['port']) && is_array($base['scheme'])) { + if (isset($uri['scheme']) && !isset($uri['port'])) { + $base['port'] = $base['port'][array_search($uri['scheme'], $base['scheme'])]; + } elseif (isset($uri['port']) && !isset($uri['scheme'])) { + $base['scheme'] = $base['scheme'][array_search($uri['port'], $base['port'])]; + } + } + + if (is_array($base) && !empty($base)) { + $uri = array_merge($base, $uri); + } + + if (isset($uri['scheme']) && is_array($uri['scheme'])) { + $uri['scheme'] = array_shift($uri['scheme']); + } + if (isset($uri['port']) && is_array($uri['port'])) { + $uri['port'] = array_shift($uri['port']); + } + + if (array_key_exists('query', $uri)) { + $uri['query'] = $this->_parseQuery($uri['query']); + } + + if (!array_intersect_key($uriBase, $uri)) { + return false; + } + return $uri; + } + +/** + * This function can be thought of as a reverse to PHP5's http_build_query(). It takes a given query string and turns it into an array and + * supports nesting by using the php bracket syntax. So this menas you can parse queries like: + * + * - ?key[subKey]=value + * - ?key[]=value1&key[]=value2 + * + * A leading '?' mark in $query is optional and does not effect the outcome of this function. + * For the complete capabilities of this implementation take a look at HttpSocketTest::testparseQuery() + * + * @param mixed $query A query string to parse into an array or an array to return directly "as is" + * @return array The $query parsed into a possibly multi-level array. If an empty $query is + * given, an empty array is returned. + * @access protected + */ + function _parseQuery($query) { + if (is_array($query)) { + return $query; + } + $parsedQuery = array(); + + if (is_string($query) && !empty($query)) { + $query = preg_replace('/^\?/', '', $query); + $items = explode('&', $query); + + foreach ($items as $item) { + if (strpos($item, '=') !== false) { + list($key, $value) = explode('=', $item, 2); + } else { + $key = $item; + $value = null; + } + + $key = urldecode($key); + $value = urldecode($value); + + if (preg_match_all('/\[([^\[\]]*)\]/iUs', $key, $matches)) { + $subKeys = $matches[1]; + $rootKey = substr($key, 0, strpos($key, '[')); + if (!empty($rootKey)) { + array_unshift($subKeys, $rootKey); + } + $queryNode =& $parsedQuery; + + foreach ($subKeys as $subKey) { + if (!is_array($queryNode)) { + $queryNode = array(); + } + + if ($subKey === '') { + $queryNode[] = array(); + end($queryNode); + $subKey = key($queryNode); + } + $queryNode =& $queryNode[$subKey]; + } + $queryNode = $value; + } else { + $parsedQuery[$key] = $value; + } + } + } + return $parsedQuery; + } + +/** + * Builds a request line according to HTTP/1.1 specs. Activate quirks mode to work outside specs. + * + * @param array $request Needs to contain a 'uri' key. Should also contain a 'method' key, otherwise defaults to GET. + * @param string $versionToken The version token to use, defaults to HTTP/1.1 + * @return string Request line + * @access protected + */ + function _buildRequestLine($request = array(), $versionToken = 'HTTP/1.1') { + $asteriskMethods = array('OPTIONS'); + + if (is_string($request)) { + $isValid = preg_match("/(.+) (.+) (.+)\r\n/U", $request, $match); + if (!$this->quirksMode && (!$isValid || ($match[2] == '*' && !in_array($match[3], $asteriskMethods)))) { + trigger_error(__('HttpSocket::_buildRequestLine - Passed an invalid request line string. Activate quirks mode to do this.', true), E_USER_WARNING); + return false; + } + return $request; + } elseif (!is_array($request)) { + return false; + } elseif (!array_key_exists('uri', $request)) { + return false; + } + + $request['uri'] = $this->_parseUri($request['uri']); + $request = array_merge(array('method' => 'GET'), $request); + $request['uri'] = $this->_buildUri($request['uri'], '/%path?%query'); + + if (!$this->quirksMode && $request['uri'] === '*' && !in_array($request['method'], $asteriskMethods)) { + trigger_error(sprintf(__('HttpSocket::_buildRequestLine - The "*" asterisk character is only allowed for the following methods: %s. Activate quirks mode to work outside of HTTP/1.1 specs.', true), join(',', $asteriskMethods)), E_USER_WARNING); + return false; + } + return $request['method'].' '.$request['uri'].' '.$versionToken.$this->lineBreak; + } + +/** + * Serializes an array for transport. + * + * @param array $data Data to serialize + * @return string Serialized variable + * @access protected + */ + function _httpSerialize($data = array()) { + if (is_string($data)) { + return $data; + } + if (empty($data) || !is_array($data)) { + return false; + } + return substr(Router::queryString($data), 1); + } + +/** + * Builds the header. + * + * @param array $header Header to build + * @return string Header built from array + * @access protected + */ + function _buildHeader($header, $mode = 'standard') { + if (is_string($header)) { + return $header; + } elseif (!is_array($header)) { + return false; + } + + $returnHeader = ''; + foreach ($header as $field => $contents) { + if (is_array($contents) && $mode == 'standard') { + $contents = implode(',', $contents); + } + foreach ((array)$contents as $content) { + $contents = preg_replace("/\r\n(?![\t ])/", "\r\n ", $content); + $field = $this->_escapeToken($field); + + $returnHeader .= $field.': '.$contents.$this->lineBreak; + } + } + return $returnHeader; + } + +/** + * Parses an array based header. + * + * @param array $header Header as an indexed array (field => value) + * @return array Parsed header + * @access protected + */ + function _parseHeader($header) { + if (is_array($header)) { + foreach ($header as $field => $value) { + unset($header[$field]); + $field = strtolower($field); + preg_match_all('/(?:^|(?<=-))[a-z]/U', $field, $offsets, PREG_OFFSET_CAPTURE); + + foreach ($offsets[0] as $offset) { + $field = substr_replace($field, strtoupper($offset[0]), $offset[1], 1); + } + $header[$field] = $value; + } + return $header; + } elseif (!is_string($header)) { + return false; + } + + preg_match_all("/(.+):(.+)(?:(?lineBreak . "|\$)/Uis", $header, $matches, PREG_SET_ORDER); + + $header = array(); + foreach ($matches as $match) { + list(, $field, $value) = $match; + + $value = trim($value); + $value = preg_replace("/[\t ]\r\n/", "\r\n", $value); + + $field = $this->_unescapeToken($field); + + $field = strtolower($field); + preg_match_all('/(?:^|(?<=-))[a-z]/U', $field, $offsets, PREG_OFFSET_CAPTURE); + foreach ($offsets[0] as $offset) { + $field = substr_replace($field, strtoupper($offset[0]), $offset[1], 1); + } + + if (!isset($header[$field])) { + $header[$field] = $value; + } else { + $header[$field] = array_merge((array)$header[$field], (array)$value); + } + } + return $header; + } + +/** + * Parses cookies in response headers. + * + * @param array $header Header array containing one ore more 'Set-Cookie' headers. + * @return mixed Either false on no cookies, or an array of cookies received. + * @access public + * @todo Make this 100% RFC 2965 confirm + */ + function parseCookies($header) { + if (!isset($header['Set-Cookie'])) { + return false; + } + + $cookies = array(); + foreach ((array)$header['Set-Cookie'] as $cookie) { + if (strpos($cookie, '";"') !== false) { + $cookie = str_replace('";"', "{__cookie_replace__}", $cookie); + $parts = str_replace("{__cookie_replace__}", '";"', explode(';', $cookie)); + } else { + $parts = preg_split('/\;[ \t]*/', $cookie); + } + + list($name, $value) = explode('=', array_shift($parts), 2); + $cookies[$name] = compact('value'); + + foreach ($parts as $part) { + if (strpos($part, '=') !== false) { + list($key, $value) = explode('=', $part); + } else { + $key = $part; + $value = true; + } + + $key = strtolower($key); + if (!isset($cookies[$name][$key])) { + $cookies[$name][$key] = $value; + } + } + } + return $cookies; + } + +/** + * Builds cookie headers for a request. + * + * @param array $cookies Array of cookies to send with the request. + * @return string Cookie header string to be sent with the request. + * @access public + * @todo Refactor token escape mechanism to be configurable + */ + function buildCookies($cookies) { + $header = array(); + foreach ($cookies as $name => $cookie) { + $header[] = $name.'='.$this->_escapeToken($cookie['value'], array(';')); + } + $header = $this->_buildHeader(array('Cookie' => implode('; ', $header)), 'pragmatic'); + return $header; + } + +/** + * Unescapes a given $token according to RFC 2616 (HTTP 1.1 specs) + * + * @param string $token Token to unescape + * @return string Unescaped token + * @access protected + * @todo Test $chars parameter + */ + function _unescapeToken($token, $chars = null) { + $regex = '/"(['.join('', $this->_tokenEscapeChars(true, $chars)).'])"/'; + $token = preg_replace($regex, '\\1', $token); + return $token; + } + +/** + * Escapes a given $token according to RFC 2616 (HTTP 1.1 specs) + * + * @param string $token Token to escape + * @return string Escaped token + * @access protected + * @todo Test $chars parameter + */ + function _escapeToken($token, $chars = null) { + $regex = '/(['.join('', $this->_tokenEscapeChars(true, $chars)).'])/'; + $token = preg_replace($regex, '"\\1"', $token); + return $token; + } + +/** + * Gets escape chars according to RFC 2616 (HTTP 1.1 specs). + * + * @param boolean $hex true to get them as HEX values, false otherwise + * @return array Escape chars + * @access protected + * @todo Test $chars parameter + */ + function _tokenEscapeChars($hex = true, $chars = null) { + if (!empty($chars)) { + $escape = $chars; + } else { + $escape = array('"', "(", ")", "<", ">", "@", ",", ";", ":", "\\", "/", "[", "]", "?", "=", "{", "}", " "); + for ($i = 0; $i <= 31; $i++) { + $escape[] = chr($i); + } + $escape[] = chr(127); + } + + if ($hex == false) { + return $escape; + } + $regexChars = ''; + foreach ($escape as $key => $char) { + $escape[$key] = '\\x'.str_pad(dechex(ord($char)), 2, '0', STR_PAD_LEFT); + } + return $escape; + } + +/** + * Resets the state of this HttpSocket instance to it's initial state (before Object::__construct got executed) or does + * the same thing partially for the request and the response property only. + * + * @param boolean $full If set to false only HttpSocket::response and HttpSocket::request are reseted + * @return boolean True on success + * @access public + */ + function reset($full = true) { + static $initalState = array(); + if (empty($initalState)) { + $initalState = get_class_vars(__CLASS__); + } + if ($full == false) { + $this->request = $initalState['request']; + $this->response = $initalState['response']; + return true; + } + parent::reset($initalState); + return true; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/i18n.php b/code/ryzom/tools/server/www/webtt/cake/libs/i18n.php new file mode 100644 index 000000000..3fb94f336 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/i18n.php @@ -0,0 +1,568 @@ +l10n =& new L10n(); + } + return $instance[0]; + } + +/** + * Used by the translation functions in basics.php + * Can also be used like I18n::translate(); but only if the App::import('I18n'); has been used to load the class. + * + * @param string $singular String to translate + * @param string $plural Plural string (if any) + * @param string $domain Domain The domain of the translation. Domains are often used by plugin translations + * @param string $category Category The integer value of the category to use. + * @param integer $count Count Count is used with $plural to choose the correct plural form. + * @return string translated string. + * @access public + */ + function translate($singular, $plural = null, $domain = null, $category = 6, $count = null) { + $_this =& I18n::getInstance(); + + if (strpos($singular, "\r\n") !== false) { + $singular = str_replace("\r\n", "\n", $singular); + } + if ($plural !== null && strpos($plural, "\r\n") !== false) { + $plural = str_replace("\r\n", "\n", $plural); + } + + if (is_numeric($category)) { + $_this->category = $_this->__categories[$category]; + } + $language = Configure::read('Config.language'); + + if (!empty($_SESSION['Config']['language'])) { + $language = $_SESSION['Config']['language']; + } + + if (($_this->__lang && $_this->__lang !== $language) || !$_this->__lang) { + $lang = $_this->l10n->get($language); + $_this->__lang = $lang; + } + + if (is_null($domain)) { + $domain = 'default'; + } + + $_this->domain = $domain . '_' . $_this->l10n->lang; + + if (!isset($_this->__domains[$domain][$_this->__lang])) { + $_this->__domains[$domain][$_this->__lang] = Cache::read($_this->domain, '_cake_core_'); + } + + if (!isset($_this->__domains[$domain][$_this->__lang][$_this->category])) { + $_this->__bindTextDomain($domain); + Cache::write($_this->domain, $_this->__domains[$domain][$_this->__lang], '_cake_core_'); + } + + if ($_this->category == 'LC_TIME') { + return $_this->__translateTime($singular,$domain); + } + + if (!isset($count)) { + $plurals = 0; + } elseif (!empty($_this->__domains[$domain][$_this->__lang][$_this->category]["%plural-c"]) && $_this->__noLocale === false) { + $header = $_this->__domains[$domain][$_this->__lang][$_this->category]["%plural-c"]; + $plurals = $_this->__pluralGuess($header, $count); + } else { + if ($count != 1) { + $plurals = 1; + } else { + $plurals = 0; + } + } + + if (!empty($_this->__domains[$domain][$_this->__lang][$_this->category][$singular])) { + if (($trans = $_this->__domains[$domain][$_this->__lang][$_this->category][$singular]) || ($plurals) && ($trans = $_this->__domains[$domain][$_this->__lang][$_this->category][$plural])) { + if (is_array($trans)) { + if (isset($trans[$plurals])) { + $trans = $trans[$plurals]; + } + } + if (strlen($trans)) { + return $trans; + } + } + } + + if (!empty($plurals)) { + return $plural; + } + return $singular; + } + +/** + * Clears the domains internal data array. Useful for testing i18n. + * + * @return void + */ + function clear() { + $self =& I18n::getInstance(); + $self->__domains = array(); + } + +/** + * Attempts to find the plural form of a string. + * + * @param string $header Type + * @param integrer $n Number + * @return integer plural match + * @access private + */ + function __pluralGuess($header, $n) { + if (!is_string($header) || $header === "nplurals=1;plural=0;" || !isset($header[0])) { + return 0; + } + + if ($header === "nplurals=2;plural=n!=1;") { + return $n != 1 ? 1 : 0; + } elseif ($header === "nplurals=2;plural=n>1;") { + return $n > 1 ? 1 : 0; + } + + if (strpos($header, "plurals=3")) { + if (strpos($header, "100!=11")) { + if (strpos($header, "10<=4")) { + return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); + } elseif (strpos($header, "100<10")) { + return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); + } + return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n != 0 ? 1 : 2); + } elseif (strpos($header, "n==2")) { + return $n == 1 ? 0 : ($n == 2 ? 1 : 2); + } elseif (strpos($header, "n==0")) { + return $n == 1 ? 0 : ($n == 0 || ($n % 100 > 0 && $n % 100 < 20) ? 1 : 2); + } elseif (strpos($header, "n>=2")) { + return $n == 1 ? 0 : ($n >= 2 && $n <= 4 ? 1 : 2); + } elseif (strpos($header, "10>=2")) { + return $n == 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); + } + return $n % 10 == 1 ? 0 : ($n % 10 == 2 ? 1 : 2); + } elseif (strpos($header, "plurals=4")) { + if (strpos($header, "100==2")) { + return $n % 100 == 1 ? 0 : ($n % 100 == 2 ? 1 : ($n % 100 == 3 || $n % 100 == 4 ? 2 : 3)); + } elseif (strpos($header, "n>=3")) { + return $n == 1 ? 0 : ($n == 2 ? 1 : ($n == 0 || ($n >= 3 && $n <= 10) ? 2 : 3)); + } elseif (strpos($header, "100>=1")) { + return $n == 1 ? 0 : ($n == 0 || ($n % 100 >= 1 && $n % 100 <= 10) ? 1 : ($n % 100 >= 11 && $n % 100 <= 20 ? 2 : 3)); + } + } elseif (strpos($header, "plurals=5")) { + return $n == 1 ? 0 : ($n == 2 ? 1 : ($n >= 3 && $n <= 6 ? 2 : ($n >= 7 && $n <= 10 ? 3 : 4))); + } + } + +/** + * Binds the given domain to a file in the specified directory. + * + * @param string $domain Domain to bind + * @return string Domain binded + * @access private + */ + function __bindTextDomain($domain) { + $this->__noLocale = true; + $core = true; + $merge = array(); + $searchPaths = App::path('locales'); + $plugins = App::objects('plugin'); + + if (!empty($plugins)) { + foreach ($plugins as $plugin) { + $plugin = Inflector::underscore($plugin); + if ($plugin === $domain) { + $searchPaths[] = App::pluginPath($plugin) . DS . 'locale' . DS; + $searchPaths = array_reverse($searchPaths); + break; + } + } + } + + + foreach ($searchPaths as $directory) { + + foreach ($this->l10n->languagePath as $lang) { + $file = $directory . $lang . DS . $this->category . DS . $domain; + $localeDef = $directory . $lang . DS . $this->category; + + if ($core) { + $app = $directory . $lang . DS . $this->category . DS . 'core'; + + if (file_exists($fn = "$app.mo")) { + $this->__loadMo($fn, $domain); + $this->__noLocale = false; + $merge[$domain][$this->__lang][$this->category] = $this->__domains[$domain][$this->__lang][$this->category]; + $core = null; + } elseif (file_exists($fn = "$app.po") && ($f = fopen($fn, "r"))) { + $this->__loadPo($f, $domain); + $this->__noLocale = false; + $merge[$domain][$this->__lang][$this->category] = $this->__domains[$domain][$this->__lang][$this->category]; + $core = null; + } + } + + if (file_exists($fn = "$file.mo")) { + $this->__loadMo($fn, $domain); + $this->__noLocale = false; + break 2; + } elseif (file_exists($fn = "$file.po") && ($f = fopen($fn, "r"))) { + $this->__loadPo($f, $domain); + $this->__noLocale = false; + break 2; + } elseif (is_file($localeDef) && ($f = fopen($localeDef, "r"))) { + $this->__loadLocaleDefinition($f, $domain); + $this->__noLocale = false; + return $domain; + } + } + } + + if (empty($this->__domains[$domain][$this->__lang][$this->category])) { + $this->__domains[$domain][$this->__lang][$this->category] = array(); + return $domain; + } + + if (isset($this->__domains[$domain][$this->__lang][$this->category][""])) { + $head = $this->__domains[$domain][$this->__lang][$this->category][""]; + + foreach (explode("\n", $head) as $line) { + $header = strtok($line,":"); + $line = trim(strtok("\n")); + $this->__domains[$domain][$this->__lang][$this->category]["%po-header"][strtolower($header)] = $line; + } + + if (isset($this->__domains[$domain][$this->__lang][$this->category]["%po-header"]["plural-forms"])) { + $switch = preg_replace("/(?:[() {}\\[\\]^\\s*\\]]+)/", "", $this->__domains[$domain][$this->__lang][$this->category]["%po-header"]["plural-forms"]); + $this->__domains[$domain][$this->__lang][$this->category]["%plural-c"] = $switch; + unset($this->__domains[$domain][$this->__lang][$this->category]["%po-header"]); + } + $this->__domains = Set::pushDiff($this->__domains, $merge); + + if (isset($this->__domains[$domain][$this->__lang][$this->category][null])) { + unset($this->__domains[$domain][$this->__lang][$this->category][null]); + } + } + return $domain; + } + +/** + * Loads the binary .mo file for translation and sets the values for this translation in the var I18n::__domains + * + * @param resource $file Binary .mo file to load + * @param string $domain Domain where to load file in + * @access private + */ + function __loadMo($file, $domain) { + $data = file_get_contents($file); + + if ($data) { + $header = substr($data, 0, 20); + $header = unpack("L1magic/L1version/L1count/L1o_msg/L1o_trn", $header); + extract($header); + + if ((dechex($magic) == '950412de' || dechex($magic) == 'ffffffff950412de') && $version == 0) { + for ($n = 0; $n < $count; $n++) { + $r = unpack("L1len/L1offs", substr($data, $o_msg + $n * 8, 8)); + $msgid = substr($data, $r["offs"], $r["len"]); + unset($msgid_plural); + + if (strpos($msgid, "\000")) { + list($msgid, $msgid_plural) = explode("\000", $msgid); + } + $r = unpack("L1len/L1offs", substr($data, $o_trn + $n * 8, 8)); + $msgstr = substr($data, $r["offs"], $r["len"]); + + if (strpos($msgstr, "\000")) { + $msgstr = explode("\000", $msgstr); + } + $this->__domains[$domain][$this->__lang][$this->category][$msgid] = $msgstr; + + if (isset($msgid_plural)) { + $this->__domains[$domain][$this->__lang][$this->category][$msgid_plural] =& $this->__domains[$domain][$this->__lang][$this->category][$msgid]; + } + } + } + } + } + +/** + * Loads the text .po file for translation and sets the values for this translation in the var I18n::__domains + * + * @param resource $file Text .po file to load + * @param string $domain Domain to load file in + * @return array Binded domain elements + * @access private + */ + function __loadPo($file, $domain) { + $type = 0; + $translations = array(); + $translationKey = ""; + $plural = 0; + $header = ""; + + do { + $line = trim(fgets($file)); + if ($line == "" || $line[0] == "#") { + continue; + } + if (preg_match("/msgid[[:space:]]+\"(.+)\"$/i", $line, $regs)) { + $type = 1; + $translationKey = stripcslashes($regs[1]); + } elseif (preg_match("/msgid[[:space:]]+\"\"$/i", $line, $regs)) { + $type = 2; + $translationKey = ""; + } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && ($type == 1 || $type == 2 || $type == 3)) { + $type = 3; + $translationKey .= stripcslashes($regs[1]); + } elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) { + $translations[$translationKey] = stripcslashes($regs[1]); + $type = 4; + } elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) { + $type = 4; + $translations[$translationKey] = ""; + } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 4 && $translationKey) { + $translations[$translationKey] .= stripcslashes($regs[1]); + } elseif (preg_match("/msgid_plural[[:space:]]+\".*\"$/i", $line, $regs)) { + $type = 6; + } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 6 && $translationKey) { + $type = 6; + } elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 6 || $type == 7) && $translationKey) { + $plural = $regs[1]; + $translations[$translationKey][$plural] = stripcslashes($regs[2]); + $type = 7; + } elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"\"$/i", $line, $regs) && ($type == 6 || $type == 7) && $translationKey) { + $plural = $regs[1]; + $translations[$translationKey][$plural] = ""; + $type = 7; + } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 7 && $translationKey) { + $translations[$translationKey][$plural] .= stripcslashes($regs[1]); + } elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && $type == 2 && !$translationKey) { + $header .= stripcslashes($regs[1]); + $type = 5; + } elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && !$translationKey) { + $header = ""; + $type = 5; + } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 5) { + $header .= stripcslashes($regs[1]); + } else { + unset($translations[$translationKey]); + $type = 0; + $translationKey = ""; + $plural = 0; + } + } while (!feof($file)); + fclose($file); + $merge[""] = $header; + return $this->__domains[$domain][$this->__lang][$this->category] = array_merge($merge ,$translations); + } + +/** + * Parses a locale definition file following the POSIX standard + * + * @param resource $file file handler + * @param string $domain Domain where locale definitions will be stored + * @return void + * @access private + */ + function __loadLocaleDefinition($file, $domain = null) { + $comment = '#'; + $escape = '\\'; + $currentToken = false; + $value = ''; + while ($line = fgets($file)) { + $line = trim($line); + if (empty($line) || $line[0] === $comment) { + continue; + } + $parts = preg_split("/[[:space:]]+/",$line); + if ($parts[0] === 'comment_char') { + $comment = $parts[1]; + continue; + } + if ($parts[0] === 'escape_char') { + $escape = $parts[1]; + continue; + } + $count = count($parts); + if ($count == 2) { + $currentToken = $parts[0]; + $value = $parts[1]; + } elseif ($count == 1) { + $value .= $parts[0]; + } else { + continue; + } + + $len = strlen($value) - 1; + if ($value[$len] === $escape) { + $value = substr($value, 0, $len); + continue; + } + + $mustEscape = array($escape . ',' , $escape . ';', $escape . '<', $escape . '>', $escape . $escape); + $replacements = array_map('crc32', $mustEscape); + $value = str_replace($mustEscape, $replacements, $value); + $value = explode(';', $value); + $this->__escape = $escape; + foreach ($value as $i => $val) { + $val = trim($val, '"'); + $val = preg_replace_callback('/(?:<)?(.[^>]*)(?:>)?/', array(&$this, '__parseLiteralValue'), $val); + $val = str_replace($replacements, $mustEscape, $val); + $value[$i] = $val; + } + if (count($value) == 1) { + $this->__domains[$domain][$this->__lang][$this->category][$currentToken] = array_pop($value); + } else { + $this->__domains[$domain][$this->__lang][$this->category][$currentToken] = $value; + } + } + } + +/** + * Auxiliary function to parse a symbol from a locale definition file + * + * @param string $string Symbol to be parsed + * @return string parsed symbol + * @access private + */ + function __parseLiteralValue($string) { + $string = $string[1]; + if (substr($string, 0, 2) === $this->__escape . 'x') { + $delimiter = $this->__escape . 'x'; + return join('', array_map('chr', array_map('hexdec',array_filter(explode($delimiter, $string))))); + } + if (substr($string, 0, 2) === $this->__escape . 'd') { + $delimiter = $this->__escape . 'd'; + return join('', array_map('chr', array_filter(explode($delimiter, $string)))); + } + if ($string[0] === $this->__escape && isset($string[1]) && is_numeric($string[1])) { + $delimiter = $this->__escape; + return join('', array_map('chr', array_filter(explode($delimiter, $string)))); + } + if (substr($string, 0, 3) === 'U00') { + $delimiter = 'U00'; + return join('', array_map('chr', array_map('hexdec', array_filter(explode($delimiter, $string))))); + } + if (preg_match('/U([0-9a-fA-F]{4})/', $string, $match)) { + return Multibyte::ascii(array(hexdec($match[1]))); + } + return $string; + } + +/** + * Returns a Time format definition from corresponding domain + * + * @param string $format Format to be translated + * @param string $domain Domain where format is stored + * @return mixed translated format string if only value or array of translated strings for corresponding format. + * @access private + */ + function __translateTime($format, $domain) { + if (!empty($this->__domains[$domain][$this->__lang]['LC_TIME'][$format])) { + if (($trans = $this->__domains[$domain][$this->__lang][$this->category][$format])) { + return $trans; + } + } + return $format; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/inflector.php b/code/ryzom/tools/server/www/webtt/cake/libs/inflector.php new file mode 100644 index 000000000..65563639f --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/inflector.php @@ -0,0 +1,626 @@ + array( + '/(s)tatus$/i' => '\1\2tatuses', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(buffal|tomat)o$/i' => '\1\2oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(ax|cris|test)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ), + 'uninflected' => array( + '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people' + ), + 'irregular' => array( + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'child' => 'children', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'person' => 'people', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs' + ) + ); + +/** + * Singular inflector rules + * + * @var array + * @access protected + */ + var $_singular = array( + 'rules' => array( + '/(s)tatuses$/i' => '\1\2tatus', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|ba|diagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '' + ), + 'uninflected' => array( + '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss' + ), + 'irregular' => array( + 'waves' => 'wave' + ) + ); + +/** + * Words that should not be inflected + * + * @var array + * @access protected + */ + var $_uninflected = array( + 'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus', + 'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps', + 'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder', + 'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti', + 'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings', + 'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', 'media', + 'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese', + 'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese', + 'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors', + 'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'swine', 'testes', + 'trousers', 'trout','tuna', 'Vermontese', 'Wenchowese', 'whiting', 'wildebeest', + 'Yengeese' + ); + +/** + * Default map of accented and special characters to ASCII characters + * + * @var array + * @access protected + */ + var $_transliteration = array( + '/ä|æ|ǽ/' => 'ae', + '/ö|Å“/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ãœ/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Ã|Â|Ã|Ä|Ã…|Ǻ|Ä€|Ä‚|Ä„|Ç/' => 'A', + '/à|á|â|ã|Ã¥|Ç»|Ä|ă|Ä…|ÇŽ|ª/' => 'a', + '/Ç|Ć|Ĉ|ÄŠ|ÄŒ/' => 'C', + '/ç|ć|ĉ|Ä‹|Ä/' => 'c', + '/Ã|ÄŽ|Ä/' => 'D', + '/ð|Ä|Ä‘/' => 'd', + '/È|É|Ê|Ë|Ä’|Ä”|Ä–|Ę|Äš/' => 'E', + '/è|é|ê|ë|Ä“|Ä•|Ä—|Ä™|Ä›/' => 'e', + '/Äœ|Äž|Ä |Ä¢/' => 'G', + '/Ä|ÄŸ|Ä¡|Ä£/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/Ä¥|ħ/' => 'h', + '/ÃŒ|Ã|ÃŽ|Ã|Ĩ|Ī|Ĭ|Ç|Ä®|Ä°/' => 'I', + '/ì|í|î|ï|Ä©|Ä«|Ä­|Ç|į|ı/' => 'i', + '/Ä´/' => 'J', + '/ĵ/' => 'j', + '/Ķ/' => 'K', + '/Ä·/' => 'k', + '/Ĺ|Ä»|Ľ|Ä¿|Å/' => 'L', + '/ĺ|ļ|ľ|Å€|Å‚/' => 'l', + '/Ñ|Ń|Å…|Ň/' => 'N', + '/ñ|Å„|ņ|ň|ʼn/' => 'n', + '/Ã’|Ó|Ô|Õ|ÅŒ|ÅŽ|Ç‘|Å|Æ |Ø|Ǿ/' => 'O', + '/ò|ó|ô|õ|Å|Å|Ç’|Å‘|Æ¡|ø|Ç¿|º/' => 'o', + '/Å”|Å–|Ř/' => 'R', + '/Å•|Å—|Å™/' => 'r', + '/Åš|Åœ|Åž|Å /' => 'S', + '/Å›|Å|ÅŸ|Å¡|Å¿/' => 's', + '/Å¢|Ť|Ŧ/' => 'T', + '/Å£|Å¥|ŧ/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Å®|Å°|Ų|Ư|Ç“|Ç•|Ç—|Ç™|Ç›/' => 'U', + '/ù|ú|û|Å©|Å«|Å­|ů|ű|ų|Æ°|Ç”|Ç–|ǘ|Çš|Çœ/' => 'u', + '/Ã|Ÿ|Ŷ/' => 'Y', + '/ý|ÿ|Å·/' => 'y', + '/Å´/' => 'W', + '/ŵ/' => 'w', + '/Ź|Å»|Ž/' => 'Z', + '/ź|ż|ž/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/'=> 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Å’/' => 'OE', + '/Æ’/' => 'f' + ); + +/** + * Cached array identity map of pluralized words. + * + * @var array + * @access protected + */ + var $_pluralized = array(); + +/** + * Cached array identity map of singularized words. + * + * @var array + * @access protected + */ + var $_singularized = array(); + +/** + * Cached Underscore Inflections + * + * @var array + * @access protected + */ + var $_underscore = array(); + +/** + * Cached Camelize Inflections + * + * @var array + * @access protected + */ + var $_camelize = array(); + +/** + * Classify cached inflecctions + * + * @var array + * @access protected + */ + var $_classify = array(); + +/** + * Tablize cached inflections + * + * @var array + * @access protected + */ + var $_tableize = array(); + +/** + * Humanize cached inflections + * + * @var array + * @access protected + */ + var $_humanize = array(); + +/** + * Gets a reference to the Inflector object instance + * + * @return object + * @access public + */ + function &getInstance() { + static $instance = array(); + + if (!$instance) { + $instance[0] =& new Inflector(); + } + return $instance[0]; + } + +/** + * Cache inflected values, and return if already available + * + * @param string $type Inflection type + * @param string $key Original value + * @param string $value Inflected value + * @return string Inflected value, from cache + * @access protected + */ + function _cache($type, $key, $value = false) { + $key = '_' . $key; + $type = '_' . $type; + if ($value !== false) { + $this->{$type}[$key] = $value; + return $value; + } + + if (!isset($this->{$type}[$key])) { + return false; + } + return $this->{$type}[$key]; + } + +/** + * Adds custom inflection $rules, of either 'plural', 'singular' or 'transliteration' $type. + * + * ### Usage: + * + * {{{ + * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables')); + * Inflector::rules('plural', array( + * 'rules' => array('/^(inflect)ors$/i' => '\1ables'), + * 'uninflected' => array('dontinflectme'), + * 'irregular' => array('red' => 'redlings') + * )); + * Inflector::rules('transliteration', array('/Ã¥/' => 'aa')); + * }}} + * + * @param string $type The type of inflection, either 'plural', 'singular' or 'transliteration' + * @param array $rules Array of rules to be added. + * @param boolean $reset If true, will unset default inflections for all + * new rules that are being defined in $rules. + * @access public + * @return void + * @static + */ + function rules($type, $rules, $reset = false) { + $_this =& Inflector::getInstance(); + $var = '_'.$type; + + switch ($type) { + case 'transliteration': + if ($reset) { + $_this->_transliteration = $rules; + } else { + $_this->_transliteration = $rules + $_this->_transliteration; + } + break; + + default: + foreach ($rules as $rule => $pattern) { + if (is_array($pattern)) { + if ($reset) { + $_this->{$var}[$rule] = $pattern; + } else { + $_this->{$var}[$rule] = array_merge($pattern, $_this->{$var}[$rule]); + } + unset($rules[$rule], $_this->{$var}['cache' . ucfirst($rule)]); + if (isset($_this->{$var}['merged'][$rule])) { + unset($_this->{$var}['merged'][$rule]); + } + if ($type === 'plural') { + $_this->_pluralized = $_this->_tableize = array(); + } elseif ($type === 'singular') { + $_this->_singularized = array(); + } + } + } + $_this->{$var}['rules'] = array_merge($rules, $_this->{$var}['rules']); + break; + } + } + +/** + * Return $word in plural form. + * + * @param string $word Word in singular + * @return string Word in plural + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function pluralize($word) { + $_this =& Inflector::getInstance(); + + if (isset($_this->_pluralized[$word])) { + return $_this->_pluralized[$word]; + } + + if (!isset($_this->_plural['merged']['irregular'])) { + $_this->_plural['merged']['irregular'] = $_this->_plural['irregular']; + } + + if (!isset($_this->plural['merged']['uninflected'])) { + $_this->_plural['merged']['uninflected'] = array_merge($_this->_plural['uninflected'], $_this->_uninflected); + } + + if (!isset($_this->_plural['cacheUninflected']) || !isset($_this->_plural['cacheIrregular'])) { + $_this->_plural['cacheUninflected'] = '(?:' . implode('|', $_this->_plural['merged']['uninflected']) . ')'; + $_this->_plural['cacheIrregular'] = '(?:' . implode('|', array_keys($_this->_plural['merged']['irregular'])) . ')'; + } + + if (preg_match('/(.*)\\b(' . $_this->_plural['cacheIrregular'] . ')$/i', $word, $regs)) { + $_this->_pluralized[$word] = $regs[1] . substr($word, 0, 1) . substr($_this->_plural['merged']['irregular'][strtolower($regs[2])], 1); + return $_this->_pluralized[$word]; + } + + if (preg_match('/^(' . $_this->_plural['cacheUninflected'] . ')$/i', $word, $regs)) { + $_this->_pluralized[$word] = $word; + return $word; + } + + foreach ($_this->_plural['rules'] as $rule => $replacement) { + if (preg_match($rule, $word)) { + $_this->_pluralized[$word] = preg_replace($rule, $replacement, $word); + return $_this->_pluralized[$word]; + } + } + } + +/** + * Return $word in singular form. + * + * @param string $word Word in plural + * @return string Word in singular + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function singularize($word) { + $_this =& Inflector::getInstance(); + + if (isset($_this->_singularized[$word])) { + return $_this->_singularized[$word]; + } + + if (!isset($_this->_singular['merged']['uninflected'])) { + $_this->_singular['merged']['uninflected'] = array_merge($_this->_singular['uninflected'], $_this->_uninflected); + } + + if (!isset($_this->_singular['merged']['irregular'])) { + $_this->_singular['merged']['irregular'] = array_merge($_this->_singular['irregular'], array_flip($_this->_plural['irregular'])); + } + + if (!isset($_this->_singular['cacheUninflected']) || !isset($_this->_singular['cacheIrregular'])) { + $_this->_singular['cacheUninflected'] = '(?:' . join( '|', $_this->_singular['merged']['uninflected']) . ')'; + $_this->_singular['cacheIrregular'] = '(?:' . join( '|', array_keys($_this->_singular['merged']['irregular'])) . ')'; + } + + if (preg_match('/(.*)\\b(' . $_this->_singular['cacheIrregular'] . ')$/i', $word, $regs)) { + $_this->_singularized[$word] = $regs[1] . substr($word, 0, 1) . substr($_this->_singular['merged']['irregular'][strtolower($regs[2])], 1); + return $_this->_singularized[$word]; + } + + if (preg_match('/^(' . $_this->_singular['cacheUninflected'] . ')$/i', $word, $regs)) { + $_this->_singularized[$word] = $word; + return $word; + } + + foreach ($_this->_singular['rules'] as $rule => $replacement) { + if (preg_match($rule, $word)) { + $_this->_singularized[$word] = preg_replace($rule, $replacement, $word); + return $_this->_singularized[$word]; + } + } + $_this->_singularized[$word] = $word; + return $word; + } + +/** + * Returns the given lower_case_and_underscored_word as a CamelCased word. + * + * @param string $lower_case_and_underscored_word Word to camelize + * @return string Camelized word. LikeThis. + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function camelize($lowerCaseAndUnderscoredWord) { + $_this =& Inflector::getInstance(); + if (!($result = $_this->_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord))) { + $result = str_replace(' ', '', Inflector::humanize($lowerCaseAndUnderscoredWord)); + $_this->_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord, $result); + } + return $result; + } + +/** + * Returns the given camelCasedWord as an underscored_word. + * + * @param string $camelCasedWord Camel-cased word to be "underscorized" + * @return string Underscore-syntaxed version of the $camelCasedWord + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function underscore($camelCasedWord) { + $_this =& Inflector::getInstance(); + if (!($result = $_this->_cache(__FUNCTION__, $camelCasedWord))) { + $result = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $camelCasedWord)); + $_this->_cache(__FUNCTION__, $camelCasedWord, $result); + } + return $result; + } + +/** + * Returns the given underscored_word_group as a Human Readable Word Group. + * (Underscores are replaced by spaces and capitalized following words.) + * + * @param string $lower_case_and_underscored_word String to be made more readable + * @return string Human-readable string + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function humanize($lowerCaseAndUnderscoredWord) { + $_this =& Inflector::getInstance(); + if (!($result = $_this->_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord))) { + $result = ucwords(str_replace('_', ' ', $lowerCaseAndUnderscoredWord)); + $_this->_cache(__FUNCTION__, $lowerCaseAndUnderscoredWord, $result); + } + return $result; + } + +/** + * Returns corresponding table name for given model $className. ("people" for the model class "Person"). + * + * @param string $className Name of class to get database table name for + * @return string Name of the database table for given class + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function tableize($className) { + $_this =& Inflector::getInstance(); + if (!($result = $_this->_cache(__FUNCTION__, $className))) { + $result = Inflector::pluralize(Inflector::underscore($className)); + $_this->_cache(__FUNCTION__, $className, $result); + } + return $result; + } + +/** + * Returns Cake model class name ("Person" for the database table "people".) for given database table. + * + * @param string $tableName Name of database table to get class name for + * @return string Class name + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function classify($tableName) { + $_this =& Inflector::getInstance(); + if (!($result = $_this->_cache(__FUNCTION__, $tableName))) { + $result = Inflector::camelize(Inflector::singularize($tableName)); + $_this->_cache(__FUNCTION__, $tableName, $result); + } + return $result; + } + +/** + * Returns camelBacked version of an underscored string. + * + * @param string $string + * @return string in variable form + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function variable($string) { + $_this =& Inflector::getInstance(); + if (!($result = $_this->_cache(__FUNCTION__, $string))) { + $string2 = Inflector::camelize(Inflector::underscore($string)); + $replace = strtolower(substr($string2, 0, 1)); + $result = preg_replace('/\\w/', $replace, $string2, 1); + $_this->_cache(__FUNCTION__, $string, $result); + } + return $result; + } + +/** + * Returns a string with all spaces converted to underscores (by default), accented + * characters converted to non-accented characters, and non word characters removed. + * + * @param string $string the string you want to slug + * @param string $replacement will replace keys in map + * @param array $map extra elements to map to the replacement + * @deprecated $map param will be removed in future versions. Use Inflector::rules() instead + * @return string + * @access public + * @static + * @link http://book.cakephp.org/view/1479/Class-methods + */ + function slug($string, $replacement = '_', $map = array()) { + $_this =& Inflector::getInstance(); + + if (is_array($replacement)) { + $map = $replacement; + $replacement = '_'; + } + $quotedReplacement = preg_quote($replacement, '/'); + + $merge = array( + '/[^\s\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]/mu' => ' ', + '/\\s+/' => $replacement, + sprintf('/^[%s]+|[%s]+$/', $quotedReplacement, $quotedReplacement) => '', + ); + + $map = $map + $_this->_transliteration + $merge; + return preg_replace(array_keys($map), array_values($map), $string); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/l10n.php b/code/ryzom/tools/server/www/webtt/cake/libs/l10n.php new file mode 100644 index 000000000..801112e82 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/l10n.php @@ -0,0 +1,488 @@ + 'af', + /* Albanian */ 'alb' => 'sq', + /* Arabic */ 'ara' => 'ar', + /* Armenian - Armenia */ 'hye' => 'hy', + /* Basque */ 'baq' => 'eu', + /* Bosnian */ 'bos' => 'bs', + /* Bulgarian */ 'bul' => 'bg', + /* Byelorussian */ 'bel' => 'be', + /* Catalan */ 'cat' => 'ca', + /* Chinese */ 'chi' => 'zh', + /* Chinese */ 'zho' => 'zh', + /* Croatian */ 'hrv' => 'hr', + /* Czech */ 'cze' => 'cs', + /* Czech */ 'ces' => 'cs', + /* Danish */ 'dan' => 'da', + /* Dutch (Standard) */ 'dut' => 'nl', + /* Dutch (Standard) */ 'nld' => 'nl', + /* English */ 'eng' => 'en', + /* Estonian */ 'est' => 'et', + /* Faeroese */ 'fao' => 'fo', + /* Farsi */ 'fas' => 'fa', + /* Farsi */ 'per' => 'fa', + /* Finnish */ 'fin' => 'fi', + /* French (Standard) */ 'fre' => 'fr', + /* French (Standard) */ 'fra' => 'fr', + /* Gaelic (Scots) */ 'gla' => 'gd', + /* Galician */ 'glg' => 'gl', + /* German (Standard) */ 'deu' => 'de', + /* German (Standard) */ 'ger' => 'de', + /* Greek */ 'gre' => 'el', + /* Greek */ 'ell' => 'el', + /* Hebrew */ 'heb' => 'he', + /* Hindi */ 'hin' => 'hi', + /* Hungarian */ 'hun' => 'hu', + /* Icelandic */ 'ice' => 'is', + /* Icelandic */ 'isl' => 'is', + /* Indonesian */ 'ind' => 'id', + /* Irish */ 'gle' => 'ga', + /* Italian */ 'ita' => 'it', + /* Japanese */ 'jpn' => 'ja', + /* Korean */ 'kor' => 'ko', + /* Latvian */ 'lav' => 'lv', + /* Lithuanian */ 'lit' => 'lt', + /* Macedonian */ 'mac' => 'mk', + /* Macedonian */ 'mkd' => 'mk', + /* Malaysian */ 'may' => 'ms', + /* Malaysian */ 'msa' => 'ms', + /* Maltese */ 'mlt' => 'mt', + /* Norwegian */ 'nor' => 'no', + /* Norwegian Bokmal */ 'nob' => 'nb', + /* Norwegian Nynorsk */ 'nno' => 'nn', + /* Polish */ 'pol' => 'pl', + /* Portuguese (Portugal) */ 'por' => 'pt', + /* Rhaeto-Romanic */ 'roh' => 'rm', + /* Romanian */ 'rum' => 'ro', + /* Romanian */ 'ron' => 'ro', + /* Russian */ 'rus' => 'ru', + /* Sami (Lappish) */ 'smi' => 'sz', + /* Serbian */ 'scc' => 'sr', + /* Serbian */ 'srp' => 'sr', + /* Slovak */ 'slo' => 'sk', + /* Slovak */ 'slk' => 'sk', + /* Slovenian */ 'slv' => 'sl', + /* Sorbian */ 'wen' => 'sb', + /* Spanish (Spain - Traditional) */ 'spa' => 'es', + /* Swedish */ 'swe' => 'sv', + /* Thai */ 'tha' => 'th', + /* Tsonga */ 'tso' => 'ts', + /* Tswana */ 'tsn' => 'tn', + /* Turkish */ 'tur' => 'tr', + /* Ukrainian */ 'ukr' => 'uk', + /* Urdu */ 'urd' => 'ur', + /* Venda */ 'ven' => 've', + /* Vietnamese */ 'vie' => 'vi', + /* Welsh */ 'cym' => 'cy', + /* Xhosa */ 'xho' => 'xh', + /* Yiddish */ 'yid' => 'yi', + /* Zulu */ 'zul' => 'zu'); + +/** + * HTTP_ACCEPT_LANGUAGE catalog + * + * holds all information related to a language + * + * @var array + * @access private + */ + var $__l10nCatalog = array('af' => array('language' => 'Afrikaans', 'locale' => 'afr', 'localeFallback' => 'afr', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ar' => array('language' => 'Arabic', 'locale' => 'ara', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ae' => array('language' => 'Arabic (U.A.E.)', 'locale' => 'ar_ae', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-bh' => array('language' => 'Arabic (Bahrain)', 'locale' => 'ar_bh', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-dz' => array('language' => 'Arabic (Algeria)', 'locale' => 'ar_dz', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-eg' => array('language' => 'Arabic (Egypt)', 'locale' => 'ar_eg', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-iq' => array('language' => 'Arabic (Iraq)', 'locale' => 'ar_iq', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-jo' => array('language' => 'Arabic (Jordan)', 'locale' => 'ar_jo', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-kw' => array('language' => 'Arabic (Kuwait)', 'locale' => 'ar_kw', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ly' => array('language' => 'Arabic (Libya)', 'locale' => 'ar_ly', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ma' => array('language' => 'Arabic (Morocco)', 'locale' => 'ar_ma', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-om' => array('language' => 'Arabic (Oman)', 'locale' => 'ar_om', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-qa' => array('language' => 'Arabic (Qatar)', 'locale' => 'ar_qa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-sa' => array('language' => 'Arabic (Saudi Arabia)', 'locale' => 'ar_sa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-sy' => array('language' => 'Arabic (Syria)', 'locale' => 'ar_sy', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-tn' => array('language' => 'Arabic (Tunisia)', 'locale' => 'ar_tn', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ye' => array('language' => 'Arabic (Yemen)', 'locale' => 'ar_ye', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'be' => array('language' => 'Byelorussian', 'locale' => 'bel', 'localeFallback' => 'bel', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'bg' => array('language' => 'Bulgarian', 'locale' => 'bul', 'localeFallback' => 'bul', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'bs' => array('language' => 'Bosnian', 'locale' => 'bos', 'localeFallback' => 'bos', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ca' => array('language' => 'Catalan', 'locale' => 'cat', 'localeFallback' => 'cat', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'cs' => array('language' => 'Czech', 'locale' => 'cze', 'localeFallback' => 'cze', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'da' => array('language' => 'Danish', 'locale' => 'dan', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'e' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'el' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-au' => array('language' => 'English (Australian)', 'locale' => 'en_au', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-bz' => array('language' => 'English (Belize)', 'locale' => 'en_bz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-ca' => array('language' => 'English (Canadian)', 'locale' => 'en_ca', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-gb' => array('language' => 'English (British)', 'locale' => 'en_gb', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-ie' => array('language' => 'English (Ireland)', 'locale' => 'en_ie', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-jm' => array('language' => 'English (Jamaica)', 'locale' => 'en_jm', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-tt' => array('language' => 'English (Trinidad)', 'locale' => 'en_tt', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-us' => array('language' => 'English (United States)', 'locale' => 'en_us', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-za' => array('language' => 'English (South Africa)', 'locale' => 'en_za', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es' => array('language' => 'Spanish (Spain - Traditional)', 'locale' => 'spa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ar' => array('language' => 'Spanish (Argentina)', 'locale' => 'es_ar', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-bo' => array('language' => 'Spanish (Bolivia)', 'locale' => 'es_bo', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-cl' => array('language' => 'Spanish (Chile)', 'locale' => 'es_cl', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-co' => array('language' => 'Spanish (Colombia)', 'locale' => 'es_co', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-cr' => array('language' => 'Spanish (Costa Rica)', 'locale' => 'es_cr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ec' => array('language' => 'Spanish (Ecuador)', 'locale' => 'es_ec', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-es' => array('language' => 'Spanish (Spain)', 'locale' => 'es_es', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-gt' => array('language' => 'Spanish (Guatemala)', 'locale' => 'es_gt', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-hn' => array('language' => 'Spanish (Honduras)', 'locale' => 'es_hn', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-mx' => array('language' => 'Spanish (Mexican)', 'locale' => 'es_mx', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ni' => array('language' => 'Spanish (Nicaragua)', 'locale' => 'es_ni', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pa' => array('language' => 'Spanish (Panama)', 'locale' => 'es_pa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pe' => array('language' => 'Spanish (Peru)', 'locale' => 'es_pe', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pr' => array('language' => 'Spanish (Puerto Rico)', 'locale' => 'es_pr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-py' => array('language' => 'Spanish (Paraguay)', 'locale' => 'es_py', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-sv' => array('language' => 'Spanish (El Salvador)', 'locale' => 'es_sv', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-uy' => array('language' => 'Spanish (Uruguay)', 'locale' => 'es_uy', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ve' => array('language' => 'Spanish (Venezuela)', 'locale' => 'es_ve', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'et' => array('language' => 'Estonian', 'locale' => 'est', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'eu' => array('language' => 'Basque', 'locale' => 'baq', 'localeFallback' => 'baq', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fa' => array('language' => 'Farsi', 'locale' => 'per', 'localeFallback' => 'per', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'fi' => array('language' => 'Finnish', 'locale' => 'fin', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fo' => array('language' => 'Faeroese', 'locale' => 'fao', 'localeFallback' => 'fao', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr' => array('language' => 'French (Standard)', 'locale' => 'fre', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-be' => array('language' => 'French (Belgium)', 'locale' => 'fr_be', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-ca' => array('language' => 'French (Canadian)', 'locale' => 'fr_ca', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-ch' => array('language' => 'French (Swiss)', 'locale' => 'fr_ch', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-fr' => array('language' => 'French (France)', 'locale' => 'fr_fr', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-lu' => array('language' => 'French (Luxembourg)', 'locale' => 'fr_lu', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ga' => array('language' => 'Irish', 'locale' => 'gle', 'localeFallback' => 'gle', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'gd' => array('language' => 'Gaelic (Scots)', 'locale' => 'gla', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'gd-ie' => array('language' => 'Gaelic (Irish)', 'locale' => 'gd_ie', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'gl' => array('language' => 'Galician', 'locale' => 'glg', 'localeFallback' => 'glg', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'he' => array('language' => 'Hebrew', 'locale' => 'heb', 'localeFallback' => 'heb', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'hi' => array('language' => 'Hindi', 'locale' => 'hin', 'localeFallback' => 'hin', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'hr' => array('language' => 'Croatian', 'locale' => 'hrv', 'localeFallback' => 'hrv', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'hu' => array('language' => 'Hungarian', 'locale' => 'hun', 'localeFallback' => 'hun', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'hy' => array('language' => 'Armenian - Armenia', 'locale' => 'hye', 'localeFallback' => 'hye', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'id' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'in' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'is' => array('language' => 'Icelandic', 'locale' => 'ice', 'localeFallback' => 'ice', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'it' => array('language' => 'Italian', 'locale' => 'ita', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'it-ch' => array('language' => 'Italian (Swiss) ', 'locale' => 'it_ch', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), + 'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), + 'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), + 'koi8-r' => array('language' => 'Russian', 'locale' => 'koi8_r', 'localeFallback' => 'rus', 'charset' => 'koi8-r', 'direction' => 'ltr'), + 'lt' => array('language' => 'Lithuanian', 'locale' => 'lit', 'localeFallback' => 'lit', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'lv' => array('language' => 'Latvian', 'locale' => 'lav', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mk', 'localeFallback' => 'mac', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mk-mk' => array('language' => 'Macedonian', 'locale' => 'mk_mk', 'localeFallback' => 'mac', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ms' => array('language' => 'Malaysian', 'locale' => 'may', 'localeFallback' => 'may', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mt' => array('language' => 'Maltese', 'locale' => 'mlt', 'localeFallback' => 'mlt', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'n' => array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nb' => array('language' => 'Norwegian Bokmal', 'locale' => 'nob', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nn' => array('language' => 'Norwegian Nynorsk', 'locale' => 'nno', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'no' => array('language' => 'Norwegian', 'locale' => 'nor', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'p' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pt' => array('language' => 'Portuguese (Portugal)', 'locale' => 'por', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'rm' => array('language' => 'Rhaeto-Romanic', 'locale' => 'roh', 'localeFallback' => 'roh', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ro' => array('language' => 'Romanian', 'locale' => 'rum', 'localeFallback' => 'rum', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ro-mo' => array('language' => 'Romanian (Moldavia)', 'locale' => 'ro_mo', 'localeFallback' => 'rum', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ru' => array('language' => 'Russian', 'locale' => 'rus', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ru-mo' => array('language' => 'Russian (Moldavia)', 'locale' => 'ru_mo', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sb' => array('language' => 'Sorbian', 'locale' => 'wen', 'localeFallback' => 'wen', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sk' => array('language' => 'Slovak', 'locale' => 'slo', 'localeFallback' => 'slo', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sl' => array('language' => 'Slovenian', 'locale' => 'slv', 'localeFallback' => 'slv', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sq' => array('language' => 'Albanian', 'locale' => 'alb', 'localeFallback' => 'alb', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sr' => array('language' => 'Serbian', 'locale' => 'scc', 'localeFallback' => 'scc', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sv' => array('language' => 'Swedish', 'locale' => 'swe', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sv-fi' => array('language' => 'Swedish (Finland)', 'locale' => 'sv_fi', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sx' => array('language' => 'Sutu', 'locale' => 'sx', 'localeFallback' => 'sx', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sz' => array('language' => 'Sami (Lappish)', 'locale' => 'smi', 'localeFallback' => 'smi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'th' => array('language' => 'Thai', 'locale' => 'tha', 'localeFallback' => 'tha', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'tn' => array('language' => 'Tswana', 'locale' => 'tsn', 'localeFallback' => 'tsn', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'tr' => array('language' => 'Turkish', 'locale' => 'tur', 'localeFallback' => 'tur', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ts' => array('language' => 'Tsonga', 'locale' => 'tso', 'localeFallback' => 'tso', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'uk' => array('language' => 'Ukrainian', 'locale' => 'ukr', 'localeFallback' => 'ukr', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ur' => array('language' => 'Urdu', 'locale' => 'urd', 'localeFallback' => 'urd', 'charset' => 'utf-8', 'direction' => 'rtl'), + 've' => array('language' => 'Venda', 'locale' => 'ven', 'localeFallback' => 'ven', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'vi' => array('language' => 'Vietnamese', 'locale' => 'vie', 'localeFallback' => 'vie', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'cy' => array('language' => 'Welsh', 'locale' => 'cym', 'localeFallback' => 'cym', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'xh' => array('language' => 'Xhosa', 'locale' => 'xho', 'localeFallback' => 'xho', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'yi' => array('language' => 'Yiddish', 'locale' => 'yid', 'localeFallback' => 'yid', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh' => array('language' => 'Chinese', 'locale' => 'chi', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-cn' => array('language' => 'Chinese (PRC)', 'locale' => 'zh_cn', 'localeFallback' => 'chi', 'charset' => 'GB2312', 'direction' => 'ltr'), + 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-sg' => array('language' => 'Chinese (Singapore)', 'locale' => 'zh_sg', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-tw' => array('language' => 'Chinese (Taiwan)', 'locale' => 'zh_tw', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zu' => array('language' => 'Zulu', 'locale' => 'zul', 'localeFallback' => 'zul', 'charset' => 'utf-8', 'direction' => 'ltr')); + +/** + * Class constructor + */ + function __construct() { + if (defined('DEFAULT_LANGUAGE')) { + $this->default = DEFAULT_LANGUAGE; + } + parent::__construct(); + } + +/** + * Gets the settings for $language. + * If $language is null it attempt to get settings from L10n::__autoLanguage(); if this fails + * the method will get the settings from L10n::__setLanguage(); + * + * @param string $language Language (if null will use DEFAULT_LANGUAGE if defined) + * @access public + */ + function get($language = null) { + if ($language !== null) { + return $this->__setLanguage($language); + } elseif ($this->__autoLanguage() === false) { + return $this->__setLanguage(); + } + } + +/** + * Sets the class vars to correct values for $language. + * If $language is null it will use the DEFAULT_LANGUAGE if defined + * + * @param string $language Language (if null will use DEFAULT_LANGUAGE if defined) + * @access private + */ + function __setLanguage($language = null) { + $langKey = null; + if ($language !== null && isset($this->__l10nMap[$language]) && isset($this->__l10nCatalog[$this->__l10nMap[$language]])) { + $langKey = $this->__l10nMap[$language]; + } else if ($language !== null && isset($this->__l10nCatalog[$language])) { + $langKey = $language; + } else if (defined('DEFAULT_LANGUAGE')) { + $langKey = $language = DEFAULT_LANGUAGE; + } + + if ($langKey !== null && isset($this->__l10nCatalog[$langKey])) { + $this->language = $this->__l10nCatalog[$langKey]['language']; + $this->languagePath = array( + $this->__l10nCatalog[$langKey]['locale'], + $this->__l10nCatalog[$langKey]['localeFallback'] + ); + $this->lang = $language; + $this->locale = $this->__l10nCatalog[$langKey]['locale']; + $this->charset = $this->__l10nCatalog[$langKey]['charset']; + $this->direction = $this->__l10nCatalog[$langKey]['direction']; + } else { + $this->lang = $language; + $this->languagePath = array($language); + } + + if ($this->default) { + if (isset($this->__l10nMap[$this->default]) && isset($this->__l10nCatalog[$this->__l10nMap[$this->default]])) { + $this->languagePath[] = $this->__l10nCatalog[$this->__l10nMap[$this->default]]['localeFallback']; + } else if (isset($this->__l10nCatalog[$this->default])) { + $this->languagePath[] = $this->__l10nCatalog[$this->default]['localeFallback']; + } + } + $this->found = true; + + if (Configure::read('Config.language') === null) { + Configure::write('Config.language', $this->lang); + } + + if ($language) { + return $language; + } + } + +/** + * Attempts to find the locale settings based on the HTTP_ACCEPT_LANGUAGE variable + * + * @return boolean Success + * @access private + */ + function __autoLanguage() { + $_detectableLanguages = preg_split('/[,;]/', env('HTTP_ACCEPT_LANGUAGE')); + foreach ($_detectableLanguages as $key => $langKey) { + $langKey = strtolower($langKey); + if (strpos($langKey, '_') !== false) { + $langKey = str_replace('_', '-', $langKey); + } + + if (isset($this->__l10nCatalog[$langKey])) { + $this->__setLanguage($langKey); + return true; + } else if (strpos($langKey, '-') !== false) { + $langKey = substr($langKey, 0, 2); + if (isset($this->__l10nCatalog[$langKey])) { + $this->__setLanguage($langKey); + return true; + } + } + } + return false; + } + +/** + * Attempts to find locale for language, or language for locale + * + * @param mixed $mixed 2/3 char string (language/locale), array of those strings, or null + * @return mixed string language/locale, array of those values, whole map as an array, + * or false when language/locale doesn't exist + * @access public + */ + function map($mixed = null) { + if (is_array($mixed)) { + $result = array(); + foreach ($mixed as $_mixed) { + if ($_result = $this->map($_mixed)) { + $result[$_mixed] = $_result; + } + } + return $result; + } else if (is_string($mixed)) { + if (strlen($mixed) === 2 && in_array($mixed, $this->__l10nMap)) { + return array_search($mixed, $this->__l10nMap); + } else if (isset($this->__l10nMap[$mixed])) { + return $this->__l10nMap[$mixed]; + } + return false; + } + return $this->__l10nMap; + } + +/** + * Attempts to find catalog record for requested language + * + * @param mixed $language string requested language, array of requested languages, or null for whole catalog + * @return mixed array catalog record for requested language, array of catalog records, whole catalog, + * or false when language doesn't exist + * @access public + */ + function catalog($language = null) { + if (is_array($language)) { + $result = array(); + foreach ($language as $_language) { + if ($_result = $this->catalog($_language)) { + $result[$_language] = $_result; + } + } + return $result; + } else if (is_string($language)) { + if (isset($this->__l10nCatalog[$language])) { + return $this->__l10nCatalog[$language]; + } else if (isset($this->__l10nMap[$language]) && isset($this->__l10nCatalog[$this->__l10nMap[$language]])) { + return $this->__l10nCatalog[$this->__l10nMap[$language]]; + } + return false; + } + return $this->__l10nCatalog; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/log/file_log.php b/code/ryzom/tools/server/www/webtt/cake/libs/log/file_log.php new file mode 100644 index 000000000..dfc24eac5 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/log/file_log.php @@ -0,0 +1,77 @@ + LOGS); + $this->_path = $options['path']; + } + +/** + * Implements writing to log files. + * + * @param string $type The type of log you are making. + * @param string $message The message you want to log. + * @return boolean success of write. + */ + function write($type, $message) { + $debugTypes = array('notice', 'info', 'debug'); + + if ($type == 'error' || $type == 'warning') { + $filename = $this->_path . 'error.log'; + } elseif (in_array($type, $debugTypes)) { + $filename = $this->_path . 'debug.log'; + } else { + $filename = $this->_path . $type . '.log'; + } + $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; + $log = new File($filename, true); + if ($log->writable()) { + return $log->append($output); + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/magic_db.php b/code/ryzom/tools/server/www/webtt/cake/libs/magic_db.php new file mode 100644 index 000000000..0c94426fd --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/magic_db.php @@ -0,0 +1,302 @@ +exists()) { + return false; + } + if ($File->ext() == 'php') { + include($File->pwd()); + $data = $magicDb; + } else { + // @TODO: Needs test coverage + $data = $File->read(); + } + } + + $magicDb = $this->toArray($data); + if (!$this->validates($magicDb)) { + return false; + } + return !!($this->db = $magicDb); + } + +/** + * Parses a MagicDb $data string into an array or returns the current MagicDb instance as an array + * + * @param string $data A MagicDb string to turn into an array + * @return array A parsed MagicDb array or an empty array if the $data param was invalid. Returns the db property if $data is not set. + * @access public + */ + function toArray($data = null) { + if (is_array($data)) { + return $data; + } + if ($data === null) { + return $this->db; + } + + if (strpos($data, '# FILE_ID DB') !== 0) { + return array(); + } + + $lines = explode("\r\n", $data); + $db = array(); + + $validHeader = count($lines) > 3 + && preg_match('/^# Date:([0-9]{4}-[0-9]{2}-[0-9]{2})$/', $lines[1], $date) + && preg_match('/^# Source:(.+)$/', $lines[2], $source) + && strlen($lines[3]) == 0; + if (!$validHeader) { + return $db; + } + + $db = array('header' => array('Date' => $date[1], 'Source' => $source[1]), 'database' => array()); + $lines = array_splice($lines, 3); + + $format = array(); + while (!empty($lines)) { + $line = array_shift($lines); + if (isset($line[0]) && $line[0] == '#' || empty($line)) { + continue; + } + + $columns = explode("\t", $line); + if (in_array($columns[0]{0}, array('>', '&'))) { + $format[] = $columns; + } elseif (!empty($format)) { + $db['database'][] = $format; + $format = array($columns); + } else { + $format = array($columns); + } + } + + return $db; + } + +/** + * Returns true if the MagicDb instance or the passed $magicDb is valid + * + * @param mixed $magicDb A $magicDb string / array to validate (optional) + * @return boolean True if the $magicDb / instance db validates, false if not + * @access public + */ + function validates($magicDb = null) { + if (is_null($magicDb)) { + $magicDb = $this->db; + } elseif (!is_array($magicDb)) { + $magicDb = $this->toArray($magicDb); + } + + return isset($magicDb['header'], $magicDb['database']) && is_array($magicDb['header']) && is_array($magicDb['database']); + } + +/** + * Analyzes a given $file using the currently loaded MagicDb information based on the desired $options + * + * @param string $file Absolute path to the file to analyze + * @param array $options TBT + * @return mixed + * @access public + */ + function analyze($file, $options = array()) { + if (!is_string($file)) { + return false; + } + + $matches = array(); + $MagicFileResource =& new MagicFileResource($file); + foreach ($this->db['database'] as $format) { + $magic = $format[0]; + $match = $MagicFileResource->test($magic); + if ($match === false) { + continue; + } + $matches[] = $magic; + } + + return $matches; + } +} + +/** + * undocumented class + * + * @package cake.tests + * @subpackage cake.tests.cases.libs + */ +class MagicFileResource extends Object{ + +/** + * undocumented variable + * + * @var unknown + * @access public + */ + var $resource = null; + +/** + * undocumented variable + * + * @var unknown + * @access public + */ + var $offset = 0; + +/** + * undocumented function + * + * @param unknown $file + * @return void + * @access public + */ + function __construct($file) { + if (file_exists($file)) { + $this->resource =& new File($file); + } else { + $this->resource = $file; + } + } + +/** + * undocumented function + * + * @param unknown $magic + * @return void + * @access public + */ + function test($magic) { + $offset = null; + $type = null; + $expected = null; + $comment = null; + if (isset($magic[0])) { + $offset = $magic[0]; + } + if (isset($magic[1])) { + $type = $magic[1]; + } + if (isset($magic[2])) { + $expected = $magic[2]; + } + if (isset($magic[3])) { + $comment = $magic[3]; + } + $val = $this->extract($offset, $type, $expected); + return $val == $expected; + } + +/** + * undocumented function + * + * @param unknown $type + * @param unknown $length + * @return void + * @access public + */ + function read($length = null) { + if (!is_object($this->resource)) { + return substr($this->resource, $this->offset, $length); + } + return $this->resource->read($length); + } + +/** + * undocumented function + * + * @param unknown $type + * @param unknown $expected + * @return void + * @access public + */ + function extract($offset, $type, $expected) { + switch ($type) { + case 'string': + $this->offset($offset); + $val = $this->read(strlen($expected)); + if ($val === $expected) { + return true; + } + break; + } + } + +/** + * undocumented function + * + * @param unknown $offset + * @param unknown $whence + * @return void + * @access public + */ + function offset($offset = null) { + if (is_null($offset)) { + if (!is_object($this->resource)) { + return $this->offset; + } + return $this->offset; + } + + if (!ctype_digit($offset)) { + return false; + } + if (is_object($this->resource)) { + $this->resource->offset($offset); + } else { + $this->offset = $offset; + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/app_model.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/app_model.php new file mode 100644 index 000000000..83b944b83 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/app_model.php @@ -0,0 +1,35 @@ + 'Aro', 'controlled' => 'Aco'); + +/** + * Sets up the configuation for the model, and loads ACL models if they haven't been already + * + * @param mixed $config + * @return void + * @access public + */ + function setup(&$model, $config = array()) { + if (is_string($config)) { + $config = array('type' => $config); + } + $this->settings[$model->name] = array_merge(array('type' => 'requester'), (array)$config); + $this->settings[$model->name]['type'] = strtolower($this->settings[$model->name]['type']); + + $type = $this->__typeMaps[$this->settings[$model->name]['type']]; + if (!class_exists('AclNode')) { + require LIBS . 'model' . DS . 'db_acl.php'; + } + if (PHP5) { + $model->{$type} = ClassRegistry::init($type); + } else { + $model->{$type} =& ClassRegistry::init($type); + } + if (!method_exists($model, 'parentNode')) { + trigger_error(sprintf(__('Callback parentNode() not defined in %s', true), $model->alias), E_USER_WARNING); + } + } + +/** + * Retrieves the Aro/Aco node for this model + * + * @param mixed $ref + * @return array + * @access public + * @link http://book.cakephp.org/view/1322/node + */ + function node(&$model, $ref = null) { + $type = $this->__typeMaps[$this->settings[$model->name]['type']]; + if (empty($ref)) { + $ref = array('model' => $model->name, 'foreign_key' => $model->id); + } + return $model->{$type}->node($ref); + } + +/** + * Creates a new ARO/ACO node bound to this record + * + * @param boolean $created True if this is a new record + * @return void + * @access public + */ + function afterSave(&$model, $created) { + $type = $this->__typeMaps[$this->settings[$model->name]['type']]; + $parent = $model->parentNode(); + if (!empty($parent)) { + $parent = $this->node($model, $parent); + } + $data = array( + 'parent_id' => isset($parent[0][$type]['id']) ? $parent[0][$type]['id'] : null, + 'model' => $model->name, + 'foreign_key' => $model->id + ); + if (!$created) { + $node = $this->node($model); + $data['id'] = isset($node[0][$type]['id']) ? $node[0][$type]['id'] : null; + } + $model->{$type}->create(); + $model->{$type}->save($data); + } + +/** + * Destroys the ARO/ACO node bound to the deleted record + * + * @return void + * @access public + */ + function afterDelete(&$model) { + $type = $this->__typeMaps[$this->settings[$model->name]['type']]; + $node = Set::extract($this->node($model), "0.{$type}.id"); + if (!empty($node)) { + $model->{$type}->delete($node); + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/containable.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/containable.php new file mode 100644 index 000000000..138f290d0 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/containable.php @@ -0,0 +1,453 @@ +settings[$Model->alias])) { + $this->settings[$Model->alias] = array('recursive' => true, 'notices' => true, 'autoFields' => true); + } + if (!is_array($settings)) { + $settings = array(); + } + $this->settings[$Model->alias] = array_merge($this->settings[$Model->alias], $settings); + } + +/** + * Runs before a find() operation. Used to allow 'contain' setting + * as part of the find call, like this: + * + * `Model->find('all', array('contain' => array('Model1', 'Model2')));` + * + * {{{ + * Model->find('all', array('contain' => array( + * 'Model1' => array('Model11', 'Model12'), + * 'Model2', + * 'Model3' => array( + * 'Model31' => 'Model311', + * 'Model32', + * 'Model33' => array('Model331', 'Model332') + * ))); + * }}} + * + * @param object $Model Model using the behavior + * @param array $query Query parameters as set by cake + * @return array + * @access public + */ + function beforeFind(&$Model, $query) { + $reset = (isset($query['reset']) ? $query['reset'] : true); + $noContain = ((isset($this->runtime[$Model->alias]['contain']) && empty($this->runtime[$Model->alias]['contain'])) || (isset($query['contain']) && empty($query['contain']))); + $contain = array(); + if (isset($this->runtime[$Model->alias]['contain'])) { + $contain = $this->runtime[$Model->alias]['contain']; + unset($this->runtime[$Model->alias]['contain']); + } + if (isset($query['contain'])) { + $contain = array_merge($contain, (array)$query['contain']); + } + if ($noContain || !$contain || in_array($contain, array(null, false), true) || (isset($contain[0]) && $contain[0] === null)) { + if ($noContain) { + $query['recursive'] = -1; + } + return $query; + } + if ((isset($contain[0]) && is_bool($contain[0])) || is_bool(end($contain))) { + $reset = is_bool(end($contain)) + ? array_pop($contain) + : array_shift($contain); + } + $containments = $this->containments($Model, $contain); + $map = $this->containmentsMap($containments); + + $mandatory = array(); + foreach ($containments['models'] as $name => $model) { + $instance =& $model['instance']; + $needed = $this->fieldDependencies($instance, $map, false); + if (!empty($needed)) { + $mandatory = array_merge($mandatory, $needed); + } + if ($contain) { + $backupBindings = array(); + foreach ($this->types as $relation) { + if (!empty($instance->__backAssociation[$relation])) { + $backupBindings[$relation] = $instance->__backAssociation[$relation]; + } else { + $backupBindings[$relation] = $instance->{$relation}; + } + } + foreach ($this->types as $type) { + $unbind = array(); + foreach ($instance->{$type} as $assoc => $options) { + if (!isset($model['keep'][$assoc])) { + $unbind[] = $assoc; + } + } + if (!empty($unbind)) { + if (!$reset && empty($instance->__backOriginalAssociation)) { + $instance->__backOriginalAssociation = $backupBindings; + } else if ($reset && empty($instance->__backContainableAssociation)) { + $instance->__backContainableAssociation = $backupBindings; + } + $instance->unbindModel(array($type => $unbind), $reset); + } + foreach ($instance->{$type} as $assoc => $options) { + if (isset($model['keep'][$assoc]) && !empty($model['keep'][$assoc])) { + if (isset($model['keep'][$assoc]['fields'])) { + $model['keep'][$assoc]['fields'] = $this->fieldDependencies($containments['models'][$assoc]['instance'], $map, $model['keep'][$assoc]['fields']); + } + if (!$reset && empty($instance->__backOriginalAssociation)) { + $instance->__backOriginalAssociation = $backupBindings; + } else if ($reset) { + $instance->__backAssociation[$type] = $backupBindings[$type]; + } + $instance->{$type}[$assoc] = array_merge($instance->{$type}[$assoc], $model['keep'][$assoc]); + } + if (!$reset) { + $instance->__backInnerAssociation[] = $assoc; + } + } + } + } + } + + if ($this->settings[$Model->alias]['recursive']) { + $query['recursive'] = (isset($query['recursive'])) ? $query['recursive'] : $containments['depth']; + } + + $autoFields = ($this->settings[$Model->alias]['autoFields'] + && !in_array($Model->findQueryType, array('list', 'count')) + && !empty($query['fields'])); + if (!$autoFields) { + return $query; + } + + $query['fields'] = (array)$query['fields']; + foreach (array('hasOne', 'belongsTo') as $type) { + if (!empty($Model->{$type})) { + foreach ($Model->{$type} as $assoc => $data) { + if ($Model->useDbConfig == $Model->{$assoc}->useDbConfig && !empty($data['fields'])) { + foreach ((array) $data['fields'] as $field) { + $query['fields'][] = (strpos($field, '.') === false ? $assoc . '.' : '') . $field; + } + } + } + } + } + + if (!empty($mandatory[$Model->alias])) { + foreach ($mandatory[$Model->alias] as $field) { + if ($field == '--primaryKey--') { + $field = $Model->primaryKey; + } else if (preg_match('/^.+\.\-\-[^-]+\-\-$/', $field)) { + list($modelName, $field) = explode('.', $field); + if ($Model->useDbConfig == $Model->{$modelName}->useDbConfig) { + $field = $modelName . '.' . ( + ($field === '--primaryKey--') ? $Model->$modelName->primaryKey : $field + ); + } else { + $field = null; + } + } + if ($field !== null) { + $query['fields'][] = $field; + } + } + } + $query['fields'] = array_unique($query['fields']); + return $query; + } + +/** + * Resets original associations on models that may have receive multiple, + * subsequent unbindings. + * + * @param object $Model Model on which we are resetting + * @param array $results Results of the find operation + * @param bool $primary true if this is the primary model that issued the find operation, false otherwise + * @access public + */ + function afterFind(&$Model, $results, $primary) { + if (!empty($Model->__backContainableAssociation)) { + foreach ($Model->__backContainableAssociation as $relation => $bindings) { + $Model->{$relation} = $bindings; + unset($Model->__backContainableAssociation); + } + } + } + +/** + * Unbinds all relations from a model except the specified ones. Calling this function without + * parameters unbinds all related models. + * + * @param object $Model Model on which binding restriction is being applied + * @return void + * @access public + * @link http://book.cakephp.org/view/1323/Containable#Using-Containable-1324 + */ + function contain(&$Model) { + $args = func_get_args(); + $contain = call_user_func_array('am', array_slice($args, 1)); + $this->runtime[$Model->alias]['contain'] = $contain; + } + +/** + * Permanently restore the original binding settings of given model, useful + * for restoring the bindings after using 'reset' => false as part of the + * contain call. + * + * @param object $Model Model on which to reset bindings + * @return void + * @access public + */ + function resetBindings(&$Model) { + if (!empty($Model->__backOriginalAssociation)) { + $Model->__backAssociation = $Model->__backOriginalAssociation; + unset($Model->__backOriginalAssociation); + } + $Model->resetAssociations(); + if (!empty($Model->__backInnerAssociation)) { + $assocs = $Model->__backInnerAssociation; + unset($Model->__backInnerAssociation); + foreach ($assocs as $currentModel) { + $this->resetBindings($Model->$currentModel); + } + } + } + +/** + * Process containments for model. + * + * @param object $Model Model on which binding restriction is being applied + * @param array $contain Parameters to use for restricting this model + * @param array $containments Current set of containments + * @param bool $throwErrors Wether unexisting bindings show throw errors + * @return array Containments + * @access public + */ + function containments(&$Model, $contain, $containments = array(), $throwErrors = null) { + $options = array('className', 'joinTable', 'with', 'foreignKey', 'associationForeignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'unique', 'finderQuery', 'deleteQuery', 'insertQuery'); + $keep = array(); + $depth = array(); + if ($throwErrors === null) { + $throwErrors = (empty($this->settings[$Model->alias]) ? true : $this->settings[$Model->alias]['notices']); + } + foreach ((array)$contain as $name => $children) { + if (is_numeric($name)) { + $name = $children; + $children = array(); + } + if (preg_match('/(? $children); + } + + $children = (array)$children; + foreach ($children as $key => $val) { + if (is_string($key) && is_string($val) && !in_array($key, $options, true)) { + $children[$key] = (array) $val; + } + } + + $keys = array_keys($children); + if ($keys && isset($children[0])) { + $keys = array_merge(array_values($children), $keys); + } + + foreach ($keys as $i => $key) { + if (is_array($key)) { + continue; + } + $optionKey = in_array($key, $options, true); + if (!$optionKey && is_string($key) && preg_match('/^[a-z(]/', $key) && (!isset($Model->{$key}) || !is_object($Model->{$key}))) { + $option = 'fields'; + $val = array($key); + if ($key{0} == '(') { + $val = preg_split('/\s*,\s*/', substr(substr($key, 1), 0, -1)); + } elseif (preg_match('/ASC|DESC$/', $key)) { + $option = 'order'; + $val = $Model->{$name}->alias.'.'.$key; + } elseif (preg_match('/[ =!]/', $key)) { + $option = 'conditions'; + $val = $Model->{$name}->alias.'.'.$key; + } + $children[$option] = is_array($val) ? $val : array($val); + $newChildren = null; + if (!empty($name) && !empty($children[$key])) { + $newChildren = $children[$key]; + } + unset($children[$key], $children[$i]); + $key = $option; + $optionKey = true; + if (!empty($newChildren)) { + $children = Set::merge($children, $newChildren); + } + } + if ($optionKey && isset($children[$key])) { + if (!empty($keep[$name][$key]) && is_array($keep[$name][$key])) { + $keep[$name][$key] = array_merge((isset($keep[$name][$key]) ? $keep[$name][$key] : array()), (array) $children[$key]); + } else { + $keep[$name][$key] = $children[$key]; + } + unset($children[$key]); + } + } + + if (!isset($Model->{$name}) || !is_object($Model->{$name})) { + if ($throwErrors) { + trigger_error(sprintf(__('Model "%s" is not associated with model "%s"', true), $Model->alias, $name), E_USER_WARNING); + } + continue; + } + + $containments = $this->containments($Model->{$name}, $children, $containments); + $depths[] = $containments['depth'] + 1; + if (!isset($keep[$name])) { + $keep[$name] = array(); + } + } + + if (!isset($containments['models'][$Model->alias])) { + $containments['models'][$Model->alias] = array('keep' => array(),'instance' => &$Model); + } + + $containments['models'][$Model->alias]['keep'] = array_merge($containments['models'][$Model->alias]['keep'], $keep); + $containments['depth'] = empty($depths) ? 0 : max($depths); + return $containments; + } + +/** + * Calculate needed fields to fetch the required bindings for the given model. + * + * @param object $Model Model + * @param array $map Map of relations for given model + * @param mixed $fields If array, fields to initially load, if false use $Model as primary model + * @return array Fields + * @access public + */ + function fieldDependencies(&$Model, $map, $fields = array()) { + if ($fields === false) { + foreach ($map as $parent => $children) { + foreach ($children as $type => $bindings) { + foreach ($bindings as $dependency) { + if ($type == 'hasAndBelongsToMany') { + $fields[$parent][] = '--primaryKey--'; + } else if ($type == 'belongsTo') { + $fields[$parent][] = $dependency . '.--primaryKey--'; + } + } + } + } + return $fields; + } + if (empty($map[$Model->alias])) { + return $fields; + } + foreach ($map[$Model->alias] as $type => $bindings) { + foreach ($bindings as $dependency) { + $innerFields = array(); + switch ($type) { + case 'belongsTo': + $fields[] = $Model->{$type}[$dependency]['foreignKey']; + break; + case 'hasOne': + case 'hasMany': + $innerFields[] = $Model->$dependency->primaryKey; + $fields[] = $Model->primaryKey; + break; + } + if (!empty($innerFields) && !empty($Model->{$type}[$dependency]['fields'])) { + $Model->{$type}[$dependency]['fields'] = array_unique(array_merge($Model->{$type}[$dependency]['fields'], $innerFields)); + } + } + } + return array_unique($fields); + } + +/** + * Build the map of containments + * + * @param array $containments Containments + * @return array Built containments + * @access public + */ + function containmentsMap($containments) { + $map = array(); + foreach ($containments['models'] as $name => $model) { + $instance =& $model['instance']; + foreach ($this->types as $type) { + foreach ($instance->{$type} as $assoc => $options) { + if (isset($model['keep'][$assoc])) { + $map[$name][$type] = isset($map[$name][$type]) ? array_merge($map[$name][$type], (array)$assoc) : (array)$assoc; + } + } + } + } + return $map; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/translate.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/translate.php new file mode 100644 index 000000000..fb2d21709 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/translate.php @@ -0,0 +1,540 @@ + array('field_one', + * 'field_two' => 'FieldAssoc', 'field_three')) + * + * With above example only one permanent hasMany will be joined (for field_two + * as FieldAssoc) + * + * $config could be empty - and translations configured dynamically by + * bindTranslation() method + * + * @param Model $model Model the behavior is being attached to. + * @param array $config Array of configuration information. + * @return mixed + * @access public + */ + function setup(&$model, $config = array()) { + $db =& ConnectionManager::getDataSource($model->useDbConfig); + if (!$db->connected) { + trigger_error( + sprintf(__('Datasource %s for TranslateBehavior of model %s is not connected', true), $model->useDbConfig, $model->alias), + E_USER_ERROR + ); + return false; + } + + $this->settings[$model->alias] = array(); + $this->runtime[$model->alias] = array('fields' => array()); + $this->translateModel($model); + return $this->bindTranslation($model, $config, false); + } + +/** + * Cleanup Callback unbinds bound translations and deletes setting information. + * + * @param Model $model Model being detached. + * @return void + * @access public + */ + function cleanup(&$model) { + $this->unbindTranslation($model); + unset($this->settings[$model->alias]); + unset($this->runtime[$model->alias]); + } + +/** + * beforeFind Callback + * + * @param Model $model Model find is being run on. + * @param array $query Array of Query parameters. + * @return array Modified query + * @access public + */ + function beforeFind(&$model, $query) { + $locale = $this->_getLocale($model); + if (empty($locale)) { + return $query; + } + $db =& ConnectionManager::getDataSource($model->useDbConfig); + $RuntimeModel =& $this->translateModel($model); + if (!empty($RuntimeModel->tablePrefix)) { + $tablePrefix = $RuntimeModel->tablePrefix; + } else { + $tablePrefix = $db->config['prefix']; + } + + if (is_string($query['fields']) && 'COUNT(*) AS '.$db->name('count') == $query['fields']) { + $query['fields'] = 'COUNT(DISTINCT('.$db->name($model->alias . '.' . $model->primaryKey) . ')) ' . $db->alias . 'count'; + $query['joins'][] = array( + 'type' => 'INNER', + 'alias' => $RuntimeModel->alias, + 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), + 'conditions' => array( + $model->alias . '.' . $model->primaryKey => $db->identifier($RuntimeModel->alias.'.foreign_key'), + $RuntimeModel->alias.'.model' => $model->name, + $RuntimeModel->alias.'.locale' => $locale + ) + ); + return $query; + } + $autoFields = false; + + if (empty($query['fields'])) { + $query['fields'] = array($model->alias.'.*'); + + $recursive = $model->recursive; + if (isset($query['recursive'])) { + $recursive = $query['recursive']; + } + + if ($recursive >= 0) { + foreach (array('hasOne', 'belongsTo') as $type) { + foreach ($model->{$type} as $key => $value) { + + if (empty($value['fields'])) { + $query['fields'][] = $key.'.*'; + } else { + foreach ($value['fields'] as $field) { + $query['fields'][] = $key.'.'.$field; + } + } + } + } + } + $autoFields = true; + } + $fields = array_merge($this->settings[$model->alias], $this->runtime[$model->alias]['fields']); + $addFields = array(); + if (is_array($query['fields'])) { + foreach ($fields as $key => $value) { + $field = (is_numeric($key)) ? $value : $key; + + if (in_array($model->alias.'.*', $query['fields']) || $autoFields || in_array($model->alias.'.'.$field, $query['fields']) || in_array($field, $query['fields'])) { + $addFields[] = $field; + } + } + } + + if ($addFields) { + foreach ($addFields as $field) { + foreach (array($field, $model->alias.'.'.$field) as $_field) { + $key = array_search($_field, $query['fields']); + + if ($key !== false) { + unset($query['fields'][$key]); + } + } + + if (is_array($locale)) { + foreach ($locale as $_locale) { + $query['fields'][] = 'I18n__'.$field.'__'.$_locale.'.content'; + $query['joins'][] = array( + 'type' => 'LEFT', + 'alias' => 'I18n__'.$field.'__'.$_locale, + 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), + 'conditions' => array( + $model->alias . '.' . $model->primaryKey => $db->identifier("I18n__{$field}__{$_locale}.foreign_key"), + 'I18n__'.$field.'__'.$_locale.'.model' => $model->name, + 'I18n__'.$field.'__'.$_locale.'.'.$RuntimeModel->displayField => $field, + 'I18n__'.$field.'__'.$_locale.'.locale' => $_locale + ) + ); + } + } else { + $query['fields'][] = 'I18n__'.$field.'.content'; + $query['joins'][] = array( + 'type' => 'LEFT', + 'alias' => 'I18n__'.$field, + 'table' => $db->name($tablePrefix . $RuntimeModel->useTable), + 'conditions' => array( + $model->alias . '.' . $model->primaryKey => $db->identifier("I18n__{$field}.foreign_key"), + 'I18n__'.$field.'.model' => $model->name, + 'I18n__'.$field.'.'.$RuntimeModel->displayField => $field + ) + ); + + if (is_string($query['conditions'])) { + $query['conditions'] = $db->conditions($query['conditions'], true, false, $model) . ' AND '.$db->name('I18n__'.$field.'.locale').' = \''.$locale.'\''; + } else { + $query['conditions'][$db->name("I18n__{$field}.locale")] = $locale; + } + } + } + } + if (is_array($query['fields'])) { + $query['fields'] = array_merge($query['fields']); + } + $this->runtime[$model->alias]['beforeFind'] = $addFields; + return $query; + } + +/** + * afterFind Callback + * + * @param Model $model Model find was run on + * @param array $results Array of model results. + * @param boolean $primary Did the find originate on $model. + * @return array Modified results + * @access public + */ + function afterFind(&$model, $results, $primary) { + $this->runtime[$model->alias]['fields'] = array(); + $locale = $this->_getLocale($model); + + if (empty($locale) || empty($results) || empty($this->runtime[$model->alias]['beforeFind'])) { + return $results; + } + $beforeFind = $this->runtime[$model->alias]['beforeFind']; + + foreach ($results as $key => $row) { + $results[$key][$model->alias]['locale'] = (is_array($locale)) ? @$locale[0] : $locale; + + foreach ($beforeFind as $field) { + if (is_array($locale)) { + foreach ($locale as $_locale) { + if (!isset($results[$key][$model->alias][$field]) && !empty($results[$key]['I18n__'.$field.'__'.$_locale]['content'])) { + $results[$key][$model->alias][$field] = $results[$key]['I18n__'.$field.'__'.$_locale]['content']; + } + unset($results[$key]['I18n__'.$field.'__'.$_locale]); + } + + if (!isset($results[$key][$model->alias][$field])) { + $results[$key][$model->alias][$field] = ''; + } + } else { + $value = ''; + if (!empty($results[$key]['I18n__'.$field]['content'])) { + $value = $results[$key]['I18n__'.$field]['content']; + } + $results[$key][$model->alias][$field] = $value; + unset($results[$key]['I18n__'.$field]); + } + } + } + return $results; + } + +/** + * beforeValidate Callback + * + * @param Model $model Model invalidFields was called on. + * @return boolean + * @access public + */ + function beforeValidate(&$model) { + $locale = $this->_getLocale($model); + if (empty($locale)) { + return true; + } + $fields = array_merge($this->settings[$model->alias], $this->runtime[$model->alias]['fields']); + $tempData = array(); + + foreach ($fields as $key => $value) { + $field = (is_numeric($key)) ? $value : $key; + + if (isset($model->data[$model->alias][$field])) { + $tempData[$field] = $model->data[$model->alias][$field]; + if (is_array($model->data[$model->alias][$field])) { + if (is_string($locale) && !empty($model->data[$model->alias][$field][$locale])) { + $model->data[$model->alias][$field] = $model->data[$model->alias][$field][$locale]; + } else { + $values = array_values($model->data[$model->alias][$field]); + $model->data[$model->alias][$field] = $values[0]; + } + } + } + } + $this->runtime[$model->alias]['beforeSave'] = $tempData; + return true; + } + +/** + * afterSave Callback + * + * @param Model $model Model the callback is called on + * @param boolean $created Whether or not the save created a record. + * @return void + * @access public + */ + function afterSave(&$model, $created) { + if (!isset($this->runtime[$model->alias]['beforeSave'])) { + return true; + } + $locale = $this->_getLocale($model); + $tempData = $this->runtime[$model->alias]['beforeSave']; + unset($this->runtime[$model->alias]['beforeSave']); + $conditions = array('model' => $model->alias, 'foreign_key' => $model->id); + $RuntimeModel =& $this->translateModel($model); + + foreach ($tempData as $field => $value) { + unset($conditions['content']); + $conditions['field'] = $field; + if (is_array($value)) { + $conditions['locale'] = array_keys($value); + } else { + $conditions['locale'] = $locale; + if (is_array($locale)) { + $value = array($locale[0] => $value); + } else { + $value = array($locale => $value); + } + } + $translations = $RuntimeModel->find('list', array('conditions' => $conditions, 'fields' => array($RuntimeModel->alias . '.locale', $RuntimeModel->alias . '.id'))); + foreach ($value as $_locale => $_value) { + $RuntimeModel->create(); + $conditions['locale'] = $_locale; + $conditions['content'] = $_value; + if (array_key_exists($_locale, $translations)) { + $RuntimeModel->save(array($RuntimeModel->alias => array_merge($conditions, array('id' => $translations[$_locale])))); + } else { + $RuntimeModel->save(array($RuntimeModel->alias => $conditions)); + } + } + } + } + +/** + * afterDelete Callback + * + * @param Model $model Model the callback was run on. + * @return void + * @access public + */ + function afterDelete(&$model) { + $RuntimeModel =& $this->translateModel($model); + $conditions = array('model' => $model->alias, 'foreign_key' => $model->id); + $RuntimeModel->deleteAll($conditions); + } + +/** + * Get selected locale for model + * + * @param Model $model Model the locale needs to be set/get on. + * @return mixed string or false + * @access protected + */ + function _getLocale(&$model) { + if (!isset($model->locale) || is_null($model->locale)) { + if (!class_exists('I18n')) { + App::import('Core', 'i18n'); + } + $I18n =& I18n::getInstance(); + $I18n->l10n->get(Configure::read('Config.language')); + $model->locale = $I18n->l10n->locale; + } + + return $model->locale; + } + +/** + * Get instance of model for translations. + * + * If the model has a translateModel property set, this will be used as the class + * name to find/use. If no translateModel property is found 'I18nModel' will be used. + * + * @param Model $model Model to get a translatemodel for. + * @return object + * @access public + */ + function &translateModel(&$model) { + if (!isset($this->runtime[$model->alias]['model'])) { + if (!isset($model->translateModel) || empty($model->translateModel)) { + $className = 'I18nModel'; + } else { + $className = $model->translateModel; + } + + if (PHP5) { + $this->runtime[$model->alias]['model'] = ClassRegistry::init($className, 'Model'); + } else { + $this->runtime[$model->alias]['model'] =& ClassRegistry::init($className, 'Model'); + } + } + if (!empty($model->translateTable) && $model->translateTable !== $this->runtime[$model->alias]['model']->useTable) { + $this->runtime[$model->alias]['model']->setSource($model->translateTable); + } elseif (empty($model->translateTable) && empty($model->translateModel)) { + $this->runtime[$model->alias]['model']->setSource('i18n'); + } + $model =& $this->runtime[$model->alias]['model']; + return $model; + } + +/** + * Bind translation for fields, optionally with hasMany association for + * fake field + * + * @param object instance of model + * @param mixed string with field or array(field1, field2=>AssocName, field3) + * @param boolean $reset + * @return bool + */ + function bindTranslation(&$model, $fields, $reset = true) { + if (is_string($fields)) { + $fields = array($fields); + } + $associations = array(); + $RuntimeModel =& $this->translateModel($model); + $default = array('className' => $RuntimeModel->alias, 'foreignKey' => 'foreign_key'); + + foreach ($fields as $key => $value) { + if (is_numeric($key)) { + $field = $value; + $association = null; + } else { + $field = $key; + $association = $value; + } + + if (array_key_exists($field, $this->settings[$model->alias])) { + unset($this->settings[$model->alias][$field]); + } elseif (in_array($field, $this->settings[$model->alias])) { + $this->settings[$model->alias] = array_merge(array_diff_assoc($this->settings[$model->alias], array($field))); + } + + if (array_key_exists($field, $this->runtime[$model->alias]['fields'])) { + unset($this->runtime[$model->alias]['fields'][$field]); + } elseif (in_array($field, $this->runtime[$model->alias]['fields'])) { + $this->runtime[$model->alias]['fields'] = array_merge(array_diff_assoc($this->runtime[$model->alias]['fields'], array($field))); + } + + if (is_null($association)) { + if ($reset) { + $this->runtime[$model->alias]['fields'][] = $field; + } else { + $this->settings[$model->alias][] = $field; + } + } else { + if ($reset) { + $this->runtime[$model->alias]['fields'][$field] = $association; + } else { + $this->settings[$model->alias][$field] = $association; + } + + foreach (array('hasOne', 'hasMany', 'belongsTo', 'hasAndBelongsToMany') as $type) { + if (isset($model->{$type}[$association]) || isset($model->__backAssociation[$type][$association])) { + trigger_error( + sprintf(__('Association %s is already binded to model %s', true), $association, $model->alias), + E_USER_ERROR + ); + return false; + } + } + $associations[$association] = array_merge($default, array('conditions' => array( + 'model' => $model->alias, + $RuntimeModel->displayField => $field + ))); + } + } + + if (!empty($associations)) { + $model->bindModel(array('hasMany' => $associations), $reset); + } + return true; + } + +/** + * Unbind translation for fields, optionally unbinds hasMany association for + * fake field + * + * @param object $model instance of model + * @param mixed $fields string with field, or array(field1, field2=>AssocName, field3), or null for + * unbind all original translations + * @return bool + */ + function unbindTranslation(&$model, $fields = null) { + if (empty($fields) && empty($this->settings[$model->alias])) { + return false; + } + if (empty($fields)) { + return $this->unbindTranslation($model, $this->settings[$model->alias]); + } + + if (is_string($fields)) { + $fields = array($fields); + } + $RuntimeModel =& $this->translateModel($model); + $associations = array(); + + foreach ($fields as $key => $value) { + if (is_numeric($key)) { + $field = $value; + $association = null; + } else { + $field = $key; + $association = $value; + } + + if (array_key_exists($field, $this->settings[$model->alias])) { + unset($this->settings[$model->alias][$field]); + } elseif (in_array($field, $this->settings[$model->alias])) { + $this->settings[$model->alias] = array_merge(array_diff_assoc($this->settings[$model->alias], array($field))); + } + + if (array_key_exists($field, $this->runtime[$model->alias]['fields'])) { + unset($this->runtime[$model->alias]['fields'][$field]); + } elseif (in_array($field, $this->runtime[$model->alias]['fields'])) { + $this->runtime[$model->alias]['fields'] = array_merge(array_diff_assoc($this->runtime[$model->alias]['fields'], array($field))); + } + + if (!is_null($association) && (isset($model->hasMany[$association]) || isset($model->__backAssociation['hasMany'][$association]))) { + $associations[] = $association; + } + } + + if (!empty($associations)) { + $model->unbindModel(array('hasMany' => $associations), false); + } + return true; + } +} +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + +/** + * @package cake + * @subpackage cake.cake.libs.model.behaviors + */ + class I18nModel extends AppModel { + var $name = 'I18nModel'; + var $useTable = 'i18n'; + var $displayField = 'field'; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/tree.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/tree.php new file mode 100644 index 000000000..3e631190f --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/behaviors/tree.php @@ -0,0 +1,977 @@ + 'parent_id', 'left' => 'lft', 'right' => 'rght', + 'scope' => '1 = 1', 'type' => 'nested', '__parentChange' => false, 'recursive' => -1 + ); + +/** + * Initiate Tree behavior + * + * @param object $Model instance of model + * @param array $config array of configuration settings. + * @return void + * @access public + */ + function setup(&$Model, $config = array()) { + if (!is_array($config)) { + $config = array('type' => $config); + } + $settings = array_merge($this->_defaults, $config); + + if (in_array($settings['scope'], $Model->getAssociated('belongsTo'))) { + $data = $Model->getAssociated($settings['scope']); + $parent =& $Model->{$settings['scope']}; + $settings['scope'] = $Model->alias . '.' . $data['foreignKey'] . ' = ' . $parent->alias . '.' . $parent->primaryKey; + $settings['recursive'] = 0; + } + $this->settings[$Model->alias] = $settings; + } + +/** + * After save method. Called after all saves + * + * Overriden to transparently manage setting the lft and rght fields if and only if the parent field is included in the + * parameters to be saved. + * + * @param AppModel $Model Model instance. + * @param boolean $created indicates whether the node just saved was created or updated + * @return boolean true on success, false on failure + * @access public + */ + function afterSave(&$Model, $created) { + extract($this->settings[$Model->alias]); + if ($created) { + if ((isset($Model->data[$Model->alias][$parent])) && $Model->data[$Model->alias][$parent]) { + return $this->_setParent($Model, $Model->data[$Model->alias][$parent], $created); + } + } elseif ($__parentChange) { + $this->settings[$Model->alias]['__parentChange'] = false; + return $this->_setParent($Model, $Model->data[$Model->alias][$parent]); + } + } + +/** + * Before delete method. Called before all deletes + * + * Will delete the current node and all children using the deleteAll method and sync the table + * + * @param AppModel $Model Model instance + * @return boolean true to continue, false to abort the delete + * @access public + */ + function beforeDelete(&$Model) { + extract($this->settings[$Model->alias]); + list($name, $data) = array($Model->alias, $Model->read()); + $data = $data[$name]; + + if (!$data[$right] || !$data[$left]) { + return true; + } + $diff = $data[$right] - $data[$left] + 1; + + if ($diff > 2) { + if (is_string($scope)) { + $scope = array($scope); + } + $scope[]["{$Model->alias}.{$left} BETWEEN ? AND ?"] = array($data[$left] + 1, $data[$right] - 1); + $Model->deleteAll($scope); + } + $this->__sync($Model, $diff, '-', '> ' . $data[$right]); + return true; + } + +/** + * Before save method. Called before all saves + * + * Overriden to transparently manage setting the lft and rght fields if and only if the parent field is included in the + * parameters to be saved. For newly created nodes with NO parent the left and right field values are set directly by + * this method bypassing the setParent logic. + * + * @since 1.2 + * @param AppModel $Model Model instance + * @return boolean true to continue, false to abort the save + * @access public + */ + function beforeSave(&$Model) { + extract($this->settings[$Model->alias]); + + $this->_addToWhitelist($Model, array($left, $right)); + if (!$Model->id) { + if (array_key_exists($parent, $Model->data[$Model->alias]) && $Model->data[$Model->alias][$parent]) { + $parentNode = $Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $Model->data[$Model->alias][$parent]), + 'fields' => array($Model->primaryKey, $right), 'recursive' => $recursive + )); + if (!$parentNode) { + return false; + } + list($parentNode) = array_values($parentNode); + $Model->data[$Model->alias][$left] = 0; //$parentNode[$right]; + $Model->data[$Model->alias][$right] = 0; //$parentNode[$right] + 1; + } else { + $edge = $this->__getMax($Model, $scope, $right, $recursive); + $Model->data[$Model->alias][$left] = $edge + 1; + $Model->data[$Model->alias][$right] = $edge + 2; + } + } elseif (array_key_exists($parent, $Model->data[$Model->alias])) { + if ($Model->data[$Model->alias][$parent] != $Model->field($parent)) { + $this->settings[$Model->alias]['__parentChange'] = true; + } + if (!$Model->data[$Model->alias][$parent]) { + $Model->data[$Model->alias][$parent] = null; + $this->_addToWhitelist($Model, $parent); + } else { + $values = $Model->find('first', array( + 'conditions' => array($scope,$Model->escapeField() => $Model->id), + 'fields' => array($Model->primaryKey, $parent, $left, $right ), 'recursive' => $recursive) + ); + + if ($values === false) { + return false; + } + list($node) = array_values($values); + + $parentNode = $Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $Model->data[$Model->alias][$parent]), + 'fields' => array($Model->primaryKey, $left, $right), 'recursive' => $recursive + )); + if (!$parentNode) { + return false; + } + list($parentNode) = array_values($parentNode); + + if (($node[$left] < $parentNode[$left]) && ($parentNode[$right] < $node[$right])) { + return false; + } elseif ($node[$Model->primaryKey] == $parentNode[$Model->primaryKey]) { + return false; + } + } + } + return true; + } + +/** + * Get the number of child nodes + * + * If the direct parameter is set to true, only the direct children are counted (based upon the parent_id field) + * If false is passed for the id parameter, all top level nodes are counted, or all nodes are counted. + * + * @param AppModel $Model Model instance + * @param mixed $id The ID of the record to read or false to read all top level nodes + * @param boolean $direct whether to count direct, or all, children + * @return integer number of child nodes + * @access public + * @link http://book.cakephp.org/view/1347/Counting-children + */ + function childcount(&$Model, $id = null, $direct = false) { + if (is_array($id)) { + extract (array_merge(array('id' => null), $id)); + } + if ($id === null && $Model->id) { + $id = $Model->id; + } elseif (!$id) { + $id = null; + } + extract($this->settings[$Model->alias]); + + if ($direct) { + return $Model->find('count', array('conditions' => array($scope, $Model->escapeField($parent) => $id))); + } + + if ($id === null) { + return $Model->find('count', array('conditions' => $scope)); + } elseif (isset($Model->data[$Model->alias][$left]) && isset($Model->data[$Model->alias][$right])) { + $data = $Model->data[$Model->alias]; + } else { + $data = $Model->find('first', array('conditions' => array($scope, $Model->escapeField() => $id), 'recursive' => $recursive)); + if (!$data) { + return 0; + } + $data = $data[$Model->alias]; + } + return ($data[$right] - $data[$left] - 1) / 2; + } + +/** + * Get the child nodes of the current model + * + * If the direct parameter is set to true, only the direct children are returned (based upon the parent_id field) + * If false is passed for the id parameter, top level, or all (depending on direct parameter appropriate) are counted. + * + * @param AppModel $Model Model instance + * @param mixed $id The ID of the record to read + * @param boolean $direct whether to return only the direct, or all, children + * @param mixed $fields Either a single string of a field name, or an array of field names + * @param string $order SQL ORDER BY conditions (e.g. "price DESC" or "name ASC") defaults to the tree order + * @param integer $limit SQL LIMIT clause, for calculating items per page. + * @param integer $page Page number, for accessing paged data + * @param integer $recursive The number of levels deep to fetch associated records + * @return array Array of child nodes + * @access public + * @link http://book.cakephp.org/view/1346/Children + */ + function children(&$Model, $id = null, $direct = false, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null) { + if (is_array($id)) { + extract (array_merge(array('id' => null), $id)); + } + $overrideRecursive = $recursive; + + if ($id === null && $Model->id) { + $id = $Model->id; + } elseif (!$id) { + $id = null; + } + $name = $Model->alias; + extract($this->settings[$Model->alias]); + + if (!is_null($overrideRecursive)) { + $recursive = $overrideRecursive; + } + if (!$order) { + $order = $Model->alias . '.' . $left . ' asc'; + } + if ($direct) { + $conditions = array($scope, $Model->escapeField($parent) => $id); + return $Model->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive')); + } + + if (!$id) { + $conditions = $scope; + } else { + $result = array_values((array)$Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $id), + 'fields' => array($left, $right), + 'recursive' => $recursive + ))); + + if (empty($result) || !isset($result[0])) { + return array(); + } + $conditions = array($scope, + $Model->escapeField($right) . ' <' => $result[0][$right], + $Model->escapeField($left) . ' >' => $result[0][$left] + ); + } + return $Model->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive')); + } + +/** + * A convenience method for returning a hierarchical array used for HTML select boxes + * + * @param AppModel $Model Model instance + * @param mixed $conditions SQL conditions as a string or as an array('field' =>'value',...) + * @param string $keyPath A string path to the key, i.e. "{n}.Post.id" + * @param string $valuePath A string path to the value, i.e. "{n}.Post.title" + * @param string $spacer The character or characters which will be repeated + * @param integer $recursive The number of levels deep to fetch associated records + * @return array An associative array of records, where the id is the key, and the display field is the value + * @access public + * @link http://book.cakephp.org/view/1348/generatetreelist + */ + function generatetreelist(&$Model, $conditions = null, $keyPath = null, $valuePath = null, $spacer = '_', $recursive = null) { + $overrideRecursive = $recursive; + extract($this->settings[$Model->alias]); + if (!is_null($overrideRecursive)) { + $recursive = $overrideRecursive; + } + + if ($keyPath == null && $valuePath == null && $Model->hasField($Model->displayField)) { + $fields = array($Model->primaryKey, $Model->displayField, $left, $right); + } else { + $fields = null; + } + + if ($keyPath == null) { + $keyPath = '{n}.' . $Model->alias . '.' . $Model->primaryKey; + } + + if ($valuePath == null) { + $valuePath = array('{0}{1}', '{n}.tree_prefix', '{n}.' . $Model->alias . '.' . $Model->displayField); + + } elseif (is_string($valuePath)) { + $valuePath = array('{0}{1}', '{n}.tree_prefix', $valuePath); + + } else { + $valuePath[0] = '{' . (count($valuePath) - 1) . '}' . $valuePath[0]; + $valuePath[] = '{n}.tree_prefix'; + } + $order = $Model->alias . '.' . $left . ' asc'; + $results = $Model->find('all', compact('conditions', 'fields', 'order', 'recursive')); + $stack = array(); + + foreach ($results as $i => $result) { + while ($stack && ($stack[count($stack) - 1] < $result[$Model->alias][$right])) { + array_pop($stack); + } + $results[$i]['tree_prefix'] = str_repeat($spacer,count($stack)); + $stack[] = $result[$Model->alias][$right]; + } + if (empty($results)) { + return array(); + } + return Set::combine($results, $keyPath, $valuePath); + } + +/** + * Get the parent node + * + * reads the parent id and returns this node + * + * @param AppModel $Model Model instance + * @param mixed $id The ID of the record to read + * @param integer $recursive The number of levels deep to fetch associated records + * @return array Array of data for the parent node + * @access public + * @link http://book.cakephp.org/view/1349/getparentnode + */ + function getparentnode(&$Model, $id = null, $fields = null, $recursive = null) { + if (is_array($id)) { + extract (array_merge(array('id' => null), $id)); + } + $overrideRecursive = $recursive; + if (empty ($id)) { + $id = $Model->id; + } + extract($this->settings[$Model->alias]); + if (!is_null($overrideRecursive)) { + $recursive = $overrideRecursive; + } + $parentId = $Model->find('first', array('conditions' => array($Model->primaryKey => $id), 'fields' => array($parent), 'recursive' => -1)); + + if ($parentId) { + $parentId = $parentId[$Model->alias][$parent]; + $parent = $Model->find('first', array('conditions' => array($Model->escapeField() => $parentId), 'fields' => $fields, 'recursive' => $recursive)); + + return $parent; + } + return false; + } + +/** + * Get the path to the given node + * + * @param AppModel $Model Model instance + * @param mixed $id The ID of the record to read + * @param mixed $fields Either a single string of a field name, or an array of field names + * @param integer $recursive The number of levels deep to fetch associated records + * @return array Array of nodes from top most parent to current node + * @access public + * @link http://book.cakephp.org/view/1350/getpath + */ + function getpath(&$Model, $id = null, $fields = null, $recursive = null) { + if (is_array($id)) { + extract (array_merge(array('id' => null), $id)); + } + $overrideRecursive = $recursive; + if (empty ($id)) { + $id = $Model->id; + } + extract($this->settings[$Model->alias]); + if (!is_null($overrideRecursive)) { + $recursive = $overrideRecursive; + } + $result = $Model->find('first', array('conditions' => array($Model->escapeField() => $id), 'fields' => array($left, $right), 'recursive' => $recursive)); + if ($result) { + $result = array_values($result); + } else { + return null; + } + $item = $result[0]; + $results = $Model->find('all', array( + 'conditions' => array($scope, $Model->escapeField($left) . ' <=' => $item[$left], $Model->escapeField($right) . ' >=' => $item[$right]), + 'fields' => $fields, 'order' => array($Model->escapeField($left) => 'asc'), 'recursive' => $recursive + )); + return $results; + } + +/** + * Reorder the node without changing the parent. + * + * If the node is the last child, or is a top level node with no subsequent node this method will return false + * + * @param AppModel $Model Model instance + * @param mixed $id The ID of the record to move + * @param int|bool $number how many places to move the node or true to move to last position + * @return boolean true on success, false on failure + * @access public + * @link http://book.cakephp.org/view/1352/moveDown + */ + function movedown(&$Model, $id = null, $number = 1) { + if (is_array($id)) { + extract (array_merge(array('id' => null), $id)); + } + if (!$number) { + return false; + } + if (empty ($id)) { + $id = $Model->id; + } + extract($this->settings[$Model->alias]); + list($node) = array_values($Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $id), + 'fields' => array($Model->primaryKey, $left, $right, $parent), 'recursive' => $recursive + ))); + if ($node[$parent]) { + list($parentNode) = array_values($Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $node[$parent]), + 'fields' => array($Model->primaryKey, $left, $right), 'recursive' => $recursive + ))); + if (($node[$right] + 1) == $parentNode[$right]) { + return false; + } + } + $nextNode = $Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField($left) => ($node[$right] + 1)), + 'fields' => array($Model->primaryKey, $left, $right), 'recursive' => $recursive) + ); + if ($nextNode) { + list($nextNode) = array_values($nextNode); + } else { + return false; + } + $edge = $this->__getMax($Model, $scope, $right, $recursive); + $this->__sync($Model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right]); + $this->__sync($Model, $nextNode[$left] - $node[$left], '-', 'BETWEEN ' . $nextNode[$left] . ' AND ' . $nextNode[$right]); + $this->__sync($Model, $edge - $node[$left] - ($nextNode[$right] - $nextNode[$left]), '-', '> ' . $edge); + + if (is_int($number)) { + $number--; + } + if ($number) { + $this->moveDown($Model, $id, $number); + } + return true; + } + +/** + * Reorder the node without changing the parent. + * + * If the node is the first child, or is a top level node with no previous node this method will return false + * + * @param AppModel $Model Model instance + * @param mixed $id The ID of the record to move + * @param int|bool $number how many places to move the node, or true to move to first position + * @return boolean true on success, false on failure + * @access public + * @link http://book.cakephp.org/view/1353/moveUp + */ + function moveup(&$Model, $id = null, $number = 1) { + if (is_array($id)) { + extract (array_merge(array('id' => null), $id)); + } + if (!$number) { + return false; + } + if (empty ($id)) { + $id = $Model->id; + } + extract($this->settings[$Model->alias]); + list($node) = array_values($Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $id), + 'fields' => array($Model->primaryKey, $left, $right, $parent ), 'recursive' => $recursive + ))); + if ($node[$parent]) { + list($parentNode) = array_values($Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $node[$parent]), + 'fields' => array($Model->primaryKey, $left, $right), 'recursive' => $recursive + ))); + if (($node[$left] - 1) == $parentNode[$left]) { + return false; + } + } + $previousNode = $Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField($right) => ($node[$left] - 1)), + 'fields' => array($Model->primaryKey, $left, $right), + 'recursive' => $recursive + )); + + if ($previousNode) { + list($previousNode) = array_values($previousNode); + } else { + return false; + } + $edge = $this->__getMax($Model, $scope, $right, $recursive); + $this->__sync($Model, $edge - $previousNode[$left] +1, '+', 'BETWEEN ' . $previousNode[$left] . ' AND ' . $previousNode[$right]); + $this->__sync($Model, $node[$left] - $previousNode[$left], '-', 'BETWEEN ' .$node[$left] . ' AND ' . $node[$right]); + $this->__sync($Model, $edge - $previousNode[$left] - ($node[$right] - $node[$left]), '-', '> ' . $edge); + if (is_int($number)) { + $number--; + } + if ($number) { + $this->moveUp($Model, $id, $number); + } + return true; + } + +/** + * Recover a corrupted tree + * + * The mode parameter is used to specify the source of info that is valid/correct. The opposite source of data + * will be populated based upon that source of info. E.g. if the MPTT fields are corrupt or empty, with the $mode + * 'parent' the values of the parent_id field will be used to populate the left and right fields. The missingParentAction + * parameter only applies to "parent" mode and determines what to do if the parent field contains an id that is not present. + * + * @todo Could be written to be faster, *maybe*. Ideally using a subquery and putting all the logic burden on the DB. + * @param AppModel $Model Model instance + * @param string $mode parent or tree + * @param mixed $missingParentAction 'return' to do nothing and return, 'delete' to + * delete, or the id of the parent to set as the parent_id + * @return boolean true on success, false on failure + * @access public + * @link http://book.cakephp.org/view/1628/Recover + */ + function recover(&$Model, $mode = 'parent', $missingParentAction = null) { + if (is_array($mode)) { + extract (array_merge(array('mode' => 'parent'), $mode)); + } + extract($this->settings[$Model->alias]); + $Model->recursive = $recursive; + if ($mode == 'parent') { + $Model->bindModel(array('belongsTo' => array('VerifyParent' => array( + 'className' => $Model->alias, + 'foreignKey' => $parent, + 'fields' => array($Model->primaryKey, $left, $right, $parent), + )))); + $missingParents = $Model->find('list', array( + 'recursive' => 0, + 'conditions' => array($scope, array( + 'NOT' => array($Model->escapeField($parent) => null), $Model->VerifyParent->escapeField() => null + )) + )); + $Model->unbindModel(array('belongsTo' => array('VerifyParent'))); + if ($missingParents) { + if ($missingParentAction == 'return') { + foreach ($missingParents as $id => $display) { + $this->errors[] = 'cannot find the parent for ' . $Model->alias . ' with id ' . $id . '(' . $display . ')'; + + } + return false; + } elseif ($missingParentAction == 'delete') { + $Model->deleteAll(array($Model->primaryKey => array_flip($missingParents))); + } else { + $Model->updateAll(array($parent => $missingParentAction), array($Model->escapeField($Model->primaryKey) => array_flip($missingParents))); + } + } + $count = 1; + foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey), 'order' => $left)) as $array) { + $Model->id = $array[$Model->alias][$Model->primaryKey]; + $lft = $count++; + $rght = $count++; + $Model->save(array($left => $lft, $right => $rght), array('callbacks' => false)); + } + foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey, $parent), 'order' => $left)) as $array) { + $Model->create(); + $Model->id = $array[$Model->alias][$Model->primaryKey]; + $this->_setParent($Model, $array[$Model->alias][$parent]); + } + } else { + $db =& ConnectionManager::getDataSource($Model->useDbConfig); + foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey, $parent), 'order' => $left)) as $array) { + $path = $this->getpath($Model, $array[$Model->alias][$Model->primaryKey]); + if ($path == null || count($path) < 2) { + $parentId = null; + } else { + $parentId = $path[count($path) - 2][$Model->alias][$Model->primaryKey]; + } + $Model->updateAll(array($parent => $db->value($parentId, $parent)), array($Model->escapeField() => $array[$Model->alias][$Model->primaryKey])); + } + } + return true; + } + +/** + * Reorder method. + * + * Reorders the nodes (and child nodes) of the tree according to the field and direction specified in the parameters. + * This method does not change the parent of any node. + * + * Requires a valid tree, by default it verifies the tree before beginning. + * + * Options: + * + * - 'id' id of record to use as top node for reordering + * - 'field' Which field to use in reordeing defaults to displayField + * - 'order' Direction to order either DESC or ASC (defaults to ASC) + * - 'verify' Whether or not to verify the tree before reorder. defaults to true. + * + * @param AppModel $Model Model instance + * @param array $options array of options to use in reordering. + * @return boolean true on success, false on failure + * @link http://book.cakephp.org/view/1355/reorder + * @link http://book.cakephp.org/view/1629/Reorder + */ + function reorder(&$Model, $options = array()) { + $options = array_merge(array('id' => null, 'field' => $Model->displayField, 'order' => 'ASC', 'verify' => true), $options); + extract($options); + if ($verify && !$this->verify($Model)) { + return false; + } + $verify = false; + extract($this->settings[$Model->alias]); + $fields = array($Model->primaryKey, $field, $left, $right); + $sort = $field . ' ' . $order; + $nodes = $this->children($Model, $id, true, $fields, $sort, null, null, $recursive); + + $cacheQueries = $Model->cacheQueries; + $Model->cacheQueries = false; + if ($nodes) { + foreach ($nodes as $node) { + $id = $node[$Model->alias][$Model->primaryKey]; + $this->moveDown($Model, $id, true); + if ($node[$Model->alias][$left] != $node[$Model->alias][$right] - 1) { + $this->reorder($Model, compact('id', 'field', 'order', 'verify')); + } + } + } + $Model->cacheQueries = $cacheQueries; + return true; + } + +/** + * Remove the current node from the tree, and reparent all children up one level. + * + * If the parameter delete is false, the node will become a new top level node. Otherwise the node will be deleted + * after the children are reparented. + * + * @param AppModel $Model Model instance + * @param mixed $id The ID of the record to remove + * @param boolean $delete whether to delete the node after reparenting children (if any) + * @return boolean true on success, false on failure + * @access public + * @link http://book.cakephp.org/view/1354/removeFromTree + */ + function removefromtree(&$Model, $id = null, $delete = false) { + if (is_array($id)) { + extract (array_merge(array('id' => null), $id)); + } + extract($this->settings[$Model->alias]); + + list($node) = array_values($Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $id), + 'fields' => array($Model->primaryKey, $left, $right, $parent), + 'recursive' => $recursive + ))); + + if ($node[$right] == $node[$left] + 1) { + if ($delete) { + return $Model->delete($id); + } else { + $Model->id = $id; + return $Model->saveField($parent, null); + } + } elseif ($node[$parent]) { + list($parentNode) = array_values($Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $node[$parent]), + 'fields' => array($Model->primaryKey, $left, $right), + 'recursive' => $recursive + ))); + } else { + $parentNode[$right] = $node[$right] + 1; + } + + $db =& ConnectionManager::getDataSource($Model->useDbConfig); + $Model->updateAll( + array($parent => $db->value($node[$parent], $parent)), + array($Model->escapeField($parent) => $node[$Model->primaryKey]) + ); + $this->__sync($Model, 1, '-', 'BETWEEN ' . ($node[$left] + 1) . ' AND ' . ($node[$right] - 1)); + $this->__sync($Model, 2, '-', '> ' . ($node[$right])); + $Model->id = $id; + + if ($delete) { + $Model->updateAll( + array( + $Model->escapeField($left) => 0, + $Model->escapeField($right) => 0, + $Model->escapeField($parent) => null + ), + array($Model->escapeField() => $id) + ); + return $Model->delete($id); + } else { + $edge = $this->__getMax($Model, $scope, $right, $recursive); + if ($node[$right] == $edge) { + $edge = $edge - 2; + } + $Model->id = $id; + return $Model->save( + array($left => $edge + 1, $right => $edge + 2, $parent => null), + array('callbacks' => false) + ); + } + } + +/** + * Check if the current tree is valid. + * + * Returns true if the tree is valid otherwise an array of (type, incorrect left/right index, message) + * + * @param AppModel $Model Model instance + * @return mixed true if the tree is valid or empty, otherwise an array of (error type [index, node], + * [incorrect left/right index,node id], message) + * @access public + * @link http://book.cakephp.org/view/1630/Verify + */ + function verify(&$Model) { + extract($this->settings[$Model->alias]); + if (!$Model->find('count', array('conditions' => $scope))) { + return true; + } + $min = $this->__getMin($Model, $scope, $left, $recursive); + $edge = $this->__getMax($Model, $scope, $right, $recursive); + $errors = array(); + + for ($i = $min; $i <= $edge; $i++) { + $count = $Model->find('count', array('conditions' => array( + $scope, 'OR' => array($Model->escapeField($left) => $i, $Model->escapeField($right) => $i) + ))); + if ($count != 1) { + if ($count == 0) { + $errors[] = array('index', $i, 'missing'); + } else { + $errors[] = array('index', $i, 'duplicate'); + } + } + } + $node = $Model->find('first', array('conditions' => array($scope, $Model->escapeField($right) . '< ' . $Model->escapeField($left)), 'recursive' => 0)); + if ($node) { + $errors[] = array('node', $node[$Model->alias][$Model->primaryKey], 'left greater than right.'); + } + + $Model->bindModel(array('belongsTo' => array('VerifyParent' => array( + 'className' => $Model->alias, + 'foreignKey' => $parent, + 'fields' => array($Model->primaryKey, $left, $right, $parent) + )))); + + foreach ($Model->find('all', array('conditions' => $scope, 'recursive' => 0)) as $instance) { + if (is_null($instance[$Model->alias][$left]) || is_null($instance[$Model->alias][$right])) { + $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey], + 'has invalid left or right values'); + } elseif ($instance[$Model->alias][$left] == $instance[$Model->alias][$right]) { + $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey], + 'left and right values identical'); + } elseif ($instance[$Model->alias][$parent]) { + if (!$instance['VerifyParent'][$Model->primaryKey]) { + $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey], + 'The parent node ' . $instance[$Model->alias][$parent] . ' doesn\'t exist'); + } elseif ($instance[$Model->alias][$left] < $instance['VerifyParent'][$left]) { + $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey], + 'left less than parent (node ' . $instance['VerifyParent'][$Model->primaryKey] . ').'); + } elseif ($instance[$Model->alias][$right] > $instance['VerifyParent'][$right]) { + $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey], + 'right greater than parent (node ' . $instance['VerifyParent'][$Model->primaryKey] . ').'); + } + } elseif ($Model->find('count', array('conditions' => array($scope, $Model->escapeField($left) . ' <' => $instance[$Model->alias][$left], $Model->escapeField($right) . ' >' => $instance[$Model->alias][$right]), 'recursive' => 0))) { + $errors[] = array('node', $instance[$Model->alias][$Model->primaryKey], 'The parent field is blank, but has a parent'); + } + } + if ($errors) { + return $errors; + } + return true; + } + +/** + * Sets the parent of the given node + * + * The force parameter is used to override the "don't change the parent to the current parent" logic in the event + * of recovering a corrupted table, or creating new nodes. Otherwise it should always be false. In reality this + * method could be private, since calling save with parent_id set also calls setParent + * + * @param AppModel $Model Model instance + * @param mixed $parentId + * @return boolean true on success, false on failure + * @access protected + */ + function _setParent(&$Model, $parentId = null, $created = false) { + extract($this->settings[$Model->alias]); + list($node) = array_values($Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $Model->id), + 'fields' => array($Model->primaryKey, $parent, $left, $right), + 'recursive' => $recursive + ))); + $edge = $this->__getMax($Model, $scope, $right, $recursive, $created); + + if (empty ($parentId)) { + $this->__sync($Model, $edge - $node[$left] + 1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created); + $this->__sync($Model, $node[$right] - $node[$left] + 1, '-', '> ' . $node[$left], $created); + } else { + $values = $Model->find('first', array( + 'conditions' => array($scope, $Model->escapeField() => $parentId), + 'fields' => array($Model->primaryKey, $left, $right), + 'recursive' => $recursive + )); + + if ($values === false) { + return false; + } + $parentNode = array_values($values); + + if (empty($parentNode) || empty($parentNode[0])) { + return false; + } + $parentNode = $parentNode[0]; + + if (($Model->id == $parentId)) { + return false; + + } elseif (($node[$left] < $parentNode[$left]) && ($parentNode[$right] < $node[$right])) { + return false; + } + if (empty ($node[$left]) && empty ($node[$right])) { + $this->__sync($Model, 2, '+', '>= ' . $parentNode[$right], $created); + $result = $Model->save( + array($left => $parentNode[$right], $right => $parentNode[$right] + 1, $parent => $parentId), + array('validate' => false, 'callbacks' => false) + ); + $Model->data = $result; + } else { + $this->__sync($Model, $edge - $node[$left] +1, '+', 'BETWEEN ' . $node[$left] . ' AND ' . $node[$right], $created); + $diff = $node[$right] - $node[$left] + 1; + + if ($node[$left] > $parentNode[$left]) { + if ($node[$right] < $parentNode[$right]) { + $this->__sync($Model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1), $created); + $this->__sync($Model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created); + } else { + $this->__sync($Model, $diff, '+', 'BETWEEN ' . $parentNode[$right] . ' AND ' . $node[$right], $created); + $this->__sync($Model, $edge - $parentNode[$right] + 1, '-', '> ' . $edge, $created); + } + } else { + $this->__sync($Model, $diff, '-', 'BETWEEN ' . $node[$right] . ' AND ' . ($parentNode[$right] - 1), $created); + $this->__sync($Model, $edge - $parentNode[$right] + $diff + 1, '-', '> ' . $edge, $created); + } + } + } + return true; + } + +/** + * get the maximum index value in the table. + * + * @param AppModel $Model + * @param string $scope + * @param string $right + * @return int + * @access private + */ + function __getMax($Model, $scope, $right, $recursive = -1, $created = false) { + $db =& ConnectionManager::getDataSource($Model->useDbConfig); + if ($created) { + if (is_string($scope)) { + $scope .= " AND {$Model->alias}.{$Model->primaryKey} <> "; + $scope .= $db->value($Model->id, $Model->getColumnType($Model->primaryKey)); + } else { + $scope['NOT'][$Model->alias . '.' . $Model->primaryKey] = $Model->id; + } + } + $name = $Model->alias . '.' . $right; + list($edge) = array_values($Model->find('first', array( + 'conditions' => $scope, + 'fields' => $db->calculate($Model, 'max', array($name, $right)), + 'recursive' => $recursive + ))); + return (empty($edge[$right])) ? 0 : $edge[$right]; + } + +/** + * get the minimum index value in the table. + * + * @param AppModel $Model + * @param string $scope + * @param string $right + * @return int + * @access private + */ + function __getMin($Model, $scope, $left, $recursive = -1) { + $db =& ConnectionManager::getDataSource($Model->useDbConfig); + $name = $Model->alias . '.' . $left; + list($edge) = array_values($Model->find('first', array( + 'conditions' => $scope, + 'fields' => $db->calculate($Model, 'min', array($name, $left)), + 'recursive' => $recursive + ))); + return (empty($edge[$left])) ? 0 : $edge[$left]; + } + +/** + * Table sync method. + * + * Handles table sync operations, Taking account of the behavior scope. + * + * @param AppModel $Model + * @param integer $shift + * @param string $direction + * @param array $conditions + * @param string $field + * @access private + */ + function __sync(&$Model, $shift, $dir = '+', $conditions = array(), $created = false, $field = 'both') { + $ModelRecursive = $Model->recursive; + extract($this->settings[$Model->alias]); + $Model->recursive = $recursive; + + if ($field == 'both') { + $this->__sync($Model, $shift, $dir, $conditions, $created, $left); + $field = $right; + } + if (is_string($conditions)) { + $conditions = array("{$Model->alias}.{$field} {$conditions}"); + } + if (($scope != '1 = 1' && $scope !== true) && $scope) { + $conditions[] = $scope; + } + if ($created) { + $conditions['NOT'][$Model->alias . '.' . $Model->primaryKey] = $Model->id; + } + $Model->updateAll(array($Model->alias . '.' . $field => $Model->escapeField($field) . ' ' . $dir . ' ' . $shift), $conditions); + $Model->recursive = $ModelRecursive; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/cake_schema.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/cake_schema.php new file mode 100644 index 000000000..4acf9a18d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/cake_schema.php @@ -0,0 +1,698 @@ +name = preg_replace('/schema$/i', '', get_class($this)); + } + if (!empty($options['plugin'])) { + $this->plugin = $options['plugin']; + } + + if (strtolower($this->name) === 'cake') { + $this->name = Inflector::camelize(Inflector::slug(Configure::read('App.dir'))); + } + + if (empty($options['path'])) { + if (is_dir(CONFIGS . 'schema')) { + $this->path = CONFIGS . 'schema'; + } else { + $this->path = CONFIGS . 'sql'; + } + } + + $options = array_merge(get_object_vars($this), $options); + $this->_build($options); + } + +/** + * Builds schema object properties + * + * @param array $data loaded object properties + * @return void + * @access protected + */ + function _build($data) { + $file = null; + foreach ($data as $key => $val) { + if (!empty($val)) { + if (!in_array($key, array('plugin', 'name', 'path', 'file', 'connection', 'tables', '_log'))) { + $this->tables[$key] = $val; + unset($this->{$key}); + } elseif ($key !== 'tables') { + if ($key === 'name' && $val !== $this->name && !isset($data['file'])) { + $file = Inflector::underscore($val) . '.php'; + } + $this->{$key} = $val; + } + } + } + if (file_exists($this->path . DS . $file) && is_file($this->path . DS . $file)) { + $this->file = $file; + } elseif (!empty($this->plugin)) { + $this->path = App::pluginPath($this->plugin) . 'config' . DS . 'schema'; + } + } + +/** + * Before callback to be implemented in subclasses + * + * @param array $events schema object properties + * @return boolean Should process continue + * @access public + */ + function before($event = array()) { + return true; + } + +/** + * After callback to be implemented in subclasses + * + * @param array $events schema object properties + * @access public + */ + function after($event = array()) { + } + +/** + * Reads database and creates schema tables + * + * @param array $options schema object properties + * @return array Set of name and tables + * @access public + */ + function &load($options = array()) { + if (is_string($options)) { + $options = array('path' => $options); + } + + $this->_build($options); + extract(get_object_vars($this)); + + $class = $name .'Schema'; + + if (!class_exists($class)) { + if (file_exists($path . DS . $file) && is_file($path . DS . $file)) { + require_once($path . DS . $file); + } elseif (file_exists($path . DS . 'schema.php') && is_file($path . DS . 'schema.php')) { + require_once($path . DS . 'schema.php'); + } + } + + if (class_exists($class)) { + $Schema =& new $class($options); + return $Schema; + } + $false = false; + return $false; + } + +/** + * Reads database and creates schema tables + * + * Options + * + * - 'connection' - the db connection to use + * - 'name' - name of the schema + * - 'models' - a list of models to use, or false to ignore models + * + * @param array $options schema object properties + * @return array Array indexed by name and tables + * @access public + */ + function read($options = array()) { + extract(array_merge( + array( + 'connection' => $this->connection, + 'name' => $this->name, + 'models' => true, + ), + $options + )); + $db =& ConnectionManager::getDataSource($connection); + + App::import('Model', 'AppModel'); + if (isset($this->plugin)) { + App::import('Model', Inflector::camelize($this->plugin) . 'AppModel'); + } + + $tables = array(); + $currentTables = $db->listSources(); + + $prefix = null; + if (isset($db->config['prefix'])) { + $prefix = $db->config['prefix']; + } + + if (!is_array($models) && $models !== false) { + if (isset($this->plugin)) { + $models = App::objects('model', App::pluginPath($this->plugin) . 'models' . DS, false); + } else { + $models = App::objects('model'); + } + } + + if (is_array($models)) { + foreach ($models as $model) { + $importModel = $model; + if (isset($this->plugin)) { + $importModel = $this->plugin . '.' . $model; + } + if (!App::import('Model', $importModel)) { + continue; + } + $vars = get_class_vars($model); + if (empty($vars['useDbConfig']) || $vars['useDbConfig'] != $connection) { + continue; + } + + if (PHP5) { + $Object = ClassRegistry::init(array('class' => $model, 'ds' => $connection)); + } else { + $Object =& ClassRegistry::init(array('class' => $model, 'ds' => $connection)); + } + + if (is_object($Object) && $Object->useTable !== false) { + $fulltable = $table = $db->fullTableName($Object, false); + if ($prefix && strpos($table, $prefix) !== 0) { + continue; + } + $table = str_replace($prefix, '', $table); + + if (in_array($fulltable, $currentTables)) { + $key = array_search($fulltable, $currentTables); + if (empty($tables[$table])) { + $tables[$table] = $this->__columns($Object); + $tables[$table]['indexes'] = $db->index($Object); + $tables[$table]['tableParameters'] = $db->readTableParameters($fulltable); + unset($currentTables[$key]); + } + if (!empty($Object->hasAndBelongsToMany)) { + foreach ($Object->hasAndBelongsToMany as $Assoc => $assocData) { + if (isset($assocData['with'])) { + $class = $assocData['with']; + } + if (is_object($Object->$class)) { + $withTable = $db->fullTableName($Object->$class, false); + if ($prefix && strpos($withTable, $prefix) !== 0) { + continue; + } + if (in_array($withTable, $currentTables)) { + $key = array_search($withTable, $currentTables); + $noPrefixWith = str_replace($prefix, '', $withTable); + + $tables[$noPrefixWith] = $this->__columns($Object->$class); + $tables[$noPrefixWith]['indexes'] = $db->index($Object->$class); + $tables[$noPrefixWith]['tableParameters'] = $db->readTableParameters($withTable); + unset($currentTables[$key]); + } + } + } + } + } + } + } + } + + if (!empty($currentTables)) { + foreach ($currentTables as $table) { + if ($prefix) { + if (strpos($table, $prefix) !== 0) { + continue; + } + $table = str_replace($prefix, '', $table); + } + $Object = new AppModel(array( + 'name' => Inflector::classify($table), 'table' => $table, 'ds' => $connection + )); + + $systemTables = array( + 'aros', 'acos', 'aros_acos', Configure::read('Session.table'), 'i18n' + ); + + if (in_array($table, $systemTables)) { + $tables[$Object->table] = $this->__columns($Object); + $tables[$Object->table]['indexes'] = $db->index($Object); + $tables[$Object->table]['tableParameters'] = $db->readTableParameters($table); + } elseif ($models === false) { + $tables[$table] = $this->__columns($Object); + $tables[$table]['indexes'] = $db->index($Object); + $tables[$table]['tableParameters'] = $db->readTableParameters($table); + } else { + $tables['missing'][$table] = $this->__columns($Object); + $tables['missing'][$table]['indexes'] = $db->index($Object); + $tables['missing'][$table]['tableParameters'] = $db->readTableParameters($table); + } + } + } + + ksort($tables); + return compact('name', 'tables'); + } + +/** + * Writes schema file from object or options + * + * @param mixed $object schema object or options array + * @param array $options schema object properties to override object + * @return mixed false or string written to file + * @access public + */ + function write($object, $options = array()) { + if (is_object($object)) { + $object = get_object_vars($object); + $this->_build($object); + } + + if (is_array($object)) { + $options = $object; + unset($object); + } + + extract(array_merge( + get_object_vars($this), $options + )); + + $out = "class {$name}Schema extends CakeSchema {\n"; + + $out .= "\tvar \$name = '{$name}';\n\n"; + + if ($path !== $this->path) { + $out .= "\tvar \$path = '{$path}';\n\n"; + } + + if ($file !== $this->file) { + $out .= "\tvar \$file = '{$file}';\n\n"; + } + + if ($connection !== 'default') { + $out .= "\tvar \$connection = '{$connection}';\n\n"; + } + + $out .= "\tfunction before(\$event = array()) {\n\t\treturn true;\n\t}\n\n\tfunction after(\$event = array()) {\n\t}\n\n"; + + if (empty($tables)) { + $this->read(); + } + + foreach ($tables as $table => $fields) { + if (!is_numeric($table) && $table !== 'missing') { + $out .= $this->generateTable($table, $fields); + } + } + $out .= "}\n"; + + $File =& new File($path . DS . $file, true); + $header = '$Id'; + $content = ""; + $content = $File->prepare($content); + if ($File->write($content)) { + return $content; + } + return false; + } + +/** + * Generate the code for a table. Takes a table name and $fields array + * Returns a completed variable declaration to be used in schema classes + * + * @param string $table Table name you want returned. + * @param array $fields Array of field information to generate the table with. + * @return string Variable declaration for a schema class + */ + function generateTable($table, $fields) { + $out = "\tvar \${$table} = array(\n"; + if (is_array($fields)) { + $cols = array(); + foreach ($fields as $field => $value) { + if ($field != 'indexes' && $field != 'tableParameters') { + if (is_string($value)) { + $type = $value; + $value = array('type'=> $type); + } + $col = "\t\t'{$field}' => array('type' => '" . $value['type'] . "', "; + unset($value['type']); + $col .= join(', ', $this->__values($value)); + } elseif ($field == 'indexes') { + $col = "\t\t'indexes' => array("; + $props = array(); + foreach ((array)$value as $key => $index) { + $props[] = "'{$key}' => array(" . join(', ', $this->__values($index)) . ")"; + } + $col .= join(', ', $props); + } elseif ($field == 'tableParameters') { + //@todo add charset, collate and engine here + $col = "\t\t'tableParameters' => array("; + $props = array(); + foreach ((array)$value as $key => $param) { + $props[] = "'{$key}' => '$param'"; + } + $col .= join(', ', $props); + } + $col .= ")"; + $cols[] = $col; + } + $out .= join(",\n", $cols); + } + $out .= "\n\t);\n"; + return $out; + } + +/** + * Compares two sets of schemas + * + * @param mixed $old Schema object or array + * @param mixed $new Schema object or array + * @return array Tables (that are added, dropped, or changed) + * @access public + */ + function compare($old, $new = null) { + if (empty($new)) { + $new =& $this; + } + if (is_array($new)) { + if (isset($new['tables'])) { + $new = $new['tables']; + } + } else { + $new = $new->tables; + } + + if (is_array($old)) { + if (isset($old['tables'])) { + $old = $old['tables']; + } + } else { + $old = $old->tables; + } + $tables = array(); + foreach ($new as $table => $fields) { + if ($table == 'missing') { + continue; + } + if (!array_key_exists($table, $old)) { + $tables[$table]['add'] = $fields; + } else { + $diff = $this->_arrayDiffAssoc($fields, $old[$table]); + if (!empty($diff)) { + $tables[$table]['add'] = $diff; + } + $diff = $this->_arrayDiffAssoc($old[$table], $fields); + if (!empty($diff)) { + $tables[$table]['drop'] = $diff; + } + } + + foreach ($fields as $field => $value) { + if (isset($old[$table][$field])) { + $diff = $this->_arrayDiffAssoc($value, $old[$table][$field]); + if (!empty($diff) && $field !== 'indexes' && $field !== 'tableParameters') { + $tables[$table]['change'][$field] = array_merge($old[$table][$field], $diff); + } + } + + if (isset($add[$table][$field])) { + $wrapper = array_keys($fields); + if ($column = array_search($field, $wrapper)) { + if (isset($wrapper[$column - 1])) { + $tables[$table]['add'][$field]['after'] = $wrapper[$column - 1]; + } + } + } + } + + if (isset($old[$table]['indexes']) && isset($new[$table]['indexes'])) { + $diff = $this->_compareIndexes($new[$table]['indexes'], $old[$table]['indexes']); + if ($diff) { + if (!isset($tables[$table])) { + $tables[$table] = array(); + } + if (isset($diff['drop'])) { + $tables[$table]['drop']['indexes'] = $diff['drop']; + } + if ($diff && isset($diff['add'])) { + $tables[$table]['add']['indexes'] = $diff['add']; + } + } + } + if (isset($old[$table]['tableParameters']) && isset($new[$table]['tableParameters'])) { + $diff = $this->_compareTableParameters($new[$table]['tableParameters'], $old[$table]['tableParameters']); + if ($diff) { + $tables[$table]['change']['tableParameters'] = $diff; + } + } + } + return $tables; + } + +/** + * Extended array_diff_assoc noticing change from/to NULL values + * + * It behaves almost the same way as array_diff_assoc except for NULL values: if + * one of the values is not NULL - change is detected. It is useful in situation + * where one value is strval('') ant other is strval(null) - in string comparing + * methods this results as EQUAL, while it is not. + * + * @param array $array1 Base array + * @param array $array2 Corresponding array checked for equality + * @return array Difference as array with array(keys => values) from input array + * where match was not found. + * @access protected + */ + function _arrayDiffAssoc($array1, $array2) { + $difference = array(); + foreach ($array1 as $key => $value) { + if (!array_key_exists($key, $array2)) { + $difference[$key] = $value; + continue; + } + $correspondingValue = $array2[$key]; + if (is_null($value) !== is_null($correspondingValue)) { + $difference[$key] = $value; + continue; + } + if (is_bool($value) !== is_bool($correspondingValue)) { + $difference[$key] = $value; + continue; + } + $compare = strval($value); + $correspondingValue = strval($correspondingValue); + if ($compare === $correspondingValue) { + continue; + } + $difference[$key] = $value; + } + return $difference; + } + +/** + * Formats Schema columns from Model Object + * + * @param array $values options keys(type, null, default, key, length, extra) + * @return array Formatted values + * @access public + */ + function __values($values) { + $vals = array(); + if (is_array($values)) { + foreach ($values as $key => $val) { + if (is_array($val)) { + $vals[] = "'{$key}' => array('" . implode("', '", $val) . "')"; + } else if (!is_numeric($key)) { + $val = var_export($val, true); + $vals[] = "'{$key}' => {$val}"; + } + } + } + return $vals; + } + +/** + * Formats Schema columns from Model Object + * + * @param array $Obj model object + * @return array Formatted columns + * @access public + */ + function __columns(&$Obj) { + $db =& ConnectionManager::getDataSource($Obj->useDbConfig); + $fields = $Obj->schema(true); + $columns = $props = array(); + foreach ($fields as $name => $value) { + if ($Obj->primaryKey == $name) { + $value['key'] = 'primary'; + } + if (!isset($db->columns[$value['type']])) { + trigger_error(sprintf(__('Schema generation error: invalid column type %s does not exist in DBO', true), $value['type']), E_USER_NOTICE); + continue; + } else { + $defaultCol = $db->columns[$value['type']]; + if (isset($defaultCol['limit']) && $defaultCol['limit'] == $value['length']) { + unset($value['length']); + } elseif (isset($defaultCol['length']) && $defaultCol['length'] == $value['length']) { + unset($value['length']); + } + unset($value['limit']); + } + + if (isset($value['default']) && ($value['default'] === '' || $value['default'] === false)) { + unset($value['default']); + } + if (empty($value['length'])) { + unset($value['length']); + } + if (empty($value['key'])) { + unset($value['key']); + } + $columns[$name] = $value; + } + + return $columns; + } + +/** + * Compare two schema files table Parameters + * + * @param array $new New indexes + * @param array $old Old indexes + * @return mixed False on failure, or an array of parameters to add & drop. + */ + function _compareTableParameters($new, $old) { + if (!is_array($new) || !is_array($old)) { + return false; + } + $change = $this->_arrayDiffAssoc($new, $old); + return $change; + } + +/** + * Compare two schema indexes + * + * @param array $new New indexes + * @param array $old Old indexes + * @return mixed false on failure or array of indexes to add and drop + */ + function _compareIndexes($new, $old) { + if (!is_array($new) || !is_array($old)) { + return false; + } + + $add = $drop = array(); + + $diff = $this->_arrayDiffAssoc($new, $old); + if (!empty($diff)) { + $add = $diff; + } + + $diff = $this->_arrayDiffAssoc($old, $new); + if (!empty($diff)) { + $drop = $diff; + } + + foreach ($new as $name => $value) { + if (isset($old[$name])) { + $newUnique = isset($value['unique']) ? $value['unique'] : 0; + $oldUnique = isset($old[$name]['unique']) ? $old[$name]['unique'] : 0; + $newColumn = $value['column']; + $oldColumn = $old[$name]['column']; + + $diff = false; + + if ($newUnique != $oldUnique) { + $diff = true; + } elseif (is_array($newColumn) && is_array($oldColumn)) { + $diff = ($newColumn !== $oldColumn); + } elseif (is_string($newColumn) && is_string($oldColumn)) { + $diff = ($newColumn != $oldColumn); + } else { + $diff = true; + } + if ($diff) { + $drop[$name] = null; + $add[$name] = $value; + } + } + } + return array_filter(compact('add', 'drop')); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/connection_manager.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/connection_manager.php new file mode 100644 index 000000000..b9e599481 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/connection_manager.php @@ -0,0 +1,295 @@ +config =& new DATABASE_CONFIG(); + $this->_getConnectionObjects(); + } + } + +/** + * Gets a reference to the ConnectionManger object instance + * + * @return object Instance + * @access public + * @static + */ + function &getInstance() { + static $instance = array(); + + if (!$instance) { + $instance[0] =& new ConnectionManager(); + } + + return $instance[0]; + } + +/** + * Gets a reference to a DataSource object + * + * @param string $name The name of the DataSource, as defined in app/config/database.php + * @return object Instance + * @access public + * @static + */ + function &getDataSource($name) { + $_this =& ConnectionManager::getInstance(); + + if (!empty($_this->_dataSources[$name])) { + $return =& $_this->_dataSources[$name]; + return $return; + } + + if (empty($_this->_connectionsEnum[$name])) { + trigger_error(sprintf(__("ConnectionManager::getDataSource - Non-existent data source %s", true), $name), E_USER_ERROR); + $null = null; + return $null; + } + $conn = $_this->_connectionsEnum[$name]; + $class = $conn['classname']; + + if ($_this->loadDataSource($name) === null) { + trigger_error(sprintf(__("ConnectionManager::getDataSource - Could not load class %s", true), $class), E_USER_ERROR); + $null = null; + return $null; + } + $_this->_dataSources[$name] =& new $class($_this->config->{$name}); + $_this->_dataSources[$name]->configKeyName = $name; + + $return =& $_this->_dataSources[$name]; + return $return; + } + +/** + * Gets the list of available DataSource connections + * + * @return array List of available connections + * @access public + * @static + */ + function sourceList() { + $_this =& ConnectionManager::getInstance(); + return array_keys($_this->_dataSources); + } + +/** + * Gets a DataSource name from an object reference. + * + * **Warning** this method may cause fatal errors in PHP4. + * + * @param object $source DataSource object + * @return string Datasource name, or null if source is not present + * in the ConnectionManager. + * @access public + * @static + */ + function getSourceName(&$source) { + $_this =& ConnectionManager::getInstance(); + foreach ($_this->_dataSources as $name => $ds) { + if ($ds == $source) { + return $name; + } + } + return ''; + } + +/** + * Loads the DataSource class for the given connection name + * + * @param mixed $connName A string name of the connection, as defined in app/config/database.php, + * or an array containing the filename (without extension) and class name of the object, + * to be found in app/models/datasources/ or cake/libs/model/datasources/. + * @return boolean True on success, null on failure or false if the class is already loaded + * @access public + * @static + */ + function loadDataSource($connName) { + $_this =& ConnectionManager::getInstance(); + + if (is_array($connName)) { + $conn = $connName; + } else { + $conn = $_this->_connectionsEnum[$connName]; + } + + if (class_exists($conn['classname'])) { + return false; + } + + if (!empty($conn['parent'])) { + $_this->loadDataSource($conn['parent']); + } + + $conn = array_merge(array('plugin' => null, 'classname' => null, 'parent' => null), $conn); + $class = "{$conn['plugin']}.{$conn['classname']}"; + + if (!App::import('Datasource', $class, !is_null($conn['plugin']))) { + trigger_error(sprintf(__('ConnectionManager::loadDataSource - Unable to import DataSource class %s', true), $class), E_USER_ERROR); + return null; + } + return true; + } + +/** + * Return a list of connections + * + * @return array An associative array of elements where the key is the connection name + * (as defined in Connections), and the value is an array with keys 'filename' and 'classname'. + * @access public + * @static + */ + function enumConnectionObjects() { + $_this =& ConnectionManager::getInstance(); + + return $_this->_connectionsEnum; + } + +/** + * Dynamically creates a DataSource object at runtime, with the given name and settings + * + * @param string $name The DataSource name + * @param array $config The DataSource configuration settings + * @return object A reference to the DataSource object, or null if creation failed + * @access public + * @static + */ + function &create($name = '', $config = array()) { + $_this =& ConnectionManager::getInstance(); + + if (empty($name) || empty($config) || array_key_exists($name, $_this->_connectionsEnum)) { + $null = null; + return $null; + } + $_this->config->{$name} = $config; + $_this->_connectionsEnum[$name] = $_this->__connectionData($config); + $return =& $_this->getDataSource($name); + return $return; + } + +/** + * Gets a list of class and file names associated with the user-defined DataSource connections + * + * @return void + * @access protected + * @static + */ + function _getConnectionObjects() { + $connections = get_object_vars($this->config); + + if ($connections != null) { + foreach ($connections as $name => $config) { + $this->_connectionsEnum[$name] = $this->__connectionData($config); + } + } else { + $this->cakeError('missingConnection', array(array('code' => 500, 'className' => 'ConnectionManager'))); + } + } + +/** + * Returns the file, class name, and parent for the given driver. + * + * @return array An indexed array with: filename, classname, plugin and parent + * @access private + */ + function __connectionData($config) { + if (!isset($config['datasource'])) { + $config['datasource'] = 'dbo'; + } + $filename = $classname = $parent = $plugin = null; + + if (!empty($config['driver'])) { + $parent = $this->__connectionData(array('datasource' => $config['datasource'])); + $parentSource = preg_replace('/_source$/', '', $parent['filename']); + + list($plugin, $classname) = pluginSplit($config['driver']); + if ($plugin) { + $source = Inflector::underscore($classname); + } else { + $source = $parentSource . '_' . $config['driver']; + $classname = Inflector::camelize(strtolower($source)); + } + $filename = $parentSource . DS . $source; + } else { + list($plugin, $classname) = pluginSplit($config['datasource']); + if ($plugin) { + $filename = Inflector::underscore($classname); + } else { + $filename = Inflector::underscore($config['datasource']); + } + if (substr($filename, -7) != '_source') { + $filename .= '_source'; + } + $classname = Inflector::camelize(strtolower($filename)); + } + return compact('filename', 'classname', 'parent', 'plugin'); + } + +/** + * Destructor. + * + * @access private + */ + function __destruct() { + if (Configure::read('Session.save') == 'database' && function_exists('session_write_close')) { + session_write_close(); + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/datasource.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/datasource.php new file mode 100644 index 000000000..5de46be8a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/datasource.php @@ -0,0 +1,601 @@ +setConfig($config); + } + +/** + * Caches/returns cached results for child instances + * + * @param mixed $data + * @return array Array of sources available in this datasource. + * @access public + */ + function listSources($data = null) { + if ($this->cacheSources === false) { + return null; + } + + if ($this->_sources !== null) { + return $this->_sources; + } + + $key = ConnectionManager::getSourceName($this) . '_' . $this->config['database'] . '_list'; + $key = preg_replace('/[^A-Za-z0-9_\-.+]/', '_', $key); + $sources = Cache::read($key, '_cake_model_'); + + if (empty($sources)) { + $sources = $data; + Cache::write($key, $data, '_cake_model_'); + } + + $this->_sources = $sources; + return $sources; + } + +/** + * Convenience method for DboSource::listSources(). Returns source names in lowercase. + * + * @param boolean $reset Whether or not the source list should be reset. + * @return array Array of sources available in this datasource + * @access public + */ + function sources($reset = false) { + if ($reset === true) { + $this->_sources = null; + } + return array_map('strtolower', $this->listSources()); + } + +/** + * Returns a Model description (metadata) or null if none found. + * + * @param Model $model + * @return array Array of Metadata for the $model + * @access public + */ + function describe(&$model) { + if ($this->cacheSources === false) { + return null; + } + $table = $model->tablePrefix . $model->table; + + if (isset($this->__descriptions[$table])) { + return $this->__descriptions[$table]; + } + $cache = $this->__cacheDescription($table); + + if ($cache !== null) { + $this->__descriptions[$table] =& $cache; + return $cache; + } + return null; + } + +/** + * Begin a transaction + * + * @return boolean Returns true if a transaction is not in progress + * @access public + */ + function begin(&$model) { + return !$this->_transactionStarted; + } + +/** + * Commit a transaction + * + * @return boolean Returns true if a transaction is in progress + * @access public + */ + function commit(&$model) { + return $this->_transactionStarted; + } + +/** + * Rollback a transaction + * + * @return boolean Returns true if a transaction is in progress + * @access public + */ + function rollback(&$model) { + return $this->_transactionStarted; + } + +/** + * Converts column types to basic types + * + * @param string $real Real column type (i.e. "varchar(255)") + * @return string Abstract column type (i.e. "string") + * @access public + */ + function column($real) { + return false; + } + +/** + * Used to create new records. The "C" CRUD. + * + * To-be-overridden in subclasses. + * + * @param Model $model The Model to be created. + * @param array $fields An Array of fields to be saved. + * @param array $values An Array of values to save. + * @return boolean success + * @access public + */ + function create(&$model, $fields = null, $values = null) { + return false; + } + +/** + * Used to read records from the Datasource. The "R" in CRUD + * + * To-be-overridden in subclasses. + * + * @param Model $model The model being read. + * @param array $queryData An array of query data used to find the data you want + * @return mixed + * @access public + */ + function read(&$model, $queryData = array()) { + return false; + } + +/** + * Update a record(s) in the datasource. + * + * To-be-overridden in subclasses. + * + * @param Model $model Instance of the model class being updated + * @param array $fields Array of fields to be updated + * @param array $values Array of values to be update $fields to. + * @return boolean Success + * @access public + */ + function update(&$model, $fields = null, $values = null) { + return false; + } + +/** + * Delete a record(s) in the datasource. + * + * To-be-overridden in subclasses. + * + * @param Model $model The model class having record(s) deleted + * @param mixed $id Primary key of the model + * @access public + */ + function delete(&$model, $id = null) { + if ($id == null) { + $id = $model->id; + } + } + +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param unknown_type $source + * @return mixed Last ID key generated in previous INSERT + * @access public + */ + function lastInsertId($source = null) { + return false; + } + +/** + * Returns the number of rows returned by last operation. + * + * @param unknown_type $source + * @return integer Number of rows returned by last operation + * @access public + */ + function lastNumRows($source = null) { + return false; + } + +/** + * Returns the number of rows affected by last query. + * + * @param unknown_type $source + * @return integer Number of rows affected by last query. + * @access public + */ + function lastAffected($source = null) { + return false; + } + +/** + * Check whether the conditions for the Datasource being available + * are satisfied. Often used from connect() to check for support + * before establishing a connection. + * + * @return boolean Whether or not the Datasources conditions for use are met. + * @access public + */ + function enabled() { + return true; + } + +/** + * Returns true if the DataSource supports the given interface (method) + * + * @param string $interface The name of the interface (method) + * @return boolean True on success + * @access public + */ + function isInterfaceSupported($interface) { + static $methods = false; + if ($methods === false) { + $methods = array_map('strtolower', get_class_methods($this)); + } + return in_array(strtolower($interface), $methods); + } + +/** + * Sets the configuration for the DataSource. + * Merges the $config information with the _baseConfig and the existing $config property. + * + * @param array $config The configuration array + * @return void + * @access public + */ + function setConfig($config = array()) { + $this->config = array_merge($this->_baseConfig, $this->config, $config); + } + +/** + * Cache the DataSource description + * + * @param string $object The name of the object (model) to cache + * @param mixed $data The description of the model, usually a string or array + * @return mixed + * @access private + */ + function __cacheDescription($object, $data = null) { + if ($this->cacheSources === false) { + return null; + } + + if ($data !== null) { + $this->__descriptions[$object] =& $data; + } + + $key = ConnectionManager::getSourceName($this) . '_' . $object; + $cache = Cache::read($key, '_cake_model_'); + + if (empty($cache)) { + $cache = $data; + Cache::write($key, $cache, '_cake_model_'); + } + + return $cache; + } + +/** + * Replaces `{$__cakeID__$}` and `{$__cakeForeignKey__$}` placeholders in query data. + * + * @param string $query Query string needing replacements done. + * @param array $data Array of data with values that will be inserted in placeholders. + * @param string $association Name of association model being replaced + * @param unknown_type $assocData + * @param Model $model Instance of the model to replace $__cakeID__$ + * @param Model $linkModel Instance of model to replace $__cakeForeignKey__$ + * @param array $stack + * @return string String of query data with placeholders replaced. + * @access public + * @todo Remove and refactor $assocData, ensure uses of the method have the param removed too. + */ + function insertQueryData($query, $data, $association, $assocData, &$model, &$linkModel, $stack) { + $keys = array('{$__cakeID__$}', '{$__cakeForeignKey__$}'); + + foreach ($keys as $key) { + $val = null; + $type = null; + + if (strpos($query, $key) !== false) { + switch ($key) { + case '{$__cakeID__$}': + if (isset($data[$model->alias]) || isset($data[$association])) { + if (isset($data[$model->alias][$model->primaryKey])) { + $val = $data[$model->alias][$model->primaryKey]; + } elseif (isset($data[$association][$model->primaryKey])) { + $val = $data[$association][$model->primaryKey]; + } + } else { + $found = false; + foreach (array_reverse($stack) as $assoc) { + if (isset($data[$assoc]) && isset($data[$assoc][$model->primaryKey])) { + $val = $data[$assoc][$model->primaryKey]; + $found = true; + break; + } + } + if (!$found) { + $val = ''; + } + } + $type = $model->getColumnType($model->primaryKey); + break; + case '{$__cakeForeignKey__$}': + foreach ($model->__associations as $id => $name) { + foreach ($model->$name as $assocName => $assoc) { + if ($assocName === $association) { + if (isset($assoc['foreignKey'])) { + $foreignKey = $assoc['foreignKey']; + $assocModel = $model->$assocName; + $type = $assocModel->getColumnType($assocModel->primaryKey); + + if (isset($data[$model->alias][$foreignKey])) { + $val = $data[$model->alias][$foreignKey]; + } elseif (isset($data[$association][$foreignKey])) { + $val = $data[$association][$foreignKey]; + } else { + $found = false; + foreach (array_reverse($stack) as $assoc) { + if (isset($data[$assoc]) && isset($data[$assoc][$foreignKey])) { + $val = $data[$assoc][$foreignKey]; + $found = true; + break; + } + } + if (!$found) { + $val = ''; + } + } + } + break 3; + } + } + } + break; + } + if (empty($val) && $val !== '0') { + return false; + } + $query = str_replace($key, $this->value($val, $type), $query); + } + } + return $query; + } + +/** + * To-be-overridden in subclasses. + * + * @param Model $model Model instance + * @param string $key Key name to make + * @return string Key name for model. + * @access public + */ + function resolveKey(&$model, $key) { + return $model->alias . $key; + } + +/** + * Closes the current datasource. + * + * @return void + * @access public + */ + function __destruct() { + if ($this->_transactionStarted) { + $null = null; + $this->rollback($null); + } + if ($this->connected) { + $this->close(); + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mssql.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mssql.php new file mode 100644 index 000000000..b7b17f7c8 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mssql.php @@ -0,0 +1,788 @@ + true, + 'host' => 'localhost', + 'login' => 'root', + 'password' => '', + 'database' => 'cake', + 'port' => '1433', + ); + +/** + * MS SQL column definition + * + * @var array + */ + var $columns = array( + 'primary_key' => array('name' => 'IDENTITY (1, 1) NOT NULL'), + 'string' => array('name' => 'varchar', 'limit' => '255'), + 'text' => array('name' => 'text'), + 'integer' => array('name' => 'int', 'formatter' => 'intval'), + 'float' => array('name' => 'numeric', 'formatter' => 'floatval'), + 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'time' => array('name' => 'datetime', 'format' => 'H:i:s', 'formatter' => 'date'), + 'date' => array('name' => 'datetime', 'format' => 'Y-m-d', 'formatter' => 'date'), + 'binary' => array('name' => 'image'), + 'boolean' => array('name' => 'bit') + ); + +/** + * Index of basic SQL commands + * + * @var array + * @access protected + */ + var $_commands = array( + 'begin' => 'BEGIN TRANSACTION', + 'commit' => 'COMMIT', + 'rollback' => 'ROLLBACK' + ); + +/** + * Define if the last query had error + * + * @var string + * @access private + */ + var $__lastQueryHadError = false; +/** + * MS SQL DBO driver constructor; sets SQL Server error reporting defaults + * + * @param array $config Configuration data from app/config/databases.php + * @return boolean True if connected successfully, false on error + */ + function __construct($config, $autoConnect = true) { + if ($autoConnect) { + if (!function_exists('mssql_min_message_severity')) { + trigger_error(__("PHP SQL Server interface is not installed, cannot continue. For troubleshooting information, see http://php.net/mssql/", true), E_USER_WARNING); + } + mssql_min_message_severity(15); + mssql_min_error_severity(2); + } + return parent::__construct($config, $autoConnect); + } + +/** + * Connects to the database using options in the given configuration array. + * + * @return boolean True if the database could be connected, else false + */ + function connect() { + $config = $this->config; + + $os = env('OS'); + if (!empty($os) && strpos($os, 'Windows') !== false) { + $sep = ','; + } else { + $sep = ':'; + } + $this->connected = false; + + if (is_numeric($config['port'])) { + $port = $sep . $config['port']; // Port number + } elseif ($config['port'] === null) { + $port = ''; // No port - SQL Server 2005 + } else { + $port = '\\' . $config['port']; // Named pipe + } + + if (!$config['persistent']) { + $this->connection = mssql_connect($config['host'] . $port, $config['login'], $config['password'], true); + } else { + $this->connection = mssql_pconnect($config['host'] . $port, $config['login'], $config['password']); + } + + if (mssql_select_db($config['database'], $this->connection)) { + $this->_execute("SET DATEFORMAT ymd"); + $this->connected = true; + } + return $this->connected; + } + +/** + * Check that MsSQL is installed/loaded + * + * @return boolean + */ + function enabled() { + return extension_loaded('mssql'); + } +/** + * Disconnects from database. + * + * @return boolean True if the database could be disconnected, else false + */ + function disconnect() { + @mssql_free_result($this->results); + $this->connected = !@mssql_close($this->connection); + return !$this->connected; + } + +/** + * Executes given SQL statement. + * + * @param string $sql SQL statement + * @return resource Result resource identifier + * @access protected + */ + function _execute($sql) { + $result = @mssql_query($sql, $this->connection); + $this->__lastQueryHadError = ($result === false); + return $result; + } + +/** + * Returns an array of sources (tables) in the database. + * + * @return array Array of tablenames in the database + */ + function listSources() { + $cache = parent::listSources(); + + if ($cache != null) { + return $cache; + } + $result = $this->fetchAll('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES', false); + + if (!$result || empty($result)) { + return array(); + } else { + $tables = array(); + + foreach ($result as $table) { + $tables[] = $table[0]['TABLE_NAME']; + } + + parent::listSources($tables); + return $tables; + } + } + +/** + * Returns an array of the fields in given table name. + * + * @param Model $model Model object to describe + * @return array Fields in table. Keys are name and type + */ + function describe(&$model) { + $cache = parent::describe($model); + + if ($cache != null) { + return $cache; + } + + $table = $this->fullTableName($model, false); + $cols = $this->fetchAll("SELECT COLUMN_NAME as Field, DATA_TYPE as Type, COL_LENGTH('" . $table . "', COLUMN_NAME) as Length, IS_NULLABLE As [Null], COLUMN_DEFAULT as [Default], COLUMNPROPERTY(OBJECT_ID('" . $table . "'), COLUMN_NAME, 'IsIdentity') as [Key], NUMERIC_SCALE as Size FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" . $table . "'", false); + + $fields = false; + foreach ($cols as $column) { + $field = $column[0]['Field']; + $fields[$field] = array( + 'type' => $this->column($column[0]['Type']), + 'null' => (strtoupper($column[0]['Null']) == 'YES'), + 'default' => preg_replace("/^[(]{1,2}'?([^')]*)?'?[)]{1,2}$/", "$1", $column[0]['Default']), + 'length' => intval($column[0]['Length']), + 'key' => ($column[0]['Key'] == '1') ? 'primary' : false + ); + if ($fields[$field]['default'] === 'null') { + $fields[$field]['default'] = null; + } else { + $this->value($fields[$field]['default'], $fields[$field]['type']); + } + + if ($fields[$field]['key'] && $fields[$field]['type'] == 'integer') { + $fields[$field]['length'] = 11; + } elseif (!$fields[$field]['key']) { + unset($fields[$field]['key']); + } + if (in_array($fields[$field]['type'], array('date', 'time', 'datetime', 'timestamp'))) { + $fields[$field]['length'] = null; + } + } + $this->__cacheDescription($this->fullTableName($model, false), $fields); + return $fields; + } + +/** + * Returns a quoted and escaped string of $data for use in an SQL statement. + * + * @param string $data String to be prepared for use in an SQL statement + * @param string $column The column into which this data will be inserted + * @param boolean $safe Whether or not numeric data should be handled automagically if no column data is provided + * @return string Quoted and escaped data + */ + function value($data, $column = null, $safe = false) { + $parent = parent::value($data, $column, $safe); + + if ($parent != null) { + return $parent; + } + if ($data === null) { + return 'NULL'; + } + if (in_array($column, array('integer', 'float', 'binary')) && $data === '') { + return 'NULL'; + } + if ($data === '') { + return "''"; + } + + switch ($column) { + case 'boolean': + $data = $this->boolean((bool)$data); + break; + default: + if (get_magic_quotes_gpc()) { + $data = stripslashes(str_replace("'", "''", $data)); + } else { + $data = str_replace("'", "''", $data); + } + break; + } + + if (in_array($column, array('integer', 'float', 'binary')) && is_numeric($data)) { + return $data; + } + return "'" . $data . "'"; + } + +/** + * Generates the fields list of an SQL query. + * + * @param Model $model + * @param string $alias Alias tablename + * @param mixed $fields + * @return array + */ + function fields(&$model, $alias = null, $fields = array(), $quote = true) { + if (empty($alias)) { + $alias = $model->alias; + } + $fields = parent::fields($model, $alias, $fields, false); + $count = count($fields); + + if ($count >= 1 && strpos($fields[0], 'COUNT(*)') === false) { + $result = array(); + for ($i = 0; $i < $count; $i++) { + $prepend = ''; + + if (strpos($fields[$i], 'DISTINCT') !== false) { + $prepend = 'DISTINCT '; + $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i])); + } + $fieldAlias = count($this->__fieldMappings); + + if (!preg_match('/\s+AS\s+/i', $fields[$i])) { + if (substr($fields[$i], -1) == '*') { + if (strpos($fields[$i], '.') !== false && $fields[$i] != $alias . '.*') { + $build = explode('.', $fields[$i]); + $AssociatedModel = $model->{$build[0]}; + } else { + $AssociatedModel = $model; + } + + $_fields = $this->fields($AssociatedModel, $AssociatedModel->alias, array_keys($AssociatedModel->schema())); + $result = array_merge($result, $_fields); + continue; + } + + if (strpos($fields[$i], '.') === false) { + $this->__fieldMappings[$alias . '__' . $fieldAlias] = $alias . '.' . $fields[$i]; + $fieldName = $this->name($alias . '.' . $fields[$i]); + $fieldAlias = $this->name($alias . '__' . $fieldAlias); + } else { + $build = explode('.', $fields[$i]); + $this->__fieldMappings[$build[0] . '__' . $fieldAlias] = $fields[$i]; + $fieldName = $this->name($build[0] . '.' . $build[1]); + $fieldAlias = $this->name(preg_replace("/^\[(.+)\]$/", "$1", $build[0]) . '__' . $fieldAlias); + } + if ($model->getColumnType($fields[$i]) == 'datetime') { + $fieldName = "CONVERT(VARCHAR(20), {$fieldName}, 20)"; + } + $fields[$i] = "{$fieldName} AS {$fieldAlias}"; + } + $result[] = $prepend . $fields[$i]; + } + return $result; + } else { + return $fields; + } + } + +/** + * Generates and executes an SQL INSERT statement for given model, fields, and values. + * Removes Identity (primary key) column from update data before returning to parent, if + * value is empty. + * + * @param Model $model + * @param array $fields + * @param array $values + * @param mixed $conditions + * @return array + */ + function create(&$model, $fields = null, $values = null) { + if (!empty($values)) { + $fields = array_combine($fields, $values); + } + $primaryKey = $this->_getPrimaryKey($model); + + if (array_key_exists($primaryKey, $fields)) { + if (empty($fields[$primaryKey])) { + unset($fields[$primaryKey]); + } else { + $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($model) . ' ON'); + } + } + $result = parent::create($model, array_keys($fields), array_values($fields)); + if (array_key_exists($primaryKey, $fields) && !empty($fields[$primaryKey])) { + $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($model) . ' OFF'); + } + return $result; + } + +/** + * Generates and executes an SQL UPDATE statement for given model, fields, and values. + * Removes Identity (primary key) column from update data before returning to parent. + * + * @param Model $model + * @param array $fields + * @param array $values + * @param mixed $conditions + * @return array + */ + function update(&$model, $fields = array(), $values = null, $conditions = null) { + if (!empty($values)) { + $fields = array_combine($fields, $values); + } + if (isset($fields[$model->primaryKey])) { + unset($fields[$model->primaryKey]); + } + if (empty($fields)) { + return true; + } + return parent::update($model, array_keys($fields), array_values($fields), $conditions); + } + +/** + * Returns a formatted error message from previous database operation. + * + * @return string Error message with error number + */ + function lastError() { + if ($this->__lastQueryHadError) { + $error = mssql_get_last_message(); + if ($error && !preg_match('/contexto de la base de datos a|contesto di database|changed database|contexte de la base de don|datenbankkontext/i', $error)) { + return $error; + } + } + return null; + } + +/** + * Returns number of affected rows in previous database operation. If no previous operation exists, + * this returns false. + * + * @return integer Number of affected rows + */ + function lastAffected() { + if ($this->_result) { + return mssql_rows_affected($this->connection); + } + return null; + } + +/** + * Returns number of rows in previous resultset. If no previous resultset exists, + * this returns false. + * + * @return integer Number of rows in resultset + */ + function lastNumRows() { + if ($this->_result) { + return @mssql_num_rows($this->_result); + } + return null; + } + +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param unknown_type $source + * @return in + */ + function lastInsertId($source = null) { + $id = $this->fetchRow('SELECT SCOPE_IDENTITY() AS insertID', false); + return $id[0]['insertID']; + } + +/** + * Returns a limit statement in the correct format for the particular database. + * + * @param integer $limit Limit of results returned + * @param integer $offset Offset from which to start results + * @return string SQL limit/offset statement + */ + function limit($limit, $offset = null) { + if ($limit) { + $rt = ''; + if (!strpos(strtolower($limit), 'top') || strpos(strtolower($limit), 'top') === 0) { + $rt = ' TOP'; + } + $rt .= ' ' . $limit; + if (is_int($offset) && $offset > 0) { + $rt .= ' OFFSET ' . $offset; + } + return $rt; + } + return null; + } + +/** + * Converts database-layer column types to basic types + * + * @param string $real Real database-layer column type (i.e. "varchar(255)") + * @return string Abstract column type (i.e. "string") + */ + function column($real) { + if (is_array($real)) { + $col = $real['name']; + + if (isset($real['limit'])) { + $col .= '(' . $real['limit'] . ')'; + } + return $col; + } + $col = str_replace(')', '', $real); + $limit = null; + if (strpos($col, '(') !== false) { + list($col, $limit) = explode('(', $col); + } + + if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) { + return $col; + } + if ($col == 'bit') { + return 'boolean'; + } + if (strpos($col, 'int') !== false) { + return 'integer'; + } + if (strpos($col, 'char') !== false) { + return 'string'; + } + if (strpos($col, 'text') !== false) { + return 'text'; + } + if (strpos($col, 'binary') !== false || $col == 'image') { + return 'binary'; + } + if (in_array($col, array('float', 'real', 'decimal', 'numeric'))) { + return 'float'; + } + return 'text'; + } + +/** + * Enter description here... + * + * @param unknown_type $results + */ + function resultSet(&$results) { + $this->results =& $results; + $this->map = array(); + $numFields = mssql_num_fields($results); + $index = 0; + $j = 0; + + while ($j < $numFields) { + $column = mssql_field_name($results, $j); + + if (strpos($column, '__')) { + if (isset($this->__fieldMappings[$column]) && strpos($this->__fieldMappings[$column], '.')) { + $map = explode('.', $this->__fieldMappings[$column]); + } elseif (isset($this->__fieldMappings[$column])) { + $map = array(0, $this->__fieldMappings[$column]); + } else { + $map = array(0, $column); + } + $this->map[$index++] = $map; + } else { + $this->map[$index++] = array(0, $column); + } + $j++; + } + } + +/** + * Builds final SQL statement + * + * @param string $type Query type + * @param array $data Query data + * @return string + */ + function renderStatement($type, $data) { + switch (strtolower($type)) { + case 'select': + extract($data); + $fields = trim($fields); + + if (strpos($limit, 'TOP') !== false && strpos($fields, 'DISTINCT ') === 0) { + $limit = 'DISTINCT ' . trim($limit); + $fields = substr($fields, 9); + } + + if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) { + $limit = preg_replace('/\s*offset.*$/i', '', $limit); + preg_match('/top\s+([0-9]+)/i', $limit, $limitVal); + $offset = intval($offset[1]) + intval($limitVal[1]); + $rOrder = $this->__switchSort($order); + list($order2, $rOrder) = array($this->__mapFields($order), $this->__mapFields($rOrder)); + return "SELECT * FROM (SELECT {$limit} * FROM (SELECT TOP {$offset} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order}) AS Set1 {$rOrder}) AS Set2 {$order2}"; + } else { + return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order}"; + } + break; + case "schema": + extract($data); + + foreach ($indexes as $i => $index) { + if (preg_match('/PRIMARY KEY/', $index)) { + unset($indexes[$i]); + break; + } + } + + foreach (array('columns', 'indexes') as $var) { + if (is_array(${$var})) { + ${$var} = "\t" . implode(",\n\t", array_filter(${$var})); + } + } + return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}"; + break; + default: + return parent::renderStatement($type, $data); + break; + } + } + +/** + * Reverses the sort direction of ORDER statements to get paging offsets to work correctly + * + * @param string $order + * @return string + * @access private + */ + function __switchSort($order) { + $order = preg_replace('/\s+ASC/i', '__tmp_asc__', $order); + $order = preg_replace('/\s+DESC/i', ' ASC', $order); + return preg_replace('/__tmp_asc__/', ' DESC', $order); + } + +/** + * Translates field names used for filtering and sorting to shortened names using the field map + * + * @param string $sql A snippet of SQL representing an ORDER or WHERE statement + * @return string The value of $sql with field names replaced + * @access private + */ + function __mapFields($sql) { + if (empty($sql) || empty($this->__fieldMappings)) { + return $sql; + } + foreach ($this->__fieldMappings as $key => $val) { + $sql = preg_replace('/' . preg_quote($val) . '/', $this->name($key), $sql); + $sql = preg_replace('/' . preg_quote($this->name($val)) . '/', $this->name($key), $sql); + } + return $sql; + } + +/** + * Returns an array of all result rows for a given SQL query. + * Returns false if no rows matched. + * + * @param string $sql SQL statement + * @param boolean $cache Enables returning/storing cached query results + * @return array Array of resultset rows, or false if no rows matched + */ + function read(&$model, $queryData = array(), $recursive = null) { + $results = parent::read($model, $queryData, $recursive); + $this->__fieldMappings = array(); + return $results; + } + +/** + * Fetches the next row from the current result set + * + * @return unknown + */ + function fetchResult() { + if ($row = mssql_fetch_row($this->results)) { + $resultRow = array(); + $i = 0; + + foreach ($row as $index => $field) { + list($table, $column) = $this->map[$index]; + $resultRow[$table][$column] = $row[$index]; + $i++; + } + return $resultRow; + } else { + return false; + } + } + +/** + * Inserts multiple values into a table + * + * @param string $table + * @param string $fields + * @param array $values + * @access protected + */ + function insertMulti($table, $fields, $values) { + $primaryKey = $this->_getPrimaryKey($table); + $hasPrimaryKey = $primaryKey != null && ( + (is_array($fields) && in_array($primaryKey, $fields) + || (is_string($fields) && strpos($fields, $this->startQuote . $primaryKey . $this->endQuote) !== false)) + ); + + if ($hasPrimaryKey) { + $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' ON'); + } + parent::insertMulti($table, $fields, $values); + if ($hasPrimaryKey) { + $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' OFF'); + } + } + +/** + * Generate a database-native column schema string + * + * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]), + * where options can be 'default', 'length', or 'key'. + * @return string + */ + function buildColumn($column) { + $result = preg_replace('/(int|integer)\([0-9]+\)/i', '$1', parent::buildColumn($column)); + if (strpos($result, 'DEFAULT NULL') !== false) { + $result = str_replace('DEFAULT NULL', 'NULL', $result); + } else if (array_keys($column) == array('type', 'name')) { + $result .= ' NULL'; + } + return $result; + } + +/** + * Format indexes for create table + * + * @param array $indexes + * @param string $table + * @return string + */ + function buildIndex($indexes, $table = null) { + $join = array(); + + foreach ($indexes as $name => $value) { + if ($name == 'PRIMARY') { + $join[] = 'PRIMARY KEY (' . $this->name($value['column']) . ')'; + } else if (isset($value['unique']) && $value['unique']) { + $out = "ALTER TABLE {$table} ADD CONSTRAINT {$name} UNIQUE"; + + if (is_array($value['column'])) { + $value['column'] = implode(', ', array_map(array(&$this, 'name'), $value['column'])); + } else { + $value['column'] = $this->name($value['column']); + } + $out .= "({$value['column']});"; + $join[] = $out; + } + } + return $join; + } + +/** + * Makes sure it will return the primary key + * + * @param mixed $model + * @access protected + * @return string + */ + function _getPrimaryKey($model) { + if (is_object($model)) { + $schema = $model->schema(); + } else { + $schema = $this->describe($model); + } + + foreach ($schema as $field => $props) { + if (isset($props['key']) && $props['key'] == 'primary') { + return $field; + } + } + return null; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mysql.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mysql.php new file mode 100644 index 000000000..7bd0fd64f --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mysql.php @@ -0,0 +1,808 @@ += 4.1 + * + * @var boolean + * @access protected + */ + var $_useAlias = true; + +/** + * Index of basic SQL commands + * + * @var array + * @access protected + */ + var $_commands = array( + 'begin' => 'START TRANSACTION', + 'commit' => 'COMMIT', + 'rollback' => 'ROLLBACK' + ); + +/** + * List of engine specific additional field parameters used on table creating + * + * @var array + * @access public + */ + var $fieldParameters = array( + 'charset' => array('value' => 'CHARACTER SET', 'quote' => false, 'join' => ' ', 'column' => false, 'position' => 'beforeDefault'), + 'collate' => array('value' => 'COLLATE', 'quote' => false, 'join' => ' ', 'column' => 'Collation', 'position' => 'beforeDefault'), + 'comment' => array('value' => 'COMMENT', 'quote' => true, 'join' => ' ', 'column' => 'Comment', 'position' => 'afterDefault') + ); + +/** + * List of table engine specific parameters used on table creating + * + * @var array + * @access public + */ + var $tableParameters = array( + 'charset' => array('value' => 'DEFAULT CHARSET', 'quote' => false, 'join' => '=', 'column' => 'charset'), + 'collate' => array('value' => 'COLLATE', 'quote' => false, 'join' => '=', 'column' => 'Collation'), + 'engine' => array('value' => 'ENGINE', 'quote' => false, 'join' => '=', 'column' => 'Engine') + ); + +/** + * MySQL column definition + * + * @var array + */ + var $columns = array( + 'primary_key' => array('name' => 'NOT NULL AUTO_INCREMENT'), + 'string' => array('name' => 'varchar', 'limit' => '255'), + 'text' => array('name' => 'text'), + 'integer' => array('name' => 'int', 'limit' => '11', 'formatter' => 'intval'), + 'float' => array('name' => 'float', 'formatter' => 'floatval'), + 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'), + 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'), + 'binary' => array('name' => 'blob'), + 'boolean' => array('name' => 'tinyint', 'limit' => '1') + ); + +/** + * Returns an array of the fields in given table name. + * + * @param string $tableName Name of database table to inspect + * @return array Fields in table. Keys are name and type + */ + function describe(&$model) { + $cache = parent::describe($model); + if ($cache != null) { + return $cache; + } + $fields = false; + $cols = $this->query('SHOW FULL COLUMNS FROM ' . $this->fullTableName($model)); + + foreach ($cols as $column) { + $colKey = array_keys($column); + if (isset($column[$colKey[0]]) && !isset($column[0])) { + $column[0] = $column[$colKey[0]]; + } + if (isset($column[0])) { + $fields[$column[0]['Field']] = array( + 'type' => $this->column($column[0]['Type']), + 'null' => ($column[0]['Null'] == 'YES' ? true : false), + 'default' => $column[0]['Default'], + 'length' => $this->length($column[0]['Type']), + ); + if (!empty($column[0]['Key']) && isset($this->index[$column[0]['Key']])) { + $fields[$column[0]['Field']]['key'] = $this->index[$column[0]['Key']]; + } + foreach ($this->fieldParameters as $name => $value) { + if (!empty($column[0][$value['column']])) { + $fields[$column[0]['Field']][$name] = $column[0][$value['column']]; + } + } + if (isset($fields[$column[0]['Field']]['collate'])) { + $charset = $this->getCharsetName($fields[$column[0]['Field']]['collate']); + if ($charset) { + $fields[$column[0]['Field']]['charset'] = $charset; + } + } + } + } + $this->__cacheDescription($this->fullTableName($model, false), $fields); + return $fields; + } + +/** + * Generates and executes an SQL UPDATE statement for given model, fields, and values. + * + * @param Model $model + * @param array $fields + * @param array $values + * @param mixed $conditions + * @return array + */ + function update(&$model, $fields = array(), $values = null, $conditions = null) { + if (!$this->_useAlias) { + return parent::update($model, $fields, $values, $conditions); + } + + if ($values == null) { + $combined = $fields; + } else { + $combined = array_combine($fields, $values); + } + + $alias = $joins = false; + $fields = $this->_prepareUpdateFields($model, $combined, empty($conditions), !empty($conditions)); + $fields = implode(', ', $fields); + $table = $this->fullTableName($model); + + if (!empty($conditions)) { + $alias = $this->name($model->alias); + if ($model->name == $model->alias) { + $joins = implode(' ', $this->_getJoins($model)); + } + } + $conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias), true, true, $model); + + if ($conditions === false) { + return false; + } + + if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) { + $model->onError(); + return false; + } + return true; + } + +/** + * Generates and executes an SQL DELETE statement for given id/conditions on given model. + * + * @param Model $model + * @param mixed $conditions + * @return boolean Success + */ + function delete(&$model, $conditions = null) { + if (!$this->_useAlias) { + return parent::delete($model, $conditions); + } + $alias = $this->name($model->alias); + $table = $this->fullTableName($model); + $joins = implode(' ', $this->_getJoins($model)); + + if (empty($conditions)) { + $alias = $joins = false; + } + $complexConditions = false; + foreach ((array)$conditions as $key => $value) { + if (strpos($key, $model->alias) === false) { + $complexConditions = true; + break; + } + } + if (!$complexConditions) { + $joins = false; + } + + $conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias), true, true, $model); + if ($conditions === false) { + return false; + } + if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) { + $model->onError(); + return false; + } + return true; + } + +/** + * Sets the database encoding + * + * @param string $enc Database encoding + */ + function setEncoding($enc) { + return $this->_execute('SET NAMES ' . $enc) != false; + } + +/** + * Returns an array of the indexes in given datasource name. + * + * @param string $model Name of model to inspect + * @return array Fields in table. Keys are column and unique + */ + function index($model) { + $index = array(); + $table = $this->fullTableName($model); + if ($table) { + $indexes = $this->query('SHOW INDEX FROM ' . $table); + if (isset($indexes[0]['STATISTICS'])) { + $keys = Set::extract($indexes, '{n}.STATISTICS'); + } else { + $keys = Set::extract($indexes, '{n}.0'); + } + foreach ($keys as $i => $key) { + if (!isset($index[$key['Key_name']])) { + $col = array(); + $index[$key['Key_name']]['column'] = $key['Column_name']; + $index[$key['Key_name']]['unique'] = intval($key['Non_unique'] == 0); + } else { + if (!is_array($index[$key['Key_name']]['column'])) { + $col[] = $index[$key['Key_name']]['column']; + } + $col[] = $key['Column_name']; + $index[$key['Key_name']]['column'] = $col; + } + } + } + return $index; + } + +/** + * Generate a MySQL Alter Table syntax for the given Schema comparison + * + * @param array $compare Result of a CakeSchema::compare() + * @return array Array of alter statements to make. + */ + function alterSchema($compare, $table = null) { + if (!is_array($compare)) { + return false; + } + $out = ''; + $colList = array(); + foreach ($compare as $curTable => $types) { + $indexes = $tableParameters = $colList = array(); + if (!$table || $table == $curTable) { + $out .= 'ALTER TABLE ' . $this->fullTableName($curTable) . " \n"; + foreach ($types as $type => $column) { + if (isset($column['indexes'])) { + $indexes[$type] = $column['indexes']; + unset($column['indexes']); + } + if (isset($column['tableParameters'])) { + $tableParameters[$type] = $column['tableParameters']; + unset($column['tableParameters']); + } + switch ($type) { + case 'add': + foreach ($column as $field => $col) { + $col['name'] = $field; + $alter = 'ADD ' . $this->buildColumn($col); + if (isset($col['after'])) { + $alter .= ' AFTER ' . $this->name($col['after']); + } + $colList[] = $alter; + } + break; + case 'drop': + foreach ($column as $field => $col) { + $col['name'] = $field; + $colList[] = 'DROP ' . $this->name($field); + } + break; + case 'change': + foreach ($column as $field => $col) { + if (!isset($col['name'])) { + $col['name'] = $field; + } + $colList[] = 'CHANGE ' . $this->name($field) . ' ' . $this->buildColumn($col); + } + break; + } + } + $colList = array_merge($colList, $this->_alterIndexes($curTable, $indexes)); + $colList = array_merge($colList, $this->_alterTableParameters($curTable, $tableParameters)); + $out .= "\t" . join(",\n\t", $colList) . ";\n\n"; + } + } + return $out; + } + +/** + * Generate a MySQL "drop table" statement for the given Schema object + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $table Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + */ + function dropSchema($schema, $table = null) { + if (!is_a($schema, 'CakeSchema')) { + trigger_error(__('Invalid schema object', true), E_USER_WARNING); + return null; + } + $out = ''; + foreach ($schema->tables as $curTable => $columns) { + if (!$table || $table == $curTable) { + $out .= 'DROP TABLE IF EXISTS ' . $this->fullTableName($curTable) . ";\n"; + } + } + return $out; + } + +/** + * Generate MySQL table parameter alteration statementes for a table. + * + * @param string $table Table to alter parameters for. + * @param array $parameters Parameters to add & drop. + * @return array Array of table property alteration statementes. + * @todo Implement this method. + */ + function _alterTableParameters($table, $parameters) { + if (isset($parameters['change'])) { + return $this->buildTableParameters($parameters['change']); + } + return array(); + } + +/** + * Generate MySQL index alteration statements for a table. + * + * @param string $table Table to alter indexes for + * @param array $new Indexes to add and drop + * @return array Index alteration statements + */ + function _alterIndexes($table, $indexes) { + $alter = array(); + if (isset($indexes['drop'])) { + foreach($indexes['drop'] as $name => $value) { + $out = 'DROP '; + if ($name == 'PRIMARY') { + $out .= 'PRIMARY KEY'; + } else { + $out .= 'KEY ' . $name; + } + $alter[] = $out; + } + } + if (isset($indexes['add'])) { + foreach ($indexes['add'] as $name => $value) { + $out = 'ADD '; + if ($name == 'PRIMARY') { + $out .= 'PRIMARY '; + $name = null; + } else { + if (!empty($value['unique'])) { + $out .= 'UNIQUE '; + } + } + if (is_array($value['column'])) { + $out .= 'KEY '. $name .' (' . implode(', ', array_map(array(&$this, 'name'), $value['column'])) . ')'; + } else { + $out .= 'KEY '. $name .' (' . $this->name($value['column']) . ')'; + } + $alter[] = $out; + } + } + return $alter; + } + +/** + * Inserts multiple values into a table + * + * @param string $table + * @param string $fields + * @param array $values + */ + function insertMulti($table, $fields, $values) { + $table = $this->fullTableName($table); + if (is_array($fields)) { + $fields = implode(', ', array_map(array(&$this, 'name'), $fields)); + } + $values = implode(', ', $values); + $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values}"); + } +/** + * Returns an detailed array of sources (tables) in the database. + * + * @param string $name Table name to get parameters + * @return array Array of tablenames in the database + */ + function listDetailedSources($name = null) { + $condition = ''; + if (is_string($name)) { + $condition = ' LIKE ' . $this->value($name); + } + $result = $this->query('SHOW TABLE STATUS FROM ' . $this->name($this->config['database']) . $condition . ';'); + if (!$result) { + return array(); + } else { + $tables = array(); + foreach ($result as $row) { + $tables[$row['TABLES']['Name']] = $row['TABLES']; + if (!empty($row['TABLES']['Collation'])) { + $charset = $this->getCharsetName($row['TABLES']['Collation']); + if ($charset) { + $tables[$row['TABLES']['Name']]['charset'] = $charset; + } + } + } + if (is_string($name)) { + return $tables[$name]; + } + return $tables; + } + } + +/** + * Converts database-layer column types to basic types + * + * @param string $real Real database-layer column type (i.e. "varchar(255)") + * @return string Abstract column type (i.e. "string") + */ + function column($real) { + if (is_array($real)) { + $col = $real['name']; + if (isset($real['limit'])) { + $col .= '('.$real['limit'].')'; + } + return $col; + } + + $col = str_replace(')', '', $real); + $limit = $this->length($real); + if (strpos($col, '(') !== false) { + list($col, $vals) = explode('(', $col); + } + + if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) { + return $col; + } + if (($col == 'tinyint' && $limit == 1) || $col == 'boolean') { + return 'boolean'; + } + if (strpos($col, 'int') !== false) { + return 'integer'; + } + if (strpos($col, 'char') !== false || $col == 'tinytext') { + return 'string'; + } + if (strpos($col, 'text') !== false) { + return 'text'; + } + if (strpos($col, 'blob') !== false || $col == 'binary') { + return 'binary'; + } + if (strpos($col, 'float') !== false || strpos($col, 'double') !== false || strpos($col, 'decimal') !== false) { + return 'float'; + } + if (strpos($col, 'enum') !== false) { + return "enum($vals)"; + } + return 'text'; + } +} + +/** + * MySQL DBO driver object + * + * Provides connection and SQL generation for MySQL RDMS + * + * @package cake + * @subpackage cake.cake.libs.model.datasources.dbo + */ +class DboMysql extends DboMysqlBase { + +/** + * Datasource description + * + * @var string + */ + var $description = "MySQL DBO Driver"; + +/** + * Base configuration settings for MySQL driver + * + * @var array + */ + var $_baseConfig = array( + 'persistent' => true, + 'host' => 'localhost', + 'login' => 'root', + 'password' => '', + 'database' => 'cake', + 'port' => '3306' + ); + +/** + * Connects to the database using options in the given configuration array. + * + * @return boolean True if the database could be connected, else false + */ + function connect() { + $config = $this->config; + $this->connected = false; + + if (!$config['persistent']) { + $this->connection = mysql_connect($config['host'] . ':' . $config['port'], $config['login'], $config['password'], true); + $config['connect'] = 'mysql_connect'; + } else { + $this->connection = mysql_pconnect($config['host'] . ':' . $config['port'], $config['login'], $config['password']); + } + + if (mysql_select_db($config['database'], $this->connection)) { + $this->connected = true; + } + + if (!empty($config['encoding'])) { + $this->setEncoding($config['encoding']); + } + + $this->_useAlias = (bool)version_compare(mysql_get_server_info($this->connection), "4.1", ">="); + + return $this->connected; + } + +/** + * Check whether the MySQL extension is installed/loaded + * + * @return boolean + */ + function enabled() { + return extension_loaded('mysql'); + } +/** + * Disconnects from database. + * + * @return boolean True if the database could be disconnected, else false + */ + function disconnect() { + if (isset($this->results) && is_resource($this->results)) { + mysql_free_result($this->results); + } + $this->connected = !@mysql_close($this->connection); + return !$this->connected; + } + +/** + * Executes given SQL statement. + * + * @param string $sql SQL statement + * @return resource Result resource identifier + * @access protected + */ + function _execute($sql) { + return mysql_query($sql, $this->connection); + } + +/** + * Returns an array of sources (tables) in the database. + * + * @return array Array of tablenames in the database + */ + function listSources() { + $cache = parent::listSources(); + if ($cache != null) { + return $cache; + } + $result = $this->_execute('SHOW TABLES FROM ' . $this->name($this->config['database']) . ';'); + + if (!$result) { + return array(); + } else { + $tables = array(); + + while ($line = mysql_fetch_row($result)) { + $tables[] = $line[0]; + } + parent::listSources($tables); + return $tables; + } + } + +/** + * Returns a quoted and escaped string of $data for use in an SQL statement. + * + * @param string $data String to be prepared for use in an SQL statement + * @param string $column The column into which this data will be inserted + * @param boolean $safe Whether or not numeric data should be handled automagically if no column data is provided + * @return string Quoted and escaped data + */ + function value($data, $column = null, $safe = false) { + $parent = parent::value($data, $column, $safe); + + if ($parent != null) { + return $parent; + } + if ($data === null || (is_array($data) && empty($data))) { + return 'NULL'; + } + if ($data === '' && $column !== 'integer' && $column !== 'float' && $column !== 'boolean') { + return "''"; + } + if (empty($column)) { + $column = $this->introspectType($data); + } + + switch ($column) { + case 'boolean': + return $this->boolean((bool)$data); + break; + case 'integer': + case 'float': + if ($data === '') { + return 'NULL'; + } + if (is_float($data)) { + return sprintf('%F', $data); + } + if ((is_int($data) || $data === '0') || ( + is_numeric($data) && strpos($data, ',') === false && + $data[0] != '0' && strpos($data, 'e') === false) + ) { + return $data; + } + default: + return "'" . mysql_real_escape_string($data, $this->connection) . "'"; + break; + } + } + +/** + * Returns a formatted error message from previous database operation. + * + * @return string Error message with error number + */ + function lastError() { + if (mysql_errno($this->connection)) { + return mysql_errno($this->connection).': '.mysql_error($this->connection); + } + return null; + } + +/** + * Returns number of affected rows in previous database operation. If no previous operation exists, + * this returns false. + * + * @return integer Number of affected rows + */ + function lastAffected() { + if ($this->_result) { + return mysql_affected_rows($this->connection); + } + return null; + } + +/** + * Returns number of rows in previous resultset. If no previous resultset exists, + * this returns false. + * + * @return integer Number of rows in resultset + */ + function lastNumRows() { + if ($this->hasResult()) { + return mysql_num_rows($this->_result); + } + return null; + } + +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param unknown_type $source + * @return in + */ + function lastInsertId($source = null) { + $id = $this->fetchRow('SELECT LAST_INSERT_ID() AS insertID', false); + if ($id !== false && !empty($id) && !empty($id[0]) && isset($id[0]['insertID'])) { + return $id[0]['insertID']; + } + + return null; + } + +/** + * Enter description here... + * + * @param unknown_type $results + */ + function resultSet(&$results) { + if (isset($this->results) && is_resource($this->results) && $this->results != $results) { + mysql_free_result($this->results); + } + $this->results =& $results; + $this->map = array(); + $numFields = mysql_num_fields($results); + $index = 0; + $j = 0; + + while ($j < $numFields) { + $column = mysql_fetch_field($results, $j); + if (!empty($column->table) && strpos($column->name, $this->virtualFieldSeparator) === false) { + $this->map[$index++] = array($column->table, $column->name); + } else { + $this->map[$index++] = array(0, $column->name); + } + $j++; + } + } + +/** + * Fetches the next row from the current result set + * + * @return unknown + */ + function fetchResult() { + if ($row = mysql_fetch_row($this->results)) { + $resultRow = array(); + $i = 0; + foreach ($row as $index => $field) { + list($table, $column) = $this->map[$index]; + $resultRow[$table][$column] = $row[$index]; + $i++; + } + return $resultRow; + } else { + return false; + } + } + +/** + * Gets the database encoding + * + * @return string The database encoding + */ + function getEncoding() { + return mysql_client_encoding($this->connection); + } + +/** + * Query charset by collation + * + * @param string $name Collation name + * @return string Character set name + */ + function getCharsetName($name) { + if ((bool)version_compare(mysql_get_server_info($this->connection), "5", ">=")) { + $cols = $this->query('SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= ' . $this->value($name) . ';'); + if (isset($cols[0]['COLLATIONS']['CHARACTER_SET_NAME'])) { + return $cols[0]['COLLATIONS']['CHARACTER_SET_NAME']; + } + } + return false; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mysqli.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mysqli.php new file mode 100644 index 000000000..844554cdd --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_mysqli.php @@ -0,0 +1,332 @@ + true, + 'host' => 'localhost', + 'login' => 'root', + 'password' => '', + 'database' => 'cake', + 'port' => '3306', + 'socket' => null + ); + +/** + * Connects to the database using options in the given configuration array. + * + * @return boolean True if the database could be connected, else false + */ + function connect() { + $config = $this->config; + $this->connected = false; + + $this->connection = mysqli_connect($config['host'], $config['login'], $config['password'], $config['database'], $config['port'], $config['socket']); + + if ($this->connection !== false) { + $this->connected = true; + } + + $this->_useAlias = (bool)version_compare(mysqli_get_server_info($this->connection), "4.1", ">="); + + if (!empty($config['encoding'])) { + $this->setEncoding($config['encoding']); + } + return $this->connected; + } + +/** + * Check that MySQLi is installed/enabled + * + * @return boolean + */ + function enabled() { + return extension_loaded('mysqli'); + } +/** + * Disconnects from database. + * + * @return boolean True if the database could be disconnected, else false + */ + function disconnect() { + if (isset($this->results) && is_resource($this->results)) { + mysqli_free_result($this->results); + } + $this->connected = !@mysqli_close($this->connection); + return !$this->connected; + } + +/** + * Executes given SQL statement. + * + * @param string $sql SQL statement + * @return resource Result resource identifier + * @access protected + */ + function _execute($sql) { + if (preg_match('/^\s*call/i', $sql)) { + return $this->_executeProcedure($sql); + } + return mysqli_query($this->connection, $sql); + } + +/** + * Executes given SQL statement (procedure call). + * + * @param string $sql SQL statement (procedure call) + * @return resource Result resource identifier for first recordset + * @access protected + */ + function _executeProcedure($sql) { + $answer = mysqli_multi_query($this->connection, $sql); + + $firstResult = mysqli_store_result($this->connection); + + if (mysqli_more_results($this->connection)) { + while ($lastResult = mysqli_next_result($this->connection)); + } + return $firstResult; + } + +/** + * Returns an array of sources (tables) in the database. + * + * @return array Array of tablenames in the database + */ + function listSources() { + $cache = parent::listSources(); + if ($cache !== null) { + return $cache; + } + $result = $this->_execute('SHOW TABLES FROM ' . $this->name($this->config['database']) . ';'); + + if (!$result) { + return array(); + } + + $tables = array(); + + while ($line = mysqli_fetch_row($result)) { + $tables[] = $line[0]; + } + parent::listSources($tables); + return $tables; + } + +/** + * Returns a quoted and escaped string of $data for use in an SQL statement. + * + * @param string $data String to be prepared for use in an SQL statement + * @param string $column The column into which this data will be inserted + * @param boolean $safe Whether or not numeric data should be handled automagically if no column data is provided + * @return string Quoted and escaped data + */ + function value($data, $column = null, $safe = false) { + $parent = parent::value($data, $column, $safe); + + if ($parent != null) { + return $parent; + } + if ($data === null || (is_array($data) && empty($data))) { + return 'NULL'; + } + if ($data === '' && $column !== 'integer' && $column !== 'float' && $column !== 'boolean') { + return "''"; + } + if (empty($column)) { + $column = $this->introspectType($data); + } + + switch ($column) { + case 'boolean': + return $this->boolean((bool)$data); + break; + case 'integer' : + case 'float' : + case null : + if ($data === '') { + return 'NULL'; + } + if ((is_int($data) || is_float($data) || $data === '0') || ( + is_numeric($data) && strpos($data, ',') === false && + $data[0] != '0' && strpos($data, 'e') === false)) { + return $data; + } + default: + $data = "'" . mysqli_real_escape_string($this->connection, $data) . "'"; + break; + } + + return $data; + } + +/** + * Returns a formatted error message from previous database operation. + * + * @return string Error message with error number + */ + function lastError() { + if (mysqli_errno($this->connection)) { + return mysqli_errno($this->connection).': '.mysqli_error($this->connection); + } + return null; + } + +/** + * Returns number of affected rows in previous database operation. If no previous operation exists, + * this returns false. + * + * @return integer Number of affected rows + */ + function lastAffected() { + if ($this->_result) { + return mysqli_affected_rows($this->connection); + } + return null; + } + +/** + * Returns number of rows in previous resultset. If no previous resultset exists, + * this returns false. + * + * @return integer Number of rows in resultset + */ + function lastNumRows() { + if ($this->hasResult()) { + return mysqli_num_rows($this->_result); + } + return null; + } + +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param unknown_type $source + * @return in + */ + function lastInsertId($source = null) { + $id = $this->fetchRow('SELECT LAST_INSERT_ID() AS insertID', false); + if ($id !== false && !empty($id) && !empty($id[0]) && isset($id[0]['insertID'])) { + return $id[0]['insertID']; + } + return null; + } + +/** + * Enter description here... + * + * @param unknown_type $results + */ + function resultSet(&$results) { + if (isset($this->results) && is_resource($this->results) && $this->results != $results) { + mysqli_free_result($this->results); + } + $this->results =& $results; + $this->map = array(); + $numFields = mysqli_num_fields($results); + $index = 0; + $j = 0; + while ($j < $numFields) { + $column = mysqli_fetch_field_direct($results, $j); + if (!empty($column->table)) { + $this->map[$index++] = array($column->table, $column->name); + } else { + $this->map[$index++] = array(0, $column->name); + } + $j++; + } + } + +/** + * Fetches the next row from the current result set + * + * @return unknown + */ + function fetchResult() { + if ($row = mysqli_fetch_row($this->results)) { + $resultRow = array(); + foreach ($row as $index => $field) { + $table = $column = null; + if (count($this->map[$index]) === 2) { + list($table, $column) = $this->map[$index]; + } + $resultRow[$table][$column] = $row[$index]; + } + return $resultRow; + } + return false; + } + +/** + * Gets the database encoding + * + * @return string The database encoding + */ + function getEncoding() { + return mysqli_client_encoding($this->connection); + } + +/** + * Query charset by collation + * + * @param string $name Collation name + * @return string Character set name + */ + function getCharsetName($name) { + if ((bool)version_compare(mysqli_get_server_info($this->connection), "5", ">=")) { + $cols = $this->query('SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= ' . $this->value($name) . ';'); + if (isset($cols[0]['COLLATIONS']['CHARACTER_SET_NAME'])) { + return $cols[0]['COLLATIONS']['CHARACTER_SET_NAME']; + } + } + return false; + } + +/** + * Checks if the result is valid + * + * @return boolean True if the result is valid, else false + */ + function hasResult() { + return is_object($this->_result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_oracle.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_oracle.php new file mode 100644 index 000000000..38fb03382 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_oracle.php @@ -0,0 +1,1159 @@ + array('name' => ''), + 'string' => array('name' => 'varchar2', 'limit' => '255'), + 'text' => array('name' => 'varchar2'), + 'integer' => array('name' => 'number'), + 'float' => array('name' => 'float'), + 'datetime' => array('name' => 'date', 'format' => 'Y-m-d H:i:s'), + 'timestamp' => array('name' => 'date', 'format' => 'Y-m-d H:i:s'), + 'time' => array('name' => 'date', 'format' => 'Y-m-d H:i:s'), + 'date' => array('name' => 'date', 'format' => 'Y-m-d H:i:s'), + 'binary' => array('name' => 'bytea'), + 'boolean' => array('name' => 'boolean'), + 'number' => array('name' => 'number'), + 'inet' => array('name' => 'inet')); + +/** + * Connection object + * + * @var mixed + * @access protected + */ + var $connection; + +/** + * Query limit + * + * @var int + * @access protected + */ + var $_limit = -1; + +/** + * Query offset + * + * @var int + * @access protected + */ + var $_offset = 0; + +/** + * Enter description here... + * + * @var unknown_type + * @access protected + */ + var $_map; + +/** + * Current Row + * + * @var mixed + * @access protected + */ + var $_currentRow; + +/** + * Number of rows + * + * @var int + * @access protected + */ + var $_numRows; + +/** + * Query results + * + * @var mixed + * @access protected + */ + var $_results; + +/** + * Last error issued by oci extension + * + * @var unknown_type + */ + var $_error; + +/** + * Base configuration settings for MySQL driver + * + * @var array + */ + var $_baseConfig = array( + 'persistent' => true, + 'host' => 'localhost', + 'login' => 'system', + 'password' => '', + 'database' => 'cake', + 'nls_sort' => '', + 'nls_sort' => '' + ); + +/** + * Table-sequence map + * + * @var unknown_type + */ + var $_sequenceMap = array(); + +/** + * Connects to the database using options in the given configuration array. + * + * @return boolean True if the database could be connected, else false + * @access public + */ + function connect() { + $config = $this->config; + $this->connected = false; + $config['charset'] = !empty($config['charset']) ? $config['charset'] : null; + + if (!$config['persistent']) { + $this->connection = @ocilogon($config['login'], $config['password'], $config['database'], $config['charset']); + } else { + $this->connection = @ociplogon($config['login'], $config['password'], $config['database'], $config['charset']); + } + + if ($this->connection) { + $this->connected = true; + if (!empty($config['nls_sort'])) { + $this->execute('ALTER SESSION SET NLS_SORT='.$config['nls_sort']); + } + + if (!empty($config['nls_comp'])) { + $this->execute('ALTER SESSION SET NLS_COMP='.$config['nls_comp']); + } + $this->execute("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'"); + } else { + $this->connected = false; + $this->_setError(); + return false; + } + return $this->connected; + } + +/** + * Keeps track of the most recent Oracle error + * + */ + function _setError($source = null, $clear = false) { + if ($source) { + $e = ocierror($source); + } else { + $e = ocierror(); + } + $this->_error = $e['message']; + if ($clear) { + $this->_error = null; + } + } + +/** + * Sets the encoding language of the session + * + * @param string $lang language constant + * @return bool + */ + function setEncoding($lang) { + if (!$this->execute('ALTER SESSION SET NLS_LANGUAGE='.$lang)) { + return false; + } + return true; + } + +/** + * Gets the current encoding language + * + * @return string language constant + */ + function getEncoding() { + $sql = 'SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER=\'NLS_LANGUAGE\''; + if (!$this->execute($sql)) { + return false; + } + + if (!$row = $this->fetchRow()) { + return false; + } + return $row[0]['VALUE']; + } + +/** + * Disconnects from database. + * + * @return boolean True if the database could be disconnected, else false + * @access public + */ + function disconnect() { + if ($this->connection) { + $this->connected = !ocilogoff($this->connection); + return !$this->connected; + } + } + +/** + * Scrape the incoming SQL to create the association map. This is an extremely + * experimental method that creates the association maps since Oracle will not tell us. + * + * @param string $sql + * @return false if sql is nor a SELECT + * @access protected + */ + function _scrapeSQL($sql) { + $sql = str_replace("\"", '', $sql); + $preFrom = preg_split('/\bFROM\b/', $sql); + $preFrom = $preFrom[0]; + $find = array('SELECT'); + $replace = array(''); + $fieldList = trim(str_replace($find, $replace, $preFrom)); + $fields = preg_split('/,\s+/', $fieldList);//explode(', ', $fieldList); + $lastTableName = ''; + + foreach($fields as $key => $value) { + if ($value != 'COUNT(*) AS count') { + if (preg_match('/\s+(\w+(\.\w+)*)$/', $value, $matches)) { + $fields[$key] = $matches[1]; + + if (preg_match('/^(\w+\.)/', $value, $matches)) { + $fields[$key] = $matches[1] . $fields[$key]; + $lastTableName = $matches[1]; + } + } + /* + if (preg_match('/(([[:alnum:]_]+)\.[[:alnum:]_]+)(\s+AS\s+(\w+))?$/i', $value, $matches)) { + $fields[$key] = isset($matches[4]) ? $matches[2] . '.' . $matches[4] : $matches[1]; + } + */ + } + } + $this->_map = array(); + + foreach($fields as $f) { + $e = explode('.', $f); + if (count($e) > 1) { + $table = $e[0]; + $field = strtolower($e[1]); + } else { + $table = 0; + $field = $e[0]; + } + $this->_map[] = array($table, $field); + } + } + +/** + * Modify a SQL query to limit (and offset) the result set + * + * @param integer $limit Maximum number of rows to return + * @param integer $offset Row to begin returning + * @return modified SQL Query + * @access public + */ + function limit($limit = -1, $offset = 0) { + $this->_limit = (int) $limit; + $this->_offset = (int) $offset; + } + +/** + * Returns number of rows in previous resultset. If no previous resultset exists, + * this returns false. + * + * @return integer Number of rows in resultset + * @access public + */ + function lastNumRows() { + return $this->_numRows; + } + +/** + * Executes given SQL statement. This is an overloaded method. + * + * @param string $sql SQL statement + * @return resource Result resource identifier or null + * @access protected + */ + function _execute($sql) { + $this->_statementId = @ociparse($this->connection, $sql); + if (!$this->_statementId) { + $this->_setError($this->connection); + return false; + } + + if ($this->__transactionStarted) { + $mode = OCI_DEFAULT; + } else { + $mode = OCI_COMMIT_ON_SUCCESS; + } + + if (!@ociexecute($this->_statementId, $mode)) { + $this->_setError($this->_statementId); + return false; + } + + $this->_setError(null, true); + + switch(ocistatementtype($this->_statementId)) { + case 'DESCRIBE': + case 'SELECT': + $this->_scrapeSQL($sql); + break; + default: + return $this->_statementId; + break; + } + + if ($this->_limit >= 1) { + ocisetprefetch($this->_statementId, $this->_limit); + } else { + ocisetprefetch($this->_statementId, 3000); + } + $this->_numRows = ocifetchstatement($this->_statementId, $this->_results, $this->_offset, $this->_limit, OCI_NUM | OCI_FETCHSTATEMENT_BY_ROW); + $this->_currentRow = 0; + $this->limit(); + return $this->_statementId; + } + +/** + * Fetch result row + * + * @return array + * @access public + */ + function fetchRow() { + if ($this->_currentRow >= $this->_numRows) { + ocifreestatement($this->_statementId); + $this->_map = null; + $this->_results = null; + $this->_currentRow = null; + $this->_numRows = null; + return false; + } + $resultRow = array(); + + foreach($this->_results[$this->_currentRow] as $index => $field) { + list($table, $column) = $this->_map[$index]; + + if (strpos($column, ' count')) { + $resultRow[0]['count'] = $field; + } else { + $resultRow[$table][$column] = $this->_results[$this->_currentRow][$index]; + } + } + $this->_currentRow++; + return $resultRow; + } + +/** + * Fetches the next row from the current result set + * + * @return unknown + */ + function fetchResult() { + return $this->fetchRow(); + } + +/** + * Checks to see if a named sequence exists + * + * @param string $sequence + * @return bool + * @access public + */ + function sequenceExists($sequence) { + $sql = "SELECT SEQUENCE_NAME FROM USER_SEQUENCES WHERE SEQUENCE_NAME = '$sequence'"; + if (!$this->execute($sql)) { + return false; + } + return $this->fetchRow(); + } + +/** + * Creates a database sequence + * + * @param string $sequence + * @return bool + * @access public + */ + function createSequence($sequence) { + $sql = "CREATE SEQUENCE $sequence"; + return $this->execute($sql); + } + +/** + * Create trigger + * + * @param string $table + * @return mixed + * @access public + */ + function createTrigger($table) { + $sql = "CREATE OR REPLACE TRIGGER pk_$table" . "_trigger BEFORE INSERT ON $table FOR EACH ROW BEGIN SELECT pk_$table.NEXTVAL INTO :NEW.ID FROM DUAL; END;"; + return $this->execute($sql); + } + +/** + * Returns an array of tables in the database. If there are no tables, an error is + * raised and the application exits. + * + * @return array tablenames in the database + * @access public + */ + function listSources() { + $cache = parent::listSources(); + if ($cache != null) { + return $cache; + } + $sql = 'SELECT view_name AS name FROM all_views UNION SELECT table_name AS name FROM all_tables'; + + if (!$this->execute($sql)) { + return false; + } + $sources = array(); + + while($r = $this->fetchRow()) { + $sources[] = strtolower($r[0]['name']); + } + parent::listSources($sources); + return $sources; + } + +/** + * Returns an array of the fields in given table name. + * + * @param object instance of a model to inspect + * @return array Fields in table. Keys are name and type + * @access public + */ + function describe(&$model) { + $table = $this->fullTableName($model, false); + + if (!empty($model->sequence)) { + $this->_sequenceMap[$table] = $model->sequence; + } elseif (!empty($model->table)) { + $this->_sequenceMap[$table] = $model->table . '_seq'; + } + + $cache = parent::describe($model); + + if ($cache != null) { + return $cache; + } + + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH FROM all_tab_columns WHERE table_name = \''; + $sql .= strtoupper($this->fullTableName($model)) . '\''; + + if (!$this->execute($sql)) { + return false; + } + + $fields = array(); + + for ($i = 0; $row = $this->fetchRow(); $i++) { + $fields[strtolower($row[0]['COLUMN_NAME'])] = array( + 'type'=> $this->column($row[0]['DATA_TYPE']), + 'length'=> $row[0]['DATA_LENGTH'] + ); + } + $this->__cacheDescription($this->fullTableName($model, false), $fields); + + return $fields; + } + +/** + * Deletes all the records in a table and drops all associated auto-increment sequences. + * Using DELETE instead of TRUNCATE because it causes locking problems. + * + * @param mixed $table A string or model class representing the table to be truncated + * @param integer $reset If -1, sequences are dropped, if 0 (default), sequences are reset, + * and if 1, sequences are not modified + * @return boolean SQL TRUNCATE TABLE statement, false if not applicable. + * @access public + * + */ + function truncate($table, $reset = 0) { + + if (empty($this->_sequences)) { + $sql = "SELECT sequence_name FROM all_sequences"; + $this->execute($sql); + while ($row = $this->fetchRow()) { + $this->_sequences[] = strtolower($row[0]['sequence_name']); + } + } + + $this->execute('DELETE FROM ' . $this->fullTableName($table)); + if (!isset($this->_sequenceMap[$table]) || !in_array($this->_sequenceMap[$table], $this->_sequences)) { + return true; + } + if ($reset === 0) { + $this->execute("SELECT {$this->_sequenceMap[$table]}.nextval FROM dual"); + $row = $this->fetchRow(); + $currval = $row[$this->_sequenceMap[$table]]['nextval']; + + $this->execute("SELECT min_value FROM all_sequences WHERE sequence_name = '{$this->_sequenceMap[$table]}'"); + $row = $this->fetchRow(); + $min_value = $row[0]['min_value']; + + if ($min_value == 1) $min_value = 0; + $offset = -($currval - $min_value); + + $this->execute("ALTER SEQUENCE {$this->_sequenceMap[$table]} INCREMENT BY $offset MINVALUE $min_value"); + $this->execute("SELECT {$this->_sequenceMap[$table]}.nextval FROM dual"); + $this->execute("ALTER SEQUENCE {$this->_sequenceMap[$table]} INCREMENT BY 1"); + } else { + //$this->execute("DROP SEQUENCE {$this->_sequenceMap[$table]}"); + } + return true; + } + +/** + * Enables, disables, and lists table constraints + * + * Note: This method could have been written using a subselect for each table, + * however the effort Oracle expends to run the constraint introspection is very high. + * Therefore, this method caches the result once and loops through the arrays to find + * what it needs. It reduced my query time by 50%. YMMV. + * + * @param string $action + * @param string $table + * @return mixed boolean true or array of constraints + */ + function constraint($action, $table) { + if (empty($table)) { + trigger_error(__('Must specify table to operate on constraints', true)); + } + + $table = strtoupper($table); + + if (empty($this->_keyConstraints)) { + $sql = "SELECT + table_name, + c.constraint_name + FROM all_cons_columns cc + LEFT JOIN all_indexes i ON (cc.constraint_name = i.index_name) + LEFT JOIN all_constraints c ON(c.constraint_name = cc.constraint_name)"; + $this->execute($sql); + while ($row = $this->fetchRow()) { + $this->_keyConstraints[] = array($row[0]['table_name'], $row['c']['constraint_name']); + } + } + + $relatedKeys = array(); + foreach ($this->_keyConstraints as $c) { + if ($c[0] == $table) { + $relatedKeys[] = $c[1]; + } + } + + if (empty($this->_constraints)) { + $sql = "SELECT + table_name, + constraint_name, + r_constraint_name + FROM + all_constraints"; + $this->execute($sql); + while ($row = $this->fetchRow()) { + $this->_constraints[] = $row[0]; + } + } + + $constraints = array(); + foreach ($this->_constraints as $c) { + if (in_array($c['r_constraint_name'], $relatedKeys)) { + $constraints[] = array($c['table_name'], $c['constraint_name']); + } + } + + foreach ($constraints as $c) { + list($table, $constraint) = $c; + switch ($action) { + case 'enable': + $this->execute("ALTER TABLE $table ENABLE CONSTRAINT $constraint"); + break; + case 'disable': + $this->execute("ALTER TABLE $table DISABLE CONSTRAINT $constraint"); + break; + case 'list': + return $constraints; + break; + default: + trigger_error(__('DboOracle::constraint() accepts only enable, disable, or list', true)); + } + } + return true; + } + +/** + * Returns an array of the indexes in given table name. + * + * @param string $model Name of model to inspect + * @return array Fields in table. Keys are column and unique + */ + function index($model) { + $index = array(); + $table = $this->fullTableName($model, false); + if ($table) { + $indexes = $this->query('SELECT + cc.table_name, + cc.column_name, + cc.constraint_name, + c.constraint_type, + i.index_name, + i.uniqueness + FROM all_cons_columns cc + LEFT JOIN all_indexes i ON(cc.constraint_name = i.index_name) + LEFT JOIN all_constraints c ON(c.constraint_name = cc.constraint_name) + WHERE cc.table_name = \'' . strtoupper($table) .'\''); + foreach ($indexes as $i => $idx) { + if ($idx['c']['constraint_type'] == 'P') { + $key = 'PRIMARY'; + } else { + continue; + } + if (!isset($index[$key])) { + $index[$key]['column'] = strtolower($idx['cc']['column_name']); + $index[$key]['unique'] = intval($idx['i']['uniqueness'] == 'UNIQUE'); + } else { + if (!is_array($index[$key]['column'])) { + $col[] = $index[$key]['column']; + } + $col[] = strtolower($idx['cc']['column_name']); + $index[$key]['column'] = $col; + } + } + } + return $index; + } + +/** + * Generate a Oracle Alter Table syntax for the given Schema comparison + * + * @param unknown_type $schema + * @return unknown + */ + function alterSchema($compare, $table = null) { + if (!is_array($compare)) { + return false; + } + $out = ''; + $colList = array(); + foreach($compare as $curTable => $types) { + if (!$table || $table == $curTable) { + $out .= 'ALTER TABLE ' . $this->fullTableName($curTable) . " \n"; + foreach($types as $type => $column) { + switch($type) { + case 'add': + foreach($column as $field => $col) { + $col['name'] = $field; + $alter = 'ADD '.$this->buildColumn($col); + if (isset($col['after'])) { + $alter .= ' AFTER '. $this->name($col['after']); + } + $colList[] = $alter; + } + break; + case 'drop': + foreach($column as $field => $col) { + $col['name'] = $field; + $colList[] = 'DROP '.$this->name($field); + } + break; + case 'change': + foreach($column as $field => $col) { + if (!isset($col['name'])) { + $col['name'] = $field; + } + $colList[] = 'CHANGE '. $this->name($field).' '.$this->buildColumn($col); + } + break; + } + } + $out .= "\t" . implode(",\n\t", $colList) . ";\n\n"; + } + } + return $out; + } + +/** + * This method should quote Oracle identifiers. Well it doesn't. + * It would break all scaffolding and all of Cake's default assumptions. + * + * @param unknown_type $var + * @return unknown + * @access public + */ + function name($name) { + if (strpos($name, '.') !== false && strpos($name, '"') === false) { + list($model, $field) = explode('.', $name); + if ($field[0] == "_") { + $name = "$model.\"$field\""; + } + } else { + if ($name[0] == "_") { + $name = "\"$name\""; + } + } + return $name; + } + +/** + * Begin a transaction + * + * @param unknown_type $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions). + */ + function begin() { + $this->__transactionStarted = true; + return true; + } + +/** + * Rollback a transaction + * + * @param unknown_type $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions, + * or a transaction has not started). + */ + function rollback() { + return ocirollback($this->connection); + } + +/** + * Commit a transaction + * + * @param unknown_type $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions, + * or a transaction has not started). + */ + function commit() { + $this->__transactionStarted = false; + return ocicommit($this->connection); + } + +/** + * Converts database-layer column types to basic types + * + * @param string $real Real database-layer column type (i.e. "varchar(255)") + * @return string Abstract column type (i.e. "string") + * @access public + */ + function column($real) { + if (is_array($real)) { + $col = $real['name']; + + if (isset($real['limit'])) { + $col .= '('.$real['limit'].')'; + } + return $col; + } else { + $real = strtolower($real); + } + $col = str_replace(')', '', $real); + $limit = null; + if (strpos($col, '(') !== false) { + list($col, $limit) = explode('(', $col); + } + + if (in_array($col, array('date', 'timestamp'))) { + return $col; + } + if (strpos($col, 'number') !== false) { + return 'integer'; + } + if (strpos($col, 'integer') !== false) { + return 'integer'; + } + if (strpos($col, 'char') !== false) { + return 'string'; + } + if (strpos($col, 'text') !== false) { + return 'text'; + } + if (strpos($col, 'blob') !== false) { + return 'binary'; + } + if (in_array($col, array('float', 'double', 'decimal'))) { + return 'float'; + } + if ($col == 'boolean') { + return $col; + } + return 'text'; + } + +/** + * Returns a quoted and escaped string of $data for use in an SQL statement. + * + * @param string $data String to be prepared for use in an SQL statement + * @return string Quoted and escaped + * @access public + */ + function value($data, $column = null, $safe = false) { + $parent = parent::value($data, $column, $safe); + + if ($parent != null) { + return $parent; + } + + if ($data === null) { + return 'NULL'; + } + + if ($data === '') { + return "''"; + } + + switch($column) { + case 'date': + $data = date('Y-m-d H:i:s', strtotime($data)); + $data = "TO_DATE('$data', 'YYYY-MM-DD HH24:MI:SS')"; + break; + case 'integer' : + case 'float' : + case null : + if (is_numeric($data)) { + break; + } + default: + $data = str_replace("'", "''", $data); + $data = "'$data'"; + break; + } + return $data; + } + +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param string + * @return integer + * @access public + */ + function lastInsertId($source) { + $sequence = $this->_sequenceMap[$source]; + $sql = "SELECT $sequence.currval FROM dual"; + + if (!$this->execute($sql)) { + return false; + } + + while($row = $this->fetchRow()) { + return $row[$sequence]['currval']; + } + return false; + } + +/** + * Returns a formatted error message from previous database operation. + * + * @return string Error message with error number + * @access public + */ + function lastError() { + return $this->_error; + } + +/** + * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false. + * + * @return int Number of affected rows + * @access public + */ + function lastAffected() { + return $this->_statementId ? ocirowcount($this->_statementId): false; + } + +/** + * Renders a final SQL statement by putting together the component parts in the correct order + * + * @param string $type + * @param array $data + * @return string + */ + function renderStatement($type, $data) { + extract($data); + $aliases = null; + + switch (strtolower($type)) { + case 'select': + return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}"; + break; + case 'create': + return "INSERT INTO {$table} ({$fields}) VALUES ({$values})"; + break; + case 'update': + if (!empty($alias)) { + $aliases = "{$this->alias}{$alias} "; + } + return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}"; + break; + case 'delete': + if (!empty($alias)) { + $aliases = "{$this->alias}{$alias} "; + } + return "DELETE FROM {$table} {$aliases}{$conditions}"; + break; + case 'schema': + foreach (array('columns', 'indexes') as $var) { + if (is_array(${$var})) { + ${$var} = "\t" . implode(",\n\t", array_filter(${$var})); + } + } + if (trim($indexes) != '') { + $columns .= ','; + } + return "CREATE TABLE {$table} (\n{$columns}{$indexes})"; + break; + case 'alter': + break; + } + } + +/** + * Enter description here... + * + * @param Model $model + * @param unknown_type $linkModel + * @param string $type Association type + * @param unknown_type $association + * @param unknown_type $assocData + * @param unknown_type $queryData + * @param unknown_type $external + * @param unknown_type $resultSet + * @param integer $recursive Number of levels of association + * @param array $stack + */ + function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) { + if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) { + if (!isset($resultSet) || !is_array($resultSet)) { + if (Configure::read() > 0) { + echo '
' . sprintf(__('SQL Error in model %s:', true), $model->alias) . ' '; + if (isset($this->error) && $this->error != null) { + echo $this->error; + } + echo '
'; + } + return null; + } + $count = count($resultSet); + + if ($type === 'hasMany' && (!isset($assocData['limit']) || empty($assocData['limit']))) { + $ins = $fetch = array(); + for ($i = 0; $i < $count; $i++) { + if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) { + $ins[] = $in; + } + } + + if (!empty($ins)) { + $fetch = array(); + $ins = array_chunk($ins, 1000); + foreach ($ins as $i) { + $q = str_replace('{$__cakeID__$}', implode(', ', $i), $query); + $q = str_replace('= (', 'IN (', $q); + $res = $this->fetchAll($q, $model->cacheQueries, $model->alias); + $fetch = array_merge($fetch, $res); + } + } + + if (!empty($fetch) && is_array($fetch)) { + if ($recursive > 0) { + + foreach ($linkModel->__associations as $type1) { + foreach ($linkModel->{$type1} as $assoc1 => $assocData1) { + $deepModel =& $linkModel->{$assoc1}; + $tmpStack = $stack; + $tmpStack[] = $assoc1; + + if ($linkModel->useDbConfig === $deepModel->useDbConfig) { + $db =& $this; + } else { + $db =& ConnectionManager::getDataSource($deepModel->useDbConfig); + } + $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack); + } + } + } + } + return $this->__mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive); + } elseif ($type === 'hasAndBelongsToMany') { + $ins = $fetch = array(); + for ($i = 0; $i < $count; $i++) { + if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) { + $ins[] = $in; + } + } + + $foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey']; + $joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']); + list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys); + $habtmFieldsCount = count($habtmFields); + + if (!empty($ins)) { + $fetch = array(); + $ins = array_chunk($ins, 1000); + foreach ($ins as $i) { + $q = str_replace('{$__cakeID__$}', '(' .implode(', ', $i) .')', $query); + $q = str_replace('= (', 'IN (', $q); + $q = str_replace(' WHERE 1 = 1', '', $q); + + $q = $this->insertQueryData($q, null, $association, $assocData, $model, $linkModel, $stack); + if ($q != false) { + $res = $this->fetchAll($q, $model->cacheQueries, $model->alias); + $fetch = array_merge($fetch, $res); + } + } + } + } + + for ($i = 0; $i < $count; $i++) { + $row =& $resultSet[$i]; + + if ($type !== 'hasAndBelongsToMany') { + $q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack); + if ($q != false) { + $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias); + } else { + $fetch = null; + } + } + + if (!empty($fetch) && is_array($fetch)) { + if ($recursive > 0) { + + foreach ($linkModel->__associations as $type1) { + foreach ($linkModel->{$type1} as $assoc1 => $assocData1) { + + $deepModel =& $linkModel->{$assoc1}; + if (($type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) { + $tmpStack = $stack; + $tmpStack[] = $assoc1; + if ($linkModel->useDbConfig == $deepModel->useDbConfig) { + $db =& $this; + } else { + $db =& ConnectionManager::getDataSource($deepModel->useDbConfig); + } + $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack); + } + } + } + } + if ($type == 'hasAndBelongsToMany') { + $merge = array(); + foreach($fetch as $j => $data) { + if (isset($data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey]) { + if ($habtmFieldsCount > 2) { + $merge[] = $data; + } else { + $merge[] = Set::diff($data, array($with => $data[$with])); + } + } + } + if (empty($merge) && !isset($row[$association])) { + $row[$association] = $merge; + } else { + $this->__mergeAssociation($resultSet[$i], $merge, $association, $type); + } + } else { + $this->__mergeAssociation($resultSet[$i], $fetch, $association, $type); + } + $resultSet[$i][$association] = $linkModel->afterfind($resultSet[$i][$association]); + + } else { + $tempArray[0][$association] = false; + $this->__mergeAssociation($resultSet[$i], $tempArray, $association, $type); + } + } + } + } + +/** + * Generate a "drop table" statement for the given Schema object + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $table Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + */ + function dropSchema($schema, $table = null) { + if (!is_a($schema, 'CakeSchema')) { + trigger_error(__('Invalid schema object', true), E_USER_WARNING); + return null; + } + $out = ''; + + foreach ($schema->tables as $curTable => $columns) { + if (!$table || $table == $curTable) { + $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . "\n"; + } + } + return $out; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_postgres.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_postgres.php new file mode 100644 index 000000000..9d1558e33 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_postgres.php @@ -0,0 +1,980 @@ + 'BEGIN', + 'commit' => 'COMMIT', + 'rollback' => 'ROLLBACK' + ); + +/** + * Base driver configuration settings. Merged with user settings. + * + * @var array + * @access protected + */ + var $_baseConfig = array( + 'persistent' => true, + 'host' => 'localhost', + 'login' => 'root', + 'password' => '', + 'database' => 'cake', + 'schema' => 'public', + 'port' => 5432, + 'encoding' => '' + ); + + var $columns = array( + 'primary_key' => array('name' => 'serial NOT NULL'), + 'string' => array('name' => 'varchar', 'limit' => '255'), + 'text' => array('name' => 'text'), + 'integer' => array('name' => 'integer', 'formatter' => 'intval'), + 'float' => array('name' => 'float', 'formatter' => 'floatval'), + 'datetime' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'), + 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'), + 'binary' => array('name' => 'bytea'), + 'boolean' => array('name' => 'boolean'), + 'number' => array('name' => 'numeric'), + 'inet' => array('name' => 'inet') + ); + +/** + * Starting Quote + * + * @var string + * @access public + */ + var $startQuote = '"'; + +/** + * Ending Quote + * + * @var string + * @access public + */ + var $endQuote = '"'; + +/** + * Contains mappings of custom auto-increment sequences, if a table uses a sequence name + * other than what is dictated by convention. + * + * @var array + */ + var $_sequenceMap = array(); + +/** + * Connects to the database using options in the given configuration array. + * + * @return True if successfully connected. + */ + function connect() { + $config = $this->config; + $conn = "host='{$config['host']}' port='{$config['port']}' dbname='{$config['database']}' "; + $conn .= "user='{$config['login']}' password='{$config['password']}'"; + + if (!$config['persistent']) { + $this->connection = pg_connect($conn, PGSQL_CONNECT_FORCE_NEW); + } else { + $this->connection = pg_pconnect($conn); + } + $this->connected = false; + + if ($this->connection) { + $this->connected = true; + $this->_execute("SET search_path TO " . $config['schema']); + } + if (!empty($config['encoding'])) { + $this->setEncoding($config['encoding']); + } + return $this->connected; + } + +/** + * Check if PostgreSQL is enabled/loaded + * + * @return boolean + */ + function enabled() { + return extension_loaded('pgsql'); + } +/** + * Disconnects from database. + * + * @return boolean True if the database could be disconnected, else false + */ + function disconnect() { + if ($this->hasResult()) { + pg_free_result($this->_result); + } + if (is_resource($this->connection)) { + $this->connected = !pg_close($this->connection); + } else { + $this->connected = false; + } + return !$this->connected; + } + +/** + * Executes given SQL statement. + * + * @param string $sql SQL statement + * @return resource Result resource identifier + */ + function _execute($sql) { + return pg_query($this->connection, $sql); + } + +/** + * Returns an array of tables in the database. If there are no tables, an error is raised and the application exits. + * + * @return array Array of tablenames in the database + */ + function listSources() { + $cache = parent::listSources(); + + if ($cache != null) { + return $cache; + } + + $schema = $this->config['schema']; + $sql = "SELECT table_name as name FROM INFORMATION_SCHEMA.tables WHERE table_schema = '{$schema}';"; + $result = $this->fetchAll($sql, false); + + if (!$result) { + return array(); + } else { + $tables = array(); + + foreach ($result as $item) { + $tables[] = $item[0]['name']; + } + + parent::listSources($tables); + return $tables; + } + } + +/** + * Returns an array of the fields in given table name. + * + * @param string $tableName Name of database table to inspect + * @return array Fields in table. Keys are name and type + */ + function &describe(&$model) { + $fields = parent::describe($model); + $table = $this->fullTableName($model, false); + $this->_sequenceMap[$table] = array(); + + if ($fields === null) { + $cols = $this->fetchAll( + "SELECT DISTINCT column_name AS name, data_type AS type, is_nullable AS null, + column_default AS default, ordinal_position AS position, character_maximum_length AS char_length, + character_octet_length AS oct_length FROM information_schema.columns + WHERE table_name = " . $this->value($table) . " AND table_schema = " . + $this->value($this->config['schema'])." ORDER BY position", + false + ); + + foreach ($cols as $column) { + $colKey = array_keys($column); + + if (isset($column[$colKey[0]]) && !isset($column[0])) { + $column[0] = $column[$colKey[0]]; + } + + if (isset($column[0])) { + $c = $column[0]; + + if (!empty($c['char_length'])) { + $length = intval($c['char_length']); + } elseif (!empty($c['oct_length'])) { + if ($c['type'] == 'character varying') { + $length = null; + $c['type'] = 'text'; + } else { + $length = intval($c['oct_length']); + } + } else { + $length = $this->length($c['type']); + } + $fields[$c['name']] = array( + 'type' => $this->column($c['type']), + 'null' => ($c['null'] == 'NO' ? false : true), + 'default' => preg_replace( + "/^'(.*)'$/", + "$1", + preg_replace('/::.*/', '', $c['default']) + ), + 'length' => $length + ); + if ($c['name'] == $model->primaryKey) { + $fields[$c['name']]['key'] = 'primary'; + if ($fields[$c['name']]['type'] !== 'string') { + $fields[$c['name']]['length'] = 11; + } + } + if ( + $fields[$c['name']]['default'] == 'NULL' || + preg_match('/nextval\([\'"]?([\w.]+)/', $c['default'], $seq) + ) { + $fields[$c['name']]['default'] = null; + if (!empty($seq) && isset($seq[1])) { + $this->_sequenceMap[$table][$c['name']] = $seq[1]; + } + } + if ($fields[$c['name']]['type'] == 'boolean' && !empty($fields[$c['name']]['default'])) { + $fields[$c['name']]['default'] = constant($fields[$c['name']]['default']); + } + } + } + $this->__cacheDescription($table, $fields); + } + if (isset($model->sequence)) { + $this->_sequenceMap[$table][$model->primaryKey] = $model->sequence; + } + return $fields; + } + +/** + * Returns a quoted and escaped string of $data for use in an SQL statement. + * + * @param string $data String to be prepared for use in an SQL statement + * @param string $column The column into which this data will be inserted + * @param boolean $read Value to be used in READ or WRITE context + * @return string Quoted and escaped + * @todo Add logic that formats/escapes data based on column type + */ + function value($data, $column = null, $read = true) { + + $parent = parent::value($data, $column); + if ($parent != null) { + return $parent; + } + + if ($data === null || (is_array($data) && empty($data))) { + return 'NULL'; + } + if (empty($column)) { + $column = $this->introspectType($data); + } + + switch($column) { + case 'binary': + $data = pg_escape_bytea($data); + break; + case 'boolean': + if ($data === true || $data === 't' || $data === 'true') { + return 'TRUE'; + } elseif ($data === false || $data === 'f' || $data === 'false') { + return 'FALSE'; + } + return (!empty($data) ? 'TRUE' : 'FALSE'); + break; + case 'float': + if (is_float($data)) { + $data = sprintf('%F', $data); + } + case 'inet': + case 'integer': + case 'date': + case 'datetime': + case 'timestamp': + case 'time': + if ($data === '') { + return $read ? 'NULL' : 'DEFAULT'; + } + default: + $data = pg_escape_string($data); + break; + } + return "'" . $data . "'"; + } + +/** + * Returns a formatted error message from previous database operation. + * + * @return string Error message + */ + function lastError() { + $error = pg_last_error($this->connection); + return ($error) ? $error : null; + } + +/** + * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false. + * + * @return integer Number of affected rows + */ + function lastAffected() { + return ($this->_result) ? pg_affected_rows($this->_result) : false; + } + +/** + * Returns number of rows in previous resultset. If no previous resultset exists, + * this returns false. + * + * @return integer Number of rows in resultset + */ + function lastNumRows() { + return ($this->_result) ? pg_num_rows($this->_result) : false; + } + +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param string $source Name of the database table + * @param string $field Name of the ID database field. Defaults to "id" + * @return integer + */ + function lastInsertId($source, $field = 'id') { + $seq = $this->getSequence($source, $field); + $data = $this->fetchRow("SELECT currval('{$seq}') as max"); + return $data[0]['max']; + } + +/** + * Gets the associated sequence for the given table/field + * + * @param mixed $table Either a full table name (with prefix) as a string, or a model object + * @param string $field Name of the ID database field. Defaults to "id" + * @return string The associated sequence name from the sequence map, defaults to "{$table}_{$field}_seq" + */ + function getSequence($table, $field = 'id') { + if (is_object($table)) { + $table = $this->fullTableName($table, false); + } + if (isset($this->_sequenceMap[$table]) && isset($this->_sequenceMap[$table][$field])) { + return $this->_sequenceMap[$table][$field]; + } else { + return "{$table}_{$field}_seq"; + } + } + +/** + * Deletes all the records in a table and drops all associated auto-increment sequences + * + * @param mixed $table A string or model class representing the table to be truncated + * @param integer $reset If -1, sequences are dropped, if 0 (default), sequences are reset, + * and if 1, sequences are not modified + * @return boolean SQL TRUNCATE TABLE statement, false if not applicable. + * @access public + */ + function truncate($table, $reset = 0) { + if (parent::truncate($table)) { + $table = $this->fullTableName($table, false); + if (isset($this->_sequenceMap[$table]) && $reset !== 1) { + foreach ($this->_sequenceMap[$table] as $field => $sequence) { + if ($reset === 0) { + $this->execute("ALTER SEQUENCE \"{$sequence}\" RESTART WITH 1"); + } elseif ($reset === -1) { + $this->execute("DROP SEQUENCE IF EXISTS \"{$sequence}\""); + } + } + } + return true; + } + return false; + } + +/** + * Prepares field names to be quoted by parent + * + * @param string $data + * @return string SQL field + */ + function name($data) { + if (is_string($data)) { + $data = str_replace('"__"', '__', $data); + } + return parent::name($data); + } + +/** + * Generates the fields list of an SQL query. + * + * @param Model $model + * @param string $alias Alias tablename + * @param mixed $fields + * @return array + */ + function fields(&$model, $alias = null, $fields = array(), $quote = true) { + if (empty($alias)) { + $alias = $model->alias; + } + $fields = parent::fields($model, $alias, $fields, false); + + if (!$quote) { + return $fields; + } + $count = count($fields); + + if ($count >= 1 && strpos($fields[0], 'COUNT(*)') === false) { + $result = array(); + for ($i = 0; $i < $count; $i++) { + if (!preg_match('/^.+\\(.*\\)/', $fields[$i]) && !preg_match('/\s+AS\s+/', $fields[$i])) { + if (substr($fields[$i], -1) == '*') { + if (strpos($fields[$i], '.') !== false && $fields[$i] != $alias . '.*') { + $build = explode('.', $fields[$i]); + $AssociatedModel = $model->{$build[0]}; + } else { + $AssociatedModel = $model; + } + + $_fields = $this->fields($AssociatedModel, $AssociatedModel->alias, array_keys($AssociatedModel->schema())); + $result = array_merge($result, $_fields); + continue; + } + + $prepend = ''; + if (strpos($fields[$i], 'DISTINCT') !== false) { + $prepend = 'DISTINCT '; + $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i])); + } + + if (strrpos($fields[$i], '.') === false) { + $fields[$i] = $prepend . $this->name($alias) . '.' . $this->name($fields[$i]) . ' AS ' . $this->name($alias . '__' . $fields[$i]); + } else { + $build = explode('.', $fields[$i]); + $fields[$i] = $prepend . $this->name($build[0]) . '.' . $this->name($build[1]) . ' AS ' . $this->name($build[0] . '__' . $build[1]); + } + } else { + $fields[$i] = preg_replace_callback('/\(([\s\.\w]+)\)/', array(&$this, '__quoteFunctionField'), $fields[$i]); + } + $result[] = $fields[$i]; + } + return $result; + } + return $fields; + } + +/** + * Auxiliary function to quote matched `(Model.fields)` from a preg_replace_callback call + * + * @param string matched string + * @return string quoted strig + * @access private + */ + function __quoteFunctionField($match) { + $prepend = ''; + if (strpos($match[1], 'DISTINCT') !== false) { + $prepend = 'DISTINCT '; + $match[1] = trim(str_replace('DISTINCT', '', $match[1])); + } + if (strpos($match[1], '.') === false) { + $match[1] = $this->name($match[1]); + } else { + $parts = explode('.', $match[1]); + if (!Set::numeric($parts)) { + $match[1] = $this->name($match[1]); + } + } + return '(' . $prepend .$match[1] . ')'; + } + +/** + * Returns an array of the indexes in given datasource name. + * + * @param string $model Name of model to inspect + * @return array Fields in table. Keys are column and unique + */ + function index($model) { + $index = array(); + $table = $this->fullTableName($model, false); + if ($table) { + $indexes = $this->query("SELECT c2.relname, i.indisprimary, i.indisunique, i.indisclustered, i.indisvalid, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) as statement, c2.reltablespace + FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i + WHERE c.oid = ( + SELECT c.oid + FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname ~ '^(" . $table . ")$' + AND pg_catalog.pg_table_is_visible(c.oid) + AND n.nspname ~ '^(" . $this->config['schema'] . ")$' + ) + AND c.oid = i.indrelid AND i.indexrelid = c2.oid + ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname", false); + foreach ($indexes as $i => $info) { + $key = array_pop($info); + if ($key['indisprimary']) { + $key['relname'] = 'PRIMARY'; + } + $col = array(); + preg_match('/\(([^\)]+)\)/', $key['statement'], $indexColumns); + $parsedColumn = $indexColumns[1]; + if (strpos($indexColumns[1], ',') !== false) { + $parsedColumn = explode(', ', $indexColumns[1]); + } + $index[$key['relname']]['unique'] = $key['indisunique']; + $index[$key['relname']]['column'] = $parsedColumn; + } + } + return $index; + } + +/** + * Alter the Schema of a table. + * + * @param array $compare Results of CakeSchema::compare() + * @param string $table name of the table + * @access public + * @return array + */ + function alterSchema($compare, $table = null) { + if (!is_array($compare)) { + return false; + } + $out = ''; + $colList = array(); + foreach ($compare as $curTable => $types) { + $indexes = $colList = array(); + if (!$table || $table == $curTable) { + $out .= 'ALTER TABLE ' . $this->fullTableName($curTable) . " \n"; + foreach ($types as $type => $column) { + if (isset($column['indexes'])) { + $indexes[$type] = $column['indexes']; + unset($column['indexes']); + } + switch ($type) { + case 'add': + foreach ($column as $field => $col) { + $col['name'] = $field; + $alter = 'ADD COLUMN '.$this->buildColumn($col); + if (isset($col['after'])) { + $alter .= ' AFTER '. $this->name($col['after']); + } + $colList[] = $alter; + } + break; + case 'drop': + foreach ($column as $field => $col) { + $col['name'] = $field; + $colList[] = 'DROP COLUMN '.$this->name($field); + } + break; + case 'change': + foreach ($column as $field => $col) { + if (!isset($col['name'])) { + $col['name'] = $field; + } + $fieldName = $this->name($field); + + $default = isset($col['default']) ? $col['default'] : null; + $nullable = isset($col['null']) ? $col['null'] : null; + unset($col['default'], $col['null']); + $colList[] = 'ALTER COLUMN '. $fieldName .' TYPE ' . str_replace($fieldName, '', $this->buildColumn($col)); + + if (isset($nullable)) { + $nullable = ($nullable) ? 'DROP NOT NULL' : 'SET NOT NULL'; + $colList[] = 'ALTER COLUMN '. $fieldName .' ' . $nullable; + } + + if (isset($default)) { + $colList[] = 'ALTER COLUMN '. $fieldName .' SET DEFAULT ' . $this->value($default, $col['type']); + } else { + $colList[] = 'ALTER COLUMN '. $fieldName .' DROP DEFAULT'; + } + + } + break; + } + } + if (isset($indexes['drop']['PRIMARY'])) { + $colList[] = 'DROP CONSTRAINT ' . $curTable . '_pkey'; + } + if (isset($indexes['add']['PRIMARY'])) { + $cols = $indexes['add']['PRIMARY']['column']; + if (is_array($cols)) { + $cols = implode(', ', $cols); + } + $colList[] = 'ADD PRIMARY KEY (' . $cols . ')'; + } + + if (!empty($colList)) { + $out .= "\t" . implode(",\n\t", $colList) . ";\n\n"; + } else { + $out = ''; + } + $out .= implode(";\n\t", $this->_alterIndexes($curTable, $indexes)); + } + } + return $out; + } + +/** + * Generate PostgreSQL index alteration statements for a table. + * + * @param string $table Table to alter indexes for + * @param array $new Indexes to add and drop + * @return array Index alteration statements + */ + function _alterIndexes($table, $indexes) { + $alter = array(); + if (isset($indexes['drop'])) { + foreach($indexes['drop'] as $name => $value) { + $out = 'DROP '; + if ($name == 'PRIMARY') { + continue; + } else { + $out .= 'INDEX ' . $name; + } + $alter[] = $out; + } + } + if (isset($indexes['add'])) { + foreach ($indexes['add'] as $name => $value) { + $out = 'CREATE '; + if ($name == 'PRIMARY') { + continue; + } else { + if (!empty($value['unique'])) { + $out .= 'UNIQUE '; + } + $out .= 'INDEX '; + } + if (is_array($value['column'])) { + $out .= $name . ' ON ' . $table . ' (' . implode(', ', array_map(array(&$this, 'name'), $value['column'])) . ')'; + } else { + $out .= $name . ' ON ' . $table . ' (' . $this->name($value['column']) . ')'; + } + $alter[] = $out; + } + } + return $alter; + } + +/** + * Returns a limit statement in the correct format for the particular database. + * + * @param integer $limit Limit of results returned + * @param integer $offset Offset from which to start results + * @return string SQL limit/offset statement + */ + function limit($limit, $offset = null) { + if ($limit) { + $rt = ''; + if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) { + $rt = ' LIMIT'; + } + + $rt .= ' ' . $limit; + if ($offset) { + $rt .= ' OFFSET ' . $offset; + } + + return $rt; + } + return null; + } + +/** + * Converts database-layer column types to basic types + * + * @param string $real Real database-layer column type (i.e. "varchar(255)") + * @return string Abstract column type (i.e. "string") + */ + function column($real) { + if (is_array($real)) { + $col = $real['name']; + if (isset($real['limit'])) { + $col .= '(' . $real['limit'] . ')'; + } + return $col; + } + + $col = str_replace(')', '', $real); + $limit = null; + + if (strpos($col, '(') !== false) { + list($col, $limit) = explode('(', $col); + } + + $floats = array( + 'float', 'float4', 'float8', 'double', 'double precision', 'decimal', 'real', 'numeric' + ); + + switch (true) { + case (in_array($col, array('date', 'time', 'inet', 'boolean'))): + return $col; + case (strpos($col, 'timestamp') !== false): + return 'datetime'; + case (strpos($col, 'time') === 0): + return 'time'; + case (strpos($col, 'int') !== false && $col != 'interval'): + return 'integer'; + case (strpos($col, 'char') !== false || $col == 'uuid'): + return 'string'; + case (strpos($col, 'text') !== false): + return 'text'; + case (strpos($col, 'bytea') !== false): + return 'binary'; + case (in_array($col, $floats)): + return 'float'; + default: + return 'text'; + break; + } + } + +/** + * Gets the length of a database-native column description, or null if no length + * + * @param string $real Real database-layer column type (i.e. "varchar(255)") + * @return int An integer representing the length of the column + */ + function length($real) { + $col = str_replace(array(')', 'unsigned'), '', $real); + $limit = null; + + if (strpos($col, '(') !== false) { + list($col, $limit) = explode('(', $col); + } + if ($col == 'uuid') { + return 36; + } + if ($limit != null) { + return intval($limit); + } + return null; + } + +/** + * Enter description here... + * + * @param unknown_type $results + */ + function resultSet(&$results) { + $this->results =& $results; + $this->map = array(); + $num_fields = pg_num_fields($results); + $index = 0; + $j = 0; + + while ($j < $num_fields) { + $columnName = pg_field_name($results, $j); + + if (strpos($columnName, '__')) { + $parts = explode('__', $columnName); + $this->map[$index++] = array($parts[0], $parts[1]); + } else { + $this->map[$index++] = array(0, $columnName); + } + $j++; + } + } + +/** + * Fetches the next row from the current result set + * + * @return unknown + */ + function fetchResult() { + if ($row = pg_fetch_row($this->results)) { + $resultRow = array(); + + foreach ($row as $index => $field) { + list($table, $column) = $this->map[$index]; + $type = pg_field_type($this->results, $index); + + switch ($type) { + case 'bool': + $resultRow[$table][$column] = $this->boolean($row[$index], false); + break; + case 'binary': + case 'bytea': + $resultRow[$table][$column] = pg_unescape_bytea($row[$index]); + break; + default: + $resultRow[$table][$column] = $row[$index]; + break; + } + } + return $resultRow; + } else { + return false; + } + } + +/** + * Translates between PHP boolean values and PostgreSQL boolean values + * + * @param mixed $data Value to be translated + * @param boolean $quote True to quote value, false otherwise + * @return mixed Converted boolean value + */ + function boolean($data, $quote = true) { + switch (true) { + case ($data === true || $data === false): + return $data; + case ($data === 't' || $data === 'f'): + return ($data === 't'); + case ($data === 'true' || $data === 'false'): + return ($data === 'true'); + case ($data === 'TRUE' || $data === 'FALSE'): + return ($data === 'TRUE'); + default: + return (bool)$data; + break; + } + } + +/** + * Sets the database encoding + * + * @param mixed $enc Database encoding + * @return boolean True on success, false on failure + */ + function setEncoding($enc) { + return pg_set_client_encoding($this->connection, $enc) == 0; + } + +/** + * Gets the database encoding + * + * @return string The database encoding + */ + function getEncoding() { + return pg_client_encoding($this->connection); + } + +/** + * Generate a Postgres-native column schema string + * + * @param array $column An array structured like the following: + * array('name'=>'value', 'type'=>'value'[, options]), + * where options can be 'default', 'length', or 'key'. + * @return string + */ + function buildColumn($column) { + $col = $this->columns[$column['type']]; + if (!isset($col['length']) && !isset($col['limit'])) { + unset($column['length']); + } + $out = preg_replace('/integer\([0-9]+\)/', 'integer', parent::buildColumn($column)); + $out = str_replace('integer serial', 'serial', $out); + if (strpos($out, 'timestamp DEFAULT')) { + if (isset($column['null']) && $column['null']) { + $out = str_replace('DEFAULT NULL', '', $out); + } else { + $out = str_replace('DEFAULT NOT NULL', '', $out); + } + } + if (strpos($out, 'DEFAULT DEFAULT')) { + if (isset($column['null']) && $column['null']) { + $out = str_replace('DEFAULT DEFAULT', 'DEFAULT NULL', $out); + } elseif (in_array($column['type'], array('integer', 'float'))) { + $out = str_replace('DEFAULT DEFAULT', 'DEFAULT 0', $out); + } elseif ($column['type'] == 'boolean') { + $out = str_replace('DEFAULT DEFAULT', 'DEFAULT FALSE', $out); + } + } + return $out; + } + +/** + * Format indexes for create table + * + * @param array $indexes + * @param string $table + * @return string + */ + function buildIndex($indexes, $table = null) { + $join = array(); + if (!is_array($indexes)) { + return array(); + } + foreach ($indexes as $name => $value) { + if ($name == 'PRIMARY') { + $out = 'PRIMARY KEY (' . $this->name($value['column']) . ')'; + } else { + $out = 'CREATE '; + if (!empty($value['unique'])) { + $out .= 'UNIQUE '; + } + if (is_array($value['column'])) { + $value['column'] = implode(', ', array_map(array(&$this, 'name'), $value['column'])); + } else { + $value['column'] = $this->name($value['column']); + } + $out .= "INDEX {$name} ON {$table}({$value['column']});"; + } + $join[] = $out; + } + return $join; + } + +/** + * Overrides DboSource::renderStatement to handle schema generation with Postgres-style indexes + * + * @param string $type + * @param array $data + * @return string + */ + function renderStatement($type, $data) { + switch (strtolower($type)) { + case 'schema': + extract($data); + + foreach ($indexes as $i => $index) { + if (preg_match('/PRIMARY KEY/', $index)) { + unset($indexes[$i]); + $columns[] = $index; + break; + } + } + $join = array('columns' => ",\n\t", 'indexes' => "\n"); + + foreach (array('columns', 'indexes') as $var) { + if (is_array(${$var})) { + ${$var} = implode($join[$var], array_filter(${$var})); + } + } + return "CREATE TABLE {$table} (\n\t{$columns}\n);\n{$indexes}"; + break; + default: + return parent::renderStatement($type, $data); + break; + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_sqlite.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_sqlite.php new file mode 100644 index 000000000..efc660f33 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo/dbo_sqlite.php @@ -0,0 +1,623 @@ + true, + 'database' => null + ); + +/** + * Index of basic SQL commands + * + * @var array + * @access protected + */ + var $_commands = array( + 'begin' => 'BEGIN TRANSACTION', + 'commit' => 'COMMIT TRANSACTION', + 'rollback' => 'ROLLBACK TRANSACTION' + ); + +/** + * SQLite column definition + * + * @var array + */ + var $columns = array( + 'primary_key' => array('name' => 'integer primary key'), + 'string' => array('name' => 'varchar', 'limit' => '255'), + 'text' => array('name' => 'text'), + 'integer' => array('name' => 'integer', 'limit' => 11, 'formatter' => 'intval'), + 'float' => array('name' => 'float', 'formatter' => 'floatval'), + 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'), + 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'), + 'binary' => array('name' => 'blob'), + 'boolean' => array('name' => 'boolean') + ); + +/** + * List of engine specific additional field parameters used on table creating + * + * @var array + * @access public + */ + var $fieldParameters = array( + 'collate' => array( + 'value' => 'COLLATE', + 'quote' => false, + 'join' => ' ', + 'column' => 'Collate', + 'position' => 'afterDefault', + 'options' => array( + 'BINARY', 'NOCASE', 'RTRIM' + ) + ), + ); + +/** + * Connects to the database using config['database'] as a filename. + * + * @param array $config Configuration array for connecting + * @return mixed + */ + function connect() { + $config = $this->config; + + if (!$config['persistent']) { + $this->connection = sqlite_open($config['database']); + } else { + $this->connection = sqlite_popen($config['database']); + } + $this->connected = is_resource($this->connection); + + if ($this->connected) { + $this->_execute('PRAGMA count_changes = 1;'); + } + return $this->connected; + } + +/** + * Check that SQLite is enabled/installed + * + * @return boolean + */ + function enabled() { + return extension_loaded('sqlite'); + } +/** + * Disconnects from database. + * + * @return boolean True if the database could be disconnected, else false + */ + function disconnect() { + @sqlite_close($this->connection); + $this->connected = false; + return $this->connected; + } + +/** + * Executes given SQL statement. + * + * @param string $sql SQL statement + * @return resource Result resource identifier + */ + function _execute($sql) { + $result = sqlite_query($this->connection, $sql); + + if (preg_match('/^(INSERT|UPDATE|DELETE)/', $sql)) { + $this->resultSet($result); + list($this->_queryStats) = $this->fetchResult(); + } + return $result; + } + +/** + * Overrides DboSource::execute() to correctly handle query statistics + * + * @param string $sql + * @return unknown + */ + function execute($sql) { + $result = parent::execute($sql); + $this->_queryStats = array(); + return $result; + } + +/** + * Returns an array of tables in the database. If there are no tables, an error is raised and the application exits. + * + * @return array Array of tablenames in the database + */ + function listSources() { + $cache = parent::listSources(); + + if ($cache != null) { + return $cache; + } + $result = $this->fetchAll("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;", false); + + if (empty($result)) { + return array(); + } else { + $tables = array(); + foreach ($result as $table) { + $tables[] = $table[0]['name']; + } + parent::listSources($tables); + return $tables; + } + return array(); + } + +/** + * Returns an array of the fields in given table name. + * + * @param string $tableName Name of database table to inspect + * @return array Fields in table. Keys are name and type + */ + function describe(&$model) { + $cache = parent::describe($model); + if ($cache != null) { + return $cache; + } + $fields = array(); + $result = $this->fetchAll('PRAGMA table_info(' . $this->fullTableName($model) . ')'); + + foreach ($result as $column) { + $fields[$column[0]['name']] = array( + 'type' => $this->column($column[0]['type']), + 'null' => !$column[0]['notnull'], + 'default' => $column[0]['dflt_value'], + 'length' => $this->length($column[0]['type']) + ); + if ($column[0]['pk'] == 1) { + $colLength = $this->length($column[0]['type']); + $fields[$column[0]['name']] = array( + 'type' => $fields[$column[0]['name']]['type'], + 'null' => false, + 'default' => $column[0]['dflt_value'], + 'key' => $this->index['PRI'], + 'length'=> ($colLength != null) ? $colLength : 11 + ); + } + } + + $this->__cacheDescription($model->tablePrefix . $model->table, $fields); + return $fields; + } + +/** + * Returns a quoted and escaped string of $data for use in an SQL statement. + * + * @param string $data String to be prepared for use in an SQL statement + * @return string Quoted and escaped + */ + function value($data, $column = null, $safe = false) { + $parent = parent::value($data, $column, $safe); + + if ($parent != null) { + return $parent; + } + if ($data === null) { + return 'NULL'; + } + if ($data === '' && $column !== 'integer' && $column !== 'float' && $column !== 'boolean') { + return "''"; + } + switch ($column) { + case 'boolean': + $data = $this->boolean((bool)$data); + break; + case 'integer': + case 'float': + if ($data === '') { + return 'NULL'; + } + default: + $data = sqlite_escape_string($data); + break; + } + return "'" . $data . "'"; + } + +/** + * Generates and executes an SQL UPDATE statement for given model, fields, and values. + * + * @param Model $model + * @param array $fields + * @param array $values + * @param mixed $conditions + * @return array + */ + function update(&$model, $fields = array(), $values = null, $conditions = null) { + if (empty($values) && !empty($fields)) { + foreach ($fields as $field => $value) { + if (strpos($field, $model->alias . '.') !== false) { + unset($fields[$field]); + $field = str_replace($model->alias . '.', "", $field); + $field = str_replace($model->alias . '.', "", $field); + $fields[$field] = $value; + } + } + } + $result = parent::update($model, $fields, $values, $conditions); + return $result; + } + +/** + * Deletes all the records in a table and resets the count of the auto-incrementing + * primary key, where applicable. + * + * @param mixed $table A string or model class representing the table to be truncated + * @return boolean SQL TRUNCATE TABLE statement, false if not applicable. + * @access public + */ + function truncate($table) { + return $this->execute('DELETE From ' . $this->fullTableName($table)); + } + +/** + * Returns a formatted error message from previous database operation. + * + * @return string Error message + */ + function lastError() { + $error = sqlite_last_error($this->connection); + if ($error) { + return $error.': '.sqlite_error_string($error); + } + return null; + } + +/** + * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false. + * + * @return integer Number of affected rows + */ + function lastAffected() { + if (!empty($this->_queryStats)) { + foreach (array('rows inserted', 'rows updated', 'rows deleted') as $key) { + if (array_key_exists($key, $this->_queryStats)) { + return $this->_queryStats[$key]; + } + } + } + return false; + } + +/** + * Returns number of rows in previous resultset. If no previous resultset exists, + * this returns false. + * + * @return integer Number of rows in resultset + */ + function lastNumRows() { + if ($this->hasResult()) { + sqlite_num_rows($this->_result); + } + return false; + } + +/** + * Returns the ID generated from the previous INSERT operation. + * + * @return int + */ + function lastInsertId() { + return sqlite_last_insert_rowid($this->connection); + } + +/** + * Converts database-layer column types to basic types + * + * @param string $real Real database-layer column type (i.e. "varchar(255)") + * @return string Abstract column type (i.e. "string") + */ + function column($real) { + if (is_array($real)) { + $col = $real['name']; + if (isset($real['limit'])) { + $col .= '('.$real['limit'].')'; + } + return $col; + } + + $col = strtolower(str_replace(')', '', $real)); + $limit = null; + if (strpos($col, '(') !== false) { + list($col, $limit) = explode('(', $col); + } + + if (in_array($col, array('text', 'integer', 'float', 'boolean', 'timestamp', 'date', 'datetime', 'time'))) { + return $col; + } + if (strpos($col, 'varchar') !== false) { + return 'string'; + } + if (in_array($col, array('blob', 'clob'))) { + return 'binary'; + } + if (strpos($col, 'numeric') !== false) { + return 'float'; + } + return 'text'; + } + +/** + * Enter description here... + * + * @param unknown_type $results + */ + function resultSet(&$results) { + $this->results =& $results; + $this->map = array(); + $fieldCount = sqlite_num_fields($results); + $index = $j = 0; + + while ($j < $fieldCount) { + $columnName = str_replace('"', '', sqlite_field_name($results, $j)); + + if (strpos($columnName, '.')) { + $parts = explode('.', $columnName); + $this->map[$index++] = array($parts[0], $parts[1]); + } else { + $this->map[$index++] = array(0, $columnName); + } + $j++; + } + } + +/** + * Fetches the next row from the current result set + * + * @return unknown + */ + function fetchResult() { + if ($row = sqlite_fetch_array($this->results, SQLITE_ASSOC)) { + $resultRow = array(); + $i = 0; + + foreach ($row as $index => $field) { + if (strpos($index, '.')) { + list($table, $column) = explode('.', str_replace('"', '', $index)); + $resultRow[$table][$column] = $row[$index]; + } else { + $resultRow[0][str_replace('"', '', $index)] = $row[$index]; + } + $i++; + } + return $resultRow; + } else { + return false; + } + } + +/** + * Returns a limit statement in the correct format for the particular database. + * + * @param integer $limit Limit of results returned + * @param integer $offset Offset from which to start results + * @return string SQL limit/offset statement + */ + function limit($limit, $offset = null) { + if ($limit) { + $rt = ''; + if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) { + $rt = ' LIMIT'; + } + $rt .= ' ' . $limit; + if ($offset) { + $rt .= ' OFFSET ' . $offset; + } + return $rt; + } + return null; + } + +/** + * Generate a database-native column schema string + * + * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]), + * where options can be 'default', 'length', or 'key'. + * @return string + */ + function buildColumn($column) { + $name = $type = null; + $column = array_merge(array('null' => true), $column); + extract($column); + + if (empty($name) || empty($type)) { + trigger_error(__('Column name or type not defined in schema', true), E_USER_WARNING); + return null; + } + + if (!isset($this->columns[$type])) { + trigger_error(sprintf(__('Column type %s does not exist', true), $type), E_USER_WARNING); + return null; + } + + $real = $this->columns[$type]; + $out = $this->name($name) . ' ' . $real['name']; + if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') { + return $this->name($name) . ' ' . $this->columns['primary_key']['name']; + } + return parent::buildColumn($column); + } + +/** + * Sets the database encoding + * + * @param string $enc Database encoding + */ + function setEncoding($enc) { + if (!in_array($enc, array("UTF-8", "UTF-16", "UTF-16le", "UTF-16be"))) { + return false; + } + return $this->_execute("PRAGMA encoding = \"{$enc}\"") !== false; + } + +/** + * Gets the database encoding + * + * @return string The database encoding + */ + function getEncoding() { + return $this->fetchRow('PRAGMA encoding'); + } + +/** + * Removes redundant primary key indexes, as they are handled in the column def of the key. + * + * @param array $indexes + * @param string $table + * @return string + */ + function buildIndex($indexes, $table = null) { + $join = array(); + + foreach ($indexes as $name => $value) { + + if ($name == 'PRIMARY') { + continue; + } + $out = 'CREATE '; + + if (!empty($value['unique'])) { + $out .= 'UNIQUE '; + } + if (is_array($value['column'])) { + $value['column'] = implode(', ', array_map(array(&$this, 'name'), $value['column'])); + } else { + $value['column'] = $this->name($value['column']); + } + $out .= "INDEX {$name} ON {$table}({$value['column']});"; + $join[] = $out; + } + return $join; + } + +/** + * Overrides DboSource::index to handle SQLite indexe introspection + * Returns an array of the indexes in given table name. + * + * @param string $model Name of model to inspect + * @return array Fields in table. Keys are column and unique + */ + function index(&$model) { + $index = array(); + $table = $this->fullTableName($model); + if ($table) { + $indexes = $this->query('PRAGMA index_list(' . $table . ')'); + $tableInfo = $this->query('PRAGMA table_info(' . $table . ')'); + foreach ($indexes as $i => $info) { + $key = array_pop($info); + $keyInfo = $this->query('PRAGMA index_info("' . $key['name'] . '")'); + foreach ($keyInfo as $keyCol) { + if (!isset($index[$key['name']])) { + $col = array(); + if (preg_match('/autoindex/', $key['name'])) { + $key['name'] = 'PRIMARY'; + } + $index[$key['name']]['column'] = $keyCol[0]['name']; + $index[$key['name']]['unique'] = intval($key['unique'] == 1); + } else { + if (!is_array($index[$key['name']]['column'])) { + $col[] = $index[$key['name']]['column']; + } + $col[] = $keyCol[0]['name']; + $index[$key['name']]['column'] = $col; + } + } + } + } + return $index; + } + +/** + * Overrides DboSource::renderStatement to handle schema generation with SQLite-style indexes + * + * @param string $type + * @param array $data + * @return string + */ + function renderStatement($type, $data) { + switch (strtolower($type)) { + case 'schema': + extract($data); + + foreach (array('columns', 'indexes') as $var) { + if (is_array(${$var})) { + ${$var} = "\t" . implode(",\n\t", array_filter(${$var})); + } + } + return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}"; + break; + default: + return parent::renderStatement($type, $data); + break; + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo_source.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo_source.php new file mode 100644 index 000000000..a851123e9 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo_source.php @@ -0,0 +1,2925 @@ + 'primary', 'MUL' => 'index', 'UNI' => 'unique'); + +/** + * Database keyword used to assign aliases to identifiers. + * + * @var string + * @access public + */ + var $alias = 'AS '; + +/** + * Caches result from query parsing operations. Cached results for both DboSource::name() and + * DboSource::conditions() will be stored here. Method caching uses `crc32()` which is + * fast but can collisions more easily than other hashing algorithms. If you have problems + * with collisions, set DboSource::$cacheMethods to false. + * + * @var array + * @access public + */ + var $methodCache = array(); + +/** + * Whether or not to cache the results of DboSource::name() and DboSource::conditions() + * into the memory cache. Set to false to disable the use of the memory cache. + * + * @var boolean. + * @access public + */ + var $cacheMethods = true ; + +/** + * Bypass automatic adding of joined fields/associations. + * + * @var boolean + * @access private + */ + var $__bypass = false; + +/** + * The set of valid SQL operations usable in a WHERE statement + * + * @var array + * @access private + */ + var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to'); + +/** + * Index of basic SQL commands + * + * @var array + * @access protected + */ + var $_commands = array( + 'begin' => 'BEGIN', + 'commit' => 'COMMIT', + 'rollback' => 'ROLLBACK' + ); + +/** + * Separator string for virtualField composition + * + * @var string + */ + var $virtualFieldSeparator = '__'; + +/** + * List of table engine specific parameters used on table creating + * + * @var array + * @access public + */ + var $tableParameters = array(); + +/** + * List of engine specific additional field parameters used on table creating + * + * @var array + * @access public + */ + var $fieldParameters = array(); + +/** + * Constructor + * + * @param array $config Array of configuration information for the Datasource. + * @param boolean $autoConnect Whether or not the datasource should automatically connect. + * @access public + */ + function __construct($config = null, $autoConnect = true) { + if (!isset($config['prefix'])) { + $config['prefix'] = ''; + } + parent::__construct($config); + $this->fullDebug = Configure::read() > 1; + if (!$this->enabled()) { + return false; + } + if ($autoConnect) { + return $this->connect(); + } else { + return true; + } + } + +/** + * Reconnects to database server with optional new settings + * + * @param array $config An array defining the new configuration settings + * @return boolean True on success, false on failure + * @access public + */ + function reconnect($config = array()) { + $this->disconnect(); + $this->setConfig($config); + $this->_sources = null; + + return $this->connect(); + } + +/** + * Prepares a value, or an array of values for database queries by quoting and escaping them. + * + * @param mixed $data A value or an array of values to prepare. + * @param string $column The column into which this data will be inserted + * @param boolean $read Value to be used in READ or WRITE context + * @return mixed Prepared value or array of values. + * @access public + */ + function value($data, $column = null, $read = true) { + if (is_array($data) && !empty($data)) { + return array_map( + array(&$this, 'value'), + $data, array_fill(0, count($data), $column), array_fill(0, count($data), $read) + ); + } elseif (is_object($data) && isset($data->type)) { + if ($data->type == 'identifier') { + return $this->name($data->value); + } elseif ($data->type == 'expression') { + return $data->value; + } + } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) { + return $data; + } else { + return null; + } + } + +/** + * Returns an object to represent a database identifier in a query + * + * @param string $identifier + * @return object An object representing a database identifier to be used in a query + * @access public + */ + function identifier($identifier) { + $obj = new stdClass(); + $obj->type = 'identifier'; + $obj->value = $identifier; + return $obj; + } + +/** + * Returns an object to represent a database expression in a query + * + * @param string $expression + * @return object An object representing a database expression to be used in a query + * @access public + */ + function expression($expression) { + $obj = new stdClass(); + $obj->type = 'expression'; + $obj->value = $expression; + return $obj; + } + +/** + * Executes given SQL statement. + * + * @param string $sql SQL statement + * @return boolean + * @access public + */ + function rawQuery($sql) { + $this->took = $this->error = $this->numRows = false; + return $this->execute($sql); + } + +/** + * Queries the database with given SQL statement, and obtains some metadata about the result + * (rows affected, timing, any errors, number of rows in resultset). The query is also logged. + * If Configure::read('debug') is set, the log is shown all the time, else it is only shown on errors. + * + * ### Options + * + * - stats - Collect meta data stats for this query. Stats include time take, rows affected, + * any errors, and number of rows returned. Defaults to `true`. + * - log - Whether or not the query should be logged to the memory log. + * + * @param string $sql + * @param array $options + * @return mixed Resource or object representing the result set, or false on failure + * @access public + */ + function execute($sql, $options = array()) { + $defaults = array('stats' => true, 'log' => $this->fullDebug); + $options = array_merge($defaults, $options); + + $t = getMicrotime(); + $this->_result = $this->_execute($sql); + if ($options['stats']) { + $this->took = round((getMicrotime() - $t) * 1000, 0); + $this->affected = $this->lastAffected(); + $this->error = $this->lastError(); + $this->numRows = $this->lastNumRows(); + } + + if ($options['log']) { + $this->logQuery($sql); + } + + if ($this->error) { + $this->showQuery($sql); + return false; + } + return $this->_result; + } + +/** + * DataSource Query abstraction + * + * @return resource Result resource identifier. + * @access public + */ + function query() { + $args = func_get_args(); + $fields = null; + $order = null; + $limit = null; + $page = null; + $recursive = null; + + if (count($args) == 1) { + return $this->fetchAll($args[0]); + + } elseif (count($args) > 1 && (strpos(strtolower($args[0]), 'findby') === 0 || strpos(strtolower($args[0]), 'findallby') === 0)) { + $params = $args[1]; + + if (strpos(strtolower($args[0]), 'findby') === 0) { + $all = false; + $field = Inflector::underscore(preg_replace('/^findBy/i', '', $args[0])); + } else { + $all = true; + $field = Inflector::underscore(preg_replace('/^findAllBy/i', '', $args[0])); + } + + $or = (strpos($field, '_or_') !== false); + if ($or) { + $field = explode('_or_', $field); + } else { + $field = explode('_and_', $field); + } + $off = count($field) - 1; + + if (isset($params[1 + $off])) { + $fields = $params[1 + $off]; + } + + if (isset($params[2 + $off])) { + $order = $params[2 + $off]; + } + + if (!array_key_exists(0, $params)) { + return false; + } + + $c = 0; + $conditions = array(); + + foreach ($field as $f) { + $conditions[$args[2]->alias . '.' . $f] = $params[$c]; + $c++; + } + + if ($or) { + $conditions = array('OR' => $conditions); + } + + if ($all) { + if (isset($params[3 + $off])) { + $limit = $params[3 + $off]; + } + + if (isset($params[4 + $off])) { + $page = $params[4 + $off]; + } + + if (isset($params[5 + $off])) { + $recursive = $params[5 + $off]; + } + return $args[2]->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive')); + } else { + if (isset($params[3 + $off])) { + $recursive = $params[3 + $off]; + } + return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive')); + } + } else { + if (isset($args[1]) && $args[1] === true) { + return $this->fetchAll($args[0], true); + } else if (isset($args[1]) && !is_array($args[1]) ) { + return $this->fetchAll($args[0], false); + } else if (isset($args[1]) && is_array($args[1])) { + $offset = 0; + if (isset($args[2])) { + $cache = $args[2]; + } else { + $cache = true; + } + $args[1] = array_map(array(&$this, 'value'), $args[1]); + return $this->fetchAll(String::insert($args[0], $args[1]), $cache); + } + } + } + +/** + * Returns a row from current resultset as an array + * + * @return array The fetched row as an array + * @access public + */ + function fetchRow($sql = null) { + if (!empty($sql) && is_string($sql) && strlen($sql) > 5) { + if (!$this->execute($sql)) { + return null; + } + } + + if ($this->hasResult()) { + $this->resultSet($this->_result); + $resultRow = $this->fetchResult(); + if (!empty($resultRow)) { + $this->fetchVirtualField($resultRow); + } + return $resultRow; + } else { + return null; + } + } + +/** + * Returns an array of all result rows for a given SQL query. + * Returns false if no rows matched. + * + * @param string $sql SQL statement + * @param boolean $cache Enables returning/storing cached query results + * @return array Array of resultset rows, or false if no rows matched + * @access public + */ + function fetchAll($sql, $cache = true, $modelName = null) { + if ($cache && isset($this->_queryCache[$sql])) { + if (preg_match('/^\s*select/i', $sql)) { + return $this->_queryCache[$sql]; + } + } + + if ($this->execute($sql)) { + $out = array(); + + $first = $this->fetchRow(); + if ($first != null) { + $out[] = $first; + } + while ($this->hasResult() && $item = $this->fetchResult()) { + $this->fetchVirtualField($item); + $out[] = $item; + } + + if ($cache) { + if (strpos(trim(strtolower($sql)), 'select') !== false) { + $this->_queryCache[$sql] = $out; + } + } + if (empty($out) && is_bool($this->_result)) { + return $this->_result; + } + return $out; + } else { + return false; + } + } + +/** + * Modifies $result array to place virtual fields in model entry where they belongs to + * + * @param array $resut REference to the fetched row + * @return void + */ + function fetchVirtualField(&$result) { + if (isset($result[0]) && is_array($result[0])) { + foreach ($result[0] as $field => $value) { + if (strpos($field, $this->virtualFieldSeparator) === false) { + continue; + } + list($alias, $virtual) = explode($this->virtualFieldSeparator, $field); + + if (!ClassRegistry::isKeySet($alias)) { + return; + } + $model = ClassRegistry::getObject($alias); + if ($model->isVirtualField($virtual)) { + $result[$alias][$virtual] = $value; + unset($result[0][$field]); + } + } + if (empty($result[0])) { + unset($result[0]); + } + } + } + +/** + * Returns a single field of the first of query results for a given SQL query, or false if empty. + * + * @param string $name Name of the field + * @param string $sql SQL query + * @return mixed Value of field read. + * @access public + */ + function field($name, $sql) { + $data = $this->fetchRow($sql); + if (!isset($data[$name]) || empty($data[$name])) { + return false; + } else { + return $data[$name]; + } + } + +/** + * Empties the method caches. + * These caches are used by DboSource::name() and DboSource::conditions() + * + * @return void + */ + function flushMethodCache() { + $this->methodCache = array(); + } + +/** + * Cache a value into the methodCaches. Will respect the value of DboSource::$cacheMethods. + * Will retrieve a value from the cache if $value is null. + * + * If caching is disabled and a write is attempted, the $value will be returned. + * A read will either return the value or null. + * + * @param string $method Name of the method being cached. + * @param string $key The keyname for the cache operation. + * @param mixed $value The value to cache into memory. + * @return mixed Either null on failure, or the value if its set. + */ + function cacheMethod($method, $key, $value = null) { + if ($this->cacheMethods === false) { + return $value; + } + if ($value === null) { + return (isset($this->methodCache[$method][$key])) ? $this->methodCache[$method][$key] : null; + } + return $this->methodCache[$method][$key] = $value; + } + +/** + * Returns a quoted name of $data for use in an SQL statement. + * Strips fields out of SQL functions before quoting. + * + * Results of this method are stored in a memory cache. This improves performance, but + * because the method uses a simple hashing algorithm it can infrequently have collisions. + * Setting DboSource::$cacheMethods to false will disable the memory cache. + * + * @param mixed $data Either a string with a column to quote. An array of columns to quote or an + * object from DboSource::expression() or DboSource::identifier() + * @return string SQL field + * @access public + */ + function name($data) { + if (is_object($data) && isset($data->type)) { + return $data->value; + } + if ($data === '*') { + return '*'; + } + if (is_array($data)) { + foreach ($data as $i => $dataItem) { + $data[$i] = $this->name($dataItem); + } + return $data; + } + $cacheKey = crc32($this->startQuote.$data.$this->endQuote); + if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) { + return $return; + } + $data = trim($data); + if (preg_match('/^[\w-]+(?:\.[^ \*]*)*$/', $data)) { // string, string.string + if (strpos($data, '.') === false) { // string + return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote); + } + $items = explode('.', $data); + return $this->cacheMethod(__FUNCTION__, $cacheKey, + $this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote + ); + } + if (preg_match('/^[\w-]+\.\*$/', $data)) { // string.* + return $this->cacheMethod(__FUNCTION__, $cacheKey, + $this->startQuote . str_replace('.*', $this->endQuote . '.*', $data) + ); + } + if (preg_match('/^([\w-]+)\((.*)\)$/', $data, $matches)) { // Functions + return $this->cacheMethod(__FUNCTION__, $cacheKey, + $matches[1] . '(' . $this->name($matches[2]) . ')' + ); + } + if ( + preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+' . preg_quote($this->alias) . '\s*([\w-]+)$/i', $data, $matches + )) { + return $this->cacheMethod( + __FUNCTION__, $cacheKey, + preg_replace( + '/\s{2,}/', ' ', $this->name($matches[1]) . ' ' . $this->alias . ' ' . $this->name($matches[3]) + ) + ); + } + if (preg_match('/^[\w-_\s]*[\w-_]+/', $data)) { + return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote); + } + return $this->cacheMethod(__FUNCTION__, $cacheKey, $data); + } + +/** + * Checks if the source is connected to the database. + * + * @return boolean True if the database is connected, else false + * @access public + */ + function isConnected() { + return $this->connected; + } + +/** + * Checks if the result is valid + * + * @return boolean True if the result is valid else false + * @access public + */ + function hasResult() { + return is_resource($this->_result); + } + +/** + * Get the query log as an array. + * + * @param boolean $sorted Get the queries sorted by time taken, defaults to false. + * @return array Array of queries run as an array + * @access public + */ + function getLog($sorted = false, $clear = true) { + if ($sorted) { + $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC); + } else { + $log = $this->_queriesLog; + } + if ($clear) { + $this->_queriesLog = array(); + } + return array('log' => $log, 'count' => $this->_queriesCnt, 'time' => $this->_queriesTime); + } + +/** + * Outputs the contents of the queries log. If in a non-CLI environment the sql_log element + * will be rendered and output. If in a CLI environment, a plain text log is generated. + * + * @param boolean $sorted Get the queries sorted by time taken, defaults to false. + * @return void + */ + function showLog($sorted = false) { + $log = $this->getLog($sorted, false); + if (empty($log['log'])) { + return; + } + if (PHP_SAPI != 'cli') { + App::import('Core', 'View'); + $controller = null; + $View =& new View($controller, false); + $View->set('logs', array($this->configKeyName => $log)); + echo $View->element('sql_dump', array('_forced_from_dbo_' => true)); + } else { + foreach ($log['log'] as $k => $i) { + print (($k + 1) . ". {$i['query']} {$i['error']}\n"); + } + } + } + +/** + * Log given SQL query. + * + * @param string $sql SQL statement + * @todo: Add hook to log errors instead of returning false + * @access public + */ + function logQuery($sql) { + $this->_queriesCnt++; + $this->_queriesTime += $this->took; + $this->_queriesLog[] = array( + 'query' => $sql, + 'error' => $this->error, + 'affected' => $this->affected, + 'numRows' => $this->numRows, + 'took' => $this->took + ); + if (count($this->_queriesLog) > $this->_queriesLogMax) { + array_pop($this->_queriesLog); + } + if ($this->error) { + return false; + } + } + +/** + * Output information about an SQL query. The SQL statement, number of rows in resultset, + * and execution time in microseconds. If the query fails, an error is output instead. + * + * @param string $sql Query to show information on. + * @access public + */ + function showQuery($sql) { + $error = $this->error; + if (strlen($sql) > 200 && !$this->fullDebug && Configure::read() > 1) { + $sql = substr($sql, 0, 200) . '[...]'; + } + if (Configure::read() > 0) { + $out = null; + if ($error) { + trigger_error('' . __('SQL Error:', true) . " {$this->error}", E_USER_WARNING); + } else { + $out = ('[' . sprintf(__('Aff:%s Num:%s Took:%sms', true), $this->affected, $this->numRows, $this->took) . ']'); + } + pr(sprintf('

' . __('Query:', true) . ' %s %s

', $sql, $out)); + } + } + +/** + * Gets full table name including prefix + * + * @param mixed $model Either a Model object or a string table name. + * @param boolean $quote Whether you want the table name quoted. + * @return string Full quoted table name + * @access public + */ + function fullTableName($model, $quote = true) { + if (is_object($model)) { + $table = $model->tablePrefix . $model->table; + } elseif (isset($this->config['prefix'])) { + $table = $this->config['prefix'] . strval($model); + } else { + $table = strval($model); + } + if ($quote) { + return $this->name($table); + } + return $table; + } + +/** + * The "C" in CRUD + * + * Creates new records in the database. + * + * @param Model $model Model object that the record is for. + * @param array $fields An array of field names to insert. If null, $model->data will be + * used to generate field names. + * @param array $values An array of values with keys matching the fields. If null, $model->data will + * be used to generate values. + * @return boolean Success + * @access public + */ + function create(&$model, $fields = null, $values = null) { + $id = null; + + if ($fields == null) { + unset($fields, $values); + $fields = array_keys($model->data); + $values = array_values($model->data); + } + $count = count($fields); + + for ($i = 0; $i < $count; $i++) { + $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]), false); + } + for ($i = 0; $i < $count; $i++) { + $fieldInsert[] = $this->name($fields[$i]); + if ($fields[$i] == $model->primaryKey) { + $id = $values[$i]; + } + } + $query = array( + 'table' => $this->fullTableName($model), + 'fields' => implode(', ', $fieldInsert), + 'values' => implode(', ', $valueInsert) + ); + + if ($this->execute($this->renderStatement('create', $query))) { + if (empty($id)) { + $id = $this->lastInsertId($this->fullTableName($model, false), $model->primaryKey); + } + $model->setInsertID($id); + $model->id = $id; + return true; + } else { + $model->onError(); + return false; + } + } + +/** + * The "R" in CRUD + * + * Reads record(s) from the database. + * + * @param Model $model A Model object that the query is for. + * @param array $queryData An array of queryData information containing keys similar to Model::find() + * @param integer $recursive Number of levels of association + * @return mixed boolean false on error/failure. An array of results on success. + */ + function read(&$model, $queryData = array(), $recursive = null) { + $queryData = $this->__scrubQueryData($queryData); + + $null = null; + $array = array(); + $linkedModels = array(); + $this->__bypass = false; + $this->__booleans = array(); + + if ($recursive === null && isset($queryData['recursive'])) { + $recursive = $queryData['recursive']; + } + + if (!is_null($recursive)) { + $_recursive = $model->recursive; + $model->recursive = $recursive; + } + + if (!empty($queryData['fields'])) { + $this->__bypass = true; + $queryData['fields'] = $this->fields($model, null, $queryData['fields']); + } else { + $queryData['fields'] = $this->fields($model); + } + + $_associations = $model->__associations; + + if ($model->recursive == -1) { + $_associations = array(); + } else if ($model->recursive == 0) { + unset($_associations[2], $_associations[3]); + } + + foreach ($_associations as $type) { + foreach ($model->{$type} as $assoc => $assocData) { + $linkModel =& $model->{$assoc}; + $external = isset($assocData['external']); + + if ($model->useDbConfig == $linkModel->useDbConfig) { + if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) { + $linkedModels[$type . '/' . $assoc] = true; + } + } + } + } + + $query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null); + + $resultSet = $this->fetchAll($query, $model->cacheQueries, $model->alias); + + if ($resultSet === false) { + $model->onError(); + return false; + } + + $filtered = $this->__filterResults($resultSet, $model); + + if ($model->recursive > -1) { + foreach ($_associations as $type) { + foreach ($model->{$type} as $assoc => $assocData) { + $linkModel =& $model->{$assoc}; + + if (empty($linkedModels[$type . '/' . $assoc])) { + if ($model->useDbConfig == $linkModel->useDbConfig) { + $db =& $this; + } else { + $db =& ConnectionManager::getDataSource($linkModel->useDbConfig); + } + } elseif ($model->recursive > 1 && ($type == 'belongsTo' || $type == 'hasOne')) { + $db =& $this; + } + + if (isset($db) && method_exists($db, 'queryAssociation')) { + $stack = array($assoc); + $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack); + unset($db); + + if ($type === 'hasMany') { + $filtered []= $assoc; + } + } + } + } + $this->__filterResults($resultSet, $model, $filtered); + } + + if (!is_null($recursive)) { + $model->recursive = $_recursive; + } + return $resultSet; + } + +/** + * Passes association results thru afterFind filters of corresponding model + * + * @param array $results Reference of resultset to be filtered + * @param object $model Instance of model to operate against + * @param array $filtered List of classes already filtered, to be skipped + * @return array Array of results that have been filtered through $model->afterFind + * @access private + */ + function __filterResults(&$results, &$model, $filtered = array()) { + $filtering = array(); + $count = count($results); + + for ($i = 0; $i < $count; $i++) { + if (is_array($results[$i])) { + $classNames = array_keys($results[$i]); + $count2 = count($classNames); + + for ($j = 0; $j < $count2; $j++) { + $className = $classNames[$j]; + if ($model->alias != $className && !in_array($className, $filtered)) { + if (!in_array($className, $filtering)) { + $filtering[] = $className; + } + + if (isset($model->{$className}) && is_object($model->{$className})) { + $data = $model->{$className}->afterFind(array(array($className => $results[$i][$className])), false); + } + if (isset($data[0][$className])) { + $results[$i][$className] = $data[0][$className]; + } + } + } + } + } + return $filtering; + } + +/** + * Queries associations. Used to fetch results on recursive models. + * + * @param Model $model Primary Model object + * @param Model $linkModel Linked model that + * @param string $type Association type, one of the model association types ie. hasMany + * @param unknown_type $association + * @param unknown_type $assocData + * @param array $queryData + * @param boolean $external Whether or not the association query is on an external datasource. + * @param array $resultSet Existing results + * @param integer $recursive Number of levels of association + * @param array $stack + */ + function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) { + if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) { + if (!isset($resultSet) || !is_array($resultSet)) { + if (Configure::read() > 0) { + echo '
' . sprintf(__('SQL Error in model %s:', true), $model->alias) . ' '; + if (isset($this->error) && $this->error != null) { + echo $this->error; + } + echo '
'; + } + return null; + } + $count = count($resultSet); + + if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) { + $ins = $fetch = array(); + for ($i = 0; $i < $count; $i++) { + if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) { + $ins[] = $in; + } + } + + if (!empty($ins)) { + $ins = array_unique($ins); + $fetch = $this->fetchAssociated($model, $query, $ins); + } + + if (!empty($fetch) && is_array($fetch)) { + if ($recursive > 0) { + foreach ($linkModel->__associations as $type1) { + foreach ($linkModel->{$type1} as $assoc1 => $assocData1) { + $deepModel =& $linkModel->{$assoc1}; + $tmpStack = $stack; + $tmpStack[] = $assoc1; + + if ($linkModel->useDbConfig === $deepModel->useDbConfig) { + $db =& $this; + } else { + $db =& ConnectionManager::getDataSource($deepModel->useDbConfig); + } + $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack); + } + } + } + } + $this->__filterResults($fetch, $model); + return $this->__mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive); + } elseif ($type === 'hasAndBelongsToMany') { + $ins = $fetch = array(); + for ($i = 0; $i < $count; $i++) { + if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) { + $ins[] = $in; + } + } + if (!empty($ins)) { + $ins = array_unique($ins); + if (count($ins) > 1) { + $query = str_replace('{$__cakeID__$}', '(' .implode(', ', $ins) .')', $query); + $query = str_replace('= (', 'IN (', $query); + } else { + $query = str_replace('{$__cakeID__$}',$ins[0], $query); + } + + $query = str_replace(' WHERE 1 = 1', '', $query); + } + + $foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey']; + $joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']); + list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys); + $habtmFieldsCount = count($habtmFields); + $q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack); + + if ($q != false) { + $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias); + } else { + $fetch = null; + } + } + + for ($i = 0; $i < $count; $i++) { + $row =& $resultSet[$i]; + + if ($type !== 'hasAndBelongsToMany') { + $q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack); + if ($q != false) { + $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias); + } else { + $fetch = null; + } + } + $selfJoin = false; + + if ($linkModel->name === $model->name) { + $selfJoin = true; + } + + if (!empty($fetch) && is_array($fetch)) { + if ($recursive > 0) { + foreach ($linkModel->__associations as $type1) { + foreach ($linkModel->{$type1} as $assoc1 => $assocData1) { + $deepModel =& $linkModel->{$assoc1}; + + if (($type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) { + $tmpStack = $stack; + $tmpStack[] = $assoc1; + if ($linkModel->useDbConfig == $deepModel->useDbConfig) { + $db =& $this; + } else { + $db =& ConnectionManager::getDataSource($deepModel->useDbConfig); + } + $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack); + } + } + } + } + if ($type == 'hasAndBelongsToMany') { + $uniqueIds = $merge = array(); + + foreach ($fetch as $j => $data) { + if ( + (isset($data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey]) + ) { + if ($habtmFieldsCount <= 2) { + unset($data[$with]); + } + $merge[] = $data; + } + } + if (empty($merge) && !isset($row[$association])) { + $row[$association] = $merge; + } else { + $this->__mergeAssociation($resultSet[$i], $merge, $association, $type); + } + } else { + $this->__mergeAssociation($resultSet[$i], $fetch, $association, $type, $selfJoin); + } + if (isset($resultSet[$i][$association])) { + $resultSet[$i][$association] = $linkModel->afterFind($resultSet[$i][$association], false); + } + } else { + $tempArray[0][$association] = false; + $this->__mergeAssociation($resultSet[$i], $tempArray, $association, $type, $selfJoin); + } + } + } + } + +/** + * A more efficient way to fetch associations. Woohoo! + * + * @param model $model Primary model object + * @param string $query Association query + * @param array $ids Array of IDs of associated records + * @return array Association results + * @access public + */ + function fetchAssociated($model, $query, $ids) { + $query = str_replace('{$__cakeID__$}', implode(', ', $ids), $query); + if (count($ids) > 1) { + $query = str_replace('= (', 'IN (', $query); + } + return $this->fetchAll($query, $model->cacheQueries, $model->alias); + } + +/** + * mergeHasMany - Merge the results of hasMany relations. + * + * + * @param array $resultSet Data to merge into + * @param array $merge Data to merge + * @param string $association Name of Model being Merged + * @param object $model Model being merged onto + * @param object $linkModel Model being merged + * @return void + */ + function __mergeHasMany(&$resultSet, $merge, $association, &$model, &$linkModel) { + foreach ($resultSet as $i => $value) { + $count = 0; + $merged[$association] = array(); + foreach ($merge as $j => $data) { + if (isset($value[$model->alias]) && $value[$model->alias][$model->primaryKey] === $data[$association][$model->hasMany[$association]['foreignKey']]) { + if (count($data) > 1) { + $data = array_merge($data[$association], $data); + unset($data[$association]); + foreach ($data as $key => $name) { + if (is_numeric($key)) { + $data[$association][] = $name; + unset($data[$key]); + } + } + $merged[$association][] = $data; + } else { + $merged[$association][] = $data[$association]; + } + } + $count++; + } + if (isset($value[$model->alias])) { + $resultSet[$i] = Set::pushDiff($resultSet[$i], $merged); + unset($merged); + } + } + } + +/** + * Enter description here... + * + * @param unknown_type $data + * @param unknown_type $merge + * @param unknown_type $association + * @param unknown_type $type + * @param boolean $selfJoin + * @access private + */ + function __mergeAssociation(&$data, $merge, $association, $type, $selfJoin = false) { + if (isset($merge[0]) && !isset($merge[0][$association])) { + $association = Inflector::pluralize($association); + } + + if ($type == 'belongsTo' || $type == 'hasOne') { + if (isset($merge[$association])) { + $data[$association] = $merge[$association][0]; + } else { + if (count($merge[0][$association]) > 1) { + foreach ($merge[0] as $assoc => $data2) { + if ($assoc != $association) { + $merge[0][$association][$assoc] = $data2; + } + } + } + if (!isset($data[$association])) { + if ($merge[0][$association] != null) { + $data[$association] = $merge[0][$association]; + } else { + $data[$association] = array(); + } + } else { + if (is_array($merge[0][$association])) { + foreach ($data[$association] as $k => $v) { + if (!is_array($v)) { + $dataAssocTmp[$k] = $v; + } + } + + foreach ($merge[0][$association] as $k => $v) { + if (!is_array($v)) { + $mergeAssocTmp[$k] = $v; + } + } + $dataKeys = array_keys($data); + $mergeKeys = array_keys($merge[0]); + + if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) { + $data[$association][$association] = $merge[0][$association]; + } else { + $diff = Set::diff($dataAssocTmp, $mergeAssocTmp); + $data[$association] = array_merge($merge[0][$association], $diff); + } + } elseif ($selfJoin && array_key_exists($association, $merge[0])) { + $data[$association] = array_merge($data[$association], array($association => array())); + } + } + } + } else { + if (isset($merge[0][$association]) && $merge[0][$association] === false) { + if (!isset($data[$association])) { + $data[$association] = array(); + } + } else { + foreach ($merge as $i => $row) { + if (count($row) == 1) { + if (empty($data[$association]) || (isset($data[$association]) && !in_array($row[$association], $data[$association]))) { + $data[$association][] = $row[$association]; + } + } else if (!empty($row)) { + $tmp = array_merge($row[$association], $row); + unset($tmp[$association]); + $data[$association][] = $tmp; + } + } + } + } + } + +/** + * Generates an array representing a query or part of a query from a single model or two associated models + * + * @param Model $model + * @param Model $linkModel + * @param string $type + * @param string $association + * @param array $assocData + * @param array $queryData + * @param boolean $external + * @param array $resultSet + * @return mixed + * @access public + */ + function generateAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) { + $queryData = $this->__scrubQueryData($queryData); + $assocData = $this->__scrubQueryData($assocData); + + if (empty($queryData['fields'])) { + $queryData['fields'] = $this->fields($model, $model->alias); + } elseif (!empty($model->hasMany) && $model->recursive > -1) { + $assocFields = $this->fields($model, $model->alias, array("{$model->alias}.{$model->primaryKey}")); + $passedFields = $this->fields($model, $model->alias, $queryData['fields']); + if (count($passedFields) === 1) { + $match = strpos($passedFields[0], $assocFields[0]); + $match1 = (bool)preg_match('/^[a-z]+\(/i', $passedFields[0]); + + if ($match === false && $match1 === false) { + $queryData['fields'] = array_merge($passedFields, $assocFields); + } else { + $queryData['fields'] = $passedFields; + } + } else { + $queryData['fields'] = array_merge($passedFields, $assocFields); + } + unset($assocFields, $passedFields); + } + + if ($linkModel == null) { + return $this->buildStatement( + array( + 'fields' => array_unique($queryData['fields']), + 'table' => $this->fullTableName($model), + 'alias' => $model->alias, + 'limit' => $queryData['limit'], + 'offset' => $queryData['offset'], + 'joins' => $queryData['joins'], + 'conditions' => $queryData['conditions'], + 'order' => $queryData['order'], + 'group' => $queryData['group'] + ), + $model + ); + } + if ($external && !empty($assocData['finderQuery'])) { + return $assocData['finderQuery']; + } + + $alias = $association; + $self = ($model->name == $linkModel->name); + $fields = array(); + + if ((!$external && in_array($type, array('hasOne', 'belongsTo')) && $this->__bypass === false) || $external) { + $fields = $this->fields($linkModel, $alias, $assocData['fields']); + } + if (empty($assocData['offset']) && !empty($assocData['page'])) { + $assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit']; + } + $assocData['limit'] = $this->limit($assocData['limit'], $assocData['offset']); + + switch ($type) { + case 'hasOne': + case 'belongsTo': + $conditions = $this->__mergeConditions( + $assocData['conditions'], + $this->getConstraint($type, $model, $linkModel, $alias, array_merge($assocData, compact('external', 'self'))) + ); + + if (!$self && $external) { + foreach ($conditions as $key => $condition) { + if (is_numeric($key) && strpos($condition, $model->alias . '.') !== false) { + unset($conditions[$key]); + } + } + } + + if ($external) { + $query = array_merge($assocData, array( + 'conditions' => $conditions, + 'table' => $this->fullTableName($linkModel), + 'fields' => $fields, + 'alias' => $alias, + 'group' => null + )); + $query = array_merge(array('order' => $assocData['order'], 'limit' => $assocData['limit']), $query); + } else { + $join = array( + 'table' => $this->fullTableName($linkModel), + 'alias' => $alias, + 'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT', + 'conditions' => trim($this->conditions($conditions, true, false, $model)) + ); + $queryData['fields'] = array_merge($queryData['fields'], $fields); + + if (!empty($assocData['order'])) { + $queryData['order'][] = $assocData['order']; + } + if (!in_array($join, $queryData['joins'])) { + $queryData['joins'][] = $join; + } + return true; + } + break; + case 'hasMany': + $assocData['fields'] = $this->fields($linkModel, $alias, $assocData['fields']); + if (!empty($assocData['foreignKey'])) { + $assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $alias, array("{$alias}.{$assocData['foreignKey']}"))); + } + $query = array( + 'conditions' => $this->__mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $alias, $assocData), $assocData['conditions']), + 'fields' => array_unique($assocData['fields']), + 'table' => $this->fullTableName($linkModel), + 'alias' => $alias, + 'order' => $assocData['order'], + 'limit' => $assocData['limit'], + 'group' => null + ); + break; + case 'hasAndBelongsToMany': + $joinFields = array(); + $joinAssoc = null; + + if (isset($assocData['with']) && !empty($assocData['with'])) { + $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']); + list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys); + + $joinTbl = $this->fullTableName($model->{$with}); + $joinAlias = $joinTbl; + + if (is_array($joinFields) && !empty($joinFields)) { + $joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields); + $joinAssoc = $joinAlias = $model->{$with}->alias; + } else { + $joinFields = array(); + } + } else { + $joinTbl = $this->fullTableName($assocData['joinTable']); + $joinAlias = $joinTbl; + } + $query = array( + 'conditions' => $assocData['conditions'], + 'limit' => $assocData['limit'], + 'table' => $this->fullTableName($linkModel), + 'alias' => $alias, + 'fields' => array_merge($this->fields($linkModel, $alias, $assocData['fields']), $joinFields), + 'order' => $assocData['order'], + 'group' => null, + 'joins' => array(array( + 'table' => $joinTbl, + 'alias' => $joinAssoc, + 'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $alias) + )) + ); + break; + } + if (isset($query)) { + return $this->buildStatement($query, $model); + } + return null; + } + +/** + * Returns a conditions array for the constraint between two models + * + * @param string $type Association type + * @param object $model Model object + * @param array $association Association array + * @return array Conditions array defining the constraint between $model and $association + * @access public + */ + function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) { + $assoc = array_merge(array('external' => false, 'self' => false), $assoc); + + if (array_key_exists('foreignKey', $assoc) && empty($assoc['foreignKey'])) { + return array(); + } + + switch (true) { + case ($assoc['external'] && $type == 'hasOne'): + return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'); + break; + case ($assoc['external'] && $type == 'belongsTo'): + return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}'); + break; + case (!$assoc['external'] && $type == 'hasOne'): + return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}")); + break; + case (!$assoc['external'] && $type == 'belongsTo'): + return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}")); + break; + case ($type == 'hasMany'): + return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}')); + break; + case ($type == 'hasAndBelongsToMany'): + return array( + array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'), + array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}")) + ); + break; + } + return array(); + } + +/** + * Builds and generates a JOIN statement from an array. Handles final clean-up before conversion. + * + * @param array $join An array defining a JOIN statement in a query + * @return string An SQL JOIN statement to be used in a query + * @access public + * @see DboSource::renderJoinStatement() + * @see DboSource::buildStatement() + */ + function buildJoinStatement($join) { + $data = array_merge(array( + 'type' => null, + 'alias' => null, + 'table' => 'join_table', + 'conditions' => array() + ), $join); + + if (!empty($data['alias'])) { + $data['alias'] = $this->alias . $this->name($data['alias']); + } + if (!empty($data['conditions'])) { + $data['conditions'] = trim($this->conditions($data['conditions'], true, false)); + } + return $this->renderJoinStatement($data); + } + +/** + * Builds and generates an SQL statement from an array. Handles final clean-up before conversion. + * + * @param array $query An array defining an SQL query + * @param object $model The model object which initiated the query + * @return string An executable SQL statement + * @access public + * @see DboSource::renderStatement() + */ + function buildStatement($query, &$model) { + $query = array_merge(array('offset' => null, 'joins' => array()), $query); + if (!empty($query['joins'])) { + $count = count($query['joins']); + for ($i = 0; $i < $count; $i++) { + if (is_array($query['joins'][$i])) { + $query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]); + } + } + } + return $this->renderStatement('select', array( + 'conditions' => $this->conditions($query['conditions'], true, true, $model), + 'fields' => implode(', ', $query['fields']), + 'table' => $query['table'], + 'alias' => $this->alias . $this->name($query['alias']), + 'order' => $this->order($query['order'], 'ASC', $model), + 'limit' => $this->limit($query['limit'], $query['offset']), + 'joins' => implode(' ', $query['joins']), + 'group' => $this->group($query['group'], $model) + )); + } + +/** + * Renders a final SQL JOIN statement + * + * @param array $data + * @return string + * @access public + */ + function renderJoinStatement($data) { + extract($data); + return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})"); + } + +/** + * Renders a final SQL statement by putting together the component parts in the correct order + * + * @param string $type type of query being run. e.g select, create, update, delete, schema, alter. + * @param array $data Array of data to insert into the query. + * @return string Rendered SQL expression to be run. + * @access public + */ + function renderStatement($type, $data) { + extract($data); + $aliases = null; + + switch (strtolower($type)) { + case 'select': + return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}"; + break; + case 'create': + return "INSERT INTO {$table} ({$fields}) VALUES ({$values})"; + break; + case 'update': + if (!empty($alias)) { + $aliases = "{$this->alias}{$alias} {$joins} "; + } + return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}"; + break; + case 'delete': + if (!empty($alias)) { + $aliases = "{$this->alias}{$alias} {$joins} "; + } + return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}"; + break; + case 'schema': + foreach (array('columns', 'indexes', 'tableParameters') as $var) { + if (is_array(${$var})) { + ${$var} = "\t" . join(",\n\t", array_filter(${$var})); + } else { + ${$var} = ''; + } + } + if (trim($indexes) != '') { + $columns .= ','; + } + return "CREATE TABLE {$table} (\n{$columns}{$indexes}){$tableParameters};"; + break; + case 'alter': + break; + } + } + +/** + * Merges a mixed set of string/array conditions + * + * @return array + * @access private + */ + function __mergeConditions($query, $assoc) { + if (empty($assoc)) { + return $query; + } + + if (is_array($query)) { + return array_merge((array)$assoc, $query); + } + + if (!empty($query)) { + $query = array($query); + if (is_array($assoc)) { + $query = array_merge($query, $assoc); + } else { + $query[] = $assoc; + } + return $query; + } + + return $assoc; + } + +/** + * Generates and executes an SQL UPDATE statement for given model, fields, and values. + * For databases that do not support aliases in UPDATE queries. + * + * @param Model $model + * @param array $fields + * @param array $values + * @param mixed $conditions + * @return boolean Success + * @access public + */ + function update(&$model, $fields = array(), $values = null, $conditions = null) { + if ($values == null) { + $combined = $fields; + } else { + $combined = array_combine($fields, $values); + } + + $fields = implode(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions))); + + $alias = $joins = null; + $table = $this->fullTableName($model); + $conditions = $this->_matchRecords($model, $conditions); + + if ($conditions === false) { + return false; + } + $query = compact('table', 'alias', 'joins', 'fields', 'conditions'); + + if (!$this->execute($this->renderStatement('update', $query))) { + $model->onError(); + return false; + } + return true; + } + +/** + * Quotes and prepares fields and values for an SQL UPDATE statement + * + * @param Model $model + * @param array $fields + * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets + * @param boolean $alias Include the model alias in the field name + * @return array Fields and values, quoted and preparted + * @access protected + */ + function _prepareUpdateFields(&$model, $fields, $quoteValues = true, $alias = false) { + $quotedAlias = $this->startQuote . $model->alias . $this->endQuote; + + $updates = array(); + foreach ($fields as $field => $value) { + if ($alias && strpos($field, '.') === false) { + $quoted = $model->escapeField($field); + } elseif (!$alias && strpos($field, '.') !== false) { + $quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace( + $model->alias . '.', '', $field + ))); + } else { + $quoted = $this->name($field); + } + + if ($value === null) { + $updates[] = $quoted . ' = NULL'; + continue; + } + $update = $quoted . ' = '; + + if ($quoteValues) { + $update .= $this->value($value, $model->getColumnType($field), false); + } elseif (!$alias) { + $update .= str_replace($quotedAlias . '.', '', str_replace( + $model->alias . '.', '', $value + )); + } else { + $update .= $value; + } + $updates[] = $update; + } + return $updates; + } + +/** + * Generates and executes an SQL DELETE statement. + * For databases that do not support aliases in UPDATE queries. + * + * @param Model $model + * @param mixed $conditions + * @return boolean Success + * @access public + */ + function delete(&$model, $conditions = null) { + $alias = $joins = null; + $table = $this->fullTableName($model); + $conditions = $this->_matchRecords($model, $conditions); + + if ($conditions === false) { + return false; + } + + if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) { + $model->onError(); + return false; + } + return true; + } + +/** + * Gets a list of record IDs for the given conditions. Used for multi-record updates and deletes + * in databases that do not support aliases in UPDATE/DELETE queries. + * + * @param Model $model + * @param mixed $conditions + * @return array List of record IDs + * @access protected + */ + function _matchRecords(&$model, $conditions = null) { + if ($conditions === true) { + $conditions = $this->conditions(true); + } elseif ($conditions === null) { + $conditions = $this->conditions($this->defaultConditions($model, $conditions, false), true, true, $model); + } else { + $noJoin = true; + foreach ($conditions as $field => $value) { + $originalField = $field; + if (strpos($field, '.') !== false) { + list($alias, $field) = explode('.', $field); + $field = ltrim($field, $this->startQuote); + $field = rtrim($field, $this->endQuote); + } + if (!$model->hasField($field)) { + $noJoin = false; + break; + } + if ($field !== $originalField) { + $conditions[$field] = $value; + unset($conditions[$originalField]); + } + } + if ($noJoin === true) { + return $this->conditions($conditions); + } + $idList = $model->find('all', array( + 'fields' => "{$model->alias}.{$model->primaryKey}", + 'conditions' => $conditions + )); + + if (empty($idList)) { + return false; + } + $conditions = $this->conditions(array( + $model->primaryKey => Set::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}") + )); + } + return $conditions; + } + +/** + * Returns an array of SQL JOIN fragments from a model's associations + * + * @param object $model + * @return array + * @access protected + */ + function _getJoins($model) { + $join = array(); + $joins = array_merge($model->getAssociated('hasOne'), $model->getAssociated('belongsTo')); + + foreach ($joins as $assoc) { + if (isset($model->{$assoc}) && $model->useDbConfig == $model->{$assoc}->useDbConfig) { + $assocData = $model->getAssociated($assoc); + $join[] = $this->buildJoinStatement(array( + 'table' => $this->fullTableName($model->{$assoc}), + 'alias' => $assoc, + 'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT', + 'conditions' => trim($this->conditions( + $this->__mergeConditions($assocData['conditions'], $this->getConstraint($assocData['association'], $model, $model->{$assoc}, $assoc, $assocData)), + true, false, $model + )) + )); + } + } + return $join; + } + +/** + * Returns an SQL calculation, i.e. COUNT() or MAX() + * + * @param model $model + * @param string $func Lowercase name of SQL function, i.e. 'count' or 'max' + * @param array $params Function parameters (any values must be quoted manually) + * @return string An SQL calculation function + * @access public + */ + function calculate(&$model, $func, $params = array()) { + $params = (array)$params; + + switch (strtolower($func)) { + case 'count': + if (!isset($params[0])) { + $params[0] = '*'; + } + if (!isset($params[1])) { + $params[1] = 'count'; + } + if (is_object($model) && $model->isVirtualField($params[0])){ + $arg = $this->__quoteFields($model->getVirtualField($params[0])); + } else { + $arg = $this->name($params[0]); + } + return 'COUNT(' . $arg . ') AS ' . $this->name($params[1]); + case 'max': + case 'min': + if (!isset($params[1])) { + $params[1] = $params[0]; + } + if (is_object($model) && $model->isVirtualField($params[0])) { + $arg = $this->__quoteFields($model->getVirtualField($params[0])); + } else { + $arg = $this->name($params[0]); + } + return strtoupper($func) . '(' . $arg . ') AS ' . $this->name($params[1]); + break; + } + } + +/** + * Deletes all the records in a table and resets the count of the auto-incrementing + * primary key, where applicable. + * + * @param mixed $table A string or model class representing the table to be truncated + * @return boolean SQL TRUNCATE TABLE statement, false if not applicable. + * @access public + */ + function truncate($table) { + return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table)); + } + +/** + * Begin a transaction + * + * @param model $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions, + * or a transaction has not started). + * @access public + */ + function begin(&$model) { + if (parent::begin($model) && $this->execute($this->_commands['begin'])) { + $this->_transactionStarted = true; + return true; + } + return false; + } + +/** + * Commit a transaction + * + * @param model $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions, + * or a transaction has not started). + * @access public + */ + function commit(&$model) { + if (parent::commit($model) && $this->execute($this->_commands['commit'])) { + $this->_transactionStarted = false; + return true; + } + return false; + } + +/** + * Rollback a transaction + * + * @param model $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions, + * or a transaction has not started). + * @access public + */ + function rollback(&$model) { + if (parent::rollback($model) && $this->execute($this->_commands['rollback'])) { + $this->_transactionStarted = false; + return true; + } + return false; + } + +/** + * Creates a default set of conditions from the model if $conditions is null/empty. + * If conditions are supplied then they will be returned. If a model doesn't exist and no conditions + * were provided either null or false will be returned based on what was input. + * + * @param object $model + * @param mixed $conditions Array of conditions, conditions string, null or false. If an array of conditions, + * or string conditions those conditions will be returned. With other values the model's existance will be checked. + * If the model doesn't exist a null or false will be returned depending on the input value. + * @param boolean $useAlias Use model aliases rather than table names when generating conditions + * @return mixed Either null, false, $conditions or an array of default conditions to use. + * @see DboSource::update() + * @see DboSource::conditions() + * @access public + */ + function defaultConditions(&$model, $conditions, $useAlias = true) { + if (!empty($conditions)) { + return $conditions; + } + $exists = $model->exists(); + if (!$exists && $conditions !== null) { + return false; + } elseif (!$exists) { + return null; + } + $alias = $model->alias; + + if (!$useAlias) { + $alias = $this->fullTableName($model, false); + } + return array("{$alias}.{$model->primaryKey}" => $model->getID()); + } + +/** + * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name) + * + * @param unknown_type $model + * @param unknown_type $key + * @param unknown_type $assoc + * @return string + * @access public + */ + function resolveKey($model, $key, $assoc = null) { + if (empty($assoc)) { + $assoc = $model->alias; + } + if (!strpos('.', $key)) { + return $this->name($model->alias) . '.' . $this->name($key); + } + return $key; + } + +/** + * Private helper method to remove query metadata in given data array. + * + * @param array $data + * @return array + * @access public + */ + function __scrubQueryData($data) { + foreach (array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group') as $key) { + if (empty($data[$key])) { + $data[$key] = array(); + } + } + return $data; + } + +/** + * Converts model virtual fields into sql expressions to be fetched later + * + * @param Model $model + * @param string $alias Alias tablename + * @param mixed $fields virtual fields to be used on query + * @return array + */ + function _constructVirtualFields(&$model, $alias, $fields) { + $virtual = array(); + foreach ($fields as $field) { + $virtualField = $this->name($alias . $this->virtualFieldSeparator . $field); + $expression = $this->__quoteFields($model->getVirtualField($field)); + $virtual[] = '(' . $expression . ") {$this->alias} {$virtualField}"; + } + return $virtual; + } + +/** + * Generates the fields list of an SQL query. + * + * @param Model $model + * @param string $alias Alias tablename + * @param mixed $fields + * @param boolean $quote If false, returns fields array unquoted + * @return array + * @access public + */ + function fields(&$model, $alias = null, $fields = array(), $quote = true) { + if (empty($alias)) { + $alias = $model->alias; + } + $cacheKey = array( + $model->useDbConfig, + $model->table, + array_keys($model->schema()), + $model->name, + $model->getVirtualField(), + $alias, + $fields, + $quote + ); + $cacheKey = crc32(serialize($cacheKey)); + if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) { + return $return; + } + $allFields = empty($fields); + if ($allFields) { + $fields = array_keys($model->schema()); + } elseif (!is_array($fields)) { + $fields = String::tokenize($fields); + } + $fields = array_values(array_filter($fields)); + $allFields = $allFields || in_array('*', $fields) || in_array($model->alias . '.*', $fields); + + $virtual = array(); + $virtualFields = $model->getVirtualField(); + if (!empty($virtualFields)) { + $virtualKeys = array_keys($virtualFields); + foreach ($virtualKeys as $field) { + $virtualKeys[] = $model->alias . '.' . $field; + } + $virtual = ($allFields) ? $virtualKeys : array_intersect($virtualKeys, $fields); + foreach ($virtual as $i => $field) { + if (strpos($field, '.') !== false) { + $virtual[$i] = str_replace($model->alias . '.', '', $field); + } + $fields = array_diff($fields, array($field)); + } + $fields = array_values($fields); + } + + if (!$quote) { + if (!empty($virtual)) { + $fields = array_merge($fields, $this->_constructVirtualFields($model, $alias, $virtual)); + } + return $fields; + } + $count = count($fields); + + if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) { + for ($i = 0; $i < $count; $i++) { + if (is_string($fields[$i]) && in_array($fields[$i], $virtual)) { + unset($fields[$i]); + continue; + } + if (is_object($fields[$i]) && isset($fields[$i]->type) && $fields[$i]->type === 'expression') { + $fields[$i] = $fields[$i]->value; + } elseif (preg_match('/^\(.*\)\s' . $this->alias . '.*/i', $fields[$i])){ + continue; + } elseif (!preg_match('/^.+\\(.*\\)/', $fields[$i])) { + $prepend = ''; + + if (strpos($fields[$i], 'DISTINCT') !== false) { + $prepend = 'DISTINCT '; + $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i])); + } + $dot = strpos($fields[$i], '.'); + + if ($dot === false) { + $prefix = !( + strpos($fields[$i], ' ') !== false || + strpos($fields[$i], '(') !== false + ); + $fields[$i] = $this->name(($prefix ? $alias . '.' : '') . $fields[$i]); + } else { + $value = array(); + $comma = strpos($fields[$i], ','); + if ($comma === false) { + $build = explode('.', $fields[$i]); + if (!Set::numeric($build)) { + $fields[$i] = $this->name(implode('.', $build)); + } + } + } + $fields[$i] = $prepend . $fields[$i]; + } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) { + if (isset($field[1])) { + if (strpos($field[1], '.') === false) { + $field[1] = $this->name($alias . '.' . $field[1]); + } else { + $field[0] = explode('.', $field[1]); + if (!Set::numeric($field[0])) { + $field[0] = implode('.', array_map(array(&$this, 'name'), $field[0])); + $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1); + } + } + } + } + } + } + if (!empty($virtual)) { + $fields = array_merge($fields, $this->_constructVirtualFields($model, $alias, $virtual)); + } + return $this->cacheMethod(__FUNCTION__, $cacheKey, array_unique($fields)); + } + +/** + * Creates a WHERE clause by parsing given conditions data. If an array or string + * conditions are provided those conditions will be parsed and quoted. If a boolean + * is given it will be integer cast as condition. Null will return 1 = 1. + * + * Results of this method are stored in a memory cache. This improves performance, but + * because the method uses a simple hashing algorithm it can infrequently have collisions. + * Setting DboSource::$cacheMethods to false will disable the memory cache. + * + * @param mixed $conditions Array or string of conditions, or any value. + * @param boolean $quoteValues If true, values should be quoted + * @param boolean $where If true, "WHERE " will be prepended to the return value + * @param Model $model A reference to the Model instance making the query + * @return string SQL fragment + * @access public + */ + function conditions($conditions, $quoteValues = true, $where = true, $model = null) { + if (is_object($model)) { + $cacheKey = array( + $model->useDbConfig, + $model->table, + $model->schema(), + $model->name, + $model->getVirtualField(), + $conditions, + $quoteValues, + $where + ); + } else { + $cacheKey = array($conditions, $quoteValues, $where); + } + $cacheKey = crc32(serialize($cacheKey)); + if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) { + return $return; + } + + $clause = $out = ''; + + if ($where) { + $clause = ' WHERE '; + } + + if (is_array($conditions) && !empty($conditions)) { + $out = $this->conditionKeysToString($conditions, $quoteValues, $model); + + if (empty($out)) { + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . ' 1 = 1'); + } + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . implode(' AND ', $out)); + } + if ($conditions === false || $conditions === true) { + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . (int)$conditions . ' = 1'); + } + + if (empty($conditions) || trim($conditions) == '') { + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . '1 = 1'); + } + $clauses = '/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i'; + + if (preg_match($clauses, $conditions, $match)) { + $clause = ''; + } + if (trim($conditions) == '') { + $conditions = ' 1 = 1'; + } else { + $conditions = $this->__quoteFields($conditions); + } + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . $conditions); + } + +/** + * Creates a WHERE clause by parsing given conditions array. Used by DboSource::conditions(). + * + * @param array $conditions Array or string of conditions + * @param boolean $quoteValues If true, values should be quoted + * @param Model $model A reference to the Model instance making the query + * @return string SQL fragment + * @access public + */ + function conditionKeysToString($conditions, $quoteValues = true, $model = null) { + $c = 0; + $out = array(); + $data = $columnType = null; + $bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&'); + + foreach ($conditions as $key => $value) { + $join = ' AND '; + $not = null; + + if (is_array($value)) { + $valueInsert = ( + !empty($value) && + (substr_count($key, '?') == count($value) || substr_count($key, ':') == count($value)) + ); + } + + if (is_numeric($key) && empty($value)) { + continue; + } elseif (is_numeric($key) && is_string($value)) { + $out[] = $not . $this->__quoteFields($value); + } elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $bool)) { + if (in_array(strtolower(trim($key)), $bool)) { + $join = ' ' . strtoupper($key) . ' '; + } else { + $key = $join; + } + $value = $this->conditionKeysToString($value, $quoteValues, $model); + + if (strpos($join, 'NOT') !== false) { + if (strtoupper(trim($key)) == 'NOT') { + $key = 'AND ' . trim($key); + } + $not = 'NOT '; + } + + if (empty($value[1])) { + if ($not) { + $out[] = $not . '(' . $value[0] . ')'; + } else { + $out[] = $value[0] ; + } + } else { + $out[] = '(' . $not . '(' . implode(') ' . strtoupper($key) . ' (', $value) . '))'; + } + + } else { + if (is_object($value) && isset($value->type)) { + if ($value->type == 'identifier') { + $data .= $this->name($key) . ' = ' . $this->name($value->value); + } elseif ($value->type == 'expression') { + if (is_numeric($key)) { + $data .= $value->value; + } else { + $data .= $this->name($key) . ' = ' . $value->value; + } + } + } elseif (is_array($value) && !empty($value) && !$valueInsert) { + $keys = array_keys($value); + if ($keys === array_values($keys)) { + $count = count($value); + if ($count === 1) { + $data = $this->__quoteFields($key) . ' = ('; + } else { + $data = $this->__quoteFields($key) . ' IN ('; + } + if ($quoteValues) { + if (is_object($model)) { + $columnType = $model->getColumnType($key); + } + $data .= implode(', ', $this->value($value, $columnType)); + } + $data .= ')'; + } else { + $ret = $this->conditionKeysToString($value, $quoteValues, $model); + if (count($ret) > 1) { + $data = '(' . implode(') AND (', $ret) . ')'; + } elseif (isset($ret[0])) { + $data = $ret[0]; + } + } + } elseif (is_numeric($key) && !empty($value)) { + $data = $this->__quoteFields($value); + } else { + $data = $this->__parseKey($model, trim($key), $value); + } + + if ($data != null) { + $out[] = $data; + $data = null; + } + } + $c++; + } + return $out; + } + +/** + * Extracts a Model.field identifier and an SQL condition operator from a string, formats + * and inserts values, and composes them into an SQL snippet. + * + * @param Model $model Model object initiating the query + * @param string $key An SQL key snippet containing a field and optional SQL operator + * @param mixed $value The value(s) to be inserted in the string + * @return string + * @access private + */ + function __parseKey(&$model, $key, $value) { + $operatorMatch = '/^((' . implode(')|(', $this->__sqlOps); + $operatorMatch .= '\\x20)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is'; + $bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false)); + + if (!strpos($key, ' ')) { + $operator = '='; + } else { + list($key, $operator) = explode(' ', trim($key), 2); + + if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) { + $key = $key . ' ' . $operator; + $split = strrpos($key, ' '); + $operator = substr($key, $split); + $key = substr($key, 0, $split); + } + } + + $virtual = false; + if (is_object($model) && $model->isVirtualField($key)) { + $key = $this->__quoteFields($model->getVirtualField($key)); + $virtual = true; + } + + $type = (is_object($model) ? $model->getColumnType($key) : null); + + $null = ($value === null || (is_array($value) && empty($value))); + + if (strtolower($operator) === 'not') { + $data = $this->conditionKeysToString( + array($operator => array($key => $value)), true, $model + ); + return $data[0]; + } + + $value = $this->value($value, $type); + + if (!$virtual && $key !== '?') { + $isKey = (strpos($key, '(') !== false || strpos($key, ')') !== false); + $key = $isKey ? $this->__quoteFields($key) : $this->name($key); + } + + if ($bound) { + return String::insert($key . ' ' . trim($operator), $value); + } + + if (!preg_match($operatorMatch, trim($operator))) { + $operator .= ' ='; + } + $operator = trim($operator); + + if (is_array($value)) { + $value = implode(', ', $value); + + switch ($operator) { + case '=': + $operator = 'IN'; + break; + case '!=': + case '<>': + $operator = 'NOT IN'; + break; + } + $value = "({$value})"; + } elseif ($null) { + switch ($operator) { + case '=': + $operator = 'IS'; + break; + case '!=': + case '<>': + $operator = 'IS NOT'; + break; + } + } + if ($virtual) { + return "({$key}) {$operator} {$value}"; + } + return "{$key} {$operator} {$value}"; + } + +/** + * Quotes Model.fields + * + * @param string $conditions + * @return string or false if no match + * @access private + */ + function __quoteFields($conditions) { + $start = $end = null; + $original = $conditions; + + if (!empty($this->startQuote)) { + $start = preg_quote($this->startQuote); + } + if (!empty($this->endQuote)) { + $end = preg_quote($this->endQuote); + } + $conditions = str_replace(array($start, $end), '', $conditions); + $conditions = preg_replace_callback('/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', array(&$this, '__quoteMatchedField'), $conditions); + + if ($conditions !== null) { + return $conditions; + } + return $original; + } + +/** + * Auxiliary function to quote matches `Model.fields` from a preg_replace_callback call + * + * @param string matched string + * @return string quoted strig + * @access private + */ + function __quoteMatchedField($match) { + if (is_numeric($match[0])) { + return $match[0]; + } + return $this->name($match[0]); + } + +/** + * Returns a limit statement in the correct format for the particular database. + * + * @param integer $limit Limit of results returned + * @param integer $offset Offset from which to start results + * @return string SQL limit/offset statement + * @access public + */ + function limit($limit, $offset = null) { + if ($limit) { + $rt = ''; + if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) { + $rt = ' LIMIT'; + } + + if ($offset) { + $rt .= ' ' . $offset . ','; + } + + $rt .= ' ' . $limit; + return $rt; + } + return null; + } + +/** + * Returns an ORDER BY clause as a string. + * + * @param string $key Field reference, as a key (i.e. Post.title) + * @param string $direction Direction (ASC or DESC) + * @param object $model model reference (used to look for virtual field) + * @return string ORDER BY clause + * @access public + */ + function order($keys, $direction = 'ASC', $model = null) { + if (!is_array($keys)) { + $keys = array($keys); + } + $keys = array_filter($keys); + $result = array(); + while (!empty($keys)) { + list($key, $dir) = each($keys); + array_shift($keys); + + if (is_numeric($key)) { + $key = $dir; + $dir = $direction; + } + + if (is_string($key) && strpos($key, ',') && !preg_match('/\(.+\,.+\)/', $key)) { + $key = array_map('trim', explode(',', $key)); + } + if (is_array($key)) { + //Flatten the array + $key = array_reverse($key, true); + foreach ($key as $k => $v) { + if (is_numeric($k)) { + array_unshift($keys, $v); + } else { + $keys = array($k => $v) + $keys; + } + } + continue; + } elseif (is_object($key) && isset($key->type) && $key->type === 'expression') { + $result[] = $key->value; + continue; + } + + if (preg_match('/\\x20(ASC|DESC).*/i', $key, $_dir)) { + $dir = $_dir[0]; + $key = preg_replace('/\\x20(ASC|DESC).*/i', '', $key); + } + + $key = trim($key); + + if (is_object($model) && $model->isVirtualField($key)) { + $key = '(' . $this->__quoteFields($model->getVirtualField($key)) . ')'; + } + + if (strpos($key, '.')) { + $key = preg_replace_callback('/([a-zA-Z0-9_-]{1,})\\.([a-zA-Z0-9_-]{1,})/', array(&$this, '__quoteMatchedField'), $key); + } + if (!preg_match('/\s/', $key) && !strpos($key, '.')) { + $key = $this->name($key); + } + $key .= ' ' . trim($dir); + $result[] = $key; + } + if (!empty($result)) { + return ' ORDER BY ' . implode(', ', $result); + } + return ''; + } + +/** + * Create a GROUP BY SQL clause + * + * @param string $group Group By Condition + * @return mixed string condition or null + * @access public + */ + function group($group, $model = null) { + if ($group) { + if (!is_array($group)) { + $group = array($group); + } + foreach($group as $index => $key) { + if (is_object($model) && $model->isVirtualField($key)) { + $group[$index] = '(' . $model->getVirtualField($key) . ')'; + } + } + $group = implode(', ', $group); + return ' GROUP BY ' . $this->__quoteFields($group); + } + return null; + } + +/** + * Disconnects database, kills the connection and says the connection is closed. + * + * @return void + * @access public + */ + function close() { + $this->disconnect(); + } + +/** + * Checks if the specified table contains any record matching specified SQL + * + * @param Model $model Model to search + * @param string $sql SQL WHERE clause (condition only, not the "WHERE" part) + * @return boolean True if the table has a matching record, else false + * @access public + */ + function hasAny(&$Model, $sql) { + $sql = $this->conditions($sql); + $table = $this->fullTableName($Model); + $alias = $this->alias . $this->name($Model->alias); + $where = $sql ? "{$sql}" : ' WHERE 1 = 1'; + $id = $Model->escapeField(); + + $out = $this->fetchRow("SELECT COUNT({$id}) {$this->alias}count FROM {$table} {$alias}{$where}"); + + if (is_array($out)) { + return $out[0]['count']; + } + return false; + } + +/** + * Gets the length of a database-native column description, or null if no length + * + * @param string $real Real database-layer column type (i.e. "varchar(255)") + * @return mixed An integer or string representing the length of the column + * @access public + */ + function length($real) { + if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) { + trigger_error(__("FIXME: Can't parse field: " . $real, true), E_USER_WARNING); + $col = str_replace(array(')', 'unsigned'), '', $real); + $limit = null; + + if (strpos($col, '(') !== false) { + list($col, $limit) = explode('(', $col); + } + if ($limit != null) { + return intval($limit); + } + return null; + } + + $types = array( + 'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1 + ); + + list($real, $type, $length, $offset, $sign, $zerofill) = $result; + $typeArr = $type; + $type = $type[0]; + $length = $length[0]; + $offset = $offset[0]; + + $isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double')); + if ($isFloat && $offset) { + return $length.','.$offset; + } + + if (($real[0] == $type) && (count($real) == 1)) { + return null; + } + + if (isset($types[$type])) { + $length += $types[$type]; + if (!empty($sign)) { + $length--; + } + } elseif (in_array($type, array('enum', 'set'))) { + $length = 0; + foreach ($typeArr as $key => $enumValue) { + if ($key == 0) { + continue; + } + $tmpLength = strlen($enumValue); + if ($tmpLength > $length) { + $length = $tmpLength; + } + } + } + return intval($length); + } + +/** + * Translates between PHP boolean values and Database (faked) boolean values + * + * @param mixed $data Value to be translated + * @return mixed Converted boolean value + * @access public + */ + function boolean($data) { + if ($data === true || $data === false) { + if ($data === true) { + return 1; + } + return 0; + } else { + return !empty($data); + } + } + +/** + * Inserts multiple values into a table + * + * @param string $table + * @param string $fields + * @param array $values + * @access protected + */ + function insertMulti($table, $fields, $values) { + $table = $this->fullTableName($table); + if (is_array($fields)) { + $fields = implode(', ', array_map(array(&$this, 'name'), $fields)); + } + $count = count($values); + for ($x = 0; $x < $count; $x++) { + $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}"); + } + } + +/** + * Returns an array of the indexes in given datasource name. + * + * @param string $model Name of model to inspect + * @return array Fields in table. Keys are column and unique + * @access public + */ + function index($model) { + return false; + } + +/** + * Generate a database-native schema for the given Schema object + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $tableName Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + * @access public + */ + function createSchema($schema, $tableName = null) { + if (!is_a($schema, 'CakeSchema')) { + trigger_error(__('Invalid schema object', true), E_USER_WARNING); + return null; + } + $out = ''; + + foreach ($schema->tables as $curTable => $columns) { + if (!$tableName || $tableName == $curTable) { + $cols = $colList = $indexes = $tableParameters = array(); + $primary = null; + $table = $this->fullTableName($curTable); + + foreach ($columns as $name => $col) { + if (is_string($col)) { + $col = array('type' => $col); + } + if (isset($col['key']) && $col['key'] == 'primary') { + $primary = $name; + } + if ($name !== 'indexes' && $name !== 'tableParameters') { + $col['name'] = $name; + if (!isset($col['type'])) { + $col['type'] = 'string'; + } + $cols[] = $this->buildColumn($col); + } elseif ($name == 'indexes') { + $indexes = array_merge($indexes, $this->buildIndex($col, $table)); + } elseif ($name == 'tableParameters') { + $tableParameters = array_merge($tableParameters, $this->buildTableParameters($col, $table)); + } + } + if (empty($indexes) && !empty($primary)) { + $col = array('PRIMARY' => array('column' => $primary, 'unique' => 1)); + $indexes = array_merge($indexes, $this->buildIndex($col, $table)); + } + $columns = $cols; + $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes', 'tableParameters')) . "\n\n"; + } + } + return $out; + } + +/** + * Generate a alter syntax from CakeSchema::compare() + * + * @param unknown_type $schema + * @return boolean + */ + function alterSchema($compare, $table = null) { + return false; + } + +/** + * Generate a "drop table" statement for the given Schema object + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $table Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + * @access public + */ + function dropSchema($schema, $table = null) { + if (!is_a($schema, 'CakeSchema')) { + trigger_error(__('Invalid schema object', true), E_USER_WARNING); + return null; + } + $out = ''; + + foreach ($schema->tables as $curTable => $columns) { + if (!$table || $table == $curTable) { + $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . ";\n"; + } + } + return $out; + } + +/** + * Generate a database-native column schema string + * + * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]), + * where options can be 'default', 'length', or 'key'. + * @return string + * @access public + */ + function buildColumn($column) { + $name = $type = null; + extract(array_merge(array('null' => true), $column)); + + if (empty($name) || empty($type)) { + trigger_error(__('Column name or type not defined in schema', true), E_USER_WARNING); + return null; + } + + if (!isset($this->columns[$type])) { + trigger_error(sprintf(__('Column type %s does not exist', true), $type), E_USER_WARNING); + return null; + } + + $real = $this->columns[$type]; + $out = $this->name($name) . ' ' . $real['name']; + + if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) { + if (isset($column['length'])) { + $length = $column['length']; + } elseif (isset($column['limit'])) { + $length = $column['limit']; + } elseif (isset($real['length'])) { + $length = $real['length']; + } else { + $length = $real['limit']; + } + $out .= '(' . $length . ')'; + } + + if (($column['type'] == 'integer' || $column['type'] == 'float' ) && isset($column['default']) && $column['default'] === '') { + $column['default'] = null; + } + $out = $this->_buildFieldParameters($out, $column, 'beforeDefault'); + + if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') { + $out .= ' ' . $this->columns['primary_key']['name']; + } elseif (isset($column['key']) && $column['key'] == 'primary') { + $out .= ' NOT NULL'; + } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) { + $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL'; + } elseif (isset($column['default'])) { + $out .= ' DEFAULT ' . $this->value($column['default'], $type); + } elseif ($type !== 'timestamp' && !empty($column['null'])) { + $out .= ' DEFAULT NULL'; + } elseif ($type === 'timestamp' && !empty($column['null'])) { + $out .= ' NULL'; + } elseif (isset($column['null']) && $column['null'] == false) { + $out .= ' NOT NULL'; + } + if ($type == 'timestamp' && isset($column['default']) && strtolower($column['default']) == 'current_timestamp') { + $out = str_replace(array("'CURRENT_TIMESTAMP'", "'current_timestamp'"), 'CURRENT_TIMESTAMP', $out); + } + $out = $this->_buildFieldParameters($out, $column, 'afterDefault'); + return $out; + } + +/** + * Build the field parameters, in a position + * + * @param string $columnString The partially built column string + * @param array $columnData The array of column data. + * @param string $position The position type to use. 'beforeDefault' or 'afterDefault' are common + * @return string a built column with the field parameters added. + * @access public + */ + function _buildFieldParameters($columnString, $columnData, $position) { + foreach ($this->fieldParameters as $paramName => $value) { + if (isset($columnData[$paramName]) && $value['position'] == $position) { + if (isset($value['options']) && !in_array($columnData[$paramName], $value['options'])) { + continue; + } + $val = $columnData[$paramName]; + if ($value['quote']) { + $val = $this->value($val); + } + $columnString .= ' ' . $value['value'] . $value['join'] . $val; + } + } + return $columnString; + } + +/** + * Format indexes for create table + * + * @param array $indexes + * @param string $table + * @return array + * @access public + */ + function buildIndex($indexes, $table = null) { + $join = array(); + foreach ($indexes as $name => $value) { + $out = ''; + if ($name == 'PRIMARY') { + $out .= 'PRIMARY '; + $name = null; + } else { + if (!empty($value['unique'])) { + $out .= 'UNIQUE '; + } + $name = $this->startQuote . $name . $this->endQuote; + } + if (is_array($value['column'])) { + $out .= 'KEY ' . $name . ' (' . implode(', ', array_map(array(&$this, 'name'), $value['column'])) . ')'; + } else { + $out .= 'KEY ' . $name . ' (' . $this->name($value['column']) . ')'; + } + $join[] = $out; + } + return $join; + } + +/** + * Read additional table parameters + * + * @param array $parameters + * @param string $table + * @return array + * @access public + */ + function readTableParameters($name) { + $parameters = array(); + if ($this->isInterfaceSupported('listDetailedSources')) { + $currentTableDetails = $this->listDetailedSources($name); + foreach ($this->tableParameters as $paramName => $parameter) { + if (!empty($parameter['column']) && !empty($currentTableDetails[$parameter['column']])) { + $parameters[$paramName] = $currentTableDetails[$parameter['column']]; + } + } + } + return $parameters; + } + +/** + * Format parameters for create table + * + * @param array $parameters + * @param string $table + * @return array + * @access public + */ + function buildTableParameters($parameters, $table = null) { + $result = array(); + foreach ($parameters as $name => $value) { + if (isset($this->tableParameters[$name])) { + if ($this->tableParameters[$name]['quote']) { + $value = $this->value($value); + } + $result[] = $this->tableParameters[$name]['value'] . $this->tableParameters[$name]['join'] . $value; + } + } + return $result; + } + +/** + * Guesses the data type of an array + * + * @param string $value + * @return void + * @access public + */ + function introspectType($value) { + if (!is_array($value)) { + if ($value === true || $value === false) { + return 'boolean'; + } + if (is_float($value) && floatval($value) === $value) { + return 'float'; + } + if (is_int($value) && intval($value) === $value) { + return 'integer'; + } + if (is_string($value) && strlen($value) > 255) { + return 'text'; + } + return 'string'; + } + + $isAllFloat = $isAllInt = true; + $containsFloat = $containsInt = $containsString = false; + foreach ($value as $key => $valElement) { + $valElement = trim($valElement); + if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) { + $isAllFloat = false; + } else { + $containsFloat = true; + continue; + } + if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) { + $isAllInt = false; + } else { + $containsInt = true; + continue; + } + $containsString = true; + } + + if ($isAllFloat) { + return 'float'; + } + if ($isAllInt) { + return 'integer'; + } + + if ($containsInt && !$containsString) { + return 'integer'; + } + return 'string'; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo_source.php.bak b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo_source.php.bak new file mode 100755 index 000000000..a851123e9 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo_source.php.bak @@ -0,0 +1,2925 @@ + 'primary', 'MUL' => 'index', 'UNI' => 'unique'); + +/** + * Database keyword used to assign aliases to identifiers. + * + * @var string + * @access public + */ + var $alias = 'AS '; + +/** + * Caches result from query parsing operations. Cached results for both DboSource::name() and + * DboSource::conditions() will be stored here. Method caching uses `crc32()` which is + * fast but can collisions more easily than other hashing algorithms. If you have problems + * with collisions, set DboSource::$cacheMethods to false. + * + * @var array + * @access public + */ + var $methodCache = array(); + +/** + * Whether or not to cache the results of DboSource::name() and DboSource::conditions() + * into the memory cache. Set to false to disable the use of the memory cache. + * + * @var boolean. + * @access public + */ + var $cacheMethods = true ; + +/** + * Bypass automatic adding of joined fields/associations. + * + * @var boolean + * @access private + */ + var $__bypass = false; + +/** + * The set of valid SQL operations usable in a WHERE statement + * + * @var array + * @access private + */ + var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to'); + +/** + * Index of basic SQL commands + * + * @var array + * @access protected + */ + var $_commands = array( + 'begin' => 'BEGIN', + 'commit' => 'COMMIT', + 'rollback' => 'ROLLBACK' + ); + +/** + * Separator string for virtualField composition + * + * @var string + */ + var $virtualFieldSeparator = '__'; + +/** + * List of table engine specific parameters used on table creating + * + * @var array + * @access public + */ + var $tableParameters = array(); + +/** + * List of engine specific additional field parameters used on table creating + * + * @var array + * @access public + */ + var $fieldParameters = array(); + +/** + * Constructor + * + * @param array $config Array of configuration information for the Datasource. + * @param boolean $autoConnect Whether or not the datasource should automatically connect. + * @access public + */ + function __construct($config = null, $autoConnect = true) { + if (!isset($config['prefix'])) { + $config['prefix'] = ''; + } + parent::__construct($config); + $this->fullDebug = Configure::read() > 1; + if (!$this->enabled()) { + return false; + } + if ($autoConnect) { + return $this->connect(); + } else { + return true; + } + } + +/** + * Reconnects to database server with optional new settings + * + * @param array $config An array defining the new configuration settings + * @return boolean True on success, false on failure + * @access public + */ + function reconnect($config = array()) { + $this->disconnect(); + $this->setConfig($config); + $this->_sources = null; + + return $this->connect(); + } + +/** + * Prepares a value, or an array of values for database queries by quoting and escaping them. + * + * @param mixed $data A value or an array of values to prepare. + * @param string $column The column into which this data will be inserted + * @param boolean $read Value to be used in READ or WRITE context + * @return mixed Prepared value or array of values. + * @access public + */ + function value($data, $column = null, $read = true) { + if (is_array($data) && !empty($data)) { + return array_map( + array(&$this, 'value'), + $data, array_fill(0, count($data), $column), array_fill(0, count($data), $read) + ); + } elseif (is_object($data) && isset($data->type)) { + if ($data->type == 'identifier') { + return $this->name($data->value); + } elseif ($data->type == 'expression') { + return $data->value; + } + } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) { + return $data; + } else { + return null; + } + } + +/** + * Returns an object to represent a database identifier in a query + * + * @param string $identifier + * @return object An object representing a database identifier to be used in a query + * @access public + */ + function identifier($identifier) { + $obj = new stdClass(); + $obj->type = 'identifier'; + $obj->value = $identifier; + return $obj; + } + +/** + * Returns an object to represent a database expression in a query + * + * @param string $expression + * @return object An object representing a database expression to be used in a query + * @access public + */ + function expression($expression) { + $obj = new stdClass(); + $obj->type = 'expression'; + $obj->value = $expression; + return $obj; + } + +/** + * Executes given SQL statement. + * + * @param string $sql SQL statement + * @return boolean + * @access public + */ + function rawQuery($sql) { + $this->took = $this->error = $this->numRows = false; + return $this->execute($sql); + } + +/** + * Queries the database with given SQL statement, and obtains some metadata about the result + * (rows affected, timing, any errors, number of rows in resultset). The query is also logged. + * If Configure::read('debug') is set, the log is shown all the time, else it is only shown on errors. + * + * ### Options + * + * - stats - Collect meta data stats for this query. Stats include time take, rows affected, + * any errors, and number of rows returned. Defaults to `true`. + * - log - Whether or not the query should be logged to the memory log. + * + * @param string $sql + * @param array $options + * @return mixed Resource or object representing the result set, or false on failure + * @access public + */ + function execute($sql, $options = array()) { + $defaults = array('stats' => true, 'log' => $this->fullDebug); + $options = array_merge($defaults, $options); + + $t = getMicrotime(); + $this->_result = $this->_execute($sql); + if ($options['stats']) { + $this->took = round((getMicrotime() - $t) * 1000, 0); + $this->affected = $this->lastAffected(); + $this->error = $this->lastError(); + $this->numRows = $this->lastNumRows(); + } + + if ($options['log']) { + $this->logQuery($sql); + } + + if ($this->error) { + $this->showQuery($sql); + return false; + } + return $this->_result; + } + +/** + * DataSource Query abstraction + * + * @return resource Result resource identifier. + * @access public + */ + function query() { + $args = func_get_args(); + $fields = null; + $order = null; + $limit = null; + $page = null; + $recursive = null; + + if (count($args) == 1) { + return $this->fetchAll($args[0]); + + } elseif (count($args) > 1 && (strpos(strtolower($args[0]), 'findby') === 0 || strpos(strtolower($args[0]), 'findallby') === 0)) { + $params = $args[1]; + + if (strpos(strtolower($args[0]), 'findby') === 0) { + $all = false; + $field = Inflector::underscore(preg_replace('/^findBy/i', '', $args[0])); + } else { + $all = true; + $field = Inflector::underscore(preg_replace('/^findAllBy/i', '', $args[0])); + } + + $or = (strpos($field, '_or_') !== false); + if ($or) { + $field = explode('_or_', $field); + } else { + $field = explode('_and_', $field); + } + $off = count($field) - 1; + + if (isset($params[1 + $off])) { + $fields = $params[1 + $off]; + } + + if (isset($params[2 + $off])) { + $order = $params[2 + $off]; + } + + if (!array_key_exists(0, $params)) { + return false; + } + + $c = 0; + $conditions = array(); + + foreach ($field as $f) { + $conditions[$args[2]->alias . '.' . $f] = $params[$c]; + $c++; + } + + if ($or) { + $conditions = array('OR' => $conditions); + } + + if ($all) { + if (isset($params[3 + $off])) { + $limit = $params[3 + $off]; + } + + if (isset($params[4 + $off])) { + $page = $params[4 + $off]; + } + + if (isset($params[5 + $off])) { + $recursive = $params[5 + $off]; + } + return $args[2]->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive')); + } else { + if (isset($params[3 + $off])) { + $recursive = $params[3 + $off]; + } + return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive')); + } + } else { + if (isset($args[1]) && $args[1] === true) { + return $this->fetchAll($args[0], true); + } else if (isset($args[1]) && !is_array($args[1]) ) { + return $this->fetchAll($args[0], false); + } else if (isset($args[1]) && is_array($args[1])) { + $offset = 0; + if (isset($args[2])) { + $cache = $args[2]; + } else { + $cache = true; + } + $args[1] = array_map(array(&$this, 'value'), $args[1]); + return $this->fetchAll(String::insert($args[0], $args[1]), $cache); + } + } + } + +/** + * Returns a row from current resultset as an array + * + * @return array The fetched row as an array + * @access public + */ + function fetchRow($sql = null) { + if (!empty($sql) && is_string($sql) && strlen($sql) > 5) { + if (!$this->execute($sql)) { + return null; + } + } + + if ($this->hasResult()) { + $this->resultSet($this->_result); + $resultRow = $this->fetchResult(); + if (!empty($resultRow)) { + $this->fetchVirtualField($resultRow); + } + return $resultRow; + } else { + return null; + } + } + +/** + * Returns an array of all result rows for a given SQL query. + * Returns false if no rows matched. + * + * @param string $sql SQL statement + * @param boolean $cache Enables returning/storing cached query results + * @return array Array of resultset rows, or false if no rows matched + * @access public + */ + function fetchAll($sql, $cache = true, $modelName = null) { + if ($cache && isset($this->_queryCache[$sql])) { + if (preg_match('/^\s*select/i', $sql)) { + return $this->_queryCache[$sql]; + } + } + + if ($this->execute($sql)) { + $out = array(); + + $first = $this->fetchRow(); + if ($first != null) { + $out[] = $first; + } + while ($this->hasResult() && $item = $this->fetchResult()) { + $this->fetchVirtualField($item); + $out[] = $item; + } + + if ($cache) { + if (strpos(trim(strtolower($sql)), 'select') !== false) { + $this->_queryCache[$sql] = $out; + } + } + if (empty($out) && is_bool($this->_result)) { + return $this->_result; + } + return $out; + } else { + return false; + } + } + +/** + * Modifies $result array to place virtual fields in model entry where they belongs to + * + * @param array $resut REference to the fetched row + * @return void + */ + function fetchVirtualField(&$result) { + if (isset($result[0]) && is_array($result[0])) { + foreach ($result[0] as $field => $value) { + if (strpos($field, $this->virtualFieldSeparator) === false) { + continue; + } + list($alias, $virtual) = explode($this->virtualFieldSeparator, $field); + + if (!ClassRegistry::isKeySet($alias)) { + return; + } + $model = ClassRegistry::getObject($alias); + if ($model->isVirtualField($virtual)) { + $result[$alias][$virtual] = $value; + unset($result[0][$field]); + } + } + if (empty($result[0])) { + unset($result[0]); + } + } + } + +/** + * Returns a single field of the first of query results for a given SQL query, or false if empty. + * + * @param string $name Name of the field + * @param string $sql SQL query + * @return mixed Value of field read. + * @access public + */ + function field($name, $sql) { + $data = $this->fetchRow($sql); + if (!isset($data[$name]) || empty($data[$name])) { + return false; + } else { + return $data[$name]; + } + } + +/** + * Empties the method caches. + * These caches are used by DboSource::name() and DboSource::conditions() + * + * @return void + */ + function flushMethodCache() { + $this->methodCache = array(); + } + +/** + * Cache a value into the methodCaches. Will respect the value of DboSource::$cacheMethods. + * Will retrieve a value from the cache if $value is null. + * + * If caching is disabled and a write is attempted, the $value will be returned. + * A read will either return the value or null. + * + * @param string $method Name of the method being cached. + * @param string $key The keyname for the cache operation. + * @param mixed $value The value to cache into memory. + * @return mixed Either null on failure, or the value if its set. + */ + function cacheMethod($method, $key, $value = null) { + if ($this->cacheMethods === false) { + return $value; + } + if ($value === null) { + return (isset($this->methodCache[$method][$key])) ? $this->methodCache[$method][$key] : null; + } + return $this->methodCache[$method][$key] = $value; + } + +/** + * Returns a quoted name of $data for use in an SQL statement. + * Strips fields out of SQL functions before quoting. + * + * Results of this method are stored in a memory cache. This improves performance, but + * because the method uses a simple hashing algorithm it can infrequently have collisions. + * Setting DboSource::$cacheMethods to false will disable the memory cache. + * + * @param mixed $data Either a string with a column to quote. An array of columns to quote or an + * object from DboSource::expression() or DboSource::identifier() + * @return string SQL field + * @access public + */ + function name($data) { + if (is_object($data) && isset($data->type)) { + return $data->value; + } + if ($data === '*') { + return '*'; + } + if (is_array($data)) { + foreach ($data as $i => $dataItem) { + $data[$i] = $this->name($dataItem); + } + return $data; + } + $cacheKey = crc32($this->startQuote.$data.$this->endQuote); + if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) { + return $return; + } + $data = trim($data); + if (preg_match('/^[\w-]+(?:\.[^ \*]*)*$/', $data)) { // string, string.string + if (strpos($data, '.') === false) { // string + return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote); + } + $items = explode('.', $data); + return $this->cacheMethod(__FUNCTION__, $cacheKey, + $this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote + ); + } + if (preg_match('/^[\w-]+\.\*$/', $data)) { // string.* + return $this->cacheMethod(__FUNCTION__, $cacheKey, + $this->startQuote . str_replace('.*', $this->endQuote . '.*', $data) + ); + } + if (preg_match('/^([\w-]+)\((.*)\)$/', $data, $matches)) { // Functions + return $this->cacheMethod(__FUNCTION__, $cacheKey, + $matches[1] . '(' . $this->name($matches[2]) . ')' + ); + } + if ( + preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+' . preg_quote($this->alias) . '\s*([\w-]+)$/i', $data, $matches + )) { + return $this->cacheMethod( + __FUNCTION__, $cacheKey, + preg_replace( + '/\s{2,}/', ' ', $this->name($matches[1]) . ' ' . $this->alias . ' ' . $this->name($matches[3]) + ) + ); + } + if (preg_match('/^[\w-_\s]*[\w-_]+/', $data)) { + return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote); + } + return $this->cacheMethod(__FUNCTION__, $cacheKey, $data); + } + +/** + * Checks if the source is connected to the database. + * + * @return boolean True if the database is connected, else false + * @access public + */ + function isConnected() { + return $this->connected; + } + +/** + * Checks if the result is valid + * + * @return boolean True if the result is valid else false + * @access public + */ + function hasResult() { + return is_resource($this->_result); + } + +/** + * Get the query log as an array. + * + * @param boolean $sorted Get the queries sorted by time taken, defaults to false. + * @return array Array of queries run as an array + * @access public + */ + function getLog($sorted = false, $clear = true) { + if ($sorted) { + $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC); + } else { + $log = $this->_queriesLog; + } + if ($clear) { + $this->_queriesLog = array(); + } + return array('log' => $log, 'count' => $this->_queriesCnt, 'time' => $this->_queriesTime); + } + +/** + * Outputs the contents of the queries log. If in a non-CLI environment the sql_log element + * will be rendered and output. If in a CLI environment, a plain text log is generated. + * + * @param boolean $sorted Get the queries sorted by time taken, defaults to false. + * @return void + */ + function showLog($sorted = false) { + $log = $this->getLog($sorted, false); + if (empty($log['log'])) { + return; + } + if (PHP_SAPI != 'cli') { + App::import('Core', 'View'); + $controller = null; + $View =& new View($controller, false); + $View->set('logs', array($this->configKeyName => $log)); + echo $View->element('sql_dump', array('_forced_from_dbo_' => true)); + } else { + foreach ($log['log'] as $k => $i) { + print (($k + 1) . ". {$i['query']} {$i['error']}\n"); + } + } + } + +/** + * Log given SQL query. + * + * @param string $sql SQL statement + * @todo: Add hook to log errors instead of returning false + * @access public + */ + function logQuery($sql) { + $this->_queriesCnt++; + $this->_queriesTime += $this->took; + $this->_queriesLog[] = array( + 'query' => $sql, + 'error' => $this->error, + 'affected' => $this->affected, + 'numRows' => $this->numRows, + 'took' => $this->took + ); + if (count($this->_queriesLog) > $this->_queriesLogMax) { + array_pop($this->_queriesLog); + } + if ($this->error) { + return false; + } + } + +/** + * Output information about an SQL query. The SQL statement, number of rows in resultset, + * and execution time in microseconds. If the query fails, an error is output instead. + * + * @param string $sql Query to show information on. + * @access public + */ + function showQuery($sql) { + $error = $this->error; + if (strlen($sql) > 200 && !$this->fullDebug && Configure::read() > 1) { + $sql = substr($sql, 0, 200) . '[...]'; + } + if (Configure::read() > 0) { + $out = null; + if ($error) { + trigger_error('' . __('SQL Error:', true) . " {$this->error}", E_USER_WARNING); + } else { + $out = ('[' . sprintf(__('Aff:%s Num:%s Took:%sms', true), $this->affected, $this->numRows, $this->took) . ']'); + } + pr(sprintf('

' . __('Query:', true) . ' %s %s

', $sql, $out)); + } + } + +/** + * Gets full table name including prefix + * + * @param mixed $model Either a Model object or a string table name. + * @param boolean $quote Whether you want the table name quoted. + * @return string Full quoted table name + * @access public + */ + function fullTableName($model, $quote = true) { + if (is_object($model)) { + $table = $model->tablePrefix . $model->table; + } elseif (isset($this->config['prefix'])) { + $table = $this->config['prefix'] . strval($model); + } else { + $table = strval($model); + } + if ($quote) { + return $this->name($table); + } + return $table; + } + +/** + * The "C" in CRUD + * + * Creates new records in the database. + * + * @param Model $model Model object that the record is for. + * @param array $fields An array of field names to insert. If null, $model->data will be + * used to generate field names. + * @param array $values An array of values with keys matching the fields. If null, $model->data will + * be used to generate values. + * @return boolean Success + * @access public + */ + function create(&$model, $fields = null, $values = null) { + $id = null; + + if ($fields == null) { + unset($fields, $values); + $fields = array_keys($model->data); + $values = array_values($model->data); + } + $count = count($fields); + + for ($i = 0; $i < $count; $i++) { + $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]), false); + } + for ($i = 0; $i < $count; $i++) { + $fieldInsert[] = $this->name($fields[$i]); + if ($fields[$i] == $model->primaryKey) { + $id = $values[$i]; + } + } + $query = array( + 'table' => $this->fullTableName($model), + 'fields' => implode(', ', $fieldInsert), + 'values' => implode(', ', $valueInsert) + ); + + if ($this->execute($this->renderStatement('create', $query))) { + if (empty($id)) { + $id = $this->lastInsertId($this->fullTableName($model, false), $model->primaryKey); + } + $model->setInsertID($id); + $model->id = $id; + return true; + } else { + $model->onError(); + return false; + } + } + +/** + * The "R" in CRUD + * + * Reads record(s) from the database. + * + * @param Model $model A Model object that the query is for. + * @param array $queryData An array of queryData information containing keys similar to Model::find() + * @param integer $recursive Number of levels of association + * @return mixed boolean false on error/failure. An array of results on success. + */ + function read(&$model, $queryData = array(), $recursive = null) { + $queryData = $this->__scrubQueryData($queryData); + + $null = null; + $array = array(); + $linkedModels = array(); + $this->__bypass = false; + $this->__booleans = array(); + + if ($recursive === null && isset($queryData['recursive'])) { + $recursive = $queryData['recursive']; + } + + if (!is_null($recursive)) { + $_recursive = $model->recursive; + $model->recursive = $recursive; + } + + if (!empty($queryData['fields'])) { + $this->__bypass = true; + $queryData['fields'] = $this->fields($model, null, $queryData['fields']); + } else { + $queryData['fields'] = $this->fields($model); + } + + $_associations = $model->__associations; + + if ($model->recursive == -1) { + $_associations = array(); + } else if ($model->recursive == 0) { + unset($_associations[2], $_associations[3]); + } + + foreach ($_associations as $type) { + foreach ($model->{$type} as $assoc => $assocData) { + $linkModel =& $model->{$assoc}; + $external = isset($assocData['external']); + + if ($model->useDbConfig == $linkModel->useDbConfig) { + if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) { + $linkedModels[$type . '/' . $assoc] = true; + } + } + } + } + + $query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null); + + $resultSet = $this->fetchAll($query, $model->cacheQueries, $model->alias); + + if ($resultSet === false) { + $model->onError(); + return false; + } + + $filtered = $this->__filterResults($resultSet, $model); + + if ($model->recursive > -1) { + foreach ($_associations as $type) { + foreach ($model->{$type} as $assoc => $assocData) { + $linkModel =& $model->{$assoc}; + + if (empty($linkedModels[$type . '/' . $assoc])) { + if ($model->useDbConfig == $linkModel->useDbConfig) { + $db =& $this; + } else { + $db =& ConnectionManager::getDataSource($linkModel->useDbConfig); + } + } elseif ($model->recursive > 1 && ($type == 'belongsTo' || $type == 'hasOne')) { + $db =& $this; + } + + if (isset($db) && method_exists($db, 'queryAssociation')) { + $stack = array($assoc); + $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack); + unset($db); + + if ($type === 'hasMany') { + $filtered []= $assoc; + } + } + } + } + $this->__filterResults($resultSet, $model, $filtered); + } + + if (!is_null($recursive)) { + $model->recursive = $_recursive; + } + return $resultSet; + } + +/** + * Passes association results thru afterFind filters of corresponding model + * + * @param array $results Reference of resultset to be filtered + * @param object $model Instance of model to operate against + * @param array $filtered List of classes already filtered, to be skipped + * @return array Array of results that have been filtered through $model->afterFind + * @access private + */ + function __filterResults(&$results, &$model, $filtered = array()) { + $filtering = array(); + $count = count($results); + + for ($i = 0; $i < $count; $i++) { + if (is_array($results[$i])) { + $classNames = array_keys($results[$i]); + $count2 = count($classNames); + + for ($j = 0; $j < $count2; $j++) { + $className = $classNames[$j]; + if ($model->alias != $className && !in_array($className, $filtered)) { + if (!in_array($className, $filtering)) { + $filtering[] = $className; + } + + if (isset($model->{$className}) && is_object($model->{$className})) { + $data = $model->{$className}->afterFind(array(array($className => $results[$i][$className])), false); + } + if (isset($data[0][$className])) { + $results[$i][$className] = $data[0][$className]; + } + } + } + } + } + return $filtering; + } + +/** + * Queries associations. Used to fetch results on recursive models. + * + * @param Model $model Primary Model object + * @param Model $linkModel Linked model that + * @param string $type Association type, one of the model association types ie. hasMany + * @param unknown_type $association + * @param unknown_type $assocData + * @param array $queryData + * @param boolean $external Whether or not the association query is on an external datasource. + * @param array $resultSet Existing results + * @param integer $recursive Number of levels of association + * @param array $stack + */ + function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) { + if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) { + if (!isset($resultSet) || !is_array($resultSet)) { + if (Configure::read() > 0) { + echo '
' . sprintf(__('SQL Error in model %s:', true), $model->alias) . ' '; + if (isset($this->error) && $this->error != null) { + echo $this->error; + } + echo '
'; + } + return null; + } + $count = count($resultSet); + + if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) { + $ins = $fetch = array(); + for ($i = 0; $i < $count; $i++) { + if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) { + $ins[] = $in; + } + } + + if (!empty($ins)) { + $ins = array_unique($ins); + $fetch = $this->fetchAssociated($model, $query, $ins); + } + + if (!empty($fetch) && is_array($fetch)) { + if ($recursive > 0) { + foreach ($linkModel->__associations as $type1) { + foreach ($linkModel->{$type1} as $assoc1 => $assocData1) { + $deepModel =& $linkModel->{$assoc1}; + $tmpStack = $stack; + $tmpStack[] = $assoc1; + + if ($linkModel->useDbConfig === $deepModel->useDbConfig) { + $db =& $this; + } else { + $db =& ConnectionManager::getDataSource($deepModel->useDbConfig); + } + $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack); + } + } + } + } + $this->__filterResults($fetch, $model); + return $this->__mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive); + } elseif ($type === 'hasAndBelongsToMany') { + $ins = $fetch = array(); + for ($i = 0; $i < $count; $i++) { + if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) { + $ins[] = $in; + } + } + if (!empty($ins)) { + $ins = array_unique($ins); + if (count($ins) > 1) { + $query = str_replace('{$__cakeID__$}', '(' .implode(', ', $ins) .')', $query); + $query = str_replace('= (', 'IN (', $query); + } else { + $query = str_replace('{$__cakeID__$}',$ins[0], $query); + } + + $query = str_replace(' WHERE 1 = 1', '', $query); + } + + $foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey']; + $joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']); + list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys); + $habtmFieldsCount = count($habtmFields); + $q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack); + + if ($q != false) { + $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias); + } else { + $fetch = null; + } + } + + for ($i = 0; $i < $count; $i++) { + $row =& $resultSet[$i]; + + if ($type !== 'hasAndBelongsToMany') { + $q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack); + if ($q != false) { + $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias); + } else { + $fetch = null; + } + } + $selfJoin = false; + + if ($linkModel->name === $model->name) { + $selfJoin = true; + } + + if (!empty($fetch) && is_array($fetch)) { + if ($recursive > 0) { + foreach ($linkModel->__associations as $type1) { + foreach ($linkModel->{$type1} as $assoc1 => $assocData1) { + $deepModel =& $linkModel->{$assoc1}; + + if (($type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) { + $tmpStack = $stack; + $tmpStack[] = $assoc1; + if ($linkModel->useDbConfig == $deepModel->useDbConfig) { + $db =& $this; + } else { + $db =& ConnectionManager::getDataSource($deepModel->useDbConfig); + } + $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack); + } + } + } + } + if ($type == 'hasAndBelongsToMany') { + $uniqueIds = $merge = array(); + + foreach ($fetch as $j => $data) { + if ( + (isset($data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey]) + ) { + if ($habtmFieldsCount <= 2) { + unset($data[$with]); + } + $merge[] = $data; + } + } + if (empty($merge) && !isset($row[$association])) { + $row[$association] = $merge; + } else { + $this->__mergeAssociation($resultSet[$i], $merge, $association, $type); + } + } else { + $this->__mergeAssociation($resultSet[$i], $fetch, $association, $type, $selfJoin); + } + if (isset($resultSet[$i][$association])) { + $resultSet[$i][$association] = $linkModel->afterFind($resultSet[$i][$association], false); + } + } else { + $tempArray[0][$association] = false; + $this->__mergeAssociation($resultSet[$i], $tempArray, $association, $type, $selfJoin); + } + } + } + } + +/** + * A more efficient way to fetch associations. Woohoo! + * + * @param model $model Primary model object + * @param string $query Association query + * @param array $ids Array of IDs of associated records + * @return array Association results + * @access public + */ + function fetchAssociated($model, $query, $ids) { + $query = str_replace('{$__cakeID__$}', implode(', ', $ids), $query); + if (count($ids) > 1) { + $query = str_replace('= (', 'IN (', $query); + } + return $this->fetchAll($query, $model->cacheQueries, $model->alias); + } + +/** + * mergeHasMany - Merge the results of hasMany relations. + * + * + * @param array $resultSet Data to merge into + * @param array $merge Data to merge + * @param string $association Name of Model being Merged + * @param object $model Model being merged onto + * @param object $linkModel Model being merged + * @return void + */ + function __mergeHasMany(&$resultSet, $merge, $association, &$model, &$linkModel) { + foreach ($resultSet as $i => $value) { + $count = 0; + $merged[$association] = array(); + foreach ($merge as $j => $data) { + if (isset($value[$model->alias]) && $value[$model->alias][$model->primaryKey] === $data[$association][$model->hasMany[$association]['foreignKey']]) { + if (count($data) > 1) { + $data = array_merge($data[$association], $data); + unset($data[$association]); + foreach ($data as $key => $name) { + if (is_numeric($key)) { + $data[$association][] = $name; + unset($data[$key]); + } + } + $merged[$association][] = $data; + } else { + $merged[$association][] = $data[$association]; + } + } + $count++; + } + if (isset($value[$model->alias])) { + $resultSet[$i] = Set::pushDiff($resultSet[$i], $merged); + unset($merged); + } + } + } + +/** + * Enter description here... + * + * @param unknown_type $data + * @param unknown_type $merge + * @param unknown_type $association + * @param unknown_type $type + * @param boolean $selfJoin + * @access private + */ + function __mergeAssociation(&$data, $merge, $association, $type, $selfJoin = false) { + if (isset($merge[0]) && !isset($merge[0][$association])) { + $association = Inflector::pluralize($association); + } + + if ($type == 'belongsTo' || $type == 'hasOne') { + if (isset($merge[$association])) { + $data[$association] = $merge[$association][0]; + } else { + if (count($merge[0][$association]) > 1) { + foreach ($merge[0] as $assoc => $data2) { + if ($assoc != $association) { + $merge[0][$association][$assoc] = $data2; + } + } + } + if (!isset($data[$association])) { + if ($merge[0][$association] != null) { + $data[$association] = $merge[0][$association]; + } else { + $data[$association] = array(); + } + } else { + if (is_array($merge[0][$association])) { + foreach ($data[$association] as $k => $v) { + if (!is_array($v)) { + $dataAssocTmp[$k] = $v; + } + } + + foreach ($merge[0][$association] as $k => $v) { + if (!is_array($v)) { + $mergeAssocTmp[$k] = $v; + } + } + $dataKeys = array_keys($data); + $mergeKeys = array_keys($merge[0]); + + if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) { + $data[$association][$association] = $merge[0][$association]; + } else { + $diff = Set::diff($dataAssocTmp, $mergeAssocTmp); + $data[$association] = array_merge($merge[0][$association], $diff); + } + } elseif ($selfJoin && array_key_exists($association, $merge[0])) { + $data[$association] = array_merge($data[$association], array($association => array())); + } + } + } + } else { + if (isset($merge[0][$association]) && $merge[0][$association] === false) { + if (!isset($data[$association])) { + $data[$association] = array(); + } + } else { + foreach ($merge as $i => $row) { + if (count($row) == 1) { + if (empty($data[$association]) || (isset($data[$association]) && !in_array($row[$association], $data[$association]))) { + $data[$association][] = $row[$association]; + } + } else if (!empty($row)) { + $tmp = array_merge($row[$association], $row); + unset($tmp[$association]); + $data[$association][] = $tmp; + } + } + } + } + } + +/** + * Generates an array representing a query or part of a query from a single model or two associated models + * + * @param Model $model + * @param Model $linkModel + * @param string $type + * @param string $association + * @param array $assocData + * @param array $queryData + * @param boolean $external + * @param array $resultSet + * @return mixed + * @access public + */ + function generateAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) { + $queryData = $this->__scrubQueryData($queryData); + $assocData = $this->__scrubQueryData($assocData); + + if (empty($queryData['fields'])) { + $queryData['fields'] = $this->fields($model, $model->alias); + } elseif (!empty($model->hasMany) && $model->recursive > -1) { + $assocFields = $this->fields($model, $model->alias, array("{$model->alias}.{$model->primaryKey}")); + $passedFields = $this->fields($model, $model->alias, $queryData['fields']); + if (count($passedFields) === 1) { + $match = strpos($passedFields[0], $assocFields[0]); + $match1 = (bool)preg_match('/^[a-z]+\(/i', $passedFields[0]); + + if ($match === false && $match1 === false) { + $queryData['fields'] = array_merge($passedFields, $assocFields); + } else { + $queryData['fields'] = $passedFields; + } + } else { + $queryData['fields'] = array_merge($passedFields, $assocFields); + } + unset($assocFields, $passedFields); + } + + if ($linkModel == null) { + return $this->buildStatement( + array( + 'fields' => array_unique($queryData['fields']), + 'table' => $this->fullTableName($model), + 'alias' => $model->alias, + 'limit' => $queryData['limit'], + 'offset' => $queryData['offset'], + 'joins' => $queryData['joins'], + 'conditions' => $queryData['conditions'], + 'order' => $queryData['order'], + 'group' => $queryData['group'] + ), + $model + ); + } + if ($external && !empty($assocData['finderQuery'])) { + return $assocData['finderQuery']; + } + + $alias = $association; + $self = ($model->name == $linkModel->name); + $fields = array(); + + if ((!$external && in_array($type, array('hasOne', 'belongsTo')) && $this->__bypass === false) || $external) { + $fields = $this->fields($linkModel, $alias, $assocData['fields']); + } + if (empty($assocData['offset']) && !empty($assocData['page'])) { + $assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit']; + } + $assocData['limit'] = $this->limit($assocData['limit'], $assocData['offset']); + + switch ($type) { + case 'hasOne': + case 'belongsTo': + $conditions = $this->__mergeConditions( + $assocData['conditions'], + $this->getConstraint($type, $model, $linkModel, $alias, array_merge($assocData, compact('external', 'self'))) + ); + + if (!$self && $external) { + foreach ($conditions as $key => $condition) { + if (is_numeric($key) && strpos($condition, $model->alias . '.') !== false) { + unset($conditions[$key]); + } + } + } + + if ($external) { + $query = array_merge($assocData, array( + 'conditions' => $conditions, + 'table' => $this->fullTableName($linkModel), + 'fields' => $fields, + 'alias' => $alias, + 'group' => null + )); + $query = array_merge(array('order' => $assocData['order'], 'limit' => $assocData['limit']), $query); + } else { + $join = array( + 'table' => $this->fullTableName($linkModel), + 'alias' => $alias, + 'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT', + 'conditions' => trim($this->conditions($conditions, true, false, $model)) + ); + $queryData['fields'] = array_merge($queryData['fields'], $fields); + + if (!empty($assocData['order'])) { + $queryData['order'][] = $assocData['order']; + } + if (!in_array($join, $queryData['joins'])) { + $queryData['joins'][] = $join; + } + return true; + } + break; + case 'hasMany': + $assocData['fields'] = $this->fields($linkModel, $alias, $assocData['fields']); + if (!empty($assocData['foreignKey'])) { + $assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $alias, array("{$alias}.{$assocData['foreignKey']}"))); + } + $query = array( + 'conditions' => $this->__mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $alias, $assocData), $assocData['conditions']), + 'fields' => array_unique($assocData['fields']), + 'table' => $this->fullTableName($linkModel), + 'alias' => $alias, + 'order' => $assocData['order'], + 'limit' => $assocData['limit'], + 'group' => null + ); + break; + case 'hasAndBelongsToMany': + $joinFields = array(); + $joinAssoc = null; + + if (isset($assocData['with']) && !empty($assocData['with'])) { + $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']); + list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys); + + $joinTbl = $this->fullTableName($model->{$with}); + $joinAlias = $joinTbl; + + if (is_array($joinFields) && !empty($joinFields)) { + $joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields); + $joinAssoc = $joinAlias = $model->{$with}->alias; + } else { + $joinFields = array(); + } + } else { + $joinTbl = $this->fullTableName($assocData['joinTable']); + $joinAlias = $joinTbl; + } + $query = array( + 'conditions' => $assocData['conditions'], + 'limit' => $assocData['limit'], + 'table' => $this->fullTableName($linkModel), + 'alias' => $alias, + 'fields' => array_merge($this->fields($linkModel, $alias, $assocData['fields']), $joinFields), + 'order' => $assocData['order'], + 'group' => null, + 'joins' => array(array( + 'table' => $joinTbl, + 'alias' => $joinAssoc, + 'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $alias) + )) + ); + break; + } + if (isset($query)) { + return $this->buildStatement($query, $model); + } + return null; + } + +/** + * Returns a conditions array for the constraint between two models + * + * @param string $type Association type + * @param object $model Model object + * @param array $association Association array + * @return array Conditions array defining the constraint between $model and $association + * @access public + */ + function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) { + $assoc = array_merge(array('external' => false, 'self' => false), $assoc); + + if (array_key_exists('foreignKey', $assoc) && empty($assoc['foreignKey'])) { + return array(); + } + + switch (true) { + case ($assoc['external'] && $type == 'hasOne'): + return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'); + break; + case ($assoc['external'] && $type == 'belongsTo'): + return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}'); + break; + case (!$assoc['external'] && $type == 'hasOne'): + return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}")); + break; + case (!$assoc['external'] && $type == 'belongsTo'): + return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}")); + break; + case ($type == 'hasMany'): + return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}')); + break; + case ($type == 'hasAndBelongsToMany'): + return array( + array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'), + array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}")) + ); + break; + } + return array(); + } + +/** + * Builds and generates a JOIN statement from an array. Handles final clean-up before conversion. + * + * @param array $join An array defining a JOIN statement in a query + * @return string An SQL JOIN statement to be used in a query + * @access public + * @see DboSource::renderJoinStatement() + * @see DboSource::buildStatement() + */ + function buildJoinStatement($join) { + $data = array_merge(array( + 'type' => null, + 'alias' => null, + 'table' => 'join_table', + 'conditions' => array() + ), $join); + + if (!empty($data['alias'])) { + $data['alias'] = $this->alias . $this->name($data['alias']); + } + if (!empty($data['conditions'])) { + $data['conditions'] = trim($this->conditions($data['conditions'], true, false)); + } + return $this->renderJoinStatement($data); + } + +/** + * Builds and generates an SQL statement from an array. Handles final clean-up before conversion. + * + * @param array $query An array defining an SQL query + * @param object $model The model object which initiated the query + * @return string An executable SQL statement + * @access public + * @see DboSource::renderStatement() + */ + function buildStatement($query, &$model) { + $query = array_merge(array('offset' => null, 'joins' => array()), $query); + if (!empty($query['joins'])) { + $count = count($query['joins']); + for ($i = 0; $i < $count; $i++) { + if (is_array($query['joins'][$i])) { + $query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]); + } + } + } + return $this->renderStatement('select', array( + 'conditions' => $this->conditions($query['conditions'], true, true, $model), + 'fields' => implode(', ', $query['fields']), + 'table' => $query['table'], + 'alias' => $this->alias . $this->name($query['alias']), + 'order' => $this->order($query['order'], 'ASC', $model), + 'limit' => $this->limit($query['limit'], $query['offset']), + 'joins' => implode(' ', $query['joins']), + 'group' => $this->group($query['group'], $model) + )); + } + +/** + * Renders a final SQL JOIN statement + * + * @param array $data + * @return string + * @access public + */ + function renderJoinStatement($data) { + extract($data); + return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})"); + } + +/** + * Renders a final SQL statement by putting together the component parts in the correct order + * + * @param string $type type of query being run. e.g select, create, update, delete, schema, alter. + * @param array $data Array of data to insert into the query. + * @return string Rendered SQL expression to be run. + * @access public + */ + function renderStatement($type, $data) { + extract($data); + $aliases = null; + + switch (strtolower($type)) { + case 'select': + return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}"; + break; + case 'create': + return "INSERT INTO {$table} ({$fields}) VALUES ({$values})"; + break; + case 'update': + if (!empty($alias)) { + $aliases = "{$this->alias}{$alias} {$joins} "; + } + return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}"; + break; + case 'delete': + if (!empty($alias)) { + $aliases = "{$this->alias}{$alias} {$joins} "; + } + return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}"; + break; + case 'schema': + foreach (array('columns', 'indexes', 'tableParameters') as $var) { + if (is_array(${$var})) { + ${$var} = "\t" . join(",\n\t", array_filter(${$var})); + } else { + ${$var} = ''; + } + } + if (trim($indexes) != '') { + $columns .= ','; + } + return "CREATE TABLE {$table} (\n{$columns}{$indexes}){$tableParameters};"; + break; + case 'alter': + break; + } + } + +/** + * Merges a mixed set of string/array conditions + * + * @return array + * @access private + */ + function __mergeConditions($query, $assoc) { + if (empty($assoc)) { + return $query; + } + + if (is_array($query)) { + return array_merge((array)$assoc, $query); + } + + if (!empty($query)) { + $query = array($query); + if (is_array($assoc)) { + $query = array_merge($query, $assoc); + } else { + $query[] = $assoc; + } + return $query; + } + + return $assoc; + } + +/** + * Generates and executes an SQL UPDATE statement for given model, fields, and values. + * For databases that do not support aliases in UPDATE queries. + * + * @param Model $model + * @param array $fields + * @param array $values + * @param mixed $conditions + * @return boolean Success + * @access public + */ + function update(&$model, $fields = array(), $values = null, $conditions = null) { + if ($values == null) { + $combined = $fields; + } else { + $combined = array_combine($fields, $values); + } + + $fields = implode(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions))); + + $alias = $joins = null; + $table = $this->fullTableName($model); + $conditions = $this->_matchRecords($model, $conditions); + + if ($conditions === false) { + return false; + } + $query = compact('table', 'alias', 'joins', 'fields', 'conditions'); + + if (!$this->execute($this->renderStatement('update', $query))) { + $model->onError(); + return false; + } + return true; + } + +/** + * Quotes and prepares fields and values for an SQL UPDATE statement + * + * @param Model $model + * @param array $fields + * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets + * @param boolean $alias Include the model alias in the field name + * @return array Fields and values, quoted and preparted + * @access protected + */ + function _prepareUpdateFields(&$model, $fields, $quoteValues = true, $alias = false) { + $quotedAlias = $this->startQuote . $model->alias . $this->endQuote; + + $updates = array(); + foreach ($fields as $field => $value) { + if ($alias && strpos($field, '.') === false) { + $quoted = $model->escapeField($field); + } elseif (!$alias && strpos($field, '.') !== false) { + $quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace( + $model->alias . '.', '', $field + ))); + } else { + $quoted = $this->name($field); + } + + if ($value === null) { + $updates[] = $quoted . ' = NULL'; + continue; + } + $update = $quoted . ' = '; + + if ($quoteValues) { + $update .= $this->value($value, $model->getColumnType($field), false); + } elseif (!$alias) { + $update .= str_replace($quotedAlias . '.', '', str_replace( + $model->alias . '.', '', $value + )); + } else { + $update .= $value; + } + $updates[] = $update; + } + return $updates; + } + +/** + * Generates and executes an SQL DELETE statement. + * For databases that do not support aliases in UPDATE queries. + * + * @param Model $model + * @param mixed $conditions + * @return boolean Success + * @access public + */ + function delete(&$model, $conditions = null) { + $alias = $joins = null; + $table = $this->fullTableName($model); + $conditions = $this->_matchRecords($model, $conditions); + + if ($conditions === false) { + return false; + } + + if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) { + $model->onError(); + return false; + } + return true; + } + +/** + * Gets a list of record IDs for the given conditions. Used for multi-record updates and deletes + * in databases that do not support aliases in UPDATE/DELETE queries. + * + * @param Model $model + * @param mixed $conditions + * @return array List of record IDs + * @access protected + */ + function _matchRecords(&$model, $conditions = null) { + if ($conditions === true) { + $conditions = $this->conditions(true); + } elseif ($conditions === null) { + $conditions = $this->conditions($this->defaultConditions($model, $conditions, false), true, true, $model); + } else { + $noJoin = true; + foreach ($conditions as $field => $value) { + $originalField = $field; + if (strpos($field, '.') !== false) { + list($alias, $field) = explode('.', $field); + $field = ltrim($field, $this->startQuote); + $field = rtrim($field, $this->endQuote); + } + if (!$model->hasField($field)) { + $noJoin = false; + break; + } + if ($field !== $originalField) { + $conditions[$field] = $value; + unset($conditions[$originalField]); + } + } + if ($noJoin === true) { + return $this->conditions($conditions); + } + $idList = $model->find('all', array( + 'fields' => "{$model->alias}.{$model->primaryKey}", + 'conditions' => $conditions + )); + + if (empty($idList)) { + return false; + } + $conditions = $this->conditions(array( + $model->primaryKey => Set::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}") + )); + } + return $conditions; + } + +/** + * Returns an array of SQL JOIN fragments from a model's associations + * + * @param object $model + * @return array + * @access protected + */ + function _getJoins($model) { + $join = array(); + $joins = array_merge($model->getAssociated('hasOne'), $model->getAssociated('belongsTo')); + + foreach ($joins as $assoc) { + if (isset($model->{$assoc}) && $model->useDbConfig == $model->{$assoc}->useDbConfig) { + $assocData = $model->getAssociated($assoc); + $join[] = $this->buildJoinStatement(array( + 'table' => $this->fullTableName($model->{$assoc}), + 'alias' => $assoc, + 'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT', + 'conditions' => trim($this->conditions( + $this->__mergeConditions($assocData['conditions'], $this->getConstraint($assocData['association'], $model, $model->{$assoc}, $assoc, $assocData)), + true, false, $model + )) + )); + } + } + return $join; + } + +/** + * Returns an SQL calculation, i.e. COUNT() or MAX() + * + * @param model $model + * @param string $func Lowercase name of SQL function, i.e. 'count' or 'max' + * @param array $params Function parameters (any values must be quoted manually) + * @return string An SQL calculation function + * @access public + */ + function calculate(&$model, $func, $params = array()) { + $params = (array)$params; + + switch (strtolower($func)) { + case 'count': + if (!isset($params[0])) { + $params[0] = '*'; + } + if (!isset($params[1])) { + $params[1] = 'count'; + } + if (is_object($model) && $model->isVirtualField($params[0])){ + $arg = $this->__quoteFields($model->getVirtualField($params[0])); + } else { + $arg = $this->name($params[0]); + } + return 'COUNT(' . $arg . ') AS ' . $this->name($params[1]); + case 'max': + case 'min': + if (!isset($params[1])) { + $params[1] = $params[0]; + } + if (is_object($model) && $model->isVirtualField($params[0])) { + $arg = $this->__quoteFields($model->getVirtualField($params[0])); + } else { + $arg = $this->name($params[0]); + } + return strtoupper($func) . '(' . $arg . ') AS ' . $this->name($params[1]); + break; + } + } + +/** + * Deletes all the records in a table and resets the count of the auto-incrementing + * primary key, where applicable. + * + * @param mixed $table A string or model class representing the table to be truncated + * @return boolean SQL TRUNCATE TABLE statement, false if not applicable. + * @access public + */ + function truncate($table) { + return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table)); + } + +/** + * Begin a transaction + * + * @param model $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions, + * or a transaction has not started). + * @access public + */ + function begin(&$model) { + if (parent::begin($model) && $this->execute($this->_commands['begin'])) { + $this->_transactionStarted = true; + return true; + } + return false; + } + +/** + * Commit a transaction + * + * @param model $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions, + * or a transaction has not started). + * @access public + */ + function commit(&$model) { + if (parent::commit($model) && $this->execute($this->_commands['commit'])) { + $this->_transactionStarted = false; + return true; + } + return false; + } + +/** + * Rollback a transaction + * + * @param model $model + * @return boolean True on success, false on fail + * (i.e. if the database/model does not support transactions, + * or a transaction has not started). + * @access public + */ + function rollback(&$model) { + if (parent::rollback($model) && $this->execute($this->_commands['rollback'])) { + $this->_transactionStarted = false; + return true; + } + return false; + } + +/** + * Creates a default set of conditions from the model if $conditions is null/empty. + * If conditions are supplied then they will be returned. If a model doesn't exist and no conditions + * were provided either null or false will be returned based on what was input. + * + * @param object $model + * @param mixed $conditions Array of conditions, conditions string, null or false. If an array of conditions, + * or string conditions those conditions will be returned. With other values the model's existance will be checked. + * If the model doesn't exist a null or false will be returned depending on the input value. + * @param boolean $useAlias Use model aliases rather than table names when generating conditions + * @return mixed Either null, false, $conditions or an array of default conditions to use. + * @see DboSource::update() + * @see DboSource::conditions() + * @access public + */ + function defaultConditions(&$model, $conditions, $useAlias = true) { + if (!empty($conditions)) { + return $conditions; + } + $exists = $model->exists(); + if (!$exists && $conditions !== null) { + return false; + } elseif (!$exists) { + return null; + } + $alias = $model->alias; + + if (!$useAlias) { + $alias = $this->fullTableName($model, false); + } + return array("{$alias}.{$model->primaryKey}" => $model->getID()); + } + +/** + * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name) + * + * @param unknown_type $model + * @param unknown_type $key + * @param unknown_type $assoc + * @return string + * @access public + */ + function resolveKey($model, $key, $assoc = null) { + if (empty($assoc)) { + $assoc = $model->alias; + } + if (!strpos('.', $key)) { + return $this->name($model->alias) . '.' . $this->name($key); + } + return $key; + } + +/** + * Private helper method to remove query metadata in given data array. + * + * @param array $data + * @return array + * @access public + */ + function __scrubQueryData($data) { + foreach (array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group') as $key) { + if (empty($data[$key])) { + $data[$key] = array(); + } + } + return $data; + } + +/** + * Converts model virtual fields into sql expressions to be fetched later + * + * @param Model $model + * @param string $alias Alias tablename + * @param mixed $fields virtual fields to be used on query + * @return array + */ + function _constructVirtualFields(&$model, $alias, $fields) { + $virtual = array(); + foreach ($fields as $field) { + $virtualField = $this->name($alias . $this->virtualFieldSeparator . $field); + $expression = $this->__quoteFields($model->getVirtualField($field)); + $virtual[] = '(' . $expression . ") {$this->alias} {$virtualField}"; + } + return $virtual; + } + +/** + * Generates the fields list of an SQL query. + * + * @param Model $model + * @param string $alias Alias tablename + * @param mixed $fields + * @param boolean $quote If false, returns fields array unquoted + * @return array + * @access public + */ + function fields(&$model, $alias = null, $fields = array(), $quote = true) { + if (empty($alias)) { + $alias = $model->alias; + } + $cacheKey = array( + $model->useDbConfig, + $model->table, + array_keys($model->schema()), + $model->name, + $model->getVirtualField(), + $alias, + $fields, + $quote + ); + $cacheKey = crc32(serialize($cacheKey)); + if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) { + return $return; + } + $allFields = empty($fields); + if ($allFields) { + $fields = array_keys($model->schema()); + } elseif (!is_array($fields)) { + $fields = String::tokenize($fields); + } + $fields = array_values(array_filter($fields)); + $allFields = $allFields || in_array('*', $fields) || in_array($model->alias . '.*', $fields); + + $virtual = array(); + $virtualFields = $model->getVirtualField(); + if (!empty($virtualFields)) { + $virtualKeys = array_keys($virtualFields); + foreach ($virtualKeys as $field) { + $virtualKeys[] = $model->alias . '.' . $field; + } + $virtual = ($allFields) ? $virtualKeys : array_intersect($virtualKeys, $fields); + foreach ($virtual as $i => $field) { + if (strpos($field, '.') !== false) { + $virtual[$i] = str_replace($model->alias . '.', '', $field); + } + $fields = array_diff($fields, array($field)); + } + $fields = array_values($fields); + } + + if (!$quote) { + if (!empty($virtual)) { + $fields = array_merge($fields, $this->_constructVirtualFields($model, $alias, $virtual)); + } + return $fields; + } + $count = count($fields); + + if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) { + for ($i = 0; $i < $count; $i++) { + if (is_string($fields[$i]) && in_array($fields[$i], $virtual)) { + unset($fields[$i]); + continue; + } + if (is_object($fields[$i]) && isset($fields[$i]->type) && $fields[$i]->type === 'expression') { + $fields[$i] = $fields[$i]->value; + } elseif (preg_match('/^\(.*\)\s' . $this->alias . '.*/i', $fields[$i])){ + continue; + } elseif (!preg_match('/^.+\\(.*\\)/', $fields[$i])) { + $prepend = ''; + + if (strpos($fields[$i], 'DISTINCT') !== false) { + $prepend = 'DISTINCT '; + $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i])); + } + $dot = strpos($fields[$i], '.'); + + if ($dot === false) { + $prefix = !( + strpos($fields[$i], ' ') !== false || + strpos($fields[$i], '(') !== false + ); + $fields[$i] = $this->name(($prefix ? $alias . '.' : '') . $fields[$i]); + } else { + $value = array(); + $comma = strpos($fields[$i], ','); + if ($comma === false) { + $build = explode('.', $fields[$i]); + if (!Set::numeric($build)) { + $fields[$i] = $this->name(implode('.', $build)); + } + } + } + $fields[$i] = $prepend . $fields[$i]; + } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) { + if (isset($field[1])) { + if (strpos($field[1], '.') === false) { + $field[1] = $this->name($alias . '.' . $field[1]); + } else { + $field[0] = explode('.', $field[1]); + if (!Set::numeric($field[0])) { + $field[0] = implode('.', array_map(array(&$this, 'name'), $field[0])); + $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1); + } + } + } + } + } + } + if (!empty($virtual)) { + $fields = array_merge($fields, $this->_constructVirtualFields($model, $alias, $virtual)); + } + return $this->cacheMethod(__FUNCTION__, $cacheKey, array_unique($fields)); + } + +/** + * Creates a WHERE clause by parsing given conditions data. If an array or string + * conditions are provided those conditions will be parsed and quoted. If a boolean + * is given it will be integer cast as condition. Null will return 1 = 1. + * + * Results of this method are stored in a memory cache. This improves performance, but + * because the method uses a simple hashing algorithm it can infrequently have collisions. + * Setting DboSource::$cacheMethods to false will disable the memory cache. + * + * @param mixed $conditions Array or string of conditions, or any value. + * @param boolean $quoteValues If true, values should be quoted + * @param boolean $where If true, "WHERE " will be prepended to the return value + * @param Model $model A reference to the Model instance making the query + * @return string SQL fragment + * @access public + */ + function conditions($conditions, $quoteValues = true, $where = true, $model = null) { + if (is_object($model)) { + $cacheKey = array( + $model->useDbConfig, + $model->table, + $model->schema(), + $model->name, + $model->getVirtualField(), + $conditions, + $quoteValues, + $where + ); + } else { + $cacheKey = array($conditions, $quoteValues, $where); + } + $cacheKey = crc32(serialize($cacheKey)); + if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) { + return $return; + } + + $clause = $out = ''; + + if ($where) { + $clause = ' WHERE '; + } + + if (is_array($conditions) && !empty($conditions)) { + $out = $this->conditionKeysToString($conditions, $quoteValues, $model); + + if (empty($out)) { + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . ' 1 = 1'); + } + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . implode(' AND ', $out)); + } + if ($conditions === false || $conditions === true) { + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . (int)$conditions . ' = 1'); + } + + if (empty($conditions) || trim($conditions) == '') { + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . '1 = 1'); + } + $clauses = '/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i'; + + if (preg_match($clauses, $conditions, $match)) { + $clause = ''; + } + if (trim($conditions) == '') { + $conditions = ' 1 = 1'; + } else { + $conditions = $this->__quoteFields($conditions); + } + return $this->cacheMethod(__FUNCTION__, $cacheKey, $clause . $conditions); + } + +/** + * Creates a WHERE clause by parsing given conditions array. Used by DboSource::conditions(). + * + * @param array $conditions Array or string of conditions + * @param boolean $quoteValues If true, values should be quoted + * @param Model $model A reference to the Model instance making the query + * @return string SQL fragment + * @access public + */ + function conditionKeysToString($conditions, $quoteValues = true, $model = null) { + $c = 0; + $out = array(); + $data = $columnType = null; + $bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&'); + + foreach ($conditions as $key => $value) { + $join = ' AND '; + $not = null; + + if (is_array($value)) { + $valueInsert = ( + !empty($value) && + (substr_count($key, '?') == count($value) || substr_count($key, ':') == count($value)) + ); + } + + if (is_numeric($key) && empty($value)) { + continue; + } elseif (is_numeric($key) && is_string($value)) { + $out[] = $not . $this->__quoteFields($value); + } elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $bool)) { + if (in_array(strtolower(trim($key)), $bool)) { + $join = ' ' . strtoupper($key) . ' '; + } else { + $key = $join; + } + $value = $this->conditionKeysToString($value, $quoteValues, $model); + + if (strpos($join, 'NOT') !== false) { + if (strtoupper(trim($key)) == 'NOT') { + $key = 'AND ' . trim($key); + } + $not = 'NOT '; + } + + if (empty($value[1])) { + if ($not) { + $out[] = $not . '(' . $value[0] . ')'; + } else { + $out[] = $value[0] ; + } + } else { + $out[] = '(' . $not . '(' . implode(') ' . strtoupper($key) . ' (', $value) . '))'; + } + + } else { + if (is_object($value) && isset($value->type)) { + if ($value->type == 'identifier') { + $data .= $this->name($key) . ' = ' . $this->name($value->value); + } elseif ($value->type == 'expression') { + if (is_numeric($key)) { + $data .= $value->value; + } else { + $data .= $this->name($key) . ' = ' . $value->value; + } + } + } elseif (is_array($value) && !empty($value) && !$valueInsert) { + $keys = array_keys($value); + if ($keys === array_values($keys)) { + $count = count($value); + if ($count === 1) { + $data = $this->__quoteFields($key) . ' = ('; + } else { + $data = $this->__quoteFields($key) . ' IN ('; + } + if ($quoteValues) { + if (is_object($model)) { + $columnType = $model->getColumnType($key); + } + $data .= implode(', ', $this->value($value, $columnType)); + } + $data .= ')'; + } else { + $ret = $this->conditionKeysToString($value, $quoteValues, $model); + if (count($ret) > 1) { + $data = '(' . implode(') AND (', $ret) . ')'; + } elseif (isset($ret[0])) { + $data = $ret[0]; + } + } + } elseif (is_numeric($key) && !empty($value)) { + $data = $this->__quoteFields($value); + } else { + $data = $this->__parseKey($model, trim($key), $value); + } + + if ($data != null) { + $out[] = $data; + $data = null; + } + } + $c++; + } + return $out; + } + +/** + * Extracts a Model.field identifier and an SQL condition operator from a string, formats + * and inserts values, and composes them into an SQL snippet. + * + * @param Model $model Model object initiating the query + * @param string $key An SQL key snippet containing a field and optional SQL operator + * @param mixed $value The value(s) to be inserted in the string + * @return string + * @access private + */ + function __parseKey(&$model, $key, $value) { + $operatorMatch = '/^((' . implode(')|(', $this->__sqlOps); + $operatorMatch .= '\\x20)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is'; + $bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false)); + + if (!strpos($key, ' ')) { + $operator = '='; + } else { + list($key, $operator) = explode(' ', trim($key), 2); + + if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) { + $key = $key . ' ' . $operator; + $split = strrpos($key, ' '); + $operator = substr($key, $split); + $key = substr($key, 0, $split); + } + } + + $virtual = false; + if (is_object($model) && $model->isVirtualField($key)) { + $key = $this->__quoteFields($model->getVirtualField($key)); + $virtual = true; + } + + $type = (is_object($model) ? $model->getColumnType($key) : null); + + $null = ($value === null || (is_array($value) && empty($value))); + + if (strtolower($operator) === 'not') { + $data = $this->conditionKeysToString( + array($operator => array($key => $value)), true, $model + ); + return $data[0]; + } + + $value = $this->value($value, $type); + + if (!$virtual && $key !== '?') { + $isKey = (strpos($key, '(') !== false || strpos($key, ')') !== false); + $key = $isKey ? $this->__quoteFields($key) : $this->name($key); + } + + if ($bound) { + return String::insert($key . ' ' . trim($operator), $value); + } + + if (!preg_match($operatorMatch, trim($operator))) { + $operator .= ' ='; + } + $operator = trim($operator); + + if (is_array($value)) { + $value = implode(', ', $value); + + switch ($operator) { + case '=': + $operator = 'IN'; + break; + case '!=': + case '<>': + $operator = 'NOT IN'; + break; + } + $value = "({$value})"; + } elseif ($null) { + switch ($operator) { + case '=': + $operator = 'IS'; + break; + case '!=': + case '<>': + $operator = 'IS NOT'; + break; + } + } + if ($virtual) { + return "({$key}) {$operator} {$value}"; + } + return "{$key} {$operator} {$value}"; + } + +/** + * Quotes Model.fields + * + * @param string $conditions + * @return string or false if no match + * @access private + */ + function __quoteFields($conditions) { + $start = $end = null; + $original = $conditions; + + if (!empty($this->startQuote)) { + $start = preg_quote($this->startQuote); + } + if (!empty($this->endQuote)) { + $end = preg_quote($this->endQuote); + } + $conditions = str_replace(array($start, $end), '', $conditions); + $conditions = preg_replace_callback('/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', array(&$this, '__quoteMatchedField'), $conditions); + + if ($conditions !== null) { + return $conditions; + } + return $original; + } + +/** + * Auxiliary function to quote matches `Model.fields` from a preg_replace_callback call + * + * @param string matched string + * @return string quoted strig + * @access private + */ + function __quoteMatchedField($match) { + if (is_numeric($match[0])) { + return $match[0]; + } + return $this->name($match[0]); + } + +/** + * Returns a limit statement in the correct format for the particular database. + * + * @param integer $limit Limit of results returned + * @param integer $offset Offset from which to start results + * @return string SQL limit/offset statement + * @access public + */ + function limit($limit, $offset = null) { + if ($limit) { + $rt = ''; + if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) { + $rt = ' LIMIT'; + } + + if ($offset) { + $rt .= ' ' . $offset . ','; + } + + $rt .= ' ' . $limit; + return $rt; + } + return null; + } + +/** + * Returns an ORDER BY clause as a string. + * + * @param string $key Field reference, as a key (i.e. Post.title) + * @param string $direction Direction (ASC or DESC) + * @param object $model model reference (used to look for virtual field) + * @return string ORDER BY clause + * @access public + */ + function order($keys, $direction = 'ASC', $model = null) { + if (!is_array($keys)) { + $keys = array($keys); + } + $keys = array_filter($keys); + $result = array(); + while (!empty($keys)) { + list($key, $dir) = each($keys); + array_shift($keys); + + if (is_numeric($key)) { + $key = $dir; + $dir = $direction; + } + + if (is_string($key) && strpos($key, ',') && !preg_match('/\(.+\,.+\)/', $key)) { + $key = array_map('trim', explode(',', $key)); + } + if (is_array($key)) { + //Flatten the array + $key = array_reverse($key, true); + foreach ($key as $k => $v) { + if (is_numeric($k)) { + array_unshift($keys, $v); + } else { + $keys = array($k => $v) + $keys; + } + } + continue; + } elseif (is_object($key) && isset($key->type) && $key->type === 'expression') { + $result[] = $key->value; + continue; + } + + if (preg_match('/\\x20(ASC|DESC).*/i', $key, $_dir)) { + $dir = $_dir[0]; + $key = preg_replace('/\\x20(ASC|DESC).*/i', '', $key); + } + + $key = trim($key); + + if (is_object($model) && $model->isVirtualField($key)) { + $key = '(' . $this->__quoteFields($model->getVirtualField($key)) . ')'; + } + + if (strpos($key, '.')) { + $key = preg_replace_callback('/([a-zA-Z0-9_-]{1,})\\.([a-zA-Z0-9_-]{1,})/', array(&$this, '__quoteMatchedField'), $key); + } + if (!preg_match('/\s/', $key) && !strpos($key, '.')) { + $key = $this->name($key); + } + $key .= ' ' . trim($dir); + $result[] = $key; + } + if (!empty($result)) { + return ' ORDER BY ' . implode(', ', $result); + } + return ''; + } + +/** + * Create a GROUP BY SQL clause + * + * @param string $group Group By Condition + * @return mixed string condition or null + * @access public + */ + function group($group, $model = null) { + if ($group) { + if (!is_array($group)) { + $group = array($group); + } + foreach($group as $index => $key) { + if (is_object($model) && $model->isVirtualField($key)) { + $group[$index] = '(' . $model->getVirtualField($key) . ')'; + } + } + $group = implode(', ', $group); + return ' GROUP BY ' . $this->__quoteFields($group); + } + return null; + } + +/** + * Disconnects database, kills the connection and says the connection is closed. + * + * @return void + * @access public + */ + function close() { + $this->disconnect(); + } + +/** + * Checks if the specified table contains any record matching specified SQL + * + * @param Model $model Model to search + * @param string $sql SQL WHERE clause (condition only, not the "WHERE" part) + * @return boolean True if the table has a matching record, else false + * @access public + */ + function hasAny(&$Model, $sql) { + $sql = $this->conditions($sql); + $table = $this->fullTableName($Model); + $alias = $this->alias . $this->name($Model->alias); + $where = $sql ? "{$sql}" : ' WHERE 1 = 1'; + $id = $Model->escapeField(); + + $out = $this->fetchRow("SELECT COUNT({$id}) {$this->alias}count FROM {$table} {$alias}{$where}"); + + if (is_array($out)) { + return $out[0]['count']; + } + return false; + } + +/** + * Gets the length of a database-native column description, or null if no length + * + * @param string $real Real database-layer column type (i.e. "varchar(255)") + * @return mixed An integer or string representing the length of the column + * @access public + */ + function length($real) { + if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) { + trigger_error(__("FIXME: Can't parse field: " . $real, true), E_USER_WARNING); + $col = str_replace(array(')', 'unsigned'), '', $real); + $limit = null; + + if (strpos($col, '(') !== false) { + list($col, $limit) = explode('(', $col); + } + if ($limit != null) { + return intval($limit); + } + return null; + } + + $types = array( + 'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1 + ); + + list($real, $type, $length, $offset, $sign, $zerofill) = $result; + $typeArr = $type; + $type = $type[0]; + $length = $length[0]; + $offset = $offset[0]; + + $isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double')); + if ($isFloat && $offset) { + return $length.','.$offset; + } + + if (($real[0] == $type) && (count($real) == 1)) { + return null; + } + + if (isset($types[$type])) { + $length += $types[$type]; + if (!empty($sign)) { + $length--; + } + } elseif (in_array($type, array('enum', 'set'))) { + $length = 0; + foreach ($typeArr as $key => $enumValue) { + if ($key == 0) { + continue; + } + $tmpLength = strlen($enumValue); + if ($tmpLength > $length) { + $length = $tmpLength; + } + } + } + return intval($length); + } + +/** + * Translates between PHP boolean values and Database (faked) boolean values + * + * @param mixed $data Value to be translated + * @return mixed Converted boolean value + * @access public + */ + function boolean($data) { + if ($data === true || $data === false) { + if ($data === true) { + return 1; + } + return 0; + } else { + return !empty($data); + } + } + +/** + * Inserts multiple values into a table + * + * @param string $table + * @param string $fields + * @param array $values + * @access protected + */ + function insertMulti($table, $fields, $values) { + $table = $this->fullTableName($table); + if (is_array($fields)) { + $fields = implode(', ', array_map(array(&$this, 'name'), $fields)); + } + $count = count($values); + for ($x = 0; $x < $count; $x++) { + $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}"); + } + } + +/** + * Returns an array of the indexes in given datasource name. + * + * @param string $model Name of model to inspect + * @return array Fields in table. Keys are column and unique + * @access public + */ + function index($model) { + return false; + } + +/** + * Generate a database-native schema for the given Schema object + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $tableName Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + * @access public + */ + function createSchema($schema, $tableName = null) { + if (!is_a($schema, 'CakeSchema')) { + trigger_error(__('Invalid schema object', true), E_USER_WARNING); + return null; + } + $out = ''; + + foreach ($schema->tables as $curTable => $columns) { + if (!$tableName || $tableName == $curTable) { + $cols = $colList = $indexes = $tableParameters = array(); + $primary = null; + $table = $this->fullTableName($curTable); + + foreach ($columns as $name => $col) { + if (is_string($col)) { + $col = array('type' => $col); + } + if (isset($col['key']) && $col['key'] == 'primary') { + $primary = $name; + } + if ($name !== 'indexes' && $name !== 'tableParameters') { + $col['name'] = $name; + if (!isset($col['type'])) { + $col['type'] = 'string'; + } + $cols[] = $this->buildColumn($col); + } elseif ($name == 'indexes') { + $indexes = array_merge($indexes, $this->buildIndex($col, $table)); + } elseif ($name == 'tableParameters') { + $tableParameters = array_merge($tableParameters, $this->buildTableParameters($col, $table)); + } + } + if (empty($indexes) && !empty($primary)) { + $col = array('PRIMARY' => array('column' => $primary, 'unique' => 1)); + $indexes = array_merge($indexes, $this->buildIndex($col, $table)); + } + $columns = $cols; + $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes', 'tableParameters')) . "\n\n"; + } + } + return $out; + } + +/** + * Generate a alter syntax from CakeSchema::compare() + * + * @param unknown_type $schema + * @return boolean + */ + function alterSchema($compare, $table = null) { + return false; + } + +/** + * Generate a "drop table" statement for the given Schema object + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $table Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + * @access public + */ + function dropSchema($schema, $table = null) { + if (!is_a($schema, 'CakeSchema')) { + trigger_error(__('Invalid schema object', true), E_USER_WARNING); + return null; + } + $out = ''; + + foreach ($schema->tables as $curTable => $columns) { + if (!$table || $table == $curTable) { + $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . ";\n"; + } + } + return $out; + } + +/** + * Generate a database-native column schema string + * + * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]), + * where options can be 'default', 'length', or 'key'. + * @return string + * @access public + */ + function buildColumn($column) { + $name = $type = null; + extract(array_merge(array('null' => true), $column)); + + if (empty($name) || empty($type)) { + trigger_error(__('Column name or type not defined in schema', true), E_USER_WARNING); + return null; + } + + if (!isset($this->columns[$type])) { + trigger_error(sprintf(__('Column type %s does not exist', true), $type), E_USER_WARNING); + return null; + } + + $real = $this->columns[$type]; + $out = $this->name($name) . ' ' . $real['name']; + + if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) { + if (isset($column['length'])) { + $length = $column['length']; + } elseif (isset($column['limit'])) { + $length = $column['limit']; + } elseif (isset($real['length'])) { + $length = $real['length']; + } else { + $length = $real['limit']; + } + $out .= '(' . $length . ')'; + } + + if (($column['type'] == 'integer' || $column['type'] == 'float' ) && isset($column['default']) && $column['default'] === '') { + $column['default'] = null; + } + $out = $this->_buildFieldParameters($out, $column, 'beforeDefault'); + + if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') { + $out .= ' ' . $this->columns['primary_key']['name']; + } elseif (isset($column['key']) && $column['key'] == 'primary') { + $out .= ' NOT NULL'; + } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) { + $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL'; + } elseif (isset($column['default'])) { + $out .= ' DEFAULT ' . $this->value($column['default'], $type); + } elseif ($type !== 'timestamp' && !empty($column['null'])) { + $out .= ' DEFAULT NULL'; + } elseif ($type === 'timestamp' && !empty($column['null'])) { + $out .= ' NULL'; + } elseif (isset($column['null']) && $column['null'] == false) { + $out .= ' NOT NULL'; + } + if ($type == 'timestamp' && isset($column['default']) && strtolower($column['default']) == 'current_timestamp') { + $out = str_replace(array("'CURRENT_TIMESTAMP'", "'current_timestamp'"), 'CURRENT_TIMESTAMP', $out); + } + $out = $this->_buildFieldParameters($out, $column, 'afterDefault'); + return $out; + } + +/** + * Build the field parameters, in a position + * + * @param string $columnString The partially built column string + * @param array $columnData The array of column data. + * @param string $position The position type to use. 'beforeDefault' or 'afterDefault' are common + * @return string a built column with the field parameters added. + * @access public + */ + function _buildFieldParameters($columnString, $columnData, $position) { + foreach ($this->fieldParameters as $paramName => $value) { + if (isset($columnData[$paramName]) && $value['position'] == $position) { + if (isset($value['options']) && !in_array($columnData[$paramName], $value['options'])) { + continue; + } + $val = $columnData[$paramName]; + if ($value['quote']) { + $val = $this->value($val); + } + $columnString .= ' ' . $value['value'] . $value['join'] . $val; + } + } + return $columnString; + } + +/** + * Format indexes for create table + * + * @param array $indexes + * @param string $table + * @return array + * @access public + */ + function buildIndex($indexes, $table = null) { + $join = array(); + foreach ($indexes as $name => $value) { + $out = ''; + if ($name == 'PRIMARY') { + $out .= 'PRIMARY '; + $name = null; + } else { + if (!empty($value['unique'])) { + $out .= 'UNIQUE '; + } + $name = $this->startQuote . $name . $this->endQuote; + } + if (is_array($value['column'])) { + $out .= 'KEY ' . $name . ' (' . implode(', ', array_map(array(&$this, 'name'), $value['column'])) . ')'; + } else { + $out .= 'KEY ' . $name . ' (' . $this->name($value['column']) . ')'; + } + $join[] = $out; + } + return $join; + } + +/** + * Read additional table parameters + * + * @param array $parameters + * @param string $table + * @return array + * @access public + */ + function readTableParameters($name) { + $parameters = array(); + if ($this->isInterfaceSupported('listDetailedSources')) { + $currentTableDetails = $this->listDetailedSources($name); + foreach ($this->tableParameters as $paramName => $parameter) { + if (!empty($parameter['column']) && !empty($currentTableDetails[$parameter['column']])) { + $parameters[$paramName] = $currentTableDetails[$parameter['column']]; + } + } + } + return $parameters; + } + +/** + * Format parameters for create table + * + * @param array $parameters + * @param string $table + * @return array + * @access public + */ + function buildTableParameters($parameters, $table = null) { + $result = array(); + foreach ($parameters as $name => $value) { + if (isset($this->tableParameters[$name])) { + if ($this->tableParameters[$name]['quote']) { + $value = $this->value($value); + } + $result[] = $this->tableParameters[$name]['value'] . $this->tableParameters[$name]['join'] . $value; + } + } + return $result; + } + +/** + * Guesses the data type of an array + * + * @param string $value + * @return void + * @access public + */ + function introspectType($value) { + if (!is_array($value)) { + if ($value === true || $value === false) { + return 'boolean'; + } + if (is_float($value) && floatval($value) === $value) { + return 'float'; + } + if (is_int($value) && intval($value) === $value) { + return 'integer'; + } + if (is_string($value) && strlen($value) > 255) { + return 'text'; + } + return 'string'; + } + + $isAllFloat = $isAllInt = true; + $containsFloat = $containsInt = $containsString = false; + foreach ($value as $key => $valElement) { + $valElement = trim($valElement); + if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) { + $isAllFloat = false; + } else { + $containsFloat = true; + continue; + } + if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) { + $isAllInt = false; + } else { + $containsInt = true; + continue; + } + $containsString = true; + } + + if ($isAllFloat) { + return 'float'; + } + if ($isAllInt) { + return 'integer'; + } + + if ($containsInt && !$containsString) { + return 'integer'; + } + return 'string'; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/db_acl.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/db_acl.php new file mode 100644 index 000000000..245e10b0b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/db_acl.php @@ -0,0 +1,332 @@ + 'nested'); + +/** + * Constructor + * + */ + function __construct() { + $config = Configure::read('Acl.database'); + if (isset($config)) { + $this->useDbConfig = $config; + } + parent::__construct(); + } + +/** + * Retrieves the Aro/Aco node for this model + * + * @param mixed $ref Array with 'model' and 'foreign_key', model object, or string value + * @return array Node found in database + * @access public + */ + function node($ref = null) { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + $type = $this->alias; + $result = null; + + if (!empty($this->useTable)) { + $table = $this->useTable; + } else { + $table = Inflector::pluralize(Inflector::underscore($type)); + } + + if (empty($ref)) { + return null; + } elseif (is_string($ref)) { + $path = explode('/', $ref); + $start = $path[0]; + unset($path[0]); + + $queryData = array( + 'conditions' => array( + $db->name("{$type}.lft") . ' <= ' . $db->name("{$type}0.lft"), + $db->name("{$type}.rght") . ' >= ' . $db->name("{$type}0.rght")), + 'fields' => array('id', 'parent_id', 'model', 'foreign_key', 'alias'), + 'joins' => array(array( + 'table' => $db->fullTableName($this), + 'alias' => "{$type}0", + 'type' => 'LEFT', + 'conditions' => array("{$type}0.alias" => $start) + )), + 'order' => $db->name("{$type}.lft") . ' DESC' + ); + + foreach ($path as $i => $alias) { + $j = $i - 1; + + $queryData['joins'][] = array( + 'table' => $db->fullTableName($this), + 'alias' => "{$type}{$i}", + 'type' => 'LEFT', + 'conditions' => array( + $db->name("{$type}{$i}.lft") . ' > ' . $db->name("{$type}{$j}.lft"), + $db->name("{$type}{$i}.rght") . ' < ' . $db->name("{$type}{$j}.rght"), + $db->name("{$type}{$i}.alias") . ' = ' . $db->value($alias, 'string'), + $db->name("{$type}{$j}.id") . ' = ' . $db->name("{$type}{$i}.parent_id") + ) + ); + + $queryData['conditions'] = array('or' => array( + $db->name("{$type}.lft") . ' <= ' . $db->name("{$type}0.lft") . ' AND ' . $db->name("{$type}.rght") . ' >= ' . $db->name("{$type}0.rght"), + $db->name("{$type}.lft") . ' <= ' . $db->name("{$type}{$i}.lft") . ' AND ' . $db->name("{$type}.rght") . ' >= ' . $db->name("{$type}{$i}.rght")) + ); + } + $result = $db->read($this, $queryData, -1); + $path = array_values($path); + + if ( + !isset($result[0][$type]) || + (!empty($path) && $result[0][$type]['alias'] != $path[count($path) - 1]) || + (empty($path) && $result[0][$type]['alias'] != $start) + ) { + return false; + } + } elseif (is_object($ref) && is_a($ref, 'Model')) { + $ref = array('model' => $ref->alias, 'foreign_key' => $ref->id); + } elseif (is_array($ref) && !(isset($ref['model']) && isset($ref['foreign_key']))) { + $name = key($ref); + + if (PHP5) { + $model = ClassRegistry::init(array('class' => $name, 'alias' => $name)); + } else { + $model =& ClassRegistry::init(array('class' => $name, 'alias' => $name)); + } + + if (empty($model)) { + trigger_error(sprintf(__("Model class '%s' not found in AclNode::node() when trying to bind %s object", true), $type, $this->alias), E_USER_WARNING); + return null; + } + + $tmpRef = null; + if (method_exists($model, 'bindNode')) { + $tmpRef = $model->bindNode($ref); + } + if (empty($tmpRef)) { + $ref = array('model' => $name, 'foreign_key' => $ref[$name][$model->primaryKey]); + } else { + if (is_string($tmpRef)) { + return $this->node($tmpRef); + } + $ref = $tmpRef; + } + } + if (is_array($ref)) { + if (is_array(current($ref)) && is_string(key($ref))) { + $name = key($ref); + $ref = current($ref); + } + foreach ($ref as $key => $val) { + if (strpos($key, $type) !== 0 && strpos($key, '.') === false) { + unset($ref[$key]); + $ref["{$type}0.{$key}"] = $val; + } + } + $queryData = array( + 'conditions' => $ref, + 'fields' => array('id', 'parent_id', 'model', 'foreign_key', 'alias'), + 'joins' => array(array( + 'table' => $db->fullTableName($this), + 'alias' => "{$type}0", + 'type' => 'LEFT', + 'conditions' => array( + $db->name("{$type}.lft") . ' <= ' . $db->name("{$type}0.lft"), + $db->name("{$type}.rght") . ' >= ' . $db->name("{$type}0.rght") + ) + )), + 'order' => $db->name("{$type}.lft") . ' DESC' + ); + $result = $db->read($this, $queryData, -1); + + if (!$result) { + trigger_error(sprintf(__("AclNode::node() - Couldn't find %s node identified by \"%s\"", true), $type, print_r($ref, true)), E_USER_WARNING); + } + } + return $result; + } +} + +/** + * Access Control Object + * + * @package cake + * @subpackage cake.cake.libs.model + */ +class Aco extends AclNode { + +/** + * Model name + * + * @var string + * @access public + */ + var $name = 'Aco'; + +/** + * Binds to ARO nodes through permissions settings + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Aro' => array('with' => 'Permission')); +} + +/** + * Action for Access Control Object + * + * @package cake + * @subpackage cake.cake.libs.model + */ +class AcoAction extends AppModel { + +/** + * Model name + * + * @var string + * @access public + */ + var $name = 'AcoAction'; + +/** + * ACO Actions belong to ACOs + * + * @var array + * @access public + */ + var $belongsTo = array('Aco'); +} + +/** + * Access Request Object + * + * @package cake + * @subpackage cake.cake.libs.model + */ +class Aro extends AclNode { + +/** + * Model name + * + * @var string + * @access public + */ + var $name = 'Aro'; + +/** + * AROs are linked to ACOs by means of Permission + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Aco' => array('with' => 'Permission')); +} + +/** + * Permissions linking AROs with ACOs + * + * @package cake + * @subpackage cake.cake.libs.model + */ +class Permission extends AppModel { + +/** + * Model name + * + * @var string + * @access public + */ + var $name = 'Permission'; + +/** + * Explicitly disable in-memory query caching + * + * @var boolean + * @access public + */ + var $cacheQueries = false; + +/** + * Override default table name + * + * @var string + * @access public + */ + var $useTable = 'aros_acos'; + +/** + * Permissions link AROs with ACOs + * + * @var array + * @access public + */ + var $belongsTo = array('Aro', 'Aco'); + +/** + * No behaviors for this model + * + * @var array + * @access public + */ + var $actsAs = null; + +/** + * Constructor, used to tell this model to use the + * database configured for ACL + */ + function __construct() { + $config = Configure::read('Acl.database'); + if (!empty($config)) { + $this->useDbConfig = $config; + } + parent::__construct(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/model.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/model.php new file mode 100644 index 000000000..e6226e0e8 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/model.php @@ -0,0 +1,3072 @@ + table 'users'; class 'Man' => table 'men') + * The table is required to have at least 'id auto_increment' primary key. + * + * @package cake + * @subpackage cake.cake.libs.model + * @link http://book.cakephp.org/view/1000/Models + */ +class Model extends Overloadable { + +/** + * The name of the DataSource connection that this Model uses + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#useDbConfig-1058 + */ + var $useDbConfig = 'default'; + +/** + * Custom database table name, or null/false if no table association is desired. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#useTable-1059 + */ + var $useTable = null; + +/** + * Custom display field name. Display fields are used by Scaffold, in SELECT boxes' OPTION elements. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#displayField-1062 + */ + var $displayField = null; + +/** + * Value of the primary key ID of the record that this model is currently pointing to. + * Automatically set after database insertions. + * + * @var mixed + * @access public + */ + var $id = false; + +/** + * Container for the data that this model gets from persistent storage (usually, a database). + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#data-1065 + */ + var $data = array(); + +/** + * Table name for this Model. + * + * @var string + * @access public + */ + var $table = false; + +/** + * The name of the primary key field for this model. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#primaryKey-1061 + */ + var $primaryKey = null; + +/** + * Field-by-field table metadata. + * + * @var array + * @access protected + * @link http://book.cakephp.org/view/1057/Model-Attributes#_schema-1066 + */ + var $_schema = null; + +/** + * List of validation rules. Append entries for validation as ('field_name' => '/^perl_compat_regexp$/') + * that have to match with preg_match(). Use these rules with Model::validate() + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#validate-1067 + * @link http://book.cakephp.org/view/1143/Data-Validation + */ + var $validate = array(); + +/** + * List of validation errors. + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1182/Validating-Data-from-the-Controller + */ + var $validationErrors = array(); + +/** + * Database table prefix for tables in model. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#tablePrefix-1060 + */ + var $tablePrefix = null; + +/** + * Name of the model. + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#name-1068 + */ + var $name = null; + +/** + * Alias name for model. + * + * @var string + * @access public + */ + var $alias = null; + +/** + * List of table names included in the model description. Used for associations. + * + * @var array + * @access public + */ + var $tableToModel = array(); + +/** + * Whether or not to log transactions for this model. + * + * @var boolean + * @access public + */ + var $logTransactions = false; + +/** + * Whether or not to cache queries for this model. This enables in-memory + * caching only, the results are not stored beyond the current request. + * + * @var boolean + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#cacheQueries-1069 + */ + var $cacheQueries = false; + +/** + * Detailed list of belongsTo associations. + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1042/belongsTo + */ + var $belongsTo = array(); + +/** + * Detailed list of hasOne associations. + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1041/hasOne + */ + var $hasOne = array(); + +/** + * Detailed list of hasMany associations. + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1043/hasMany + */ + var $hasMany = array(); + +/** + * Detailed list of hasAndBelongsToMany associations. + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1044/hasAndBelongsToMany-HABTM + */ + var $hasAndBelongsToMany = array(); + +/** + * List of behaviors to load when the model object is initialized. Settings can be + * passed to behaviors by using the behavior name as index. Eg: + * + * var $actsAs = array('Translate', 'MyBehavior' => array('setting1' => 'value1')) + * + * @var array + * @access public + * @link http://book.cakephp.org/view/1072/Using-Behaviors + */ + var $actsAs = null; + +/** + * Holds the Behavior objects currently bound to this model. + * + * @var BehaviorCollection + * @access public + */ + var $Behaviors = null; + +/** + * Whitelist of fields allowed to be saved. + * + * @var array + * @access public + */ + var $whitelist = array(); + +/** + * Whether or not to cache sources for this model. + * + * @var boolean + * @access public + */ + var $cacheSources = true; + +/** + * Type of find query currently executing. + * + * @var string + * @access public + */ + var $findQueryType = null; + +/** + * Number of associations to recurse through during find calls. Fetches only + * the first level by default. + * + * @var integer + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#recursive-1063 + */ + var $recursive = 1; + +/** + * The column name(s) and direction(s) to order find results by default. + * + * var $order = "Post.created DESC"; + * var $order = array("Post.view_count DESC", "Post.rating DESC"); + * + * @var string + * @access public + * @link http://book.cakephp.org/view/1057/Model-Attributes#order-1064 + */ + var $order = null; + +/** + * Array of virtual fields this model has. Virtual fields are aliased + * SQL expressions. Fields added to this property will be read as other fields in a model + * but will not be saveable. + * + * `var $virtualFields = array('two' => '1 + 1');` + * + * Is a simplistic example of how to set virtualFields + * + * @var array + * @access public + */ + var $virtualFields = array(); + +/** + * Default list of association keys. + * + * @var array + * @access private + */ + var $__associationKeys = array( + 'belongsTo' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'counterCache'), + 'hasOne' => array('className', 'foreignKey','conditions', 'fields','order', 'dependent'), + 'hasMany' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'dependent', 'exclusive', 'finderQuery', 'counterQuery'), + 'hasAndBelongsToMany' => array('className', 'joinTable', 'with', 'foreignKey', 'associationForeignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'unique', 'finderQuery', 'deleteQuery', 'insertQuery') + ); + +/** + * Holds provided/generated association key names and other data for all associations. + * + * @var array + * @access private + */ + var $__associations = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'); + +/** + * Holds model associations temporarily to allow for dynamic (un)binding. + * + * @var array + * @access private + */ + var $__backAssociation = array(); + +/** + * The ID of the model record that was last inserted. + * + * @var integer + * @access private + */ + var $__insertID = null; + +/** + * The number of records returned by the last query. + * + * @var integer + * @access private + */ + var $__numRows = null; + +/** + * The number of records affected by the last query. + * + * @var integer + * @access private + */ + var $__affectedRows = null; + +/** + * List of valid finder method options, supplied as the first parameter to find(). + * + * @var array + * @access protected + */ + var $_findMethods = array( + 'all' => true, 'first' => true, 'count' => true, + 'neighbors' => true, 'list' => true, 'threaded' => true + ); + +/** + * Constructor. Binds the model's database table to the object. + * + * If `$id` is an array it can be used to pass several options into the model. + * + * - id - The id to start the model on. + * - table - The table to use for this model. + * - ds - The connection name this model is connected to. + * - name - The name of the model eg. Post. + * - alias - The alias of the model, this is used for registering the instance in the `ClassRegistry`. + * eg. `ParentThread` + * + * ### Overriding Model's __construct method. + * + * When overriding Model::__construct() be careful to include and pass in all 3 of the + * arguments to `parent::__construct($id, $table, $ds);` + * + * ### Dynamically creating models + * + * You can dynamically create model instances using the $id array syntax. + * + * {{{ + * $Post = new Model(array('table' => 'posts', 'name' => 'Post', 'ds' => 'connection2')); + * }}} + * + * Would create a model attached to the posts table on connection2. Dynamic model creation is useful + * when you want a model object that contains no associations or attached behaviors. + * + * @param mixed $id Set this ID for this model on startup, can also be an array of options, see above. + * @param string $table Name of database table to use. + * @param string $ds DataSource connection name. + */ + function __construct($id = false, $table = null, $ds = null) { + parent::__construct(); + + if (is_array($id)) { + extract(array_merge( + array( + 'id' => $this->id, 'table' => $this->useTable, 'ds' => $this->useDbConfig, + 'name' => $this->name, 'alias' => $this->alias + ), + $id + )); + } + + if ($this->name === null) { + $this->name = (isset($name) ? $name : get_class($this)); + } + + if ($this->alias === null) { + $this->alias = (isset($alias) ? $alias : $this->name); + } + + if ($this->primaryKey === null) { + $this->primaryKey = 'id'; + } + + ClassRegistry::addObject($this->alias, $this); + + $this->id = $id; + unset($id); + + if ($table === false) { + $this->useTable = false; + } elseif ($table) { + $this->useTable = $table; + } + + if ($ds !== null) { + $this->useDbConfig = $ds; + } + + if (is_subclass_of($this, 'AppModel')) { + $appVars = get_class_vars('AppModel'); + $merge = array('_findMethods'); + + if ($this->actsAs !== null || $this->actsAs !== false) { + $merge[] = 'actsAs'; + } + $parentClass = get_parent_class($this); + if (strtolower($parentClass) !== 'appmodel') { + $parentVars = get_class_vars($parentClass); + foreach ($merge as $var) { + if (isset($parentVars[$var]) && !empty($parentVars[$var])) { + $appVars[$var] = Set::merge($appVars[$var], $parentVars[$var]); + } + } + } + + foreach ($merge as $var) { + if (isset($appVars[$var]) && !empty($appVars[$var]) && is_array($this->{$var})) { + $this->{$var} = Set::merge($appVars[$var], $this->{$var}); + } + } + } + $this->Behaviors = new BehaviorCollection(); + + if ($this->useTable !== false) { + $this->setDataSource($ds); + + if ($this->useTable === null) { + $this->useTable = Inflector::tableize($this->name); + } + $this->setSource($this->useTable); + + if ($this->displayField == null) { + $this->displayField = $this->hasField(array('title', 'name', $this->primaryKey)); + } + } elseif ($this->table === false) { + $this->table = Inflector::tableize($this->name); + } + $this->__createLinks(); + $this->Behaviors->init($this->alias, $this->actsAs); + } + +/** + * Handles custom method calls, like findBy for DB models, + * and custom RPC calls for remote data sources. + * + * @param string $method Name of method to call. + * @param array $params Parameters for the method. + * @return mixed Whatever is returned by called method + * @access protected + */ + function call__($method, $params) { + $result = $this->Behaviors->dispatchMethod($this, $method, $params); + + if ($result !== array('unhandled')) { + return $result; + } + $db =& ConnectionManager::getDataSource($this->useDbConfig); + $return = $db->query($method, $params, $this); + + if (!PHP5) { + $this->resetAssociations(); + } + return $return; + } + +/** + * Bind model associations on the fly. + * + * If `$reset` is false, association will not be reset + * to the originals defined in the model + * + * Example: Add a new hasOne binding to the Profile model not + * defined in the model source code: + * + * `$this->User->bindModel( array('hasOne' => array('Profile')) );` + * + * Bindings that are not made permanent will be reset by the next Model::find() call on this + * model. + * + * @param array $params Set of bindings (indexed by binding type) + * @param boolean $reset Set to false to make the binding permanent + * @return boolean Success + * @access public + * @link http://book.cakephp.org/view/1045/Creating-and-Destroying-Associations-on-the-Fly + */ + function bindModel($params, $reset = true) { + foreach ($params as $assoc => $model) { + if ($reset === true && !isset($this->__backAssociation[$assoc])) { + $this->__backAssociation[$assoc] = $this->{$assoc}; + } + foreach ($model as $key => $value) { + $assocName = $key; + + if (is_numeric($key)) { + $assocName = $value; + $value = array(); + } + $modelName = $assocName; + $this->{$assoc}[$assocName] = $value; + + if ($reset === false && isset($this->__backAssociation[$assoc])) { + $this->__backAssociation[$assoc][$assocName] = $value; + } + } + } + $this->__createLinks(); + return true; + } + +/** + * Turn off associations on the fly. + * + * If $reset is false, association will not be reset + * to the originals defined in the model + * + * Example: Turn off the associated Model Support request, + * to temporarily lighten the User model: + * + * `$this->User->unbindModel( array('hasMany' => array('Supportrequest')) );` + * + * unbound models that are not made permanent will reset with the next call to Model::find() + * + * @param array $params Set of bindings to unbind (indexed by binding type) + * @param boolean $reset Set to false to make the unbinding permanent + * @return boolean Success + * @access public + * @link http://book.cakephp.org/view/1045/Creating-and-Destroying-Associations-on-the-Fly + */ + function unbindModel($params, $reset = true) { + foreach ($params as $assoc => $models) { + if ($reset === true && !isset($this->__backAssociation[$assoc])) { + $this->__backAssociation[$assoc] = $this->{$assoc}; + } + foreach ($models as $model) { + if ($reset === false && isset($this->__backAssociation[$assoc][$model])) { + unset($this->__backAssociation[$assoc][$model]); + } + unset($this->{$assoc}[$model]); + } + } + return true; + } + +/** + * Create a set of associations. + * + * @return void + * @access private + */ + function __createLinks() { + foreach ($this->__associations as $type) { + if (!is_array($this->{$type})) { + $this->{$type} = explode(',', $this->{$type}); + + foreach ($this->{$type} as $i => $className) { + $className = trim($className); + unset ($this->{$type}[$i]); + $this->{$type}[$className] = array(); + } + } + + if (!empty($this->{$type})) { + foreach ($this->{$type} as $assoc => $value) { + $plugin = null; + + if (is_numeric($assoc)) { + unset ($this->{$type}[$assoc]); + $assoc = $value; + $value = array(); + $this->{$type}[$assoc] = $value; + + if (strpos($assoc, '.') !== false) { + $value = $this->{$type}[$assoc]; + unset($this->{$type}[$assoc]); + list($plugin, $assoc) = pluginSplit($assoc, true); + $this->{$type}[$assoc] = $value; + } + } + $className = $assoc; + + if (!empty($value['className'])) { + list($plugin, $className) = pluginSplit($value['className'], true); + $this->{$type}[$assoc]['className'] = $className; + } + $this->__constructLinkedModel($assoc, $plugin . $className); + } + $this->__generateAssociation($type); + } + } + } + +/** + * Private helper method to create associated models of a given class. + * + * @param string $assoc Association name + * @param string $className Class name + * @deprecated $this->$className use $this->$assoc instead. $assoc is the 'key' in the associations array; + * examples: var $hasMany = array('Assoc' => array('className' => 'ModelName')); + * usage: $this->Assoc->modelMethods(); + * + * var $hasMany = array('ModelName'); + * usage: $this->ModelName->modelMethods(); + * @return void + * @access private + */ + function __constructLinkedModel($assoc, $className = null) { + if (empty($className)) { + $className = $assoc; + } + + if (!isset($this->{$assoc}) || $this->{$assoc}->name !== $className) { + $model = array('class' => $className, 'alias' => $assoc); + if (PHP5) { + $this->{$assoc} = ClassRegistry::init($model); + } else { + $this->{$assoc} =& ClassRegistry::init($model); + } + if (strpos($className, '.') !== false) { + ClassRegistry::addObject($className, $this->{$assoc}); + } + if ($assoc) { + $this->tableToModel[$this->{$assoc}->table] = $assoc; + } + } + } + +/** + * Build an array-based association from string. + * + * @param string $type 'belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany' + * @return void + * @access private + */ + function __generateAssociation($type) { + foreach ($this->{$type} as $assocKey => $assocData) { + $class = $assocKey; + $dynamicWith = false; + + foreach ($this->__associationKeys[$type] as $key) { + + if (!isset($this->{$type}[$assocKey][$key]) || $this->{$type}[$assocKey][$key] === null) { + $data = ''; + + switch ($key) { + case 'fields': + $data = ''; + break; + + case 'foreignKey': + $data = (($type == 'belongsTo') ? Inflector::underscore($assocKey) : Inflector::singularize($this->table)) . '_id'; + break; + + case 'associationForeignKey': + $data = Inflector::singularize($this->{$class}->table) . '_id'; + break; + + case 'with': + $data = Inflector::camelize(Inflector::singularize($this->{$type}[$assocKey]['joinTable'])); + $dynamicWith = true; + break; + + case 'joinTable': + $tables = array($this->table, $this->{$class}->table); + sort ($tables); + $data = $tables[0] . '_' . $tables[1]; + break; + + case 'className': + $data = $class; + break; + + case 'unique': + $data = true; + break; + } + $this->{$type}[$assocKey][$key] = $data; + } + } + + if (!empty($this->{$type}[$assocKey]['with'])) { + $joinClass = $this->{$type}[$assocKey]['with']; + if (is_array($joinClass)) { + $joinClass = key($joinClass); + } + + $plugin = null; + if (strpos($joinClass, '.') !== false) { + list($plugin, $joinClass) = explode('.', $joinClass); + $plugin .= '.'; + $this->{$type}[$assocKey]['with'] = $joinClass; + } + + if (!ClassRegistry::isKeySet($joinClass) && $dynamicWith === true) { + $this->{$joinClass} = new AppModel(array( + 'name' => $joinClass, + 'table' => $this->{$type}[$assocKey]['joinTable'], + 'ds' => $this->useDbConfig + )); + } else { + $this->__constructLinkedModel($joinClass, $plugin . $joinClass); + $this->{$type}[$assocKey]['joinTable'] = $this->{$joinClass}->table; + } + + if (count($this->{$joinClass}->schema()) <= 2 && $this->{$joinClass}->primaryKey !== false) { + $this->{$joinClass}->primaryKey = $this->{$type}[$assocKey]['foreignKey']; + } + } + } + } + +/** + * Sets a custom table for your controller class. Used by your controller to select a database table. + * + * @param string $tableName Name of the custom table + * @return void + * @access public + */ + function setSource($tableName) { +// debug_print_backtrace(); + $this->setDataSource($this->useDbConfig); + $db =& ConnectionManager::getDataSource($this->useDbConfig); + $db->cacheSources = ($this->cacheSources && $db->cacheSources); + + if ($db->isInterfaceSupported('listSources')) { + $sources = $db->listSources(); + if (is_array($sources) && !in_array(strtolower($this->tablePrefix . $tableName), array_map('strtolower', $sources))) { + return $this->cakeError('missingTable', array(array( + 'className' => $this->alias, + 'table' => $this->tablePrefix . $tableName, + 'code' => 500 + ))); + } + $this->_schema = null; + } + $this->table = $this->useTable = $tableName; + $this->tableToModel[$this->table] = $this->alias; + $this->schema(); + } + +/** + * This function does two things: + * + * 1. it scans the array $one for the primary key, + * and if that's found, it sets the current id to the value of $one[id]. + * For all other keys than 'id' the keys and values of $one are copied to the 'data' property of this object. + * 2. Returns an array with all of $one's keys and values. + * (Alternative indata: two strings, which are mangled to + * a one-item, two-dimensional array using $one for a key and $two as its value.) + * + * @param mixed $one Array or string of data + * @param string $two Value string for the alternative indata method + * @return array Data with all of $one's keys and values + * @access public + * @link http://book.cakephp.org/view/1031/Saving-Your-Data + */ + function set($one, $two = null) { + if (!$one) { + return; + } + if (is_object($one)) { + $one = Set::reverse($one); + } + + if (is_array($one)) { + $data = $one; + if (empty($one[$this->alias])) { + if ($this->getAssociated(key($one)) === null) { + $data = array($this->alias => $one); + } + } + } else { + $data = array($this->alias => array($one => $two)); + } + + foreach ($data as $modelName => $fieldSet) { + if (is_array($fieldSet)) { + + foreach ($fieldSet as $fieldName => $fieldValue) { + if (isset($this->validationErrors[$fieldName])) { + unset ($this->validationErrors[$fieldName]); + } + + if ($modelName === $this->alias) { + if ($fieldName === $this->primaryKey) { + $this->id = $fieldValue; + } + } + if (is_array($fieldValue) || is_object($fieldValue)) { + $fieldValue = $this->deconstruct($fieldName, $fieldValue); + } + $this->data[$modelName][$fieldName] = $fieldValue; + } + } + } + return $data; + } + +/** + * Deconstructs a complex data type (array or object) into a single field value. + * + * @param string $field The name of the field to be deconstructed + * @param mixed $data An array or object to be deconstructed into a field + * @return mixed The resulting data that should be assigned to a field + * @access public + */ + function deconstruct($field, $data) { + if (!is_array($data)) { + return $data; + } + + $copy = $data; + $type = $this->getColumnType($field); + + if (in_array($type, array('datetime', 'timestamp', 'date', 'time'))) { + $useNewDate = (isset($data['year']) || isset($data['month']) || + isset($data['day']) || isset($data['hour']) || isset($data['minute'])); + + $dateFields = array('Y' => 'year', 'm' => 'month', 'd' => 'day', 'H' => 'hour', 'i' => 'min', 's' => 'sec'); + $timeFields = array('H' => 'hour', 'i' => 'min', 's' => 'sec'); + + $db =& ConnectionManager::getDataSource($this->useDbConfig); + $format = $db->columns[$type]['format']; + $date = array(); + + if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] != 12 && 'pm' == $data['meridian']) { + $data['hour'] = $data['hour'] + 12; + } + if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] == 12 && 'am' == $data['meridian']) { + $data['hour'] = '00'; + } + if ($type == 'time') { + foreach ($timeFields as $key => $val) { + if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') { + $data[$val] = '00'; + } elseif ($data[$val] === '') { + $data[$val] = ''; + } else { + $data[$val] = sprintf('%02d', $data[$val]); + } + if (!empty($data[$val])) { + $date[$key] = $data[$val]; + } else { + return null; + } + } + } + + if ($type == 'datetime' || $type == 'timestamp' || $type == 'date') { + foreach ($dateFields as $key => $val) { + if ($val == 'hour' || $val == 'min' || $val == 'sec') { + if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') { + $data[$val] = '00'; + } else { + $data[$val] = sprintf('%02d', $data[$val]); + } + } + if (!isset($data[$val]) || isset($data[$val]) && (empty($data[$val]) || $data[$val][0] === '-')) { + return null; + } + if (isset($data[$val]) && !empty($data[$val])) { + $date[$key] = $data[$val]; + } + } + } + $date = str_replace(array_keys($date), array_values($date), $format); + if ($useNewDate && !empty($date)) { + return $date; + } + } + return $data; + } + +/** + * Returns an array of table metadata (column names and types) from the database. + * $field => keys(type, null, default, key, length, extra) + * + * @param mixed $field Set to true to reload schema, or a string to return a specific field + * @return array Array of table metadata + * @access public + */ + function schema($field = false) { + if (!is_array($this->_schema) || $field === true) { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + $db->cacheSources = ($this->cacheSources && $db->cacheSources); + if ($db->isInterfaceSupported('describe') && $this->useTable !== false) { + $this->_schema = $db->describe($this, $field); + } elseif ($this->useTable === false) { + $this->_schema = array(); + } + } + if (is_string($field)) { + if (isset($this->_schema[$field])) { + return $this->_schema[$field]; + } else { + return null; + } + } + return $this->_schema; + } + +/** + * Returns an associative array of field names and column types. + * + * @return array Field types indexed by field name + * @access public + */ + function getColumnTypes() { + $columns = $this->schema(); + if (empty($columns)) { + trigger_error(__('(Model::getColumnTypes) Unable to build model field data. If you are using a model without a database table, try implementing schema()', true), E_USER_WARNING); + } + $cols = array(); + foreach ($columns as $field => $values) { + $cols[$field] = $values['type']; + } + return $cols; + } + +/** + * Returns the column type of a column in the model. + * + * @param string $column The name of the model column + * @return string Column type + * @access public + */ + function getColumnType($column) { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + $cols = $this->schema(); + $model = null; + + $column = str_replace(array($db->startQuote, $db->endQuote), '', $column); + + if (strpos($column, '.')) { + list($model, $column) = explode('.', $column); + } + if ($model != $this->alias && isset($this->{$model})) { + return $this->{$model}->getColumnType($column); + } + if (isset($cols[$column]) && isset($cols[$column]['type'])) { + return $cols[$column]['type']; + } + return null; + } + +/** + * Returns true if the supplied field exists in the model's database table. + * + * @param mixed $name Name of field to look for, or an array of names + * @param boolean $checkVirtual checks if the field is declared as virtual + * @return mixed If $name is a string, returns a boolean indicating whether the field exists. + * If $name is an array of field names, returns the first field that exists, + * or false if none exist. + * @access public + */ + function hasField($name, $checkVirtual = false) { + if (is_array($name)) { + foreach ($name as $n) { + if ($this->hasField($n, $checkVirtual)) { + return $n; + } + } + return false; + } + + if ($checkVirtual && !empty($this->virtualFields)) { + if ($this->isVirtualField($name)) { + return true; + } + } + + if (empty($this->_schema)) { + $this->schema(); + } + + if ($this->_schema != null) { + return isset($this->_schema[$name]); + } + return false; + } + +/** + * Returns true if the supplied field is a model Virtual Field + * + * @param mixed $name Name of field to look for + * @return boolean indicating whether the field exists as a model virtual field. + * @access public + */ + function isVirtualField($field) { + if (empty($this->virtualFields) || !is_string($field)) { + return false; + } + if (isset($this->virtualFields[$field])) { + return true; + } + if (strpos($field, '.') !== false) { + list($model, $field) = explode('.', $field); + if (isset($this->virtualFields[$field])) { + return true; + } + } + return false; + } + +/** + * Returns the expression for a model virtual field + * + * @param mixed $name Name of field to look for + * @return mixed If $field is string expression bound to virtual field $field + * If $field is null, returns an array of all model virtual fields + * or false if none $field exist. + * @access public + */ + function getVirtualField($field = null) { + if ($field == null) { + return empty($this->virtualFields) ? false : $this->virtualFields; + } + if ($this->isVirtualField($field)) { + if (strpos($field, '.') !== false) { + list($model, $field) = explode('.', $field); + } + return $this->virtualFields[$field]; + } + return false; + } + +/** + * Initializes the model for writing a new record, loading the default values + * for those fields that are not defined in $data, and clearing previous validation errors. + * Especially helpful for saving data in loops. + * + * @param mixed $data Optional data array to assign to the model after it is created. If null or false, + * schema data defaults are not merged. + * @param boolean $filterKey If true, overwrites any primary key input with an empty value + * @return array The current Model::data; after merging $data and/or defaults from database + * @access public + * @link http://book.cakephp.org/view/1031/Saving-Your-Data + */ + function create($data = array(), $filterKey = false) { + $defaults = array(); + $this->id = false; + $this->data = array(); + $this->validationErrors = array(); + + if ($data !== null && $data !== false) { + foreach ($this->schema() as $field => $properties) { + if ($this->primaryKey !== $field && isset($properties['default']) && $properties['default'] !== '') { + $defaults[$field] = $properties['default']; + } + } + $this->set($defaults); + $this->set($data); + } + if ($filterKey) { + $this->set($this->primaryKey, false); + } + return $this->data; + } + +/** + * Returns a list of fields from the database, and sets the current model + * data (Model::$data) with the record found. + * + * @param mixed $fields String of single fieldname, or an array of fieldnames. + * @param mixed $id The ID of the record to read + * @return array Array of database fields, or false if not found + * @access public + * @link http://book.cakephp.org/view/1017/Retrieving-Your-Data#read-1029 + */ + function read($fields = null, $id = null) { + $this->validationErrors = array(); + + if ($id != null) { + $this->id = $id; + } + + $id = $this->id; + + if (is_array($this->id)) { + $id = $this->id[0]; + } + + if ($id !== null && $id !== false) { + $this->data = $this->find('first', array( + 'conditions' => array($this->alias . '.' . $this->primaryKey => $id), + 'fields' => $fields + )); + return $this->data; + } else { + return false; + } + } + +/** + * Returns the contents of a single field given the supplied conditions, in the + * supplied order. + * + * @param string $name Name of field to get + * @param array $conditions SQL conditions (defaults to NULL) + * @param string $order SQL ORDER BY fragment + * @return string field contents, or false if not found + * @access public + * @link http://book.cakephp.org/view/1017/Retrieving-Your-Data#field-1028 + */ + function field($name, $conditions = null, $order = null) { + if ($conditions === null && $this->id !== false) { + $conditions = array($this->alias . '.' . $this->primaryKey => $this->id); + } + if ($this->recursive >= 1) { + $recursive = -1; + } else { + $recursive = $this->recursive; + } + $fields = $name; + if ($data = $this->find('first', compact('conditions', 'fields', 'order', 'recursive'))) { + if (strpos($name, '.') === false) { + if (isset($data[$this->alias][$name])) { + return $data[$this->alias][$name]; + } + } else { + $name = explode('.', $name); + if (isset($data[$name[0]][$name[1]])) { + return $data[$name[0]][$name[1]]; + } + } + if (isset($data[0]) && count($data[0]) > 0) { + return array_shift($data[0]); + } + } else { + return false; + } + } + +/** + * Saves the value of a single field to the database, based on the current + * model ID. + * + * @param string $name Name of the table field + * @param mixed $value Value of the field + * @param array $validate See $options param in Model::save(). Does not respect 'fieldList' key if passed + * @return boolean See Model::save() + * @access public + * @see Model::save() + * @link http://book.cakephp.org/view/1031/Saving-Your-Data + */ + function saveField($name, $value, $validate = false) { + $id = $this->id; + $this->create(false); + + if (is_array($validate)) { + $options = array_merge(array('validate' => false, 'fieldList' => array($name)), $validate); + } else { + $options = array('validate' => $validate, 'fieldList' => array($name)); + } + return $this->save(array($this->alias => array($this->primaryKey => $id, $name => $value)), $options); + } + +/** + * Saves model data (based on white-list, if supplied) to the database. By + * default, validation occurs before save. + * + * @param array $data Data to save. + * @param mixed $validate Either a boolean, or an array. + * If a boolean, indicates whether or not to validate before saving. + * If an array, allows control of validate, callbacks, and fieldList + * @param array $fieldList List of fields to allow to be written + * @return mixed On success Model::$data if its not empty or true, false on failure + * @access public + * @link http://book.cakephp.org/view/1031/Saving-Your-Data + */ + function save($data = null, $validate = true, $fieldList = array()) { + $defaults = array('validate' => true, 'fieldList' => array(), 'callbacks' => true); + $_whitelist = $this->whitelist; + $fields = array(); + + if (!is_array($validate)) { + $options = array_merge($defaults, compact('validate', 'fieldList', 'callbacks')); + } else { + $options = array_merge($defaults, $validate); + } + + if (!empty($options['fieldList'])) { + $this->whitelist = $options['fieldList']; + } elseif ($options['fieldList'] === null) { + $this->whitelist = array(); + } + $this->set($data); + + if (empty($this->data) && !$this->hasField(array('created', 'updated', 'modified'))) { + return false; + } + + foreach (array('created', 'updated', 'modified') as $field) { + $keyPresentAndEmpty = ( + isset($this->data[$this->alias]) && + array_key_exists($field, $this->data[$this->alias]) && + $this->data[$this->alias][$field] === null + ); + if ($keyPresentAndEmpty) { + unset($this->data[$this->alias][$field]); + } + } + + $exists = $this->exists(); + $dateFields = array('modified', 'updated'); + + if (!$exists) { + $dateFields[] = 'created'; + } + if (isset($this->data[$this->alias])) { + $fields = array_keys($this->data[$this->alias]); + } + if ($options['validate'] && !$this->validates($options)) { + $this->whitelist = $_whitelist; + return false; + } + + $db =& ConnectionManager::getDataSource($this->useDbConfig); + + foreach ($dateFields as $updateCol) { + if ($this->hasField($updateCol) && !in_array($updateCol, $fields)) { + $default = array('formatter' => 'date'); + $colType = array_merge($default, $db->columns[$this->getColumnType($updateCol)]); + if (!array_key_exists('format', $colType)) { + $time = strtotime('now'); + } else { + $time = $colType['formatter']($colType['format']); + } + if (!empty($this->whitelist)) { + $this->whitelist[] = $updateCol; + } + $this->set($updateCol, $time); + } + } + + if ($options['callbacks'] === true || $options['callbacks'] === 'before') { + $result = $this->Behaviors->trigger($this, 'beforeSave', array($options), array( + 'break' => true, 'breakOn' => false + )); + if (!$result || !$this->beforeSave($options)) { + $this->whitelist = $_whitelist; + return false; + } + } + + if (empty($this->data[$this->alias][$this->primaryKey])) { + unset($this->data[$this->alias][$this->primaryKey]); + } + $fields = $values = array(); + + foreach ($this->data as $n => $v) { + if (isset($this->hasAndBelongsToMany[$n])) { + if (isset($v[$n])) { + $v = $v[$n]; + } + $joined[$n] = $v; + } else { + if ($n === $this->alias) { + foreach (array('created', 'updated', 'modified') as $field) { + if (array_key_exists($field, $v) && empty($v[$field])) { + unset($v[$field]); + } + } + + foreach ($v as $x => $y) { + if ($this->hasField($x) && (empty($this->whitelist) || in_array($x, $this->whitelist))) { + list($fields[], $values[]) = array($x, $y); + } + } + } + } + } + $count = count($fields); + + if (!$exists && $count > 0) { + $this->id = false; + } + $success = true; + $created = false; + + if ($count > 0) { + $cache = $this->_prepareUpdateFields(array_combine($fields, $values)); + + if (!empty($this->id)) { + $success = (bool)$db->update($this, $fields, $values); + } else { + $fInfo = $this->_schema[$this->primaryKey]; + $isUUID = ($fInfo['length'] == 36 && + ($fInfo['type'] === 'string' || $fInfo['type'] === 'binary') + ); + if (empty($this->data[$this->alias][$this->primaryKey]) && $isUUID) { + if (array_key_exists($this->primaryKey, $this->data[$this->alias])) { + $j = array_search($this->primaryKey, $fields); + $values[$j] = String::uuid(); + } else { + list($fields[], $values[]) = array($this->primaryKey, String::uuid()); + } + } + + if (!$db->create($this, $fields, $values)) { + $success = $created = false; + } else { + $created = true; + } + } + + if ($success && !empty($this->belongsTo)) { + $this->updateCounterCache($cache, $created); + } + } + + if (!empty($joined) && $success === true) { + $this->__saveMulti($joined, $this->id, $db); + } + + if ($success && $count > 0) { + if (!empty($this->data)) { + $success = $this->data; + } + if ($options['callbacks'] === true || $options['callbacks'] === 'after') { + $this->Behaviors->trigger($this, 'afterSave', array($created, $options)); + $this->afterSave($created); + } + if (!empty($this->data)) { + $success = Set::merge($success, $this->data); + } + $this->data = false; + $this->_clearCache(); + $this->validationErrors = array(); + } + $this->whitelist = $_whitelist; + return $success; + } + +/** + * Saves model hasAndBelongsToMany data to the database. + * + * @param array $joined Data to save + * @param mixed $id ID of record in this model + * @access private + */ + function __saveMulti($joined, $id, &$db) { + foreach ($joined as $assoc => $data) { + + if (isset($this->hasAndBelongsToMany[$assoc])) { + list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']); + + $isUUID = !empty($this->{$join}->primaryKey) && ( + $this->{$join}->_schema[$this->{$join}->primaryKey]['length'] == 36 && ( + $this->{$join}->_schema[$this->{$join}->primaryKey]['type'] === 'string' || + $this->{$join}->_schema[$this->{$join}->primaryKey]['type'] === 'binary' + ) + ); + + $newData = $newValues = array(); + $primaryAdded = false; + + $fields = array( + $db->name($this->hasAndBelongsToMany[$assoc]['foreignKey']), + $db->name($this->hasAndBelongsToMany[$assoc]['associationForeignKey']) + ); + + $idField = $db->name($this->{$join}->primaryKey); + if ($isUUID && !in_array($idField, $fields)) { + $fields[] = $idField; + $primaryAdded = true; + } + + foreach ((array)$data as $row) { + if ((is_string($row) && (strlen($row) == 36 || strlen($row) == 16)) || is_numeric($row)) { + $values = array( + $db->value($id, $this->getColumnType($this->primaryKey)), + $db->value($row) + ); + if ($isUUID && $primaryAdded) { + $values[] = $db->value(String::uuid()); + } + $values = implode(',', $values); + $newValues[] = "({$values})"; + unset($values); + } elseif (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) { + $newData[] = $row; + } elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) { + $newData[] = $row[$join]; + } + } + + if ($this->hasAndBelongsToMany[$assoc]['unique']) { + $conditions = array( + $join . '.' . $this->hasAndBelongsToMany[$assoc]['foreignKey'] => $id + ); + if (!empty($this->hasAndBelongsToMany[$assoc]['conditions'])) { + $conditions = array_merge($conditions, (array)$this->hasAndBelongsToMany[$assoc]['conditions']); + } + $links = $this->{$join}->find('all', array( + 'conditions' => $conditions, + 'recursive' => empty($this->hasAndBelongsToMany[$assoc]['conditions']) ? -1 : 0, + 'fields' => $this->hasAndBelongsToMany[$assoc]['associationForeignKey'] + )); + + $associationForeignKey = "{$join}." . $this->hasAndBelongsToMany[$assoc]['associationForeignKey']; + $oldLinks = Set::extract($links, "{n}.{$associationForeignKey}"); + if (!empty($oldLinks)) { + $conditions[$associationForeignKey] = $oldLinks; + $db->delete($this->{$join}, $conditions); + } + } + + if (!empty($newData)) { + foreach ($newData as $data) { + $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $id; + $this->{$join}->create($data); + $this->{$join}->save(); + } + } + + if (!empty($newValues)) { + $fields = implode(',', $fields); + $db->insertMulti($this->{$join}, $fields, $newValues); + } + } + } + } + +/** + * Updates the counter cache of belongsTo associations after a save or delete operation + * + * @param array $keys Optional foreign key data, defaults to the information $this->data + * @param boolean $created True if a new record was created, otherwise only associations with + * 'counterScope' defined get updated + * @return void + * @access public + */ + function updateCounterCache($keys = array(), $created = false) { + $keys = empty($keys) ? $this->data[$this->alias] : $keys; + $keys['old'] = isset($keys['old']) ? $keys['old'] : array(); + + foreach ($this->belongsTo as $parent => $assoc) { + $foreignKey = $assoc['foreignKey']; + $fkQuoted = $this->escapeField($assoc['foreignKey']); + + if (!empty($assoc['counterCache'])) { + if ($assoc['counterCache'] === true) { + $assoc['counterCache'] = Inflector::underscore($this->alias) . '_count'; + } + if (!$this->{$parent}->hasField($assoc['counterCache'])) { + continue; + } + + if (!array_key_exists($foreignKey, $keys)) { + $keys[$foreignKey] = $this->field($foreignKey); + } + $recursive = (isset($assoc['counterScope']) ? 1 : -1); + $conditions = ($recursive == 1) ? (array)$assoc['counterScope'] : array(); + + if (isset($keys['old'][$foreignKey])) { + if ($keys['old'][$foreignKey] != $keys[$foreignKey]) { + $conditions[$fkQuoted] = $keys['old'][$foreignKey]; + $count = intval($this->find('count', compact('conditions', 'recursive'))); + + $this->{$parent}->updateAll( + array($assoc['counterCache'] => $count), + array($this->{$parent}->escapeField() => $keys['old'][$foreignKey]) + ); + } + } + $conditions[$fkQuoted] = $keys[$foreignKey]; + + if ($recursive == 1) { + $conditions = array_merge($conditions, (array)$assoc['counterScope']); + } + $count = intval($this->find('count', compact('conditions', 'recursive'))); + + $this->{$parent}->updateAll( + array($assoc['counterCache'] => $count), + array($this->{$parent}->escapeField() => $keys[$foreignKey]) + ); + } + } + } + +/** + * Helper method for Model::updateCounterCache(). Checks the fields to be updated for + * + * @param array $data The fields of the record that will be updated + * @return array Returns updated foreign key values, along with an 'old' key containing the old + * values, or empty if no foreign keys are updated. + * @access protected + */ + function _prepareUpdateFields($data) { + $foreignKeys = array(); + foreach ($this->belongsTo as $assoc => $info) { + if ($info['counterCache']) { + $foreignKeys[$assoc] = $info['foreignKey']; + } + } + $included = array_intersect($foreignKeys, array_keys($data)); + + if (empty($included) || empty($this->id)) { + return array(); + } + $old = $this->find('first', array( + 'conditions' => array($this->primaryKey => $this->id), + 'fields' => array_values($included), + 'recursive' => -1 + )); + return array_merge($data, array('old' => $old[$this->alias])); + } + +/** + * Saves multiple individual records for a single model; Also works with a single record, as well as + * all its associated records. + * + * #### Options + * + * - validate: Set to false to disable validation, true to validate each record before saving, + * 'first' to validate *all* records before any are saved (default), + * or 'only' to only validate the records, but not save them. + * - atomic: If true (default), will attempt to save all records in a single transaction. + * Should be set to false if database/table does not support transactions. + * - fieldList: Equivalent to the $fieldList parameter in Model::save() + * + * @param array $data Record data to save. This can be either a numerically-indexed array (for saving multiple + * records of the same type), or an array indexed by association name. + * @param array $options Options to use when saving record data, See $options above. + * @return mixed If atomic: True on success, or false on failure. + * Otherwise: array similar to the $data array passed, but values are set to true/false + * depending on whether each record saved successfully. + * @access public + * @link http://book.cakephp.org/view/1032/Saving-Related-Model-Data-hasOne-hasMany-belongsTo + * @link http://book.cakephp.org/view/1031/Saving-Your-Data + */ + function saveAll($data = null, $options = array()) { + if (empty($data)) { + $data = $this->data; + } + $db =& ConnectionManager::getDataSource($this->useDbConfig); + + $options = array_merge(array('validate' => 'first', 'atomic' => true), $options); + $this->validationErrors = $validationErrors = array(); + $validates = true; + $return = array(); + + if (empty($data) && $options['validate'] !== false) { + $result = $this->save($data, $options); + return !empty($result); + } + + if ($options['atomic'] && $options['validate'] !== 'only') { + $transactionBegun = $db->begin($this); + } + + if (Set::numeric(array_keys($data))) { + while ($validates) { + $return = array(); + foreach ($data as $key => $record) { + if (!$currentValidates = $this->__save($record, $options)) { + $validationErrors[$key] = $this->validationErrors; + } + + if ($options['validate'] === 'only' || $options['validate'] === 'first') { + $validating = true; + if ($options['atomic']) { + $validates = $validates && $currentValidates; + } else { + $validates = $currentValidates; + } + } else { + $validating = false; + $validates = $currentValidates; + } + + if (!$options['atomic']) { + $return[] = $validates; + } elseif (!$validates && !$validating) { + break; + } + } + $this->validationErrors = $validationErrors; + + switch (true) { + case ($options['validate'] === 'only'): + return ($options['atomic'] ? $validates : $return); + break; + case ($options['validate'] === 'first'): + $options['validate'] = true; + break; + default: + if ($options['atomic']) { + if ($validates) { + if ($transactionBegun) { + return $db->commit($this) !== false; + } else { + return true; + } + } + $db->rollback($this); + return false; + } + return $return; + break; + } + } + if ($options['atomic'] && !$validates) { + $db->rollback($this); + return false; + } + return $return; + } + $associations = $this->getAssociated(); + + while ($validates) { + foreach ($data as $association => $values) { + if (isset($associations[$association])) { + switch ($associations[$association]) { + case 'belongsTo': + if ($this->{$association}->__save($values, $options)) { + $data[$this->alias][$this->belongsTo[$association]['foreignKey']] = $this->{$association}->id; + } else { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + if (!$options['atomic']) { + $return[$association][] = $validates; + } + break; + } + } + } + + if (!$this->__save($data, $options)) { + $validationErrors[$this->alias] = $this->validationErrors; + $validates = false; + } + if (!$options['atomic']) { + $return[$this->alias] = $validates; + } + $validating = ($options['validate'] === 'only' || $options['validate'] === 'first'); + + foreach ($data as $association => $values) { + if (!$validates && !$validating) { + break; + } + if (isset($associations[$association])) { + $type = $associations[$association]; + switch ($type) { + case 'hasOne': + $values[$this->{$type}[$association]['foreignKey']] = $this->id; + if (!$this->{$association}->__save($values, $options)) { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + if (!$options['atomic']) { + $return[$association][] = $validates; + } + break; + case 'hasMany': + foreach ($values as $i => $value) { + $values[$i][$this->{$type}[$association]['foreignKey']] = $this->id; + } + $_options = array_merge($options, array('atomic' => false)); + + if ($_options['validate'] === 'first') { + $_options['validate'] = 'only'; + } + $_return = $this->{$association}->saveAll($values, $_options); + + if ($_return === false || (is_array($_return) && in_array(false, $_return, true))) { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + if (is_array($_return)) { + foreach ($_return as $val) { + if (!isset($return[$association])) { + $return[$association] = array(); + } elseif (!is_array($return[$association])) { + $return[$association] = array($return[$association]); + } + $return[$association][] = $val; + } + } else { + $return[$association] = $_return; + } + break; + } + } + } + $this->validationErrors = $validationErrors; + + if (isset($validationErrors[$this->alias])) { + $this->validationErrors = $validationErrors[$this->alias]; + } + + switch (true) { + case ($options['validate'] === 'only'): + return ($options['atomic'] ? $validates : $return); + break; + case ($options['validate'] === 'first'): + $options['validate'] = true; + $return = array(); + break; + default: + if ($options['atomic']) { + if ($validates) { + if ($transactionBegun) { + return $db->commit($this) !== false; + } else { + return true; + } + } else { + $db->rollback($this); + } + } + return $return; + break; + } + if ($options['atomic'] && !$validates) { + $db->rollback($this); + return false; + } + } + } + +/** + * Private helper method used by saveAll. + * + * @return boolean Success + * @access private + * @see Model::saveAll() + */ + function __save($data, $options) { + if ($options['validate'] === 'first' || $options['validate'] === 'only') { + if (!($this->create($data) && $this->validates($options))) { + return false; + } + } elseif (!($this->create(null) !== null && $this->save($data, $options))) { + return false; + } + return true; + } + +/** + * Updates multiple model records based on a set of conditions. + * + * @param array $fields Set of fields and values, indexed by fields. + * Fields are treated as SQL snippets, to insert literal values manually escape your data. + * @param mixed $conditions Conditions to match, true for all records + * @return boolean True on success, false on failure + * @access public + * @link http://book.cakephp.org/view/1031/Saving-Your-Data + */ + function updateAll($fields, $conditions = true) { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + return $db->update($this, $fields, null, $conditions); + } + +/** + * Removes record for given ID. If no ID is given, the current ID is used. Returns true on success. + * + * @param mixed $id ID of record to delete + * @param boolean $cascade Set to true to delete records that depend on this record + * @return boolean True on success + * @access public + * @link http://book.cakephp.org/view/1036/delete + */ + function delete($id = null, $cascade = true) { + if (!empty($id)) { + $this->id = $id; + } + $id = $this->id; + + if ($this->beforeDelete($cascade)) { + $filters = $this->Behaviors->trigger($this, 'beforeDelete', array($cascade), array( + 'break' => true, 'breakOn' => false + )); + if (!$filters || !$this->exists()) { + return false; + } + $db =& ConnectionManager::getDataSource($this->useDbConfig); + + $this->_deleteDependent($id, $cascade); + $this->_deleteLinks($id); + $this->id = $id; + + if (!empty($this->belongsTo)) { + $keys = $this->find('first', array( + 'fields' => $this->__collectForeignKeys(), + 'conditions' => array($this->alias . '.' . $this->primaryKey => $id) + )); + } + + if ($db->delete($this, array($this->alias . '.' . $this->primaryKey => $id))) { + if (!empty($this->belongsTo)) { + $this->updateCounterCache($keys[$this->alias]); + } + $this->Behaviors->trigger($this, 'afterDelete'); + $this->afterDelete(); + $this->_clearCache(); + $this->id = false; + return true; + } + } + return false; + } + +/** + * Cascades model deletes through associated hasMany and hasOne child records. + * + * @param string $id ID of record that was deleted + * @param boolean $cascade Set to true to delete records that depend on this record + * @return void + * @access protected + */ + function _deleteDependent($id, $cascade) { + if (!empty($this->__backAssociation)) { + $savedAssociatons = $this->__backAssociation; + $this->__backAssociation = array(); + } + foreach (array_merge($this->hasMany, $this->hasOne) as $assoc => $data) { + if ($data['dependent'] === true && $cascade === true) { + + $model =& $this->{$assoc}; + $conditions = array($model->escapeField($data['foreignKey']) => $id); + if ($data['conditions']) { + $conditions = array_merge((array)$data['conditions'], $conditions); + } + $model->recursive = -1; + + if (isset($data['exclusive']) && $data['exclusive']) { + $model->deleteAll($conditions); + } else { + $records = $model->find('all', array( + 'conditions' => $conditions, 'fields' => $model->primaryKey + )); + + if (!empty($records)) { + foreach ($records as $record) { + $model->delete($record[$model->alias][$model->primaryKey]); + } + } + } + } + } + if (isset($savedAssociatons)) { + $this->__backAssociation = $savedAssociatons; + } + } + +/** + * Cascades model deletes through HABTM join keys. + * + * @param string $id ID of record that was deleted + * @return void + * @access protected + */ + function _deleteLinks($id) { + foreach ($this->hasAndBelongsToMany as $assoc => $data) { + $joinModel = $data['with']; + $records = $this->{$joinModel}->find('all', array( + 'conditions' => array_merge(array($this->{$joinModel}->escapeField($data['foreignKey']) => $id)), + 'fields' => $this->{$joinModel}->primaryKey, + 'recursive' => -1 + )); + if (!empty($records)) { + foreach ($records as $record) { + $this->{$joinModel}->delete($record[$this->{$joinModel}->alias][$this->{$joinModel}->primaryKey]); + } + } + } + } + +/** + * Deletes multiple model records based on a set of conditions. + * + * @param mixed $conditions Conditions to match + * @param boolean $cascade Set to true to delete records that depend on this record + * @param boolean $callbacks Run callbacks + * @return boolean True on success, false on failure + * @access public + * @link http://book.cakephp.org/view/1038/deleteAll + */ + function deleteAll($conditions, $cascade = true, $callbacks = false) { + if (empty($conditions)) { + return false; + } + $db =& ConnectionManager::getDataSource($this->useDbConfig); + + if (!$cascade && !$callbacks) { + return $db->delete($this, $conditions); + } else { + $ids = $this->find('all', array_merge(array( + 'fields' => "{$this->alias}.{$this->primaryKey}", + 'recursive' => 0), compact('conditions')) + ); + if ($ids === false) { + return false; + } + + $ids = Set::extract($ids, "{n}.{$this->alias}.{$this->primaryKey}"); + if (empty($ids)) { + return true; + } + + if ($callbacks) { + $_id = $this->id; + $result = true; + foreach ($ids as $id) { + $result = ($result && $this->delete($id, $cascade)); + } + $this->id = $_id; + return $result; + } else { + foreach ($ids as $id) { + $this->_deleteLinks($id); + if ($cascade) { + $this->_deleteDependent($id, $cascade); + } + } + return $db->delete($this, array($this->alias . '.' . $this->primaryKey => $ids)); + } + } + } + +/** + * Collects foreign keys from associations. + * + * @return array + * @access private + */ + function __collectForeignKeys($type = 'belongsTo') { + $result = array(); + + foreach ($this->{$type} as $assoc => $data) { + if (isset($data['foreignKey']) && is_string($data['foreignKey'])) { + $result[$assoc] = $data['foreignKey']; + } + } + return $result; + } + +/** + * Returns true if a record with the currently set ID exists. + * + * Internally calls Model::getID() to obtain the current record ID to verify, + * and then performs a Model::find('count') on the currently configured datasource + * to ascertain the existence of the record in persistent storage. + * + * @return boolean True if such a record exists + * @access public + */ + function exists() { + if ($this->getID() === false) { + return false; + } + $conditions = array($this->alias . '.' . $this->primaryKey => $this->getID()); + $query = array('conditions' => $conditions, 'recursive' => -1, 'callbacks' => false); + return ($this->find('count', $query) > 0); + } + +/** + * Returns true if a record that meets given conditions exists. + * + * @param array $conditions SQL conditions array + * @return boolean True if such a record exists + * @access public + */ + function hasAny($conditions = null) { + return ($this->find('count', array('conditions' => $conditions, 'recursive' => -1)) != false); + } + +/** + * Queries the datasource and returns a result set array. + * + * Also used to perform new-notation finds, where the first argument is type of find operation to perform + * (all / first / count / neighbors / list / threaded ), + * second parameter options for finding ( indexed array, including: 'conditions', 'limit', + * 'recursive', 'page', 'fields', 'offset', 'order') + * + * Eg: + * {{{ + * find('all', array( + * 'conditions' => array('name' => 'Thomas Anderson'), + * 'fields' => array('name', 'email'), + * 'order' => 'field3 DESC', + * 'recursive' => 2, + * 'group' => 'type' + * )); + * }}} + * + * In addition to the standard query keys above, you can provide Datasource, and behavior specific + * keys. For example, when using a SQL based datasource you can use the joins key to specify additional + * joins that should be part of the query. + * + * {{{ + * find('all', array( + * 'conditions' => array('name' => 'Thomas Anderson'), + * 'joins' => array( + * array( + * 'alias' => 'Thought', + * 'table' => 'thoughts', + * 'type' => 'LEFT', + * 'conditions' => '`Thought`.`person_id` = `Person`.`id`' + * ) + * ) + * )); + * }}} + * + * Behaviors and find types can also define custom finder keys which are passed into find(). + * + * Specifying 'fields' for new-notation 'list': + * + * - If no fields are specified, then 'id' is used for key and 'model->displayField' is used for value. + * - If a single field is specified, 'id' is used for key and specified field is used for value. + * - If three fields are specified, they are used (in order) for key, value and group. + * - Otherwise, first and second fields are used for key and value. + * + * @param array $conditions SQL conditions array, or type of find operation (all / first / count / + * neighbors / list / threaded) + * @param mixed $fields Either a single string of a field name, or an array of field names, or + * options for matching + * @param string $order SQL ORDER BY conditions (e.g. "price DESC" or "name ASC") + * @param integer $recursive The number of levels deep to fetch associated records + * @return array Array of records + * @access public + * @link http://book.cakephp.org/view/1018/find + */ + function find($conditions = null, $fields = array(), $order = null, $recursive = null) { + if (!is_string($conditions) || (is_string($conditions) && !array_key_exists($conditions, $this->_findMethods))) { + $type = 'first'; + $query = array_merge(compact('conditions', 'fields', 'order', 'recursive'), array('limit' => 1)); + } else { + list($type, $query) = array($conditions, $fields); + } + + $this->findQueryType = $type; + $this->id = $this->getID(); + + $query = array_merge( + array( + 'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null, + 'offset' => null, 'order' => null, 'page' => null, 'group' => null, 'callbacks' => true + ), + (array)$query + ); + + if ($type != 'all') { + if ($this->_findMethods[$type] === true) { + $query = $this->{'_find' . ucfirst($type)}('before', $query); + } + } + if (!is_numeric($query['page']) || intval($query['page']) < 1) { + $query['page'] = 1; + } + if ($query['page'] > 1 && !empty($query['limit'])) { + $query['offset'] = ($query['page'] - 1) * $query['limit']; + } + if ($query['order'] === null && $this->order !== null) { + $query['order'] = $this->order; + } + $query['order'] = array($query['order']); + + if ($query['callbacks'] === true || $query['callbacks'] === 'before') { + $return = $this->Behaviors->trigger($this, 'beforeFind', array($query), array( + 'break' => true, 'breakOn' => false, 'modParams' => true + )); + $query = (is_array($return)) ? $return : $query; + + if ($return === false) { + return null; + } + + $return = $this->beforeFind($query); + $query = (is_array($return)) ? $return : $query; + + if ($return === false) { + return null; + } + } + + if (!$db =& ConnectionManager::getDataSource($this->useDbConfig)) { + return false; + } + $results = $db->read($this, $query); + $this->resetAssociations(); + + if ($query['callbacks'] === true || $query['callbacks'] === 'after') { + $results = $this->__filterResults($results); + } + + $this->findQueryType = null; + if ($type === 'all') { + return $results; + } else { + if ($this->_findMethods[$type] === true) { + return $this->{'_find' . ucfirst($type)}('after', $query, $results); + } + } + } + +/** + * Handles the before/after filter logic for find('first') operations. Only called by Model::find(). + * + * @param string $state Either "before" or "after" + * @param array $query + * @param array $data + * @return array + * @access protected + * @see Model::find() + */ + function _findFirst($state, $query, $results = array()) { + if ($state == 'before') { + $query['limit'] = 1; + return $query; + } elseif ($state == 'after') { + if (empty($results[0])) { + return false; + } + return $results[0]; + } + } + +/** + * Handles the before/after filter logic for find('count') operations. Only called by Model::find(). + * + * @param string $state Either "before" or "after" + * @param array $query + * @param array $data + * @return int The number of records found, or false + * @access protected + * @see Model::find() + */ + function _findCount($state, $query, $results = array()) { + if ($state == 'before') { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + if (empty($query['fields'])) { + $query['fields'] = $db->calculate($this, 'count'); + } elseif (is_string($query['fields']) && !preg_match('/count/i', $query['fields'])) { + $query['fields'] = $db->calculate($this, 'count', array( + $db->expression($query['fields']), 'count' + )); + } + $query['order'] = false; + return $query; + } elseif ($state == 'after') { + if (isset($results[0][0]['count'])) { + return intval($results[0][0]['count']); + } elseif (isset($results[0][$this->alias]['count'])) { + return intval($results[0][$this->alias]['count']); + } + return false; + } + } + +/** + * Handles the before/after filter logic for find('list') operations. Only called by Model::find(). + * + * @param string $state Either "before" or "after" + * @param array $query + * @param array $data + * @return array Key/value pairs of primary keys/display field values of all records found + * @access protected + * @see Model::find() + */ + function _findList($state, $query, $results = array()) { + if ($state == 'before') { + if (empty($query['fields'])) { + $query['fields'] = array("{$this->alias}.{$this->primaryKey}", "{$this->alias}.{$this->displayField}"); + $list = array("{n}.{$this->alias}.{$this->primaryKey}", "{n}.{$this->alias}.{$this->displayField}", null); + } else { + if (!is_array($query['fields'])) { + $query['fields'] = String::tokenize($query['fields']); + } + + if (count($query['fields']) == 1) { + if (strpos($query['fields'][0], '.') === false) { + $query['fields'][0] = $this->alias . '.' . $query['fields'][0]; + } + + $list = array("{n}.{$this->alias}.{$this->primaryKey}", '{n}.' . $query['fields'][0], null); + $query['fields'] = array("{$this->alias}.{$this->primaryKey}", $query['fields'][0]); + } elseif (count($query['fields']) == 3) { + for ($i = 0; $i < 3; $i++) { + if (strpos($query['fields'][$i], '.') === false) { + $query['fields'][$i] = $this->alias . '.' . $query['fields'][$i]; + } + } + + $list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], '{n}.' . $query['fields'][2]); + } else { + for ($i = 0; $i < 2; $i++) { + if (strpos($query['fields'][$i], '.') === false) { + $query['fields'][$i] = $this->alias . '.' . $query['fields'][$i]; + } + } + + $list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], null); + } + } + if (!isset($query['recursive']) || $query['recursive'] === null) { + $query['recursive'] = -1; + } + list($query['list']['keyPath'], $query['list']['valuePath'], $query['list']['groupPath']) = $list; + return $query; + } elseif ($state == 'after') { + if (empty($results)) { + return array(); + } + $lst = $query['list']; + return Set::combine($results, $lst['keyPath'], $lst['valuePath'], $lst['groupPath']); + } + } + +/** + * Detects the previous field's value, then uses logic to find the 'wrapping' + * rows and return them. + * + * @param string $state Either "before" or "after" + * @param mixed $query + * @param array $results + * @return array + * @access protected + */ + function _findNeighbors($state, $query, $results = array()) { + if ($state == 'before') { + $query = array_merge(array('recursive' => 0), $query); + extract($query); + $conditions = (array)$conditions; + if (isset($field) && isset($value)) { + if (strpos($field, '.') === false) { + $field = $this->alias . '.' . $field; + } + } else { + $field = $this->alias . '.' . $this->primaryKey; + $value = $this->id; + } + $query['conditions'] = array_merge($conditions, array($field . ' <' => $value)); + $query['order'] = $field . ' DESC'; + $query['limit'] = 1; + $query['field'] = $field; + $query['value'] = $value; + return $query; + } elseif ($state == 'after') { + extract($query); + unset($query['conditions'][$field . ' <']); + $return = array(); + if (isset($results[0])) { + $prevVal = Set::extract('/' . str_replace('.', '/', $field), $results[0]); + $query['conditions'][$field . ' >='] = $prevVal[0]; + $query['conditions'][$field . ' !='] = $value; + $query['limit'] = 2; + } else { + $return['prev'] = null; + $query['conditions'][$field . ' >'] = $value; + $query['limit'] = 1; + } + $query['order'] = $field . ' ASC'; + $return2 = $this->find('all', $query); + if (!array_key_exists('prev', $return)) { + $return['prev'] = $return2[0]; + } + if (count($return2) == 2) { + $return['next'] = $return2[1]; + } elseif (count($return2) == 1 && !$return['prev']) { + $return['next'] = $return2[0]; + } else { + $return['next'] = null; + } + return $return; + } + } + +/** + * In the event of ambiguous results returned (multiple top level results, with different parent_ids) + * top level results with different parent_ids to the first result will be dropped + * + * @param mixed $state + * @param mixed $query + * @param array $results + * @return array Threaded results + * @access protected + */ + function _findThreaded($state, $query, $results = array()) { + if ($state == 'before') { + return $query; + } elseif ($state == 'after') { + $return = $idMap = array(); + $ids = Set::extract($results, '{n}.' . $this->alias . '.' . $this->primaryKey); + + foreach ($results as $result) { + $result['children'] = array(); + $id = $result[$this->alias][$this->primaryKey]; + $parentId = $result[$this->alias]['parent_id']; + if (isset($idMap[$id]['children'])) { + $idMap[$id] = array_merge($result, (array)$idMap[$id]); + } else { + $idMap[$id] = array_merge($result, array('children' => array())); + } + if (!$parentId || !in_array($parentId, $ids)) { + $return[] =& $idMap[$id]; + } else { + $idMap[$parentId]['children'][] =& $idMap[$id]; + } + } + if (count($return) > 1) { + $ids = array_unique(Set::extract('/' . $this->alias . '/parent_id', $return)); + if (count($ids) > 1) { + $root = $return[0][$this->alias]['parent_id']; + foreach ($return as $key => $value) { + if ($value[$this->alias]['parent_id'] != $root) { + unset($return[$key]); + } + } + } + } + return $return; + } + } + +/** + * Passes query results through model and behavior afterFilter() methods. + * + * @param array Results to filter + * @param boolean $primary If this is the primary model results (results from model where the find operation was performed) + * @return array Set of filtered results + * @access private + */ + function __filterResults($results, $primary = true) { + $return = $this->Behaviors->trigger($this, 'afterFind', array($results, $primary), array('modParams' => true)); + if ($return !== true) { + $results = $return; + } + return $this->afterFind($results, $primary); + } + +/** + * This resets the association arrays for the model back + * to those originally defined in the model. Normally called at the end + * of each call to Model::find() + * + * @return boolean Success + * @access public + */ + function resetAssociations() { + if (!empty($this->__backAssociation)) { + foreach ($this->__associations as $type) { + if (isset($this->__backAssociation[$type])) { + $this->{$type} = $this->__backAssociation[$type]; + } + } + $this->__backAssociation = array(); + } + + foreach ($this->__associations as $type) { + foreach ($this->{$type} as $key => $name) { + if (!empty($this->{$key}->__backAssociation)) { + $this->{$key}->resetAssociations(); + } + } + } + $this->__backAssociation = array(); + return true; + } + +/** + * Returns false if any fields passed match any (by default, all if $or = false) of their matching values. + * + * @param array $fields Field/value pairs to search (if no values specified, they are pulled from $this->data) + * @param boolean $or If false, all fields specified must match in order for a false return value + * @return boolean False if any records matching any fields are found + * @access public + */ + function isUnique($fields, $or = true) { + if (!is_array($fields)) { + $fields = func_get_args(); + if (is_bool($fields[count($fields) - 1])) { + $or = $fields[count($fields) - 1]; + unset($fields[count($fields) - 1]); + } + } + + foreach ($fields as $field => $value) { + if (is_numeric($field)) { + unset($fields[$field]); + + $field = $value; + if (isset($this->data[$this->alias][$field])) { + $value = $this->data[$this->alias][$field]; + } else { + $value = null; + } + } + + if (strpos($field, '.') === false) { + unset($fields[$field]); + $fields[$this->alias . '.' . $field] = $value; + } + } + if ($or) { + $fields = array('or' => $fields); + } + if (!empty($this->id)) { + $fields[$this->alias . '.' . $this->primaryKey . ' !='] = $this->id; + } + return ($this->find('count', array('conditions' => $fields, 'recursive' => -1)) == 0); + } + +/** + * Returns a resultset for a given SQL statement. Custom SQL queries should be performed with this method. + * + * @param string $sql SQL statement + * @return array Resultset + * @access public + * @link http://book.cakephp.org/view/1027/query + */ + function query() { + $params = func_get_args(); + $db =& ConnectionManager::getDataSource($this->useDbConfig); + return call_user_func_array(array(&$db, 'query'), $params); + } + +/** + * Returns true if all fields pass validation. Will validate hasAndBelongsToMany associations + * that use the 'with' key as well. Since __saveMulti is incapable of exiting a save operation. + * + * Will validate the currently set data. Use Model::set() or Model::create() to set the active data. + * + * @param string $options An optional array of custom options to be made available in the beforeValidate callback + * @return boolean True if there are no errors + * @access public + * @link http://book.cakephp.org/view/1182/Validating-Data-from-the-Controller + */ + function validates($options = array()) { + $errors = $this->invalidFields($options); + if (empty($errors) && $errors !== false) { + $errors = $this->__validateWithModels($options); + } + if (is_array($errors)) { + return count($errors) === 0; + } + return $errors; + } + +/** + * Returns an array of fields that have failed validation. On the current model. + * + * @param string $options An optional array of custom options to be made available in the beforeValidate callback + * @return array Array of invalid fields + * @see Model::validates() + * @access public + * @link http://book.cakephp.org/view/1182/Validating-Data-from-the-Controller + */ + function invalidFields($options = array()) { + if ( + !$this->Behaviors->trigger( + $this, + 'beforeValidate', + array($options), + array('break' => true, 'breakOn' => false) + ) || + $this->beforeValidate($options) === false + ) { + return false; + } + + if (!isset($this->validate) || empty($this->validate)) { + return $this->validationErrors; + } + + $data = $this->data; + $methods = array_map('strtolower', get_class_methods($this)); + $behaviorMethods = array_keys($this->Behaviors->methods()); + + if (isset($data[$this->alias])) { + $data = $data[$this->alias]; + } elseif (!is_array($data)) { + $data = array(); + } + + $Validation =& Validation::getInstance(); + $exists = $this->exists(); + + $_validate = $this->validate; + $whitelist = $this->whitelist; + + if (!empty($options['fieldList'])) { + $whitelist = $options['fieldList']; + } + + if (!empty($whitelist)) { + $validate = array(); + foreach ((array)$whitelist as $f) { + if (!empty($this->validate[$f])) { + $validate[$f] = $this->validate[$f]; + } + } + $this->validate = $validate; + } + + foreach ($this->validate as $fieldName => $ruleSet) { + if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) { + $ruleSet = array($ruleSet); + } + $default = array( + 'allowEmpty' => null, + 'required' => null, + 'rule' => 'blank', + 'last' => false, + 'on' => null + ); + + foreach ($ruleSet as $index => $validator) { + if (!is_array($validator)) { + $validator = array('rule' => $validator); + } + $validator = array_merge($default, $validator); + + if (isset($validator['message'])) { + $message = $validator['message']; + } else { + $message = __('This field cannot be left blank', true); + } + + if ( + empty($validator['on']) || ($validator['on'] == 'create' && + !$exists) || ($validator['on'] == 'update' && $exists + )) { + $required = ( + (!isset($data[$fieldName]) && $validator['required'] === true) || + ( + isset($data[$fieldName]) && (empty($data[$fieldName]) && + !is_numeric($data[$fieldName])) && $validator['allowEmpty'] === false + ) + ); + + if ($required) { + $this->invalidate($fieldName, $message); + if ($validator['last']) { + break; + } + } elseif (array_key_exists($fieldName, $data)) { + if (empty($data[$fieldName]) && $data[$fieldName] != '0' && $validator['allowEmpty'] === true) { + break; + } + if (is_array($validator['rule'])) { + $rule = $validator['rule'][0]; + unset($validator['rule'][0]); + $ruleParams = array_merge(array($data[$fieldName]), array_values($validator['rule'])); + } else { + $rule = $validator['rule']; + $ruleParams = array($data[$fieldName]); + } + + $valid = true; + + if (in_array(strtolower($rule), $methods)) { + $ruleParams[] = $validator; + $ruleParams[0] = array($fieldName => $ruleParams[0]); + $valid = $this->dispatchMethod($rule, $ruleParams); + } elseif (in_array($rule, $behaviorMethods) || in_array(strtolower($rule), $behaviorMethods)) { + $ruleParams[] = $validator; + $ruleParams[0] = array($fieldName => $ruleParams[0]); + $valid = $this->Behaviors->dispatchMethod($this, $rule, $ruleParams); + } elseif (method_exists($Validation, $rule)) { + $valid = $Validation->dispatchMethod($rule, $ruleParams); + } elseif (!is_array($validator['rule'])) { + $valid = preg_match($rule, $data[$fieldName]); + } elseif (Configure::read('debug') > 0) { + trigger_error(sprintf(__('Could not find validation handler %s for %s', true), $rule, $fieldName), E_USER_WARNING); + } + + if (!$valid || (is_string($valid) && strlen($valid) > 0)) { + if (is_string($valid) && strlen($valid) > 0) { + $validator['message'] = $valid; + } elseif (!isset($validator['message'])) { + if (is_string($index)) { + $validator['message'] = $index; + } elseif (is_numeric($index) && count($ruleSet) > 1) { + $validator['message'] = $index + 1; + } else { + $validator['message'] = $message; + } + } + $this->invalidate($fieldName, $validator['message']); + + if ($validator['last']) { + break; + } + } + } + } + } + } + $this->validate = $_validate; + return $this->validationErrors; + } + +/** + * Runs validation for hasAndBelongsToMany associations that have 'with' keys + * set. And data in the set() data set. + * + * @param array $options Array of options to use on Valdation of with models + * @return boolean Failure of validation on with models. + * @access private + * @see Model::validates() + */ + function __validateWithModels($options) { + $valid = true; + foreach ($this->hasAndBelongsToMany as $assoc => $association) { + if (empty($association['with']) || !isset($this->data[$assoc])) { + continue; + } + list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']); + $data = $this->data[$assoc]; + + $newData = array(); + foreach ((array)$data as $row) { + if (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) { + $newData[] = $row; + } elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) { + $newData[] = $row[$join]; + } + } + if (empty($newData)) { + continue; + } + foreach ($newData as $data) { + $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $this->id; + $this->{$join}->create($data); + $valid = ($valid && $this->{$join}->validates($options)); + } + } + return $valid; + } +/** + * Marks a field as invalid, optionally setting the name of validation + * rule (in case of multiple validation for field) that was broken. + * + * @param string $field The name of the field to invalidate + * @param mixed $value Name of validation rule that was not failed, or validation message to + * be returned. If no validation key is provided, defaults to true. + * @access public + */ + function invalidate($field, $value = true) { + if (!is_array($this->validationErrors)) { + $this->validationErrors = array(); + } + $this->validationErrors[$field] = $value; + } + +/** + * Returns true if given field name is a foreign key in this model. + * + * @param string $field Returns true if the input string ends in "_id" + * @return boolean True if the field is a foreign key listed in the belongsTo array. + * @access public + */ + function isForeignKey($field) { + $foreignKeys = array(); + if (!empty($this->belongsTo)) { + foreach ($this->belongsTo as $assoc => $data) { + $foreignKeys[] = $data['foreignKey']; + } + } + return in_array($field, $foreignKeys); + } + +/** + * Escapes the field name and prepends the model name. Escaping is done according to the + * current database driver's rules. + * + * @param string $field Field to escape (e.g: id) + * @param string $alias Alias for the model (e.g: Post) + * @return string The name of the escaped field for this Model (i.e. id becomes `Post`.`id`). + * @access public + */ + function escapeField($field = null, $alias = null) { + if (empty($alias)) { + $alias = $this->alias; + } + if (empty($field)) { + $field = $this->primaryKey; + } + $db =& ConnectionManager::getDataSource($this->useDbConfig); + if (strpos($field, $db->name($alias) . '.') === 0) { + return $field; + } + return $db->name($alias . '.' . $field); + } + +/** + * Returns the current record's ID + * + * @param integer $list Index on which the composed ID is located + * @return mixed The ID of the current record, false if no ID + * @access public + */ + function getID($list = 0) { + if (empty($this->id) || (is_array($this->id) && isset($this->id[0]) && empty($this->id[0]))) { + return false; + } + + if (!is_array($this->id)) { + return $this->id; + } + + if (empty($this->id)) { + return false; + } + + if (isset($this->id[$list]) && !empty($this->id[$list])) { + return $this->id[$list]; + } elseif (isset($this->id[$list])) { + return false; + } + + foreach ($this->id as $id) { + return $id; + } + + return false; + } + +/** + * Returns the ID of the last record this model inserted. + * + * @return mixed Last inserted ID + * @access public + */ + function getLastInsertID() { + return $this->getInsertID(); + } + +/** + * Returns the ID of the last record this model inserted. + * + * @return mixed Last inserted ID + * @access public + */ + function getInsertID() { + return $this->__insertID; + } + +/** + * Sets the ID of the last record this model inserted + * + * @param mixed Last inserted ID + * @access public + */ + function setInsertID($id) { + $this->__insertID = $id; + } + +/** + * Returns the number of rows returned from the last query. + * + * @return int Number of rows + * @access public + */ + function getNumRows() { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + return $db->lastNumRows(); + } + +/** + * Returns the number of rows affected by the last query. + * + * @return int Number of rows + * @access public + */ + function getAffectedRows() { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + return $db->lastAffected(); + } + +/** + * Sets the DataSource to which this model is bound. + * + * @param string $dataSource The name of the DataSource, as defined in app/config/database.php + * @return boolean True on success + * @access public + */ + function setDataSource($dataSource = null) { + $oldConfig = $this->useDbConfig; + + if ($dataSource != null) { + $this->useDbConfig = $dataSource; + } + $db =& ConnectionManager::getDataSource($this->useDbConfig); + if (!empty($oldConfig) && isset($db->config['prefix'])) { + $oldDb =& ConnectionManager::getDataSource($oldConfig); + + if (!isset($this->tablePrefix) || (!isset($oldDb->config['prefix']) || $this->tablePrefix == $oldDb->config['prefix'])) { + $this->tablePrefix = $db->config['prefix']; + } + } elseif (isset($db->config['prefix'])) { + $this->tablePrefix = $db->config['prefix']; + } + + if (empty($db) || !is_object($db)) { + return $this->cakeError('missingConnection', array(array('code' => 500, 'className' => $this->alias))); + } + } + +/** + * Gets the DataSource to which this model is bound. + * Not safe for use with some versions of PHP4, because this class is overloaded. + * + * @return object A DataSource object + * @access public + */ + function &getDataSource() { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + return $db; + } + +/** + * Gets all the models with which this model is associated. + * + * @param string $type Only result associations of this type + * @return array Associations + * @access public + */ + function getAssociated($type = null) { + if ($type == null) { + $associated = array(); + foreach ($this->__associations as $assoc) { + if (!empty($this->{$assoc})) { + $models = array_keys($this->{$assoc}); + foreach ($models as $m) { + $associated[$m] = $assoc; + } + } + } + return $associated; + } elseif (in_array($type, $this->__associations)) { + if (empty($this->{$type})) { + return array(); + } + return array_keys($this->{$type}); + } else { + $assoc = array_merge( + $this->hasOne, + $this->hasMany, + $this->belongsTo, + $this->hasAndBelongsToMany + ); + if (array_key_exists($type, $assoc)) { + foreach ($this->__associations as $a) { + if (isset($this->{$a}[$type])) { + $assoc[$type]['association'] = $a; + break; + } + } + return $assoc[$type]; + } + return null; + } + } + +/** + * Gets the name and fields to be used by a join model. This allows specifying join fields + * in the association definition. + * + * @param object $model The model to be joined + * @param mixed $with The 'with' key of the model association + * @param array $keys Any join keys which must be merged with the keys queried + * @return array + * @access public + */ + function joinModel($assoc, $keys = array()) { + if (is_string($assoc)) { + return array($assoc, array_keys($this->{$assoc}->schema())); + } elseif (is_array($assoc)) { + $with = key($assoc); + return array($with, array_unique(array_merge($assoc[$with], $keys))); + } + trigger_error( + sprintf(__('Invalid join model settings in %s', true), $model->alias), + E_USER_WARNING + ); + } + +/** + * Called before each find operation. Return false if you want to halt the find + * call, otherwise return the (modified) query data. + * + * @param array $queryData Data used to execute this query, i.e. conditions, order, etc. + * @return mixed true if the operation should continue, false if it should abort; or, modified + * $queryData to continue with new $queryData + * @access public + * @link http://book.cakephp.org/view/1048/Callback-Methods#beforeFind-1049 + */ + function beforeFind($queryData) { + return true; + } + +/** + * Called after each find operation. Can be used to modify any results returned by find(). + * Return value should be the (modified) results. + * + * @param mixed $results The results of the find operation + * @param boolean $primary Whether this model is being queried directly (vs. being queried as an association) + * @return mixed Result of the find operation + * @access public + * @link http://book.cakephp.org/view/1048/Callback-Methods#afterFind-1050 + */ + function afterFind($results, $primary = false) { + return $results; + } + +/** + * Called before each save operation, after validation. Return a non-true result + * to halt the save. + * + * @return boolean True if the operation should continue, false if it should abort + * @access public + * @link http://book.cakephp.org/view/1048/Callback-Methods#beforeSave-1052 + */ + function beforeSave($options = array()) { + return true; + } + +/** + * Called after each successful save operation. + * + * @param boolean $created True if this save created a new record + * @access public + * @link http://book.cakephp.org/view/1048/Callback-Methods#afterSave-1053 + */ + function afterSave($created) { + } + +/** + * Called before every deletion operation. + * + * @param boolean $cascade If true records that depend on this record will also be deleted + * @return boolean True if the operation should continue, false if it should abort + * @access public + * @link http://book.cakephp.org/view/1048/Callback-Methods#beforeDelete-1054 + */ + function beforeDelete($cascade = true) { + return true; + } + +/** + * Called after every deletion operation. + * + * @access public + * @link http://book.cakephp.org/view/1048/Callback-Methods#afterDelete-1055 + */ + function afterDelete() { + } + +/** + * Called during validation operations, before validation. Please note that custom + * validation rules can be defined in $validate. + * + * @return boolean True if validate operation should continue, false to abort + * @param $options array Options passed from model::save(), see $options of model::save(). + * @access public + * @link http://book.cakephp.org/view/1048/Callback-Methods#beforeValidate-1051 + */ + function beforeValidate($options = array()) { + return true; + } + +/** + * Called when a DataSource-level error occurs. + * + * @access public + * @link http://book.cakephp.org/view/1048/Callback-Methods#onError-1056 + */ + function onError() { + } + +/** + * Private method. Clears cache for this model. + * + * @param string $type If null this deletes cached views if Cache.check is true + * Will be used to allow deleting query cache also + * @return boolean true on delete + * @access protected + * @todo + */ + function _clearCache($type = null) { + if ($type === null) { + if (Configure::read('Cache.check') === true) { + $assoc[] = strtolower(Inflector::pluralize($this->alias)); + $assoc[] = strtolower(Inflector::underscore(Inflector::pluralize($this->alias))); + foreach ($this->__associations as $key => $association) { + foreach ($this->$association as $key => $className) { + $check = strtolower(Inflector::pluralize($className['className'])); + if (!in_array($check, $assoc)) { + $assoc[] = strtolower(Inflector::pluralize($className['className'])); + $assoc[] = strtolower(Inflector::underscore(Inflector::pluralize($className['className']))); + } + } + } + clearCache($assoc); + return true; + } + } else { + //Will use for query cache deleting + } + } + +/** + * Called when serializing a model. + * + * @return array Set of object variable names this model has + * @access private + */ + function __sleep() { + $return = array_keys(get_object_vars($this)); + return $return; + } + +/** + * Called when de-serializing a model. + * + * @access private + * @todo + */ + function __wakeup() { + } +} +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + Overloadable::overload('Model'); +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/model/model_behavior.php b/code/ryzom/tools/server/www/webtt/cake/libs/model/model_behavior.php new file mode 100644 index 000000000..000b3186d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/model/model_behavior.php @@ -0,0 +1,533 @@ +settings[$model->alias])) { + unset($this->settings[$model->alias]); + } + } + +/** + * Before find callback + * + * @param object $model Model using this behavior + * @param array $queryData Data used to execute this query, i.e. conditions, order, etc. + * @return mixed False if the operation should abort. An array will replace the value of $query. + * @access public + */ + function beforeFind(&$model, $query) { } + +/** + * After find callback. Can be used to modify any results returned by find and findAll. + * + * @param object $model Model using this behavior + * @param mixed $results The results of the find operation + * @param boolean $primary Whether this model is being queried directly (vs. being queried as an association) + * @return mixed An array value will replace the value of $results - any other value will be ignored. + * @access public + */ + function afterFind(&$model, $results, $primary) { } + +/** + * Before validate callback + * + * @param object $model Model using this behavior + * @return mixed False if the operation should abort. Any other result will continue. + * @access public + */ + function beforeValidate(&$model) { } + +/** + * Before save callback + * + * @param object $model Model using this behavior + * @return mixed False if the operation should abort. Any other result will continue. + * @access public + */ + function beforeSave(&$model) { } + +/** + * After save callback + * + * @param object $model Model using this behavior + * @param boolean $created True if this save created a new record + * @access public + */ + function afterSave(&$model, $created) { } + +/** + * Before delete callback + * + * @param object $model Model using this behavior + * @param boolean $cascade If true records that depend on this record will also be deleted + * @return mixed False if the operation should abort. Any other result will continue. + * @access public + */ + function beforeDelete(&$model, $cascade = true) { } + +/** + * After delete callback + * + * @param object $model Model using this behavior + * @access public + */ + function afterDelete(&$model) { } + +/** + * DataSource error callback + * + * @param object $model Model using this behavior + * @param string $error Error generated in DataSource + * @access public + */ + function onError(&$model, $error) { } + +/** + * Overrides Object::dispatchMethod to account for PHP4's broken reference support + * + * @see Object::dispatchMethod + * @access public + * @return mixed + */ + function dispatchMethod(&$model, $method, $params = array()) { + if (empty($params)) { + return $this->{$method}($model); + } + $params = array_values($params); + + switch (count($params)) { + case 1: + return $this->{$method}($model, $params[0]); + case 2: + return $this->{$method}($model, $params[0], $params[1]); + case 3: + return $this->{$method}($model, $params[0], $params[1], $params[2]); + case 4: + return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3]); + case 5: + return $this->{$method}($model, $params[0], $params[1], $params[2], $params[3], $params[4]); + default: + $params = array_merge(array(&$model), $params); + return call_user_func_array(array(&$this, $method), $params); + break; + } + } + +/** + * If $model's whitelist property is non-empty, $field will be added to it. + * Note: this method should *only* be used in beforeValidate or beforeSave to ensure + * that it only modifies the whitelist for the current save operation. Also make sure + * you explicitly set the value of the field which you are allowing. + * + * @param object $model Model using this behavior + * @param string $field Field to be added to $model's whitelist + * @access protected + * @return void + */ + function _addToWhitelist(&$model, $field) { + if (is_array($field)) { + foreach ($field as $f) { + $this->_addToWhitelist($model, $f); + } + return; + } + if (!empty($model->whitelist) && !in_array($field, $model->whitelist)) { + $model->whitelist[] = $field; + } + } +} + +/** + * Model behavior collection class. + * + * Defines the Behavior interface, and contains common model interaction functionality. + * + * @package cake + * @subpackage cake.cake.libs.model + */ +class BehaviorCollection extends Object { + +/** + * Stores a reference to the attached name + * + * @var string + * @access public + */ + var $modelName = null; + +/** + * Lists the currently-attached behavior objects + * + * @var array + * @access private + */ + var $_attached = array(); + +/** + * Lists the currently-attached behavior objects which are disabled + * + * @var array + * @access private + */ + var $_disabled = array(); + +/** + * Keeps a list of all methods of attached behaviors + * + * @var array + */ + var $__methods = array(); + +/** + * Keeps a list of all methods which have been mapped with regular expressions + * + * @var array + */ + var $__mappedMethods = array(); + +/** + * Attaches a model object and loads a list of behaviors + * + * @access public + * @return void + */ + function init($modelName, $behaviors = array()) { + $this->modelName = $modelName; + + if (!empty($behaviors)) { + foreach (Set::normalize($behaviors) as $behavior => $config) { + $this->attach($behavior, $config); + } + } + } + +/** + * Attaches a behavior to a model + * + * @param string $behavior CamelCased name of the behavior to load + * @param array $config Behavior configuration parameters + * @return boolean True on success, false on failure + * @access public + */ + function attach($behavior, $config = array()) { + list($plugin, $name) = pluginSplit($behavior); + $class = $name . 'Behavior'; + + if (!App::import('Behavior', $behavior)) { + $this->cakeError('missingBehaviorFile', array(array( + 'behavior' => $behavior, + 'file' => Inflector::underscore($behavior) . '.php', + 'code' => 500, + 'base' => '/' + ))); + return false; + } + if (!class_exists($class)) { + $this->cakeError('missingBehaviorClass', array(array( + 'behavior' => $class, + 'file' => Inflector::underscore($class) . '.php', + 'code' => 500, + 'base' => '/' + ))); + return false; + } + + if (!isset($this->{$name})) { + if (ClassRegistry::isKeySet($class)) { + if (PHP5) { + $this->{$name} = ClassRegistry::getObject($class); + } else { + $this->{$name} =& ClassRegistry::getObject($class); + } + } else { + if (PHP5) { + $this->{$name} = new $class; + } else { + $this->{$name} =& new $class; + } + ClassRegistry::addObject($class, $this->{$name}); + if (!empty($plugin)) { + ClassRegistry::addObject($plugin.'.'.$class, $this->{$name}); + } + } + } elseif (isset($this->{$name}->settings) && isset($this->{$name}->settings[$this->modelName])) { + if ($config !== null && $config !== false) { + $config = array_merge($this->{$name}->settings[$this->modelName], $config); + } else { + $config = array(); + } + } + if (empty($config)) { + $config = array(); + } + $this->{$name}->setup(ClassRegistry::getObject($this->modelName), $config); + + foreach ($this->{$name}->mapMethods as $method => $alias) { + $this->__mappedMethods[$method] = array($alias, $name); + } + $methods = get_class_methods($this->{$name}); + $parentMethods = array_flip(get_class_methods('ModelBehavior')); + $callbacks = array( + 'setup', 'cleanup', 'beforeFind', 'afterFind', 'beforeSave', 'afterSave', + 'beforeDelete', 'afterDelete', 'afterError' + ); + + foreach ($methods as $m) { + if (!isset($parentMethods[$m])) { + $methodAllowed = ( + $m[0] != '_' && !array_key_exists($m, $this->__methods) && + !in_array($m, $callbacks) + ); + if ($methodAllowed) { + $this->__methods[$m] = array($m, $name); + } + } + } + + if (!in_array($name, $this->_attached)) { + $this->_attached[] = $name; + } + if (in_array($name, $this->_disabled) && !(isset($config['enabled']) && $config['enabled'] === false)) { + $this->enable($name); + } elseif (isset($config['enabled']) && $config['enabled'] === false) { + $this->disable($name); + } + return true; + } + +/** + * Detaches a behavior from a model + * + * @param string $name CamelCased name of the behavior to unload + * @return void + * @access public + */ + function detach($name) { + list($plugin, $name) = pluginSplit($name); + if (isset($this->{$name})) { + $this->{$name}->cleanup(ClassRegistry::getObject($this->modelName)); + unset($this->{$name}); + } + foreach ($this->__methods as $m => $callback) { + if (is_array($callback) && $callback[1] == $name) { + unset($this->__methods[$m]); + } + } + $this->_attached = array_values(array_diff($this->_attached, (array)$name)); + } + +/** + * Enables callbacks on a behavior or array of behaviors + * + * @param mixed $name CamelCased name of the behavior(s) to enable (string or array) + * @return void + * @access public + */ + function enable($name) { + $this->_disabled = array_diff($this->_disabled, (array)$name); + } + +/** + * Disables callbacks on a behavior or array of behaviors. Public behavior methods are still + * callable as normal. + * + * @param mixed $name CamelCased name of the behavior(s) to disable (string or array) + * @return void + * @access public + */ + function disable($name) { + foreach ((array)$name as $behavior) { + if (in_array($behavior, $this->_attached) && !in_array($behavior, $this->_disabled)) { + $this->_disabled[] = $behavior; + } + } + } + +/** + * Gets the list of currently-enabled behaviors, or, the current status of a single behavior + * + * @param string $name Optional. The name of the behavior to check the status of. If omitted, + * returns an array of currently-enabled behaviors + * @return mixed If $name is specified, returns the boolean status of the corresponding behavior. + * Otherwise, returns an array of all enabled behaviors. + * @access public + */ + function enabled($name = null) { + if (!empty($name)) { + return (in_array($name, $this->_attached) && !in_array($name, $this->_disabled)); + } + return array_diff($this->_attached, $this->_disabled); + } + +/** + * Dispatches a behavior method + * + * @return array All methods for all behaviors attached to this object + * @access public + */ + function dispatchMethod(&$model, $method, $params = array(), $strict = false) { + $methods = array_keys($this->__methods); + foreach ($methods as $key => $value) { + $methods[$key] = strtolower($value); + } + $method = strtolower($method); + $check = array_flip($methods); + $found = isset($check[$method]); + $call = null; + + if ($strict && !$found) { + trigger_error(sprintf(__("BehaviorCollection::dispatchMethod() - Method %s not found in any attached behavior", true), $method), E_USER_WARNING); + return null; + } elseif ($found) { + $methods = array_combine($methods, array_values($this->__methods)); + $call = $methods[$method]; + } else { + $count = count($this->__mappedMethods); + $mapped = array_keys($this->__mappedMethods); + + for ($i = 0; $i < $count; $i++) { + if (preg_match($mapped[$i] . 'i', $method)) { + $call = $this->__mappedMethods[$mapped[$i]]; + array_unshift($params, $method); + break; + } + } + } + + if (!empty($call)) { + return $this->{$call[1]}->dispatchMethod($model, $call[0], $params); + } + return array('unhandled'); + } + +/** + * Dispatches a behavior callback on all attached behavior objects + * + * @param model $model + * @param string $callback + * @param array $params + * @param array $options + * @return mixed + * @access public + */ + function trigger(&$model, $callback, $params = array(), $options = array()) { + if (empty($this->_attached)) { + return true; + } + $options = array_merge(array('break' => false, 'breakOn' => array(null, false), 'modParams' => false), $options); + $count = count($this->_attached); + + for ($i = 0; $i < $count; $i++) { + $name = $this->_attached[$i]; + if (in_array($name, $this->_disabled)) { + continue; + } + $result = $this->{$name}->dispatchMethod($model, $callback, $params); + + if ($options['break'] && ($result === $options['breakOn'] || (is_array($options['breakOn']) && in_array($result, $options['breakOn'], true)))) { + return $result; + } elseif ($options['modParams'] && is_array($result)) { + $params[0] = $result; + } + } + if ($options['modParams'] && isset($params[0])) { + return $params[0]; + } + return true; + } + +/** + * Gets the method list for attached behaviors, i.e. all public, non-callback methods + * + * @return array All public methods for all behaviors attached to this collection + * @access public + */ + function methods() { + return $this->__methods; + } + +/** + * Gets the list of attached behaviors, or, whether the given behavior is attached + * + * @param string $name Optional. The name of the behavior to check the status of. If omitted, + * returns an array of currently-attached behaviors + * @return mixed If $name is specified, returns the boolean status of the corresponding behavior. + * Otherwise, returns an array of all attached behaviors. + * @access public + */ + function attached($name = null) { + if (!empty($name)) { + return (in_array($name, $this->_attached)); + } + return $this->_attached; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/multibyte.php b/code/ryzom/tools/server/www/webtt/cake/libs/multibyte.php new file mode 100644 index 000000000..71a4ca6ca --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/multibyte.php @@ -0,0 +1,1172 @@ + 1) { + $matches[$needle[0]] = $matches[$needle[0]] - 1; + } elseif ($i === $needleCount) { + $found = true; + } + } + + if (!$found && isset($haystack[$position])) { + $parts[] = $haystack[$position]; + unset($haystack[$position]); + } + $position++; + } + + if ($found && $part && !empty($parts)) { + return Multibyte::ascii($parts); + } elseif ($found && !empty($haystack)) { + return Multibyte::ascii($haystack); + } + return false; + } + +/** + * Finds the last occurrence of a character in a string within another, case insensitive. + * + * @param string $haystack The string from which to get the last occurrence of $needle. + * @param string $needle The string to find in $haystack. + * @param boolean $part Determines which portion of $haystack this function returns. + * If set to true, it returns all of $haystack from the beginning to the last occurrence of $needle. + * If set to false, it returns all of $haystack from the last occurrence of $needle to the end, + * Default value is false. + * @return string|boolean The portion of $haystack. or false if $needle is not found. + * @access public + * @static + */ + function strrichr($haystack, $needle, $part = false) { + $check = Multibyte::strtoupper($haystack); + $check = Multibyte::utf8($check); + $found = false; + + $haystack = Multibyte::utf8($haystack); + $haystackCount = count($haystack); + + $matches = array_count_values($check); + + $needle = Multibyte::strtoupper($needle); + $needle = Multibyte::utf8($needle); + $needleCount = count($needle); + + $parts = array(); + $position = 0; + + while (($found === false) && ($position < $haystackCount)) { + if (isset($needle[0]) && $needle[0] === $check[$position]) { + for ($i = 1; $i < $needleCount; $i++) { + if ($needle[$i] !== $check[$position + $i]) { + if ($needle[$i] === $check[($position + $i) -1]) { + $found = true; + } + unset($parts[$position - 1]); + $haystack = array_merge(array($haystack[$position]), $haystack); + break; + } + } + if (isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) { + $matches[$needle[0]] = $matches[$needle[0]] - 1; + } elseif ($i === $needleCount) { + $found = true; + } + } + + if (!$found && isset($haystack[$position])) { + $parts[] = $haystack[$position]; + unset($haystack[$position]); + } + $position++; + } + + if ($found && $part && !empty($parts)) { + return Multibyte::ascii($parts); + } elseif ($found && !empty($haystack)) { + return Multibyte::ascii($haystack); + } + return false; + } + +/** + * Finds position of last occurrence of a string within another, case insensitive + * + * @param string $haystack The string from which to get the position of the last occurrence of $needle. + * @param string $needle The string to find in $haystack. + * @param integer $offset The position in $haystack to start searching. + * @return integer|boolean The numeric position of the last occurrence of $needle in the $haystack string, + * or false if $needle is not found. + * @access public + * @static + */ + function strripos($haystack, $needle, $offset = 0) { + if (!PHP5 || Multibyte::checkMultibyte($haystack)) { + $found = false; + $haystack = Multibyte::strtoupper($haystack); + $haystack = Multibyte::utf8($haystack); + $haystackCount = count($haystack); + + $matches = array_count_values($haystack); + + $needle = Multibyte::strtoupper($needle); + $needle = Multibyte::utf8($needle); + $needleCount = count($needle); + + $position = $offset; + + while (($found === false) && ($position < $haystackCount)) { + if (isset($needle[0]) && $needle[0] === $haystack[$position]) { + for ($i = 1; $i < $needleCount; $i++) { + if ($needle[$i] !== $haystack[$position + $i]) { + if ($needle[$i] === $haystack[($position + $i) -1]) { + $position--; + $found = true; + continue; + } + } + } + + if (!$offset && isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) { + $matches[$needle[0]] = $matches[$needle[0]] - 1; + } elseif ($i === $needleCount) { + $found = true; + $position--; + } + } + $position++; + } + return ($found) ? $position : false; + } + return strripos($haystack, $needle, $offset); + } + +/** + * Find position of last occurrence of a string in a string. + * + * @param string $haystack The string being checked, for the last occurrence of $needle. + * @param string $needle The string to find in $haystack. + * @param integer $offset May be specified to begin searching an arbitrary number of characters into the string. + * Negative values will stop searching at an arbitrary point prior to the end of the string. + * @return integer|boolean The numeric position of the last occurrence of $needle in the $haystack string. + * If $needle is not found, it returns false. + * @access public + * @static + */ + function strrpos($haystack, $needle, $offset = 0) { + if (!PHP5 || Multibyte::checkMultibyte($haystack)) { + $found = false; + + $haystack = Multibyte::utf8($haystack); + $haystackCount = count($haystack); + + $matches = array_count_values($haystack); + + $needle = Multibyte::utf8($needle); + $needleCount = count($needle); + + $position = $offset; + + while (($found === false) && ($position < $haystackCount)) { + if (isset($needle[0]) && $needle[0] === $haystack[$position]) { + for ($i = 1; $i < $needleCount; $i++) { + if ($needle[$i] !== $haystack[$position + $i]) { + if ($needle[$i] === $haystack[($position + $i) -1]) { + $position--; + $found = true; + continue; + } + } + } + + if (!$offset && isset($matches[$needle[0]]) && $matches[$needle[0]] > 1) { + $matches[$needle[0]] = $matches[$needle[0]] - 1; + } elseif ($i === $needleCount) { + $found = true; + $position--; + } + } + $position++; + } + return ($found) ? $position : false; + } + return strrpos($haystack, $needle, $offset); + } + +/** + * Finds first occurrence of a string within another + * + * @param string $haystack The string from which to get the first occurrence of $needle. + * @param string $needle The string to find in $haystack + * @param boolean $part Determines which portion of $haystack this function returns. + * If set to true, it returns all of $haystack from the beginning to the first occurrence of $needle. + * If set to false, it returns all of $haystack from the first occurrence of $needle to the end, + * Default value is FALSE. + * @return string|boolean The portion of $haystack, or true if $needle is not found. + * @access public + * @static + */ + function strstr($haystack, $needle, $part = false) { + $php = (PHP_VERSION < 5.3); + + if (($php && $part) || Multibyte::checkMultibyte($haystack)) { + $check = Multibyte::utf8($haystack); + $found = false; + + $haystack = Multibyte::utf8($haystack); + $haystackCount = count($haystack); + + $needle = Multibyte::utf8($needle); + $needleCount = count($needle); + + $parts = array(); + $position = 0; + + while (($found === false) && ($position < $haystackCount)) { + if (isset($needle[0]) && $needle[0] === $check[$position]) { + for ($i = 1; $i < $needleCount; $i++) { + if ($needle[$i] !== $check[$position + $i]) { + break; + } + } + if ($i === $needleCount) { + $found = true; + } + } + if (!$found) { + $parts[] = $haystack[$position]; + unset($haystack[$position]); + } + $position++; + } + + if ($found && $part && !empty($parts)) { + return Multibyte::ascii($parts); + } elseif ($found && !empty($haystack)) { + return Multibyte::ascii($haystack); + } + return false; + } + + if (!$php) { + return strstr($haystack, $needle, $part); + } + return strstr($haystack, $needle); + } + +/** + * Make a string lowercase + * + * @param string $string The string being lowercased. + * @return string with all alphabetic characters converted to lowercase. + * @access public + * @static + */ + function strtolower($string) { + $_this =& Multibyte::getInstance(); + $utf8Map = Multibyte::utf8($string); + + $length = count($utf8Map); + $lowerCase = array(); + $matched = false; + + for ($i = 0 ; $i < $length; $i++) { + $char = $utf8Map[$i]; + + if ($char < 128) { + $str = strtolower(chr($char)); + $strlen = strlen($str); + for ($ii = 0 ; $ii < $strlen; $ii++) { + $lower = ord(substr($str, $ii, 1)); + } + $lowerCase[] = $lower; + $matched = true; + } else { + $matched = false; + $keys = $_this->__find($char, 'upper'); + + if (!empty($keys)) { + foreach ($keys as $key => $value) { + if ($keys[$key]['upper'] == $char && count($keys[$key]['lower'][0]) === 1) { + $lowerCase[] = $keys[$key]['lower'][0]; + $matched = true; + break 1; + } + } + } + } + if ($matched === false) { + $lowerCase[] = $char; + } + } + return Multibyte::ascii($lowerCase); + } + +/** + * Make a string uppercase + * + * @param string $string The string being uppercased. + * @param string $encoding Character encoding name to use. If it is omitted, internal character encoding is used. + * @return string with all alphabetic characters converted to uppercase. + * @access public + * @static + */ + function strtoupper($string) { + $_this =& Multibyte::getInstance(); + $utf8Map = Multibyte::utf8($string); + + $length = count($utf8Map); + $matched = false; + $replaced = array(); + $upperCase = array(); + + for ($i = 0 ; $i < $length; $i++) { + $char = $utf8Map[$i]; + + if ($char < 128) { + $str = strtoupper(chr($char)); + $strlen = strlen($str); + for ($ii = 0 ; $ii < $strlen; $ii++) { + $upper = ord(substr($str, $ii, 1)); + } + $upperCase[] = $upper; + $matched = true; + + } else { + $matched = false; + $keys = $_this->__find($char); + $keyCount = count($keys); + + if (!empty($keys)) { + foreach ($keys as $key => $value) { + $matched = false; + $replace = 0; + if ($length > 1 && count($keys[$key]['lower']) > 1) { + $j = 0; + + for ($ii = 0, $count = count($keys[$key]['lower']); $ii < $count; $ii++) { + $nextChar = $utf8Map[$i + $ii]; + + if (isset($nextChar) && ($nextChar == $keys[$key]['lower'][$j + $ii])) { + $replace++; + } + } + if ($replace == $count) { + $upperCase[] = $keys[$key]['upper']; + $replaced = array_merge($replaced, array_values($keys[$key]['lower'])); + $matched = true; + break 1; + } + } elseif ($length > 1 && $keyCount > 1) { + $j = 0; + for ($ii = 1; $ii < $keyCount; $ii++) { + $nextChar = $utf8Map[$i + $ii - 1]; + + if (in_array($nextChar, $keys[$ii]['lower'])) { + + for ($jj = 0, $count = count($keys[$ii]['lower']); $jj < $count; $jj++) { + $nextChar = $utf8Map[$i + $jj]; + + if (isset($nextChar) && ($nextChar == $keys[$ii]['lower'][$j + $jj])) { + $replace++; + } + } + if ($replace == $count) { + $upperCase[] = $keys[$ii]['upper']; + $replaced = array_merge($replaced, array_values($keys[$ii]['lower'])); + $matched = true; + break 2; + } + } + } + } + if ($keys[$key]['lower'][0] == $char) { + $upperCase[] = $keys[$key]['upper']; + $matched = true; + break 1; + } + } + } + } + if ($matched === false && !in_array($char, $replaced, true)) { + $upperCase[] = $char; + } + } + return Multibyte::ascii($upperCase); + } + +/** + * Count the number of substring occurrences + * + * @param string $haystack The string being checked. + * @param string $needle The string being found. + * @return integer The number of times the $needle substring occurs in the $haystack string. + * @access public + * @static + */ + function substrCount($haystack, $needle) { + $count = 0; + $haystack = Multibyte::utf8($haystack); + $haystackCount = count($haystack); + $matches = array_count_values($haystack); + $needle = Multibyte::utf8($needle); + $needleCount = count($needle); + + if ($needleCount === 1 && isset($matches[$needle[0]])) { + return $matches[$needle[0]]; + } + + for ($i = 0; $i < $haystackCount; $i++) { + if (isset($needle[0]) && $needle[0] === $haystack[$i]) { + for ($ii = 1; $ii < $needleCount; $ii++) { + if ($needle[$ii] === $haystack[$i + 1]) { + if ((isset($needle[$ii + 1]) && $haystack[$i + 2]) && $needle[$ii + 1] !== $haystack[$i + 2]) { + $count--; + } else { + $count++; + } + } + } + } + } + return $count; + } + +/** + * Get part of string + * + * @param string $string The string being checked. + * @param integer $start The first position used in $string. + * @param integer $length The maximum length of the returned string. + * @return string The portion of $string specified by the $string and $length parameters. + * @access public + * @static + */ + function substr($string, $start, $length = null) { + if ($start === 0 && $length === null) { + return $string; + } + + $string = Multibyte::utf8($string); + $stringCount = count($string); + + for ($i = 1; $i <= $start; $i++) { + unset($string[$i - 1]); + } + + if ($length === null || count($string) < $length) { + return Multibyte::ascii($string); + } + $string = array_values($string); + + $value = array(); + for ($i = 0; $i < $length; $i++) { + $value[] = $string[$i]; + } + return Multibyte::ascii($value); + } + +/** + * Prepare a string for mail transport, using the provided encoding + * + * @param string $string value to encode + * @param string $charset charset to use for encoding. defaults to UTF-8 + * @param string $newline + * @return string + * @access public + * @static + * @TODO: add support for 'Q'('Quoted Printable') encoding + */ + function mimeEncode($string, $charset = null, $newline = "\r\n") { + if (!Multibyte::checkMultibyte($string) && strlen($string) < 75) { + return $string; + } + + if (empty($charset)) { + $charset = Configure::read('App.encoding'); + } + $charset = strtoupper($charset); + + $start = '=?' . $charset . '?B?'; + $end = '?='; + $spacer = $end . $newline . ' ' . $start; + + $length = 75 - strlen($start) - strlen($end); + $length = $length - ($length % 4); + if ($charset == 'UTF-8') { + $parts = array(); + $maxchars = floor(($length * 3) / 4); + while (strlen($string) > $maxchars) { + $i = $maxchars; + $test = ord($string[$i]); + while ($test >= 128 && $test <= 191) { + $i--; + $test = ord($string[$i]); + } + $parts[] = base64_encode(substr($string, 0, $i)); + $string = substr($string, $i); + } + $parts[] = base64_encode($string); + $string = implode($spacer, $parts); + } else { + $string = chunk_split(base64_encode($string), $length, $spacer); + $string = preg_replace('/' . preg_quote($spacer) . '$/', '', $string); + } + return $start . $string . $end; + } + +/** + * Return the Code points range for Unicode characters + * + * @param interger $decimal + * @return string + * @access private + */ + function __codepoint($decimal) { + if ($decimal > 128 && $decimal < 256) { + $return = '0080_00ff'; // Latin-1 Supplement + } elseif ($decimal < 384) { + $return = '0100_017f'; // Latin Extended-A + } elseif ($decimal < 592) { + $return = '0180_024F'; // Latin Extended-B + } elseif ($decimal < 688) { + $return = '0250_02af'; // IPA Extensions + } elseif ($decimal >= 880 && $decimal < 1024) { + $return = '0370_03ff'; // Greek and Coptic + } elseif ($decimal < 1280) { + $return = '0400_04ff'; // Cyrillic + } elseif ($decimal < 1328) { + $return = '0500_052f'; // Cyrillic Supplement + } elseif ($decimal < 1424) { + $return = '0530_058f'; // Armenian + } elseif ($decimal >= 7680 && $decimal < 7936) { + $return = '1e00_1eff'; // Latin Extended Additional + } elseif ($decimal < 8192) { + $return = '1f00_1fff'; // Greek Extended + } elseif ($decimal >= 8448 && $decimal < 8528) { + $return = '2100_214f'; // Letterlike Symbols + } elseif ($decimal < 8592) { + $return = '2150_218f'; // Number Forms + } elseif ($decimal >= 9312 && $decimal < 9472) { + $return = '2460_24ff'; // Enclosed Alphanumerics + } elseif ($decimal >= 11264 && $decimal < 11360) { + $return = '2c00_2c5f'; // Glagolitic + } elseif ($decimal < 11392) { + $return = '2c60_2c7f'; // Latin Extended-C + } elseif ($decimal < 11520) { + $return = '2c80_2cff'; // Coptic + } elseif ($decimal >= 65280 && $decimal < 65520) { + $return = 'ff00_ffef'; // Halfwidth and Fullwidth Forms + } else { + $return = false; + } + $this->__codeRange[$decimal] = $return; + return $return; + } + +/** + * Find the related code folding values for $char + * + * @param integer $char decimal value of character + * @param string $type + * @return array + * @access private + */ + function __find($char, $type = 'lower') { + $value = false; + $found = array(); + if (!isset($this->__codeRange[$char])) { + $range = $this->__codepoint($char); + if ($range === false) { + return null; + } + Configure::load('unicode' . DS . 'casefolding' . DS . $range); + $this->__caseFold[$range] = Configure::read($range); + Configure::delete($range); + } + + if (!$this->__codeRange[$char]) { + return null; + } + $this->__table = $this->__codeRange[$char]; + $count = count($this->__caseFold[$this->__table]); + + for ($i = 0; $i < $count; $i++) { + if ($type === 'lower' && $this->__caseFold[$this->__table][$i][$type][0] === $char) { + $found[] = $this->__caseFold[$this->__table][$i]; + } elseif ($type === 'upper' && $this->__caseFold[$this->__table][$i][$type] === $char) { + $found[] = $this->__caseFold[$this->__table][$i]; + } + } + return $found; + } + +/** + * Check the $string for multibyte characters + * @param string $string value to test + * @return boolean + * @access public + * @static + */ + function checkMultibyte($string) { + $length = strlen($string); + + for ($i = 0; $i < $length; $i++ ) { + $value = ord(($string[$i])); + if ($value > 128) { + return true; + } + } + return false; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/object.php b/code/ryzom/tools/server/www/webtt/cake/libs/object.php new file mode 100644 index 000000000..8fcb3cf05 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/object.php @@ -0,0 +1,298 @@ + 0, 'autoRender' => 1)); + } + if (is_array($url) && !isset($extra['url'])) { + $extra['url'] = array(); + } + $params = array_merge(array('autoRender' => 0, 'return' => 1, 'bare' => 1, 'requested' => 1), $extra); + $dispatcher = new Dispatcher; + return $dispatcher->dispatch($url, $params); + } + +/** + * Calls a method on this object with the given parameters. Provides an OO wrapper + * for `call_user_func_array` + * + * @param string $method Name of the method to call + * @param array $params Parameter list to use when calling $method + * @return mixed Returns the result of the method call + * @access public + */ + function dispatchMethod($method, $params = array()) { + switch (count($params)) { + case 0: + return $this->{$method}(); + case 1: + return $this->{$method}($params[0]); + case 2: + return $this->{$method}($params[0], $params[1]); + case 3: + return $this->{$method}($params[0], $params[1], $params[2]); + case 4: + return $this->{$method}($params[0], $params[1], $params[2], $params[3]); + case 5: + return $this->{$method}($params[0], $params[1], $params[2], $params[3], $params[4]); + default: + return call_user_func_array(array(&$this, $method), $params); + break; + } + } + +/** + * Stop execution of the current script. Wraps exit() making + * testing easier. + * + * @param $status see http://php.net/exit for values + * @return void + * @access public + */ + function _stop($status = 0) { + exit($status); + } + +/** + * Convience method to write a message to CakeLog. See CakeLog::write() + * for more information on writing to logs. + * + * @param string $msg Log message + * @param integer $type Error type constant. Defined in app/config/core.php. + * @return boolean Success of log write + * @access public + */ + function log($msg, $type = LOG_ERROR) { + if (!class_exists('CakeLog')) { + require LIBS . 'cake_log.php'; + } + if (!is_string($msg)) { + $msg = print_r($msg, true); + } + return CakeLog::write($type, $msg); + } + +/** + * Allows setting of multiple properties of the object in a single line of code. Will only set + * properties that are part of a class declaration. + * + * @param array $properties An associative array containing properties and corresponding values. + * @return void + * @access protected + */ + function _set($properties = array()) { + if (is_array($properties) && !empty($properties)) { + $vars = get_object_vars($this); + foreach ($properties as $key => $val) { + if (array_key_exists($key, $vars)) { + $this->{$key} = $val; + } + } + } + } + +/** + * Used to report user friendly errors. + * If there is a file app/error.php or app/app_error.php this file will be loaded + * error.php is the AppError class it should extend ErrorHandler class. + * + * @param string $method Method to be called in the error class (AppError or ErrorHandler classes) + * @param array $messages Message that is to be displayed by the error class + * @return error message + * @access public + */ + function cakeError($method, $messages = array()) { + if (!class_exists('ErrorHandler')) { + App::import('Core', 'Error'); + + if (file_exists(APP . 'error.php')) { + include_once (APP . 'error.php'); + } elseif (file_exists(APP . 'app_error.php')) { + include_once (APP . 'app_error.php'); + } + } + + if (class_exists('AppError')) { + $error = new AppError($method, $messages); + } else { + $error = new ErrorHandler($method, $messages); + } + return $error; + } + +/** + * Checks for a persistent class file, if found file is opened and true returned + * If file is not found a file is created and false returned + * If used in other locations of the model you should choose a unique name for the persistent file + * There are many uses for this method, see manual for examples + * + * @param string $name name of the class to persist + * @param string $object the object to persist + * @return boolean Success + * @access protected + * @todo add examples to manual + */ + function _persist($name, $return = null, &$object, $type = null) { + $file = CACHE . 'persistent' . DS . strtolower($name) . '.php'; + if ($return === null) { + if (!file_exists($file)) { + return false; + } else { + return true; + } + } + + if (!file_exists($file)) { + $this->_savePersistent($name, $object); + return false; + } else { + $this->__openPersistent($name, $type); + return true; + } + } + +/** + * You should choose a unique name for the persistent file + * + * There are many uses for this method, see manual for examples + * + * @param string $name name used for object to cache + * @param object $object the object to persist + * @return boolean true on save, throws error if file can not be created + * @access protected + */ + function _savePersistent($name, &$object) { + $file = 'persistent' . DS . strtolower($name) . '.php'; + $objectArray = array(&$object); + $data = str_replace('\\', '\\\\', serialize($objectArray)); + $data = ''; + $duration = '+999 days'; + if (Configure::read() >= 1) { + $duration = '+10 seconds'; + } + cache($file, $data, $duration); + } + +/** + * Open the persistent class file for reading + * Used by Object::_persist() + * + * @param string $name Name of persisted class + * @param string $type Type of persistance (e.g: registry) + * @return void + * @access private + */ + function __openPersistent($name, $type = null) { + $file = CACHE . 'persistent' . DS . strtolower($name) . '.php'; + include($file); + + switch ($type) { + case 'registry': + $vars = unserialize(${$name}); + foreach ($vars['0'] as $key => $value) { + if (strpos($key, '_behavior') !== false) { + App::import('Behavior', Inflector::classify(substr($key, 0, -9))); + } else { + App::import('Model', Inflector::camelize($key)); + } + unset ($value); + } + unset($vars); + $vars = unserialize(${$name}); + foreach ($vars['0'] as $key => $value) { + ClassRegistry::addObject($key, $value); + unset ($value); + } + unset($vars); + break; + default: + $vars = unserialize(${$name}); + $this->{$name} = $vars['0']; + unset($vars); + break; + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/overloadable.php b/code/ryzom/tools/server/www/webtt/cake/libs/overloadable.php new file mode 100644 index 000000000..2d2eea290 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/overloadable.php @@ -0,0 +1,36 @@ +overload(); + parent::__construct(); + } + +/** + * Overload implementation. + * + * @access public + */ + function overload() { + if (function_exists('overload')) { + if (func_num_args() > 0) { + foreach (func_get_args() as $class) { + if (is_object($class)) { + overload(get_class($class)); + } elseif (is_string($class)) { + overload($class); + } + } + } else { + overload(get_class($this)); + } + } + } + +/** + * Magic method handler. + * + * @param string $method Method name + * @param array $params Parameters to send to method + * @param mixed $return Where to store return value from method + * @return boolean Success + * @access private + */ + function __call($method, $params, &$return) { + if (!method_exists($this, 'call__')) { + trigger_error(sprintf(__('Magic method handler call__ not defined in %s', true), get_class($this)), E_USER_ERROR); + } + $return = $this->call__($method, $params); + return true; + } +} +Overloadable::overload('Overloadable'); + +/** + * Overloadable2 class selector + * + * Load the interface class based on the version of PHP. + * + * @package cake + * @subpackage cake.cake.libs + */ +class Overloadable2 extends Object { + +/** + * Constructor + * + * @access private + */ + function __construct() { + $this->overload(); + parent::__construct(); + } + +/** + * Overload implementation. + * + * @access public + */ + function overload() { + if (function_exists('overload')) { + if (func_num_args() > 0) { + foreach (func_get_args() as $class) { + if (is_object($class)) { + overload(get_class($class)); + } elseif (is_string($class)) { + overload($class); + } + } + } else { + overload(get_class($this)); + } + } + } + +/** + * Magic method handler. + * + * @param string $method Method name + * @param array $params Parameters to send to method + * @param mixed $return Where to store return value from method + * @return boolean Success + * @access private + */ + function __call($method, $params, &$return) { + if (!method_exists($this, 'call__')) { + trigger_error(sprintf(__('Magic method handler call__ not defined in %s', true), get_class($this)), E_USER_ERROR); + } + $return = $this->call__($method, $params); + return true; + } + +/** + * Getter. + * + * @param mixed $name What to get + * @param mixed $value Where to store returned value + * @return boolean Success + * @access private + */ + function __get($name, &$value) { + $value = $this->get__($name); + return true; + } + +/** + * Setter. + * + * @param mixed $name What to set + * @param mixed $value Value to set + * @return boolean Success + * @access private + */ + function __set($name, $value) { + $this->set__($name, $value); + return true; + } +} +Overloadable::overload('Overloadable2'); diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/overloadable_php5.php b/code/ryzom/tools/server/www/webtt/cake/libs/overloadable_php5.php new file mode 100644 index 000000000..ed27493b9 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/overloadable_php5.php @@ -0,0 +1,108 @@ +call__($method, $params); + } +} + +/** + * Overloadable2 class selector + * + * Load the interface class based on the version of PHP. + * + * @package cake + */ +class Overloadable2 extends Object { + +/** + * Overload implementation. No need for implementation in PHP5. + * + * @access public + */ + function overload() { } + +/** + * Magic method handler. + * + * @param string $method Method name + * @param array $params Parameters to send to method + * @return mixed Return value from method + * @access private + */ + function __call($method, $params) { + if (!method_exists($this, 'call__')) { + trigger_error(sprintf(__('Magic method handler call__ not defined in %s', true), get_class($this)), E_USER_ERROR); + } + return $this->call__($method, $params); + } + +/** + * Getter. + * + * @param mixed $name What to get + * @param mixed $value Where to store returned value + * @return boolean Success + * @access private + */ + function __get($name) { + return $this->get__($name); + } + +/** + * Setter. + * + * @param mixed $name What to set + * @param mixed $value Value to set + * @return boolean Success + * @access private + */ + function __set($name, $value) { + return $this->set__($name, $value); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/router.php b/code/ryzom/tools/server/www/webtt/cake/libs/router.php new file mode 100644 index 000000000..362cef2d2 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/router.php @@ -0,0 +1,1648 @@ + 'index|show|add|create|edit|update|remove|del|delete|view|item', + 'Year' => '[12][0-9]{3}', + 'Month' => '0[1-9]|1[012]', + 'Day' => '0[1-9]|[12][0-9]|3[01]', + 'ID' => '[0-9]+', + 'UUID' => '[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}' + ); + +/** + * Stores all information necessary to decide what named arguments are parsed under what conditions. + * + * @var string + * @access public + */ + var $named = array( + 'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'), + 'greedy' => true, + 'separator' => ':', + 'rules' => false, + ); + +/** + * The route matching the URL of the current request + * + * @var array + * @access private + */ + var $__currentRoute = array(); + +/** + * Default HTTP request method => controller action map. + * + * @var array + * @access private + */ + var $__resourceMap = array( + array('action' => 'index', 'method' => 'GET', 'id' => false), + array('action' => 'view', 'method' => 'GET', 'id' => true), + array('action' => 'add', 'method' => 'POST', 'id' => false), + array('action' => 'edit', 'method' => 'PUT', 'id' => true), + array('action' => 'delete', 'method' => 'DELETE', 'id' => true), + array('action' => 'edit', 'method' => 'POST', 'id' => true) + ); + +/** + * List of resource-mapped controllers + * + * @var array + * @access private + */ + var $__resourceMapped = array(); + +/** + * Maintains the parameter stack for the current request + * + * @var array + * @access private + */ + var $__params = array(); + +/** + * Maintains the path stack for the current request + * + * @var array + * @access private + */ + var $__paths = array(); + +/** + * Keeps Router state to determine if default routes have already been connected + * + * @var boolean + * @access private + */ + var $__defaultsMapped = false; + +/** + * Keeps track of whether the connection of default routes is enabled or disabled. + * + * @var boolean + * @access private + */ + var $__connectDefaults = true; + +/** + * Constructor for Router. + * Builds __prefixes + * + * @return void + */ + function Router() { + $this->__setPrefixes(); + } + +/** + * Sets the Routing prefixes. Includes compatibility for existing Routing.admin + * configurations. + * + * @return void + * @access private + * @todo Remove support for Routing.admin in future versions. + */ + function __setPrefixes() { + $routing = Configure::read('Routing'); + if (!empty($routing['admin'])) { + $this->__prefixes[] = $routing['admin']; + } + if (!empty($routing['prefixes'])) { + $this->__prefixes = array_merge($this->__prefixes, (array)$routing['prefixes']); + } + } + +/** + * Gets a reference to the Router object instance + * + * @return Router Instance of the Router. + * @access public + * @static + */ + function &getInstance() { + static $instance = array(); + + if (!$instance) { + $instance[0] =& new Router(); + } + return $instance[0]; + } + +/** + * Gets the named route elements for use in app/config/routes.php + * + * @return array Named route elements + * @access public + * @see Router::$__named + * @static + */ + function getNamedExpressions() { + $self =& Router::getInstance(); + return $self->__named; + } + +/** + * Connects a new Route in the router. + * + * Routes are a way of connecting request urls to objects in your application. At their core routes + * are a set or regular expressions that are used to match requests to destinations. + * + * Examples: + * + * `Router::connect('/:controller/:action/*');` + * + * The first parameter will be used as a controller name while the second is used as the action name. + * the '/*' syntax makes this route greedy in that it will match requests like `/posts/index` as well as requests + * like `/posts/edit/1/foo/bar`. + * + * `Router::connect('/home-page', array('controller' => 'pages', 'action' => 'display', 'home'));` + * + * The above shows the use of route parameter defaults. And providing routing parameters for a static route. + * + * {{{ + * Router::connect( + * '/:lang/:controller/:action/:id', + * array(), + * array('id' => '[0-9]+', 'lang' => '[a-z]{3}') + * ); + * }}} + * + * Shows connecting a route with custom route parameters as well as providing patterns for those parameters. + * Patterns for routing parameters do not need capturing groups, as one will be added for each route params. + * + * $options offers three 'special' keys. `pass`, `persist` and `routeClass` have special meaning in the $options array. + * + * `pass` is used to define which of the routed parameters should be shifted into the pass array. Adding a + * parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')` + * + * `persist` is used to define which route parameters should be automatically included when generating + * new urls. You can override persistent parameters by redefining them in a url or remove them by + * setting the parameter to `false`. Ex. `'persist' => array('lang')` + * + * `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing, + * via a custom routing class. Ex. `'routeClass' => 'SlugRoute'` + * + * @param string $route A string describing the template of the route + * @param array $defaults An array describing the default route parameters. These parameters will be used by default + * and can supply routing parameters that are not dynamic. See above. + * @param array $options An array matching the named elements in the route to regular expressions which that + * element should match. Also contains additional parameters such as which routed parameters should be + * shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a + * custom routing class. + * @see routes + * @return array Array of routes + * @access public + * @static + */ + function connect($route, $defaults = array(), $options = array()) { + $self =& Router::getInstance(); + + foreach ($self->__prefixes as $prefix) { + if (isset($defaults[$prefix])) { + $defaults['prefix'] = $prefix; + break; + } + } + if (isset($defaults['prefix'])) { + $self->__prefixes[] = $defaults['prefix']; + $self->__prefixes = array_keys(array_flip($self->__prefixes)); + } + $defaults += array('plugin' => null); + if (empty($options['action'])) { + $defaults += array('action' => 'index'); + } + $routeClass = 'CakeRoute'; + if (isset($options['routeClass'])) { + $routeClass = $options['routeClass']; + unset($options['routeClass']); + } + //TODO 2.0 refactor this to use a string class name, throw exception, and then construct. + $Route =& new $routeClass($route, $defaults, $options); + if ($routeClass !== 'CakeRoute' && !is_subclass_of($Route, 'CakeRoute')) { + trigger_error(__('Route classes must extend CakeRoute', true), E_USER_WARNING); + return false; + } + $self->routes[] =& $Route; + return $self->routes; + } + +/** + * Specifies what named parameters CakePHP should be parsing. The most common setups are: + * + * Do not parse any named parameters: + * + * {{{ Router::connectNamed(false); }}} + * + * Parse only default parameters used for CakePHP's pagination: + * + * {{{ Router::connectNamed(false, array('default' => true)); }}} + * + * Parse only the page parameter if its value is a number: + * + * {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}} + * + * Parse only the page parameter no mater what. + * + * {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}} + * + * Parse only the page parameter if the current action is 'index'. + * + * {{{ + * Router::connectNamed( + * array('page' => array('action' => 'index')), + * array('default' => false, 'greedy' => false) + * ); + * }}} + * + * Parse only the page parameter if the current action is 'index' and the controller is 'pages'. + * + * {{{ + * Router::connectNamed( + * array('page' => array('action' => 'index', 'controller' => 'pages')), + * array('default' => false, 'greedy' => false) + * ); + * }}} + * + * @param array $named A list of named parameters. Key value pairs are accepted where values are + * either regex strings to match, or arrays as seen above. + * @param array $options Allows to control all settings: separator, greedy, reset, default + * @return array + * @access public + * @static + */ + function connectNamed($named, $options = array()) { + $self =& Router::getInstance(); + + if (isset($options['argSeparator'])) { + $self->named['separator'] = $options['argSeparator']; + unset($options['argSeparator']); + } + + if ($named === true || $named === false) { + $options = array_merge(array('default' => $named, 'reset' => true, 'greedy' => $named), $options); + $named = array(); + } else { + $options = array_merge(array('default' => false, 'reset' => false, 'greedy' => true), $options); + } + + if ($options['reset'] == true || $self->named['rules'] === false) { + $self->named['rules'] = array(); + } + + if ($options['default']) { + $named = array_merge($named, $self->named['default']); + } + + foreach ($named as $key => $val) { + if (is_numeric($key)) { + $self->named['rules'][$val] = true; + } else { + $self->named['rules'][$key] = $val; + } + } + $self->named['greedy'] = $options['greedy']; + return $self->named; + } + +/** + * Tell router to connect or not connect the default routes. + * + * If default routes are disabled all automatic route generation will be disabled + * and you will need to manually configure all the routes you want. + * + * @param boolean $connect Set to true or false depending on whether you want or don't want default routes. + * @return void + * @access public + * @static + */ + function defaults($connect = true) { + $self =& Router::getInstance(); + $self->__connectDefaults = $connect; + } + +/** + * Creates REST resource routes for the given controller(s) + * + * ### Options: + * + * - 'id' - The regular expression fragment to use when matching IDs. By default, matches + * integer values and UUIDs. + * - 'prefix' - URL prefix to use for the generated routes. Defaults to '/'. + * + * @param mixed $controller A controller name or array of controller names (i.e. "Posts" or "ListItems") + * @param array $options Options to use when generating REST routes + * @return void + * @access public + * @static + */ + function mapResources($controller, $options = array()) { + $self =& Router::getInstance(); + $options = array_merge(array('prefix' => '/', 'id' => $self->__named['ID'] . '|' . $self->__named['UUID']), $options); + $prefix = $options['prefix']; + + foreach ((array)$controller as $ctlName) { + $urlName = Inflector::underscore($ctlName); + + foreach ($self->__resourceMap as $params) { + extract($params); + $url = $prefix . $urlName . (($id) ? '/:id' : ''); + + Router::connect($url, + array('controller' => $urlName, 'action' => $action, '[method]' => $params['method']), + array('id' => $options['id'], 'pass' => array('id')) + ); + } + $self->__resourceMapped[] = $urlName; + } + } + +/** + * Returns the list of prefixes used in connected routes + * + * @return array A list of prefixes used in connected routes + * @access public + * @static + */ + function prefixes() { + $self =& Router::getInstance(); + return $self->__prefixes; + } + +/** + * Parses given URL and returns an array of controller, action and parameters + * taken from that URL. + * + * @param string $url URL to be parsed + * @return array Parsed elements from URL + * @access public + * @static + */ + function parse($url) { + $self =& Router::getInstance(); + if (!$self->__defaultsMapped && $self->__connectDefaults) { + $self->__connectDefaultRoutes(); + } + $out = array( + 'pass' => array(), + 'named' => array(), + ); + $r = $ext = null; + + if (ini_get('magic_quotes_gpc') === '1') { + $url = stripslashes_deep($url); + } + + if ($url && strpos($url, '/') !== 0) { + $url = '/' . $url; + } + if (strpos($url, '?') !== false) { + $url = substr($url, 0, strpos($url, '?')); + } + extract($self->__parseExtension($url)); + for ($i = 0, $len = count($self->routes); $i < $len; $i++) { + $route =& $self->routes[$i]; + if (($r = $route->parse($url)) !== false) { + $self->__currentRoute[] =& $route; + + $params = $route->options; + $argOptions = array(); + + if (array_key_exists('named', $params)) { + $argOptions['named'] = $params['named']; + unset($params['named']); + } + if (array_key_exists('greedy', $params)) { + $argOptions['greedy'] = $params['greedy']; + unset($params['greedy']); + } + $out = $r; + + if (isset($out['_args_'])) { + $argOptions['context'] = array('action' => $out['action'], 'controller' => $out['controller']); + $parsedArgs = $self->getArgs($out['_args_'], $argOptions); + $out['pass'] = array_merge($out['pass'], $parsedArgs['pass']); + $out['named'] = $parsedArgs['named']; + unset($out['_args_']); + } + + if (isset($params['pass'])) { + $j = count($params['pass']); + while($j--) { + if (isset($out[$params['pass'][$j]])) { + array_unshift($out['pass'], $out[$params['pass'][$j]]); + } + } + } + break; + } + } + + if (!empty($ext) && !isset($out['url']['ext'])) { + $out['url']['ext'] = $ext; + } + return $out; + } + +/** + * Parses a file extension out of a URL, if Router::parseExtensions() is enabled. + * + * @param string $url + * @return array Returns an array containing the altered URL and the parsed extension. + * @access private + */ + function __parseExtension($url) { + $ext = null; + + if ($this->__parseExtensions) { + if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) === 1) { + $match = substr($match[0], 1); + if (empty($this->__validExtensions)) { + $url = substr($url, 0, strpos($url, '.' . $match)); + $ext = $match; + } else { + foreach ($this->__validExtensions as $name) { + if (strcasecmp($name, $match) === 0) { + $url = substr($url, 0, strpos($url, '.' . $name)); + $ext = $match; + break; + } + } + } + } + if (empty($ext)) { + $ext = 'html'; + } + } + return compact('ext', 'url'); + } + +/** + * Connects the default, built-in routes, including prefix and plugin routes. The following routes are created + * in the order below: + * + * For each of the Routing.prefixes the following routes are created. Routes containing `:plugin` are only + * created when your application has one or more plugins. + * + * - `/:prefix/:plugin` a plugin shortcut route. + * - `/:prefix/:plugin/:action/*` a plugin shortcut route. + * - `/:prefix/:plugin/:controller` + * - `/:prefix/:plugin/:controller/:action/*` + * - `/:prefix/:controller` + * - `/:prefix/:controller/:action/*` + * + * If plugins are found in your application the following routes are created: + * + * - `/:plugin` a plugin shortcut route. + * - `/:plugin/:action/*` a plugin shortcut route. + * - `/:plugin/:controller` + * - `/:plugin/:controller/:action/*` + * + * And lastly the following catch-all routes are connected. + * + * - `/:controller' + * - `/:controller/:action/*' + * + * You can disable the connection of default routes with Router::defaults(). + * + * @return void + * @access private + */ + function __connectDefaultRoutes() { + if ($plugins = App::objects('plugin')) { + foreach ($plugins as $key => $value) { + $plugins[$key] = Inflector::underscore($value); + } + $pluginPattern = implode('|', $plugins); + $match = array('plugin' => $pluginPattern); + $shortParams = array('routeClass' => 'PluginShortRoute', 'plugin' => $pluginPattern); + + foreach ($this->__prefixes as $prefix) { + $params = array('prefix' => $prefix, $prefix => true); + $indexParams = $params + array('action' => 'index'); + $this->connect("/{$prefix}/:plugin", $indexParams, $shortParams); + $this->connect("/{$prefix}/:plugin/:controller", $indexParams, $match); + $this->connect("/{$prefix}/:plugin/:controller/:action/*", $params, $match); + } + $this->connect('/:plugin', array('action' => 'index'), $shortParams); + $this->connect('/:plugin/:controller', array('action' => 'index'), $match); + $this->connect('/:plugin/:controller/:action/*', array(), $match); + } + + foreach ($this->__prefixes as $prefix) { + $params = array('prefix' => $prefix, $prefix => true); + $indexParams = $params + array('action' => 'index'); + $this->connect("/{$prefix}/:controller", $indexParams); + $this->connect("/{$prefix}/:controller/:action/*", $params); + } + $this->connect('/:controller', array('action' => 'index')); + $this->connect('/:controller/:action/*'); + + if ($this->named['rules'] === false) { + $this->connectNamed(true); + } + $this->__defaultsMapped = true; + } + +/** + * Takes parameter and path information back from the Dispatcher, sets these + * parameters as the current request parameters that are merged with url arrays + * created later in the request. + * + * @param array $params Parameters and path information + * @return void + * @access public + * @static + */ + function setRequestInfo($params) { + $self =& Router::getInstance(); + $defaults = array('plugin' => null, 'controller' => null, 'action' => null); + $params[0] = array_merge($defaults, (array)$params[0]); + $params[1] = array_merge($defaults, (array)$params[1]); + list($self->__params[], $self->__paths[]) = $params; + + if (count($self->__paths)) { + if (isset($self->__paths[0]['namedArgs'])) { + foreach ($self->__paths[0]['namedArgs'] as $arg => $value) { + $self->named['rules'][$arg] = true; + } + } + } + } + +/** + * Gets parameter information + * + * @param boolean $current Get current request parameter, useful when using requestAction + * @return array Parameter information + * @access public + * @static + */ + function getParams($current = false) { + $self =& Router::getInstance(); + if ($current) { + return $self->__params[count($self->__params) - 1]; + } + if (isset($self->__params[0])) { + return $self->__params[0]; + } + return array(); + } + +/** + * Gets URL parameter by name + * + * @param string $name Parameter name + * @param boolean $current Current parameter, useful when using requestAction + * @return string Parameter value + * @access public + * @static + */ + function getParam($name = 'controller', $current = false) { + $params = Router::getParams($current); + if (isset($params[$name])) { + return $params[$name]; + } + return null; + } + +/** + * Gets path information + * + * @param boolean $current Current parameter, useful when using requestAction + * @return array + * @access public + * @static + */ + function getPaths($current = false) { + $self =& Router::getInstance(); + if ($current) { + return $self->__paths[count($self->__paths) - 1]; + } + if (!isset($self->__paths[0])) { + return array('base' => null); + } + return $self->__paths[0]; + } + +/** + * Reloads default Router settings. Resets all class variables and + * removes all connected routes. + * + * @access public + * @return void + * @static + */ + function reload() { + $self =& Router::getInstance(); + foreach (get_class_vars('Router') as $key => $val) { + $self->{$key} = $val; + } + $self->__setPrefixes(); + } + +/** + * Promote a route (by default, the last one added) to the beginning of the list + * + * @param $which A zero-based array index representing the route to move. For example, + * if 3 routes have been added, the last route would be 2. + * @return boolean Returns false if no route exists at the position specified by $which. + * @access public + * @static + */ + function promote($which = null) { + $self =& Router::getInstance(); + if ($which === null) { + $which = count($self->routes) - 1; + } + if (!isset($self->routes[$which])) { + return false; + } + $route =& $self->routes[$which]; + unset($self->routes[$which]); + array_unshift($self->routes, $route); + return true; + } + +/** + * Finds URL for specified action. + * + * Returns an URL pointing to a combination of controller and action. Param + * $url can be: + * + * - Empty - the method will find address to actual controller/action. + * - '/' - the method will find base URL of application. + * - A combination of controller/action - the method will find url for it. + * + * There are a few 'special' parameters that can change the final URL string that is generated + * + * - `base` - Set to false to remove the base path from the generated url. If your application + * is not in the root directory, this can be used to generate urls that are 'cake relative'. + * cake relative urls are required when using requestAction. + * - `?` - Takes an array of query string parameters + * - `#` - Allows you to set url hash fragments. + * - `full_base` - If true the `FULL_BASE_URL` constant will be prepended to generated urls. + * + * @param mixed $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4" + * or an array specifying any of the following: 'controller', 'action', + * and/or 'plugin', in addition to named arguments (keyed array elements), + * and standard URL arguments (indexed array elements) + * @param mixed $full If (bool) true, the full base URL will be prepended to the result. + * If an array accepts the following keys + * - escape - used when making urls embedded in html escapes query string '&' + * - full - if true the full base URL will be prepended. + * @return string Full translated URL with base path. + * @access public + * @static + */ + function url($url = null, $full = false) { + $self =& Router::getInstance(); + $defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index'); + + if (is_bool($full)) { + $escape = false; + } else { + extract($full + array('escape' => false, 'full' => false)); + } + + if (!empty($self->__params)) { + if (isset($this) && !isset($this->params['requested'])) { + $params = $self->__params[0]; + } else { + $params = end($self->__params); + } + } + $path = array('base' => null); + + if (!empty($self->__paths)) { + if (isset($this) && !isset($this->params['requested'])) { + $path = $self->__paths[0]; + } else { + $path = end($self->__paths); + } + } + $base = $path['base']; + $extension = $output = $mapped = $q = $frag = null; + + if (is_array($url)) { + if (isset($url['base']) && $url['base'] === false) { + $base = null; + unset($url['base']); + } + if (isset($url['full_base']) && $url['full_base'] === true) { + $full = true; + unset($url['full_base']); + } + if (isset($url['?'])) { + $q = $url['?']; + unset($url['?']); + } + if (isset($url['#'])) { + $frag = '#' . urlencode($url['#']); + unset($url['#']); + } + if (empty($url['action'])) { + if (empty($url['controller']) || $params['controller'] === $url['controller']) { + $url['action'] = $params['action']; + } else { + $url['action'] = 'index'; + } + } + + $prefixExists = (array_intersect_key($url, array_flip($self->__prefixes))); + foreach ($self->__prefixes as $prefix) { + if (!empty($params[$prefix]) && !$prefixExists) { + $url[$prefix] = true; + } elseif (isset($url[$prefix]) && !$url[$prefix]) { + unset($url[$prefix]); + } + if (isset($url[$prefix]) && strpos($url['action'], $prefix . '_') === 0) { + $url['action'] = substr($url['action'], strlen($prefix) + 1); + } + } + + $url += array('controller' => $params['controller'], 'plugin' => $params['plugin']); + + if (isset($url['ext'])) { + $extension = '.' . $url['ext']; + unset($url['ext']); + } + $match = false; + + for ($i = 0, $len = count($self->routes); $i < $len; $i++) { + $originalUrl = $url; + + if (isset($self->routes[$i]->options['persist'], $params)) { + $url = $self->routes[$i]->persistParams($url, $params); + } + + if ($match = $self->routes[$i]->match($url)) { + $output = trim($match, '/'); + break; + } + $url = $originalUrl; + } + if ($match === false) { + $output = $self->_handleNoRoute($url); + } + $output = str_replace('//', '/', $base . '/' . $output); + } else { + if (((strpos($url, '://')) || (strpos($url, 'javascript:') === 0) || (strpos($url, 'mailto:') === 0)) || (!strncmp($url, '#', 1))) { + return $url; + } + if (empty($url)) { + if (!isset($path['here'])) { + $path['here'] = '/'; + } + $output = $path['here']; + } elseif (substr($url, 0, 1) === '/') { + $output = $base . $url; + } else { + $output = $base . '/'; + foreach ($self->__prefixes as $prefix) { + if (isset($params[$prefix])) { + $output .= $prefix . '/'; + break; + } + } + if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) { + $output .= Inflector::underscore($params['plugin']) . '/'; + } + $output .= Inflector::underscore($params['controller']) . '/' . $url; + } + $output = str_replace('//', '/', $output); + } + if ($full && defined('FULL_BASE_URL')) { + $output = FULL_BASE_URL . $output; + } + if (!empty($extension) && substr($output, -1) === '/') { + $output = substr($output, 0, -1); + } + + return $output . $extension . $self->queryString($q, array(), $escape) . $frag; + } + +/** + * A special fallback method that handles url arrays that cannot match + * any defined routes. + * + * @param array $url A url that didn't match any routes + * @return string A generated url for the array + * @access protected + * @see Router::url() + */ + function _handleNoRoute($url) { + $named = $args = array(); + $skip = array_merge( + array('bare', 'action', 'controller', 'plugin', 'prefix'), + $this->__prefixes + ); + + $keys = array_values(array_diff(array_keys($url), $skip)); + $count = count($keys); + + // Remove this once parsed URL parameters can be inserted into 'pass' + for ($i = 0; $i < $count; $i++) { + if (is_numeric($keys[$i])) { + $args[] = $url[$keys[$i]]; + } else { + $named[$keys[$i]] = $url[$keys[$i]]; + } + } + + list($args, $named) = array(Set::filter($args, true), Set::filter($named, true)); + foreach ($this->__prefixes as $prefix) { + if (!empty($url[$prefix])) { + $url['action'] = str_replace($prefix . '_', '', $url['action']); + break; + } + } + + if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) { + $url['action'] = null; + } + + $urlOut = array_filter(array($url['controller'], $url['action'])); + + if (isset($url['plugin'])) { + array_unshift($urlOut, $url['plugin']); + } + + foreach ($this->__prefixes as $prefix) { + if (isset($url[$prefix])) { + array_unshift($urlOut, $prefix); + break; + } + } + $output = implode('/', $urlOut); + + if (!empty($args)) { + $output .= '/' . implode('/', $args); + } + + if (!empty($named)) { + foreach ($named as $name => $value) { + $output .= '/' . $name . $this->named['separator'] . $value; + } + } + return $output; + } + +/** + * Takes an array of URL parameters and separates the ones that can be used as named arguments + * + * @param array $params Associative array of URL parameters. + * @param string $controller Name of controller being routed. Used in scoping. + * @param string $action Name of action being routed. Used in scoping. + * @return array + * @access public + * @static + */ + function getNamedElements($params, $controller = null, $action = null) { + $self =& Router::getInstance(); + $named = array(); + + foreach ($params as $param => $val) { + if (isset($self->named['rules'][$param])) { + $rule = $self->named['rules'][$param]; + if (Router::matchNamed($param, $val, $rule, compact('controller', 'action'))) { + $named[$param] = $val; + unset($params[$param]); + } + } + } + return array($named, $params); + } + +/** + * Return true if a given named $param's $val matches a given $rule depending on $context. Currently implemented + * rule types are controller, action and match that can be combined with each other. + * + * @param string $param The name of the named parameter + * @param string $val The value of the named parameter + * @param array $rule The rule(s) to apply, can also be a match string + * @param string $context An array with additional context information (controller / action) + * @return boolean + * @access public + * @static + */ + function matchNamed($param, $val, $rule, $context = array()) { + if ($rule === true || $rule === false) { + return $rule; + } + if (is_string($rule)) { + $rule = array('match' => $rule); + } + if (!is_array($rule)) { + return false; + } + + $controllerMatches = !isset($rule['controller'], $context['controller']) || in_array($context['controller'], (array)$rule['controller']); + if (!$controllerMatches) { + return false; + } + $actionMatches = !isset($rule['action'], $context['action']) || in_array($context['action'], (array)$rule['action']); + if (!$actionMatches) { + return false; + } + return (!isset($rule['match']) || preg_match('/' . $rule['match'] . '/', $val)); + } + +/** + * Generates a well-formed querystring from $q + * + * @param mixed $q Query string + * @param array $extra Extra querystring parameters. + * @param bool $escape Whether or not to use escaped & + * @return array + * @access public + * @static + */ + function queryString($q, $extra = array(), $escape = false) { + if (empty($q) && empty($extra)) { + return null; + } + $join = '&'; + if ($escape === true) { + $join = '&'; + } + $out = ''; + + if (is_array($q)) { + $q = array_merge($extra, $q); + } else { + $out = $q; + $q = $extra; + } + $out .= http_build_query($q, null, $join); + if (isset($out[0]) && $out[0] != '?') { + $out = '?' . $out; + } + return $out; + } + +/** + * Reverses a parsed parameter array into a string. Works similarly to Router::url(), but + * Since parsed URL's contain additional 'pass' and 'named' as well as 'url.url' keys. + * Those keys need to be specially handled in order to reverse a params array into a string url. + * + * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those + * are used for CakePHP internals and should not normally be part of an output url. + * + * @param array $param The params array that needs to be reversed. + * @return string The string that is the reversed result of the array + * @access public + * @static + */ + function reverse($params) { + $pass = $params['pass']; + $named = $params['named']; + $url = $params['url']; + unset( + $params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'], + $params['autoRender'], $params['bare'], $params['requested'], $params['return'] + ); + $params = array_merge($params, $pass, $named); + if (!empty($url)) { + $params['?'] = $url; + } + return Router::url($params); + } + +/** + * Normalizes a URL for purposes of comparison. Will strip the base path off + * and replace any double /'s. It will not unify the casing and underscoring + * of the input value. + * + * @param mixed $url URL to normalize Either an array or a string url. + * @return string Normalized URL + * @access public + * @static + */ + function normalize($url = '/') { + if (is_array($url)) { + $url = Router::url($url); + } elseif (preg_match('/^[a-z\-]+:\/\//', $url)) { + return $url; + } + $paths = Router::getPaths(); + + if (!empty($paths['base']) && stristr($url, $paths['base'])) { + $url = preg_replace('/^' . preg_quote($paths['base'], '/') . '/', '', $url, 1); + } + $url = '/' . $url; + + while (strpos($url, '//') !== false) { + $url = str_replace('//', '/', $url); + } + $url = preg_replace('/(?:(\/$))/', '', $url); + + if (empty($url)) { + return '/'; + } + return $url; + } + +/** + * Returns the route matching the current request URL. + * + * @return CakeRoute Matching route object. + * @access public + * @static + */ + function &requestRoute() { + $self =& Router::getInstance(); + return $self->__currentRoute[0]; + } + +/** + * Returns the route matching the current request (useful for requestAction traces) + * + * @return CakeRoute Matching route object. + * @access public + * @static + */ + function ¤tRoute() { + $self =& Router::getInstance(); + return $self->__currentRoute[count($self->__currentRoute) - 1]; + } + +/** + * Removes the plugin name from the base URL. + * + * @param string $base Base URL + * @param string $plugin Plugin name + * @return base url with plugin name removed if present + * @access public + * @static + */ + function stripPlugin($base, $plugin = null) { + if ($plugin != null) { + $base = preg_replace('/(?:' . $plugin . ')/', '', $base); + $base = str_replace('//', '', $base); + $pos1 = strrpos($base, '/'); + $char = strlen($base) - 1; + + if ($pos1 === $char) { + $base = substr($base, 0, $char); + } + } + return $base; + } + +/** + * Instructs the router to parse out file extensions from the URL. For example, + * http://example.com/posts.rss would yield an file extension of "rss". + * The file extension itself is made available in the controller as + * $this->params['url']['ext'], and is used by the RequestHandler component to + * automatically switch to alternate layouts and templates, and load helpers + * corresponding to the given content, i.e. RssHelper. + * + * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml'); + * If no parameters are given, anything after the first . (dot) after the last / in the URL will be + * parsed, excluding querystring parameters (i.e. ?q=...). + * + * @access public + * @return void + * @static + */ + function parseExtensions() { + $self =& Router::getInstance(); + $self->__parseExtensions = true; + if (func_num_args() > 0) { + $self->__validExtensions = func_get_args(); + } + } + +/** + * Takes a passed params and converts it to args + * + * @param array $params + * @return array Array containing passed and named parameters + * @access public + * @static + */ + function getArgs($args, $options = array()) { + $self =& Router::getInstance(); + $pass = $named = array(); + $args = explode('/', $args); + + $greedy = isset($options['greedy']) ? $options['greedy'] : $self->named['greedy']; + $context = array(); + if (isset($options['context'])) { + $context = $options['context']; + } + $rules = $self->named['rules']; + if (isset($options['named'])) { + $greedy = isset($options['greedy']) && $options['greedy'] === true; + foreach ((array)$options['named'] as $key => $val) { + if (is_numeric($key)) { + $rules[$val] = true; + continue; + } + $rules[$key] = $val; + } + } + + foreach ($args as $param) { + if (empty($param) && $param !== '0' && $param !== 0) { + continue; + } + + $separatorIsPresent = strpos($param, $self->named['separator']) !== false; + if ((!isset($options['named']) || !empty($options['named'])) && $separatorIsPresent) { + list($key, $val) = explode($self->named['separator'], $param, 2); + $hasRule = isset($rules[$key]); + $passIt = (!$hasRule && !$greedy) || ($hasRule && !$self->matchNamed($key, $val, $rules[$key], $context)); + if ($passIt) { + $pass[] = $param; + } else { + $named[$key] = $val; + } + } else { + $pass[] = $param; + } + } + return compact('pass', 'named'); + } +} + +/** + * A single Route used by the Router to connect requests to + * parameter maps. + * + * Not normally created as a standalone. Use Router::connect() to create + * Routes for your application. + * + * @package cake.libs + * @since 1.3.0 + * @see Router::connect() + */ +class CakeRoute { + +/** + * An array of named segments in a Route. + * `/:controller/:action/:id` has 3 key elements + * + * @var array + * @access public + */ + var $keys = array(); + +/** + * An array of additional parameters for the Route. + * + * @var array + * @access public + */ + var $options = array(); + +/** + * Default parameters for a Route + * + * @var array + * @access public + */ + var $defaults = array(); + +/** + * The routes template string. + * + * @var string + * @access public + */ + var $template = null; + +/** + * Is this route a greedy route? Greedy routes have a `/*` in their + * template + * + * @var string + * @access protected + */ + var $_greedy = false; + +/** + * The compiled route regular expresssion + * + * @var string + * @access protected + */ + var $_compiledRoute = null; + +/** + * HTTP header shortcut map. Used for evaluating header-based route expressions. + * + * @var array + * @access private + */ + var $__headerMap = array( + 'type' => 'content_type', + 'method' => 'request_method', + 'server' => 'server_name' + ); + +/** + * Constructor for a Route + * + * @param string $template Template string with parameter placeholders + * @param array $defaults Array of defaults for the route. + * @param string $params Array of parameters and additional options for the Route + * @return void + * @access public + */ + function CakeRoute($template, $defaults = array(), $options = array()) { + $this->template = $template; + $this->defaults = (array)$defaults; + $this->options = (array)$options; + } + +/** + * Check if a Route has been compiled into a regular expression. + * + * @return boolean + * @access public + */ + function compiled() { + return !empty($this->_compiledRoute); + } + +/** + * Compiles the route's regular expression. Modifies defaults property so all necessary keys are set + * and populates $this->names with the named routing elements. + * + * @return array Returns a string regular expression of the compiled route. + * @access public + */ + function compile() { + if ($this->compiled()) { + return $this->_compiledRoute; + } + $this->_writeRoute(); + return $this->_compiledRoute; + } + +/** + * Builds a route regular expression. Uses the template, defaults and options + * properties to compile a regular expression that can be used to parse request strings. + * + * @return void + * @access protected + */ + function _writeRoute() { + if (empty($this->template) || ($this->template === '/')) { + $this->_compiledRoute = '#^/*$#'; + $this->keys = array(); + return; + } + $route = $this->template; + $names = $routeParams = array(); + $parsed = preg_quote($this->template, '#'); + $parsed = str_replace('\\-', '-', $parsed); + + preg_match_all('#:([A-Za-z0-9_-]+[A-Z0-9a-z])#', $parsed, $namedElements); + foreach ($namedElements[1] as $i => $name) { + $search = '\\' . $namedElements[0][$i]; + if (isset($this->options[$name])) { + $option = null; + if ($name !== 'plugin' && array_key_exists($name, $this->defaults)) { + $option = '?'; + } + $slashParam = '/\\' . $namedElements[0][$i]; + if (strpos($parsed, $slashParam) !== false) { + $routeParams[$slashParam] = '(?:/(' . $this->options[$name] . ')' . $option . ')' . $option; + } else { + $routeParams[$search] = '(?:(' . $this->options[$name] . ')' . $option . ')' . $option; + } + } else { + $routeParams[$search] = '(?:([^/]+))'; + } + $names[] = $name; + } + if (preg_match('#\/\*$#', $route, $m)) { + $parsed = preg_replace('#/\\\\\*$#', '(?:/(?P<_args_>.*))?', $parsed); + $this->_greedy = true; + } + krsort($routeParams); + $parsed = str_replace(array_keys($routeParams), array_values($routeParams), $parsed); + $this->_compiledRoute = '#^' . $parsed . '[/]*$#'; + $this->keys = $names; + } + +/** + * Checks to see if the given URL can be parsed by this route. + * If the route can be parsed an array of parameters will be returned; if not, + * false will be returned. String urls are parsed if they match a routes regular expression. + * + * @param string $url The url to attempt to parse. + * @return mixed Boolean false on failure, otherwise an array or parameters + * @access public + */ + function parse($url) { + if (!$this->compiled()) { + $this->compile(); + } + if (!preg_match($this->_compiledRoute, $url, $parsed)) { + return false; + } else { + foreach ($this->defaults as $key => $val) { + if ($key[0] === '[' && preg_match('/^\[(\w+)\]$/', $key, $header)) { + if (isset($this->__headerMap[$header[1]])) { + $header = $this->__headerMap[$header[1]]; + } else { + $header = 'http_' . $header[1]; + } + + $val = (array)$val; + $h = false; + + foreach ($val as $v) { + if (env(strtoupper($header)) === $v) { + $h = true; + } + } + if (!$h) { + return false; + } + } + } + array_shift($parsed); + $route = array(); + foreach ($this->keys as $i => $key) { + if (isset($parsed[$i])) { + $route[$key] = $parsed[$i]; + } + } + $route['pass'] = $route['named'] = array(); + $route += $this->defaults; + if (isset($parsed['_args_'])) { + $route['_args_'] = $parsed['_args_']; + } + foreach ($route as $key => $value) { + if (is_integer($key)) { + $route['pass'][] = $value; + unset($route[$key]); + } + } + return $route; + } + } + +/** + * Apply persistent parameters to a url array. Persistant parameters are a special + * key used during route creation to force route parameters to persist when omitted from + * a url array. + * + * @param array $url The array to apply persistent parameters to. + * @param array $params An array of persistent values to replace persistent ones. + * @return array An array with persistent parameters applied. + * @access public + */ + function persistParams($url, $params) { + foreach ($this->options['persist'] as $persistKey) { + if (array_key_exists($persistKey, $params) && !isset($url[$persistKey])) { + $url[$persistKey] = $params[$persistKey]; + } + } + return $url; + } + +/** + * Attempt to match a url array. If the url matches the route parameters and settings, then + * return a generated string url. If the url doesn't match the route parameters, false will be returned. + * This method handles the reverse routing or conversion of url arrays into string urls. + * + * @param array $url An array of parameters to check matching with. + * @return mixed Either a string url for the parameters if they match or false. + * @access public + */ + function match($url) { + if (!$this->compiled()) { + $this->compile(); + } + $defaults = $this->defaults; + + if (isset($defaults['prefix'])) { + $url['prefix'] = $defaults['prefix']; + } + + //check that all the key names are in the url + $keyNames = array_flip($this->keys); + if (array_intersect_key($keyNames, $url) != $keyNames) { + return false; + } + + $diffUnfiltered = Set::diff($url, $defaults); + $diff = array(); + + foreach ($diffUnfiltered as $key => $var) { + if ($var === 0 || $var === '0' || !empty($var)) { + $diff[$key] = $var; + } + } + + //if a not a greedy route, no extra params are allowed. + if (!$this->_greedy && array_diff_key($diff, $keyNames) != array()) { + return false; + } + + //remove defaults that are also keys. They can cause match failures + foreach ($this->keys as $key) { + unset($defaults[$key]); + } + $filteredDefaults = array_filter($defaults); + + //if the difference between the url diff and defaults contains keys from defaults its not a match + if (array_intersect_key($filteredDefaults, $diffUnfiltered) !== array()) { + return false; + } + + $passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames); + list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']); + + //remove any pass params, they have numeric indexes, skip any params that are in the defaults + $pass = array(); + $i = 0; + while (isset($url[$i])) { + if (!isset($diff[$i])) { + $i++; + continue; + } + $pass[] = $url[$i]; + unset($url[$i], $params[$i]); + $i++; + } + + //still some left over parameters that weren't named or passed args, bail. + if (!empty($params)) { + return false; + } + + //check patterns for routed params + if (!empty($this->options)) { + foreach ($this->options as $key => $pattern) { + if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) { + return false; + } + } + } + return $this->_writeUrl(array_merge($url, compact('pass', 'named'))); + } + +/** + * Converts a matching route array into a url string. Composes the string url using the template + * used to create the route. + * + * @param array $params The params to convert to a string url. + * @return string Composed route string. + * @access protected + */ + function _writeUrl($params) { + if (isset($params['prefix'], $params['action'])) { + $params['action'] = str_replace($params['prefix'] . '_', '', $params['action']); + unset($params['prefix']); + } + + if (is_array($params['pass'])) { + $params['pass'] = implode('/', $params['pass']); + } + + $instance =& Router::getInstance(); + $separator = $instance->named['separator']; + + if (!empty($params['named']) && is_array($params['named'])) { + $named = array(); + foreach ($params['named'] as $key => $value) { + $named[] = $key . $separator . $value; + } + $params['pass'] = $params['pass'] . '/' . implode('/', $named); + } + $out = $this->template; + + $search = $replace = array(); + foreach ($this->keys as $key) { + $string = null; + if (isset($params[$key])) { + $string = $params[$key]; + } elseif (strpos($out, $key) != strlen($out) - strlen($key)) { + $key .= '/'; + } + $search[] = ':' . $key; + $replace[] = $string; + } + $out = str_replace($search, $replace, $out); + + if (strpos($this->template, '*')) { + $out = str_replace('*', $params['pass'], $out); + } + $out = str_replace('//', '/', $out); + return $out; + } +} + +/** + * Plugin short route, that copies the plugin param to the controller parameters + * It is used for supporting /:plugin routes. + * + * @package cake.libs + */ +class PluginShortRoute extends CakeRoute { + +/** + * Parses a string url into an array. If a plugin key is found, it will be copied to the + * controller parameter + * + * @param string $url The url to parse + * @return mixed false on failure, or an array of request parameters + */ + function parse($url) { + $params = parent::parse($url); + if (!$params) { + return false; + } + $params['controller'] = $params['plugin']; + return $params; + } + +/** + * Reverse route plugin shortcut urls. If the plugin and controller + * are not the same the match is an auto fail. + * + * @param array $url Array of parameters to convert to a string. + * @return mixed either false or a string url. + */ + function match($url) { + if (isset($url['controller']) && isset($url['plugin']) && $url['plugin'] != $url['controller']) { + return false; + } + $this->defaults['controller'] = $url['controller']; + $result = parent::match($url); + unset($this->defaults['controller']); + return $result; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/sanitize.php b/code/ryzom/tools/server/www/webtt/cake/libs/sanitize.php new file mode 100644 index 000000000..3878ac512 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/sanitize.php @@ -0,0 +1,348 @@ + $clean) { + $cleaned[$key] = preg_replace("/[^{$allow}a-zA-Z0-9]/", '', $clean); + } + } else { + $cleaned = preg_replace("/[^{$allow}a-zA-Z0-9]/", '', $string); + } + return $cleaned; + } + +/** + * Makes a string SQL-safe. + * + * @param string $string String to sanitize + * @param string $connection Database connection being used + * @return string SQL safe string + * @access public + * @static + */ + function escape($string, $connection = 'default') { + $db =& ConnectionManager::getDataSource($connection); + if (is_numeric($string) || $string === null || is_bool($string)) { + return $string; + } + $string = substr($db->value($string), 1); + $string = substr($string, 0, -1); + return $string; + } + +/** + * Returns given string safe for display as HTML. Renders entities. + * + * strip_tags() does not validating HTML syntax or structure, so it might strip whole passages + * with broken HTML. + * + * ### Options: + * + * - remove (boolean) if true strips all HTML tags before encoding + * - charset (string) the charset used to encode the string + * - quotes (int) see http://php.net/manual/en/function.htmlentities.php + * + * @param string $string String from where to strip tags + * @param array $options Array of options to use. + * @return string Sanitized string + * @access public + * @static + */ + function html($string, $options = array()) { + static $defaultCharset = false; + if ($defaultCharset === false) { + $defaultCharset = Configure::read('App.encoding'); + if ($defaultCharset === null) { + $defaultCharset = 'UTF-8'; + } + } + $default = array( + 'remove' => false, + 'charset' => $defaultCharset, + 'quotes' => ENT_QUOTES + ); + + $options = array_merge($default, $options); + + if ($options['remove']) { + $string = strip_tags($string); + } + + return htmlentities($string, $options['quotes'], $options['charset']); + } + +/** + * Strips extra whitespace from output + * + * @param string $str String to sanitize + * @return string whitespace sanitized string + * @access public + * @static + */ + function stripWhitespace($str) { + $r = preg_replace('/[\n\r\t]+/', '', $str); + return preg_replace('/\s{2,}/', ' ', $r); + } + +/** + * Strips image tags from output + * + * @param string $str String to sanitize + * @return string Sting with images stripped. + * @access public + * @static + */ + function stripImages($str) { + $str = preg_replace('/(]*>)(]+alt=")([^"]*)("[^>]*>)(<\/a>)/i', '$1$3$5
', $str); + $str = preg_replace('/(]+alt=")([^"]*)("[^>]*>)/i', '$2
', $str); + $str = preg_replace('/]*>/i', '', $str); + return $str; + } + +/** + * Strips scripts and stylesheets from output + * + * @param string $str String to sanitize + * @return string String with ', + 'javascriptstart' => '', + 'javascriptend' => '' + ); + +/** + * Breadcrumbs. + * + * @var array + * @access protected + */ + var $_crumbs = array(); + +/** + * Names of script files that have been included once + * + * @var array + * @access private + */ + var $__includedScripts = array(); +/** + * Options for the currently opened script block buffer if any. + * + * @var array + * @access protected + */ + var $_scriptBlockOptions = array(); +/** + * Document type definitions + * + * @var array + * @access private + */ + var $__docTypes = array( + 'html4-strict' => '', + 'html4-trans' => '', + 'html4-frame' => '', + 'xhtml-strict' => '', + 'xhtml-trans' => '', + 'xhtml-frame' => '', + 'xhtml11' => '' + ); + +/** + * Adds a link to the breadcrumbs array. + * + * @param string $name Text for link + * @param string $link URL for link (if empty it won't be a link) + * @param mixed $options Link attributes e.g. array('id'=>'selected') + * @return void + * @see HtmlHelper::link() for details on $options that can be used. + * @access public + */ + function addCrumb($name, $link = null, $options = null) { + $this->_crumbs[] = array($name, $link, $options); + } + +/** + * Returns a doctype string. + * + * Possible doctypes: + * + * - html4-strict: HTML4 Strict. + * - html4-trans: HTML4 Transitional. + * - html4-frame: HTML4 Frameset. + * - xhtml-strict: XHTML1 Strict. + * - xhtml-trans: XHTML1 Transitional. + * - xhtml-frame: XHTML1 Frameset. + * - xhtml11: XHTML1.1. + * + * @param string $type Doctype to use. + * @return string Doctype string + * @access public + * @link http://book.cakephp.org/view/1439/docType + */ + function docType($type = 'xhtml-strict') { + if (isset($this->__docTypes[$type])) { + return $this->__docTypes[$type]; + } + return null; + } + +/** + * Creates a link to an external resource and handles basic meta tags + * + * ### Options + * + * - `inline` Whether or not the link element should be output inline, or in scripts_for_layout. + * + * @param string $type The title of the external resource + * @param mixed $url The address of the external resource or string for content attribute + * @param array $options Other attributes for the generated tag. If the type attribute is html, + * rss, atom, or icon, the mime-type is returned. + * @return string A completed `` element. + * @access public + * @link http://book.cakephp.org/view/1438/meta + */ + function meta($type, $url = null, $options = array()) { + $inline = isset($options['inline']) ? $options['inline'] : true; + unset($options['inline']); + + if (!is_array($type)) { + $types = array( + 'rss' => array('type' => 'application/rss+xml', 'rel' => 'alternate', 'title' => $type, 'link' => $url), + 'atom' => array('type' => 'application/atom+xml', 'title' => $type, 'link' => $url), + 'icon' => array('type' => 'image/x-icon', 'rel' => 'icon', 'link' => $url), + 'keywords' => array('name' => 'keywords', 'content' => $url), + 'description' => array('name' => 'description', 'content' => $url), + ); + + if ($type === 'icon' && $url === null) { + $types['icon']['link'] = $this->webroot('favicon.ico'); + } + + if (isset($types[$type])) { + $type = $types[$type]; + } elseif (!isset($options['type']) && $url !== null) { + if (is_array($url) && isset($url['ext'])) { + $type = $types[$url['ext']]; + } else { + $type = $types['rss']; + } + } elseif (isset($options['type']) && isset($types[$options['type']])) { + $type = $types[$options['type']]; + unset($options['type']); + } else { + $type = array(); + } + } elseif ($url !== null) { + $inline = $url; + } + $options = array_merge($type, $options); + $out = null; + + if (isset($options['link'])) { + if (isset($options['rel']) && $options['rel'] === 'icon') { + $out = sprintf($this->tags['metalink'], $options['link'], $this->_parseAttributes($options, array('link'), ' ', ' ')); + $options['rel'] = 'shortcut icon'; + } else { + $options['link'] = $this->url($options['link'], true); + } + $out .= sprintf($this->tags['metalink'], $options['link'], $this->_parseAttributes($options, array('link'), ' ', ' ')); + } else { + $out = sprintf($this->tags['meta'], $this->_parseAttributes($options, array('type'), ' ', ' ')); + } + + if ($inline) { + return $out; + } else { + $view =& ClassRegistry::getObject('view'); + $view->addScript($out); + } + } + +/** + * Returns a charset META-tag. + * + * @param string $charset The character set to be used in the meta tag. If empty, + * The App.encoding value will be used. Example: "utf-8". + * @return string A meta tag containing the specified character set. + * @access public + * @link http://book.cakephp.org/view/1436/charset + */ + function charset($charset = null) { + if (empty($charset)) { + $charset = strtolower(Configure::read('App.encoding')); + } + return sprintf($this->tags['charset'], (!empty($charset) ? $charset : 'utf-8')); + } + +/** + * Creates an HTML link. + * + * If $url starts with "http://" this is treated as an external link. Else, + * it is treated as a path to controller/action and parsed with the + * HtmlHelper::url() method. + * + * If the $url is empty, $title is used instead. + * + * ### Options + * + * - `escape` Set to false to disable escaping of title and attributes. + * + * @param string $title The content to be wrapped by tags. + * @param mixed $url Cake-relative URL or array of URL parameters, or external URL (starts with http://) + * @param array $options Array of HTML attributes. + * @param string $confirmMessage JavaScript confirmation message. + * @return string An `` element. + * @access public + * @link http://book.cakephp.org/view/1442/link + */ + function link($title, $url = null, $options = array(), $confirmMessage = false) { + $escapeTitle = true; + if ($url !== null) { + $url = $this->url($url); + } else { + $url = $this->url($title); + $title = $url; + $escapeTitle = false; + } + + if (isset($options['escape'])) { + $escapeTitle = $options['escape']; + } + + if ($escapeTitle === true) { + $title = h($title); + } elseif (is_string($escapeTitle)) { + $title = htmlentities($title, ENT_QUOTES, $escapeTitle); + } + + if (!empty($options['confirm'])) { + $confirmMessage = $options['confirm']; + unset($options['confirm']); + } + if ($confirmMessage) { + $confirmMessage = str_replace("'", "\'", $confirmMessage); + $confirmMessage = str_replace('"', '\"', $confirmMessage); + $options['onclick'] = "return confirm('{$confirmMessage}');"; + } elseif (isset($options['default']) && $options['default'] == false) { + if (isset($options['onclick'])) { + $options['onclick'] .= ' event.returnValue = false; return false;'; + } else { + $options['onclick'] = 'event.returnValue = false; return false;'; + } + unset($options['default']); + } + return sprintf($this->tags['link'], $url, $this->_parseAttributes($options), $title); + } + +/** + * Creates a link element for CSS stylesheets. + * + * ### Options + * + * - `inline` If set to false, the generated tag appears in the head tag of the layout. Defaults to true + * + * @param mixed $path The name of a CSS style sheet or an array containing names of + * CSS stylesheets. If `$path` is prefixed with '/', the path will be relative to the webroot + * of your application. Otherwise, the path will be relative to your CSS path, usually webroot/css. + * @param string $rel Rel attribute. Defaults to "stylesheet". If equal to 'import' the stylesheet will be imported. + * @param array $options Array of HTML attributes. + * @return string CSS or + + +

+ + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/js/default.ctp b/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/js/default.ctp new file mode 100644 index 000000000..d94dc903a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/js/default.ctp @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/rss/default.ctp b/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/rss/default.ctp new file mode 100644 index 000000000..70dcb6ff4 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/rss/default.ctp @@ -0,0 +1,16 @@ +Rss->header(); + +if (!isset($channel)) { + $channel = array(); +} +if (!isset($channel['title'])) { + $channel['title'] = $title_for_layout; +} + +echo $this->Rss->document( + $this->Rss->channel( + array(), $channel, $content_for_layout + ) +); +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/xml/default.ctp b/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/xml/default.ctp new file mode 100644 index 000000000..07f603d3a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/layouts/xml/default.ctp @@ -0,0 +1,2 @@ +Xml->header(); ?> + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/media.php b/code/ryzom/tools/server/www/webtt/cake/libs/view/media.php new file mode 100644 index 000000000..c8bb613a7 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/media.php @@ -0,0 +1,285 @@ + 'application/postscript', 'bcpio' => 'application/x-bcpio', 'bin' => 'application/octet-stream', + 'ccad' => 'application/clariscad', 'cdf' => 'application/x-netcdf', 'class' => 'application/octet-stream', + 'cpio' => 'application/x-cpio', 'cpt' => 'application/mac-compactpro', 'csh' => 'application/x-csh', + 'csv' => 'application/csv', 'dcr' => 'application/x-director', 'dir' => 'application/x-director', + 'dms' => 'application/octet-stream', 'doc' => 'application/msword', 'drw' => 'application/drafting', + 'dvi' => 'application/x-dvi', 'dwg' => 'application/acad', 'dxf' => 'application/dxf', + 'dxr' => 'application/x-director', 'eot' => 'application/vnd.ms-fontobject', 'eps' => 'application/postscript', + 'exe' => 'application/octet-stream', 'ez' => 'application/andrew-inset', + 'flv' => 'video/x-flv', 'gtar' => 'application/x-gtar', 'gz' => 'application/x-gzip', + 'bz2' => 'application/x-bzip', '7z' => 'application/x-7z-compressed', 'hdf' => 'application/x-hdf', + 'hqx' => 'application/mac-binhex40', 'ico' => 'image/vnd.microsoft.icon', 'ips' => 'application/x-ipscript', + 'ipx' => 'application/x-ipix', 'js' => 'application/x-javascript', 'latex' => 'application/x-latex', + 'lha' => 'application/octet-stream', 'lsp' => 'application/x-lisp', 'lzh' => 'application/octet-stream', + 'man' => 'application/x-troff-man', 'me' => 'application/x-troff-me', 'mif' => 'application/vnd.mif', + 'ms' => 'application/x-troff-ms', 'nc' => 'application/x-netcdf', 'oda' => 'application/oda', + 'otf' => 'font/otf', 'pdf' => 'application/pdf', + 'pgn' => 'application/x-chess-pgn', 'pot' => 'application/mspowerpoint', 'pps' => 'application/mspowerpoint', + 'ppt' => 'application/mspowerpoint', 'ppz' => 'application/mspowerpoint', 'pre' => 'application/x-freelance', + 'prt' => 'application/pro_eng', 'ps' => 'application/postscript', 'roff' => 'application/x-troff', + 'scm' => 'application/x-lotusscreencam', 'set' => 'application/set', 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', 'sit' => 'application/x-stuffit', 'skd' => 'application/x-koan', + 'skm' => 'application/x-koan', 'skp' => 'application/x-koan', 'skt' => 'application/x-koan', + 'smi' => 'application/smil', 'smil' => 'application/smil', 'sol' => 'application/solids', + 'spl' => 'application/x-futuresplash', 'src' => 'application/x-wais-source', 'step' => 'application/STEP', + 'stl' => 'application/SLA', 'stp' => 'application/STEP', 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', 't' => 'application/x-troff', + 'tar' => 'application/x-tar', 'tcl' => 'application/x-tcl', 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', 'texinfo' => 'application/x-texinfo', 'tr' => 'application/x-troff', + 'tsp' => 'application/dsptype', 'ttf' => 'font/ttf', + 'unv' => 'application/i-deas', 'ustar' => 'application/x-ustar', + 'vcd' => 'application/x-cdlink', 'vda' => 'application/vda', 'xlc' => 'application/vnd.ms-excel', + 'xll' => 'application/vnd.ms-excel', 'xlm' => 'application/vnd.ms-excel', 'xls' => 'application/vnd.ms-excel', + 'xlw' => 'application/vnd.ms-excel', 'zip' => 'application/zip', 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', 'au' => 'audio/basic', 'kar' => 'audio/midi', 'mid' => 'audio/midi', + 'midi' => 'audio/midi', 'mp2' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'mpga' => 'audio/mpeg', + 'ra' => 'audio/x-realaudio', 'ram' => 'audio/x-pn-realaudio', 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', 'snd' => 'audio/basic', 'tsi' => 'audio/TSP-audio', 'wav' => 'audio/x-wav', + 'asc' => 'text/plain', 'c' => 'text/plain', 'cc' => 'text/plain', 'css' => 'text/css', 'etx' => 'text/x-setext', + 'f' => 'text/plain', 'f90' => 'text/plain', 'h' => 'text/plain', 'hh' => 'text/plain', 'htm' => 'text/html', + 'html' => 'text/html', 'm' => 'text/plain', 'rtf' => 'text/rtf', 'rtx' => 'text/richtext', 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', 'tsv' => 'text/tab-separated-values', 'tpl' => 'text/template', 'txt' => 'text/plain', + 'xml' => 'text/xml', 'avi' => 'video/x-msvideo', 'fli' => 'video/x-fli', 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', + 'qt' => 'video/quicktime', 'viv' => 'video/vnd.vivo', 'vivo' => 'video/vnd.vivo', 'gif' => 'image/gif', + 'ief' => 'image/ief', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', + 'pbm' => 'image/x-portable-bitmap', 'pgm' => 'image/x-portable-graymap', 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', 'ppm' => 'image/x-portable-pixmap', 'ras' => 'image/cmu-raster', + 'rgb' => 'image/x-rgb', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'xbm' => 'image/x-xbitmap', + 'xpm' => 'image/x-xpixmap', 'xwd' => 'image/x-xwindowdump', 'ice' => 'x-conference/x-cooltalk', + 'iges' => 'model/iges', 'igs' => 'model/iges', 'mesh' => 'model/mesh', 'msh' => 'model/mesh', + 'silo' => 'model/mesh', 'vrml' => 'model/vrml', 'wrl' => 'model/vrml', + 'mime' => 'www/mime', 'pdb' => 'chemical/x-pdb', 'xyz' => 'chemical/x-pdb'); + +/** + * Holds headers sent to browser before rendering media + * + * @var array + * @access protected + */ + var $_headers = array(); + +/** + * Constructor + * + * @param object $controller + */ + function __construct(&$controller) { + parent::__construct($controller); + } + +/** + * Display or download the given file + * + * @return unknown + */ + function render() { + $name = $download = $extension = $id = $modified = $path = $size = $cache = $mimeType = null; + extract($this->viewVars, EXTR_OVERWRITE); + + if ($size) { + $id = $id . '_' . $size; + } + + if (is_dir($path)) { + $path = $path . $id; + } else { + $path = APP . $path . $id; + } + + if (!file_exists($path)) { + header('Content-Type: text/html'); + $this->cakeError('error404'); + } + + if (is_null($name)) { + $name = $id; + } + + if (is_array($mimeType)) { + $this->mimeType = array_merge($this->mimeType, $mimeType); + } + + if (isset($extension) && isset($this->mimeType[$extension]) && connection_status() == 0) { + $chunkSize = 8192; + $buffer = ''; + $fileSize = @filesize($path); + $handle = fopen($path, 'rb'); + + if ($handle === false) { + return false; + } + if (!empty($modified)) { + $modified = gmdate('D, d M Y H:i:s', strtotime($modified, time())) . ' GMT'; + } else { + $modified = gmdate('D, d M Y H:i:s') . ' GMT'; + } + + if ($download) { + $contentTypes = array('application/octet-stream'); + $agent = env('HTTP_USER_AGENT'); + + if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) { + $contentTypes[0] = 'application/octetstream'; + } else if (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { + $contentTypes[0] = 'application/force-download'; + array_merge($contentTypes, array( + 'application/octet-stream', + 'application/download' + )); + } + foreach($contentTypes as $contentType) { + $this->_header('Content-Type: ' . $contentType); + } + $this->_header(array( + 'Content-Disposition: attachment; filename="' . $name . '.' . $extension . '";', + 'Expires: 0', + 'Accept-Ranges: bytes', + 'Cache-Control: private' => false, + 'Pragma: private')); + + $httpRange = env('HTTP_RANGE'); + if (isset($httpRange)) { + list($toss, $range) = explode('=', $httpRange); + + $size = $fileSize - 1; + $length = $fileSize - $range; + + $this->_header(array( + 'HTTP/1.1 206 Partial Content', + 'Content-Length: ' . $length, + 'Content-Range: bytes ' . $range . $size . '/' . $fileSize)); + + fseek($handle, $range); + } else { + $this->_header('Content-Length: ' . $fileSize); + } + } else { + $this->_header('Date: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); + if ($cache) { + if (!is_numeric($cache)) { + $cache = strtotime($cache) - time(); + } + $this->_header(array( + 'Cache-Control: max-age=' . $cache, + 'Expires: ' . gmdate('D, d M Y H:i:s', time() + $cache) . ' GMT', + 'Pragma: cache')); + } else { + $this->_header(array( + 'Cache-Control: must-revalidate, post-check=0, pre-check=0', + 'Pragma: no-cache')); + } + $this->_header(array( + 'Last-Modified: ' . $modified, + 'Content-Type: ' . $this->mimeType[$extension], + 'Content-Length: ' . $fileSize)); + } + $this->_output(); + $this->_clearBuffer(); + + while (!feof($handle)) { + if (!$this->_isActive()) { + fclose($handle); + return false; + } + set_time_limit(0); + $buffer = fread($handle, $chunkSize); + echo $buffer; + $this->_flushBuffer(); + } + fclose($handle); + return; + } + return false; + } + +/** + * Method to set headers + * @param mixed $header + * @param boolean $boolean + * @access protected + */ + function _header($header, $boolean = true) { + if (is_array($header)) { + foreach ($header as $string => $boolean) { + if (is_numeric($string)) { + $this->_headers[] = array($boolean => true); + } else { + $this->_headers[] = array($string => $boolean); + } + } + return; + } + $this->_headers[] = array($header => $boolean); + return; + } + +/** + * Method to output headers + * @access protected + */ + function _output() { + foreach ($this->_headers as $key => $value) { + $header = key($value); + header($header, $value[$header]); + } + } + +/** + * Returns true if connection is still active + * @return boolean + * @access protected + */ + function _isActive() { + return connection_status() == 0 && !connection_aborted(); + } + +/** + * Clears the contents of the topmost output buffer and discards them + * @return boolean + * @access protected + */ + function _clearBuffer() { + return @ob_end_clean(); + } + +/** + * Flushes the contents of the output buffer + * @access protected + */ + function _flushBuffer() { + @flush(); + @ob_flush(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/pages/home.ctp b/code/ryzom/tools/server/www/webtt/cake/libs/view/pages/home.ctp new file mode 100644 index 000000000..a5ac078af --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/pages/home.ctp @@ -0,0 +1,170 @@ +cakeError('error404'); +endif; +?> +

+ + 0): + Debugger::checkSecurityKeys(); +endif; +?> +

+ '; + __('Your tmp directory is writable.'); + echo ''; + else: + echo ''; + __('Your tmp directory is NOT writable.'); + echo ''; + endif; + ?> +

+

+ '; + printf(__('The %s is being used for caching. To change the config edit APP/config/core.php ', true), ''. $settings['engine'] . 'Engine'); + echo ''; + else: + echo ''; + __('Your cache is NOT working. Please check the settings in APP/config/core.php'); + echo ''; + endif; + ?> +

+

+ '; + __('Your database configuration file is present.'); + $filePresent = true; + echo ''; + else: + echo ''; + __('Your database configuration file is NOT present.'); + echo '
'; + __('Rename config/database.php.default to config/database.php'); + echo '
'; + endif; + ?> +

+'; + __('PCRE has not been compiled with Unicode support.'); + echo '
'; + __('Recompile PCRE with Unicode support by adding --enable-unicode-properties when configuring'); + echo '

'; + } +?> +getDataSource('default'); +?> +

+ isConnected()): + echo ''; + __('Cake is able to connect to the database.'); + echo ''; + else: + echo ''; + __('Cake is NOT able to connect to the database.'); + echo ''; + endif; + ?> +

+ +

+

+ +To change its layout, create: APP/views/layouts/default.ctp.
+You can also add some CSS styles for your pages at: APP/webroot/css.'); +?> +

+ +

+

+ Html->link( + sprintf('%s %s', __('New', true), __('CakePHP 1.3 Docs', true)), + 'http://book.cakephp.org/view/875/x1-3-Collection', + array('target' => '_blank', 'escape' => false) + ); + ?> +

+

+ Html->link( + __('The 15 min Blog Tutorial', true), + 'http://book.cakephp.org/view/1528/Blog', + array('target' => '_blank', 'escape' => false) + ); + ?> +

+ +

+

+ +

+

+ +

+ + diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/edit.ctp b/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/edit.ctp new file mode 100644 index 000000000..34b570631 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/edit.ctp @@ -0,0 +1,47 @@ + +
+Form->create(); + echo $this->Form->inputs($scaffoldFields, array('created', 'modified', 'updated')); + echo $this->Form->end(__('Submit', true)); +?> +
+
+

+
    +action != 'add'):?> +
  • Html->link(__('Delete', true), array('action' => 'delete', $this->Form->value($modelClass.'.'.$primaryKey)), null, __('Are you sure you want to delete', true).' #' . $this->Form->value($modelClass.'.'.$primaryKey)); ?>
  • + +
  • Html->link(__('List', true).' '.$pluralHumanName, array('action' => 'index'));?>
  • + $_data) { + foreach ($_data as $_alias => $_details) { + if ($_details['controller'] != $this->name && !in_array($_details['controller'], $done)) { + echo "\t\t
  • " . $this->Html->link(sprintf(__('List %s', true), Inflector::humanize($_details['controller'])), array('controller' => $_details['controller'], 'action' =>'index')) . "
  • \n"; + echo "\t\t
  • " . $this->Html->link(sprintf(__('New %s', true), Inflector::humanize(Inflector::underscore($_alias))), array('controller' => $_details['controller'], 'action' =>'add')) . "
  • \n"; + $done[] = $_details['controller']; + } + } + } +?> +
+
\ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/index.ctp b/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/index.ctp new file mode 100644 index 000000000..ce4a09a3d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/index.ctp @@ -0,0 +1,93 @@ + +
+

+ + + + + + + +\n"; + foreach ($scaffoldFields as $_field) { + $isKey = false; + if (!empty($associations['belongsTo'])) { + foreach ($associations['belongsTo'] as $_alias => $_details) { + if ($_field === $_details['foreignKey']) { + $isKey = true; + echo "\t\t\n"; + break; + } + } + } + if ($isKey !== true) { + echo "\t\t\n"; + } + } + + echo "\t\t\n"; + echo "\t\n"; + +endforeach; +echo "\n"; +?> +
Paginator->sort($_field);?>
\n\t\t\t" . $this->Html->link(${$singularVar}[$_alias][$_details['displayField']], array('controller' => $_details['controller'], 'action' => 'view', ${$singularVar}[$_alias][$_details['primaryKey']])) . "\n\t\t\n\t\t\t" . ${$singularVar}[$modelClass][$_field] . " \n\t\t\n"; + echo "\t\t\t" . $this->Html->link(__('View', true), array('action' => 'view', ${$singularVar}[$modelClass][$primaryKey])) . "\n"; + echo "\t\t\t" . $this->Html->link(__('Edit', true), array('action' => 'edit', ${$singularVar}[$modelClass][$primaryKey])) . "\n"; + echo "\t\t\t" . $this->Html->link(__('Delete', true), array('action' => 'delete', ${$singularVar}[$modelClass][$primaryKey]), null, __('Are you sure you want to delete', true).' #' . ${$singularVar}[$modelClass][$primaryKey]) . "\n"; + echo "\t\t
+

Paginator->counter(array( + 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true) + )); + ?>

+
+ Paginator->prev('<< ' . __('previous', true), array(), null, array('class' => 'disabled')) . "\n";?> + | Paginator->numbers() . "\n"?> + Paginator->next(__('next', true) .' >>', array(), null, array('class' => 'disabled')) . "\n";?> +
+
+
+

+
    +
  • Html->link(sprintf(__('New %s', true), $singularHumanName), array('action' => 'add')); ?>
  • + $_data) { + foreach ($_data as $_alias => $_details) { + if ($_details['controller'] != $this->name && !in_array($_details['controller'], $done)) { + echo "\t\t
  • " . $this->Html->link(sprintf(__('List %s', true), Inflector::humanize($_details['controller'])), array('controller' => $_details['controller'], 'action' => 'index')) . "
  • \n"; + echo "\t\t
  • " . $this->Html->link(sprintf(__('New %s', true), Inflector::humanize(Inflector::underscore($_alias))), array('controller' => $_details['controller'], 'action' => 'add')) . "
  • \n"; + $done[] = $_details['controller']; + } + } + } +?> +
+
\ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/view.ctp b/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/view.ctp new file mode 100644 index 000000000..ba14d3a6f --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/scaffolds/view.ctp @@ -0,0 +1,160 @@ + +
+

+
+ $_details) { + if ($_field === $_details['foreignKey']) { + $isKey = true; + echo "\t\t" . Inflector::humanize($_alias) . "\n"; + echo "\t\t\n\t\t\t" . $this->Html->link(${$singularVar}[$_alias][$_details['displayField']], array('controller' => $_details['controller'], 'action' => 'view', ${$singularVar}[$_alias][$_details['primaryKey']])) . "\n\t\t \n"; + break; + } + } + } + if ($isKey !== true) { + echo "\t\t" . Inflector::humanize($_field) . "\n"; + echo "\t\t\n\t\t\t{${$singularVar}[$modelClass][$_field]}\n \t\t\n"; + } +} +?> +
+
+
+

+
    +" .$this->Html->link(sprintf(__('Edit %s', true), $singularHumanName), array('action' => 'edit', ${$singularVar}[$modelClass][$primaryKey])). " \n"; + echo "\t\t
  • " .$this->Html->link(sprintf(__('Delete %s', true), $singularHumanName), array('action' => 'delete', ${$singularVar}[$modelClass][$primaryKey]), null, __('Are you sure you want to delete', true).' #' . ${$singularVar}[$modelClass][$primaryKey] . '?'). "
  • \n"; + echo "\t\t
  • " .$this->Html->link(sprintf(__('List %s', true), $pluralHumanName), array('action' => 'index')). "
  • \n"; + echo "\t\t
  • " .$this->Html->link(sprintf(__('New %s', true), $singularHumanName), array('action' => 'add')). "
  • \n"; + + $done = array(); + foreach ($associations as $_type => $_data) { + foreach ($_data as $_alias => $_details) { + if ($_details['controller'] != $this->name && !in_array($_details['controller'], $done)) { + echo "\t\t
  • " . $this->Html->link(sprintf(__('List %s', true), Inflector::humanize($_details['controller'])), array('controller' => $_details['controller'], 'action' => 'index')) . "
  • \n"; + echo "\t\t
  • " . $this->Html->link(sprintf(__('New %s', true), Inflector::humanize(Inflector::underscore($_alias))), array('controller' => $_details['controller'], 'action' => 'add')) . "
  • \n"; + $done[] = $_details['controller']; + } + } + } +?> +
+
+ $_details): ?> + + $_details): +$otherSingularVar = Inflector::variable($_alias); +?> + + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/theme.php b/code/ryzom/tools/server/www/webtt/cake/libs/view/theme.php new file mode 100644 index 000000000..22a06f050 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/theme.php @@ -0,0 +1,74 @@ +theme` and `$this->view = 'Theme'` + * in your Controller to use the ThemeView. + * + * Example of theme path with `$this->theme = 'super_hot';` Would be `app/views/themed/super_hot/posts` + * + * @package cake + * @subpackage cake.cake.libs.view + */ +class ThemeView extends View { +/** + * Constructor for ThemeView sets $this->theme. + * + * @param Controller $controller Controller object to be rendered. + * @param boolean $register Should the view be registered in the registry. + */ + function __construct(&$controller, $register = true) { + parent::__construct($controller, $register); + $this->theme =& $controller->theme; + } + +/** + * Return all possible paths to find view files in order + * + * @param string $plugin The name of the plugin views are being found for. + * @param boolean $cached Set to true to force dir scan. + * @return array paths + * @access protected + * @todo Make theme path building respect $cached parameter. + */ + function _paths($plugin = null, $cached = true) { + $paths = parent::_paths($plugin, $cached); + $themePaths = array(); + + if (!empty($this->theme)) { + $count = count($paths); + for ($i = 0; $i < $count; $i++) { + if (strpos($paths[$i], DS . 'plugins' . DS) === false + && strpos($paths[$i], DS . 'libs' . DS . 'view') === false) { + if ($plugin) { + $themePaths[] = $paths[$i] . 'themed'. DS . $this->theme . DS . 'plugins' . DS . $plugin . DS; + } + $themePaths[] = $paths[$i] . 'themed'. DS . $this->theme . DS; + } + } + $paths = array_merge($themePaths, $paths); + } + return $paths; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/view/view.php b/code/ryzom/tools/server/www/webtt/cake/libs/view/view.php new file mode 100644 index 000000000..c55d5db98 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/view/view.php @@ -0,0 +1,988 @@ + tags) for the layout + * + * @var array + * @access private + */ + var $__scripts = array(); + +/** + * Holds an array of paths. + * + * @var array + * @access private + */ + var $__paths = array(); + +/** + * Constructor + * + * @param Controller $controller A controller object to pull View::__passedArgs from. + * @param boolean $register Should the View instance be registered in the ClassRegistry + * @return View + */ + function __construct(&$controller, $register = true) { + if (is_object($controller)) { + $count = count($this->__passedVars); + for ($j = 0; $j < $count; $j++) { + $var = $this->__passedVars[$j]; + $this->{$var} = $controller->{$var}; + } + } + parent::__construct(); + + if ($register) { + ClassRegistry::addObject('view', $this); + } + } + +/** + * Renders a piece of PHP with provided parameters and returns HTML, XML, or any other string. + * + * This realizes the concept of Elements, (or "partial layouts") + * and the $params array is used to send data to be used in the + * Element. Elements can be cached through use of the cache key. + * + * ### Special params + * + * - `cache` - enable caching for this element accepts boolean or strtotime compatible string. + * Can also be an array. If `cache` is an array, + * `time` is used to specify duration of cache. + * `key` can be used to create unique cache files. + * - `plugin` - Load an element from a specific plugin. + * + * @param string $name Name of template file in the/app/views/elements/ folder + * @param array $params Array of data to be made available to the for rendered + * view (i.e. the Element) + * @return string Rendered Element + * @access public + */ + function element($name, $params = array(), $loadHelpers = false) { + $file = $plugin = $key = null; + + if (isset($params['plugin'])) { + $plugin = $params['plugin']; + } + + if (isset($this->plugin) && !$plugin) { + $plugin = $this->plugin; + } + + if (isset($params['cache'])) { + $expires = '+1 day'; + + if (is_array($params['cache'])) { + $expires = $params['cache']['time']; + $key = Inflector::slug($params['cache']['key']); + } elseif ($params['cache'] !== true) { + $expires = $params['cache']; + $key = implode('_', array_keys($params)); + } + + if ($expires) { + $cacheFile = 'element_' . $key . '_' . $plugin . Inflector::slug($name); + $cache = cache('views' . DS . $cacheFile, null, $expires); + + if (is_string($cache)) { + return $cache; + } + } + } + $paths = $this->_paths($plugin); + $exts = $this->_getExtensions(); + foreach ($exts as $ext) { + foreach ($paths as $path) { + if (file_exists($path . 'elements' . DS . $name . $ext)) { + $file = $path . 'elements' . DS . $name . $ext; + break; + } + } + } + + if (is_file($file)) { + $vars = array_merge($this->viewVars, $params); + foreach ($this->loaded as $name => $helper) { + if (!isset($vars[$name])) { + $vars[$name] =& $this->loaded[$name]; + } + } + $element = $this->_render($file, $vars, $loadHelpers); + if (isset($params['cache']) && isset($cacheFile) && isset($expires)) { + cache('views' . DS . $cacheFile, $element, $expires); + } + return $element; + } + $file = $paths[0] . 'elements' . DS . $name . $this->ext; + + if (Configure::read() > 0) { + return "Not Found: " . $file; + } + } + +/** + * Renders view for given action and layout. If $file is given, that is used + * for a view filename (e.g. customFunkyView.ctp). + * + * @param string $action Name of action to render for + * @param string $layout Layout to use + * @param string $file Custom filename for view + * @return string Rendered Element + * @access public + */ + function render($action = null, $layout = null, $file = null) { + if ($this->hasRendered) { + return true; + } + $out = null; + + if ($file != null) { + $action = $file; + } + + if ($action !== false && $viewFileName = $this->_getViewFileName($action)) { + $out = $this->_render($viewFileName, $this->viewVars); + } + + if ($layout === null) { + $layout = $this->layout; + } + + if ($out !== false) { + if ($layout && $this->autoLayout) { + $out = $this->renderLayout($out, $layout); + $isCached = ( + isset($this->loaded['cache']) || + Configure::read('Cache.check') === true + ); + + if ($isCached) { + $replace = array('', ''); + $out = str_replace($replace, '', $out); + } + } + $this->hasRendered = true; + } else { + $out = $this->_render($viewFileName, $this->viewVars); + trigger_error(sprintf(__("Error in view %s, got:
%s
", true), $viewFileName, $out), E_USER_ERROR); + } + return $out; + } + +/** + * Renders a layout. Returns output from _render(). Returns false on error. + * Several variables are created for use in layout. + * + * - `title_for_layout` - A backwards compatible place holder, you should set this value if you want more control. + * - `content_for_layout` - contains rendered view file + * - `scripts_for_layout` - contains scripts added to header + * + * @param string $content_for_layout Content to render in a view, wrapped by the surrounding layout. + * @return mixed Rendered output, or false on error + * @access public + */ + function renderLayout($content_for_layout, $layout = null) { + $layoutFileName = $this->_getLayoutFileName($layout); + if (empty($layoutFileName)) { + return $this->output; + } + + $dataForLayout = array_merge($this->viewVars, array( + 'content_for_layout' => $content_for_layout, + 'scripts_for_layout' => implode("\n\t", $this->__scripts), + )); + + if (!isset($dataForLayout['title_for_layout'])) { + $dataForLayout['title_for_layout'] = Inflector::humanize($this->viewPath); + } + + if (empty($this->loaded) && !empty($this->helpers)) { + $loadHelpers = true; + } else { + $loadHelpers = false; + $dataForLayout = array_merge($dataForLayout, $this->loaded); + } + + $this->_triggerHelpers('beforeLayout'); + $this->output = $this->_render($layoutFileName, $dataForLayout, $loadHelpers, true); + + if ($this->output === false) { + $this->output = $this->_render($layoutFileName, $dataForLayout); + trigger_error(sprintf(__("Error in layout %s, got:
%s
", true), $layoutFileName, $this->output), E_USER_ERROR); + return false; + } + + $this->_triggerHelpers('afterLayout'); + + return $this->output; + } + +/** + * Fire a callback on all loaded Helpers. All helpers must implement this method, + * it is not checked before being called. You can add additional helper callbacks in AppHelper. + * + * @param string $callback name of callback fire. + * @access protected + * @return void + */ + function _triggerHelpers($callback) { + if (empty($this->loaded)) { + return false; + } + $helpers = array_keys($this->loaded); + foreach ($helpers as $helperName) { + $helper =& $this->loaded[$helperName]; + if (is_object($helper)) { + if (is_subclass_of($helper, 'Helper')) { + $helper->{$callback}(); + } + } + } + } + +/** + * Render cached view. Works in concert with CacheHelper and Dispatcher to + * render cached view files. + * + * @param string $filename the cache file to include + * @param string $timeStart the page render start time + * @return boolean Success of rendering the cached file. + * @access public + */ + function renderCache($filename, $timeStart) { + ob_start(); + include ($filename); + + if (Configure::read() > 0 && $this->layout != 'xml') { + echo ""; + } + $out = ob_get_clean(); + + if (preg_match('/^/', $out, $match)) { + if (time() >= $match['1']) { + @unlink($filename); + unset ($out); + return false; + } else { + if ($this->layout === 'xml') { + header('Content-type: text/xml'); + } + $commentLength = strlen(''); + echo substr($out, $commentLength); + return true; + } + } + } + +/** + * Returns a list of variables available in the current View context + * + * @return array Array of the set view variable names. + * @access public + */ + function getVars() { + return array_keys($this->viewVars); + } + +/** + * Returns the contents of the given View variable(s) + * + * @param string $var The view var you want the contents of. + * @return mixed The content of the named var if its set, otherwise null. + * @access public + */ + function getVar($var) { + if (!isset($this->viewVars[$var])) { + return null; + } else { + return $this->viewVars[$var]; + } + } + +/** + * Adds a script block or other element to be inserted in $scripts_for_layout in + * the `` of a document layout + * + * @param string $name Either the key name for the script, or the script content. Name can be used to + * update/replace a script element. + * @param string $content The content of the script being added, optional. + * @return void + * @access public + */ + function addScript($name, $content = null) { + if (empty($content)) { + if (!in_array($name, array_values($this->__scripts))) { + $this->__scripts[] = $name; + } + } else { + $this->__scripts[$name] = $content; + } + } + +/** + * Generates a unique, non-random DOM ID for an object, based on the object type and the target URL. + * + * @param string $object Type of object, i.e. 'form' or 'link' + * @param string $url The object's target URL + * @return string + * @access public + */ + function uuid($object, $url) { + $c = 1; + $url = Router::url($url); + $hash = $object . substr(md5($object . $url), 0, 10); + while (in_array($hash, $this->uuids)) { + $hash = $object . substr(md5($object . $url . $c), 0, 10); + $c++; + } + $this->uuids[] = $hash; + return $hash; + } + +/** + * Returns the entity reference of the current context as an array of identity parts + * + * @return array An array containing the identity elements of an entity + * @access public + */ + function entity() { + $assoc = ($this->association) ? $this->association : $this->model; + if (!empty($this->entityPath)) { + $path = explode('.', $this->entityPath); + $count = count($path); + if ( + ($count == 1 && !empty($this->association)) || + ($count == 1 && $this->model != $this->entityPath) || + ($count == 1 && empty($this->association) && !empty($this->field)) || + ($count == 2 && !empty($this->fieldSuffix)) || + is_numeric($path[0]) && !empty($assoc) + ) { + array_unshift($path, $assoc); + } + return Set::filter($path); + } + return array_values(Set::filter( + array($assoc, $this->modelId, $this->field, $this->fieldSuffix) + )); + } + +/** + * Allows a template or element to set a variable that will be available in + * a layout or other element. Analagous to Controller::set. + * + * @param mixed $one A string or an array of data. + * @param mixed $two Value in case $one is a string (which then works as the key). + * Unused if $one is an associative array, otherwise serves as the values to $one's keys. + * @return void + * @access public + */ + function set($one, $two = null) { + $data = null; + if (is_array($one)) { + if (is_array($two)) { + $data = array_combine($one, $two); + } else { + $data = $one; + } + } else { + $data = array($one => $two); + } + if ($data == null) { + return false; + } + $this->viewVars = $data + $this->viewVars; + } + +/** + * Displays an error page to the user. Uses layouts/error.ctp to render the page. + * + * @param integer $code HTTP Error code (for instance: 404) + * @param string $name Name of the error (for instance: Not Found) + * @param string $message Error message as a web page + * @access public + */ + function error($code, $name, $message) { + header ("HTTP/1.1 {$code} {$name}"); + print ($this->_render( + $this->_getLayoutFileName('error'), + array('code' => $code, 'name' => $name, 'message' => $message) + )); + } + +/** + * Renders and returns output for given view filename with its + * array of data. + * + * @param string $___viewFn Filename of the view + * @param array $___dataForView Data to include in rendered view + * @param boolean $loadHelpers Boolean to indicate that helpers should be loaded. + * @param boolean $cached Whether or not to trigger the creation of a cache file. + * @return string Rendered output + * @access protected + */ + function _render($___viewFn, $___dataForView, $loadHelpers = true, $cached = false) { + $loadedHelpers = array(); + + if ($this->helpers != false && $loadHelpers === true) { + $loadedHelpers = $this->_loadHelpers($loadedHelpers, $this->helpers); + $helpers = array_keys($loadedHelpers); + $helperNames = array_map(array('Inflector', 'variable'), $helpers); + + for ($i = count($helpers) - 1; $i >= 0; $i--) { + $name = $helperNames[$i]; + $helper =& $loadedHelpers[$helpers[$i]]; + + if (!isset($___dataForView[$name])) { + ${$name} =& $helper; + } + $this->loaded[$helperNames[$i]] =& $helper; + $this->{$helpers[$i]} =& $helper; + } + $this->_triggerHelpers('beforeRender'); + unset($name, $loadedHelpers, $helpers, $i, $helperNames, $helper); + } + + extract($___dataForView, EXTR_SKIP); + ob_start(); + + if (Configure::read() > 0) { + include ($___viewFn); + } else { + @include ($___viewFn); + } + + if ($loadHelpers === true) { + $this->_triggerHelpers('afterRender'); + } + + $out = ob_get_clean(); + $caching = ( + isset($this->loaded['cache']) && + (($this->cacheAction != false)) && (Configure::read('Cache.check') === true) + ); + + if ($caching) { + if (is_a($this->loaded['cache'], 'CacheHelper')) { + $cache =& $this->loaded['cache']; + $cache->base = $this->base; + $cache->here = $this->here; + $cache->helpers = $this->helpers; + $cache->action = $this->action; + $cache->controllerName = $this->name; + $cache->layout = $this->layout; + $cache->cacheAction = $this->cacheAction; + $cache->viewVars = $this->viewVars; + $cache->cache($___viewFn, $out, $cached); + } + } + return $out; + } + +/** + * Loads helpers, with their dependencies. + * + * @param array $loaded List of helpers that are already loaded. + * @param array $helpers List of helpers to load. + * @param string $parent holds name of helper, if loaded helper has helpers + * @return array Array containing the loaded helpers. + * @access protected + */ + function &_loadHelpers(&$loaded, $helpers, $parent = null) { + foreach ($helpers as $i => $helper) { + $options = array(); + + if (!is_int($i)) { + $options = $helper; + $helper = $i; + } + list($plugin, $helper) = pluginSplit($helper, true, $this->plugin); + $helperCn = $helper . 'Helper'; + + if (!isset($loaded[$helper])) { + if (!class_exists($helperCn)) { + $isLoaded = false; + if (!is_null($plugin)) { + $isLoaded = App::import('Helper', $plugin . $helper); + } + if (!$isLoaded) { + if (!App::import('Helper', $helper)) { + $this->cakeError('missingHelperFile', array(array( + 'helper' => $helper, + 'file' => Inflector::underscore($helper) . '.php', + 'base' => $this->base + ))); + return false; + } + } + if (!class_exists($helperCn)) { + $this->cakeError('missingHelperClass', array(array( + 'helper' => $helper, + 'file' => Inflector::underscore($helper) . '.php', + 'base' => $this->base + ))); + return false; + } + } + $loaded[$helper] =& new $helperCn($options); + $vars = array('base', 'webroot', 'here', 'params', 'action', 'data', 'theme', 'plugin'); + $c = count($vars); + + for ($j = 0; $j < $c; $j++) { + $loaded[$helper]->{$vars[$j]} = $this->{$vars[$j]}; + } + + if (!empty($this->validationErrors)) { + $loaded[$helper]->validationErrors = $this->validationErrors; + } + if (is_array($loaded[$helper]->helpers) && !empty($loaded[$helper]->helpers)) { + $loaded =& $this->_loadHelpers($loaded, $loaded[$helper]->helpers, $helper); + } + } + if (isset($loaded[$parent])) { + $loaded[$parent]->{$helper} =& $loaded[$helper]; + } + } + return $loaded; + } + +/** + * Returns filename of given action's template file (.ctp) as a string. + * CamelCased action names will be under_scored! This means that you can have + * LongActionNames that refer to long_action_names.ctp views. + * + * @param string $name Controller action to find template filename for + * @return string Template filename + * @access protected + */ + function _getViewFileName($name = null) { + $subDir = null; + + if (!is_null($this->subDir)) { + $subDir = $this->subDir . DS; + } + + if ($name === null) { + $name = $this->action; + } + $name = str_replace('/', DS, $name); + + if (strpos($name, DS) === false && $name[0] !== '.') { + $name = $this->viewPath . DS . $subDir . Inflector::underscore($name); + } elseif (strpos($name, DS) !== false) { + if ($name{0} === DS || $name{1} === ':') { + if (is_file($name)) { + return $name; + } + $name = trim($name, DS); + } else if ($name[0] === '.') { + $name = substr($name, 3); + } else { + $name = $this->viewPath . DS . $subDir . $name; + } + } + $paths = $this->_paths(Inflector::underscore($this->plugin)); + + $exts = $this->_getExtensions(); + foreach ($exts as $ext) { + foreach ($paths as $path) { + if (file_exists($path . $name . $ext)) { + return $path . $name . $ext; + } + } + } + $defaultPath = $paths[0]; + + if ($this->plugin) { + $pluginPaths = App::path('plugins'); + foreach ($paths as $path) { + if (strpos($path, $pluginPaths[0]) === 0) { + $defaultPath = $path; + break; + } + } + } + return $this->_missingView($defaultPath . $name . $this->ext, 'missingView'); + } + +/** + * Returns layout filename for this template as a string. + * + * @param string $name The name of the layout to find. + * @return string Filename for layout file (.ctp). + * @access protected + */ + function _getLayoutFileName($name = null) { + if ($name === null) { + $name = $this->layout; + } + $subDir = null; + + if (!is_null($this->layoutPath)) { + $subDir = $this->layoutPath . DS; + } + $paths = $this->_paths(Inflector::underscore($this->plugin)); + $file = 'layouts' . DS . $subDir . $name; + + $exts = $this->_getExtensions(); + foreach ($exts as $ext) { + foreach ($paths as $path) { + if (file_exists($path . $file . $ext)) { + return $path . $file . $ext; + } + } + } + return $this->_missingView($paths[0] . $file . $this->ext, 'missingLayout'); + } + + +/** + * Get the extensions that view files can use. + * + * @return array Array of extensions view files use. + * @access protected + */ + function _getExtensions() { + $exts = array($this->ext); + if ($this->ext !== '.ctp') { + array_push($exts, '.ctp'); + } + return $exts; + } + +/** + * Return a misssing view error message + * + * @param string $viewFileName the filename that should exist + * @return false + * @access protected + */ + function _missingView($file, $error = 'missingView') { + if ($error === 'missingView') { + $this->cakeError('missingView', array( + 'className' => $this->name, + 'action' => $this->action, + 'file' => $file, + 'base' => $this->base + )); + return false; + } elseif ($error === 'missingLayout') { + $this->cakeError('missingLayout', array( + 'layout' => $this->layout, + 'file' => $file, + 'base' => $this->base + )); + return false; + } + } + +/** + * Return all possible paths to find view files in order + * + * @param string $plugin Optional plugin name to scan for view files. + * @param boolean $cached Set to true to force a refresh of view paths. + * @return array paths + * @access protected + */ + function _paths($plugin = null, $cached = true) { + if ($plugin === null && $cached === true && !empty($this->__paths)) { + return $this->__paths; + } + $paths = array(); + $viewPaths = App::path('views'); + $corePaths = array_flip(App::core('views')); + + if (!empty($plugin)) { + $count = count($viewPaths); + for ($i = 0; $i < $count; $i++) { + if (!isset($corePaths[$viewPaths[$i]])) { + $paths[] = $viewPaths[$i] . 'plugins' . DS . $plugin . DS; + } + } + $paths[] = App::pluginPath($plugin) . 'views' . DS; + } + $this->__paths = array_merge($paths, $viewPaths); + return $this->__paths; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/libs/xml.php b/code/ryzom/tools/server/www/webtt/cake/libs/xml.php new file mode 100644 index 000000000..b6798ca21 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/libs/xml.php @@ -0,0 +1,1457 @@ +name = $name; + if ($namespace) { + $this->namespace = $namespace; + } + + if (is_array($value) || is_object($value)) { + $this->normalize($value); + } elseif (!empty($value) || $value === 0 || $value === '0') { + $this->createTextNode($value); + } + } +/** + * Adds a namespace to the current node + * + * @param string $prefix The namespace prefix + * @param string $url The namespace DTD URL + * @return void + */ + function addNamespace($prefix, $url) { + if ($ns = Xml::addGlobalNs($prefix, $url)) { + $this->namespaces = array_merge($this->namespaces, $ns); + return true; + } + return false; + } + +/** + * Adds a namespace to the current node + * + * @param string $prefix The namespace prefix + * @param string $url The namespace DTD URL + * @return void + */ + function removeNamespace($prefix) { + if (Xml::removeGlobalNs($prefix)) { + return true; + } + return false; + } + +/** + * Creates an XmlNode object that can be appended to this document or a node in it + * + * @param string $name Node name + * @param string $value Node value + * @param string $namespace Node namespace + * @return object XmlNode + */ + function &createNode($name = null, $value = null, $namespace = false) { + $node =& new XmlNode($name, $value, $namespace); + $node->setParent($this); + return $node; + } + +/** + * Creates an XmlElement object that can be appended to this document or a node in it + * + * @param string $name Element name + * @param string $value Element value + * @param array $attributes Element attributes + * @param string $namespace Node namespace + * @return object XmlElement + */ + function &createElement($name = null, $value = null, $attributes = array(), $namespace = false) { + $element =& new XmlElement($name, $value, $attributes, $namespace); + $element->setParent($this); + return $element; + } + +/** + * Creates an XmlTextNode object that can be appended to this document or a node in it + * + * @param string $value Node value + * @return object XmlTextNode + */ + function &createTextNode($value = null) { + $node = new XmlTextNode($value); + $node->setParent($this); + return $node; + } + +/** + * Gets the XML element properties from an object. + * + * @param object $object Object to get properties from + * @return array Properties from object + * @access public + */ + function normalize($object, $keyName = null, $options = array()) { + if (is_a($object, 'XmlNode')) { + return $object; + } + $name = null; + $options += array('format' => 'attributes'); + + if ($keyName !== null && !is_numeric($keyName)) { + $name = $keyName; + } elseif (!empty($object->_name_)) { + $name = $object->_name_; + } elseif (isset($object->name)) { + $name = $object->name; + } elseif ($options['format'] == 'attributes') { + $name = get_class($object); + } + + $tagOpts = $this->__tagOptions($name); + + if ($tagOpts === false) { + return; + } + + if (isset($tagOpts['name'])) { + $name = $tagOpts['name']; + } elseif ($name != strtolower($name) && $options['slug'] !== false) { + $name = Inflector::slug(Inflector::underscore($name)); + } + + if (!empty($name)) { + $node =& $this->createElement($name); + } else { + $node =& $this; + } + + $namespace = array(); + $attributes = array(); + $children = array(); + $chldObjs = array(); + + if (is_object($object)) { + $chldObjs = get_object_vars($object); + } elseif (is_array($object)) { + $chldObjs = $object; + } elseif (!empty($object) || $object === 0 || $object === '0') { + $node->createTextNode($object); + } + $attr = array(); + + if (isset($tagOpts['attributes'])) { + $attr = $tagOpts['attributes']; + } + if (isset($tagOpts['value']) && isset($chldObjs[$tagOpts['value']])) { + $node->createTextNode($chldObjs[$tagOpts['value']]); + unset($chldObjs[$tagOpts['value']]); + } + + $n = $name; + if (isset($chldObjs['_name_'])) { + $n = null; + unset($chldObjs['_name_']); + } + $c = 0; + + foreach ($chldObjs as $key => $val) { + if (in_array($key, $attr) && !is_object($val) && !is_array($val)) { + $attributes[$key] = $val; + } else { + if (!isset($tagOpts['children']) || $tagOpts['children'] === array() || (is_array($tagOpts['children']) && in_array($key, $tagOpts['children']))) { + if (!is_numeric($key)) { + $n = $key; + } + if (is_array($val)) { + foreach ($val as $n2 => $obj2) { + if (is_numeric($n2)) { + $n2 = $n; + } + $node->normalize($obj2, $n2, $options); + } + } else { + if (is_object($val)) { + + $node->normalize($val, $n, $options); + } elseif ($options['format'] == 'tags' && $this->__tagOptions($key) !== false) { + $tmp =& $node->createElement($key); + if (!empty($val) || $val === 0 || $val === '0') { + $tmp->createTextNode($val); + } + } elseif ($options['format'] == 'attributes') { + $node->addAttribute($key, $val); + } + } + } + } + $c++; + } + if (!empty($name)) { + return $node; + } + return $children; + } + +/** + * Gets the tag-specific options for the given node name + * + * @param string $name XML tag name + * @param string $option The specific option to query. Omit for all options + * @return mixed A specific option value if $option is specified, otherwise an array of all options + * @access private + */ + function __tagOptions($name, $option = null) { + if (isset($this->__tags[$name])) { + $tagOpts = $this->__tags[$name]; + } elseif (isset($this->__tags[strtolower($name)])) { + $tagOpts = $this->__tags[strtolower($name)]; + } else { + return null; + } + if ($tagOpts === false) { + return false; + } + if (empty($option)) { + return $tagOpts; + } + if (isset($tagOpts[$option])) { + return $tagOpts[$option]; + } + return null; + } + +/** + * Returns the fully-qualified XML node name, with namespace + * + * @access public + */ + function name() { + if (!empty($this->namespace)) { + $_this =& XmlManager::getInstance(); + if (!isset($_this->options['verifyNs']) || !$_this->options['verifyNs'] || in_array($this->namespace, array_keys($_this->namespaces))) { + return $this->namespace . ':' . $this->name; + } + } + return $this->name; + } + +/** + * Sets the parent node of this XmlNode. + * + * @access public + */ + function setParent(&$parent) { + if (strtolower(get_class($this)) == 'xml') { + return; + } + if (isset($this->__parent) && is_object($this->__parent)) { + if ($this->__parent->compare($parent)) { + return; + } + foreach ($this->__parent->children as $i => $child) { + if ($this->compare($child)) { + array_splice($this->__parent->children, $i, 1); + break; + } + } + } + if ($parent == null) { + unset($this->__parent); + } else { + $parent->children[] =& $this; + $this->__parent =& $parent; + } + } + +/** + * Returns a copy of self. + * + * @return object Cloned instance + * @access public + */ + function cloneNode() { + return clone($this); + } + +/** + * Compares $node to this XmlNode object + * + * @param object An XmlNode or subclass instance + * @return boolean True if the nodes match, false otherwise + * @access public + */ + function compare($node) { + $keys = array(get_object_vars($this), get_object_vars($node)); + return ($keys[0] === $keys[1]); + } + +/** + * Append given node as a child. + * + * @param object $child XmlNode with appended child + * @param array $options XML generator options for objects and arrays + * @return object A reference to the appended child node + * @access public + */ + function &append(&$child, $options = array()) { + if (empty($child)) { + $return = false; + return $return; + } + + if (is_object($child)) { + if ($this->compare($child)) { + trigger_error(__('Cannot append a node to itself.', true)); + $return = false; + return $return; + } + } else if (is_array($child)) { + $child = Set::map($child); + if (is_array($child)) { + if (!is_a(current($child), 'XmlNode')) { + foreach ($child as $i => $childNode) { + $child[$i] = $this->normalize($childNode, null, $options); + } + } else { + foreach ($child as $childNode) { + $this->append($childNode, $options); + } + } + return $child; + } + } else { + $attributes = array(); + if (func_num_args() >= 2) { + $attributes = func_get_arg(1); + } + $child =& $this->createNode($child, null, $attributes); + } + + $child = $this->normalize($child, null, $options); + + if (empty($child->namespace) && !empty($this->namespace)) { + $child->namespace = $this->namespace; + } + + if (is_a($child, 'XmlNode')) { + $child->setParent($this); + } + + return $child; + } + +/** + * Returns first child node, or null if empty. + * + * @return object First XmlNode + * @access public + */ + function &first() { + if (isset($this->children[0])) { + return $this->children[0]; + } else { + $return = null; + return $return; + } + } + +/** + * Returns last child node, or null if empty. + * + * @return object Last XmlNode + * @access public + */ + function &last() { + if (count($this->children) > 0) { + return $this->children[count($this->children) - 1]; + } else { + $return = null; + return $return; + } + } + +/** + * Returns child node with given ID. + * + * @param string $id Name of child node + * @return object Child XmlNode + * @access public + */ + function &child($id) { + $null = null; + + if (is_int($id)) { + if (isset($this->children[$id])) { + return $this->children[$id]; + } else { + return null; + } + } elseif (is_string($id)) { + for ($i = 0; $i < count($this->children); $i++) { + if ($this->children[$i]->name == $id) { + return $this->children[$i]; + } + } + } + return $null; + } + +/** + * Gets a list of childnodes with the given tag name. + * + * @param string $name Tag name of child nodes + * @return array An array of XmlNodes with the given tag name + * @access public + */ + function children($name) { + $nodes = array(); + $count = count($this->children); + for ($i = 0; $i < $count; $i++) { + if ($this->children[$i]->name == $name) { + $nodes[] =& $this->children[$i]; + } + } + return $nodes; + } + +/** + * Gets a reference to the next child node in the list of this node's parent. + * + * @return object A reference to the XmlNode object + * @access public + */ + function &nextSibling() { + $null = null; + $count = count($this->__parent->children); + for ($i = 0; $i < $count; $i++) { + if ($this->__parent->children[$i] == $this) { + if ($i >= $count - 1 || !isset($this->__parent->children[$i + 1])) { + return $null; + } + return $this->__parent->children[$i + 1]; + } + } + return $null; + } + +/** + * Gets a reference to the previous child node in the list of this node's parent. + * + * @return object A reference to the XmlNode object + * @access public + */ + function &previousSibling() { + $null = null; + $count = count($this->__parent->children); + for ($i = 0; $i < $count; $i++) { + if ($this->__parent->children[$i] == $this) { + if ($i == 0 || !isset($this->__parent->children[$i - 1])) { + return $null; + } + return $this->__parent->children[$i - 1]; + } + } + return $null; + } + +/** + * Returns parent node. + * + * @return object Parent XmlNode + * @access public + */ + function &parent() { + return $this->__parent; + } + +/** + * Returns the XML document to which this node belongs + * + * @return object Parent XML object + * @access public + */ + function &document() { + $document =& $this; + while (true) { + if (get_class($document) == 'Xml' || $document == null) { + break; + } + $document =& $document->parent(); + } + return $document; + } + +/** + * Returns true if this structure has child nodes. + * + * @return bool + * @access public + */ + function hasChildren() { + if (is_array($this->children) && !empty($this->children)) { + return true; + } + return false; + } + +/** + * Returns this XML structure as a string. + * + * @return string String representation of the XML structure. + * @access public + */ + function toString($options = array(), $depth = 0) { + if (is_int($options)) { + $depth = $options; + $options = array(); + } + $defaults = array('cdata' => true, 'whitespace' => false, 'convertEntities' => false, 'showEmpty' => true, 'leaveOpen' => false); + $options = array_merge($defaults, Xml::options(), $options); + $tag = !(strpos($this->name, '#') === 0); + $d = ''; + + if ($tag) { + if ($options['whitespace']) { + $d .= str_repeat("\t", $depth); + } + + $d .= '<' . $this->name(); + if (!empty($this->namespaces) > 0) { + foreach ($this->namespaces as $key => $val) { + $val = str_replace('"', '\"', $val); + $d .= ' xmlns:' . $key . '="' . $val . '"'; + } + } + + $parent =& $this->parent(); + if ($parent->name === '#document' && !empty($parent->namespaces)) { + foreach ($parent->namespaces as $key => $val) { + $val = str_replace('"', '\"', $val); + $d .= ' xmlns:' . $key . '="' . $val . '"'; + } + } + + if (is_array($this->attributes) && !empty($this->attributes)) { + foreach ($this->attributes as $key => $val) { + if (is_bool($val) && $val === false) { + $val = 0; + } + $d .= ' ' . $key . '="' . htmlspecialchars($val, ENT_QUOTES, Configure::read('App.encoding')) . '"'; + } + } + } + + if (!$this->hasChildren() && empty($this->value) && $this->value !== 0 && $tag) { + if (!$options['leaveOpen']) { + $d .= ' />'; + } + if ($options['whitespace']) { + $d .= "\n"; + } + } elseif ($tag || $this->hasChildren()) { + if ($tag) { + $d .= '>'; + } + if ($this->hasChildren()) { + if ($options['whitespace']) { + $d .= "\n"; + } + $count = count($this->children); + $cDepth = $depth + 1; + for ($i = 0; $i < $count; $i++) { + $d .= $this->children[$i]->toString($options, $cDepth); + } + if ($tag) { + if ($options['whitespace'] && $tag) { + $d .= str_repeat("\t", $depth); + } + if (!$options['leaveOpen']) { + $d .= 'name() . '>'; + } + if ($options['whitespace']) { + $d .= "\n"; + } + } + } + } + return $d; + } + +/** + * Return array representation of current object. + * + * @param boolean $camelize true will camelize child nodes, false will not alter node names + * @return array Array representation + * @access public + */ + function toArray($camelize = true) { + $out = $this->attributes; + + foreach ($this->children as $child) { + $key = $camelize ? Inflector::camelize($child->name) : $child->name; + + $leaf = false; + if (is_a($child, 'XmlTextNode')) { + $out['value'] = $child->value; + continue; + } elseif (isset($child->children[0]) && is_a($child->children[0], 'XmlTextNode')) { + $value = $child->children[0]->value; + if ($child->attributes) { + $value = array_merge(array('value' => $value), $child->attributes); + } + if (count($child->children) == 1) { + $leaf = true; + } + } elseif (count($child->children) === 0 && $child->value == '') { + $value = $child->attributes; + if (empty($value)) { + $leaf = true; + } + } else { + $value = $child->toArray($camelize); + } + + if (isset($out[$key])) { + if(!isset($out[$key][0]) || !is_array($out[$key]) || !is_int(key($out[$key]))) { + $out[$key] = array($out[$key]); + } + $out[$key][] = $value; + } elseif (isset($out[$child->name])) { + $t = $out[$child->name]; + unset($out[$child->name]); + $out[$key] = array($t); + $out[$key][] = $value; + } elseif ($leaf) { + $out[$child->name] = $value; + } else { + $out[$key] = $value; + } + } + return $out; + } + +/** + * Returns data from toString when this object is converted to a string. + * + * @return string String representation of this structure. + * @access private + */ + function __toString() { + return $this->toString(); + } + +/** + * Debug method. Deletes the parent. Also deletes this node's children, + * if given the $recursive parameter. + * + * @param boolean $recursive Recursively delete elements. + * @access protected + */ + function _killParent($recursive = true) { + unset($this->__parent, $this->_log); + if ($recursive && $this->hasChildren()) { + for ($i = 0; $i < count($this->children); $i++) { + $this->children[$i]->_killParent(true); + } + } + } +} + +/** + * Main XML class. + * + * Parses and stores XML data, representing the root of an XML document + * + * @package cake + * @subpackage cake.cake.libs + * @since CakePHP v .0.10.3.1400 + */ +class Xml extends XmlNode { + +/** + * Resource handle to XML parser. + * + * @var resource + * @access private + */ + var $__parser; + +/** + * File handle to XML indata file. + * + * @var resource + * @access private + */ + var $__file; + +/** + * Raw XML string data (for loading purposes) + * + * @var string + * @access private + */ + var $__rawData = null; + +/** + * XML document header + * + * @var string + * @access private + */ + var $__header = null; + +/** + * Default array keys/object properties to use as tag names when converting objects or array + * structures to XML. Set by passing $options['tags'] to this object's constructor. + * + * @var array + * @access private + */ + var $__tags = array(); + +/** + * XML document version + * + * @var string + * @access private + */ + var $version = '1.0'; + +/** + * XML document encoding + * + * @var string + * @access private + */ + var $encoding = 'UTF-8'; + +/** + * Constructor. Sets up the XML parser with options, gives it this object as + * its XML object, and sets some variables. + * + * ### Options + * - 'root': The name of the root element, defaults to '#document' + * - 'version': The XML version, defaults to '1.0' + * - 'encoding': Document encoding, defaults to 'UTF-8' + * - 'namespaces': An array of namespaces (as strings) used in this document + * - 'format': Specifies the format this document converts to when parsed or + * rendered out as text, either 'attributes' or 'tags', defaults to 'attributes' + * - 'tags': An array specifying any tag-specific formatting options, indexed + * by tag name. See XmlNode::normalize(). + * - 'slug': A boolean to indicate whether or not you want the string version of the XML document + * to have its tags run through Inflector::slug(). Defaults to true + * + * @param mixed $input The content with which this XML document should be initialized. Can be a + * string, array or object. If a string is specified, it may be a literal XML + * document, or a URL or file path to read from. + * @param array $options Options to set up with, for valid options see above: + * @see XmlNode::normalize() + */ + function __construct($input = null, $options = array()) { + $defaults = array( + 'root' => '#document', 'tags' => array(), 'namespaces' => array(), + 'version' => '1.0', 'encoding' => 'UTF-8', 'format' => 'attributes', + 'slug' => true + ); + $options = array_merge($defaults, Xml::options(), $options); + + foreach (array('version', 'encoding', 'namespaces') as $key) { + $this->{$key} = $options[$key]; + } + $this->__tags = $options['tags']; + parent::__construct('#document'); + + if ($options['root'] !== '#document') { + $Root =& $this->createNode($options['root']); + } else { + $Root =& $this; + } + + if (!empty($input)) { + if (is_string($input)) { + $Root->load($input); + } elseif (is_array($input) || is_object($input)) { + $Root->append($input, $options); + } + } + } + +/** + * Initialize XML object from a given XML string. Returns false on error. + * + * @param string $input XML string, a path to a file, or an HTTP resource to load + * @return boolean Success + * @access public + */ + function load($input) { + if (!is_string($input)) { + return false; + } + $this->__rawData = null; + $this->__header = null; + + if (strstr($input, "<")) { + $this->__rawData = $input; + } elseif (strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) { + App::import('Core', 'HttpSocket'); + $socket = new HttpSocket(); + $this->__rawData = $socket->get($input); + } elseif (file_exists($input)) { + $this->__rawData = file_get_contents($input); + } else { + trigger_error(__('XML cannot be read', true)); + return false; + } + return $this->parse(); + } + +/** + * Parses and creates XML nodes from the __rawData property. + * + * @return boolean Success + * @access public + * @see Xml::load() + * @todo figure out how to link attributes and namespaces + */ + function parse() { + $this->__initParser(); + $this->__rawData = trim($this->__rawData); + $this->__header = trim(str_replace( + array('<' . '?', '?' . '>'), + array('', ''), + substr($this->__rawData, 0, strpos($this->__rawData, '?' . '>')) + )); + + xml_parse_into_struct($this->__parser, $this->__rawData, $vals); + $xml =& $this; + $count = count($vals); + + for ($i = 0; $i < $count; $i++) { + $data = $vals[$i]; + $data += array('tag' => null, 'value' => null, 'attributes' => array()); + switch ($data['type']) { + case "open" : + $xml =& $xml->createElement($data['tag'], $data['value'], $data['attributes']); + break; + case "close" : + $xml =& $xml->parent(); + break; + case "complete" : + $xml->createElement($data['tag'], $data['value'], $data['attributes']); + break; + case 'cdata': + $xml->createTextNode($data['value']); + break; + } + } + xml_parser_free($this->__parser); + $this->__parser = null; + return true; + } + +/** + * Initializes the XML parser resource + * + * @return void + * @access private + */ + function __initParser() { + if (empty($this->__parser)) { + $this->__parser = xml_parser_create(); + xml_set_object($this->__parser, $this); + xml_parser_set_option($this->__parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($this->__parser, XML_OPTION_SKIP_WHITE, 1); + } + } + +/** + * Returns a string representation of the XML object + * + * @param mixed $options If boolean: whether to include the XML header with the document + * (defaults to true); if an array, overrides the default XML generation options + * @return string XML data + * @access public + * @deprecated + * @see Xml::toString() + */ + function compose($options = array()) { + return $this->toString($options); + } + +/** + * If debug mode is on, this method echoes an error message. + * + * @param string $msg Error message + * @param integer $code Error code + * @param integer $line Line in file + * @access public + */ + function error($msg, $code = 0, $line = 0) { + if (Configure::read('debug')) { + echo $msg . " " . $code . " " . $line; + } + } + +/** + * Returns a string with a textual description of the error code, or FALSE if no description was found. + * + * @param integer $code Error code + * @return string Error message + * @access public + */ + function getError($code) { + $r = @xml_error_string($code); + return $r; + } + +// Overridden functions from superclass + +/** + * Get next element. NOT implemented. + * + * @return object + * @access public + */ + function &next() { + $return = null; + return $return; + } + +/** + * Get previous element. NOT implemented. + * + * @return object + * @access public + */ + function &previous() { + $return = null; + return $return; + } + +/** + * Get parent element. NOT implemented. + * + * @return object + * @access public + */ + function &parent() { + $return = null; + return $return; + } + +/** + * Adds a namespace to the current document + * + * @param string $prefix The namespace prefix + * @param string $url The namespace DTD URL + * @return void + */ + function addNamespace($prefix, $url) { + if ($count = count($this->children)) { + for ($i = 0; $i < $count; $i++) { + $this->children[$i]->addNamespace($prefix, $url); + } + return true; + } + return parent::addNamespace($prefix, $url); + } + +/** + * Removes a namespace to the current document + * + * @param string $prefix The namespace prefix + * @return void + */ + function removeNamespace($prefix) { + if ($count = count($this->children)) { + for ($i = 0; $i < $count; $i++) { + $this->children[$i]->removeNamespace($prefix); + } + return true; + } + return parent::removeNamespace($prefix); + } + +/** + * Return string representation of current object. + * + * @return string String representation + * @access public + */ + function toString($options = array()) { + if (is_bool($options)) { + $options = array('header' => $options); + } + + $defaults = array('header' => false, 'encoding' => $this->encoding); + $options = array_merge($defaults, Xml::options(), $options); + $data = parent::toString($options, 0); + + if ($options['header']) { + if (!empty($this->__header)) { + return $this->header($this->__header) . "\n" . $data; + } + return $this->header() . "\n" . $data; + } + + return $data; + } + +/** + * Return a header used on the first line of the xml file + * + * @param mixed $attrib attributes of the header element + * @return string formated header + */ + function header($attrib = array()) { + $header = 'xml'; + if (is_string($attrib)) { + $header = $attrib; + } else { + + $attrib = array_merge(array('version' => $this->version, 'encoding' => $this->encoding), $attrib); + foreach ($attrib as $key=>$val) { + $header .= ' ' . $key . '="' . $val . '"'; + } + } + return '<' . '?' . $header . ' ?' . '>'; + } + +/** + * Destructor, used to free resources. + * + * @access private + */ + function __destruct() { + $this->_killParent(true); + } + +/** + * Adds a namespace to any XML documents generated or parsed + * + * @param string $name The namespace name + * @param string $url The namespace URI; can be empty if in the default namespace map + * @return boolean False if no URL is specified, and the namespace does not exist + * default namespace map, otherwise true + * @access public + * @static + */ + function addGlobalNs($name, $url = null) { + $_this =& XmlManager::getInstance(); + if ($ns = Xml::resolveNamespace($name, $url)) { + $_this->namespaces = array_merge($_this->namespaces, $ns); + return $ns; + } + return false; + } + +/** + * Resolves current namespace + * + * @param string $name + * @param string $url + * @return array + */ + function resolveNamespace($name, $url) { + $_this =& XmlManager::getInstance(); + if ($url == null && isset($_this->defaultNamespaceMap[$name])) { + $url = $_this->defaultNamespaceMap[$name]; + } elseif ($url == null) { + return false; + } + + if (!strpos($url, '://') && isset($_this->defaultNamespaceMap[$name])) { + $_url = $_this->defaultNamespaceMap[$name]; + $name = $url; + $url = $_url; + } + return array($name => $url); + } + +/** + * Alias to Xml::addNs + * + * @access public + * @static + */ + function addGlobalNamespace($name, $url = null) { + return Xml::addGlobalNs($name, $url); + } + +/** + * Removes a namespace added in addNs() + * + * @param string $name The namespace name or URI + * @access public + * @static + */ + function removeGlobalNs($name) { + $_this =& XmlManager::getInstance(); + if (isset($_this->namespaces[$name])) { + unset($_this->namespaces[$name]); + unset($this->namespaces[$name]); + return true; + } elseif (in_array($name, $_this->namespaces)) { + $keys = array_keys($_this->namespaces); + $count = count($keys); + for ($i = 0; $i < $count; $i++) { + if ($_this->namespaces[$keys[$i]] == $name) { + unset($_this->namespaces[$keys[$i]]); + unset($this->namespaces[$keys[$i]]); + return true; + } + } + } + return false; + } + +/** + * Alias to Xml::removeNs + * + * @access public + * @static + */ + function removeGlobalNamespace($name) { + return Xml::removeGlobalNs($name); + } + +/** + * Sets/gets global XML options + * + * @param array $options + * @return array + * @access public + * @static + */ + function options($options = array()) { + $_this =& XmlManager::getInstance(); + $_this->options = array_merge($_this->options, $options); + return $_this->options; + } +} + +/** + * The XML Element + * + */ +class XmlElement extends XmlNode { + +/** + * Construct an Xml element + * + * @param string $name name of the node + * @param string $value value of the node + * @param array $attributes + * @param string $namespace + * @return string A copy of $data in XML format + */ + function __construct($name = null, $value = null, $attributes = array(), $namespace = false) { + parent::__construct($name, $value, $namespace); + $this->addAttribute($attributes); + } + +/** + * Get all the attributes for this element + * + * @return array + */ + function attributes() { + return $this->attributes; + } + +/** + * Add attributes to this element + * + * @param string $name name of the node + * @param string $value value of the node + * @return boolean + */ + function addAttribute($name, $val = null) { + if (is_object($name)) { + $name = get_object_vars($name); + } + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->addAttribute($key, $val); + } + return true; + } + if (is_numeric($name)) { + $name = $val; + $val = null; + } + if (!empty($name)) { + if (strpos($name, 'xmlns') === 0) { + if ($name == 'xmlns') { + $this->namespace = $val; + } else { + list($pre, $prefix) = explode(':', $name); + $this->addNamespace($prefix, $val); + return true; + } + } + $this->attributes[$name] = $val; + return true; + } + return false; + } + +/** + * Remove attributes to this element + * + * @param string $name name of the node + * @return boolean + */ + function removeAttribute($attr) { + if (array_key_exists($attr, $this->attributes)) { + unset($this->attributes[$attr]); + return true; + } + return false; + } +} + +/** + * XML text or CDATA node + * + * Stores XML text data according to the encoding of the parent document + * + * @package cake + * @subpackage cake.cake.libs + * @since CakePHP v .1.2.6000 + */ +class XmlTextNode extends XmlNode { + +/** + * Harcoded XML node name, represents this object as a text node + * + * @var string + */ + var $name = '#text'; + +/** + * The text/data value which this node contains + * + * @var string + */ + var $value = null; + +/** + * Construct text node with the given parent object and data + * + * @param object $parent Parent XmlNode/XmlElement object + * @param mixed $value Node value + */ + function __construct($value = null) { + $this->value = $value; + } + +/** + * Looks for child nodes in this element + * + * @return boolean False - not supported + */ + function hasChildren() { + return false; + } + +/** + * Append an XML node: XmlTextNode does not support this operation + * + * @return boolean False - not supported + * @todo make convertEntities work without mb support, convert entities to number entities + */ + function append() { + return false; + } + +/** + * Return string representation of current text node object. + * + * @return string String representation + * @access public + */ + function toString($options = array(), $depth = 0) { + if (is_int($options)) { + $depth = $options; + $options = array(); + } + + $defaults = array('cdata' => true, 'whitespace' => false, 'convertEntities' => false); + $options = array_merge($defaults, Xml::options(), $options); + $val = $this->value; + + if ($options['convertEntities'] && function_exists('mb_convert_encoding')) { + $val = mb_convert_encoding($val,'UTF-8', 'HTML-ENTITIES'); + } + + if ($options['cdata'] === true && !is_numeric($val)) { + $val = ''; + } + + if ($options['whitespace']) { + return str_repeat("\t", $depth) . $val . "\n"; + } + return $val; + } +} + +/** + * Manages application-wide namespaces and XML parsing/generation settings. + * Private class, used exclusively within scope of XML class. + * + * @access private + */ +class XmlManager { + +/** + * Global XML namespaces. Used in all XML documents processed by this application + * + * @var array + * @access public + */ + var $namespaces = array(); + +/** + * Global XML document parsing/generation settings. + * + * @var array + * @access public + */ + var $options = array(); + +/** + * Map of common namespace URIs + * + * @access private + * @var array + */ + var $defaultNamespaceMap = array( + 'dc' => 'http://purl.org/dc/elements/1.1/', // Dublin Core + 'dct' => 'http://purl.org/dc/terms/', // Dublin Core Terms + 'g' => 'http://base.google.com/ns/1.0', // Google Base + 'rc' => 'http://purl.org/rss/1.0/modules/content/', // RSS 1.0 Content Module + 'wf' => 'http://wellformedweb.org/CommentAPI/', // Well-Formed Web Comment API + 'fb' => 'http://rssnamespace.org/feedburner/ext/1.0', // FeedBurner extensions + 'lj' => 'http://www.livejournal.org/rss/lj/1.0/', // Live Journal + 'itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd', // iTunes + 'xhtml' => 'http://www.w3.org/1999/xhtml', // XHTML, + 'atom' => 'http://www.w3.org/2005/Atom' // Atom + ); + +/** + * Returns a reference to the global XML object that manages app-wide XML settings + * + * @return object + * @access public + */ + function &getInstance() { + static $instance = array(); + + if (!$instance) { + $instance[0] =& new XmlManager(); + } + return $instance[0]; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/basics.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/basics.test.php new file mode 100644 index 000000000..6ba22e6b8 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/basics.test.php @@ -0,0 +1,828 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once CAKE . 'basics.php'; +App::import('Core', 'Folder'); + +/** + * BasicsTest class + * + * @package cake + * @subpackage cake.tests.cases + */ +class BasicsTest extends CakeTestCase { + +/** + * setUp method + * + * @return void + * @access public + */ + function setUp() { + App::build(array( + 'locales' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'locale' . DS) + )); + $this->_language = Configure::read('Config.language'); + } + +/** + * tearDown method + * + * @return void + * @access public + */ + function tearDown() { + App::build(); + Configure::write('Config.language', $this->_language); + } + +/** + * test the array_diff_key compatibility function. + * + * @return void + * @access public + */ + function testArrayDiffKey() { + $one = array('one' => 1, 'two' => 2, 'three' => 3); + $two = array('one' => 'one', 'two' => 'two'); + $result = array_diff_key($one, $two); + $expected = array('three' => 3); + $this->assertEqual($result, $expected); + + $one = array('one' => array('value', 'value-two'), 'two' => 2, 'three' => 3); + $two = array('two' => 'two'); + $result = array_diff_key($one, $two); + $expected = array('one' => array('value', 'value-two'), 'three' => 3); + $this->assertEqual($result, $expected); + + $one = array('one' => null, 'two' => 2, 'three' => '', 'four' => 0); + $two = array('two' => 'two'); + $result = array_diff_key($one, $two); + $expected = array('one' => null, 'three' => '', 'four' => 0); + $this->assertEqual($result, $expected); + + $one = array('minYear' => null, 'maxYear' => null, 'separator' => '-', 'interval' => 1, 'monthNames' => true); + $two = array('minYear' => null, 'maxYear' => null, 'separator' => '-', 'interval' => 1, 'monthNames' => true); + $result = array_diff_key($one, $two); + $this->assertEqual($result, array()); + + } +/** + * testHttpBase method + * + * @return void + * @access public + */ + function testEnv() { + $this->skipIf(!function_exists('ini_get') || ini_get('safe_mode') === '1', '%s safe mode is on'); + + $__SERVER = $_SERVER; + $__ENV = $_ENV; + + $_SERVER['HTTP_HOST'] = 'localhost'; + $this->assertEqual(env('HTTP_BASE'), '.localhost'); + + $_SERVER['HTTP_HOST'] = 'com.ar'; + $this->assertEqual(env('HTTP_BASE'), '.com.ar'); + + $_SERVER['HTTP_HOST'] = 'example.ar'; + $this->assertEqual(env('HTTP_BASE'), '.example.ar'); + + $_SERVER['HTTP_HOST'] = 'example.com'; + $this->assertEqual(env('HTTP_BASE'), '.example.com'); + + $_SERVER['HTTP_HOST'] = 'www.example.com'; + $this->assertEqual(env('HTTP_BASE'), '.example.com'); + + $_SERVER['HTTP_HOST'] = 'subdomain.example.com'; + $this->assertEqual(env('HTTP_BASE'), '.example.com'); + + $_SERVER['HTTP_HOST'] = 'example.com.ar'; + $this->assertEqual(env('HTTP_BASE'), '.example.com.ar'); + + $_SERVER['HTTP_HOST'] = 'www.example.com.ar'; + $this->assertEqual(env('HTTP_BASE'), '.example.com.ar'); + + $_SERVER['HTTP_HOST'] = 'subdomain.example.com.ar'; + $this->assertEqual(env('HTTP_BASE'), '.example.com.ar'); + + $_SERVER['HTTP_HOST'] = 'double.subdomain.example.com'; + $this->assertEqual(env('HTTP_BASE'), '.subdomain.example.com'); + + $_SERVER['HTTP_HOST'] = 'double.subdomain.example.com.ar'; + $this->assertEqual(env('HTTP_BASE'), '.subdomain.example.com.ar'); + + $_SERVER = $_ENV = array(); + + $_SERVER['SCRIPT_NAME'] = '/a/test/test.php'; + $this->assertEqual(env('SCRIPT_NAME'), '/a/test/test.php'); + + $_SERVER = $_ENV = array(); + + $_ENV['CGI_MODE'] = 'BINARY'; + $_ENV['SCRIPT_URL'] = '/a/test/test.php'; + $this->assertEqual(env('SCRIPT_NAME'), '/a/test/test.php'); + + $_SERVER = $_ENV = array(); + + $this->assertFalse(env('HTTPS')); + + $_SERVER['HTTPS'] = 'on'; + $this->assertTrue(env('HTTPS')); + + $_SERVER['HTTPS'] = '1'; + $this->assertTrue(env('HTTPS')); + + $_SERVER['HTTPS'] = 'I am not empty'; + $this->assertTrue(env('HTTPS')); + + $_SERVER['HTTPS'] = 1; + $this->assertTrue(env('HTTPS')); + + $_SERVER['HTTPS'] = 'off'; + $this->assertFalse(env('HTTPS')); + + $_SERVER['HTTPS'] = false; + $this->assertFalse(env('HTTPS')); + + $_SERVER['HTTPS'] = ''; + $this->assertFalse(env('HTTPS')); + + $_SERVER = array(); + + $_ENV['SCRIPT_URI'] = 'https://domain.test/a/test.php'; + $this->assertTrue(env('HTTPS')); + + $_ENV['SCRIPT_URI'] = 'http://domain.test/a/test.php'; + $this->assertFalse(env('HTTPS')); + + $_SERVER = $_ENV = array(); + + $this->assertFalse(env('TEST_ME')); + + $_ENV['TEST_ME'] = 'a'; + $this->assertEqual(env('TEST_ME'), 'a'); + + $_SERVER['TEST_ME'] = 'b'; + $this->assertEqual(env('TEST_ME'), 'b'); + + unset($_ENV['TEST_ME']); + $this->assertEqual(env('TEST_ME'), 'b'); + + $_SERVER = $__SERVER; + $_ENV = $__ENV; + } + +/** + * test uses() + * + * @return void + * @access public + * @deprecated + */ + function testUses() { + $this->skipIf(class_exists('Security') || class_exists('Sanitize'), '%s Security and/or Sanitize class already loaded'); + + $this->assertFalse(class_exists('Security')); + $this->assertFalse(class_exists('Sanitize')); + + uses('Security', 'Sanitize'); + + $this->assertTrue(class_exists('Security')); + $this->assertTrue(class_exists('Sanitize')); + } + +/** + * Test h() + * + * @return void + * @access public + */ + function testH() { + $string = ''; + $result = h($string); + $this->assertEqual('<foo>', $result); + + $in = array('this & that', '

Which one

'); + $result = h($in); + $expected = array('this & that', '<p>Which one</p>'); + $this->assertEqual($expected, $result); + } + +/** + * Test a() + * + * @return void + * @access public + */ + function testA() { + $result = a('this', 'that', 'bar'); + $this->assertEqual(array('this', 'that', 'bar'), $result); + } + +/** + * Test aa() + * + * @return void + * @access public + */ + function testAa() { + $result = aa('a', 'b', 'c', 'd'); + $expected = array('a' => 'b', 'c' => 'd'); + $this->assertEqual($expected, $result); + + $result = aa('a', 'b', 'c', 'd', 'e'); + $expected = array('a' => 'b', 'c' => 'd', 'e' => null); + $this->assertEqual($result, $expected); + } + +/** + * Test am() + * + * @return void + * @access public + */ + function testAm() { + $result = am(array('one', 'two'), 2, 3, 4); + $expected = array('one', 'two', 2, 3, 4); + $this->assertEqual($result, $expected); + + $result = am(array('one' => array(2, 3), 'two' => array('foo')), array('one' => array(4, 5))); + $expected = array('one' => array(4, 5),'two' => array('foo')); + $this->assertEqual($result, $expected); + } + +/** + * test cache() + * + * @return void + * @access public + */ + function testCache() { + $_cacheDisable = Configure::read('Cache.disable'); + if ($this->skipIf($_cacheDisable, 'Cache is disabled, skipping cache() tests. %s')) { + return; + } + + Configure::write('Cache.disable', true); + $result = cache('basics_test', 'simple cache write'); + $this->assertNull($result); + + $result = cache('basics_test'); + $this->assertNull($result); + + Configure::write('Cache.disable', false); + $result = cache('basics_test', 'simple cache write'); + $this->assertTrue($result); + $this->assertTrue(file_exists(CACHE . 'basics_test')); + + $result = cache('basics_test'); + $this->assertEqual($result, 'simple cache write'); + @unlink(CACHE . 'basics_test'); + + cache('basics_test', 'expired', '+1 second'); + sleep(2); + $result = cache('basics_test', null, '+1 second'); + $this->assertNull($result); + + Configure::write('Cache.disable', $_cacheDisable); + } + +/** + * test clearCache() + * + * @return void + * @access public + */ + function testClearCache() { + $cacheOff = Configure::read('Cache.disable'); + if ($this->skipIf($cacheOff, 'Cache is disabled, skipping clearCache() tests. %s')) { + return; + } + + cache('views' . DS . 'basics_test.cache', 'simple cache write'); + $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test.cache')); + + cache('views' . DS . 'basics_test_2.cache', 'simple cache write 2'); + $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test_2.cache')); + + cache('views' . DS . 'basics_test_3.cache', 'simple cache write 3'); + $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test_3.cache')); + + $result = clearCache(array('basics_test', 'basics_test_2'), 'views', '.cache'); + $this->assertTrue($result); + $this->assertFalse(file_exists(CACHE . 'views' . DS . 'basics_test.cache')); + $this->assertFalse(file_exists(CACHE . 'views' . DS . 'basics_test.cache')); + $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test_3.cache')); + + $result = clearCache(null, 'views', '.cache'); + $this->assertTrue($result); + $this->assertFalse(file_exists(CACHE . 'views' . DS . 'basics_test_3.cache')); + + // Different path from views and with prefix + cache('models' . DS . 'basics_test.cache', 'simple cache write'); + $this->assertTrue(file_exists(CACHE . 'models' . DS . 'basics_test.cache')); + + cache('models' . DS . 'basics_test_2.cache', 'simple cache write 2'); + $this->assertTrue(file_exists(CACHE . 'models' . DS . 'basics_test_2.cache')); + + cache('models' . DS . 'basics_test_3.cache', 'simple cache write 3'); + $this->assertTrue(file_exists(CACHE . 'models' . DS . 'basics_test_3.cache')); + + $result = clearCache('basics', 'models', '.cache'); + $this->assertTrue($result); + $this->assertFalse(file_exists(CACHE . 'models' . DS . 'basics_test.cache')); + $this->assertFalse(file_exists(CACHE . 'models' . DS . 'basics_test_2.cache')); + $this->assertFalse(file_exists(CACHE . 'models' . DS . 'basics_test_3.cache')); + + // checking if empty files were not removed + $emptyExists = file_exists(CACHE . 'views' . DS . 'empty'); + if (!$emptyExists) { + cache('views' . DS . 'empty', ''); + } + cache('views' . DS . 'basics_test.php', 'simple cache write'); + $this->assertTrue(file_exists(CACHE . 'views' . DS . 'basics_test.php')); + $this->assertTrue(file_exists(CACHE . 'views' . DS . 'empty')); + + $result = clearCache(); + $this->assertTrue($result); + $this->assertTrue(file_exists(CACHE . 'views' . DS . 'empty')); + $this->assertFalse(file_exists(CACHE . 'views' . DS . 'basics_test.php')); + if (!$emptyExists) { + unlink(CACHE . 'views' . DS . 'empty'); + } + } + +/** + * test __() + * + * @return void + * @access public + */ + function test__() { + Configure::write('Config.language', 'rule_1_po'); + + $result = __('Plural Rule 1', true); + $expected = 'Plural Rule 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __('Plural Rule 1 (from core)', true); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + + ob_start(); + __('Plural Rule 1 (from core)'); + $result = ob_get_clean(); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + } + +/** + * test __n() + * + * @return void + * @access public + */ + function test__n() { + Configure::write('Config.language', 'rule_1_po'); + + $result = __n('%d = 1', '%d = 0 or > 1', 0, true); + $expected = '%d = 0 or > 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __n('%d = 1', '%d = 0 or > 1', 1, true); + $expected = '%d = 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __n('%d = 1 (from core)', '%d = 0 or > 1 (from core)', 2, true); + $expected = '%d = 0 or > 1 (from core translated)'; + $this->assertEqual($result, $expected); + + ob_start(); + __n('%d = 1 (from core)', '%d = 0 or > 1 (from core)', 2); + $result = ob_get_clean(); + $expected = '%d = 0 or > 1 (from core translated)'; + $this->assertEqual($result, $expected); + } + +/** + * test __d() + * + * @return void + * @access public + */ + function test__d() { + Configure::write('Config.language', 'rule_1_po'); + + $result = __d('default', 'Plural Rule 1', true); + $expected = 'Plural Rule 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __d('core', 'Plural Rule 1', true); + $expected = 'Plural Rule 1'; + $this->assertEqual($result, $expected); + + $result = __d('core', 'Plural Rule 1 (from core)', true); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + + ob_start(); + __d('core', 'Plural Rule 1 (from core)'); + $result = ob_get_clean(); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + } + +/** + * test __dn() + * + * @return void + * @access public + */ + function test__dn() { + Configure::write('Config.language', 'rule_1_po'); + + $result = __dn('default', '%d = 1', '%d = 0 or > 1', 0, true); + $expected = '%d = 0 or > 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __dn('core', '%d = 1', '%d = 0 or > 1', 0, true); + $expected = '%d = 0 or > 1'; + $this->assertEqual($result, $expected); + + $result = __dn('core', '%d = 1 (from core)', '%d = 0 or > 1 (from core)', 0, true); + $expected = '%d = 0 or > 1 (from core translated)'; + $this->assertEqual($result, $expected); + + $result = __dn('default', '%d = 1', '%d = 0 or > 1', 1, true); + $expected = '%d = 1 (translated)'; + $this->assertEqual($result, $expected); + + ob_start(); + __dn('core', '%d = 1 (from core)', '%d = 0 or > 1 (from core)', 2); + $result = ob_get_clean(); + $expected = '%d = 0 or > 1 (from core translated)'; + $this->assertEqual($result, $expected); + } + +/** + * test __c() + * + * @return void + * @access public + */ + function test__c() { + Configure::write('Config.language', 'rule_1_po'); + + $result = __c('Plural Rule 1', 6, true); + $expected = 'Plural Rule 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __c('Plural Rule 1 (from core)', 6, true); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + + ob_start(); + __c('Plural Rule 1 (from core)', 6); + $result = ob_get_clean(); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + } + +/** + * test __dc() + * + * @return void + * @access public + */ + function test__dc() { + Configure::write('Config.language', 'rule_1_po'); + + $result = __dc('default', 'Plural Rule 1', 6, true); + $expected = 'Plural Rule 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __dc('default', 'Plural Rule 1 (from core)', 6, true); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + + $result = __dc('core', 'Plural Rule 1', 6, true); + $expected = 'Plural Rule 1'; + $this->assertEqual($result, $expected); + + $result = __dc('core', 'Plural Rule 1 (from core)', 6, true); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + + ob_start(); + __dc('default', 'Plural Rule 1 (from core)', 6); + $result = ob_get_clean(); + $expected = 'Plural Rule 1 (from core translated)'; + $this->assertEqual($result, $expected); + } + +/** + * test __dcn() + * + * @return void + * @access public + */ + function test__dcn() { + Configure::write('Config.language', 'rule_1_po'); + + $result = __dcn('default', '%d = 1', '%d = 0 or > 1', 0, 6, true); + $expected = '%d = 0 or > 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __dcn('default', '%d = 1 (from core)', '%d = 0 or > 1 (from core)', 1, 6, true); + $expected = '%d = 1 (from core translated)'; + $this->assertEqual($result, $expected); + + $result = __dcn('core', '%d = 1', '%d = 0 or > 1', 0, 6, true); + $expected = '%d = 0 or > 1'; + $this->assertEqual($result, $expected); + + ob_start(); + __dcn('default', '%d = 1 (from core)', '%d = 0 or > 1 (from core)', 1, 6); + $result = ob_get_clean(); + $expected = '%d = 1 (from core translated)'; + $this->assertEqual($result, $expected); + } + +/** + * test LogError() + * + * @return void + * @access public + */ + function testLogError() { + @unlink(LOGS . 'error.log'); + + LogError('Testing LogError() basic function'); + LogError("Testing with\nmulti-line\nstring"); + + $result = file_get_contents(LOGS . 'error.log'); + $this->assertPattern('/Error: Testing LogError\(\) basic function/', $result); + $this->assertNoPattern("/Error: Testing with\nmulti-line\nstring/", $result); + $this->assertPattern('/Error: Testing with multi-line string/', $result); + } + +/** + * test fileExistsInPath() + * + * @return void + * @access public + */ + function testFileExistsInPath() { + $this->skipUnless(function_exists('ini_set'), '%s ini_set function not available'); + + $_includePath = ini_get('include_path'); + + $path = TMP . 'basics_test'; + $folder1 = $path . DS . 'folder1'; + $folder2 = $path . DS . 'folder2'; + $file1 = $path . DS . 'file1.php'; + $file2 = $folder1 . DS . 'file2.php'; + $file3 = $folder1 . DS . 'file3.php'; + $file4 = $folder2 . DS . 'file4.php'; + + new Folder($path, true); + new Folder($folder1, true); + new Folder($folder2, true); + touch($file1); + touch($file2); + touch($file3); + touch($file4); + + ini_set('include_path', $path . PATH_SEPARATOR . $folder1); + + $this->assertEqual(fileExistsInPath('file1.php'), $file1); + $this->assertEqual(fileExistsInPath('file2.php'), $file2); + $this->assertEqual(fileExistsInPath('folder1' . DS . 'file2.php'), $file2); + $this->assertEqual(fileExistsInPath($file2), $file2); + $this->assertEqual(fileExistsInPath('file3.php'), $file3); + $this->assertEqual(fileExistsInPath($file4), $file4); + + $this->assertFalse(fileExistsInPath('file1')); + $this->assertFalse(fileExistsInPath('file4.php')); + + $Folder = new Folder($path); + $Folder->delete(); + + ini_set('include_path', $_includePath); + } + +/** + * test convertSlash() + * + * @return void + * @access public + */ + function testConvertSlash() { + $result = convertSlash('\path\to\location\\'); + $expected = '\path\to\location\\'; + $this->assertEqual($result, $expected); + + $result = convertSlash('/path/to/location/'); + $expected = 'path_to_location'; + $this->assertEqual($result, $expected); + } + +/** + * test debug() + * + * @return void + * @access public + */ + function testDebug() { + ob_start(); + debug('this-is-a-test'); + $result = ob_get_clean(); + $pattern = '/.*\>(.+?tests(\/|\\\)cases(\/|\\\)basics\.test\.php|'; + $pattern .= preg_quote(substr(__FILE__, 1), '/') . ')'; + $pattern .= '.*line.*' . (__LINE__ - 4) . '.*this-is-a-test.*/s'; + $this->assertPattern($pattern, $result); + + ob_start(); + debug('
this-is-a-test
', true); + $result = ob_get_clean(); + $pattern = '/.*\>(.+?tests(\/|\\\)cases(\/|\\\)basics\.test\.php|'; + $pattern .= preg_quote(substr(__FILE__, 1), '/') . ')'; + $pattern .= '.*line.*' . (__LINE__ - 4) . '.*<div>this-is-a-test<\/div>.*/s'; + $this->assertPattern($pattern, $result); + } + +/** + * test pr() + * + * @return void + * @access public + */ + function testPr() { + ob_start(); + pr('this is a test'); + $result = ob_get_clean(); + $expected = "
this is a test
"; + $this->assertEqual($result, $expected); + + ob_start(); + pr(array('this' => 'is', 'a' => 'test')); + $result = ob_get_clean(); + $expected = "
Array\n(\n    [this] => is\n    [a] => test\n)\n
"; + $this->assertEqual($result, $expected); + } + +/** + * test params() + * + * @return void + * @access public + */ + function testParams() { + $this->assertNull(params('weekend')); + $this->assertNull(params(array())); + $this->assertEqual(params(array('weekend')), array('weekend')); + + $nested = array(array('weekend')); + $this->assertEqual(params($nested), array('weekend')); + + $multiple = array(array('weekend'), 'jean-luc', 'godard'); + $this->assertEqual(params($multiple), $multiple); + } + +/** + * test stripslashes_deep() + * + * @return void + * @access public + */ + function testStripslashesDeep() { + $this->skipIf(ini_get('magic_quotes_sybase') === '1', '%s magic_quotes_sybase is on'); + + $this->assertEqual(stripslashes_deep("tes\'t"), "tes't"); + $this->assertEqual(stripslashes_deep('tes\\' . chr(0) .'t'), 'tes' . chr(0) .'t'); + $this->assertEqual(stripslashes_deep('tes\"t'), 'tes"t'); + $this->assertEqual(stripslashes_deep("tes\'t"), "tes't"); + $this->assertEqual(stripslashes_deep('te\\st'), 'test'); + + $nested = array( + 'a' => "tes\'t", + 'b' => 'tes\\' . chr(0) .'t', + 'c' => array( + 'd' => 'tes\"t', + 'e' => "te\'s\'t", + array('f' => "tes\'t") + ), + 'g' => 'te\\st' + ); + $expected = array( + 'a' => "tes't", + 'b' => 'tes' . chr(0) .'t', + 'c' => array( + 'd' => 'tes"t', + 'e' => "te's't", + array('f' => "tes't") + ), + 'g' => 'test' + ); + $this->assertEqual(stripslashes_deep($nested), $expected); + } + +/** + * test stripslashes_deep() with magic_quotes_sybase on + * + * @return void + * @access public + */ + function testStripslashesDeepSybase() { + $this->skipUnless(ini_get('magic_quotes_sybase') === '1', '%s magic_quotes_sybase is off'); + + $this->assertEqual(stripslashes_deep("tes\'t"), "tes\'t"); + + $nested = array( + 'a' => "tes't", + 'b' => "tes''t", + 'c' => array( + 'd' => "tes'''t", + 'e' => "tes''''t", + array('f' => "tes''t") + ), + 'g' => "te'''''st" + ); + $expected = array( + 'a' => "tes't", + 'b' => "tes't", + 'c' => array( + 'd' => "tes''t", + 'e' => "tes''t", + array('f' => "tes't") + ), + 'g' => "te'''st" + ); + $this->assertEqual(stripslashes_deep($nested), $expected); + } + +/** + * test ife() + * + * @return void + * @access public + */ + function testIfe() { + $this->assertEqual(ife(true, 'a', 'b'), 'a'); + $this->assertEqual(ife(' ', 'a', 'b'), 'a'); + $this->assertEqual(ife('test', 'a', 'b'), 'a'); + $this->assertEqual(ife(23, 'a', 'b'), 'a'); + $this->assertEqual(ife(array('t' => 'est'), 'a', 'b'), 'a'); + + $this->assertEqual(ife(false, 'a', 'b'), 'b'); + $this->assertEqual(ife(null, 'a', 'b'), 'b'); + $this->assertEqual(ife('', 'a', 'b'), 'b'); + $this->assertEqual(ife(0, 'a', 'b'), 'b'); + $this->assertEqual(ife(array(), 'a', 'b'), 'b'); + } + +/** + * test pluginSplit + * + * @return void + */ + function testPluginSplit() { + $result = pluginSplit('Something.else'); + $this->assertEqual($result, array('Something', 'else')); + + $result = pluginSplit('Something.else.more.dots'); + $this->assertEqual($result, array('Something', 'else.more.dots')); + + $result = pluginSplit('Somethingelse'); + $this->assertEqual($result, array(null, 'Somethingelse')); + + $result = pluginSplit('Something.else', true); + $this->assertEqual($result, array('Something.', 'else')); + + $result = pluginSplit('Something.else.more.dots', true); + $this->assertEqual($result, array('Something.', 'else.more.dots')); + + $result = pluginSplit('Post', false, 'Blog'); + $this->assertEqual($result, array('Blog', 'Post')); + + $result = pluginSplit('Blog.Post', false, 'Ultimate'); + $this->assertEqual($result, array('Blog', 'Post')); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/cake.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/cake.test.php new file mode 100644 index 000000000..3a558655a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/cake.test.php @@ -0,0 +1,956 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.console + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!defined('DISABLE_AUTO_DISPATCH')) { + define('DISABLE_AUTO_DISPATCH', true); +} + +if (!class_exists('ShellDispatcher')) { + ob_start(); + $argv = false; + require CAKE . 'console' . DS . 'cake.php'; + ob_end_clean(); +} + +require_once CONSOLE_LIBS . 'shell.php'; + +/** + * TestShellDispatcher class + * + * @package cake + * @subpackage cake.tests.cases.console + */ +class TestShellDispatcher extends ShellDispatcher { + +/** + * params property + * + * @var array + * @access public + */ + var $params = array(); + +/** + * stdout property + * + * @var string + * @access public + */ + var $stdout = ''; + +/** + * stderr property + * + * @var string + * @access public + */ + var $stderr = ''; + +/** + * stopped property + * + * @var string + * @access public + */ + var $stopped = null; + +/** + * TestShell + * + * @var mixed + * @access public + */ + var $TestShell; + +/** + * _initEnvironment method + * + * @return void + * @access protected + */ + function _initEnvironment() { + } + +/** + * stderr method + * + * @return void + * @access public + */ + function stderr($string) { + $this->stderr .= rtrim($string, ' '); + } + +/** + * stdout method + * + * @return void + * @access public + */ + function stdout($string, $newline = true) { + if ($newline) { + $this->stdout .= rtrim($string, ' ') . "\n"; + } else { + $this->stdout .= rtrim($string, ' '); + } + } + +/** + * clear method + * + * @return void + * @access public + */ + function clear() { + + } + +/** + * _stop method + * + * @return void + * @access protected + */ + function _stop($status = 0) { + $this->stopped = 'Stopped with status: ' . $status; + return $status; + } + +/** + * getShell + * + * @param mixed $plugin + * @return mixed + * @access public + */ + function getShell($plugin = null) { + return $this->_getShell($plugin); + } + +/** + * _getShell + * + * @param mixed $plugin + * @return mixed + * @access protected + */ + function _getShell($plugin = null) { + if (isset($this->TestShell)) { + return $this->TestShell; + } + return parent::_getShell($plugin); + } +} + +/** + * ShellDispatcherTest + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ShellDispatcherTest extends CakeTestCase { + +/** + * setUp method + * + * @return void + * @access public + */ + function setUp() { + App::build(array( + 'plugins' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS + ), + 'shells' => array( + CORE_PATH ? CONSOLE_LIBS : ROOT . DS . CONSOLE_LIBS, + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS + ) + ), true); + } + +/** + * tearDown method + * + * @return void + * @access public + */ + function tearDown() { + App::build(); + } + +/** + * testParseParams method + * + * @return void + * @access public + */ + function testParseParams() { + $Dispatcher =& new TestShellDispatcher(); + + $params = array( + '/cake/1.2.x.x/cake/console/cake.php', + 'bake', + '-app', + 'new', + '-working', + '/var/www/htdocs' + ); + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => '/var/www/htdocs/new', + 'root' => '/var/www/htdocs' + ); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array('cake.php'); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'app'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-app', + 'new', + ); + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + './cake.php', + 'bake', + '-app', + 'new', + '-working', + '/cake/1.2.x.x/cake/console' + ); + + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) + ); + + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + './console/cake.php', + 'bake', + '-app', + 'new', + '-working', + '/cake/1.2.x.x/cake' + ); + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + './console/cake.php', + 'bake', + '-app', + 'new', + '-dry', + '-working', + '/cake/1.2.x.x/cake' + ); + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), + 'dry' => 1 + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + './console/cake.php', + '-working', + '/cake/1.2.x.x/cake', + 'schema', + 'run', + 'create', + '-dry', + '-f', + '-name', + 'DbAcl' + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'app'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), + 'dry' => 1, + 'f' => 1, + 'name' => 'DbAcl' + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $expected = array('./console/cake.php', 'schema', 'run', 'create'); + $this->assertEqual($expected, $Dispatcher->args); + + $params = array( + '/cake/1.2.x.x/cake/console/cake.php', + '-working', + '/cake/1.2.x.x/app', + 'schema', + 'run', + 'create', + '-dry', + '-name', + 'DbAcl' + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => '/cake/1.2.x.x/app', + 'root' => '/cake/1.2.x.x', + 'dry' => 1, + 'name' => 'DbAcl' + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $expected = array('/cake/1.2.x.x/cake/console/cake.php', 'schema', 'run', 'create'); + $this->assertEqual($expected, $Dispatcher->args); + $params = array( + 'cake.php', + '-working', + 'C:/wamp/www/cake/app', + 'bake', + '-app', + 'C:/wamp/www/apps/cake/app', + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => 'C:\wamp\www\apps\cake\app', + 'root' => 'C:\wamp\www\apps\cake' + ); + + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-working', + 'C:\wamp\www\cake\app', + 'bake', + '-app', + 'C:\wamp\www\apps\cake\app', + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => 'C:\wamp\www\apps\cake\app', + 'root' => 'C:\wamp\www\apps\cake' + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-working', + 'C:\wamp\www\apps', + 'bake', + '-app', + 'cake\app', + '-url', + 'http://example.com/some/url/with/a/path' + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => 'C:\wamp\www\apps\cake\app', + 'root' => 'C:\wamp\www\apps\cake', + 'url' => 'http://example.com/some/url/with/a/path' + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + '/home/amelo/dev/cake-common/cake/console/cake.php', + '-root', + '/home/amelo/dev/lsbu-vacancy', + '-working', + '/home/amelo/dev/lsbu-vacancy', + '-app', + 'app', + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => '/home/amelo/dev/lsbu-vacancy/app', + 'root' => '/home/amelo/dev/lsbu-vacancy', + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-working', + 'D:\www', + 'bake', + 'my_app', + ); + $expected = array( + 'working' => 'D:\www', + 'app' => 'www', + 'root' => 'D:', + 'webroot' => 'webroot' + ); + + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-working', + 'D:\ ', + 'bake', + 'my_app', + ); + $expected = array( + 'working' => '.', + 'app' => 'D:', + 'root' => '.', + 'webroot' => 'webroot' + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + } + +/** + * testBuildPaths method + * + * @return void + * @access public + */ + function testBuildPaths() { + $Dispatcher =& new TestShellDispatcher(); + + $result = $Dispatcher->shellPaths; + + $expected = array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS . 'vendors' . DS . 'shells' . DS, + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin_two' . DS . 'vendors' . DS . 'shells' . DS, + APP . 'vendors' . DS . 'shells' . DS, + VENDORS . 'shells' . DS, + CORE_PATH ? CONSOLE_LIBS : ROOT . DS . CONSOLE_LIBS, + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS, + ); + $this->assertIdentical(array_diff($result, $expected), array()); + $this->assertIdentical(array_diff($expected, $result), array()); + } + +/** + * Verify loading of (plugin-) shells + * + * @return void + * @access public + */ + function testGetShell() { + $this->skipIf(class_exists('SampleShell'), '%s SampleShell Class already loaded'); + $this->skipIf(class_exists('ExampleShell'), '%s ExampleShell Class already loaded'); + + $Dispatcher =& new TestShellDispatcher(); + + $Dispatcher->shell = 'sample'; + $Dispatcher->shellName = 'Sample'; + $Dispatcher->shellClass = 'SampleShell'; + + $result = $Dispatcher->getShell(); + $this->assertIsA($result, 'SampleShell'); + + $Dispatcher =& new TestShellDispatcher(); + + $Dispatcher->shell = 'example'; + $Dispatcher->shellName = 'Example'; + $Dispatcher->shellClass = 'ExampleShell'; + + $result = $Dispatcher->getShell('test_plugin'); + $this->assertIsA($result, 'ExampleShell'); + } + +/** + * Verify correct dispatch of Shell subclasses with a main method + * + * @return void + * @access public + */ + function testDispatchShellWithMain() { + Mock::generate('Shell', 'MockWithMainShell', array('main', '_secret')); + + $Dispatcher =& new TestShellDispatcher(); + + $Shell = new MockWithMainShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('initialize'); + $Shell->expectOnce('loadTasks'); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWithMainShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main', 'initdb'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array('initdb')); + + $Shell = new MockWithMainShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('help'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main', 'help'); + $result = $Dispatcher->dispatch(); + $this->assertNull($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWithMainShell(); + $Shell->setReturnValue('main', true); + $Shell->expectNever('hr'); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main', 'hr'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array('hr')); + + $Shell = new MockWithMainShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main', 'dispatch'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array('dispatch')); + + $Shell = new MockWithMainShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main', 'idontexist'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array('idontexist')); + + $Shell = new MockWithMainShell(); + $Shell->expectNever('startup'); + $Shell->expectNever('main'); + $Shell->expectNever('_secret'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main', '_secret'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + } + +/** + * Verify correct dispatch of Shell subclasses without a main method + * + * @return void + * @access public + */ + function testDispatchShellWithoutMain() { + Mock::generate('Shell', 'MockWithoutMainShell', array('initDb', '_secret')); + + $Dispatcher =& new TestShellDispatcher(); + + $Shell = new MockWithoutMainShell(); + $Shell->setReturnValue('initDb', true); + $Shell->expectOnce('initialize'); + $Shell->expectOnce('loadTasks'); + $Shell->expectNever('startup'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWithoutMainShell(); + $Shell->setReturnValue('initDb', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('initDb'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main', 'initdb'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWithoutMainShell(); + $Shell->setReturnValue('initDb', true); + $Shell->expectNever('startup'); + $Shell->expectNever('hr'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main', 'hr'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + $this->assertEqual($Dispatcher->args, array('hr')); + + $Shell = new MockWithoutMainShell(); + $Shell->setReturnValue('initDb', true); + $Shell->expectNever('startup'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main', 'dispatch'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + + $Shell = new MockWithoutMainShell(); + $Shell->expectNever('startup'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main', 'idontexist'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + + $Shell = new MockWithoutMainShell(); + $Shell->expectNever('startup'); + $Shell->expectNever('_secret'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main', '_secret'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + } + +/** + * Verify correct dispatch of custom classes with a main method + * + * @return void + * @access public + */ + function testDispatchNotAShellWithMain() { + Mock::generate('Object', 'MockWithMainNotAShell', + array('main', 'initialize', 'loadTasks', 'startup', '_secret')); + + $Dispatcher =& new TestShellDispatcher(); + + $Shell = new MockWithMainNotAShell(); + $Shell->setReturnValue('main', true); + $Shell->expectNever('initialize'); + $Shell->expectNever('loadTasks'); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main_not_a'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWithMainNotAShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main_not_a', 'initdb'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array('initdb')); + + $Shell = new MockWithMainNotAShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main_not_a', 'hr'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array('hr')); + + $Shell = new MockWithMainNotAShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main_not_a', 'dispatch'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array('dispatch')); + + $Shell = new MockWithMainNotAShell(); + $Shell->setReturnValue('main', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('main'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main_not_a', 'idontexist'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array('idontexist')); + + $Shell = new MockWithMainNotAShell(); + $Shell->expectNever('startup'); + $Shell->expectNever('main'); + $Shell->expectNever('_secret'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_with_main_not_a', '_secret'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + } + +/** + * Verify correct dispatch of custom classes without a main method + * + * @return void + * @access public + */ + function testDispatchNotAShellWithoutMain() { + Mock::generate('Object', 'MockWithoutMainNotAShell', + array('initDb', 'initialize', 'loadTasks', 'startup', '_secret')); + + $Dispatcher =& new TestShellDispatcher(); + + $Shell = new MockWithoutMainNotAShell(); + $Shell->setReturnValue('initDb', true); + $Shell->expectNever('initialize'); + $Shell->expectNever('loadTasks'); + $Shell->expectNever('startup'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main_not_a'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + + $Shell = new MockWithoutMainNotAShell(); + $Shell->setReturnValue('initDb', true); + $Shell->expectOnce('startup'); + $Shell->expectOnce('initDb'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main_not_a', 'initdb'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWithoutMainNotAShell(); + $Shell->setReturnValue('initDb', true); + $Shell->expectNever('startup'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main_not_a', 'hr'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + + $Shell = new MockWithoutMainNotAShell(); + $Shell->setReturnValue('initDb', true); + $Shell->expectNever('startup'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main_not_a', 'dispatch'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + + $Shell = new MockWithoutMainNotAShell(); + $Shell->expectNever('startup'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main_not_a', 'idontexist'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + + $Shell = new MockWithoutMainNotAShell(); + $Shell->expectNever('startup'); + $Shell->expectNever('_secret'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_without_main_not_a', '_secret'); + $result = $Dispatcher->dispatch(); + $this->assertFalse($result); + } + +/** + * Verify that a task is called instead of the shell if the first arg equals + * the name of the task + * + * @return void + * @access public + */ + function testDispatchTask() { + Mock::generate('Shell', 'MockWeekShell', array('main')); + Mock::generate('Shell', 'MockOnSundayTask', array('execute')); + + $Dispatcher =& new TestShellDispatcher(); + + $Shell = new MockWeekShell(); + $Shell->expectOnce('initialize'); + $Shell->expectOnce('loadTasks'); + $Shell->expectNever('startup'); + $Shell->expectNever('main'); + + $Task = new MockOnSundayTask(); + $Task->setReturnValue('execute', true); + $Task->expectOnce('initialize'); + $Task->expectOnce('loadTasks'); + $Task->expectOnce('startup'); + $Task->expectOnce('execute'); + + $Shell->MockOnSunday =& $Task; + $Shell->taskNames = array('MockOnSunday'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_week', 'mock_on_sunday'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWeekShell(); + $Task = new MockOnSundayTask(); + $Task->expectNever('execute'); + $Task->expectOnce('help'); + + $Shell->MockOnSunday =& $Task; + $Shell->taskNames = array('MockOnSunday'); + $Dispatcher->TestShell =& $Shell; + + $Dispatcher->args = array('mock_week', 'mock_on_sunday', 'help'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + } + +/** + * Verify shifting of arguments + * + * @return void + * @access public + */ + function testShiftArgs() { + $Dispatcher =& new TestShellDispatcher(); + + $Dispatcher->args = array('a', 'b', 'c'); + $this->assertEqual($Dispatcher->shiftArgs(), 'a'); + $this->assertIdentical($Dispatcher->args, array('b', 'c')); + + $Dispatcher->args = array('a' => 'b', 'c', 'd'); + $this->assertEqual($Dispatcher->shiftArgs(), 'b'); + $this->assertIdentical($Dispatcher->args, array('c', 'd')); + + $Dispatcher->args = array('a', 'b' => 'c', 'd'); + $this->assertEqual($Dispatcher->shiftArgs(), 'a'); + $this->assertIdentical($Dispatcher->args, array('b' => 'c', 'd')); + + $Dispatcher->args = array(0 => 'a', 2 => 'b', 30 => 'c'); + $this->assertEqual($Dispatcher->shiftArgs(), 'a'); + $this->assertIdentical($Dispatcher->args, array(0 => 'b', 1 => 'c')); + + $Dispatcher->args = array(); + $this->assertNull($Dispatcher->shiftArgs()); + $this->assertIdentical($Dispatcher->args, array()); + } + +/** + * testHelpCommand method + * + * @return void + * @access public + */ + function testHelpCommand() { + $Dispatcher =& new TestShellDispatcher(); + + $expected = "/example \[.*TestPlugin, TestPluginTwo.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/welcome \[.*TestPluginTwo.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/acl \[.*CORE.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/api \[.*CORE.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/bake \[.*CORE.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/console \[.*CORE.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/i18n \[.*CORE.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/schema \[.*CORE.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/testsuite \[.*CORE.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + + $expected = "/sample \[.*test_app.*\]/"; + $this->assertPattern($expected, $Dispatcher->stdout); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/acl.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/acl.test.php new file mode 100644 index 000000000..a7dd1b321 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/acl.test.php @@ -0,0 +1,346 @@ +_aclDb = Configure::read('Acl.database'); + $this->_aclClass = Configure::read('Acl.classname'); + + Configure::write('Acl.database', 'test_suite'); + Configure::write('Acl.classname', 'DbAcl'); + } + +/** + * restore Environment settings + * + * @return void + * @access public + */ + function endCase() { + Configure::write('Acl.database', $this->_aclDb); + Configure::write('Acl.classname', $this->_aclClass); + } + +/** + * startTest method + * + * @return void + * @access public + */ + function startTest() { + $this->Dispatcher =& new TestAclShellMockShellDispatcher(); + $this->Task =& new MockAclShell($this->Dispatcher); + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->params['datasource'] = 'test_suite'; + $this->Task->Acl =& new AclComponent(); + $controller = null; + $this->Task->Acl->startup($controller); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + ClassRegistry::flush(); + } + +/** + * test that model.foreign_key output works when looking at acl rows + * + * @return void + * @access public + */ + function testViewWithModelForeignKeyOutput() { + $this->Task->command = 'view'; + $this->Task->startup(); + $data = array( + 'parent_id' => null, + 'model' => 'MyModel', + 'foreign_key' => 2, + ); + $this->Task->Acl->Aro->create($data); + $this->Task->Acl->Aro->save(); + $this->Task->args[0] = 'aro'; + + $this->Task->expectAt(0, 'out', array('Aro tree:')); + $this->Task->expectAt(1, 'out', array(new PatternExpectation('/\[1\] ROOT/'))); + $this->Task->expectAt(3, 'out', array(new PatternExpectation('/\[3\] Gandalf/'))); + $this->Task->expectAt(5, 'out', array(new PatternExpectation('/\[5\] MyModel.2/'))); + + $this->Task->view(); + } + +/** + * test view with an argument + * + * @return void + * @access public + */ + function testViewWithArgument() { + $this->Task->args = array('aro', 'admins'); + $this->Task->expectAt(0, 'out', array('Aro tree:')); + $this->Task->expectAt(1, 'out', array(' [2] admins')); + $this->Task->expectAt(2, 'out', array(' [3] Gandalf')); + $this->Task->expectAt(3, 'out', array(' [4] Elrond')); + $this->Task->view(); + } + +/** + * test the method that splits model.foreign key. and that it returns an array. + * + * @return void + * @access public + */ + function testParsingModelAndForeignKey() { + $result = $this->Task->parseIdentifier('Model.foreignKey'); + $expected = array('model' => 'Model', 'foreign_key' => 'foreignKey'); + + $result = $this->Task->parseIdentifier('mySuperUser'); + $this->assertEqual($result, 'mySuperUser'); + + $result = $this->Task->parseIdentifier('111234'); + $this->assertEqual($result, '111234'); + } + +/** + * test creating aro/aco nodes + * + * @return void + * @access public + */ + function testCreate() { + $this->Task->args = array('aro', 'root', 'User.1'); + $this->Task->expectAt(0, 'out', array(new PatternExpectation('/created/'), '*')); + $this->Task->create(); + + $Aro =& ClassRegistry::init('Aro'); + $Aro->cacheQueries = false; + $result = $Aro->read(); + $this->assertEqual($result['Aro']['model'], 'User'); + $this->assertEqual($result['Aro']['foreign_key'], 1); + $this->assertEqual($result['Aro']['parent_id'], null); + $id = $result['Aro']['id']; + + $this->Task->args = array('aro', 'User.1', 'User.3'); + $this->Task->expectAt(1, 'out', array(new PatternExpectation('/created/'), '*')); + $this->Task->create(); + + $Aro =& ClassRegistry::init('Aro'); + $result = $Aro->read(); + $this->assertEqual($result['Aro']['model'], 'User'); + $this->assertEqual($result['Aro']['foreign_key'], 3); + $this->assertEqual($result['Aro']['parent_id'], $id); + + $this->Task->args = array('aro', 'root', 'somealias'); + $this->Task->expectAt(2, 'out', array(new PatternExpectation('/created/'), '*')); + $this->Task->create(); + + $Aro =& ClassRegistry::init('Aro'); + $result = $Aro->read(); + $this->assertEqual($result['Aro']['alias'], 'somealias'); + $this->assertEqual($result['Aro']['model'], null); + $this->assertEqual($result['Aro']['foreign_key'], null); + $this->assertEqual($result['Aro']['parent_id'], null); + } + +/** + * test the delete method with different node types. + * + * @return void + * @access public + */ + function testDelete() { + $this->Task->args = array('aro', 'AuthUser.1'); + $this->Task->expectAt(0, 'out', array(new NoPatternExpectation('/not/'), true)); + $this->Task->delete(); + + $Aro =& ClassRegistry::init('Aro'); + $result = $Aro->read(null, 3); + $this->assertFalse($result); + } + +/** + * test setParent method. + * + * @return void + * @access public + */ + function testSetParent() { + $this->Task->args = array('aro', 'AuthUser.2', 'root'); + $this->Task->setParent(); + + $Aro =& ClassRegistry::init('Aro'); + $result = $Aro->read(null, 4); + $this->assertEqual($result['Aro']['parent_id'], null); + } + +/** + * test grant + * + * @return void + * @access public + */ + function testGrant() { + $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', 'create'); + $this->Task->expectAt(0, 'out', array(new PatternExpectation('/Permission granted/'), true)); + $this->Task->grant(); + + $node = $this->Task->Acl->Aro->read(null, 4); + $this->assertFalse(empty($node['Aco'][0])); + $this->assertEqual($node['Aco'][0]['Permission']['_create'], 1); + } + +/** + * test deny + * + * @return void + * @access public + */ + function testDeny() { + $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', 'create'); + $this->Task->expectAt(0, 'out', array(new PatternExpectation('/Permission denied/'), true)); + $this->Task->deny(); + + $node = $this->Task->Acl->Aro->read(null, 4); + $this->assertFalse(empty($node['Aco'][0])); + $this->assertEqual($node['Aco'][0]['Permission']['_create'], -1); + } + +/** + * test checking allowed and denied perms + * + * @return void + * @access public + */ + function testCheck() { + $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', '*'); + $this->Task->expectAt(0, 'out', array(new PatternExpectation('/not allowed/'), true)); + $this->Task->check(); + + $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', 'create'); + $this->Task->expectAt(1, 'out', array(new PatternExpectation('/Permission granted/'), true)); + $this->Task->grant(); + + $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', 'create'); + $this->Task->expectAt(2, 'out', array(new PatternExpectation('/is allowed/'), true)); + $this->Task->check(); + + $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', '*'); + $this->Task->expectAt(3, 'out', array(new PatternExpectation('/not allowed/'), true)); + $this->Task->check(); + } + +/** + * test inherit and that it 0's the permission fields. + * + * @return void + * @access public + */ + function testInherit() { + $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', 'create'); + $this->Task->expectAt(0, 'out', array(new PatternExpectation('/Permission granted/'), true)); + $this->Task->grant(); + + $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', 'all'); + $this->Task->expectAt(1, 'out', array(new PatternExpectation('/permission inherited/i'), true)); + $this->Task->inherit(); + + $node = $this->Task->Acl->Aro->read(null, 4); + $this->assertFalse(empty($node['Aco'][0])); + $this->assertEqual($node['Aco'][0]['Permission']['_create'], 0); + } + +/** + * test getting the path for an aro/aco + * + * @return void + * @access public + */ + function testGetPath() { + $this->Task->args = array('aro', 'AuthUser.2'); + $this->Task->expectAt(1, 'out', array('[1] ROOT')); + $this->Task->expectAt(2, 'out', array(' [2] admins')); + $this->Task->expectAt(3, 'out', array(' [4] Elrond')); + $this->Task->getPath(); + } + +/** + * test that initdb makes the correct call. + * + * @return void + */ + function testInitDb() { + $this->Task->Dispatch->expectOnce('dispatch'); + $this->Task->initdb(); + + $this->assertEqual($this->Task->Dispatch->args, array('schema', 'create', 'DbAcl')); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/api.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/api.test.php new file mode 100644 index 000000000..c290ac3e8 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/api.test.php @@ -0,0 +1,116 @@ +Dispatcher =& new ApiShellMockShellDispatcher(); + $this->Shell =& new MockApiShell($this->Dispatcher); + $this->Shell->Dispatch =& $this->Dispatcher; + } + +/** + * tearDown method + * + * @return void + * @access public + */ + function endTest() { + ClassRegistry::flush(); + } + +/** + * Test that method names are detected properly including those with no arguments. + * + * @return void + * @access public + */ + function testMethodNameDetection () { + $this->Shell->setReturnValueAt(0, 'in', 'q'); + $this->Shell->expectAt(0, 'out', array('Controller')); + $expected = array( + array( + '1. afterFilter()', + '2. beforeFilter()', + '3. beforeRender()', + '4. constructClasses()', + '5. disableCache()', + '6. flash($message, $url, $pause = 1, $layout = \'flash\')', + '7. header($status)', + '8. httpCodes($code = null)', + '9. isAuthorized()', + '10. loadModel($modelClass = null, $id = null)', + '11. paginate($object = null, $scope = array(), $whitelist = array())', + '12. postConditions($data = array(), $op = null, $bool = \'AND\', $exclusive = false)', + '13. redirect($url, $status = null, $exit = true)', + '14. referer($default = null, $local = false)', + '15. render($action = null, $layout = null, $file = null)', + '16. set($one, $two = null)', + '17. setAction($action)', + '18. shutdownProcess()', + '19. startupProcess()', + '20. validate()', + '21. validateErrors()' + ) + ); + $this->Shell->expectAt(1, 'out', $expected); + + $this->Shell->args = array('controller'); + $this->Shell->paths['controller'] = CAKE_CORE_INCLUDE_PATH . DS . LIBS . 'controller' . DS; + $this->Shell->main(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/bake.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/bake.test.php new file mode 100644 index 000000000..2771c0269 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/bake.test.php @@ -0,0 +1,130 @@ +Dispatch =& new BakeShellMockShellDispatcher(); + $this->Shell =& new MockBakeShell(); + $this->Shell->Dispatch =& $this->Dispatch; + $this->Shell->Dispatch->shellPaths = App::path('shells'); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + unset($this->Dispatch, $this->Shell); + } + +/** + * test bake all + * + * @return void + * @access public + */ + function testAllWithModelName() { + App::import('Model', 'User'); + $userExists = class_exists('User'); + if ($this->skipIf($userExists, 'User class exists, cannot test `bake all [param]`. %s')) { + return; + } + $this->Shell->Model =& new BakeShellMockModelTask(); + $this->Shell->Controller =& new BakeShellMockControllerTask(); + $this->Shell->View =& new BakeShellMockModelTask(); + $this->Shell->DbConfig =& new BakeShellMockDbConfigTask(); + + $this->Shell->DbConfig->expectOnce('getConfig'); + $this->Shell->DbConfig->setReturnValue('getConfig', 'test_suite'); + + $this->Shell->Model->setReturnValue('bake', true); + $this->Shell->Model->expectNever('getName'); + $this->Shell->Model->expectOnce('bake'); + + $this->Shell->Controller->expectOnce('bake'); + $this->Shell->Controller->setReturnValue('bake', true); + + $this->Shell->View->expectOnce('execute'); + + $this->Shell->expectAt(0, 'out', array('Bake All')); + $this->Shell->expectAt(1, 'out', array('User Model was baked.')); + $this->Shell->expectAt(2, 'out', array('User Controller was baked.')); + $this->Shell->expectAt(3, 'out', array('User Views were baked.')); + $this->Shell->expectAt(4, 'out', array('Bake All complete')); + + $this->Shell->params = array(); + $this->Shell->args = array('User'); + $this->Shell->all(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/schema.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/schema.test.php new file mode 100644 index 000000000..0f97950d6 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/schema.test.php @@ -0,0 +1,503 @@ + array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'post_id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'user_id' => array('type' => 'integer', 'null' => false), + 'title' => array('type' => 'string', 'null' => false, 'length' => 100), + 'comment' => array('type' => 'text', 'null' => false, 'default' => null), + 'published' => array('type' => 'string', 'null' => true, 'default' => 'N', 'length' => 1), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + ); + +/** + * posts property + * + * @var array + * @access public + */ + var $articles = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'user_id' => array('type' => 'integer', 'null' => true, 'default' => ''), + 'title' => array('type' => 'string', 'null' => false, 'default' => 'Title'), + 'body' => array('type' => 'text', 'null' => true, 'default' => null), + 'summary' => array('type' => 'text', 'null' => true), + 'published' => array('type' => 'string', 'null' => true, 'default' => 'Y', 'length' => 1), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + ); +} + +/** + * SchemaShellTest class + * + * @package cake + * @subpackage cake.tests.cases.console.libs.Shells + */ +class SchemaShellTest extends CakeTestCase { + +/** + * Fixtures + * + * @var array + * @access public + */ + var $fixtures = array('core.article', 'core.user', 'core.post', 'core.auth_user', 'core.author', + 'core.comment', 'core.test_plugin_comment' + ); + +/** + * startTest method + * + * @return void + * @access public + */ + function startTest() { + $this->Dispatcher =& new TestSchemaShellMockShellDispatcher(); + $this->Shell =& new MockSchemaShell($this->Dispatcher); + $this->Shell->Dispatch =& $this->Dispatcher; + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + ClassRegistry::flush(); + } + +/** + * test startup method + * + * @return void + * @access public + */ + function testStartup() { + $this->Shell->startup(); + $this->assertTrue(isset($this->Shell->Schema)); + $this->assertTrue(is_a($this->Shell->Schema, 'CakeSchema')); + $this->assertEqual(strtolower($this->Shell->Schema->name), strtolower(APP_DIR)); + $this->assertEqual($this->Shell->Schema->file, 'schema.php'); + + unset($this->Shell->Schema); + $this->Shell->params = array( + 'name' => 'TestSchema' + ); + $this->Shell->startup(); + $this->assertEqual($this->Shell->Schema->name, 'TestSchema'); + $this->assertEqual($this->Shell->Schema->file, 'test_schema.php'); + $this->assertEqual($this->Shell->Schema->connection, 'default'); + $this->assertEqual($this->Shell->Schema->path, APP . 'config' . DS . 'schema'); + + unset($this->Shell->Schema); + $this->Shell->params = array( + 'file' => 'other_file.php', + 'connection' => 'test_suite', + 'path' => '/test/path' + ); + $this->Shell->startup(); + $this->assertEqual(strtolower($this->Shell->Schema->name), strtolower(APP_DIR)); + $this->assertEqual($this->Shell->Schema->file, 'other_file.php'); + $this->assertEqual($this->Shell->Schema->connection, 'test_suite'); + $this->assertEqual($this->Shell->Schema->path, '/test/path'); + } + +/** + * Test View - and that it dumps the schema file to stdout + * + * @return void + * @access public + */ + function testView() { + $this->Shell->startup(); + $this->Shell->Schema->path = APP . 'config' . DS . 'schema'; + $this->Shell->params['file'] = 'i18n.php'; + $this->Shell->expectOnce('_stop'); + $this->Shell->expectOnce('out'); + $this->Shell->view(); + } + +/** + * test that view() can find plugin schema files. + * + * @return void + * @access public + */ + function testViewWithPlugins() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + $this->Shell->args = array('TestPlugin.schema'); + $this->Shell->startup(); + $this->Shell->expectCallCount('_stop', 2); + $this->Shell->expectCallCount('out', 2); + $this->Shell->view(); + + $this->Shell->args = array(); + $this->Shell->params = array('plugin' => 'TestPlugin'); + $this->Shell->startup(); + $this->Shell->view(); + + App::build(); + } + +/** + * test dump() with sql file generation + * + * @return void + * @access public + */ + function testDumpWithFileWriting() { + $this->Shell->params = array( + 'name' => 'i18n', + 'write' => TMP . 'tests' . DS . 'i18n.sql' + ); + $this->Shell->expectOnce('_stop'); + $this->Shell->startup(); + $this->Shell->dump(); + + $sql =& new File(TMP . 'tests' . DS . 'i18n.sql'); + $contents = $sql->read(); + $this->assertPattern('/DROP TABLE/', $contents); + $this->assertPattern('/CREATE TABLE `i18n`/', $contents); + $this->assertPattern('/id/', $contents); + $this->assertPattern('/model/', $contents); + $this->assertPattern('/field/', $contents); + $this->assertPattern('/locale/', $contents); + $this->assertPattern('/foreign_key/', $contents); + $this->assertPattern('/content/', $contents); + + $sql->delete(); + } + +/** + * test that dump() can find and work with plugin schema files. + * + * @return void + * @access public + */ + function testDumpFileWritingWithPlugins() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + $this->Shell->args = array('TestPlugin.TestPluginApp'); + $this->Shell->params = array( + 'connection' => 'test_suite', + 'write' => TMP . 'tests' . DS . 'dump_test.sql' + ); + $this->Shell->startup(); + $this->Shell->expectOnce('_stop'); + $this->Shell->dump(); + + $file =& new File(TMP . 'tests' . DS . 'dump_test.sql'); + $contents = $file->read(); + + $this->assertPattern('/CREATE TABLE `acos`/', $contents); + $this->assertPattern('/id/', $contents); + $this->assertPattern('/model/', $contents); + + $file->delete(); + App::build(); + } + +/** + * test generate with snapshot generation + * + * @return void + * @access public + */ + function testGenerateSnaphot() { + $this->Shell->path = TMP; + $this->Shell->params['file'] = 'schema.php'; + $this->Shell->args = array('snapshot'); + $this->Shell->Schema =& new MockSchemaCakeSchema(); + $this->Shell->Schema->setReturnValue('read', array('schema data')); + $this->Shell->Schema->setReturnValue('write', true); + + $this->Shell->Schema->expectOnce('read'); + $this->Shell->Schema->expectOnce('write', array(array('schema data', 'file' => 'schema_1.php'))); + + $this->Shell->generate(); + } + +/** + * test generate without a snapshot. + * + * @return void + * @access public + */ + function testGenerateNoOverwrite() { + touch(TMP . 'schema.php'); + $this->Shell->params['file'] = 'schema.php'; + $this->Shell->args = array(); + + $this->Shell->setReturnValue('in', 'q'); + $this->Shell->Schema =& new MockSchemaCakeSchema(); + $this->Shell->Schema->path = TMP; + $this->Shell->Schema->expectNever('read'); + + $result = $this->Shell->generate(); + unlink(TMP . 'schema.php'); + } + +/** + * test generate with overwriting of the schema files. + * + * @return void + * @access public + */ + function testGenerateOverwrite() { + touch(TMP . 'schema.php'); + $this->Shell->params['file'] = 'schema.php'; + $this->Shell->args = array(); + + $this->Shell->setReturnValue('in', 'o'); + $this->Shell->expectAt(1, 'out', array(new PatternExpectation('/Schema file:\s[a-z\.]+\sgenerated/'))); + $this->Shell->Schema =& new MockSchemaCakeSchema(); + $this->Shell->Schema->path = TMP; + $this->Shell->Schema->setReturnValue('read', array('schema data')); + $this->Shell->Schema->setReturnValue('write', true); + + $this->Shell->Schema->expectOnce('read'); + $this->Shell->Schema->expectOnce('write', array(array('schema data', 'file' => 'schema.php'))); + + $this->Shell->generate(); + unlink(TMP . 'schema.php'); + } + +/** + * test that generate() can read plugin dirs and generate schema files for the models + * in a plugin. + * + * @return void + * @access public + */ + function testGenerateWithPlugins() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + $this->Shell->params = array( + 'plugin' => 'TestPlugin', + 'connection' => 'test_suite' + ); + $this->Shell->startup(); + $this->Shell->Schema->path = TMP . 'tests' . DS; + + $this->Shell->generate(); + $file =& new File(TMP . 'tests' . DS . 'schema.php'); + $contents = $file->read(); + + $this->assertPattern('/class TestPluginSchema/', $contents); + $this->assertPattern('/var \$posts/', $contents); + $this->assertPattern('/var \$auth_users/', $contents); + $this->assertPattern('/var \$authors/', $contents); + $this->assertPattern('/var \$test_plugin_comments/', $contents); + $this->assertNoPattern('/var \$users/', $contents); + $this->assertNoPattern('/var \$articles/', $contents); + + $file->delete(); + App::build(); + } + +/** + * Test schema run create with no table args. + * + * @return void + * @access public + */ + function testCreateNoArgs() { + $this->Shell->params = array( + 'connection' => 'test_suite', + 'path' => APP . 'config' . DS . 'sql' + ); + $this->Shell->args = array('i18n'); + $this->Shell->startup(); + $this->Shell->setReturnValue('in', 'y'); + $this->Shell->create(); + + $db =& ConnectionManager::getDataSource('test_suite'); + $sources = $db->listSources(); + $this->assertTrue(in_array($db->config['prefix'] . 'i18n', $sources)); + + $schema =& new i18nSchema(); + $db->execute($db->dropSchema($schema)); + } + +/** + * Test schema run create with no table args. + * + * @return void + * @access public + */ + function testCreateWithTableArgs() { + $this->Shell->params = array( + 'connection' => 'test_suite', + 'name' => 'DbAcl', + 'path' => APP . 'config' . DS . 'schema' + ); + $this->Shell->args = array('DbAcl', 'acos'); + $this->Shell->startup(); + $this->Shell->setReturnValue('in', 'y'); + $this->Shell->create(); + + $db =& ConnectionManager::getDataSource('test_suite'); + $sources = $db->listSources(); + $this->assertTrue(in_array($db->config['prefix'] . 'acos', $sources)); + $this->assertFalse(in_array($db->config['prefix'] . 'aros', $sources)); + $this->assertFalse(in_array('aros_acos', $sources)); + + $db->execute('DROP TABLE ' . $db->config['prefix'] . 'acos'); + } + +/** + * test run update with a table arg. + * + * @return void + * @access public + */ + function testUpdateWithTable() { + $this->Shell->params = array( + 'connection' => 'test_suite', + 'f' => true + ); + $this->Shell->args = array('SchemaShellTest', 'articles'); + $this->Shell->startup(); + $this->Shell->setReturnValue('in', 'y'); + $this->Shell->update(); + + $article =& new Model(array('name' => 'Article', 'ds' => 'test_suite')); + $fields = $article->schema(); + $this->assertTrue(isset($fields['summary'])); + + $this->_fixtures['core.article']->drop($this->db); + $this->_fixtures['core.article']->create($this->db); + } + +/** + * test that the plugin param creates the correct path in the schema object. + * + * @return void + * @access public + */ + function testPluginParam() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + $this->Shell->params = array( + 'plugin' => 'TestPlugin', + 'connection' => 'test_suite' + ); + $this->Shell->startup(); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS . 'config' . DS . 'schema'; + $this->assertEqual($this->Shell->Schema->path, $expected); + + App::build(); + } + +/** + * test that using Plugin.name with write. + * + * @return void + * @access public + */ + function testPluginDotSyntaxWithCreate() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + $this->Shell->params = array( + 'connection' => 'test_suite' + ); + $this->Shell->args = array('TestPlugin.TestPluginApp'); + $this->Shell->startup(); + $this->Shell->setReturnValue('in', 'y'); + $this->Shell->create(); + + $db =& ConnectionManager::getDataSource('test_suite'); + $sources = $db->listSources(); + $this->assertTrue(in_array($db->config['prefix'] . 'acos', $sources)); + + $db->execute('DROP TABLE ' . $db->config['prefix'] . 'acos'); + App::build(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/shell.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/shell.test.php new file mode 100644 index 000000000..6ed6dc1e1 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/shell.test.php @@ -0,0 +1,501 @@ +stopped = $status; + } +} + +/** + * TestAppleTask class + * + * @package cake + * @subpackage cake.tests.cases.console.libs + */ +class TestAppleTask extends Shell { +} + +/** + * TestBananaTask class + * + * @package cake + * @subpackage cake.tests.cases.console.libs + */ +class TestBananaTask extends Shell { +} + +/** + * ShellTest class + * + * @package cake + * @subpackage cake.tests.cases.console.libs + */ +class ShellTest extends CakeTestCase { + +/** + * Fixtures used in this test case + * + * @var array + * @access public + */ + var $fixtures = array( + 'core.post', 'core.comment', 'core.article', 'core.user', + 'core.tag', 'core.articles_tag', 'core.attachment' + ); + +/** + * setUp method + * + * @return void + * @access public + */ + function setUp() { + $this->Dispatcher =& new TestShellMockShellDispatcher(); + $this->Shell =& new TestShell($this->Dispatcher); + } + +/** + * tearDown method + * + * @return void + * @access public + */ + function tearDown() { + ClassRegistry::flush(); + } + +/** + * testConstruct method + * + * @return void + * @access public + */ + function testConstruct() { + $this->assertIsA($this->Shell->Dispatch, 'TestShellMockShellDispatcher'); + $this->assertEqual($this->Shell->name, 'TestShell'); + $this->assertEqual($this->Shell->alias, 'TestShell'); + } + +/** + * testInitialize method + * + * @return void + * @access public + */ + function testInitialize() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'models' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS) + ), true); + + $this->Shell->uses = array('TestPlugin.TestPluginPost'); + $this->Shell->initialize(); + + $this->assertTrue(isset($this->Shell->TestPluginPost)); + $this->assertIsA($this->Shell->TestPluginPost, 'TestPluginPost'); + $this->assertEqual($this->Shell->modelClass, 'TestPluginPost'); + + $this->Shell->uses = array('Comment'); + $this->Shell->initialize(); + $this->assertTrue(isset($this->Shell->Comment)); + $this->assertIsA($this->Shell->Comment, 'Comment'); + $this->assertEqual($this->Shell->modelClass, 'Comment'); + + $this->Shell->uses = true; + $this->Shell->initialize(); + $this->assertTrue(isset($this->Shell->AppModel)); + $this->assertIsA($this->Shell->AppModel, 'AppModel'); + + App::build(); + } + +/** + * testIn method + * + * @return void + * @access public + */ + function testIn() { + $this->Shell->Dispatch->setReturnValueAt(0, 'getInput', 'n'); + $this->Shell->Dispatch->expectAt(0, 'getInput', array('Just a test?', array('y', 'n'), 'n')); + $result = $this->Shell->in('Just a test?', array('y', 'n'), 'n'); + $this->assertEqual($result, 'n'); + + $this->Shell->Dispatch->setReturnValueAt(1, 'getInput', 'Y'); + $this->Shell->Dispatch->expectAt(1, 'getInput', array('Just a test?', array('y', 'n'), 'n')); + $result = $this->Shell->in('Just a test?', array('y', 'n'), 'n'); + $this->assertEqual($result, 'Y'); + + $this->Shell->Dispatch->setReturnValueAt(2, 'getInput', 'y'); + $this->Shell->Dispatch->expectAt(2, 'getInput', array('Just a test?', 'y,n', 'n')); + $result = $this->Shell->in('Just a test?', 'y,n', 'n'); + $this->assertEqual($result, 'y'); + + $this->Shell->Dispatch->setReturnValueAt(3, 'getInput', 'y'); + $this->Shell->Dispatch->expectAt(3, 'getInput', array('Just a test?', 'y/n', 'n')); + $result = $this->Shell->in('Just a test?', 'y/n', 'n'); + $this->assertEqual($result, 'y'); + + $this->Shell->Dispatch->setReturnValueAt(4, 'getInput', 'y'); + $this->Shell->Dispatch->expectAt(4, 'getInput', array('Just a test?', 'y', 'y')); + $result = $this->Shell->in('Just a test?', 'y', 'y'); + $this->assertEqual($result, 'y'); + + $this->Shell->interactive = false; + + $result = $this->Shell->in('Just a test?', 'y/n', 'n'); + $this->assertEqual($result, 'n'); + } + +/** + * testOut method + * + * @return void + * @access public + */ + function testOut() { + $this->Shell->Dispatch->expectAt(0, 'stdout', array("Just a test\n", false)); + $this->Shell->out('Just a test'); + + $this->Shell->Dispatch->expectAt(1, 'stdout', array("Just\na\ntest\n", false)); + $this->Shell->out(array('Just', 'a', 'test')); + + $this->Shell->Dispatch->expectAt(2, 'stdout', array("Just\na\ntest\n\n", false)); + $this->Shell->out(array('Just', 'a', 'test'), 2); + + $this->Shell->Dispatch->expectAt(3, 'stdout', array("\n", false)); + $this->Shell->out(); + } + +/** + * testErr method + * + * @return void + * @access public + */ + function testErr() { + $this->Shell->Dispatch->expectAt(0, 'stderr', array("Just a test\n")); + $this->Shell->err('Just a test'); + + $this->Shell->Dispatch->expectAt(1, 'stderr', array("Just\na\ntest\n")); + $this->Shell->err(array('Just', 'a', 'test')); + + $this->Shell->Dispatch->expectAt(2, 'stderr', array("Just\na\ntest\n\n")); + $this->Shell->err(array('Just', 'a', 'test'), 2); + + $this->Shell->Dispatch->expectAt(3, 'stderr', array("\n")); + $this->Shell->err(); + } + +/** + * testNl + * + * @return void + * @access public + */ + function testNl() { + $this->assertEqual($this->Shell->nl(), "\n"); + $this->assertEqual($this->Shell->nl(true), "\n"); + $this->assertEqual($this->Shell->nl(false), ""); + $this->assertEqual($this->Shell->nl(2), "\n\n"); + $this->assertEqual($this->Shell->nl(1), "\n"); + } + +/** + * testHr + * + * @return void + * @access public + */ + function testHr() { + $bar = '---------------------------------------------------------------'; + + $this->Shell->Dispatch->expectAt(0, 'stdout', array('', false)); + $this->Shell->Dispatch->expectAt(1, 'stdout', array($bar . "\n", false)); + $this->Shell->Dispatch->expectAt(2, 'stdout', array('', false)); + $this->Shell->hr(); + + $this->Shell->Dispatch->expectAt(3, 'stdout', array("\n", false)); + $this->Shell->Dispatch->expectAt(4, 'stdout', array($bar . "\n", false)); + $this->Shell->Dispatch->expectAt(5, 'stdout', array("\n", false)); + $this->Shell->hr(true); + + $this->Shell->Dispatch->expectAt(3, 'stdout', array("\n\n", false)); + $this->Shell->Dispatch->expectAt(4, 'stdout', array($bar . "\n", false)); + $this->Shell->Dispatch->expectAt(5, 'stdout', array("\n\n", false)); + $this->Shell->hr(2); + } + +/** + * testError + * + * @return void + * @access public + */ + function testError() { + $this->Shell->Dispatch->expectAt(0, 'stderr', array("Error: Foo Not Found\n")); + $this->Shell->error('Foo Not Found'); + $this->assertIdentical($this->Shell->stopped, 1); + + $this->Shell->stopped = null; + + $this->Shell->Dispatch->expectAt(1, 'stderr', array("Error: Foo Not Found\n")); + $this->Shell->Dispatch->expectAt(2, 'stderr', array("Searched all...\n")); + $this->Shell->error('Foo Not Found', 'Searched all...'); + $this->assertIdentical($this->Shell->stopped, 1); + } + +/** + * testLoadTasks method + * + * @return void + * @access public + */ + function testLoadTasks() { + $this->assertTrue($this->Shell->loadTasks()); + + $this->Shell->tasks = null; + $this->assertTrue($this->Shell->loadTasks()); + + $this->Shell->tasks = false; + $this->assertTrue($this->Shell->loadTasks()); + + $this->Shell->tasks = true; + $this->assertTrue($this->Shell->loadTasks()); + + $this->Shell->tasks = array(); + $this->assertTrue($this->Shell->loadTasks()); + + // Fatal Error + // $this->Shell->tasks = 'TestIDontExist'; + // $this->assertFalse($this->Shell->loadTasks()); + // $this->assertFalse(isset($this->Shell->TestIDontExist)); + + $this->Shell->tasks = 'TestApple'; + $this->assertTrue($this->Shell->loadTasks()); + $this->assertIsA($this->Shell->TestApple, 'TestAppleTask'); + + $this->Shell->tasks = 'TestBanana'; + $this->assertTrue($this->Shell->loadTasks()); + $this->assertIsA($this->Shell->TestApple, 'TestAppleTask'); + $this->assertIsA($this->Shell->TestBanana, 'TestBananaTask'); + + unset($this->Shell->ShellTestApple, $this->Shell->TestBanana); + + $this->Shell->tasks = array('TestApple', 'TestBanana'); + $this->assertTrue($this->Shell->loadTasks()); + $this->assertIsA($this->Shell->TestApple, 'TestAppleTask'); + $this->assertIsA($this->Shell->TestBanana, 'TestBananaTask'); + } + +/** + * testShortPath method + * + * @return void + * @access public + */ + function testShortPath() { + $path = $expected = DS . 'tmp' . DS . 'ab' . DS . 'cd'; + $this->assertEqual($this->Shell->shortPath($path), $expected); + + $path = $expected = DS . 'tmp' . DS . 'ab' . DS . 'cd' . DS ; + $this->assertEqual($this->Shell->shortPath($path), $expected); + + $path = $expected = DS . 'tmp' . DS . 'ab' . DS . 'index.php'; + $this->assertEqual($this->Shell->shortPath($path), $expected); + + // Shell::shortPath needs Folder::realpath + // $path = DS . 'tmp' . DS . 'ab' . DS . '..' . DS . 'cd'; + // $expected = DS . 'tmp' . DS . 'cd'; + // $this->assertEqual($this->Shell->shortPath($path), $expected); + + $path = DS . 'tmp' . DS . 'ab' . DS . DS . 'cd'; + $expected = DS . 'tmp' . DS . 'ab' . DS . 'cd'; + $this->assertEqual($this->Shell->shortPath($path), $expected); + + $path = 'tmp' . DS . 'ab'; + $expected = 'tmp' . DS . 'ab'; + $this->assertEqual($this->Shell->shortPath($path), $expected); + + $path = 'tmp' . DS . 'ab'; + $expected = 'tmp' . DS . 'ab'; + $this->assertEqual($this->Shell->shortPath($path), $expected); + + $path = APP; + $expected = DS . basename(APP) . DS; + $this->assertEqual($this->Shell->shortPath($path), $expected); + + $path = APP . 'index.php'; + $expected = DS . basename(APP) . DS . 'index.php'; + $this->assertEqual($this->Shell->shortPath($path), $expected); + } + +/** + * testCreateFile method + * + * @return void + * @access public + */ + function testCreateFile() { + $this->skipIf(DIRECTORY_SEPARATOR === '\\', '%s Not supported on Windows'); + + $path = TMP . 'shell_test'; + $file = $path . DS . 'file1.php'; + + new Folder($path, true); + + $this->Shell->interactive = false; + + $contents = ""; + $result = $this->Shell->createFile($file, $contents); + $this->assertTrue($result); + $this->assertTrue(file_exists($file)); + $this->assertEqual(file_get_contents($file), $contents); + + $contents = ""; + $result = $this->Shell->createFile($file, $contents); + $this->assertTrue($result); + $this->assertTrue(file_exists($file)); + $this->assertEqual(file_get_contents($file), $contents); + + $this->Shell->interactive = true; + + $this->Shell->Dispatch->setReturnValueAt(0, 'getInput', 'n'); + $this->Shell->Dispatch->expectAt(1, 'stdout', array('File exists, overwrite?', '*')); + + $contents = ""; + $result = $this->Shell->createFile($file, $contents); + $this->assertFalse($result); + $this->assertTrue(file_exists($file)); + $this->assertNotEqual(file_get_contents($file), $contents); + + $this->Shell->Dispatch->setReturnValueAt(1, 'getInput', 'y'); + $this->Shell->Dispatch->expectAt(3, 'stdout', array('File exists, overwrite?', '*')); + + $result = $this->Shell->createFile($file, $contents); + $this->assertTrue($result); + $this->assertTrue(file_exists($file)); + $this->assertEqual(file_get_contents($file), $contents); + + $Folder = new Folder($path); + $Folder->delete(); + } + +/** + * testCreateFileWindows method + * + * @return void + * @access public + */ + function testCreateFileWindows() { + $this->skipUnless(DIRECTORY_SEPARATOR === '\\', 'testCreateFileWindows supported on Windows only'); + + $path = TMP . 'shell_test'; + $file = $path . DS . 'file1.php'; + + new Folder($path, true); + + $this->Shell->interactive = false; + + $contents = ""; + $result = $this->Shell->createFile($file, $contents); + $this->assertTrue($result); + $this->assertTrue(file_exists($file)); + $this->assertEqual(file_get_contents($file), $contents); + + $contents = ""; + $result = $this->Shell->createFile($file, $contents); + $this->assertTrue($result); + $this->assertTrue(file_exists($file)); + $this->assertEqual(file_get_contents($file), $contents); + + $this->Shell->interactive = true; + + $this->Shell->Dispatch->setReturnValueAt(0, 'getInput', 'n'); + $this->Shell->Dispatch->expectAt(1, 'stdout', array('File exists, overwrite?')); + + $contents = ""; + $result = $this->Shell->createFile($file, $contents); + $this->assertFalse($result); + $this->assertTrue(file_exists($file)); + $this->assertNotEqual(file_get_contents($file), $contents); + + $this->Shell->Dispatch->setReturnValueAt(1, 'getInput', 'y'); + $this->Shell->Dispatch->expectAt(3, 'stdout', array('File exists, overwrite?')); + + $result = $this->Shell->createFile($file, $contents); + $this->assertTrue($result); + $this->assertTrue(file_exists($file)); + $this->assertEqual(file_get_contents($file), $contents); + + $Folder = new Folder($path); + $Folder->delete(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/controller.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/controller.test.php new file mode 100644 index 000000000..3dc758ac3 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/controller.test.php @@ -0,0 +1,633 @@ +Dispatcher =& new TestControllerTaskMockShellDispatcher(); + $this->Task =& new MockControllerTask($this->Dispatcher); + $this->Task->name = 'ControllerTask'; + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->Dispatch->shellPaths = App::path('shells'); + $this->Task->Template =& new TemplateTask($this->Task->Dispatch); + $this->Task->Template->params['theme'] = 'default'; + $this->Task->Model =& new ControllerMockModelTask($this->Task->Dispatch); + $this->Task->Project =& new ControllerMockProjectTask($this->Task->Dispatch); + $this->Task->Test =& new ControllerMockTestTask(); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + unset($this->Task, $this->Dispatcher); + ClassRegistry::flush(); + } + +/** + * test ListAll + * + * @return void + * @access public + */ + function testListAll() { + $this->Task->connection = 'test_suite'; + $this->Task->interactive = true; + $this->Task->expectAt(1, 'out', array('1. Articles')); + $this->Task->expectAt(2, 'out', array('2. ArticlesTags')); + $this->Task->expectAt(3, 'out', array('3. Comments')); + $this->Task->expectAt(4, 'out', array('4. Tags')); + + $expected = array('Articles', 'ArticlesTags', 'Comments', 'Tags'); + $result = $this->Task->listAll('test_suite'); + $this->assertEqual($result, $expected); + + $this->Task->expectAt(6, 'out', array('1. Articles')); + $this->Task->expectAt(7, 'out', array('2. ArticlesTags')); + $this->Task->expectAt(8, 'out', array('4. Comments')); + $this->Task->expectAt(9, 'out', array('5. Tags')); + + $this->Task->interactive = false; + $result = $this->Task->listAll(); + + $expected = array('articles', 'articles_tags', 'comments', 'tags'); + $this->assertEqual($result, $expected); + } + +/** + * Test that getName interacts with the user and returns the controller name. + * + * @return void + * @access public + */ + function testGetName() { + $this->Task->interactive = true; + $this->Task->setReturnValue('in', 1); + + $this->Task->setReturnValueAt(0, 'in', 'q'); + $this->Task->expectOnce('_stop'); + $this->Task->getName('test_suite'); + + $this->Task->setReturnValueAt(1, 'in', 1); + $result = $this->Task->getName('test_suite'); + $expected = 'Articles'; + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(2, 'in', 3); + $result = $this->Task->getName('test_suite'); + $expected = 'Comments'; + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(3, 'in', 10); + $result = $this->Task->getName('test_suite'); + $this->Task->expectOnce('err'); + } + +/** + * test helper interactions + * + * @return void + * @access public + */ + function testDoHelpers() { + $this->Task->setReturnValue('in', 'n'); + $result = $this->Task->doHelpers(); + $this->assertEqual($result, array()); + + $this->Task->setReturnValueAt(1, 'in', 'y'); + $this->Task->setReturnValueAt(2, 'in', ' Javascript, Ajax, CustomOne '); + $result = $this->Task->doHelpers(); + $expected = array('Javascript', 'Ajax', 'CustomOne'); + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(3, 'in', 'y'); + $this->Task->setReturnValueAt(4, 'in', ' Javascript, Ajax, CustomOne, , '); + $result = $this->Task->doHelpers(); + $expected = array('Javascript', 'Ajax', 'CustomOne'); + $this->assertEqual($result, $expected); + } + +/** + * test component interactions + * + * @return void + * @access public + */ + function testDoComponents() { + $this->Task->setReturnValue('in', 'n'); + $result = $this->Task->doComponents(); + $this->assertEqual($result, array()); + + $this->Task->setReturnValueAt(1, 'in', 'y'); + $this->Task->setReturnValueAt(2, 'in', ' RequestHandler, Security '); + $result = $this->Task->doComponents(); + $expected = array('RequestHandler', 'Security'); + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(3, 'in', 'y'); + $this->Task->setReturnValueAt(4, 'in', ' RequestHandler, Security, , '); + $result = $this->Task->doComponents(); + $expected = array('RequestHandler', 'Security'); + $this->assertEqual($result, $expected); + } + +/** + * test Confirming controller user interaction + * + * @return void + * @access public + */ + function testConfirmController() { + $controller = 'Posts'; + $scaffold = false; + $helpers = array('Ajax', 'Time'); + $components = array('Acl', 'Auth'); + $uses = array('Comment', 'User'); + + $this->Task->expectAt(2, 'out', array("Controller Name:\n\t$controller")); + $this->Task->expectAt(3, 'out', array("Helpers:\n\tAjax, Time")); + $this->Task->expectAt(4, 'out', array("Components:\n\tAcl, Auth")); + $this->Task->confirmController($controller, $scaffold, $helpers, $components); + } + +/** + * test the bake method + * + * @return void + * @access public + */ + function testBake() { + $helpers = array('Ajax', 'Time'); + $components = array('Acl', 'Auth'); + $this->Task->setReturnValue('createFile', true); + + $result = $this->Task->bake('Articles', '--actions--', $helpers, $components); + $this->assertPattern('/class ArticlesController extends AppController/', $result); + $this->assertPattern('/\$components \= array\(\'Acl\', \'Auth\'\)/', $result); + $this->assertPattern('/\$helpers \= array\(\'Ajax\', \'Time\'\)/', $result); + $this->assertPattern('/\-\-actions\-\-/', $result); + + $result = $this->Task->bake('Articles', 'scaffold', $helpers, $components); + $this->assertPattern('/class ArticlesController extends AppController/', $result); + $this->assertPattern('/var \$scaffold/', $result); + $this->assertNoPattern('/helpers/', $result); + $this->assertNoPattern('/components/', $result); + + $result = $this->Task->bake('Articles', '--actions--', array(), array()); + $this->assertPattern('/class ArticlesController extends AppController/', $result); + $this->assertNoPattern('/components/', $result); + $this->assertNoPattern('/helpers/', $result); + $this->assertPattern('/\-\-actions\-\-/', $result); + } + +/** + * test bake() with a -plugin param + * + * @return void + * @access public + */ + function testBakeWithPlugin() { + $this->Task->plugin = 'ControllerTest'; + $helpers = array('Ajax', 'Time'); + $components = array('Acl', 'Auth'); + $uses = array('Comment', 'User'); + + $path = APP . 'plugins' . DS . 'controller_test' . DS . 'controllers' . DS . 'articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array($path, '*')); + $this->Task->bake('Articles', '--actions--', array(), array(), array()); + + $this->Task->plugin = 'controllerTest'; + $path = APP . 'plugins' . DS . 'controller_test' . DS . 'controllers' . DS . 'articles_controller.php'; + $this->Task->expectAt(1, 'createFile', array( + $path, new PatternExpectation('/ArticlesController extends ControllerTestAppController/'))); + $this->Task->bake('Articles', '--actions--', array(), array(), array()); + + $this->assertEqual($this->Task->Template->templateVars['plugin'], 'ControllerTest'); + } + +/** + * test that bakeActions is creating the correct controller Code. (Using sessions) + * + * @return void + * @access public + */ + function testBakeActionsUsingSessions() { + $skip = $this->skipIf(!defined('ARTICLE_MODEL_CREATED'), + 'Testing bakeActions requires Article, Comment & Tag Model to be undefined. %s'); + if ($skip) { + return; + } + $result = $this->Task->bakeActions('Articles', null, true); + + $this->assertTrue(strpos($result, 'function index() {') !== false); + $this->assertTrue(strpos($result, '$this->Article->recursive = 0;') !== false); + $this->assertTrue(strpos($result, "\$this->set('articles', \$this->paginate());") !== false); + + $this->assertTrue(strpos($result, 'function view($id = null)') !== false); + $this->assertTrue(strpos($result, "\$this->Session->setFlash(__('Invalid article', true));") !== false); + $this->assertTrue(strpos($result, "\$this->set('article', \$this->Article->read(null, \$id)") !== false); + + $this->assertTrue(strpos($result, 'function add()') !== false); + $this->assertTrue(strpos($result, 'if (!empty($this->data))') !== false); + $this->assertTrue(strpos($result, 'if ($this->Article->save($this->data))') !== false); + $this->assertTrue(strpos($result, "\$this->Session->setFlash(__('The article has been saved', true));") !== false); + + $this->assertTrue(strpos($result, 'function edit($id = null)') !== false); + $this->assertTrue(strpos($result, "\$this->Session->setFlash(__('The article could not be saved. Please, try again.', true));") !== false); + + $this->assertTrue(strpos($result, 'function delete($id = null)') !== false); + $this->assertTrue(strpos($result, 'if ($this->Article->delete($id))') !== false); + $this->assertTrue(strpos($result, "\$this->Session->setFlash(__('Article deleted', true));") !== false); + + $result = $this->Task->bakeActions('Articles', 'admin_', true); + + $this->assertTrue(strpos($result, 'function admin_index() {') !== false); + $this->assertTrue(strpos($result, 'function admin_add()') !== false); + $this->assertTrue(strpos($result, 'function admin_view($id = null)') !== false); + $this->assertTrue(strpos($result, 'function admin_edit($id = null)') !== false); + $this->assertTrue(strpos($result, 'function admin_delete($id = null)') !== false); + } + +/** + * Test baking with Controller::flash() or no sessions. + * + * @return void + * @access public + */ + function testBakeActionsWithNoSessions() { + $skip = $this->skipIf(!defined('ARTICLE_MODEL_CREATED'), + 'Testing bakeActions requires Article, Tag, Comment Models to be undefined. %s'); + if ($skip) { + return; + } + $result = $this->Task->bakeActions('Articles', null, false); + + $this->assertTrue(strpos($result, 'function index() {') !== false); + $this->assertTrue(strpos($result, '$this->Article->recursive = 0;') !== false); + $this->assertTrue(strpos($result, "\$this->set('articles', \$this->paginate());") !== false); + + $this->assertTrue(strpos($result, 'function view($id = null)') !== false); + $this->assertTrue(strpos($result, "\$this->flash(__('Invalid article', true), array('action' => 'index'))") !== false); + $this->assertTrue(strpos($result, "\$this->set('article', \$this->Article->read(null, \$id)") !== false); + + $this->assertTrue(strpos($result, 'function add()') !== false); + $this->assertTrue(strpos($result, 'if (!empty($this->data))') !== false); + $this->assertTrue(strpos($result, 'if ($this->Article->save($this->data))') !== false); + $this->assertTrue(strpos($result, "\$this->flash(__('The article has been saved.', true), array('action' => 'index'))") !== false); + + $this->assertTrue(strpos($result, 'function edit($id = null)') !== false); + $this->assertTrue(strpos($result, "\$this->Article->Tag->find('list')") !== false); + $this->assertTrue(strpos($result, "\$this->set(compact('tags'))") !== false); + + $this->assertTrue(strpos($result, 'function delete($id = null)') !== false); + $this->assertTrue(strpos($result, 'if ($this->Article->delete($id))') !== false); + $this->assertTrue(strpos($result, "\$this->flash(__('Article deleted', true), array('action' => 'index'))") !== false); + } + +/** + * test baking a test + * + * @return void + * @access public + */ + function testBakeTest() { + $this->Task->plugin = 'ControllerTest'; + $this->Task->connection = 'test_suite'; + $this->Task->interactive = false; + + $this->Task->Test->expectOnce('bake', array('Controller', 'Articles')); + $this->Task->bakeTest('Articles'); + + $this->assertEqual($this->Task->plugin, $this->Task->Test->plugin); + $this->assertEqual($this->Task->connection, $this->Task->Test->connection); + $this->assertEqual($this->Task->interactive, $this->Task->Test->interactive); + } + +/** + * test Interactive mode. + * + * @return void + * @access public + */ + function testInteractive() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path'; + $this->Task->setReturnValue('in', '1'); + $this->Task->setReturnValueAt(1, 'in', 'y'); // build interactive + $this->Task->setReturnValueAt(2, 'in', 'n'); // build no scaffolds + $this->Task->setReturnValueAt(3, 'in', 'y'); // build normal methods + $this->Task->setReturnValueAt(4, 'in', 'n'); // build admin methods + $this->Task->setReturnValueAt(5, 'in', 'n'); // helpers? + $this->Task->setReturnValueAt(6, 'in', 'n'); // components? + $this->Task->setReturnValueAt(7, 'in', 'y'); // use sessions + $this->Task->setReturnValueAt(8, 'in', 'y'); // looks good + + $this->Task->execute(); + + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class ArticlesController/'))); + } + +/** + * test Interactive mode. + * + * @return void + * @access public + */ + function testInteractiveAdminMethodsNotInteractive() { + $this->Task->connection = 'test_suite'; + $this->Task->interactive = true; + $this->Task->path = '/my/path'; + $this->Task->setReturnValue('in', '1'); + $this->Task->setReturnValueAt(1, 'in', 'y'); // build interactive + $this->Task->setReturnValueAt(2, 'in', 'n'); // build no scaffolds + $this->Task->setReturnValueAt(3, 'in', 'y'); // build normal methods + $this->Task->setReturnValueAt(4, 'in', 'y'); // build admin methods + $this->Task->setReturnValueAt(5, 'in', 'n'); // helpers? + $this->Task->setReturnValueAt(6, 'in', 'n'); // components? + $this->Task->setReturnValueAt(7, 'in', 'y'); // use sessions + $this->Task->setReturnValueAt(8, 'in', 'y'); // looks good + $this->Task->setReturnValue('createFile', true); + $this->Task->Project->setReturnValue('getPrefix', 'admin_'); + + $result = $this->Task->execute(); + $this->assertPattern('/admin_index/', $result); + + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class ArticlesController/'))); + } + +/** + * test that execute runs all when the first arg == all + * + * @return void + * @access public + */ + function testExecuteIntoAll() { + $skip = $this->skipIf(!defined('ARTICLE_MODEL_CREATED'), + 'Execute into all could not be run as an Article, Tag or Comment model was already loaded. %s'); + if ($skip) { + return; + } + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('all'); + + $this->Task->setReturnValue('createFile', true); + $this->Task->setReturnValue('_checkUnitTest', true); + $this->Task->Test->expectCallCount('bake', 1); + + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class ArticlesController/'))); + + $this->Task->execute(); + } + +/** + * test that `cake bake controller foos` works. + * + * @return void + * @access public + */ + function testExecuteWithController() { + $skip = $this->skipIf(!defined('ARTICLE_MODEL_CREATED'), + 'Execute with scaffold param requires no Article, Tag or Comment model to be defined. %s'); + if ($skip) { + return; + } + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('Articles'); + + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array( + $filename, new PatternExpectation('/\$scaffold/') + )); + + $this->Task->execute(); + } + +/** + * test that both plural and singular forms work for controller baking. + * + * @return void + * @access public + */ + function testExecuteWithControllerNameVariations() { + $skip = $this->skipIf(!defined('ARTICLE_MODEL_CREATED'), + 'Execute with scaffold param requires no Article, Tag or Comment model to be defined. %s'); + if ($skip) { + return; + } + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('Articles'); + + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array( + $filename, new PatternExpectation('/\$scaffold/') + )); + + $this->Task->execute(); + + $this->Task->args = array('Article'); + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(1, 'createFile', array( + $filename, new PatternExpectation('/class ArticlesController/') + )); + $this->Task->execute(); + + $this->Task->args = array('article'); + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(2, 'createFile', array( + $filename, new PatternExpectation('/class ArticlesController/') + )); + + $this->Task->args = array('articles'); + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(3, 'createFile', array( + $filename, new PatternExpectation('/class ArticlesController/') + )); + $this->Task->execute(); + + $this->Task->args = array('Articles'); + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(4, 'createFile', array( + $filename, new PatternExpectation('/class ArticlesController/') + )); + $this->Task->execute(); + $this->Task->execute(); + } + +/** + * test that `cake bake controller foo scaffold` works. + * + * @return void + * @access public + */ + function testExecuteWithPublicParam() { + $skip = $this->skipIf(!defined('ARTICLE_MODEL_CREATED'), + 'Execute with scaffold param requires no Article, Tag or Comment model to be defined. %s'); + if ($skip) { + return; + } + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('Articles', 'public'); + + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array( + $filename, new NoPatternExpectation('/var \$scaffold/') + )); + + $this->Task->execute(); + } + +/** + * test that `cake bake controller foos both` works. + * + * @return void + * @access public + */ + function testExecuteWithControllerAndBoth() { + $skip = $this->skipIf(!defined('ARTICLE_MODEL_CREATED'), + 'Execute with scaffold param requires no Article, Tag or Comment model to be defined. %s'); + if ($skip) { + return; + } + $this->Task->Project->setReturnValue('getPrefix', 'admin_'); + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('Articles', 'public', 'admin'); + + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array( + $filename, new PatternExpectation('/admin_index/') + )); + + $this->Task->execute(); + } + +/** + * test that `cake bake controller foos admin` works. + * + * @return void + * @access public + */ + function testExecuteWithControllerAndAdmin() { + $skip = $this->skipIf(!defined('ARTICLE_MODEL_CREATED'), + 'Execute with scaffold param requires no Article, Tag or Comment model to be defined. %s'); + if ($skip) { + return; + } + $this->Task->Project->setReturnValue('getPrefix', 'admin_'); + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('Articles', 'admin'); + + $filename = '/my/path/articles_controller.php'; + $this->Task->expectAt(0, 'createFile', array( + $filename, new PatternExpectation('/admin_index/') + )); + + $this->Task->execute(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/db_config.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/db_config.test.php new file mode 100644 index 000000000..1fff44a8e --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/db_config.test.php @@ -0,0 +1,155 @@ + 'mysql', + 'persistent' => false, + 'host' => 'localhost', + 'login' => 'user', + 'password' => 'password', + 'database' => 'database_name', + 'prefix' => '', + ); + + var $otherOne = array( + 'driver' => 'mysql', + 'persistent' => false, + 'host' => 'localhost', + 'login' => 'user', + 'password' => 'password', + 'database' => 'other_one', + 'prefix' => '', + ); +} + +/** + * DbConfigTest class + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class DbConfigTaskTest extends CakeTestCase { + +/** + * startTest method + * + * @return void + * @access public + */ + function startTest() { + $this->Dispatcher =& new TestDbConfigTaskMockShellDispatcher(); + $this->Task =& new MockDbConfigTask($this->Dispatcher); + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->Dispatch->shellPaths = App::path('shells'); + + $this->Task->params['working'] = rtrim(APP, DS); + $this->Task->databaseClassName = 'TEST_DATABASE_CONFIG'; + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + unset($this->Task, $this->Dispatcher); + ClassRegistry::flush(); + } + +/** + * Test the getConfig method. + * + * @return void + * @access public + */ + function testGetConfig() { + $this->Task->setReturnValueAt(0, 'in', 'otherOne'); + $result = $this->Task->getConfig(); + $this->assertEqual($result, 'otherOne'); + } + +/** + * test that initialize sets the path up. + * + * @return void + * @access public + */ + function testInitialize() { + $this->assertTrue(empty($this->Task->path)); + $this->Task->initialize(); + $this->assertFalse(empty($this->Task->path)); + $this->assertEqual($this->Task->path, APP . 'config' . DS); + + } + +/** + * test execute and by extension __interactive + * + * @return void + * @access public + */ + function testExecuteIntoInteractive() { + $this->Task->initialize(); + + $this->Task->expectOnce('_stop'); + $this->Task->setReturnValue('in', 'y'); + $this->Task->setReturnValueAt(0, 'in', 'default'); + $this->Task->setReturnValueAt(1, 'in', 'n'); + $this->Task->setReturnValueAt(2, 'in', 'localhost'); + $this->Task->setReturnValueAt(3, 'in', 'n'); + $this->Task->setReturnValueAt(4, 'in', 'root'); + $this->Task->setReturnValueAt(5, 'in', 'password'); + $this->Task->setReturnValueAt(6, 'in', 'cake_test'); + $this->Task->setReturnValueAt(7, 'in', 'n'); + $this->Task->setReturnValueAt(8, 'in', 'y'); + $this->Task->setReturnValueAt(9, 'in', 'y'); + $this->Task->setReturnValueAt(10, 'in', 'y'); + $this->Task->setReturnValueAt(11, 'in', 'n'); + + $result = $this->Task->execute(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/extract.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/extract.test.php new file mode 100644 index 000000000..f93a9a6a7 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/extract.test.php @@ -0,0 +1,187 @@ +Dispatcher =& new TestExtractTaskMockShellDispatcher(); + $this->Task =& new ExtractTask($this->Dispatcher); + } + +/** + * tearDown method + * + * @return void + * @access public + */ + function tearDown() { + ClassRegistry::flush(); + } + +/** + * testExecute method + * + * @return void + * @access public + */ + function testExecute() { + $path = TMP . 'tests' . DS . 'extract_task_test'; + new Folder($path . DS . 'locale', true); + + $this->Task->interactive = false; + + $this->Task->params['paths'] = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'pages'; + $this->Task->params['output'] = $path . DS; + $this->Task->Dispatch->expectNever('stderr'); + $this->Task->Dispatch->expectNever('_stop'); + $this->Task->execute(); + $this->assertTrue(file_exists($path . DS . 'default.pot')); + $result = file_get_contents($path . DS . 'default.pot'); + + $pattern = '/"Content-Type\: text\/plain; charset\=utf-8/'; + $this->assertPattern($pattern, $result); + $pattern = '/"Content-Transfer-Encoding\: 8bit/'; + $this->assertPattern($pattern, $result); + $pattern = '/"Plural-Forms\: nplurals\=INTEGER; plural\=EXPRESSION;/'; + $this->assertPattern($pattern, $result); + + // home.ctp + $pattern = '/msgid "Your tmp directory is writable."\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "Your tmp directory is NOT writable."\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "The %s is being used for caching. To change the config edit '; + $pattern .= 'APP\/config\/core.php "\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "Your cache is NOT working. Please check '; + $pattern .= 'the settings in APP\/config\/core.php"\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "Your database configuration file is present."\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "Your database configuration file is NOT present."\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "Rename config\/database.php.default to '; + $pattern .= 'config\/database.php"\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "Cake is able to connect to the database."\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "Cake is NOT able to connect to the database."\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "Editing this Page"\nmsgstr ""\n/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "To change the content of this page, edit: %s.*To change its layout, '; + $pattern .= 'edit: %s.*You can also add some CSS styles for your pages at: %s"\nmsgstr ""/s'; + $this->assertPattern($pattern, $result); + + // extract.ctp + $pattern = '/\#: (\\\\|\/)extract\.ctp:6\n'; + $pattern .= 'msgid "You have %d new message."\nmsgid_plural "You have %d new messages."/'; + $this->assertPattern($pattern, $result); + + $pattern = '/\#: (\\\\|\/)extract\.ctp:7\n'; + $pattern .= 'msgid "You deleted %d message."\nmsgid_plural "You deleted %d messages."/'; + $this->assertPattern($pattern, $result); + + $pattern = '/\#: (\\\\|\/)extract\.ctp:14\n'; + $pattern .= '\#: (\\\\|\/)home\.ctp:74\n'; + $pattern .= 'msgid "Editing this Page"\nmsgstr ""/'; + $this->assertPattern($pattern, $result); + + // extract.ctp - reading the domain.pot + $result = file_get_contents($path . DS . 'domain.pot'); + + $pattern = '/msgid "You have %d new message."\nmsgid_plural "You have %d new messages."/'; + $this->assertNoPattern($pattern, $result); + $pattern = '/msgid "You deleted %d message."\nmsgid_plural "You deleted %d messages."/'; + $this->assertNoPattern($pattern, $result); + + $pattern = '/msgid "You have %d new message \(domain\)."\nmsgid_plural "You have %d new messages \(domain\)."/'; + $this->assertPattern($pattern, $result); + $pattern = '/msgid "You deleted %d message \(domain\)."\nmsgid_plural "You deleted %d messages \(domain\)."/'; + $this->assertPattern($pattern, $result); + + $Folder = new Folder($path); + $Folder->delete(); + } + function getTests() { + return array('start', 'startCase', 'testExtractMultiplePaths', 'endCase', 'end'); + } + +/** + * test extract can read more than one path. + * + * @return void + */ + function testExtractMultiplePaths() { + $path = TMP . 'tests' . DS . 'extract_task_test'; + new Folder($path . DS . 'locale', true); + + $this->Task->interactive = false; + + $this->Task->params['paths'] = + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'pages,' . + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'posts'; + + $this->Task->params['output'] = $path . DS; + $this->Task->Dispatch->expectNever('stderr'); + $this->Task->Dispatch->expectNever('_stop'); + $this->Task->execute(); + + $result = file_get_contents($path . DS . 'default.pot'); + + $pattern = '/msgid "Add User"/'; + $this->assertPattern($pattern, $result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/fixture.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/fixture.test.php new file mode 100644 index 000000000..45920d393 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/fixture.test.php @@ -0,0 +1,373 @@ +Dispatcher =& new TestFixtureTaskMockShellDispatcher(); + $this->Task =& new MockFixtureTask(); + $this->Task->Model =& new MockFixtureModelTask(); + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->Template =& new TemplateTask($this->Task->Dispatch); + $this->Task->Dispatch->shellPaths = App::path('shells'); + $this->Task->Template->initialize(); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + unset($this->Task, $this->Dispatcher); + ClassRegistry::flush(); + } + +/** + * test that initialize sets the path + * + * @return void + * @access public + */ + function testConstruct() { + $this->Dispatch->params['working'] = DS . 'my' . DS . 'path'; + $Task =& new FixtureTask($this->Dispatch); + + $expected = DS . 'my' . DS . 'path' . DS . 'tests' . DS . 'fixtures' . DS; + $this->assertEqual($Task->path, $expected); + } + +/** + * test import option array generation + * + * @return void + * @access public + */ + function testImportOptions() { + $this->Task->setReturnValueAt(0, 'in', 'y'); + $this->Task->setReturnValueAt(1, 'in', 'y'); + + $result = $this->Task->importOptions('Article'); + $expected = array('schema' => 'Article', 'records' => true); + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(2, 'in', 'n'); + $this->Task->setReturnValueAt(3, 'in', 'n'); + $this->Task->setReturnValueAt(4, 'in', 'n'); + + $result = $this->Task->importOptions('Article'); + $expected = array(); + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(5, 'in', 'n'); + $this->Task->setReturnValueAt(6, 'in', 'n'); + $this->Task->setReturnValueAt(7, 'in', 'y'); + $result = $this->Task->importOptions('Article'); + $expected = array('fromTable' => true); + $this->assertEqual($result, $expected); + } + +/** + * test that connection gets set to the import options when a different connection is used. + * + * @return void + */ + function testImportOptionsAlternateConnection() { + $this->Task->connection = 'test'; + $result = $this->Task->bake('Article', false, array('schema' => 'Article')); + $this->assertPattern("/'connection' => 'test'/", $result); + } + +/** + * test generating a fixture with database conditions. + * + * @return void + * @access public + */ + function testImportRecordsFromDatabaseWithConditions() { + $this->Task->interactive = true; + $this->Task->setReturnValueAt(0, 'in', 'WHERE 1=1 LIMIT 10'); + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $result = $this->Task->bake('Article', false, array('fromTable' => true, 'schema' => 'Article', 'records' => false)); + + $this->assertPattern('/class ArticleFixture extends CakeTestFixture/', $result); + $this->assertPattern('/var \$records/', $result); + $this->assertPattern('/var \$import/', $result); + $this->assertPattern("/'title' => 'First Article'/", $result, 'Missing import data %s'); + $this->assertPattern('/Second Article/', $result, 'Missing import data %s'); + $this->assertPattern('/Third Article/', $result, 'Missing import data %s'); + } + +/** + * test that execute passes runs bake depending with named model. + * + * @return void + * @access public + */ + function testExecuteWithNamedModel() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('article'); + $filename = '/my/path/article_fixture.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class ArticleFixture/'))); + $this->Task->execute(); + } + +/** + * test that execute passes runs bake depending with named model. + * + * @return void + * @access public + */ + function testExecuteWithNamedModelVariations() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + + $this->Task->args = array('article'); + $filename = '/my/path/article_fixture.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class ArticleFixture/'))); + $this->Task->execute(); + + $this->Task->args = array('articles'); + $filename = '/my/path/article_fixture.php'; + $this->Task->expectAt(1, 'createFile', array($filename, new PatternExpectation('/class ArticleFixture/'))); + $this->Task->execute(); + + $this->Task->args = array('Articles'); + $filename = '/my/path/article_fixture.php'; + $this->Task->expectAt(2, 'createFile', array($filename, new PatternExpectation('/class ArticleFixture/'))); + $this->Task->execute(); + + $this->Task->args = array('Article'); + $filename = '/my/path/article_fixture.php'; + $this->Task->expectAt(3, 'createFile', array($filename, new PatternExpectation('/class ArticleFixture/'))); + $this->Task->execute(); + } + +/** + * test that execute runs all() when args[0] = all + * + * @return void + * @access public + */ + function testExecuteIntoAll() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('all'); + $this->Task->Model->setReturnValue('listAll', array('articles', 'comments')); + + $filename = '/my/path/article_fixture.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class ArticleFixture/'))); + $this->Task->execute(); + + $filename = '/my/path/comment_fixture.php'; + $this->Task->expectAt(1, 'createFile', array($filename, new PatternExpectation('/class CommentFixture/'))); + $this->Task->execute(); + } + +/** + * test using all() with -count and -records + * + * @return void + * @access public + */ + function testAllWithCountAndRecordsFlags() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('all'); + $this->Task->params = array('count' => 10, 'records' => true); + $this->Task->Model->setReturnValue('listAll', array('articles', 'comments')); + + $filename = '/my/path/article_fixture.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/title\' => \'Third Article\'/'))); + + $filename = '/my/path/comment_fixture.php'; + $this->Task->expectAt(1, 'createFile', array($filename, new PatternExpectation('/comment\' => \'First Comment for First Article/'))); + $this->Task->expectCallCount('createFile', 2); + $this->Task->all(); + } + +/** + * test interactive mode of execute + * + * @return void + * @access public + */ + function testExecuteInteractive() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + + $this->Task->setReturnValue('in', 'y'); + $this->Task->Model->setReturnValue('getName', 'Article'); + $this->Task->Model->setReturnValue('getTable', 'articles', array('Article')); + + $filename = '/my/path/article_fixture.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class ArticleFixture/'))); + $this->Task->execute(); + } + +/** + * Test that bake works + * + * @return void + * @access public + */ + function testBake() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + + $result = $this->Task->bake('Article'); + $this->assertPattern('/class ArticleFixture extends CakeTestFixture/', $result); + $this->assertPattern('/var \$fields/', $result); + $this->assertPattern('/var \$records/', $result); + $this->assertNoPattern('/var \$import/', $result); + + $result = $this->Task->bake('Article', 'comments'); + $this->assertPattern('/class ArticleFixture extends CakeTestFixture/', $result); + $this->assertPattern('/var \$name \= \'Article\';/', $result); + $this->assertPattern('/var \$table \= \'comments\';/', $result); + $this->assertPattern('/var \$fields = array\(/', $result); + + $result = $this->Task->bake('Article', 'comments', array('records' => true)); + $this->assertPattern( + "/var \\\$import \= array\('records' \=\> true, 'connection' => 'test_suite'\);/", + $result + ); + $this->assertNoPattern('/var \$records/', $result); + + $result = $this->Task->bake('Article', 'comments', array('schema' => 'Article')); + $this->assertPattern( + "/var \\\$import \= array\('model' \=\> 'Article', 'connection' => 'test_suite'\);/", + $result + ); + $this->assertNoPattern('/var \$fields/', $result); + + $result = $this->Task->bake('Article', 'comments', array('schema' => 'Article', 'records' => true)); + $this->assertPattern( + "/var \\\$import \= array\('model' \=\> 'Article'\, 'records' \=\> true, 'connection' => 'test_suite'\);/", + $result + ); + $this->assertNoPattern('/var \$fields/', $result); + $this->assertNoPattern('/var \$records/', $result); + } + +/** + * test record generation with float and binary types + * + * @return void + * @access public + */ + function testRecordGenerationForBinaryAndFloat() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + + $result = $this->Task->bake('Article', 'datatypes'); + $this->assertPattern("/'float_field' => 1/", $result); + + $result = $this->Task->bake('Article', 'binary_tests'); + $this->assertPattern("/'data' => 'Lorem ipsum dolor sit amet'/", $result); + } + +/** + * Test that file generation includes headers and correct path for plugins. + * + * @return void + * @access public + */ + function testGenerateFixtureFile() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $filename = '/my/path/article_fixture.php'; + + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/Article/'))); + $result = $this->Task->generateFixtureFile('Article', array()); + + $this->Task->expectAt(1, 'createFile', array($filename, new PatternExpectation('/\<\?php(.*)\?\>/ms'))); + $result = $this->Task->generateFixtureFile('Article', array()); + } + +/** + * test generating files into plugins. + * + * @return void + * @access public + */ + function testGeneratePluginFixtureFile() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->plugin = 'TestFixture'; + $filename = APP . 'plugins' . DS . 'test_fixture' . DS . 'tests' . DS . 'fixtures' . DS . 'article_fixture.php'; + + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/Article/'))); + $result = $this->Task->generateFixtureFile('Article', array()); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/model.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/model.test.php new file mode 100644 index 000000000..ef8c5b39b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/model.test.php @@ -0,0 +1,919 @@ +Dispatcher =& new TestModelTaskMockShellDispatcher(); + $this->Task =& new MockModelTask($this->Dispatcher); + $this->Task->name = 'ModelTask'; + $this->Task->interactive = true; + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->Dispatch->shellPaths = App::path('shells'); + $this->Task->Template =& new TemplateTask($this->Task->Dispatch); + $this->Task->Fixture =& new MockModelTaskFixtureTask(); + $this->Task->Test =& new MockModelTaskFixtureTask(); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + unset($this->Task, $this->Dispatcher); + ClassRegistry::flush(); + } + +/** + * Test that listAll scans the database connection and lists all the tables in it.s + * + * @return void + * @access public + */ + function testListAll() { + $this->Task->expectAt(1, 'out', array('1. Article')); + $this->Task->expectAt(2, 'out', array('2. ArticlesTag')); + $this->Task->expectAt(3, 'out', array('3. CategoryThread')); + $this->Task->expectAt(4, 'out', array('4. Comment')); + $this->Task->expectAt(5, 'out', array('5. Tag')); + $result = $this->Task->listAll('test_suite'); + $expected = array('articles', 'articles_tags', 'category_threads', 'comments', 'tags'); + $this->assertEqual($result, $expected); + + $this->Task->expectAt(7, 'out', array('1. Article')); + $this->Task->expectAt(8, 'out', array('2. ArticlesTag')); + $this->Task->expectAt(9, 'out', array('3. CategoryThread')); + $this->Task->expectAt(10, 'out', array('4. Comment')); + $this->Task->expectAt(11, 'out', array('5. Tag')); + + $this->Task->connection = 'test_suite'; + $result = $this->Task->listAll(); + $expected = array('articles', 'articles_tags', 'category_threads', 'comments', 'tags'); + $this->assertEqual($result, $expected); + } + +/** + * Test that getName interacts with the user and returns the model name. + * + * @return void + * @access public + */ + function testGetName() { + $this->Task->setReturnValue('in', 1); + + $this->Task->setReturnValueAt(0, 'in', 'q'); + $this->Task->expectOnce('_stop'); + $this->Task->getName('test_suite'); + + $this->Task->setReturnValueAt(1, 'in', 1); + $result = $this->Task->getName('test_suite'); + $expected = 'Article'; + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(2, 'in', 4); + $result = $this->Task->getName('test_suite'); + $expected = 'Comment'; + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(3, 'in', 10); + $result = $this->Task->getName('test_suite'); + $this->Task->expectOnce('err'); + } + +/** + * Test table name interactions + * + * @return void + * @access public + */ + function testGetTableName() { + $this->Task->setReturnValueAt(0, 'in', 'y'); + $result = $this->Task->getTable('Article', 'test_suite'); + $expected = 'articles'; + $this->assertEqual($result, $expected); + + $this->Task->setReturnValueAt(1, 'in', 'n'); + $this->Task->setReturnValueAt(2, 'in', 'my_table'); + $result = $this->Task->getTable('Article', 'test_suite'); + $expected = 'my_table'; + $this->assertEqual($result, $expected); + } + +/** + * test that initializing the validations works. + * + * @return void + * @access public + */ + function testInitValidations() { + $result = $this->Task->initValidations(); + $this->assertTrue(in_array('notempty', $result)); + } + +/** + * test that individual field validation works, with interactive = false + * tests the guessing features of validation + * + * @return void + * @access public + */ + function testFieldValidationGuessing() { + $this->Task->interactive = false; + $this->Task->initValidations(); + + $result = $this->Task->fieldValidation('text', array('type' => 'string', 'length' => 10, 'null' => false)); + $expected = array('notempty' => 'notempty'); + $this->assertEqual($expected, $result); + + $result = $this->Task->fieldValidation('text', array('type' => 'date', 'length' => 10, 'null' => false)); + $expected = array('date' => 'date'); + $this->assertEqual($expected, $result); + + $result = $this->Task->fieldValidation('text', array('type' => 'time', 'length' => 10, 'null' => false)); + $expected = array('time' => 'time'); + $this->assertEqual($expected, $result); + + $result = $this->Task->fieldValidation('email', array('type' => 'string', 'length' => 10, 'null' => false)); + $expected = array('email' => 'email'); + $this->assertEqual($expected, $result); + + $result = $this->Task->fieldValidation('test', array('type' => 'integer', 'length' => 10, 'null' => false)); + $expected = array('numeric' => 'numeric'); + $this->assertEqual($expected, $result); + + $result = $this->Task->fieldValidation('test', array('type' => 'boolean', 'length' => 10, 'null' => false)); + $expected = array('boolean' => 'boolean'); + $this->assertEqual($expected, $result); + + $result = $this->Task->fieldValidation('test', array('type' => 'string', 'length' => 36, 'null' => false)); + $expected = array('uuid' => 'uuid'); + $this->assertEqual($expected, $result); + } + +/** + * test that interactive field validation works and returns multiple validators. + * + * @return void + * @access public + */ + function testInteractiveFieldValidation() { + $this->Task->initValidations(); + $this->Task->interactive = true; + $this->Task->setReturnValueAt(0, 'in', '19'); + $this->Task->setReturnValueAt(1, 'in', 'y'); + $this->Task->setReturnValueAt(2, 'in', '15'); + $this->Task->setReturnValueAt(3, 'in', 'n'); + + $result = $this->Task->fieldValidation('text', array('type' => 'string', 'length' => 10, 'null' => false)); + $expected = array('notempty' => 'notempty', 'maxlength' => 'maxlength'); + $this->assertEqual($result, $expected); + } + +/** + * test that a bogus response doesn't cause errors to bubble up. + * + * @return void + */ + function testInteractiveFieldValidationWithBogusResponse() { + $this->Task->initValidations(); + $this->Task->interactive = true; + $this->Task->setReturnValueAt(0, 'in', '999999'); + $this->Task->setReturnValueAt(1, 'in', '19'); + $this->Task->setReturnValueAt(2, 'in', 'n'); + $this->Task->expectAt(4, 'out', array(new PatternExpectation('/make a valid/'))); + + $result = $this->Task->fieldValidation('text', array('type' => 'string', 'length' => 10, 'null' => false)); + $expected = array('notempty' => 'notempty'); + $this->assertEqual($result, $expected); + } + +/** + * test that a regular expression can be used for validation. + * + * @return void + */ + function testInteractiveFieldValidationWithRegexp() { + $this->Task->initValidations(); + $this->Task->interactive = true; + $this->Task->setReturnValueAt(0, 'in', '/^[a-z]{0,9}$/'); + $this->Task->setReturnValueAt(1, 'in', 'n'); + + $result = $this->Task->fieldValidation('text', array('type' => 'string', 'length' => 10, 'null' => false)); + $expected = array('a_z_0_9' => '/^[a-z]{0,9}$/'); + $this->assertEqual($result, $expected); + } + +/** + * test the validation Generation routine + * + * @return void + * @access public + */ + function testNonInteractiveDoValidation() { + $Model =& new MockModelTaskModel(); + $Model->primaryKey = 'id'; + $Model->setReturnValue('schema', array( + 'id' => array( + 'type' => 'integer', + 'length' => 11, + 'null' => false, + 'key' => 'primary', + ), + 'name' => array( + 'type' => 'string', + 'length' => 20, + 'null' => false, + ), + 'email' => array( + 'type' => 'string', + 'length' => 255, + 'null' => false, + ), + 'some_date' => array( + 'type' => 'date', + 'length' => '', + 'null' => false, + ), + 'some_time' => array( + 'type' => 'time', + 'length' => '', + 'null' => false, + ), + 'created' => array( + 'type' => 'datetime', + 'length' => '', + 'null' => false, + ) + )); + $this->Task->interactive = false; + + $result = $this->Task->doValidation($Model); + $expected = array( + 'name' => array( + 'notempty' => 'notempty' + ), + 'email' => array( + 'email' => 'email', + ), + 'some_date' => array( + 'date' => 'date' + ), + 'some_time' => array( + 'time' => 'time' + ), + ); + $this->assertEqual($result, $expected); + } + +/** + * test that finding primary key works + * + * @return void + * @access public + */ + function testFindPrimaryKey() { + $fields = array( + 'one' => array(), + 'two' => array(), + 'key' => array('key' => 'primary') + ); + $this->Task->expectAt(0, 'in', array('*', null, 'key')); + $this->Task->setReturnValue('in', 'my_field'); + $result = $this->Task->findPrimaryKey($fields); + $expected = 'my_field'; + $this->assertEqual($result, $expected); + } + +/** + * test finding Display field + * + * @return void + * @access public + */ + function testFindDisplayField() { + $fields = array('id' => array(), 'tagname' => array(), 'body' => array(), + 'created' => array(), 'modified' => array()); + + $this->Task->setReturnValue('in', 'n'); + $this->Task->setReturnValueAt(0, 'in', 'n'); + $result = $this->Task->findDisplayField($fields); + $this->assertFalse($result); + + $this->Task->setReturnValueAt(1, 'in', 'y'); + $this->Task->setReturnValueAt(2, 'in', 2); + $result = $this->Task->findDisplayField($fields); + $this->assertEqual($result, 'tagname'); + } + +/** + * test that belongsTo generation works. + * + * @return void + * @access public + */ + function testBelongsToGeneration() { + $model = new Model(array('ds' => 'test_suite', 'name' => 'Comment')); + $result = $this->Task->findBelongsTo($model, array()); + $expected = array( + 'belongsTo' => array( + array( + 'alias' => 'Article', + 'className' => 'Article', + 'foreignKey' => 'article_id', + ), + array( + 'alias' => 'User', + 'className' => 'User', + 'foreignKey' => 'user_id', + ), + ) + ); + $this->assertEqual($result, $expected); + + $model = new Model(array('ds' => 'test_suite', 'name' => 'CategoryThread')); + $result = $this->Task->findBelongsTo($model, array()); + $expected = array( + 'belongsTo' => array( + array( + 'alias' => 'ParentCategoryThread', + 'className' => 'CategoryThread', + 'foreignKey' => 'parent_id', + ), + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * test that hasOne and/or hasMany relations are generated properly. + * + * @return void + * @access public + */ + function testHasManyHasOneGeneration() { + $model = new Model(array('ds' => 'test_suite', 'name' => 'Article')); + $this->Task->connection = 'test_suite'; + $this->Task->listAll(); + $result = $this->Task->findHasOneAndMany($model, array()); + $expected = array( + 'hasMany' => array( + array( + 'alias' => 'Comment', + 'className' => 'Comment', + 'foreignKey' => 'article_id', + ), + ), + 'hasOne' => array( + array( + 'alias' => 'Comment', + 'className' => 'Comment', + 'foreignKey' => 'article_id', + ), + ), + ); + $this->assertEqual($result, $expected); + + $model = new Model(array('ds' => 'test_suite', 'name' => 'CategoryThread')); + $result = $this->Task->findHasOneAndMany($model, array()); + $expected = array( + 'hasOne' => array( + array( + 'alias' => 'ChildCategoryThread', + 'className' => 'CategoryThread', + 'foreignKey' => 'parent_id', + ), + ), + 'hasMany' => array( + array( + 'alias' => 'ChildCategoryThread', + 'className' => 'CategoryThread', + 'foreignKey' => 'parent_id', + ), + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * Test that HABTM generation works + * + * @return void + * @access public + */ + function testHasAndBelongsToManyGeneration() { + $model = new Model(array('ds' => 'test_suite', 'name' => 'Article')); + $this->Task->connection = 'test_suite'; + $this->Task->listAll(); + $result = $this->Task->findHasAndBelongsToMany($model, array()); + $expected = array( + 'hasAndBelongsToMany' => array( + array( + 'alias' => 'Tag', + 'className' => 'Tag', + 'foreignKey' => 'article_id', + 'joinTable' => 'articles_tags', + 'associationForeignKey' => 'tag_id', + ), + ), + ); + $this->assertEqual($result, $expected); + } + +/** + * test non interactive doAssociations + * + * @return void + * @access public + */ + function testDoAssociationsNonInteractive() { + $this->Task->connection = 'test_suite'; + $this->Task->interactive = false; + $model = new Model(array('ds' => 'test_suite', 'name' => 'Article')); + $result = $this->Task->doAssociations($model); + $expected = array( + 'hasMany' => array( + array( + 'alias' => 'Comment', + 'className' => 'Comment', + 'foreignKey' => 'article_id', + ), + ), + 'hasAndBelongsToMany' => array( + array( + 'alias' => 'Tag', + 'className' => 'Tag', + 'foreignKey' => 'article_id', + 'joinTable' => 'articles_tags', + 'associationForeignKey' => 'tag_id', + ), + ), + ); + } + +/** + * Ensure that the fixutre object is correctly called. + * + * @return void + * @access public + */ + function testBakeFixture() { + $this->Task->plugin = 'test_plugin'; + $this->Task->interactive = true; + $this->Task->Fixture->expectAt(0, 'bake', array('Article', 'articles')); + $this->Task->bakeFixture('Article', 'articles'); + + $this->assertEqual($this->Task->plugin, $this->Task->Fixture->plugin); + $this->assertEqual($this->Task->connection, $this->Task->Fixture->connection); + $this->assertEqual($this->Task->interactive, $this->Task->Fixture->interactive); + } + +/** + * Ensure that the test object is correctly called. + * + * @return void + * @access public + */ + function testBakeTest() { + $this->Task->plugin = 'test_plugin'; + $this->Task->interactive = true; + $this->Task->Test->expectAt(0, 'bake', array('Model', 'Article')); + $this->Task->bakeTest('Article'); + + $this->assertEqual($this->Task->plugin, $this->Task->Test->plugin); + $this->assertEqual($this->Task->connection, $this->Task->Test->connection); + $this->assertEqual($this->Task->interactive, $this->Task->Test->interactive); + } + +/** + * test confirming of associations, and that when an association is hasMany + * a question for the hasOne is also not asked. + * + * @return void + * @access public + */ + function testConfirmAssociations() { + $associations = array( + 'hasOne' => array( + array( + 'alias' => 'ChildCategoryThread', + 'className' => 'CategoryThread', + 'foreignKey' => 'parent_id', + ), + ), + 'hasMany' => array( + array( + 'alias' => 'ChildCategoryThread', + 'className' => 'CategoryThread', + 'foreignKey' => 'parent_id', + ), + ), + 'belongsTo' => array( + array( + 'alias' => 'User', + 'className' => 'User', + 'foreignKey' => 'user_id', + ), + ) + ); + $model = new Model(array('ds' => 'test_suite', 'name' => 'CategoryThread')); + $this->Task->setReturnValueAt(0, 'in', 'y'); + $result = $this->Task->confirmAssociations($model, $associations); + $this->assertTrue(empty($result['hasOne'])); + + $this->Task->setReturnValue('in', 'n'); + $result = $this->Task->confirmAssociations($model, $associations); + $this->assertTrue(empty($result['hasMany'])); + $this->assertTrue(empty($result['hasOne'])); + } + +/** + * test that inOptions generates questions and only accepts a valid answer + * + * @return void + * @access public + */ + function testInOptions() { + $options = array('one', 'two', 'three'); + $this->Task->expectAt(0, 'out', array('1. one')); + $this->Task->expectAt(1, 'out', array('2. two')); + $this->Task->expectAt(2, 'out', array('3. three')); + $this->Task->setReturnValueAt(0, 'in', 10); + + $this->Task->expectAt(3, 'out', array('1. one')); + $this->Task->expectAt(4, 'out', array('2. two')); + $this->Task->expectAt(5, 'out', array('3. three')); + $this->Task->setReturnValueAt(1, 'in', 2); + $result = $this->Task->inOptions($options, 'Pick a number'); + $this->assertEqual($result, 1); + } + +/** + * test baking validation + * + * @return void + * @access public + */ + function testBakeValidation() { + $validate = array( + 'name' => array( + 'notempty' => 'notempty' + ), + 'email' => array( + 'email' => 'email', + ), + 'some_date' => array( + 'date' => 'date' + ), + 'some_time' => array( + 'time' => 'time' + ) + ); + $result = $this->Task->bake('Article', compact('validate')); + $this->assertPattern('/class Article extends AppModel \{/', $result); + $this->assertPattern('/\$name \= \'Article\'/', $result); + $this->assertPattern('/\$validate \= array\(/', $result); + $expected = <<< STRINGEND +array( + 'notempty' => array( + 'rule' => array('notempty'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), +STRINGEND; + $this->assertPattern('/' . preg_quote(str_replace("\r\n", "\n", $expected), '/') . '/', $result); + } + +/** + * test baking relations + * + * @return void + * @access public + */ + function testBakeRelations() { + $associations = array( + 'belongsTo' => array( + array( + 'alias' => 'SomethingElse', + 'className' => 'SomethingElse', + 'foreignKey' => 'something_else_id', + ), + array( + 'alias' => 'User', + 'className' => 'User', + 'foreignKey' => 'user_id', + ), + ), + 'hasOne' => array( + array( + 'alias' => 'OtherModel', + 'className' => 'OtherModel', + 'foreignKey' => 'other_model_id', + ), + ), + 'hasMany' => array( + array( + 'alias' => 'Comment', + 'className' => 'Comment', + 'foreignKey' => 'parent_id', + ), + ), + 'hasAndBelongsToMany' => array( + array( + 'alias' => 'Tag', + 'className' => 'Tag', + 'foreignKey' => 'article_id', + 'joinTable' => 'articles_tags', + 'associationForeignKey' => 'tag_id', + ), + ) + ); + $result = $this->Task->bake('Article', compact('associations')); + $this->assertPattern('/\$hasAndBelongsToMany \= array\(/', $result); + $this->assertPattern('/\$hasMany \= array\(/', $result); + $this->assertPattern('/\$belongsTo \= array\(/', $result); + $this->assertPattern('/\$hasOne \= array\(/', $result); + $this->assertPattern('/Tag/', $result); + $this->assertPattern('/OtherModel/', $result); + $this->assertPattern('/SomethingElse/', $result); + $this->assertPattern('/Comment/', $result); + } + +/** + * test bake() with a -plugin param + * + * @return void + * @access public + */ + function testBakeWithPlugin() { + $this->Task->plugin = 'ControllerTest'; + + $path = APP . 'plugins' . DS . 'controller_test' . DS . 'models' . DS . 'article.php'; + $this->Task->expectAt(0, 'createFile', array($path, '*')); + $this->Task->bake('Article', array(), array()); + + $this->Task->plugin = 'controllerTest'; + + $path = APP . 'plugins' . DS . 'controller_test' . DS . 'models' . DS . 'article.php'; + $this->Task->expectAt(1, 'createFile', array( + $path, new PatternExpectation('/Article extends ControllerTestAppModel/'))); + $this->Task->bake('Article', array(), array()); + + $this->assertEqual(count(ClassRegistry::keys()), 0); + $this->assertEqual(count(ClassRegistry::mapKeys()), 0); + } + +/** + * test that execute passes runs bake depending with named model. + * + * @return void + * @access public + */ + function testExecuteWithNamedModel() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('article'); + $filename = '/my/path/article.php'; + $this->Task->setReturnValue('_checkUnitTest', 1); + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class Article extends AppModel/'))); + $this->Task->execute(); + + $this->assertEqual(count(ClassRegistry::keys()), 0); + $this->assertEqual(count(ClassRegistry::mapKeys()), 0); + } + +/** + * test that execute passes with different inflections of the same name. + * + * @return void + * @access public + */ + function testExecuteWithNamedModelVariations() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->setReturnValue('_checkUnitTest', 1); + + $this->Task->args = array('article'); + $filename = '/my/path/article.php'; + + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class Article extends AppModel/'))); + $this->Task->execute(); + + $this->Task->args = array('Articles'); + $this->Task->expectAt(1, 'createFile', array($filename, new PatternExpectation('/class Article extends AppModel/'))); + $this->Task->execute(); + + $this->Task->args = array('articles'); + $this->Task->expectAt(2, 'createFile', array($filename, new PatternExpectation('/class Article extends AppModel/'))); + $this->Task->execute(); + } + +/** + * test that execute with a model name picks up hasMany associations. + * + * @return void + * @access public + */ + function testExecuteWithNamedModelHasManyCreated() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('article'); + $filename = '/my/path/article.php'; + $this->Task->setReturnValue('_checkUnitTest', 1); + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation("/'Comment' \=\> array\(/"))); + $this->Task->execute(); + } + +/** + * test that execute runs all() when args[0] = all + * + * @return void + * @access public + */ + function testExecuteIntoAll() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('all'); + $this->Task->setReturnValue('_checkUnitTest', true); + + $this->Task->Fixture->expectCallCount('bake', 5); + $this->Task->Test->expectCallCount('bake', 5); + + $filename = '/my/path/article.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class Article/'))); + + $filename = '/my/path/articles_tag.php'; + $this->Task->expectAt(1, 'createFile', array($filename, new PatternExpectation('/class ArticlesTag/'))); + + $filename = '/my/path/category_thread.php'; + $this->Task->expectAt(2, 'createFile', array($filename, new PatternExpectation('/class CategoryThread/'))); + + $filename = '/my/path/comment.php'; + $this->Task->expectAt(3, 'createFile', array($filename, new PatternExpectation('/class Comment/'))); + + $filename = '/my/path/tag.php'; + $this->Task->expectAt(4, 'createFile', array($filename, new PatternExpectation('/class Tag/'))); + + $this->Task->execute(); + + $this->assertEqual(count(ClassRegistry::keys()), 0); + $this->assertEqual(count(ClassRegistry::mapKeys()), 0); + } + +/** + * test that skipTables changes how all() works. + * + * @return void + */ + function testSkipTablesAndAll() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('all'); + $this->Task->setReturnValue('_checkUnitTest', true); + $this->Task->skipTables = array('tags'); + + $this->Task->Fixture->expectCallCount('bake', 4); + $this->Task->Test->expectCallCount('bake', 4); + + $filename = '/my/path/article.php'; + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class Article/'))); + + $filename = '/my/path/articles_tag.php'; + $this->Task->expectAt(1, 'createFile', array($filename, new PatternExpectation('/class ArticlesTag/'))); + + $filename = '/my/path/category_thread.php'; + $this->Task->expectAt(2, 'createFile', array($filename, new PatternExpectation('/class CategoryThread/'))); + + $filename = '/my/path/comment.php'; + $this->Task->expectAt(3, 'createFile', array($filename, new PatternExpectation('/class Comment/'))); + + $this->Task->execute(); + } + +/** + * test the interactive side of bake. + * + * @return void + * @access public + */ + function testExecuteIntoInteractive() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + $this->Task->interactive = true; + + $this->Task->setReturnValueAt(0, 'in', '1'); //choose article + $this->Task->setReturnValueAt(1, 'in', 'n'); //no validation + $this->Task->setReturnValueAt(2, 'in', 'y'); //yes to associations + $this->Task->setReturnValueAt(3, 'in', 'y'); //yes to comment relation + $this->Task->setReturnValueAt(4, 'in', 'y'); //yes to user relation + $this->Task->setReturnValueAt(5, 'in', 'y'); //yes to tag relation + $this->Task->setReturnValueAt(6, 'in', 'n'); //no to additional assocs + $this->Task->setReturnValueAt(7, 'in', 'y'); //yes to looksGood? + $this->Task->setReturnValue('_checkUnitTest', true); + + $this->Task->Test->expectOnce('bake'); + $this->Task->Fixture->expectOnce('bake'); + + $filename = '/my/path/article.php'; + $this->Task->expectOnce('createFile'); + $this->Task->expectAt(0, 'createFile', array($filename, new PatternExpectation('/class Article/'))); + $this->Task->execute(); + + $this->assertEqual(count(ClassRegistry::keys()), 0); + $this->assertEqual(count(ClassRegistry::mapKeys()), 0); + } + +/** + * test using bake interactively with a table that does not exist. + * + * @return void + * @access public + */ + function testExecuteWithNonExistantTableName() { + $this->Task->connection = 'test_suite'; + $this->Task->path = '/my/path/'; + + $this->Task->expectOnce('_stop'); + $this->Task->expectOnce('err'); + + $this->Task->setReturnValueAt(0, 'in', 'Foobar'); + $this->Task->setReturnValueAt(1, 'in', 'y'); + $this->Task->execute(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/plugin.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/plugin.test.php new file mode 100644 index 000000000..8210a2a38 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/plugin.test.php @@ -0,0 +1,263 @@ +Dispatcher =& new TestPluginTaskMockShellDispatcher(); + $this->Dispatcher->shellPaths = App::path('shells'); + $this->Task =& new MockPluginTask($this->Dispatcher); + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->path = TMP . 'tests' . DS; + } + +/** + * startCase methods + * + * @return void + * @access public + */ + function startCase() { + $this->_paths = $paths = App::path('plugins'); + $this->_testPath = array_push($paths, TMP . 'tests' . DS); + App::build(array('plugins' => $paths)); + } + +/** + * endCase + * + * @return void + * @access public + */ + function endCase() { + App::build(array('plugins' => $this->_paths)); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + ClassRegistry::flush(); + } + +/** + * test bake() + * + * @return void + * @access public + */ + function testBakeFoldersAndFiles() { + $this->Task->setReturnValueAt(0, 'in', $this->_testPath); + $this->Task->setReturnValueAt(1, 'in', 'y'); + $this->Task->bake('BakeTestPlugin'); + + $path = $this->Task->path . 'bake_test_plugin'; + $this->assertTrue(is_dir($path), 'No plugin dir %s'); + + $this->assertTrue(is_dir($path . DS . 'config'), 'No config dir %s'); + $this->assertTrue(is_dir($path . DS . 'config' . DS . 'schema'), 'No schema dir %s'); + $this->assertTrue(file_exists($path . DS . 'config' . DS . 'schema' . DS . 'empty'), 'No empty file %s'); + + $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir %s'); + $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir %s'); + $this->assertTrue(file_exists($path . DS . 'controllers' . DS . 'components' . DS . 'empty'), 'No empty file %s'); + + $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir %s'); + $this->assertTrue(file_exists($path . DS . 'models' . DS . 'behaviors' . DS . 'empty'), 'No empty file %s'); + $this->assertTrue(is_dir($path . DS . 'models' . DS . 'datasources'), 'No datasources dir %s'); + $this->assertTrue(file_exists($path . DS . 'models' . DS . 'datasources' . DS . 'empty'), 'No empty file %s'); + + $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir %s'); + $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir %s'); + $this->assertTrue(file_exists($path . DS . 'views' . DS . 'helpers' . DS . 'empty'), 'No empty file %s'); + + $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir %s'); + + $this->assertTrue( + is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'components'), 'No components cases dir %s' + ); + $this->assertTrue( + file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'components' . DS . 'empty'), 'No empty file %s' + ); + + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'behaviors'), 'No behaviors cases dir %s'); + $this->assertTrue( + file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'behaviors' . DS . 'empty'), 'No empty file %s' + ); + + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'helpers'), 'No helpers cases dir %s'); + $this->assertTrue( + file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'helpers' . DS . 'empty'), 'No empty file %s' + ); + + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'models'), 'No models cases dir %s'); + $this->assertTrue( + file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'models' . DS . 'empty'), 'No empty file %s' + ); + + $this->assertTrue( + is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'controllers'), + 'No controllers cases dir %s' + ); + $this->assertTrue( + file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'controllers' . DS . 'empty'), 'No empty file %s' + ); + + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'groups'), 'No groups dir %s'); + $this->assertTrue(file_exists($path . DS . 'tests' . DS . 'groups' . DS . 'empty'), 'No empty file %s'); + + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir %s'); + $this->assertTrue(file_exists($path . DS . 'tests' . DS . 'fixtures' . DS . 'empty'), 'No empty file %s'); + + $this->assertTrue(is_dir($path . DS . 'vendors'), 'No vendors dir %s'); + + $this->assertTrue(is_dir($path . DS . 'vendors' . DS . 'shells'), 'No vendors shells dir %s'); + $this->assertTrue(is_dir($path . DS . 'vendors' . DS . 'shells' . DS . 'tasks'), 'No vendors shells tasks dir %s'); + $this->assertTrue(file_exists($path . DS . 'vendors' . DS . 'shells' . DS . 'tasks' . DS . 'empty'), 'No empty file %s'); + $this->assertTrue(is_dir($path . DS . 'libs'), 'No libs dir %s'); + $this->assertTrue(is_dir($path . DS . 'webroot'), 'No webroot dir %s'); + + $file = $path . DS . 'bake_test_plugin_app_controller.php'; + $this->Task->expectAt(0, 'createFile', array($file, '*'), 'No AppController %s'); + + $file = $path . DS . 'bake_test_plugin_app_model.php'; + $this->Task->expectAt(1, 'createFile', array($file, '*'), 'No AppModel %s'); + + $Folder =& new Folder($this->Task->path . 'bake_test_plugin'); + $Folder->delete(); + } + +/** + * test execute with no args, flowing into interactive, + * + * @return void + * @access public + */ + function testExecuteWithNoArgs() { + $this->Task->setReturnValueAt(0, 'in', 'TestPlugin'); + $this->Task->setReturnValueAt(1, 'in', '3'); + $this->Task->setReturnValueAt(2, 'in', 'y'); + $this->Task->setReturnValueAt(3, 'in', 'n'); + + $path = $this->Task->path . 'test_plugin'; + $file = $path . DS . 'test_plugin_app_controller.php'; + $this->Task->expectAt(0, 'createFile', array($file, '*'), 'No AppController %s'); + + $file = $path . DS . 'test_plugin_app_model.php'; + $this->Task->expectAt(1, 'createFile', array($file, '*'), 'No AppModel %s'); + + $this->Task->args = array(); + $this->Task->execute(); + + $Folder =& new Folder($path); + $Folder->delete(); + } + +/** + * Test Execute + * + * @return void + * @access public + */ + function testExecuteWithOneArg() { + $this->Task->setReturnValueAt(0, 'in', $this->_testPath); + $this->Task->setReturnValueAt(1, 'in', 'y'); + $this->Task->Dispatch->args = array('BakeTestPlugin'); + $this->Task->args =& $this->Task->Dispatch->args; + + $path = $this->Task->path . 'bake_test_plugin'; + $file = $path . DS . 'bake_test_plugin_app_controller.php'; + $this->Task->expectAt(0, 'createFile', array($file, '*'), 'No AppController %s'); + + $file = $path . DS . 'bake_test_plugin_app_model.php'; + $this->Task->expectAt(1, 'createFile', array($file, '*'), 'No AppModel %s'); + + $this->Task->execute(); + + $Folder =& new Folder($this->Task->path . 'bake_test_plugin'); + $Folder->delete(); + } + +/** + * test execute chaining into MVC parts + * + * @return void + * @access public + */ + function testExecuteWithTwoArgs() { + $this->Task->Model =& new PluginTestMockModelTask(); + $this->Task->setReturnValueAt(0, 'in', $this->_testPath); + $this->Task->setReturnValueAt(1, 'in', 'y'); + + $Folder =& new Folder($this->Task->path . 'bake_test_plugin', true); + + $this->Task->Dispatch->args = array('BakeTestPlugin', 'model'); + $this->Task->args =& $this->Task->Dispatch->args; + + $this->Task->Model->expectOnce('loadTasks'); + $this->Task->Model->expectOnce('execute'); + $this->Task->execute(); + $Folder->delete(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/project.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/project.test.php new file mode 100644 index 000000000..e6fa38623 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/project.test.php @@ -0,0 +1,301 @@ +Dispatcher =& new TestProjectTaskMockShellDispatcher(); + $this->Dispatcher->shellPaths = App::path('shells'); + $this->Task =& new MockProjectTask($this->Dispatcher); + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->path = TMP . 'tests' . DS; + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + ClassRegistry::flush(); + + $Folder =& new Folder($this->Task->path . 'bake_test_app'); + $Folder->delete(); + } + +/** + * creates a test project that is used for testing project task. + * + * @return void + * @access protected + */ + function _setupTestProject() { + $skel = CAKE_CORE_INCLUDE_PATH . DS . CAKE . 'console' . DS . 'templates' . DS . 'skel'; + $this->Task->setReturnValueAt(0, 'in', 'y'); + $this->Task->setReturnValueAt(1, 'in', 'n'); + $this->Task->bake($this->Task->path . 'bake_test_app', $skel); + } + +/** + * test bake() method and directory creation. + * + * @return void + * @access public + */ + function testBake() { + $this->_setupTestProject(); + + $path = $this->Task->path . 'bake_test_app'; + $this->assertTrue(is_dir($path), 'No project dir %s'); + $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir %s'); + $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir %s'); + $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir %s'); + $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir %s'); + $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'groups'), 'No groups dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir %s'); + } + +/** + * test bake() method with -empty flag, directory creation and empty files. + * + * @return void + * @access public + */ + function testBakeEmptyFlag() { + $this->Task->params['empty'] = true; + $this->_setupTestProject(); + $path = $this->Task->path . 'bake_test_app'; + $this->assertTrue(is_dir($path), 'No project dir %s'); + $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir %s'); + $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir %s'); + $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir %s'); + $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir %s'); + $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'groups'), 'No groups dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir %s'); + + $this->assertTrue(is_file($path . DS . 'controllers' . DS .'components' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'locale' . DS . 'eng' . DS . 'LC_MESSAGES' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'models' . DS . 'behaviors' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'models' . DS . 'datasources' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'plugins' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'behaviors' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'components' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'controllers' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'datasources' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'helpers' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'models' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'shells' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'fixtures' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'tests' . DS . 'groups' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'vendors' . DS . 'shells' . DS . 'tasks' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'views' . DS . 'errors' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'views' . DS . 'helpers' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'views' . DS . 'scaffolds' . DS . 'empty'), 'No empty file in dir %s'); + $this->assertTrue(is_file($path . DS . 'webroot' . DS . 'js' . DS . 'empty'), 'No empty file in dir %s'); + } + +/** + * test generation of Security.salt + * + * @return void + * @access public + */ + function testSecuritySaltGeneration() { + $this->_setupTestProject(); + + $path = $this->Task->path . 'bake_test_app' . DS; + $result = $this->Task->securitySalt($path); + $this->assertTrue($result); + + $file =& new File($path . 'config' . DS . 'core.php'); + $contents = $file->read(); + $this->assertNoPattern('/DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi/', $contents, 'Default Salt left behind. %s'); + } + + /** + * test generation of Security.cipherSeed + * + * @return void + * @access public + */ + function testSecurityCipherSeedGeneration() { + $this->_setupTestProject(); + + $path = $this->Task->path . 'bake_test_app' . DS; + $result = $this->Task->securityCipherSeed($path); + $this->assertTrue($result); + + $file =& new File($path . 'config' . DS . 'core.php'); + $contents = $file->read(); + $this->assertNoPattern('/76859309657453542496749683645/', $contents, 'Default CipherSeed left behind. %s'); + } + +/** + * Test that index.php is generated correctly. + * + * @return void + * @access public + */ + function testIndexPhpGeneration() { + $this->_setupTestProject(); + + $path = $this->Task->path . 'bake_test_app' . DS; + $this->Task->corePath($path); + + $file =& new File($path . 'webroot' . DS . 'index.php'); + $contents = $file->read(); + $this->assertNoPattern('/define\(\'CAKE_CORE_INCLUDE_PATH\', \'ROOT/', $contents); + + $file =& new File($path . 'webroot' . DS . 'test.php'); + $contents = $file->read(); + $this->assertNoPattern('/define\(\'CAKE_CORE_INCLUDE_PATH\', \'ROOT/', $contents); + } + +/** + * test getPrefix method, and that it returns Routing.prefix or writes to config file. + * + * @return void + * @access public + */ + function testGetPrefix() { + Configure::write('Routing.prefixes', array('admin')); + $result = $this->Task->getPrefix(); + $this->assertEqual($result, 'admin_'); + + Configure::write('Routing.prefixes', null); + $this->_setupTestProject(); + $this->Task->configPath = $this->Task->path . 'bake_test_app' . DS . 'config' . DS; + $this->Task->setReturnValue('in', 'super_duper_admin'); + + $result = $this->Task->getPrefix(); + $this->assertEqual($result, 'super_duper_admin_'); + + $file =& new File($this->Task->configPath . 'core.php'); + $file->delete(); + } + +/** + * test cakeAdmin() writing core.php + * + * @return void + * @access public + */ + function testCakeAdmin() { + $file =& new File(CONFIGS . 'core.php'); + $contents = $file->read();; + $file =& new File(TMP . 'tests' . DS . 'core.php'); + $file->write($contents); + + Configure::write('Routing.prefixes', null); + $this->Task->configPath = TMP . 'tests' . DS; + $result = $this->Task->cakeAdmin('my_prefix'); + $this->assertTrue($result); + + $this->assertEqual(Configure::read('Routing.prefixes'), array('my_prefix')); + $file->delete(); + } + +/** + * test getting the prefix with more than one prefix setup + * + * @return void + * @access public + */ + function testGetPrefixWithMultiplePrefixes() { + Configure::write('Routing.prefixes', array('admin', 'ninja', 'shinobi')); + $this->_setupTestProject(); + $this->Task->configPath = $this->Task->path . 'bake_test_app' . DS . 'config' . DS; + $this->Task->setReturnValue('in', 2); + + $result = $this->Task->getPrefix(); + $this->assertEqual($result, 'ninja_'); + } + +/** + * Test execute method with one param to destination folder. + * + * @return void + * @access public + */ + function testExecute() { + $this->Task->params['skel'] = CAKE_CORE_INCLUDE_PATH . DS . CAKE . DS . 'console' . DS. 'templates' . DS . 'skel'; + $this->Task->params['working'] = TMP . 'tests' . DS; + + $path = $this->Task->path . 'bake_test_app'; + $this->Task->setReturnValue('in', 'y'); + $this->Task->setReturnValueAt(0, 'in', $path); + + $this->Task->execute(); + $this->assertTrue(is_dir($path), 'No project dir %s'); + $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir %s'); + $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir %s'); + $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir %s'); + $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir %s'); + $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'groups'), 'No groups dir %s'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir %s'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/template.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/template.test.php new file mode 100644 index 000000000..01fc1348e --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/template.test.php @@ -0,0 +1,189 @@ +Dispatcher =& new TestTemplateTaskMockShellDispatcher(); + $this->Task =& new MockTemplateTask($this->Dispatcher); + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->Dispatch->shellPaths = App::path('shells'); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + unset($this->Task, $this->Dispatcher); + ClassRegistry::flush(); + } + +/** + * test that set sets variables + * + * @return void + * @access public + */ + function testSet() { + $this->Task->set('one', 'two'); + $this->assertTrue(isset($this->Task->templateVars['one'])); + $this->assertEqual($this->Task->templateVars['one'], 'two'); + + $this->Task->set(array('one' => 'three', 'four' => 'five')); + $this->assertTrue(isset($this->Task->templateVars['one'])); + $this->assertEqual($this->Task->templateVars['one'], 'three'); + $this->assertTrue(isset($this->Task->templateVars['four'])); + $this->assertEqual($this->Task->templateVars['four'], 'five'); + + $this->Task->templateVars = array(); + $this->Task->set(array(3 => 'three', 4 => 'four')); + $this->Task->set(array(1 => 'one', 2 => 'two')); + $expected = array(3 => 'three', 4 => 'four', 1 => 'one', 2 => 'two'); + $this->assertEqual($this->Task->templateVars, $expected); + } + +/** + * test finding themes installed in + * + * @return void + * @access public + */ + function testFindingInstalledThemesForBake() { + $consoleLibs = CAKE_CORE_INCLUDE_PATH . DS . CAKE . 'console' . DS; + $this->Task->Dispatch->shellPaths = array($consoleLibs); + $this->Task->initialize(); + $this->assertEqual($this->Task->templatePaths, array('default' => $consoleLibs . 'templates' . DS . 'default' . DS)); + } + +/** + * test getting the correct theme name. Ensure that with only one theme, or a theme param + * that the user is not bugged. If there are more, find and return the correct theme name + * + * @return void + * @access public + */ + function testGetThemePath() { + $defaultTheme = CAKE_CORE_INCLUDE_PATH . DS . dirname(CONSOLE_LIBS) . 'templates' . DS . 'default' .DS; + $this->Task->templatePaths = array('default' => $defaultTheme); + $this->Task->expectCallCount('in', 1); + + $result = $this->Task->getThemePath(); + $this->assertEqual($result, $defaultTheme); + + $this->Task->templatePaths = array('default' => $defaultTheme, 'other' => '/some/path'); + $this->Task->params['theme'] = 'other'; + $result = $this->Task->getThemePath(); + $this->assertEqual($result, '/some/path'); + + $this->Task->params = array(); + $this->Task->setReturnValueAt(0, 'in', '1'); + $result = $this->Task->getThemePath(); + $this->assertEqual($result, $defaultTheme); + $this->assertEqual($this->Dispatcher->params['theme'], 'default'); + } + +/** + * test generate + * + * @return void + * @access public + */ + function testGenerate() { + App::build(array( + 'shells' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS + ) + )); + $this->Task->initialize(); + $this->Task->setReturnValue('in', 1); + $result = $this->Task->generate('classes', 'test_object', array('test' => 'foo')); + $expected = "I got rendered\nfoo"; + $this->assertEqual($result, $expected); + } + +/** + * test generate with a missing template in the chosen theme. + * ensure fallback to default works. + * + * @return void + * @access public + */ + function testGenerateWithTemplateFallbacks() { + App::build(array( + 'shells' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS, + CAKE_CORE_INCLUDE_PATH . DS . 'console' . DS + ) + )); + $this->Task->initialize(); + $this->Task->params['theme'] = 'test'; + $this->Task->set(array( + 'model' => 'Article', + 'table' => 'articles', + 'import' => false, + 'records' => false, + 'schema' => '' + )); + $result = $this->Task->generate('classes', 'fixture'); + $this->assertPattern('/ArticleFixture extends CakeTestFixture/', $result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/test.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/test.test.php new file mode 100644 index 000000000..b6f1e8c2a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/test.test.php @@ -0,0 +1,646 @@ + array( + 'className' => 'TestTask.TestTaskComment', + 'foreignKey' => 'article_id', + ) + ); + +/** + * Has and Belongs To Many Associations + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array( + 'Tag' => array( + 'className' => 'TestTaskTag', + 'joinTable' => 'articles_tags', + 'foreignKey' => 'article_id', + 'associationForeignKey' => 'tag_id' + ) + ); + +/** + * Example public method + * + * @return void + * @access public + */ + function doSomething() { + } + +/** + * Example Secondary public method + * + * @return void + * @access public + */ + function doSomethingElse() { + } + +/** + * Example protected method + * + * @return void + * @access protected + */ + function _innerMethod() { + } +} + +/** + * Tag Testing Model + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class TestTaskTag extends Model { + +/** + * Model name + * + * @var string + * @access public + */ + var $name = 'TestTaskTag'; + +/** + * Table name + * + * @var string + * @access public + */ + var $useTable = 'tags'; + +/** + * Has and Belongs To Many Associations + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array( + 'Article' => array( + 'className' => 'TestTaskArticle', + 'joinTable' => 'articles_tags', + 'foreignKey' => 'tag_id', + 'associationForeignKey' => 'article_id' + ) + ); +} + +/** + * Simulated plugin + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class TestTaskAppModel extends Model { +} + +/** + * Testing AppMode (TaskComment) + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class TestTaskComment extends TestTaskAppModel { + +/** + * Model name + * + * @var string + * @access public + */ + var $name = 'TestTaskComment'; + +/** + * Table name + * + * @var string + * @access public + */ + var $useTable = 'comments'; + +/** + * Belongs To Associations + * + * @var array + * @access public + */ + var $belongsTo = array( + 'Article' => array( + 'className' => 'TestTaskArticle', + 'foreignKey' => 'article_id', + ) + ); +} + +/** + * Test Task Comments Controller + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class TestTaskCommentsController extends Controller { + +/** + * Controller Name + * + * @var string + * @access public + */ + var $name = 'TestTaskComments'; + +/** + * Models to use + * + * @var array + * @access public + */ + var $uses = array('TestTaskComment', 'TestTaskTag'); +} + +/** + * TestTaskTest class + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class TestTaskTest extends CakeTestCase { + +/** + * Fixtures + * + * @var string + * @access public + */ + var $fixtures = array('core.article', 'core.comment', 'core.articles_tag', 'core.tag'); + +/** + * startTest method + * + * @return void + * @access public + */ + function startTest() { + $this->Dispatcher =& new TestTestTaskMockShellDispatcher(); + $this->Dispatcher->shellPaths = App::path('shells'); + $this->Task =& new MockTestTask($this->Dispatcher); + $this->Task->name = 'TestTask'; + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->Template =& new TemplateTask($this->Dispatcher); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + ClassRegistry::flush(); + App::build(); + } + +/** + * Test that file path generation doesn't continuously append paths. + * + * @return void + * @access public + */ + function testFilePathGeneration() { + $file = TESTS . 'cases' . DS . 'models' . DS . 'my_class.test.php'; + + $this->Task->Dispatch->expectNever('stderr'); + $this->Task->Dispatch->expectNever('_stop'); + + $this->Task->setReturnValue('in', 'y'); + $this->Task->expectAt(0, 'createFile', array($file, '*')); + $this->Task->bake('Model', 'MyClass'); + + $this->Task->expectAt(1, 'createFile', array($file, '*')); + $this->Task->bake('Model', 'MyClass'); + + $file = TESTS . 'cases' . DS . 'controllers' . DS . 'comments_controller.test.php'; + $this->Task->expectAt(2, 'createFile', array($file, '*')); + $this->Task->bake('Controller', 'Comments'); + } + +/** + * Test that method introspection pulls all relevant non parent class + * methods into the test case. + * + * @return void + */ + function testMethodIntrospection() { + $result = $this->Task->getTestableMethods('TestTaskArticle'); + $expected = array('dosomething', 'dosomethingelse'); + $this->assertEqual(array_map('strtolower', $result), $expected); + } + +/** + * test that the generation of fixtures works correctly. + * + * @return void + * @access public + */ + function testFixtureArrayGenerationFromModel() { + $subject = ClassRegistry::init('TestTaskArticle'); + $result = $this->Task->generateFixtureList($subject); + $expected = array('plugin.test_task.test_task_comment', 'app.articles_tags', + 'app.test_task_article', 'app.test_task_tag'); + + $this->assertEqual(sort($result), sort($expected)); + } + +/** + * test that the generation of fixtures works correctly. + * + * @return void + * @access public + */ + function testFixtureArrayGenerationFromController() { + $subject = new TestTaskCommentsController(); + $result = $this->Task->generateFixtureList($subject); + $expected = array('plugin.test_task.test_task_comment', 'app.articles_tags', + 'app.test_task_article', 'app.test_task_tag'); + + $this->assertEqual(sort($result), sort($expected)); + } + +/** + * test user interaction to get object type + * + * @return void + * @access public + */ + function testGetObjectType() { + $this->Task->expectOnce('_stop'); + $this->Task->setReturnValueAt(0, 'in', 'q'); + $this->Task->getObjectType(); + + $this->Task->setReturnValueAt(1, 'in', 2); + $result = $this->Task->getObjectType(); + $this->assertEqual($result, $this->Task->classTypes[1]); + } + +/** + * creating test subjects should clear the registry so the registry is always fresh + * + * @return void + * @access public + */ + function testRegistryClearWhenBuildingTestObjects() { + ClassRegistry::flush(); + $model = ClassRegistry::init('TestTaskComment'); + $model->bindModel(array( + 'belongsTo' => array( + 'Random' => array( + 'className' => 'TestTaskArticle', + 'foreignKey' => 'article_id', + ) + ) + )); + $keys = ClassRegistry::keys(); + $this->assertTrue(in_array('random', $keys)); + $object =& $this->Task->buildTestSubject('Model', 'TestTaskComment'); + + $keys = ClassRegistry::keys(); + $this->assertFalse(in_array('random', $keys)); + } + +/** + * test that getClassName returns the user choice as a classname. + * + * @return void + * @access public + */ + function testGetClassName() { + $objects = App::objects('model'); + $skip = $this->skipIf(empty($objects), 'No models in app, this test will fail. %s'); + if ($skip) { + return; + } + $this->Task->setReturnValueAt(0, 'in', 'MyCustomClass'); + $result = $this->Task->getClassName('Model'); + $this->assertEqual($result, 'MyCustomClass'); + + $this->Task->setReturnValueAt(1, 'in', 1); + $result = $this->Task->getClassName('Model'); + $options = App::objects('model'); + $this->assertEqual($result, $options[0]); + } + +/** + * Test the user interaction for defining additional fixtures. + * + * @return void + * @access public + */ + function testGetUserFixtures() { + $this->Task->setReturnValueAt(0, 'in', 'y'); + $this->Task->setReturnValueAt(1, 'in', 'app.pizza, app.topping, app.side_dish'); + $result = $this->Task->getUserFixtures(); + $expected = array('app.pizza', 'app.topping', 'app.side_dish'); + $this->assertEqual($result, $expected); + } + +/** + * test that resolving classnames works + * + * @return void + * @access public + */ + function testGetRealClassname() { + $result = $this->Task->getRealClassname('Model', 'Post'); + $this->assertEqual($result, 'Post'); + + $result = $this->Task->getRealClassname('Controller', 'Posts'); + $this->assertEqual($result, 'PostsController'); + + $result = $this->Task->getRealClassname('Helper', 'Form'); + $this->assertEqual($result, 'FormHelper'); + + $result = $this->Task->getRealClassname('Behavior', 'Containable'); + $this->assertEqual($result, 'ContainableBehavior'); + + $result = $this->Task->getRealClassname('Component', 'Auth'); + $this->assertEqual($result, 'AuthComponent'); + } + +/** + * test baking files. The conditionally run tests are known to fail in PHP4 + * as PHP4 classnames are all lower case, breaking the plugin path inflection. + * + * @return void + * @access public + */ + function testBakeModelTest() { + $this->Task->setReturnValue('createFile', true); + $this->Task->setReturnValue('isLoadableClass', true); + + $result = $this->Task->bake('Model', 'TestTaskArticle'); + + $this->assertPattern('/App::import\(\'Model\', \'TestTaskArticle\'\)/', $result); + $this->assertPattern('/class TestTaskArticleTestCase extends CakeTestCase/', $result); + + $this->assertPattern('/function startTest\(\)/', $result); + $this->assertPattern("/\\\$this->TestTaskArticle \=\& ClassRegistry::init\('TestTaskArticle'\)/", $result); + + $this->assertPattern('/function endTest\(\)/', $result); + $this->assertPattern('/unset\(\$this->TestTaskArticle\)/', $result); + + $this->assertPattern('/function testDoSomething\(\)/i', $result); + $this->assertPattern('/function testDoSomethingElse\(\)/i', $result); + + $this->assertPattern("/'app\.test_task_article'/", $result); + if (PHP5) { + $this->assertPattern("/'plugin\.test_task\.test_task_comment'/", $result); + } + $this->assertPattern("/'app\.test_task_tag'/", $result); + $this->assertPattern("/'app\.articles_tag'/", $result); + } + +/** + * test baking controller test files, ensure that the stub class is generated. + * Conditional assertion is known to fail on PHP4 as classnames are all lower case + * causing issues with inflection of path name from classname. + * + * @return void + * @access public + */ + function testBakeControllerTest() { + $this->Task->setReturnValue('createFile', true); + $this->Task->setReturnValue('isLoadableClass', true); + + $result = $this->Task->bake('Controller', 'TestTaskComments'); + + $this->assertPattern('/App::import\(\'Controller\', \'TestTaskComments\'\)/', $result); + $this->assertPattern('/class TestTaskCommentsControllerTestCase extends CakeTestCase/', $result); + + $this->assertPattern('/class TestTestTaskCommentsController extends TestTaskCommentsController/', $result); + $this->assertPattern('/var \$autoRender = false/', $result); + $this->assertPattern('/function redirect\(\$url, \$status = null, \$exit = true\)/', $result); + + $this->assertPattern('/function startTest\(\)/', $result); + $this->assertPattern("/\\\$this->TestTaskComments \=\& new TestTestTaskCommentsController\(\)/", $result); + $this->assertPattern("/\\\$this->TestTaskComments->constructClasses\(\)/", $result); + + $this->assertPattern('/function endTest\(\)/', $result); + $this->assertPattern('/unset\(\$this->TestTaskComments\)/', $result); + + $this->assertPattern("/'app\.test_task_article'/", $result); + if (PHP5) { + $this->assertPattern("/'plugin\.test_task\.test_task_comment'/", $result); + } + $this->assertPattern("/'app\.test_task_tag'/", $result); + $this->assertPattern("/'app\.articles_tag'/", $result); + } + +/** + * test Constructor generation ensure that constructClasses is called for controllers + * + * @return void + * @access public + */ + function testGenerateConstructor() { + $result = $this->Task->generateConstructor('controller', 'PostsController'); + $expected = "new TestPostsController();\n\t\t\$this->Posts->constructClasses();\n"; + $this->assertEqual($result, $expected); + + $result = $this->Task->generateConstructor('model', 'Post'); + $expected = "ClassRegistry::init('Post');\n"; + $this->assertEqual($result, $expected); + + $result = $this->Task->generateConstructor('helper', 'FormHelper'); + $expected = "new FormHelper();\n"; + $this->assertEqual($result, $expected); + } + +/** + * Test that mock class generation works for the appropriate classes + * + * @return void + * @access public + */ + function testMockClassGeneration() { + $result = $this->Task->hasMockClass('controller'); + $this->assertTrue($result); + } + +/** + * test bake() with a -plugin param + * + * @return void + * @access public + */ + function testBakeWithPlugin() { + $this->Task->plugin = 'TestTest'; + + $path = APP . 'plugins' . DS . 'test_test' . DS . 'tests' . DS . 'cases' . DS . 'helpers' . DS . 'form.test.php'; + $this->Task->expectAt(0, 'createFile', array($path, '*')); + $this->Task->bake('Helper', 'Form'); + } + +/** + * test interactive with plugins lists from the plugin + * + * @return void + */ + function testInteractiveWithPlugin() { + $testApp = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS; + App::build(array( + 'plugins' => array($testApp) + ), true); + + $this->Task->plugin = 'TestPlugin'; + $path = $testApp . 'test_plugin' . DS . 'tests' . DS . 'cases' . DS . 'helpers' . DS . 'other_helper.test.php'; + $this->Task->setReturnValueAt(0, 'in', 5); //helper + $this->Task->setReturnValueAt(1, 'in', 1); //OtherHelper + $this->Task->expectAt(0, 'createFile', array($path, '*')); + $this->Task->expectAt(9, 'out', array('1. OtherHelper')); + $this->Task->execute(); + } + +/** + * Test filename generation for each type + plugins + * + * @return void + * @access public + */ + function testTestCaseFileName() { + $this->Task->path = '/my/path/tests/'; + + $result = $this->Task->testCaseFileName('Model', 'Post'); + $expected = $this->Task->path . 'cases' . DS . 'models' . DS . 'post.test.php'; + $this->assertEqual($result, $expected); + + $result = $this->Task->testCaseFileName('Helper', 'Form'); + $expected = $this->Task->path . 'cases' . DS . 'helpers' . DS . 'form.test.php'; + $this->assertEqual($result, $expected); + + $result = $this->Task->testCaseFileName('Controller', 'Posts'); + $expected = $this->Task->path . 'cases' . DS . 'controllers' . DS . 'posts_controller.test.php'; + $this->assertEqual($result, $expected); + + $result = $this->Task->testCaseFileName('Behavior', 'Containable'); + $expected = $this->Task->path . 'cases' . DS . 'behaviors' . DS . 'containable.test.php'; + $this->assertEqual($result, $expected); + + $result = $this->Task->testCaseFileName('Component', 'Auth'); + $expected = $this->Task->path . 'cases' . DS . 'components' . DS . 'auth.test.php'; + $this->assertEqual($result, $expected); + + $this->Task->plugin = 'TestTest'; + $result = $this->Task->testCaseFileName('Model', 'Post'); + $expected = APP . 'plugins' . DS . 'test_test' . DS . 'tests' . DS . 'cases' . DS . 'models' . DS . 'post.test.php'; + $this->assertEqual($result, $expected); + } + +/** + * test execute with a type defined + * + * @return void + * @access public + */ + function testExecuteWithOneArg() { + $this->Task->args[0] = 'Model'; + $this->Task->setReturnValueAt(0, 'in', 'TestTaskTag'); + $this->Task->setReturnValue('isLoadableClass', true); + $this->Task->expectAt(0, 'createFile', array('*', new PatternExpectation('/class TestTaskTagTestCase extends CakeTestCase/'))); + $this->Task->execute(); + } + +/** + * test execute with type and class name defined + * + * @return void + * @access public + */ + function testExecuteWithTwoArgs() { + $this->Task->args = array('Model', 'TestTaskTag'); + $this->Task->setReturnValueAt(0, 'in', 'TestTaskTag'); + $this->Task->setReturnValue('isLoadableClass', true); + $this->Task->expectAt(0, 'createFile', array('*', new PatternExpectation('/class TestTaskTagTestCase extends CakeTestCase/'))); + $this->Task->execute(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/view.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/view.test.php new file mode 100644 index 000000000..0e3b0db7b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/console/libs/tasks/view.test.php @@ -0,0 +1,677 @@ + array( + 'className' => 'ViewTaskArticle', + 'foreignKey' => 'article_id' + ) + ); +} + +/** + * Test View Task Article Model + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class ViewTaskArticle extends Model { + +/** + * Model name + * + * @var string + * @access public + */ + var $name = 'ViewTaskArticle'; + +/** + * Table name + * + * @var string + * @access public + */ + var $useTable = 'articles'; +} + +/** + * Test View Task Comments Controller + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class ViewTaskCommentsController extends Controller { + +/** + * Controller name + * + * @var string + * @access public + */ + var $name = 'ViewTaskComments'; + +/** + * Testing public controller action + * + * @return void + * @access public + */ + function index() { + } + +/** + * Testing public controller action + * + * @return void + * @access public + */ + function add() { + } +} + +/** + * Test View Task Articles Controller + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class ViewTaskArticlesController extends Controller { + +/** + * Controller name + * + * @var string + * @access public + */ + var $name = 'ViewTaskArticles'; + +/** + * Test public controller action + * + * @return void + * @access public + */ + function index() { + } + +/** + * Test public controller action + * + * @return void + * @access public + */ + function add() { + } + +/** + * Test admin prefixed controller action + * + * @return void + * @access public + */ + function admin_index() { + } + +/** + * Test admin prefixed controller action + * + * @return void + * @access public + */ + function admin_add() { + } + +/** + * Test admin prefixed controller action + * + * @return void + * @access public + */ + function admin_view() { + } + +/** + * Test admin prefixed controller action + * + * @return void + * @access public + */ + function admin_edit() { + } + +/** + * Test admin prefixed controller action + * + * @return void + * @access public + */ + function admin_delete() { + } +} + +/** + * ViewTaskTest class + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class ViewTaskTest extends CakeTestCase { + +/** + * Fixtures + * + * @var array + * @access public + */ + var $fixtures = array('core.article', 'core.comment', 'core.articles_tag', 'core.tag'); + +/** + * startTest method + * + * Ensure that the default theme is used + * + * @return void + * @access public + */ + function startTest() { + $this->Dispatcher =& new TestViewTaskMockShellDispatcher(); + $this->Dispatcher->shellPaths = App::path('shells'); + $this->Task =& new MockViewTask($this->Dispatcher); + $this->Task->name = 'ViewTask'; + $this->Task->Dispatch =& $this->Dispatcher; + $this->Task->Template =& new TemplateTask($this->Dispatcher); + $this->Task->Controller =& new ViewTaskMockControllerTask(); + $this->Task->Project =& new ViewTaskMockProjectTask(); + $this->Task->path = TMP; + $this->Task->Template->params['theme'] = 'default'; + + $this->_routing = Configure::read('Routing'); + } + +/** + * endTest method + * + * @return void + * @access public + */ + function endTest() { + ClassRegistry::flush(); + Configure::write('Routing', $this->_routing); + } + +/** + * Test getContent and parsing of Templates. + * + * @return void + * @access public + */ + function testGetContent() { + $vars = array( + 'modelClass' => 'TestViewModel', + 'schema' => array(), + 'primaryKey' => 'id', + 'displayField' => 'name', + 'singularVar' => 'testViewModel', + 'pluralVar' => 'testViewModels', + 'singularHumanName' => 'Test View Model', + 'pluralHumanName' => 'Test View Models', + 'fields' => array('id', 'name', 'body'), + 'associations' => array() + ); + $result = $this->Task->getContent('view', $vars); + + $this->assertPattern('/Delete Test View Model/', $result); + $this->assertPattern('/Edit Test View Model/', $result); + $this->assertPattern('/List Test View Models/', $result); + $this->assertPattern('/New Test View Model/', $result); + + $this->assertPattern('/testViewModel\[\'TestViewModel\'\]\[\'id\'\]/', $result); + $this->assertPattern('/testViewModel\[\'TestViewModel\'\]\[\'name\'\]/', $result); + $this->assertPattern('/testViewModel\[\'TestViewModel\'\]\[\'body\'\]/', $result); + } + +/** + * test getContent() using an admin_prefixed action. + * + * @return void + * @access public + */ + function testGetContentWithAdminAction() { + $_back = Configure::read('Routing'); + Configure::write('Routing.prefixes', array('admin')); + $vars = array( + 'modelClass' => 'TestViewModel', + 'schema' => array(), + 'primaryKey' => 'id', + 'displayField' => 'name', + 'singularVar' => 'testViewModel', + 'pluralVar' => 'testViewModels', + 'singularHumanName' => 'Test View Model', + 'pluralHumanName' => 'Test View Models', + 'fields' => array('id', 'name', 'body'), + 'associations' => array() + ); + $result = $this->Task->getContent('admin_view', $vars); + + $this->assertPattern('/Delete Test View Model/', $result); + $this->assertPattern('/Edit Test View Model/', $result); + $this->assertPattern('/List Test View Models/', $result); + $this->assertPattern('/New Test View Model/', $result); + + $this->assertPattern('/testViewModel\[\'TestViewModel\'\]\[\'id\'\]/', $result); + $this->assertPattern('/testViewModel\[\'TestViewModel\'\]\[\'name\'\]/', $result); + $this->assertPattern('/testViewModel\[\'TestViewModel\'\]\[\'body\'\]/', $result); + + $result = $this->Task->getContent('admin_add', $vars); + $this->assertPattern("/input\('name'\)/", $result); + $this->assertPattern("/input\('body'\)/", $result); + $this->assertPattern('/List Test View Models/', $result); + + Configure::write('Routing', $_back); + } + +/** + * test Bake method + * + * @return void + * @access public + */ + function testBake() { + $this->Task->controllerName = 'ViewTaskComments'; + $this->Task->controllerPath = 'view_task_comments'; + + $this->Task->expectAt(0, 'createFile', array( + TMP . 'view_task_comments' . DS . 'view.ctp', + new PatternExpectation('/View Task Articles/') + )); + $this->Task->bake('view', true); + + $this->Task->expectAt(1, 'createFile', array(TMP . 'view_task_comments' . DS . 'edit.ctp', '*')); + $this->Task->bake('edit', true); + + $this->Task->expectAt(2, 'createFile', array( + TMP . 'view_task_comments' . DS . 'index.ctp', + new PatternExpectation('/\$viewTaskComment\[\'Article\'\]\[\'title\'\]/') + )); + $this->Task->bake('index', true); + } + +/** + * test that baking a view with no template doesn't make a file. + * + * @return void + */ + function testBakeWithNoTemplate() { + $this->Task->controllerName = 'ViewTaskComments'; + $this->Task->controllerPath = 'view_task_comments'; + + $this->Task->expectNever('createFile'); + $this->Task->bake('delete', true); + } + +/** + * test bake() with a -plugin param + * + * @return void + * @access public + */ + function testBakeWithPlugin() { + $this->Task->controllerName = 'ViewTaskComments'; + $this->Task->controllerPath = 'view_task_comments'; + $this->Task->plugin = 'TestTest'; + + $path = APP . 'plugins' . DS . 'test_test' . DS . 'views' . DS . 'view_task_comments' . DS . 'view.ctp'; + $this->Task->expectAt(0, 'createFile', array($path, '*')); + $this->Task->bake('view', true); + } + +/** + * test bake actions baking multiple actions. + * + * @return void + * @access public + */ + function testBakeActions() { + $this->Task->controllerName = 'ViewTaskComments'; + $this->Task->controllerPath = 'view_task_comments'; + + $this->Task->expectAt(0, 'createFile', array( + TMP . 'view_task_comments' . DS . 'view.ctp', + new PatternExpectation('/View Task Comments/') + )); + $this->Task->expectAt(1, 'createFile', array( + TMP . 'view_task_comments' . DS . 'edit.ctp', + new PatternExpectation('/Edit View Task Comment/') + )); + $this->Task->expectAt(2, 'createFile', array( + TMP . 'view_task_comments' . DS . 'index.ctp', + new PatternExpectation('/ViewTaskComment/') + )); + + $this->Task->bakeActions(array('view', 'edit', 'index'), array()); + } + +/** + * test baking a customAction (non crud) + * + * @return void + * @access public + */ + function testCustomAction() { + $this->Task->controllerName = 'ViewTaskComments'; + $this->Task->controllerPath = 'view_task_comments'; + $this->Task->params['app'] = APP; + + $this->Task->setReturnValueAt(0, 'in', ''); + $this->Task->setReturnValueAt(1, 'in', 'my_action'); + $this->Task->setReturnValueAt(2, 'in', 'y'); + $this->Task->expectAt(0, 'createFile', array(TMP . 'view_task_comments' . DS . 'my_action.ctp', '*')); + + $this->Task->customAction(); + } + +/** + * Test all() + * + * @return void + * @access public + */ + function testExecuteIntoAll() { + $this->Task->args[0] = 'all'; + + $this->Task->Controller->setReturnValue('listAll', array('view_task_comments')); + $this->Task->Controller->expectOnce('listAll'); + + $this->Task->expectCallCount('createFile', 2); + $this->Task->expectAt(0, 'createFile', array(TMP . 'view_task_comments' . DS . 'index.ctp', '*')); + $this->Task->expectAt(1, 'createFile', array(TMP . 'view_task_comments' . DS . 'add.ctp', '*')); + + $this->Task->execute(); + } + +/** + * Test all() with action parameter + * + * @return void + * @access public + */ + function testExecuteIntoAllWithActionName() { + $this->Task->args = array('all', 'index'); + + $this->Task->Controller->setReturnValue('listAll', array('view_task_comments')); + $this->Task->Controller->expectOnce('listAll'); + + $this->Task->expectCallCount('createFile', 1); + $this->Task->expectAt(0, 'createFile', array(TMP . 'view_task_comments' . DS . 'index.ctp', '*')); + + $this->Task->execute(); + } + +/** + * test `cake bake view $controller view` + * + * @return void + * @access public + */ + function testExecuteWithActionParam() { + $this->Task->args[0] = 'ViewTaskComments'; + $this->Task->args[1] = 'view'; + + $this->Task->expectCallCount('createFile', 1); + $this->Task->expectAt(0, 'createFile', array(TMP . 'view_task_comments' . DS . 'view.ctp', '*')); + $this->Task->execute(); + } + +/** + * test `cake bake view $controller` + * Ensure that views are only baked for actions that exist in the controller. + * + * @return void + * @access public + */ + function testExecuteWithController() { + $this->Task->args[0] = 'ViewTaskComments'; + + $this->Task->expectCallCount('createFile', 2); + $this->Task->expectAt(0, 'createFile', array(TMP . 'view_task_comments' . DS . 'index.ctp', '*')); + $this->Task->expectAt(1, 'createFile', array(TMP . 'view_task_comments' . DS . 'add.ctp', '*')); + + $this->Task->execute(); + } + +/** + * test that both plural and singular forms can be used for baking views. + * + * @return void + * @access public + */ + function testExecuteWithControllerVariations() { + $this->Task->args = array('ViewTaskComments'); + + $this->Task->expectAt(0, 'createFile', array(TMP . 'view_task_comments' . DS . 'index.ctp', '*')); + $this->Task->expectAt(1, 'createFile', array(TMP . 'view_task_comments' . DS . 'add.ctp', '*')); + $this->Task->execute(); + + $this->Task->args = array('ViewTaskComment'); + + $this->Task->expectAt(0, 'createFile', array(TMP . 'view_task_comments' . DS . 'index.ctp', '*')); + $this->Task->expectAt(1, 'createFile', array(TMP . 'view_task_comments' . DS . 'add.ctp', '*')); + $this->Task->execute(); + } + +/** + * test `cake bake view $controller -admin` + * Which only bakes admin methods, not non-admin methods. + * + * @return void + * @access public + */ + function testExecuteWithControllerAndAdminFlag() { + $_back = Configure::read('Routing'); + Configure::write('Routing.prefixes', array('admin')); + $this->Task->args[0] = 'ViewTaskArticles'; + $this->Task->params['admin'] = 1; + $this->Task->Project->setReturnValue('getPrefix', 'admin_'); + + $this->Task->expectCallCount('createFile', 4); + $this->Task->expectAt(0, 'createFile', array(TMP . 'view_task_articles' . DS . 'admin_index.ctp', '*')); + $this->Task->expectAt(1, 'createFile', array(TMP . 'view_task_articles' . DS . 'admin_add.ctp', '*')); + $this->Task->expectAt(2, 'createFile', array(TMP . 'view_task_articles' . DS . 'admin_view.ctp', '*')); + $this->Task->expectAt(3, 'createFile', array(TMP . 'view_task_articles' . DS . 'admin_edit.ctp', '*')); + + $this->Task->execute(); + Configure::write('Routing', $_back); + } + +/** + * test execute into interactive. + * + * @return void + * @access public + */ + function testExecuteInteractive() { + $this->Task->connection = 'test_suite'; + $this->Task->args = array(); + $this->Task->params = array(); + + $this->Task->Controller->setReturnValue('getName', 'ViewTaskComments'); + $this->Task->setReturnValue('in', 'y'); + $this->Task->setReturnValueAt(0, 'in', 'y'); + $this->Task->setReturnValueAt(1, 'in', 'y'); + $this->Task->setReturnValueAt(2, 'in', 'n'); + + $this->Task->expectCallCount('createFile', 4); + $this->Task->expectAt(0, 'createFile', array( + TMP . 'view_task_comments' . DS . 'index.ctp', + new PatternExpectation('/ViewTaskComment/') + )); + $this->Task->expectAt(1, 'createFile', array( + TMP . 'view_task_comments' . DS . 'view.ctp', + new PatternExpectation('/ViewTaskComment/') + )); + $this->Task->expectAt(2, 'createFile', array( + TMP . 'view_task_comments' . DS . 'add.ctp', + new PatternExpectation('/Add View Task Comment/') + )); + $this->Task->expectAt(3, 'createFile', array( + TMP . 'view_task_comments' . DS . 'edit.ctp', + new PatternExpectation('/Edit View Task Comment/') + )); + + $this->Task->execute(); + } + +/** + * test `cake bake view posts index list` + * + * @return void + * @access public + */ + function testExecuteWithAlternateTemplates() { + $this->Task->connection = 'test_suite'; + $this->Task->args = array('ViewTaskComments', 'index', 'list'); + $this->Task->params = array(); + + $this->Task->expectCallCount('createFile', 1); + $this->Task->expectAt(0, 'createFile', array( + TMP . 'view_task_comments' . DS . 'list.ctp', + new PatternExpectation('/ViewTaskComment/') + )); + $this->Task->execute(); + } + +/** + * test execute into interactive() with admin methods. + * + * @return void + * @access public + */ + function testExecuteInteractiveWithAdmin() { + Configure::write('Routing.prefixes', array('admin')); + $this->Task->connection = 'test_suite'; + $this->Task->args = array(); + + $this->Task->Controller->setReturnValue('getName', 'ViewTaskComments'); + $this->Task->Project->setReturnValue('getPrefix', 'admin_'); + $this->Task->setReturnValueAt(0, 'in', 'y'); + $this->Task->setReturnValueAt(1, 'in', 'n'); + $this->Task->setReturnValueAt(2, 'in', 'y'); + + $this->Task->expectCallCount('createFile', 4); + $this->Task->expectAt(0, 'createFile', array( + TMP . 'view_task_comments' . DS . 'admin_index.ctp', + new PatternExpectation('/ViewTaskComment/') + )); + $this->Task->expectAt(1, 'createFile', array( + TMP . 'view_task_comments' . DS . 'admin_view.ctp', + new PatternExpectation('/ViewTaskComment/') + )); + $this->Task->expectAt(2, 'createFile', array( + TMP . 'view_task_comments' . DS . 'admin_add.ctp', + new PatternExpectation('/Add View Task Comment/') + )); + $this->Task->expectAt(3, 'createFile', array( + TMP . 'view_task_comments' . DS . 'admin_edit.ctp', + new PatternExpectation('/Edit View Task Comment/') + )); + + $this->Task->execute(); + } + +/** + * test getting templates, make sure noTemplateActions works + * + * @return void + */ + function testGetTemplate() { + $result = $this->Task->getTemplate('delete'); + $this->assertFalse($result); + + $result = $this->Task->getTemplate('add'); + $this->assertEqual($result, 'form'); + + Configure::write('Routing.prefixes', array('admin')); + + $result = $this->Task->getTemplate('admin_add'); + $this->assertEqual($result, 'form'); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/dispatcher.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/dispatcher.test.php new file mode 100644 index 000000000..942e1f757 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/dispatcher.test.php @@ -0,0 +1,2600 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once CAKE . 'dispatcher.php'; + +if (!class_exists('AppController')) { + require_once LIBS . 'controller' . DS . 'app_controller.php'; +} elseif (!defined('APP_CONTROLLER_EXISTS')){ + define('APP_CONTROLLER_EXISTS', true); +} + +/** + * TestDispatcher class + * + * @package cake + * @subpackage cake.tests.cases + */ +class TestDispatcher extends Dispatcher { + +/** + * invoke method + * + * @param mixed $controller + * @param mixed $params + * @param mixed $missingAction + * @return void + * @access protected + */ + function _invoke(&$controller, $params) { + restore_error_handler(); + if ($result = parent::_invoke($controller, $params)) { + if ($result[0] === 'missingAction') { + return $result; + } + } + set_error_handler('simpleTestErrorHandler'); + + return $controller; + } + +/** + * cakeError method + * + * @param mixed $filename + * @return void + * @access public + */ + function cakeError($filename, $params) { + return array($filename, $params); + } + +/** + * _stop method + * + * @return void + * @access protected + */ + function _stop() { + $this->stopped = true; + return true; + } +} + +/** + * MyPluginAppController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class MyPluginAppController extends AppController { +} + +/** + * MyPluginController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class MyPluginController extends MyPluginAppController { + +/** + * name property + * + * @var string 'MyPlugin' + * @access public + */ + var $name = 'MyPlugin'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * index method + * + * @return void + * @access public + */ + function index() { + return true; + } + +/** + * add method + * + * @return void + * @access public + */ + function add() { + return true; + } + +/** + * admin_add method + * + * @param mixed $id + * @return void + * @access public + */ + function admin_add($id = null) { + return $id; + } +} + +/** + * SomePagesController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class SomePagesController extends AppController { + +/** + * name property + * + * @var string 'SomePages' + * @access public + */ + var $name = 'SomePages'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * display method + * + * @param mixed $page + * @return void + * @access public + */ + function display($page = null) { + return $page; + } + +/** + * index method + * + * @return void + * @access public + */ + function index() { + return true; + } + +/** + * protected method + * + * @return void + * @access protected + */ + function _protected() { + return true; + } + +/** + * redirect method overriding + * + * @return void + * @access public + */ + function redirect() { + echo 'this should not be accessible'; + } +} + +/** + * OtherPagesController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class OtherPagesController extends MyPluginAppController { + +/** + * name property + * + * @var string 'OtherPages' + * @access public + */ + var $name = 'OtherPages'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * display method + * + * @param mixed $page + * @return void + * @access public + */ + function display($page = null) { + return $page; + } + +/** + * index method + * + * @return void + * @access public + */ + function index() { + return true; + } +} + +/** + * TestDispatchPagesController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class TestDispatchPagesController extends AppController { + +/** + * name property + * + * @var string 'TestDispatchPages' + * @access public + */ + var $name = 'TestDispatchPages'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * admin_index method + * + * @return void + * @access public + */ + function admin_index() { + return true; + } + +/** + * camelCased method + * + * @return void + * @access public + */ + function camelCased() { + return true; + } +} + +/** + * ArticlesTestAppController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class ArticlesTestAppController extends AppController { +} + +/** + * ArticlesTestController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class ArticlesTestController extends ArticlesTestAppController { + +/** + * name property + * + * @var string 'ArticlesTest' + * @access public + */ + var $name = 'ArticlesTest'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * admin_index method + * + * @return void + * @access public + */ + function admin_index() { + return true; + } +/** + * fake index method. + * + * @return void + */ + function index() { + return true; + } +} + +/** + * SomePostsController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class SomePostsController extends AppController { + +/** + * name property + * + * @var string 'SomePosts' + * @access public + */ + var $name = 'SomePosts'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * autoRender property + * + * @var bool false + * @access public + */ + var $autoRender = false; + +/** + * beforeFilter method + * + * @return void + * @access public + */ + function beforeFilter() { + if ($this->params['action'] == 'index') { + $this->params['action'] = 'view'; + } else { + $this->params['action'] = 'change'; + } + $this->params['pass'] = array('changed'); + } + +/** + * index method + * + * @return void + * @access public + */ + function index() { + return true; + } + +/** + * change method + * + * @return void + * @access public + */ + function change() { + return true; + } +} + +/** + * TestCachedPagesController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class TestCachedPagesController extends AppController { + +/** + * name property + * + * @var string 'TestCachedPages' + * @access public + */ + var $name = 'TestCachedPages'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * helpers property + * + * @var array + * @access public + */ + var $helpers = array('Cache'); + +/** + * cacheAction property + * + * @var array + * @access public + */ + var $cacheAction = array( + 'index' => '+2 sec', + 'test_nocache_tags' => '+2 sec', + 'view' => '+2 sec' + ); + +/** + * viewPath property + * + * @var string 'posts' + * @access public + */ + var $viewPath = 'posts'; + +/** + * index method + * + * @return void + * @access public + */ + function index() { + $this->render(); + } + +/** + * test_nocache_tags method + * + * @return void + * @access public + */ + function test_nocache_tags() { + $this->render(); + } + +/** + * view method + * + * @return void + * @access public + */ + function view($id = null) { + $this->render('index'); + } +/** + * test cached forms / tests view object being registered + * + * @return void + */ + function cache_form() { + $this->cacheAction = 10; + $this->helpers[] = 'Form'; + } +} + +/** + * TimesheetsController class + * + * @package cake + * @subpackage cake.tests.cases + */ +class TimesheetsController extends AppController { + +/** + * name property + * + * @var string 'Timesheets' + * @access public + */ + var $name = 'Timesheets'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * index method + * + * @return void + * @access public + */ + function index() { + return true; + } +} + +/** + * DispatcherTest class + * + * @package cake + * @subpackage cake.tests.cases + */ +class DispatcherTest extends CakeTestCase { + +/** + * setUp method + * + * @return void + * @access public + */ + function startTest() { + $this->_get = $_GET; + $_GET = array(); + $this->_post = $_POST; + $this->_files = $_FILES; + $this->_server = $_SERVER; + + $this->_app = Configure::read('App'); + Configure::write('App.base', false); + Configure::write('App.baseUrl', false); + Configure::write('App.dir', 'app'); + Configure::write('App.webroot', 'webroot'); + + $this->_cache = Configure::read('Cache'); + Configure::write('Cache.disable', true); + + $this->_debug = Configure::read('debug'); + + App::build(App::core()); + } + +/** + * tearDown method + * + * @return void + * @access public + */ + function endTest() { + $_GET = $this->_get; + $_POST = $this->_post; + $_FILES = $this->_files; + $_SERVER = $this->_server; + App::build(); + Configure::write('App', $this->_app); + Configure::write('Cache', $this->_cache); + Configure::write('debug', $this->_debug); + } + +/** + * testParseParamsWithoutZerosAndEmptyPost method + * + * @return void + * @access public + */ + function testParseParamsWithoutZerosAndEmptyPost() { + $Dispatcher =& new Dispatcher(); + $test = $Dispatcher->parseParams("/testcontroller/testaction/params1/params2/params3"); + $this->assertIdentical($test['controller'], 'testcontroller'); + $this->assertIdentical($test['action'], 'testaction'); + $this->assertIdentical($test['pass'][0], 'params1'); + $this->assertIdentical($test['pass'][1], 'params2'); + $this->assertIdentical($test['pass'][2], 'params3'); + $this->assertFalse(!empty($test['form'])); + } + +/** + * testParseParamsReturnsPostedData method + * + * @return void + * @access public + */ + function testParseParamsReturnsPostedData() { + $_POST['testdata'] = "My Posted Content"; + $Dispatcher =& new Dispatcher(); + $test = $Dispatcher->parseParams("/"); + $this->assertTrue($test['form'], "Parsed URL not returning post data"); + $this->assertIdentical($test['form']['testdata'], "My Posted Content"); + } + +/** + * testParseParamsWithSingleZero method + * + * @return void + * @access public + */ + function testParseParamsWithSingleZero() { + $Dispatcher =& new Dispatcher(); + $test = $Dispatcher->parseParams("/testcontroller/testaction/1/0/23"); + $this->assertIdentical($test['controller'], 'testcontroller'); + $this->assertIdentical($test['action'], 'testaction'); + $this->assertIdentical($test['pass'][0], '1'); + $this->assertPattern('/\\A(?:0)\\z/', $test['pass'][1]); + $this->assertIdentical($test['pass'][2], '23'); + } + +/** + * testParseParamsWithManySingleZeros method + * + * @return void + * @access public + */ + function testParseParamsWithManySingleZeros() { + $Dispatcher =& new Dispatcher(); + $test = $Dispatcher->parseParams("/testcontroller/testaction/0/0/0/0/0/0"); + $this->assertPattern('/\\A(?:0)\\z/', $test['pass'][0]); + $this->assertPattern('/\\A(?:0)\\z/', $test['pass'][1]); + $this->assertPattern('/\\A(?:0)\\z/', $test['pass'][2]); + $this->assertPattern('/\\A(?:0)\\z/', $test['pass'][3]); + $this->assertPattern('/\\A(?:0)\\z/', $test['pass'][4]); + $this->assertPattern('/\\A(?:0)\\z/', $test['pass'][5]); + } + +/** + * testParseParamsWithManyZerosInEachSectionOfUrl method + * + * @return void + * @access public + */ + function testParseParamsWithManyZerosInEachSectionOfUrl() { + $Dispatcher =& new Dispatcher(); + $test = $Dispatcher->parseParams("/testcontroller/testaction/000/0000/00000/000000/000000/0000000"); + $this->assertPattern('/\\A(?:000)\\z/', $test['pass'][0]); + $this->assertPattern('/\\A(?:0000)\\z/', $test['pass'][1]); + $this->assertPattern('/\\A(?:00000)\\z/', $test['pass'][2]); + $this->assertPattern('/\\A(?:000000)\\z/', $test['pass'][3]); + $this->assertPattern('/\\A(?:000000)\\z/', $test['pass'][4]); + $this->assertPattern('/\\A(?:0000000)\\z/', $test['pass'][5]); + } + +/** + * testParseParamsWithMixedOneToManyZerosInEachSectionOfUrl method + * + * @return void + * @access public + */ + function testParseParamsWithMixedOneToManyZerosInEachSectionOfUrl() { + $Dispatcher =& new Dispatcher(); + $test = $Dispatcher->parseParams("/testcontroller/testaction/01/0403/04010/000002/000030/0000400"); + $this->assertPattern('/\\A(?:01)\\z/', $test['pass'][0]); + $this->assertPattern('/\\A(?:0403)\\z/', $test['pass'][1]); + $this->assertPattern('/\\A(?:04010)\\z/', $test['pass'][2]); + $this->assertPattern('/\\A(?:000002)\\z/', $test['pass'][3]); + $this->assertPattern('/\\A(?:000030)\\z/', $test['pass'][4]); + $this->assertPattern('/\\A(?:0000400)\\z/', $test['pass'][5]); + } + +/** + * testQueryStringOnRoot method + * + * @return void + * @access public + */ + function testQueryStringOnRoot() { + Router::reload(); + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); + + $_GET = array('coffee' => 'life', 'sleep' => 'sissies'); + $Dispatcher =& new Dispatcher(); + $uri = 'posts/home/?coffee=life&sleep=sissies'; + $result = $Dispatcher->parseParams($uri); + $this->assertPattern('/posts/', $result['controller']); + $this->assertPattern('/home/', $result['action']); + $this->assertTrue(isset($result['url']['sleep'])); + $this->assertTrue(isset($result['url']['coffee'])); + + $Dispatcher =& new Dispatcher(); + $uri = '/?coffee=life&sleep=sissy'; + $result = $Dispatcher->parseParams($uri); + $this->assertPattern('/pages/', $result['controller']); + $this->assertPattern('/display/', $result['action']); + $this->assertTrue(isset($result['url']['sleep'])); + $this->assertTrue(isset($result['url']['coffee'])); + $this->assertEqual($result['url']['coffee'], 'life'); + } + +/** + * testFileUploadArrayStructure method + * + * @return void + * @access public + */ + function testFileUploadArrayStructure() { + $_FILES = array('data' => array('name' => array( + 'File' => array( + array('data' => 'cake_mssql_patch.patch'), + array('data' => 'controller.diff'), + array('data' => ''), + array('data' => ''), + ), + 'Post' => array('attachment' => 'jquery-1.2.1.js'), + ), + 'type' => array( + 'File' => array( + array('data' => ''), + array('data' => ''), + array('data' => ''), + array('data' => ''), + ), + 'Post' => array('attachment' => 'application/x-javascript'), + ), + 'tmp_name' => array( + 'File' => array( + array('data' => '/private/var/tmp/phpy05Ywj'), + array('data' => '/private/var/tmp/php7MBztY'), + array('data' => ''), + array('data' => ''), + ), + 'Post' => array('attachment' => '/private/var/tmp/phpEwlrIo'), + ), + 'error' => array( + 'File' => array( + array('data' => 0), + array('data' => 0), + array('data' => 4), + array('data' => 4) + ), + 'Post' => array('attachment' => 0) + ), + 'size' => array( + 'File' => array( + array('data' => 6271), + array('data' => 350), + array('data' => 0), + array('data' => 0), + ), + 'Post' => array('attachment' => 80469) + ), + )); + + $Dispatcher =& new Dispatcher(); + $result = $Dispatcher->parseParams('/'); + + $expected = array( + 'File' => array( + array('data' => array( + 'name' => 'cake_mssql_patch.patch', + 'type' => '', + 'tmp_name' => '/private/var/tmp/phpy05Ywj', + 'error' => 0, + 'size' => 6271, + ), + ), + array('data' => array( + 'name' => 'controller.diff', + 'type' => '', + 'tmp_name' => '/private/var/tmp/php7MBztY', + 'error' => 0, + 'size' => 350, + )), + array('data' => array( + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => 4, + 'size' => 0, + )), + array('data' => array( + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => 4, + 'size' => 0, + )), + ), + 'Post' => array('attachment' => array( + 'name' => 'jquery-1.2.1.js', + 'type' => 'application/x-javascript', + 'tmp_name' => '/private/var/tmp/phpEwlrIo', + 'error' => 0, + 'size' => 80469, + ))); + $this->assertEqual($result['data'], $expected); + + $_FILES = array( + 'data' => array( + 'name' => array( + 'Document' => array( + 1 => array( + 'birth_cert' => 'born on.txt', + 'passport' => 'passport.txt', + 'drivers_license' => 'ugly pic.jpg' + ), + 2 => array( + 'birth_cert' => 'aunt betty.txt', + 'passport' => 'betty-passport.txt', + 'drivers_license' => 'betty-photo.jpg' + ), + ), + ), + 'type' => array( + 'Document' => array( + 1 => array( + 'birth_cert' => 'application/octet-stream', + 'passport' => 'application/octet-stream', + 'drivers_license' => 'application/octet-stream', + ), + 2 => array( + 'birth_cert' => 'application/octet-stream', + 'passport' => 'application/octet-stream', + 'drivers_license' => 'application/octet-stream', + ) + ) + ), + 'tmp_name' => array( + 'Document' => array( + 1 => array( + 'birth_cert' => '/private/var/tmp/phpbsUWfH', + 'passport' => '/private/var/tmp/php7f5zLt', + 'drivers_license' => '/private/var/tmp/phpMXpZgT', + ), + 2 => array( + 'birth_cert' => '/private/var/tmp/php5kHZt0', + 'passport' => '/private/var/tmp/phpnYkOuM', + 'drivers_license' => '/private/var/tmp/php9Rq0P3', + ) + ) + ), + 'error' => array( + 'Document' => array( + 1 => array( + 'birth_cert' => 0, + 'passport' => 0, + 'drivers_license' => 0, + ), + 2 => array( + 'birth_cert' => 0, + 'passport' => 0, + 'drivers_license' => 0, + ) + ) + ), + 'size' => array( + 'Document' => array( + 1 => array( + 'birth_cert' => 123, + 'passport' => 458, + 'drivers_license' => 875, + ), + 2 => array( + 'birth_cert' => 876, + 'passport' => 976, + 'drivers_license' => 9783, + ) + ) + ) + ) + ); + $Dispatcher =& new Dispatcher(); + $result = $Dispatcher->parseParams('/'); + $expected = array( + 'Document' => array( + 1 => array( + 'birth_cert' => array( + 'name' => 'born on.txt', + 'tmp_name' => '/private/var/tmp/phpbsUWfH', + 'error' => 0, + 'size' => 123, + 'type' => 'application/octet-stream', + ), + 'passport' => array( + 'name' => 'passport.txt', + 'tmp_name' => '/private/var/tmp/php7f5zLt', + 'error' => 0, + 'size' => 458, + 'type' => 'application/octet-stream', + ), + 'drivers_license' => array( + 'name' => 'ugly pic.jpg', + 'tmp_name' => '/private/var/tmp/phpMXpZgT', + 'error' => 0, + 'size' => 875, + 'type' => 'application/octet-stream', + ), + ), + 2 => array( + 'birth_cert' => array( + 'name' => 'aunt betty.txt', + 'tmp_name' => '/private/var/tmp/php5kHZt0', + 'error' => 0, + 'size' => 876, + 'type' => 'application/octet-stream', + ), + 'passport' => array( + 'name' => 'betty-passport.txt', + 'tmp_name' => '/private/var/tmp/phpnYkOuM', + 'error' => 0, + 'size' => 976, + 'type' => 'application/octet-stream', + ), + 'drivers_license' => array( + 'name' => 'betty-photo.jpg', + 'tmp_name' => '/private/var/tmp/php9Rq0P3', + 'error' => 0, + 'size' => 9783, + 'type' => 'application/octet-stream', + ), + ), + ) + ); + $this->assertEqual($result['data'], $expected); + + + $_FILES = array( + 'data' => array( + 'name' => array('birth_cert' => 'born on.txt'), + 'type' => array('birth_cert' => 'application/octet-stream'), + 'tmp_name' => array('birth_cert' => '/private/var/tmp/phpbsUWfH'), + 'error' => array('birth_cert' => 0), + 'size' => array('birth_cert' => 123) + ) + ); + + $Dispatcher =& new Dispatcher(); + $result = $Dispatcher->parseParams('/'); + + $expected = array( + 'birth_cert' => array( + 'name' => 'born on.txt', + 'type' => 'application/octet-stream', + 'tmp_name' => '/private/var/tmp/phpbsUWfH', + 'error' => 0, + 'size' => 123 + ) + ); + + $this->assertEqual($result['data'], $expected); + } + +/** + * testGetUrl method + * + * @return void + * @access public + */ + function testGetUrl() { + $Dispatcher =& new Dispatcher(); + $Dispatcher->base = '/app/webroot/index.php'; + $uri = '/app/webroot/index.php/posts/add'; + $result = $Dispatcher->getUrl($uri); + $expected = 'posts/add'; + $this->assertEqual($expected, $result); + + Configure::write('App.baseUrl', '/app/webroot/index.php'); + + $uri = '/posts/add'; + $result = $Dispatcher->getUrl($uri); + $expected = 'posts/add'; + $this->assertEqual($expected, $result); + + $_GET['url'] = array(); + Configure::write('App.base', '/control'); + $Dispatcher =& new Dispatcher(); + $Dispatcher->baseUrl(); + $uri = '/control/students/browse'; + $result = $Dispatcher->getUrl($uri); + $expected = 'students/browse'; + $this->assertEqual($expected, $result); + + $_GET['url'] = array(); + $Dispatcher =& new Dispatcher(); + $Dispatcher->base = ''; + $uri = '/?/home'; + $result = $Dispatcher->getUrl($uri); + $expected = '?/home'; + $this->assertEqual($expected, $result); + + $_GET['url'] = array(); + $Dispatcher =& new Dispatcher(); + $Dispatcher->base = '/shop'; + $uri = '/shop/fr/pages/shop'; + $result = $Dispatcher->getUrl($uri); + $expected = 'fr/pages/shop'; + $this->assertEqual($expected, $result); + } + +/** + * testBaseUrlAndWebrootWithModRewrite method + * + * @return void + * @access public + */ + function testBaseUrlAndWebrootWithModRewrite() { + $Dispatcher =& new Dispatcher(); + + $Dispatcher->base = false; + $_SERVER['DOCUMENT_ROOT'] = '/cake/repo/branches'; + $_SERVER['SCRIPT_FILENAME'] = '/cake/repo/branches/1.2.x.x/app/webroot/index.php'; + $_SERVER['PHP_SELF'] = '/1.2.x.x/app/webroot/index.php'; + $result = $Dispatcher->baseUrl(); + $expected = '/1.2.x.x'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/1.2.x.x/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + $Dispatcher->base = false; + $_SERVER['DOCUMENT_ROOT'] = '/cake/repo/branches/1.2.x.x/app/webroot'; + $_SERVER['SCRIPT_FILENAME'] = '/cake/repo/branches/1.2.x.x/app/webroot/index.php'; + $_SERVER['PHP_SELF'] = '/index.php'; + $result = $Dispatcher->baseUrl(); + $expected = ''; + $this->assertEqual($expected, $result); + $expectedWebroot = '/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + $Dispatcher->base = false; + $_SERVER['DOCUMENT_ROOT'] = '/cake/repo/branches/1.2.x.x/test/'; + $_SERVER['SCRIPT_FILENAME'] = '/cake/repo/branches/1.2.x.x/test/webroot/index.php'; + $_SERVER['PHP_SELF'] = '/webroot/index.php'; + $result = $Dispatcher->baseUrl(); + $expected = ''; + $this->assertEqual($expected, $result); + $expectedWebroot = '/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + $Dispatcher->base = false; + $_SERVER['DOCUMENT_ROOT'] = '/some/apps/where'; + $_SERVER['SCRIPT_FILENAME'] = '/some/apps/where/app/webroot/index.php'; + $_SERVER['PHP_SELF'] = '/some/apps/where/app/webroot/index.php'; + $result = $Dispatcher->baseUrl(); + $expected = '/some/apps/where'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/some/apps/where/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + + Configure::write('App.dir', 'auth'); + + $Dispatcher->base = false; + $_SERVER['DOCUMENT_ROOT'] = '/cake/repo/branches'; + $_SERVER['SCRIPT_FILENAME'] = '/cake/repo/branches/demos/auth/webroot/index.php'; + $_SERVER['PHP_SELF'] = '/demos/auth/webroot/index.php'; + + $result = $Dispatcher->baseUrl(); + $expected = '/demos/auth'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/demos/auth/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.dir', 'code'); + + $Dispatcher->base = false; + $_SERVER['DOCUMENT_ROOT'] = '/Library/WebServer/Documents'; + $_SERVER['SCRIPT_FILENAME'] = '/Library/WebServer/Documents/clients/PewterReport/code/webroot/index.php'; + $_SERVER['PHP_SELF'] = '/clients/PewterReport/code/webroot/index.php'; + $result = $Dispatcher->baseUrl(); + $expected = '/clients/PewterReport/code'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/clients/PewterReport/code/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + } + +/** + * testBaseUrlwithModRewriteAlias method + * + * @return void + * @access public + */ + function testBaseUrlwithModRewriteAlias() { + $_SERVER['DOCUMENT_ROOT'] = '/home/aplusnur/public_html'; + $_SERVER['SCRIPT_FILENAME'] = '/home/aplusnur/cake2/app/webroot/index.php'; + $_SERVER['PHP_SELF'] = '/control/index.php'; + + Configure::write('App.base', '/control'); + + $Dispatcher =& new Dispatcher(); + $result = $Dispatcher->baseUrl(); + $expected = '/control'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/control/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.base', false); + Configure::write('App.dir', 'affiliate'); + Configure::write('App.webroot', 'newaffiliate'); + + $_SERVER['DOCUMENT_ROOT'] = '/var/www/abtravaff/html'; + $_SERVER['SCRIPT_FILENAME'] = '/var/www/abtravaff/html/newaffiliate/index.php'; + $_SERVER['PHP_SELF'] = '/newaffiliate/index.php'; + $Dispatcher =& new Dispatcher(); + $result = $Dispatcher->baseUrl(); + $expected = '/newaffiliate'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/newaffiliate/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + } + +/** + * testBaseUrlAndWebrootWithBaseUrl method + * + * @return void + * @access public + */ + function testBaseUrlAndWebrootWithBaseUrl() { + $Dispatcher =& new Dispatcher(); + + Configure::write('App.dir', 'app'); + + Configure::write('App.baseUrl', '/app/webroot/index.php'); + $result = $Dispatcher->baseUrl(); + $expected = '/app/webroot/index.php'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/app/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.baseUrl', '/app/webroot/test.php'); + $result = $Dispatcher->baseUrl(); + $expected = '/app/webroot/test.php'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/app/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.baseUrl', '/app/index.php'); + $result = $Dispatcher->baseUrl(); + $expected = '/app/index.php'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/app/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.baseUrl', '/index.php'); + $result = $Dispatcher->baseUrl(); + $expected = '/index.php'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/app/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.baseUrl', '/CakeBB/app/webroot/index.php'); + $result = $Dispatcher->baseUrl(); + $expected = '/CakeBB/app/webroot/index.php'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/CakeBB/app/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.baseUrl', '/CakeBB/app/index.php'); + $result = $Dispatcher->baseUrl(); + $expected = '/CakeBB/app/index.php'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/CakeBB/app/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.baseUrl', '/CakeBB/index.php'); + $result = $Dispatcher->baseUrl(); + $expected = '/CakeBB/index.php'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/CakeBB/app/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.baseUrl', '/dbhauser/index.php'); + $_SERVER['DOCUMENT_ROOT'] = '/kunden/homepages/4/d181710652/htdocs/joomla'; + $_SERVER['SCRIPT_FILENAME'] = '/kunden/homepages/4/d181710652/htdocs/joomla/dbhauser/index.php'; + $result = $Dispatcher->baseUrl(); + $expected = '/dbhauser/index.php'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/dbhauser/app/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + } + +/** + * test baseUrl with no rewrite and using the top level index.php. + * + * @return void + */ + function testBaseUrlNoRewriteTopLevelIndex() { + $Dispatcher =& new Dispatcher(); + + Configure::write('App.baseUrl', '/index.php'); + $_SERVER['DOCUMENT_ROOT'] = '/Users/markstory/Sites/cake_dev'; + $_SERVER['SCRIPT_FILENAME'] = '/Users/markstory/Sites/cake_dev/index.php'; + + $result = $Dispatcher->baseUrl(); + $this->assertEqual('/index.php', $result); + $this->assertEqual('/app/webroot/', $Dispatcher->webroot); + $this->assertEqual('', $Dispatcher->base); + } + +/** + * test baseUrl with no rewrite, and using the app/webroot/index.php file as is normal with virtual hosts. + * + * @return void + */ + function testBaseUrlNoRewriteWebrootIndex() { + $Dispatcher =& new Dispatcher(); + + Configure::write('App.baseUrl', '/index.php'); + $_SERVER['DOCUMENT_ROOT'] = '/Users/markstory/Sites/cake_dev/app/webroot'; + $_SERVER['SCRIPT_FILENAME'] = '/Users/markstory/Sites/cake_dev/app/webroot/index.php'; + + $result = $Dispatcher->baseUrl(); + $this->assertEqual('/index.php', $result); + $this->assertEqual('/', $Dispatcher->webroot); + $this->assertEqual('', $Dispatcher->base); + } + +/** + * testBaseUrlAndWebrootWithBase method + * + * @return void + * @access public + */ + function testBaseUrlAndWebrootWithBase() { + $Dispatcher =& new Dispatcher(); + $Dispatcher->base = '/app'; + $result = $Dispatcher->baseUrl(); + $expected = '/app'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/app/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + $Dispatcher->base = ''; + $result = $Dispatcher->baseUrl(); + $expected = ''; + $this->assertEqual($expected, $result); + $expectedWebroot = '/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + + Configure::write('App.dir', 'testbed'); + $Dispatcher->base = '/cake/testbed/webroot'; + $result = $Dispatcher->baseUrl(); + $expected = '/cake/testbed/webroot'; + $this->assertEqual($expected, $result); + $expectedWebroot = '/cake/testbed/webroot/'; + $this->assertEqual($expectedWebroot, $Dispatcher->webroot); + } + +/** + * testMissingController method + * + * @return void + * @access public + */ + function testMissingController() { + $Dispatcher =& new TestDispatcher(); + Configure::write('App.baseUrl', '/index.php'); + $url = 'some_controller/home/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $expected = array('missingController', array(array( + 'className' => 'SomeControllerController', + 'webroot' => '/app/webroot/', + 'url' => 'some_controller/home/param:value/param2:value2', + 'base' => '/index.php' + ))); + $this->assertEqual($expected, $controller); + } + +/** + * testPrivate method + * + * @return void + * @access public + */ + function testPrivate() { + $Dispatcher =& new TestDispatcher(); + Configure::write('App.baseUrl','/index.php'); + $url = 'some_pages/_protected/param:value/param2:value2'; + + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $expected = array('privateAction', array(array( + 'className' => 'SomePagesController', + 'action' => '_protected', + 'webroot' => '/app/webroot/', + 'url' => 'some_pages/_protected/param:value/param2:value2', + 'base' => '/index.php' + ))); + $this->assertEqual($controller, $expected); + } + +/** + * testMissingAction method + * + * @return void + * @access public + */ + function testMissingAction() { + $Dispatcher =& new TestDispatcher(); + Configure::write('App.baseUrl', '/index.php'); + $url = 'some_pages/home/param:value/param2:value2'; + + $controller = $Dispatcher->dispatch($url, array('return'=> 1)); + + $expected = array('missingAction', array(array( + 'className' => 'SomePagesController', + 'action' => 'home', + 'webroot' => '/app/webroot/', + 'url' => '/index.php/some_pages/home/param:value/param2:value2', + 'base' => '/index.php' + ))); + $this->assertEqual($expected, $controller); + + $Dispatcher =& new TestDispatcher(); + Configure::write('App.baseUrl','/index.php'); + $url = 'some_pages/redirect/param:value/param2:value2'; + + $controller = $Dispatcher->dispatch($url, array('return'=> 1)); + + $expected = array('missingAction', array(array( + 'className' => 'SomePagesController', + 'action' => 'redirect', + 'webroot' => '/app/webroot/', + 'url' => '/index.php/some_pages/redirect/param:value/param2:value2', + 'base' => '/index.php' + ))); + $this->assertEqual($expected, $controller); + } + +/** + * testDispatch method + * + * @return void + * @access public + */ + function testDispatch() { + $Dispatcher =& new TestDispatcher(); + Configure::write('App.baseUrl','/index.php'); + $url = 'pages/home/param:value/param2:value2'; + + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $expected = 'Pages'; + $this->assertEqual($expected, $controller->name); + + $expected = array('0' => 'home', 'param' => 'value', 'param2' => 'value2'); + $this->assertIdentical($expected, $controller->passedArgs); + + Configure::write('App.baseUrl','/pages/index.php'); + + $url = 'pages/home'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $expected = 'Pages'; + $this->assertEqual($expected, $controller->name); + + $url = 'pages/home/'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertNull($controller->plugin); + $this->assertNull($Dispatcher->params['plugin']); + + $expected = 'Pages'; + $this->assertEqual($expected, $controller->name); + + unset($Dispatcher); + + $Dispatcher =& new TestDispatcher(); + Configure::write('App.baseUrl','/timesheets/index.php'); + + $url = 'timesheets'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $expected = 'Timesheets'; + $this->assertEqual($expected, $controller->name); + + $url = 'timesheets/'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $this->assertEqual('Timesheets', $controller->name); + $this->assertEqual('/timesheets/index.php', $Dispatcher->base); + + + $url = 'test_dispatch_pages/camelCased'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertEqual('TestDispatchPages', $controller->name); + + $url = 'test_dispatch_pages/camelCased/something. .'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertEqual($controller->params['pass'][0], 'something. .', 'Period was chopped off. %s'); + + } + +/** + * testDispatchWithArray method + * + * @return void + * @access public + */ + function testDispatchWithArray() { + $Dispatcher =& new TestDispatcher(); + $url = 'pages/home/param:value/param2:value2'; + + $url = array('controller' => 'pages', 'action' => 'display'); + $controller = $Dispatcher->dispatch($url, array( + 'pass' => array('home'), + 'named' => array('param' => 'value', 'param2' => 'value2'), + 'return' => 1 + )); + $expected = 'Pages'; + $this->assertEqual($expected, $controller->name); + + $expected = array('0' => 'home', 'param' => 'value', 'param2' => 'value2'); + $this->assertIdentical($expected, $controller->passedArgs); + + $this->assertEqual($Dispatcher->base . '/pages/display/home/param:value/param2:value2', $Dispatcher->here); + } + +/** + * test that a garbage url doesn't cause errors. + * + * @return void + */ + function testDispatchWithGarbageUrl() { + Configure::write('App.baseUrl', '/index.php'); + + $Dispatcher =& new TestDispatcher(); + $url = 'http://google.com'; + $result = $Dispatcher->dispatch($url); + $expected = array('missingController', array(array( + 'className' => 'Controller', + 'webroot' => '/app/webroot/', + 'url' => 'http://google.com', + 'base' => '/index.php' + ))); + $this->assertEqual($expected, $result); + } + +/** + * testAdminDispatch method + * + * @return void + * @access public + */ + function testAdminDispatch() { + $_POST = array(); + $Dispatcher =& new TestDispatcher(); + Configure::write('Routing.prefixes', array('admin')); + Configure::write('App.baseUrl','/cake/repo/branches/1.2.x.x/index.php'); + $url = 'admin/test_dispatch_pages/index/param:value/param2:value2'; + + Router::reload(); + $Router =& Router::getInstance(); + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $this->assertEqual($controller->name, 'TestDispatchPages'); + + $this->assertIdentical($controller->passedArgs, array('param' => 'value', 'param2' => 'value2')); + $this->assertTrue($controller->params['admin']); + + $expected = '/cake/repo/branches/1.2.x.x/index.php/admin/test_dispatch_pages/index/param:value/param2:value2'; + $this->assertIdentical($expected, $controller->here); + + $expected = '/cake/repo/branches/1.2.x.x/index.php'; + $this->assertIdentical($expected, $controller->base); + } + +/** + * testPluginDispatch method + * + * @return void + * @access public + */ + function testPluginDispatch() { + $_POST = array(); + $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + + Router::reload(); + $Dispatcher =& new TestDispatcher(); + Router::connect( + '/my_plugin/:controller/*', + array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'display') + ); + + $Dispatcher->base = false; + $url = 'my_plugin/some_pages/home/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $result = $Dispatcher->parseParams($url); + $expected = array( + 'pass' => array('home'), + 'named' => array('param'=> 'value', 'param2'=> 'value2'), 'plugin'=> 'my_plugin', + 'controller'=> 'some_pages', 'action'=> 'display', 'form'=> null, + 'url'=> array('url'=> 'my_plugin/some_pages/home/param:value/param2:value2'), + ); + ksort($expected); + ksort($result); + + $this->assertEqual($expected, $result); + + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'SomePages'); + $this->assertIdentical($controller->params['controller'], 'some_pages'); + $this->assertIdentical($controller->passedArgs, array('0' => 'home', 'param'=>'value', 'param2'=>'value2')); + + $expected = '/cake/repo/branches/1.2.x.x/my_plugin/some_pages/home/param:value/param2:value2'; + $this->assertIdentical($expected, $controller->here); + + $expected = '/cake/repo/branches/1.2.x.x'; + $this->assertIdentical($expected, $controller->base); + } + +/** + * testAutomaticPluginDispatch method + * + * @return void + * @access public + */ + function testAutomaticPluginDispatch() { + $_POST = array(); + $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + + Router::reload(); + $Dispatcher =& new TestDispatcher(); + Router::connect( + '/my_plugin/:controller/:action/*', + array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'display') + ); + + $Dispatcher->base = false; + + $url = 'my_plugin/other_pages/index/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'OtherPages'); + $this->assertIdentical($controller->action, 'index'); + $this->assertIdentical($controller->passedArgs, array('param' => 'value', 'param2' => 'value2')); + + $expected = '/cake/repo/branches/1.2.x.x/my_plugin/other_pages/index/param:value/param2:value2'; + $this->assertIdentical($expected, $controller->here); + + $expected = '/cake/repo/branches/1.2.x.x'; + $this->assertIdentical($expected, $controller->base); + } + +/** + * testAutomaticPluginControllerDispatch method + * + * @return void + * @access public + */ + function testAutomaticPluginControllerDispatch() { + $_POST = array(); + $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + + $plugins = App::objects('plugin'); + $plugins[] = 'MyPlugin'; + $plugins[] = 'ArticlesTest'; + + $app = App::getInstance(); + $app->__objects['plugin'] = $plugins; + + Router::reload(); + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + $url = 'my_plugin/my_plugin/add/param:value/param2:value2'; + + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'MyPlugin'); + $this->assertIdentical($controller->action, 'add'); + $this->assertEqual($controller->params['named'], array('param' => 'value', 'param2' => 'value2')); + + + Router::reload(); + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + // Simulates the Route for a real plugin, installed in APP/plugins + Router::connect('/my_plugin/:controller/:action/*', array('plugin' => 'my_plugin')); + + $plugin = 'MyPlugin'; + $pluginUrl = Inflector::underscore($plugin); + + $url = $pluginUrl; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'MyPlugin'); + $this->assertIdentical($controller->action, 'index'); + + $expected = $pluginUrl; + $this->assertEqual($controller->params['controller'], $expected); + + + Configure::write('Routing.prefixes', array('admin')); + + Router::reload(); + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + $url = 'admin/my_plugin/my_plugin/add/5/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $this->assertEqual($controller->params['plugin'], 'my_plugin'); + $this->assertEqual($controller->params['controller'], 'my_plugin'); + $this->assertEqual($controller->params['action'], 'admin_add'); + $this->assertEqual($controller->params['pass'], array(5)); + $this->assertEqual($controller->params['named'], array('param' => 'value', 'param2' => 'value2')); + $this->assertIdentical($controller->plugin, 'my_plugin'); + $this->assertIdentical($controller->name, 'MyPlugin'); + $this->assertIdentical($controller->action, 'admin_add'); + + $expected = array(0 => 5, 'param'=>'value', 'param2'=>'value2'); + $this->assertEqual($controller->passedArgs, $expected); + + Configure::write('Routing.prefixes', array('admin')); + Router::reload(); + + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + $controller = $Dispatcher->dispatch('admin/articles_test', array('return' => 1)); + $this->assertIdentical($controller->plugin, 'articles_test'); + $this->assertIdentical($controller->name, 'ArticlesTest'); + $this->assertIdentical($controller->action, 'admin_index'); + + $expected = array( + 'pass'=> array(), + 'named' => array(), + 'controller' => 'articles_test', + 'plugin' => 'articles_test', + 'action' => 'admin_index', + 'prefix' => 'admin', + 'admin' => true, + 'form' => array(), + 'url' => array('url' => 'admin/articles_test'), + 'return' => 1 + ); + $this->assertEqual($controller->params, $expected); + } + +/** + * test Plugin dispatching without controller name and using + * plugin short form instead. + * + * @return void + * @access public + */ + function testAutomaticPluginDispatchWithShortAccess() { + $_POST = array(); + $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + $plugins = App::objects('plugin'); + $plugins[] = 'MyPlugin'; + + $app = App::getInstance(); + $app->__objects['plugin'] = $plugins; + + Router::reload(); + + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + $url = 'my_plugin/'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertEqual($controller->params['controller'], 'my_plugin'); + $this->assertEqual($controller->params['plugin'], 'my_plugin'); + $this->assertEqual($controller->params['action'], 'index'); + $this->assertFalse(isset($controller->params['pass'][0])); + } + +/** + * test plugin shortcut urls with controllers that need to be loaded, + * the above test uses a controller that has already been included. + * + * @return void + */ + function testPluginShortCutUrlsWithControllerThatNeedsToBeLoaded() { + $loaded = class_exists('TestPluginController', false); + if ($this->skipIf($loaded, 'TestPluginController already loaded, this test will always pass, skipping %s')) { + return true; + } + Router::reload(); + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + ), true); + App::objects('plugin', null, false); + + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + $url = 'test_plugin/'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertEqual($controller->params['controller'], 'test_plugin'); + $this->assertEqual($controller->params['plugin'], 'test_plugin'); + $this->assertEqual($controller->params['action'], 'index'); + $this->assertFalse(isset($controller->params['pass'][0])); + + $url = '/test_plugin/tests/index'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertEqual($controller->params['controller'], 'tests'); + $this->assertEqual($controller->params['plugin'], 'test_plugin'); + $this->assertEqual($controller->params['action'], 'index'); + $this->assertFalse(isset($controller->params['pass'][0])); + + $url = '/test_plugin/tests/index/some_param'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertEqual($controller->params['controller'], 'tests'); + $this->assertEqual($controller->params['plugin'], 'test_plugin'); + $this->assertEqual($controller->params['action'], 'index'); + $this->assertEqual($controller->params['pass'][0], 'some_param'); + + App::build(); + } + +/** + * testAutomaticPluginControllerMissingActionDispatch method + * + * @return void + * @access public + */ + function testAutomaticPluginControllerMissingActionDispatch() { + $_POST = array(); + $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + + Router::reload(); + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + $url = 'my_plugin/not_here/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return'=> 1)); + + $expected = array('missingAction', array(array( + 'className' => 'MyPluginController', + 'action' => 'not_here', + 'webroot' => '/cake/repo/branches/1.2.x.x/', + 'url' => '/cake/repo/branches/1.2.x.x/my_plugin/not_here/param:value/param2:value2', + 'base' => '/cake/repo/branches/1.2.x.x' + ))); + $this->assertIdentical($expected, $controller); + + Router::reload(); + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + $url = 'my_plugin/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return'=> 1)); + + $expected = array('missingAction', array(array( + 'className' => 'MyPluginController', + 'action' => 'param:value', + 'webroot' => '/cake/repo/branches/1.2.x.x/', + 'url' => '/cake/repo/branches/1.2.x.x/my_plugin/param:value/param2:value2', + 'base' => '/cake/repo/branches/1.2.x.x' + ))); + $this->assertIdentical($expected, $controller); + } + +/** + * testPrefixProtection method + * + * @return void + * @access public + */ + function testPrefixProtection() { + $_POST = array(); + $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + + Router::reload(); + Router::connect('/admin/:controller/:action/*', array('prefix'=>'admin'), array('controller', 'action')); + + $Dispatcher =& new TestDispatcher(); + $Dispatcher->base = false; + + $url = 'test_dispatch_pages/admin_index/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $expected = array('privateAction', array(array( + 'className' => 'TestDispatchPagesController', + 'action' => 'admin_index', + 'webroot' => '/cake/repo/branches/1.2.x.x/', + 'url' => 'test_dispatch_pages/admin_index/param:value/param2:value2', + 'base' => '/cake/repo/branches/1.2.x.x' + ))); + $this->assertIdentical($expected, $controller); + } + +/** + * Test dispatching into the TestPlugin in the test_app + * + * @return void + * @access public + */ + function testTestPluginDispatch() { + $Dispatcher =& new TestDispatcher(); + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + App::objects('plugin', null, false); + Router::reload(); + Router::parse('/'); + + $url = '/test_plugin/tests/index'; + $result = $Dispatcher->dispatch($url, array('return' => 1)); + $this->assertTrue(class_exists('TestsController')); + $this->assertTrue(class_exists('TestPluginAppController')); + $this->assertTrue(class_exists('OtherComponentComponent')); + $this->assertTrue(class_exists('PluginsComponentComponent')); + + $this->assertEqual($result->params['controller'], 'tests'); + $this->assertEqual($result->params['plugin'], 'test_plugin'); + $this->assertEqual($result->params['action'], 'index'); + + App::build(); + } + +/** + * testChangingParamsFromBeforeFilter method + * + * @return void + * @access public + */ + function testChangingParamsFromBeforeFilter() { + $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + $Dispatcher =& new TestDispatcher(); + $url = 'some_posts/index/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $expected = array('missingAction', array(array( + 'className' => 'SomePostsController', + 'action' => 'view', + 'webroot' => '/cake/repo/branches/1.2.x.x/', + 'url' => '/cake/repo/branches/1.2.x.x/some_posts/index/param:value/param2:value2', + 'base' => '/cake/repo/branches/1.2.x.x' + ))); + $this->assertEqual($expected, $controller); + + $url = 'some_posts/something_else/param:value/param2:value2'; + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + + $expected = 'SomePosts'; + $this->assertEqual($expected, $controller->name); + + $expected = 'change'; + $this->assertEqual($expected, $controller->action); + + $expected = array('changed'); + $this->assertIdentical($expected, $controller->params['pass']); + } + +/** + * testStaticAssets method + * + * @return void + * @access public + */ + function testAssets() { + Router::reload(); + $Configure =& Configure::getInstance(); + $Configure->__objects = null; + + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'vendors' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors'. DS), + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + )); + + $Dispatcher =& new TestDispatcher(); + $debug = Configure::read('debug'); + Configure::write('debug', 0); + + ob_start(); + $Dispatcher->dispatch('theme/test_theme/../webroot/css/test_asset.css'); + $result = ob_get_clean(); + $this->assertFalse($result); + + ob_start(); + $Dispatcher->dispatch('theme/test_theme/pdfs'); + $result = ob_get_clean(); + $this->assertFalse($result); + + ob_start(); + $Dispatcher->dispatch('theme/test_theme/flash/theme_test.swf'); + $result = ob_get_clean(); + $file = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'themed' . DS . 'test_theme' . DS . 'webroot' . DS . 'flash' . DS . 'theme_test.swf'); + $this->assertEqual($file, $result); + $this->assertEqual('this is just a test to load swf file from the theme.', $result); + + ob_start(); + $Dispatcher->dispatch('theme/test_theme/pdfs/theme_test.pdf'); + $result = ob_get_clean(); + $file = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'themed' . DS . 'test_theme' . DS . 'webroot' . DS . 'pdfs' . DS . 'theme_test.pdf'); + $this->assertEqual($file, $result); + $this->assertEqual('this is just a test to load pdf file from the theme.', $result); + + ob_start(); + $Dispatcher->dispatch('theme/test_theme/img/test.jpg'); + $result = ob_get_clean(); + $file = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'themed' . DS . 'test_theme' . DS . 'webroot' . DS . 'img' . DS . 'test.jpg'); + $this->assertEqual($file, $result); + + $Dispatcher->params = $Dispatcher->parseParams('theme/test_theme/css/test_asset.css'); + ob_start(); + $Dispatcher->asset('theme/test_theme/css/test_asset.css'); + $result = ob_get_clean(); + $this->assertEqual('this is the test asset css file', $result); + + $Dispatcher->params = $Dispatcher->parseParams('theme/test_theme/js/theme.js'); + ob_start(); + $Dispatcher->asset('theme/test_theme/js/theme.js'); + $result = ob_get_clean(); + $this->assertEqual('root theme js file', $result); + + $Dispatcher->params = $Dispatcher->parseParams('theme/test_theme/js/one/theme_one.js'); + ob_start(); + $Dispatcher->asset('theme/test_theme/js/one/theme_one.js'); + $result = ob_get_clean(); + $this->assertEqual('nested theme js file', $result); + + ob_start(); + $Dispatcher->asset('test_plugin/root.js'); + $result = ob_get_clean(); + $expected = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS . 'webroot' . DS . 'root.js'); + $this->assertEqual($result, $expected); + + ob_start(); + $Dispatcher->dispatch('test_plugin/flash/plugin_test.swf'); + $result = ob_get_clean(); + $file = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS . 'webroot' . DS . 'flash' . DS . 'plugin_test.swf'); + $this->assertEqual($file, $result); + $this->assertEqual('this is just a test to load swf file from the plugin.', $result); + + ob_start(); + $Dispatcher->dispatch('test_plugin/pdfs/plugin_test.pdf'); + $result = ob_get_clean(); + $file = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS . 'webroot' . DS . 'pdfs' . DS . 'plugin_test.pdf'); + $this->assertEqual($file, $result); + $this->assertEqual('this is just a test to load pdf file from the plugin.', $result); + + ob_start(); + $Dispatcher->asset('test_plugin/js/test_plugin/test.js'); + $result = ob_get_clean(); + $this->assertEqual('alert("Test App");', $result); + + $Dispatcher->params = $Dispatcher->parseParams('test_plugin/js/test_plugin/test.js'); + ob_start(); + $Dispatcher->asset('test_plugin/js/test_plugin/test.js'); + $result = ob_get_clean(); + $this->assertEqual('alert("Test App");', $result); + + $Dispatcher->params = $Dispatcher->parseParams('test_plugin/css/test_plugin_asset.css'); + ob_start(); + $Dispatcher->asset('test_plugin/css/test_plugin_asset.css'); + $result = ob_get_clean(); + $this->assertEqual('this is the test plugin asset css file', $result); + + $Dispatcher->params = $Dispatcher->parseParams('test_plugin/img/cake.icon.gif'); + ob_start(); + $Dispatcher->asset('test_plugin/img/cake.icon.gif'); + $result = ob_get_clean(); + $file = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' .DS . 'webroot' . DS . 'img' . DS . 'cake.icon.gif'); + $this->assertEqual($file, $result); + + $Dispatcher->params = $Dispatcher->parseParams('plugin_js/js/plugin_js.js'); + ob_start(); + $Dispatcher->asset('plugin_js/js/plugin_js.js'); + $result = ob_get_clean(); + $expected = "alert('win sauce');"; + $this->assertEqual($result, $expected); + + $Dispatcher->params = $Dispatcher->parseParams('plugin_js/js/one/plugin_one.js'); + ob_start(); + $Dispatcher->asset('plugin_js/js/one/plugin_one.js'); + $result = ob_get_clean(); + $expected = "alert('plugin one nested js file');"; + $this->assertEqual($result, $expected); + Configure::write('debug', $debug); + //reset the header content-type without page can render as plain text. + header('Content-type: text/html'); + + $Dispatcher->params = $Dispatcher->parseParams('test_plugin/css/theme_one.htc'); + ob_start(); + $Dispatcher->asset('test_plugin/css/unknown.extension'); + $result = ob_get_clean(); + $this->assertEqual('Testing a file with unknown extension to mime mapping.', $result); + header('Content-type: text/html'); + + $Dispatcher->params = $Dispatcher->parseParams('test_plugin/css/theme_one.htc'); + ob_start(); + $Dispatcher->asset('test_plugin/css/theme_one.htc'); + $result = ob_get_clean(); + $this->assertEqual('htc file', $result); + header('Content-type: text/html'); + } + +/** + * test that missing asset processors trigger a 404 with no response body. + * + * @return void + */ + function testMissingAssetProcessor404() { + $Dispatcher =& new TestDispatcher(); + Configure::write('Asset.filter', array( + 'js' => '', + 'css' => null + )); + $this->assertNoErrors(); + + ob_start(); + $Dispatcher->asset('ccss/cake.generic.css'); + $result = ob_get_clean(); + $this->assertTrue($Dispatcher->stopped); + + header('HTTP/1.1 200 Ok'); + } + +/** + * test that asset filters work for theme and plugin assets + * + * @return void + */ + function testAssetFilterForThemeAndPlugins() { + $Dispatcher =& new TestDispatcher(); + Configure::write('Asset.filter', array( + 'js' => '', + 'css' => '' + )); + $Dispatcher->asset('theme/test_theme/ccss/cake.generic.css'); + $this->assertTrue($Dispatcher->stopped); + + $Dispatcher->stopped = false; + $Dispatcher->asset('theme/test_theme/cjs/debug_kit.js'); + $this->assertTrue($Dispatcher->stopped); + + $Dispatcher->stopped = false; + $Dispatcher->asset('test_plugin/ccss/cake.generic.css'); + $this->assertTrue($Dispatcher->stopped); + + $Dispatcher->stopped = false; + $Dispatcher->asset('test_plugin/cjs/debug_kit.js'); + $this->assertTrue($Dispatcher->stopped); + + $Dispatcher->stopped = false; + $Dispatcher->asset('css/ccss/debug_kit.css'); + $this->assertFalse($Dispatcher->stopped); + + $Dispatcher->stopped = false; + $Dispatcher->asset('js/cjs/debug_kit.js'); + $this->assertFalse($Dispatcher->stopped); + } +/** + * testFullPageCachingDispatch method + * + * @return void + * @access public + */ + function testFullPageCachingDispatch() { + Configure::write('Cache.disable', false); + Configure::write('Cache.check', true); + Configure::write('debug', 2); + + $_POST = array(); + $_SERVER['PHP_SELF'] = '/'; + + Router::reload(); + Router::connect('/', array('controller' => 'test_cached_pages', 'action' => 'index')); + + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS), + ), true); + + $dispatcher =& new TestDispatcher(); + $dispatcher->base = false; + + $url = '/'; + + ob_start(); + $dispatcher->dispatch($url); + $out = ob_get_clean(); + + ob_start(); + $dispatcher->cached($url); + $cached = ob_get_clean(); + + $result = str_replace(array("\t", "\r\n", "\n"), "", $out); + $cached = preg_replace('//', '', $cached); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $cached); + + $this->assertEqual($result, $expected); + + $filename = $this->__cachePath($dispatcher->here); + unlink($filename); + + $dispatcher->base = false; + $url = 'test_cached_pages/index'; + + ob_start(); + $dispatcher->dispatch($url); + $out = ob_get_clean(); + + ob_start(); + $dispatcher->cached($url); + $cached = ob_get_clean(); + + $result = str_replace(array("\t", "\r\n", "\n"), "", $out); + $cached = preg_replace('//', '', $cached); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $cached); + + $this->assertEqual($result, $expected); + $filename = $this->__cachePath($dispatcher->here); + unlink($filename); + + $url = 'TestCachedPages/index'; + + ob_start(); + $dispatcher->dispatch($url); + $out = ob_get_clean(); + + ob_start(); + $dispatcher->cached($url); + $cached = ob_get_clean(); + + $result = str_replace(array("\t", "\r\n", "\n"), "", $out); + $cached = preg_replace('//', '', $cached); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $cached); + + $this->assertEqual($result, $expected); + $filename = $this->__cachePath($dispatcher->here); + unlink($filename); + + $url = 'TestCachedPages/test_nocache_tags'; + + ob_start(); + $dispatcher->dispatch($url); + $out = ob_get_clean(); + + ob_start(); + $dispatcher->cached($url); + $cached = ob_get_clean(); + + $result = str_replace(array("\t", "\r\n", "\n"), "", $out); + $cached = preg_replace('//', '', $cached); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $cached); + + $this->assertEqual($result, $expected); + $filename = $this->__cachePath($dispatcher->here); + unlink($filename); + + $url = 'test_cached_pages/view/param/param'; + + ob_start(); + $dispatcher->dispatch($url); + $out = ob_get_clean(); + + ob_start(); + $dispatcher->cached($url); + $cached = ob_get_clean(); + + $result = str_replace(array("\t", "\r\n", "\n"), "", $out); + $cached = preg_replace('//', '', $cached); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $cached); + + $this->assertEqual($result, $expected); + $filename = $this->__cachePath($dispatcher->here); + unlink($filename); + + $url = 'test_cached_pages/view/foo:bar/value:goo'; + + ob_start(); + $dispatcher->dispatch($url); + $out = ob_get_clean(); + + ob_start(); + $dispatcher->cached($url); + $cached = ob_get_clean(); + + $result = str_replace(array("\t", "\r\n", "\n"), "", $out); + $cached = preg_replace('//', '', $cached); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $cached); + + $this->assertEqual($result, $expected); + $filename = $this->__cachePath($dispatcher->here); + $this->assertTrue(file_exists($filename)); + unlink($filename); + } + +/** + * test that cached() registers a view and un-registers it. Tests + * that helpers using ClassRegistry::getObject('view'); don't fail + * + * @return void + */ + function testCachedRegisteringViewObject() { + Configure::write('Cache.disable', false); + Configure::write('Cache.check', true); + Configure::write('debug', 2); + + $_POST = array(); + $_SERVER['PHP_SELF'] = '/'; + + Router::reload(); + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + )); + + $dispatcher =& new TestDispatcher(); + $dispatcher->base = false; + + $url = 'test_cached_pages/cache_form'; + ob_start(); + $dispatcher->dispatch($url); + $out = ob_get_clean(); + + ClassRegistry::flush(); + + ob_start(); + $dispatcher->cached($url); + $cached = ob_get_clean(); + + $result = str_replace(array("\t", "\r\n", "\n"), "", $out); + $cached = preg_replace('//', '', $cached); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $cached); + + $this->assertEqual($result, $expected); + $filename = $this->__cachePath($dispatcher->here); + @unlink($filename); + ClassRegistry::flush(); + } + +/** + * testHttpMethodOverrides method + * + * @return void + * @access public + */ + function testHttpMethodOverrides() { + Router::reload(); + Router::mapResources('Posts'); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $dispatcher =& new Dispatcher(); + $dispatcher->base = false; + + $result = $dispatcher->parseParams('/posts'); + $expected = array('pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'add', '[method]' => 'POST', 'form' => array(), 'url' => array()); + $this->assertEqual($result, $expected); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'PUT'; + + $result = $dispatcher->parseParams('/posts/5'); + $expected = array('pass' => array('5'), 'named' => array(), 'id' => '5', 'plugin' => null, 'controller' => 'posts', 'action' => 'edit', '[method]' => 'PUT', 'form' => array(), 'url' => array()); + $this->assertEqual($result, $expected); + + unset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $result = $dispatcher->parseParams('/posts/5'); + $expected = array('pass' => array('5'), 'named' => array(), 'id' => '5', 'plugin' => null, 'controller' => 'posts', 'action' => 'view', '[method]' => 'GET', 'form' => array(), 'url' => array()); + $this->assertEqual($result, $expected); + + $_POST['_method'] = 'PUT'; + + $result = $dispatcher->parseParams('/posts/5'); + $expected = array('pass' => array('5'), 'named' => array(), 'id' => '5', 'plugin' => null, 'controller' => 'posts', 'action' => 'edit', '[method]' => 'PUT', 'form' => array(), 'url' => array()); + $this->assertEqual($result, $expected); + + $_POST['_method'] = 'POST'; + $_POST['data'] = array('Post' => array('title' => 'New Post')); + $_POST['extra'] = 'data'; + $_SERVER = array(); + + $result = $dispatcher->parseParams('/posts'); + $expected = array( + 'pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'add', + '[method]' => 'POST', 'form' => array('extra' => 'data'), 'data' => array('Post' => array('title' => 'New Post')), + 'url' => array() + ); + $this->assertEqual($result, $expected); + + unset($_POST['_method']); + } + +/** + * Tests that invalid characters cannot be injected into the application base path. + * + * @return void + * @access public + */ + function testBasePathInjection() { + $self = $_SERVER['PHP_SELF']; + $_SERVER['PHP_SELF'] = urldecode( + "/index.php/%22%3E%3Ch1%20onclick=%22alert('xss');%22%3Eheya%3C/h1%3E" + ); + + $dispatcher =& new Dispatcher(); + $result = $dispatcher->baseUrl(); + $expected = '/index.php/h1 onclick=alert(xss);heya'; + $this->assertEqual($result, $expected); + } + +/** + * testEnvironmentDetection method + * + * @return void + * @access public + */ + function testEnvironmentDetection() { + $dispatcher =& new Dispatcher(); + + $environments = array( + 'IIS' => array( + 'No rewrite base path' => array( + 'App' => array('base' => false, 'baseUrl' => '/index.php?', 'server' => 'IIS'), + 'SERVER' => array('HTTPS' => 'off', 'SCRIPT_NAME' => '/index.php', 'PATH_TRANSLATED' => 'C:\\Inetpub\\wwwroot', 'QUERY_STRING' => '', 'REMOTE_ADDR' => '127.0.0.1', 'REMOTE_HOST' => '127.0.0.1', 'REQUEST_METHOD' => 'GET', 'SERVER_NAME' => 'localhost', 'SERVER_PORT' => '80', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'APPL_PHYSICAL_PATH' => 'C:\\Inetpub\\wwwroot\\', 'REQUEST_URI' => '/index.php', 'URL' => '/index.php', 'SCRIPT_FILENAME' => 'C:\\Inetpub\\wwwroot\\index.php', 'ORIG_PATH_INFO' => '/index.php', 'PATH_INFO' => '', 'ORIG_PATH_TRANSLATED' => 'C:\\Inetpub\\wwwroot\\index.php', 'DOCUMENT_ROOT' => 'C:\\Inetpub\\wwwroot', 'PHP_SELF' => '/index.php', 'HTTP_HOST' => 'localhost', 'argv' => array(), 'argc' => 0), + 'reload' => true, + 'path' => '' + ), + 'No rewrite with path' => array( + 'SERVER' => array('QUERY_STRING' => '/posts/add', 'REQUEST_URI' => '/index.php?/posts/add', 'URL' => '/index.php?/posts/add', 'argv' => array('/posts/add'), 'argc' => 1), + 'reload' => false, + 'path' => '/posts/add' + ), + 'No rewrite sub dir 1' => array( + 'GET' => array(), + 'SERVER' => array('QUERY_STRING' => '', 'REQUEST_URI' => '/index.php', 'URL' => '/index.php', 'SCRIPT_FILENAME' => 'C:\\Inetpub\\wwwroot\\index.php', 'ORIG_PATH_INFO' => '/index.php', 'PATH_INFO' => '', 'ORIG_PATH_TRANSLATED' => 'C:\\Inetpub\\wwwroot\\index.php', 'DOCUMENT_ROOT' => 'C:\\Inetpub\\wwwroot', 'PHP_SELF' => '/index.php', 'argv' => array(), 'argc' => 0), + 'reload' => false, + 'path' => '' + ), + 'No rewrite sub dir 1 with path' => array( + 'GET' => array('/posts/add' => ''), + 'SERVER' => array('QUERY_STRING' => '/posts/add', 'REQUEST_URI' => '/index.php?/posts/add', 'URL' => '/index.php?/posts/add', 'SCRIPT_FILENAME' => 'C:\\Inetpub\\wwwroot\\index.php', 'argv' => array('/posts/add'), 'argc' => 1), + 'reload' => false, + 'path' => '/posts/add' + ), + 'No rewrite sub dir 2' => array( + 'App' => array('base' => false, 'baseUrl' => '/site/index.php?', 'dir' => 'app', 'webroot' => 'webroot', 'server' => 'IIS'), + 'GET' => array(), + 'POST' => array(), + 'SERVER' => array('SCRIPT_NAME' => '/site/index.php', 'PATH_TRANSLATED' => 'C:\\Inetpub\\wwwroot', 'QUERY_STRING' => '', 'REQUEST_URI' => '/site/index.php', 'URL' => '/site/index.php', 'SCRIPT_FILENAME' => 'C:\\Inetpub\\wwwroot\\site\\index.php', 'DOCUMENT_ROOT' => 'C:\\Inetpub\\wwwroot', 'PHP_SELF' => '/site/index.php', 'argv' => array(), 'argc' => 0), + 'reload' => false, + 'path' => '' + ), + 'No rewrite sub dir 2 with path' => array( + 'GET' => array('/posts/add' => ''), + 'SERVER' => array('SCRIPT_NAME' => '/site/index.php', 'PATH_TRANSLATED' => 'C:\\Inetpub\\wwwroot', 'QUERY_STRING' => '/posts/add', 'REQUEST_URI' => '/site/index.php?/posts/add', 'URL' => '/site/index.php?/posts/add', 'ORIG_PATH_TRANSLATED' => 'C:\\Inetpub\\wwwroot\\site\\index.php', 'DOCUMENT_ROOT' => 'C:\\Inetpub\\wwwroot', 'PHP_SELF' => '/site/index.php', 'argv' => array('/posts/add'), 'argc' => 1), + 'reload' => false, + 'path' => '/posts/add' + ) + ), + 'Apache' => array( + 'No rewrite base path' => array( + 'App' => array('base' => false, 'baseUrl' => '/index.php', 'dir' => 'app', 'webroot' => 'webroot'), + 'SERVER' => array( + 'SERVER_NAME' => 'localhost', + 'SERVER_ADDR' => '::1', + 'SERVER_PORT' => '80', + 'REMOTE_ADDR' => '::1', + 'DOCUMENT_ROOT' => '/Library/WebServer/Documents/officespace/app/webroot', + 'SCRIPT_FILENAME' => '/Library/WebServer/Documents/site/app/webroot/index.php', + 'QUERY_STRING' => '', + 'REQUEST_URI' => '/', + 'SCRIPT_NAME' => '/index.php', + 'PHP_SELF' => '/index.php', + 'argv' => array(), + 'argc' => 0 + ), + 'reload' => true, + 'path' => '' + ), + 'No rewrite with path' => array( + 'SERVER' => array( + 'HTTP_HOST' => 'localhost', + 'DOCUMENT_ROOT' => '/Library/WebServer/Documents/officespace/app/webroot', + 'SCRIPT_FILENAME' => '/Library/WebServer/Documents/officespace/app/webroot/index.php', + 'QUERY_STRING' => '', + 'REQUEST_URI' => '/index.php/posts/add', + 'SCRIPT_NAME' => '/index.php', + 'PATH_INFO' => '/posts/add', + 'PHP_SELF' => '/index.php/posts/add', + 'argv' => array(), + 'argc' => 0), + 'reload' => false, + 'path' => '/posts/add' + ), + 'GET Request at base domain' => array( + 'App' => array('base' => false, 'baseUrl' => null, 'dir' => 'app', 'webroot' => 'webroot'), + 'SERVER' => array( + 'HTTP_HOST' => 'cake.1.2', + 'SERVER_NAME' => 'cake.1.2', + 'SERVER_ADDR' => '127.0.0.1', + 'SERVER_PORT' => '80', + 'REMOTE_ADDR' => '127.0.0.1', + 'DOCUMENT_ROOT' => '/Volumes/Home/htdocs/cake/repo/branches/1.2.x.x/app/webroot', + 'SCRIPT_FILENAME' => '/Volumes/Home/htdocs/cake/repo/branches/1.2.x.x/app/webroot/index.php', + 'REMOTE_PORT' => '53550', + 'QUERY_STRING' => 'a=b', + 'REQUEST_URI' => '/?a=b', + 'SCRIPT_NAME' => '/index.php', + 'PHP_SELF' => '/index.php' + ), + 'GET' => array('a' => 'b'), + 'POST' => array(), + 'reload' => true, + 'path' => '', + 'urlParams' => array('a' => 'b'), + 'environment' => array('CGI_MODE' => false) + ), + 'New CGI no mod_rewrite' => array( + 'App' => array('base' => false, 'baseUrl' => '/limesurvey20/index.php', 'dir' => 'app', 'webroot' => 'webroot'), + 'SERVER' => array( + 'DOCUMENT_ROOT' => '/home/.sites/110/site313/web', + 'PATH_INFO' => '/installations', + 'PATH_TRANSLATED' => '/home/.sites/110/site313/web/limesurvey20/index.php', + 'PHPRC' => '/home/.sites/110/site313', + 'QUERY_STRING' => '', + 'REQUEST_URI' => '/limesurvey20/index.php/installations', + 'SCRIPT_FILENAME' => '/home/.sites/110/site313/web/limesurvey20/index.php', + 'SCRIPT_NAME' => '/limesurvey20/index.php', + 'SCRIPT_URI' => 'http://www.gisdat-umfragen.at/limesurvey20/index.php/installations', + 'PHP_SELF' => '/limesurvey20/index.php/installations', + 'CGI_MODE' => true + ), + 'GET' => array(), + 'POST' => array(), + 'reload' => true, + 'path' => '/installations', + 'urlParams' => array(), + 'environment' => array('CGI_MODE' => true) + ) + ) + ); + $backup = $this->__backupEnvironment(); + + foreach ($environments as $name => $env) { + foreach ($env as $descrip => $settings) { + if ($settings['reload']) { + $this->__reloadEnvironment(); + } + $this->__loadEnvironment($settings); + $this->assertEqual($dispatcher->uri(), $settings['path'], "%s on environment: {$name}, on setting: {$descrip}"); + + if (isset($settings['urlParams'])) { + $this->assertEqual($_GET, $settings['urlParams'], "%s on environment: {$name}, on setting: {$descrip}"); + } + if (isset($settings['environment'])) { + foreach ($settings['environment'] as $key => $val) { + $this->assertEqual(env($key), $val, "%s on key {$key} on environment: {$name}, on setting: {$descrip}"); + } + } + } + } + $this->__loadEnvironment(array_merge(array('reload' => true), $backup)); + } + +/** + * Tests that the Dispatcher does not return an empty action + * + * @return void + * @access public + */ + function testTrailingSlash() { + $_POST = array(); + $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php'; + + Router::reload(); + $Dispatcher =& new TestDispatcher(); + Router::connect('/myalias/:action/*', array('controller' => 'my_controller', 'action' => null)); + + $Dispatcher->base = false; + $url = 'myalias/'; //Fails + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $result = $Dispatcher->parseParams($url); + $this->assertEqual('index', $result['action']); + + $url = 'myalias'; //Passes + $controller = $Dispatcher->dispatch($url, array('return' => 1)); + $result = $Dispatcher->parseParams($url); + $this->assertEqual('index', $result['action']); + } + +/** + * backupEnvironment method + * + * @return void + * @access private + */ + function __backupEnvironment() { + return array( + 'App' => Configure::read('App'), + 'GET' => $_GET, + 'POST' => $_POST, + 'SERVER'=> $_SERVER + ); + } + +/** + * reloadEnvironment method + * + * @return void + * @access private + */ + function __reloadEnvironment() { + foreach ($_GET as $key => $val) { + unset($_GET[$key]); + } + foreach ($_POST as $key => $val) { + unset($_POST[$key]); + } + foreach ($_SERVER as $key => $val) { + unset($_SERVER[$key]); + } + Configure::write('App', array()); + } + +/** + * loadEnvironment method + * + * @param mixed $env + * @return void + * @access private + */ + function __loadEnvironment($env) { + if ($env['reload']) { + $this->__reloadEnvironment(); + } + + if (isset($env['App'])) { + Configure::write('App', $env['App']); + } + + if (isset($env['GET'])) { + foreach ($env['GET'] as $key => $val) { + $_GET[$key] = $val; + } + } + + if (isset($env['POST'])) { + foreach ($env['POST'] as $key => $val) { + $_POST[$key] = $val; + } + } + + if (isset($env['SERVER'])) { + foreach ($env['SERVER'] as $key => $val) { + $_SERVER[$key] = $val; + } + } + } + +/** + * cachePath method + * + * @param mixed $her + * @return string + * @access private + */ + function __cachePath($here) { + $path = $here; + if ($here == '/') { + $path = 'home'; + } + $path = strtolower(Inflector::slug($path)); + + $filename = CACHE . 'views' . DS . $path . '.php'; + + if (!file_exists($filename)) { + $filename = CACHE . 'views' . DS . $path . '_index.php'; + } + return $filename; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache.test.php new file mode 100644 index 000000000..149118e9c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache.test.php @@ -0,0 +1,372 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!class_exists('Cache')) { + require LIBS . 'cache.php'; +} + +/** + * CacheTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class CacheTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_cacheDisable = Configure::read('Cache.disable'); + Configure::write('Cache.disable', false); + + $this->_defaultCacheConfig = Cache::config('default'); + Cache::config('default', array('engine' => 'File', 'path' => TMP . 'tests')); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('Cache.disable', $this->_cacheDisable); + Cache::config('default', $this->_defaultCacheConfig['settings']); + } + +/** + * testConfig method + * + * @access public + * @return void + */ + function testConfig() { + $settings = array('engine' => 'File', 'path' => TMP . 'tests', 'prefix' => 'cake_test_'); + $results = Cache::config('new', $settings); + $this->assertEqual($results, Cache::config('new')); + $this->assertTrue(isset($results['engine'])); + $this->assertTrue(isset($results['settings'])); + } + +/** + * Check that no fatal errors are issued doing normal things when Cache.disable is true. + * + * @return void + */ + function testNonFatalErrorsWithCachedisable() { + Configure::write('Cache.disable', true); + Cache::config('test', array('engine' => 'File', 'path' => TMP, 'prefix' => 'error_test_')); + + Cache::write('no_save', 'Noooo!', 'test'); + Cache::read('no_save', 'test'); + Cache::delete('no_save', 'test'); + Cache::set('duration', '+10 minutes'); + + Configure::write('Cache.disable', false); + } + +/** + * test configuring CacheEngines in App/libs + * + * @return void + */ + function testConfigWithLibAndPluginEngines() { + App::build(array( + 'libs' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'libs' . DS), + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + ), true); + + $settings = array('engine' => 'TestAppCache', 'path' => TMP, 'prefix' => 'cake_test_'); + $result = Cache::config('libEngine', $settings); + $this->assertEqual($result, Cache::config('libEngine')); + + $settings = array('engine' => 'TestPlugin.TestPluginCache', 'path' => TMP, 'prefix' => 'cake_test_'); + $result = Cache::config('pluginLibEngine', $settings); + $this->assertEqual($result, Cache::config('pluginLibEngine')); + + Cache::drop('libEngine'); + Cache::drop('pluginLibEngine'); + + App::build(); + } + +/** + * testInvalidConfig method + * + * Test that the cache class doesn't cause fatal errors with a partial path + * + * @access public + * @return void + */ + function testInvaidConfig() { + $this->expectError(); + Cache::config('invalid', array( + 'engine' => 'File', + 'duration' => '+1 year', + 'prefix' => 'testing_invalid_', + 'path' => 'data/', + 'serialize' => true, + 'random' => 'wii' + )); + $read = Cache::read('Test', 'invalid'); + $this->assertEqual($read, null); + } + +/** + * testConfigChange method + * + * @access public + * @return void + */ + function testConfigChange() { + $_cacheConfigSessions = Cache::config('sessions'); + $_cacheConfigTests = Cache::config('tests'); + + $result = Cache::config('sessions', array('engine'=> 'File', 'path' => TMP . 'sessions')); + $this->assertEqual($result['settings'], Cache::settings('sessions')); + + $result = Cache::config('tests', array('engine'=> 'File', 'path' => TMP . 'tests')); + $this->assertEqual($result['settings'], Cache::settings('tests')); + + Cache::config('sessions', $_cacheConfigSessions['settings']); + Cache::config('tests', $_cacheConfigTests['settings']); + } + +/** + * test that calling config() sets the 'default' configuration up. + * + * @return void + */ + function testConfigSettingDefaultConfigKey() { + Cache::config('test_name', array('engine' => 'File', 'prefix' => 'test_name_')); + + Cache::config('test_name'); + Cache::write('value_one', 'I am cached'); + $result = Cache::read('value_one'); + $this->assertEqual($result, 'I am cached'); + + Cache::config('default'); + $result = Cache::read('value_one'); + $this->assertEqual($result, null); + + Cache::write('value_one', 'I am in default config!'); + $result = Cache::read('value_one'); + $this->assertEqual($result, 'I am in default config!'); + + Cache::config('test_name'); + $result = Cache::read('value_one'); + $this->assertEqual($result, 'I am cached'); + + Cache::delete('value_one'); + Cache::config('default'); + Cache::delete('value_one'); + } + +/** + * testWritingWithConfig method + * + * @access public + * @return void + */ + function testWritingWithConfig() { + $_cacheConfigSessions = Cache::config('sessions'); + + Cache::write('test_somthing', 'this is the test data', 'tests'); + + $expected = array( + 'path' => TMP . 'sessions', + 'prefix' => 'cake_', + 'lock' => false, + 'serialize' => true, + 'duration' => 3600, + 'probability' => 100, + 'engine' => 'File', + 'isWindows' => DIRECTORY_SEPARATOR == '\\' + ); + $this->assertEqual($expected, Cache::settings('sessions')); + + Cache::config('sessions', $_cacheConfigSessions['settings']); + } + +/** + * test that configured returns an array of the currently configured cache + * settings + * + * @return void + */ + function testConfigured() { + $result = Cache::configured(); + $this->assertTrue(in_array('_cake_core_', $result)); + $this->assertTrue(in_array('default', $result)); + } + +/** + * testInitSettings method + * + * @access public + * @return void + */ + function testInitSettings() { + Cache::config('for_test', array('engine' => 'File', 'path' => TMP . 'tests')); + + $settings = Cache::settings(); + $expecting = array( + 'engine' => 'File', + 'duration'=> 3600, + 'probability' => 100, + 'path'=> TMP . 'tests', + 'prefix'=> 'cake_', + 'lock' => false, + 'serialize'=> true, + 'isWindows' => DIRECTORY_SEPARATOR == '\\' + ); + $this->assertEqual($settings, $expecting); + } + +/** + * test that drop removes cache configs, and that further attempts to use that config + * do not work. + * + * @return void + */ + function testDrop() { + App::build(array( + 'libs' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'libs' . DS), + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + ), true); + + $result = Cache::drop('some_config_that_does_not_exist'); + $this->assertFalse($result); + + $_testsConfig = Cache::config('tests'); + $result = Cache::drop('tests'); + $this->assertTrue($result); + + Cache::config('unconfigTest', array( + 'engine' => 'TestAppCache' + )); + $this->assertTrue(Cache::isInitialized('unconfigTest')); + + $this->assertTrue(Cache::drop('unconfigTest')); + $this->assertFalse(Cache::isInitialized('TestAppCache')); + + Cache::config('tests', $_testsConfig); + App::build(); + } + +/** + * testWriteEmptyValues method + * + * @access public + * @return void + */ + function testWriteEmptyValues() { + Cache::write('App.falseTest', false); + $this->assertIdentical(Cache::read('App.falseTest'), false); + + Cache::write('App.trueTest', true); + $this->assertIdentical(Cache::read('App.trueTest'), true); + + Cache::write('App.nullTest', null); + $this->assertIdentical(Cache::read('App.nullTest'), null); + + Cache::write('App.zeroTest', 0); + $this->assertIdentical(Cache::read('App.zeroTest'), 0); + + Cache::write('App.zeroTest2', '0'); + $this->assertIdentical(Cache::read('App.zeroTest2'), '0'); + } + +/** + * testCacheDisable method + * + * Check that the "Cache.disable" configuration and a change to it + * (even after a cache config has been setup) is taken into account. + * + * @link https://trac.cakephp.org/ticket/6236 + * @access public + * @return void + */ + function testCacheDisable() { + Configure::write('Cache.disable', false); + Cache::config('test_cache_disable_1', array('engine'=> 'File', 'path' => TMP . 'tests')); + + $this->assertTrue(Cache::write('key_1', 'hello')); + $this->assertIdentical(Cache::read('key_1'), 'hello'); + + Configure::write('Cache.disable', true); + + $this->assertFalse(Cache::write('key_2', 'hello')); + $this->assertFalse(Cache::read('key_2')); + + Configure::write('Cache.disable', false); + + $this->assertTrue(Cache::write('key_3', 'hello')); + $this->assertIdentical(Cache::read('key_3'), 'hello'); + + Configure::write('Cache.disable', true); + Cache::config('test_cache_disable_2', array('engine'=> 'File', 'path' => TMP . 'tests')); + + $this->assertFalse(Cache::write('key_4', 'hello')); + $this->assertFalse(Cache::read('key_4')); + + Configure::write('Cache.disable', false); + + $this->assertTrue(Cache::write('key_5', 'hello')); + $this->assertIdentical(Cache::read('key_5'), 'hello'); + + Configure::write('Cache.disable', true); + + $this->assertFalse(Cache::write('key_6', 'hello')); + $this->assertFalse(Cache::read('key_6')); + } + +/** + * testSet method + * + * @access public + * @return void + */ + function testSet() { + $_cacheSet = Cache::set(); + + Cache::set(array('duration' => '+1 year')); + $data = Cache::read('test_cache'); + $this->assertFalse($data); + + $data = 'this is just a simple test of the cache system'; + $write = Cache::write('test_cache', $data); + $this->assertTrue($write); + + Cache::set(array('duration' => '+1 year')); + $data = Cache::read('test_cache'); + $this->assertEqual($data, 'this is just a simple test of the cache system'); + + Cache::delete('test_cache'); + + $global = Cache::settings(); + + Cache::set($_cacheSet); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/apc.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/apc.test.php new file mode 100644 index 000000000..883f9b852 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/apc.test.php @@ -0,0 +1,196 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.cache + * @since CakePHP(tm) v 1.2.0.5434 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!class_exists('Cache')) { + require LIBS . 'cache.php'; +} + +/** + * ApcEngineTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.cache + */ +class ApcEngineTest extends CakeTestCase { + +/** + * skip method + * + * @access public + * @return void + */ + function skip() { + $skip = true; + if (function_exists('apc_store')) { + $skip = false; + } + $this->skipIf($skip, '%s Apc is not installed or configured properly'); + } + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_cacheDisable = Configure::read('Cache.disable'); + Configure::write('Cache.disable', false); + Cache::config('apc', array('engine' => 'Apc', 'prefix' => 'cake_')); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('Cache.disable', $this->_cacheDisable); + Cache::drop('apc'); + Cache::config('default'); + } + +/** + * testReadAndWriteCache method + * + * @access public + * @return void + */ + function testReadAndWriteCache() { + Cache::set(array('duration' => 1)); + + $result = Cache::read('test'); + $expecting = ''; + $this->assertEqual($result, $expecting); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('test', $data); + $this->assertTrue($result); + + $result = Cache::read('test'); + $expecting = $data; + $this->assertEqual($result, $expecting); + + Cache::delete('test'); + } + +/** + * testExpiry method + * + * @access public + * @return void + */ + function testExpiry() { + Cache::set(array('duration' => 1)); + + $result = Cache::read('test'); + $this->assertFalse($result); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + + Cache::set(array('duration' => 1)); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + } + +/** + * testDeleteCache method + * + * @access public + * @return void + */ + function testDeleteCache() { + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('delete_test', $data); + $this->assertTrue($result); + + $result = Cache::delete('delete_test'); + $this->assertTrue($result); + } + +/** + * testDecrement method + * + * @access public + * @return void + */ + function testDecrement() { + if ($this->skipIf(!function_exists('apc_dec'), 'No apc_dec() function, cannot test decrement() %s')) { + return; + } + $result = Cache::write('test_decrement', 5); + $this->assertTrue($result); + + $result = Cache::decrement('test_decrement'); + $this->assertEqual(4, $result); + + $result = Cache::read('test_decrement'); + $this->assertEqual(4, $result); + + $result = Cache::decrement('test_decrement', 2); + $this->assertEqual(2, $result); + + $result = Cache::read('test_decrement'); + $this->assertEqual(2, $result); + + } + +/** + * testIncrement method + * + * @access public + * @return void + */ + function testIncrement() { + if ($this->skipIf(!function_exists('apc_inc'), 'No apc_inc() function, cannot test increment() %s')) { + return; + } + $result = Cache::write('test_increment', 5); + $this->assertTrue($result); + + $result = Cache::increment('test_increment'); + $this->assertEqual(6, $result); + + $result = Cache::read('test_increment'); + $this->assertEqual(6, $result); + + $result = Cache::increment('test_increment', 2); + $this->assertEqual(8, $result); + + $result = Cache::read('test_increment'); + $this->assertEqual(8, $result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/file.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/file.test.php new file mode 100644 index 000000000..ffb571f73 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/file.test.php @@ -0,0 +1,404 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.cache + * @since CakePHP(tm) v 1.2.0.5434 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!class_exists('Cache')) { + require LIBS . 'cache.php'; +} + +/** + * FileEngineTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.cache + */ +class FileEngineTest extends CakeTestCase { + +/** + * config property + * + * @var array + * @access public + */ + var $config = array(); + +/** + * startCase method + * + * @access public + * @return void + */ + function startCase() { + $this->_cacheDisable = Configure::read('Cache.disable'); + $this->_cacheConfig = Cache::config('default'); + Configure::write('Cache.disable', false); + Cache::config('default', array('engine' => 'File', 'path' => CACHE)); + } + +/** + * endCase method + * + * @access public + * @return void + */ + function endCase() { + Configure::write('Cache.disable', $this->_cacheDisable); + Cache::config('default', $this->_cacheConfig['settings']); + } + +/** + * testCacheDirChange method + * + * @access public + * @return void + */ + function testCacheDirChange() { + $result = Cache::config('sessions', array('engine'=> 'File', 'path' => TMP . 'sessions')); + $this->assertEqual($result['settings'], Cache::settings('sessions')); + + $result = Cache::config('sessions', array('engine'=> 'File', 'path' => TMP . 'tests')); + $this->assertEqual($result['settings'], Cache::settings('sessions')); + $this->assertNotEqual($result['settings'], Cache::settings('default')); + } + +/** + * testReadAndWriteCache method + * + * @access public + * @return void + */ + function testReadAndWriteCache() { + Cache::config('default'); + + $result = Cache::write(null, 'here'); + $this->assertFalse($result); + + Cache::set(array('duration' => 1)); + + $result = Cache::read('test'); + $expecting = ''; + $this->assertEqual($result, $expecting); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('test', $data); + $this->assertTrue(file_exists(CACHE . 'cake_test')); + + $result = Cache::read('test'); + $expecting = $data; + $this->assertEqual($result, $expecting); + + Cache::delete('test'); + } + +/** + * testExpiry method + * + * @access public + * @return void + */ + function testExpiry() { + Cache::set(array('duration' => 1)); + + $result = Cache::read('test'); + $this->assertFalse($result); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + + Cache::set(array('duration' => "+1 second")); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + } + +/** + * testDeleteCache method + * + * @access public + * @return void + */ + function testDeleteCache() { + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('delete_test', $data); + $this->assertTrue($result); + + $result = Cache::delete('delete_test'); + $this->assertTrue($result); + $this->assertFalse(file_exists(TMP . 'tests' . DS . 'delete_test')); + + $result = Cache::delete('delete_test'); + $this->assertFalse($result); + } + +/** + * testSerialize method + * + * @access public + * @return void + */ + function testSerialize() { + Cache::config('default', array('engine' => 'File', 'serialize' => true)); + $data = 'this is a test of the emergency broadcasting system'; + $write = Cache::write('serialize_test', $data); + $this->assertTrue($write); + + Cache::config('default', array('serialize' => false)); + $read = Cache::read('serialize_test'); + + $newread = Cache::read('serialize_test'); + + $delete = Cache::delete('serialize_test'); + + $this->assertIdentical($read, serialize($data)); + + $this->assertIdentical(unserialize($newread), $data); + } + +/** + * testClear method + * + * @access public + * @return void + */ + function testClear() { + Cache::config('default', array('engine' => 'File', 'duration' => 1)); + $data = 'this is a test of the emergency broadcasting system'; + $write = Cache::write('serialize_test1', $data); + $write = Cache::write('serialize_test2', $data); + $write = Cache::write('serialize_test3', $data); + $this->assertTrue(file_exists(CACHE . 'cake_serialize_test1')); + $this->assertTrue(file_exists(CACHE . 'cake_serialize_test2')); + $this->assertTrue(file_exists(CACHE . 'cake_serialize_test3')); + sleep(2); + $result = Cache::clear(true); + $this->assertTrue($result); + $this->assertFalse(file_exists(CACHE . 'cake_serialize_test1')); + $this->assertFalse(file_exists(CACHE . 'cake_serialize_test2')); + $this->assertFalse(file_exists(CACHE . 'cake_serialize_test3')); + + $data = 'this is a test of the emergency broadcasting system'; + $write = Cache::write('serialize_test1', $data); + $write = Cache::write('serialize_test2', $data); + $write = Cache::write('serialize_test3', $data); + $this->assertTrue(file_exists(CACHE . 'cake_serialize_test1')); + $this->assertTrue(file_exists(CACHE . 'cake_serialize_test2')); + $this->assertTrue(file_exists(CACHE . 'cake_serialize_test3')); + + $result = Cache::clear(); + $this->assertTrue($result); + $this->assertFalse(file_exists(CACHE . 'cake_serialize_test1')); + $this->assertFalse(file_exists(CACHE . 'cake_serialize_test2')); + $this->assertFalse(file_exists(CACHE . 'cake_serialize_test3')); + + Cache::config('default', array('engine' => 'File', 'path' => CACHE . 'views')); + + $data = 'this is a test of the emergency broadcasting system'; + $write = Cache::write('controller_view_1', $data); + $write = Cache::write('controller_view_2', $data); + $write = Cache::write('controller_view_3', $data); + $write = Cache::write('controller_view_10', $data); + $write = Cache::write('controller_view_11', $data); + $write = Cache::write('controller_view_12', $data); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_1')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_2')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_3')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_10')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_11')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_12')); + + clearCache('controller_view_1', 'views', ''); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_1')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_2')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_3')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_10')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_11')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_12')); + + clearCache('controller_view', 'views', ''); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_1')); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_2')); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_3')); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_10')); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_11')); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_12')); + + $write = Cache::write('controller_view_1', $data); + $write = Cache::write('controller_view_2', $data); + $write = Cache::write('controller_view_3', $data); + $write = Cache::write('controller_view_10', $data); + $write = Cache::write('controller_view_11', $data); + $write = Cache::write('controller_view_12', $data); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_1')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_2')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_3')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_10')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_11')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_12')); + + clearCache(array('controller_view_2', 'controller_view_11', 'controller_view_12'), 'views', ''); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_1')); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_2')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_3')); + $this->assertTrue(file_exists(CACHE . 'views'. DS . 'cake_controller_view_10')); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_11')); + $this->assertFalse(file_exists(CACHE . 'views'. DS . 'cake_controller_view_12')); + + clearCache('controller_view'); + + Cache::config('default', array('engine' => 'File', 'path' => CACHE)); + } + +/** + * test that clear() doesn't wipe files not in the current engine's prefix. + * + * @return void + */ + function testClearWithPrefixes() { + $FileOne =& new FileEngine(); + $FileOne->init(array( + 'prefix' => 'prefix_one_', + 'duration' => DAY + )); + $FileTwo =& new FileEngine(); + $FileTwo->init(array( + 'prefix' => 'prefix_two_', + 'duration' => DAY + )); + + $data1 = $data2 = $expected = 'content to cache'; + $FileOne->write('key_one', $data1, DAY); + $FileTwo->write('key_two', $data2, DAY); + + $this->assertEqual($FileOne->read('key_one'), $expected); + $this->assertEqual($FileTwo->read('key_two'), $expected); + + $FileOne->clear(false); + $this->assertEqual($FileTwo->read('key_two'), $expected, 'secondary config was cleared by accident.'); + } + +/** + * testKeyPath method + * + * @access public + * @return void + */ + function testKeyPath() { + $result = Cache::write('views.countries.something', 'here'); + $this->assertTrue($result); + $this->assertTrue(file_exists(CACHE . 'cake_views_countries_something')); + + $result = Cache::read('views.countries.something'); + $this->assertEqual($result, 'here'); + + $result = Cache::clear(); + $this->assertTrue($result); + } + +/** + * testRemoveWindowsSlashesFromCache method + * + * @access public + * @return void + */ + function testRemoveWindowsSlashesFromCache() { + Cache::config('windows_test', array('engine' => 'File', 'isWindows' => true, 'prefix' => null, 'path' => TMP)); + + $expected = array ( + 'C:\dev\prj2\sites\cake\libs' => array( + 0 => 'C:\dev\prj2\sites\cake\libs', 1 => 'C:\dev\prj2\sites\cake\libs\view', + 2 => 'C:\dev\prj2\sites\cake\libs\view\scaffolds', 3 => 'C:\dev\prj2\sites\cake\libs\view\pages', + 4 => 'C:\dev\prj2\sites\cake\libs\view\layouts', 5 => 'C:\dev\prj2\sites\cake\libs\view\layouts\xml', + 6 => 'C:\dev\prj2\sites\cake\libs\view\layouts\rss', 7 => 'C:\dev\prj2\sites\cake\libs\view\layouts\js', + 8 => 'C:\dev\prj2\sites\cake\libs\view\layouts\email', 9 => 'C:\dev\prj2\sites\cake\libs\view\layouts\email\text', + 10 => 'C:\dev\prj2\sites\cake\libs\view\layouts\email\html', 11 => 'C:\dev\prj2\sites\cake\libs\view\helpers', + 12 => 'C:\dev\prj2\sites\cake\libs\view\errors', 13 => 'C:\dev\prj2\sites\cake\libs\view\elements', + 14 => 'C:\dev\prj2\sites\cake\libs\view\elements\email', 15 => 'C:\dev\prj2\sites\cake\libs\view\elements\email\text', + 16 => 'C:\dev\prj2\sites\cake\libs\view\elements\email\html', 17 => 'C:\dev\prj2\sites\cake\libs\model', + 18 => 'C:\dev\prj2\sites\cake\libs\model\datasources', 19 => 'C:\dev\prj2\sites\cake\libs\model\datasources\dbo', + 20 => 'C:\dev\prj2\sites\cake\libs\model\behaviors', 21 => 'C:\dev\prj2\sites\cake\libs\controller', + 22 => 'C:\dev\prj2\sites\cake\libs\controller\components', 23 => 'C:\dev\prj2\sites\cake\libs\cache'), + 'C:\dev\prj2\sites\main_site\vendors' => array( + 0 => 'C:\dev\prj2\sites\main_site\vendors', 1 => 'C:\dev\prj2\sites\main_site\vendors\shells', + 2 => 'C:\dev\prj2\sites\main_site\vendors\shells\templates', 3 => 'C:\dev\prj2\sites\main_site\vendors\shells\templates\cdc_project', + 4 => 'C:\dev\prj2\sites\main_site\vendors\shells\tasks', 5 => 'C:\dev\prj2\sites\main_site\vendors\js', + 6 => 'C:\dev\prj2\sites\main_site\vendors\css'), + 'C:\dev\prj2\sites\vendors' => array( + 0 => 'C:\dev\prj2\sites\vendors', 1 => 'C:\dev\prj2\sites\vendors\simpletest', + 2 => 'C:\dev\prj2\sites\vendors\simpletest\test', 3 => 'C:\dev\prj2\sites\vendors\simpletest\test\support', + 4 => 'C:\dev\prj2\sites\vendors\simpletest\test\support\collector', 5 => 'C:\dev\prj2\sites\vendors\simpletest\extensions', + 6 => 'C:\dev\prj2\sites\vendors\simpletest\extensions\testdox', 7 => 'C:\dev\prj2\sites\vendors\simpletest\docs', + 8 => 'C:\dev\prj2\sites\vendors\simpletest\docs\fr', 9 => 'C:\dev\prj2\sites\vendors\simpletest\docs\en'), + 'C:\dev\prj2\sites\main_site\views\helpers' => array( + 0 => 'C:\dev\prj2\sites\main_site\views\helpers') + ); + + Cache::write('test_dir_map', $expected, 'windows_test'); + $data = Cache::read('test_dir_map', 'windows_test'); + Cache::delete('test_dir_map', 'windows_test'); + $this->assertEqual($expected, $data); + + Cache::drop('windows_test'); + } + +/** + * testWriteQuotedString method + * + * @access public + * @return void + */ + function testWriteQuotedString() { + Cache::config('default', array('engine' => 'File', 'path' => TMP . 'tests')); + Cache::write('App.doubleQuoteTest', '"this is a quoted string"'); + $this->assertIdentical(Cache::read('App.doubleQuoteTest'), '"this is a quoted string"'); + Cache::write('App.singleQuoteTest', "'this is a quoted string'"); + $this->assertIdentical(Cache::read('App.singleQuoteTest'), "'this is a quoted string'"); + + Cache::config('default', array('isWindows' => true, 'path' => TMP . 'tests')); + $this->assertIdentical(Cache::read('App.doubleQuoteTest'), '"this is a quoted string"'); + Cache::write('App.singleQuoteTest', "'this is a quoted string'"); + $this->assertIdentical(Cache::read('App.singleQuoteTest'), "'this is a quoted string'"); + } + +/** + * check that FileEngine generates an error when a configured Path does not exist. + * + * @return void + */ + function testErrorWhenPathDoesNotExist() { + if ($this->skipIf(is_dir(TMP . 'tests' . DS . 'file_failure'), 'Cannot run test directory exists. %s')) { + return; + } + $this->expectError(); + Cache::config('failure', array( + 'engine' => 'File', + 'path' => TMP . 'tests' . DS . 'file_failure' + )); + + Cache::drop('failure'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/memcache.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/memcache.test.php new file mode 100644 index 000000000..e3553691d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/memcache.test.php @@ -0,0 +1,390 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.cache + * @since CakePHP(tm) v 1.2.0.5434 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!class_exists('Cache')) { + require LIBS . 'cache.php'; +} +App::import('Core', 'cache/Memcache'); + + +class TestMemcacheEngine extends MemcacheEngine { +/** + * public accessor to _parseServerString + * + * @param string $server + * @return array + */ + function parseServerString($server) { + return $this->_parseServerString($server); + } + + function setMemcache(&$memcache) { + $this->__Memcache = $memcache; + } +} + +Mock::generate('Memcache', 'MemcacheMockMemcache'); + +/** + * MemcacheEngineTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.cache + */ +class MemcacheEngineTest extends CakeTestCase { + +/** + * skip method + * + * @access public + * @return void + */ + function skip() { + $skip = true; + if (class_exists('Memcache')) { + $skip = false; + } + $this->skipIf($skip, '%s Memcache is not installed or configured properly.'); + } + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_cacheDisable = Configure::read('Cache.disable'); + Configure::write('Cache.disable', false); + Cache::config('memcache', array( + 'engine' => 'Memcache', + 'prefix' => 'cake_', + 'duration' => 3600 + )); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('Cache.disable', $this->_cacheDisable); + Cache::drop('memcache'); + Cache::config('default'); + } + +/** + * testSettings method + * + * @access public + * @return void + */ + function testSettings() { + $settings = Cache::settings(); + unset($settings['serialize'], $settings['path']); + $expecting = array( + 'prefix' => 'cake_', + 'duration'=> 3600, + 'probability' => 100, + 'servers' => array('127.0.0.1'), + 'compress' => false, + 'engine' => 'Memcache', + 'persistent' => true, + ); + $this->assertEqual($settings, $expecting); + } + +/** + * testSettings method + * + * @access public + * @return void + */ + function testMultipleServers() { + $servers = array('127.0.0.1:11211', '127.0.0.1:11222'); + $available = true; + $Memcache =& new Memcache(); + + foreach($servers as $server) { + list($host, $port) = explode(':', $server); + if (!@$Memcache->connect($host, $port)) { + $available = false; + } + } + + if ($this->skipIf(!$available, '%s Need memcache servers at ' . implode(', ', $servers) . ' to run this test')) { + return; + } + $Memcache =& new MemcacheEngine(); + $Memcache->init(array('engine' => 'Memcache', 'servers' => $servers)); + + $servers = array_keys($Memcache->__Memcache->getExtendedStats()); + $settings = $Memcache->settings(); + $this->assertEqual($servers, $settings['servers']); + Cache::drop('dual_server'); + } + +/** + * testConnect method + * + * @access public + * @return void + */ + function testConnect() { + $Memcache =& new MemcacheEngine(); + $Memcache->init(Cache::settings('memcache')); + $result = $Memcache->connect('127.0.0.1'); + $this->assertTrue($result); + } + +/** + * test connecting to an ipv6 server. + * + * @return void + */ + function testConnectIpv6() { + $Memcache =& new MemcacheEngine(); + $result = $Memcache->init(array( + 'prefix' => 'cake_', + 'duration' => 200, + 'engine' => 'Memcache', + 'servers' => array( + '[::1]:11211' + ) + )); + $this->assertTrue($result); + } + +/** + * test non latin domains. + * + * @return void + */ + function testParseServerStringNonLatin() { + $Memcache =& new TestMemcacheEngine(); + $result = $Memcache->parseServerString('schülervz.net:13211'); + $this->assertEqual($result, array('schülervz.net', '13211')); + + $result = $Memcache->parseServerString('sülül:1111'); + $this->assertEqual($result, array('sülül', '1111')); + } + +/** + * testReadAndWriteCache method + * + * @access public + * @return void + */ + function testReadAndWriteCache() { + Cache::set(array('duration' => 1)); + + $result = Cache::read('test'); + $expecting = ''; + $this->assertEqual($result, $expecting); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('test', $data); + $this->assertTrue($result); + + $result = Cache::read('test'); + $expecting = $data; + $this->assertEqual($result, $expecting); + + Cache::delete('test'); + } + +/** + * testExpiry method + * + * @access public + * @return void + */ + function testExpiry() { + Cache::set(array('duration' => 1)); + + $result = Cache::read('test'); + $this->assertFalse($result); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + + Cache::set(array('duration' => "+1 second")); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + + Cache::config('memcache', array('duration' => '+1 second')); + sleep(2); + + $result = Cache::read('other_test'); + $this->assertFalse($result); + + Cache::config('memcache', array('duration' => '+29 days')); + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('long_expiry_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('long_expiry_test'); + $expecting = $data; + $this->assertEqual($result, $expecting); + + Cache::config('memcache', array('duration' => 3600)); + } + +/** + * testDeleteCache method + * + * @access public + * @return void + */ + function testDeleteCache() { + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('delete_test', $data); + $this->assertTrue($result); + + $result = Cache::delete('delete_test'); + $this->assertTrue($result); + } + +/** + * testDecrement method + * + * @access public + * @return void + */ + function testDecrement() { + $result = Cache::write('test_decrement', 5); + $this->assertTrue($result); + + $result = Cache::decrement('test_decrement'); + $this->assertEqual(4, $result); + + $result = Cache::read('test_decrement'); + $this->assertEqual(4, $result); + + $result = Cache::decrement('test_decrement', 2); + $this->assertEqual(2, $result); + + $result = Cache::read('test_decrement'); + $this->assertEqual(2, $result); + } + +/** + * testIncrement method + * + * @access public + * @return void + */ + function testIncrement() { + $result = Cache::write('test_increment', 5); + $this->assertTrue($result); + + $result = Cache::increment('test_increment'); + $this->assertEqual(6, $result); + + $result = Cache::read('test_increment'); + $this->assertEqual(6, $result); + + $result = Cache::increment('test_increment', 2); + $this->assertEqual(8, $result); + + $result = Cache::read('test_increment'); + $this->assertEqual(8, $result); + } + +/** + * test that configurations don't conflict, when a file engine is declared after a memcache one. + * + * @return void + */ + function testConfigurationConflict() { + Cache::config('long_memcache', array( + 'engine' => 'Memcache', + 'duration'=> '+2 seconds', + 'servers' => array('127.0.0.1:11211'), + )); + Cache::config('short_memcache', array( + 'engine' => 'Memcache', + 'duration'=> '+1 seconds', + 'servers' => array('127.0.0.1:11211'), + )); + Cache::config('some_file', array('engine' => 'File')); + + $this->assertTrue(Cache::write('duration_test', 'yay', 'long_memcache')); + $this->assertTrue(Cache::write('short_duration_test', 'boo', 'short_memcache')); + + $this->assertEqual(Cache::read('duration_test', 'long_memcache'), 'yay', 'Value was not read %s'); + $this->assertEqual(Cache::read('short_duration_test', 'short_memcache'), 'boo', 'Value was not read %s'); + + sleep(1); + $this->assertEqual(Cache::read('duration_test', 'long_memcache'), 'yay', 'Value was not read %s'); + + sleep(2); + $this->assertFalse(Cache::read('short_duration_test', 'short_memcache'), 'Cache was not invalidated %s'); + $this->assertFalse(Cache::read('duration_test', 'long_memcache'), 'Value did not expire %s'); + + Cache::delete('duration_test', 'long_memcache'); + Cache::delete('short_duration_test', 'short_memcache'); + } + +/** + * test that a 0 duration can succesfully write. + * + * @return void + */ + function testZeroDuration() { + Cache::config('memcache', array('duration' => 0)); + $result = Cache::write('test_key', 'written!', 'memcache'); + + $this->assertTrue($result, 'Could not write with duration 0'); + $result = Cache::read('test_key', 'memcache'); + $this->assertEqual($result, 'written!'); + } + +/** + * test that durations greater than 30 days never expire + * + * @return void + */ + function testLongDurationEqualToZero() { + $memcache =& new TestMemcacheEngine(); + $memcache->settings['compress'] = false; + + $mock = new MemcacheMockMemcache(); + $memcache->setMemcache($mock); + $mock->expectAt(0, 'set', array('key', 'value', false, 0)); + + $value = 'value'; + $memcache->write('key', $value, 50 * DAY); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/xcache.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/xcache.test.php new file mode 100644 index 000000000..b104c9564 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cache/xcache.test.php @@ -0,0 +1,222 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.cache + * @since CakePHP(tm) v 1.2.0.5434 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!class_exists('Cache')) { + require LIBS . 'cache.php'; +} + +/** + * XcacheEngineTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.cache + */ +class XcacheEngineTest extends UnitTestCase { + +/** + * skip method + * + * @access public + * @return void + */ + function skip() { + $skip = true; + if (function_exists('xcache_set')) { + $skip = false; + } + $this->skipIf($skip, '%s Xcache is not installed or configured properly'); + } + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_cacheDisable = Configure::read('Cache.disable'); + Configure::write('Cache.disable', false); + Cache::config('xcache', array('engine' => 'Xcache', 'prefix' => 'cake_')); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('Cache.disable', $this->_cacheDisable); + Cache::config('default'); + } + +/** + * testSettings method + * + * @access public + * @return void + */ + function testSettings() { + $settings = Cache::settings(); + $expecting = array( + 'prefix' => 'cake_', + 'duration'=> 3600, + 'probability' => 100, + 'engine' => 'Xcache', + ); + $this->assertTrue(isset($settings['PHP_AUTH_USER'])); + $this->assertTrue(isset($settings['PHP_AUTH_PW'])); + + unset($settings['PHP_AUTH_USER'], $settings['PHP_AUTH_PW']); + $this->assertEqual($settings, $expecting); + } + +/** + * testReadAndWriteCache method + * + * @access public + * @return void + */ + function testReadAndWriteCache() { + Cache::set(array('duration' => 1)); + + $result = Cache::read('test'); + $expecting = ''; + $this->assertEqual($result, $expecting); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('test', $data); + $this->assertTrue($result); + + $result = Cache::read('test'); + $expecting = $data; + $this->assertEqual($result, $expecting); + + Cache::delete('test'); + } + +/** + * testExpiry method + * + * @access public + * @return void + */ + function testExpiry() { + Cache::set(array('duration' => 1)); + $result = Cache::read('test'); + $this->assertFalse($result); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + + Cache::set(array('duration' => "+1 second")); + + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('other_test', $data); + $this->assertTrue($result); + + sleep(2); + $result = Cache::read('other_test'); + $this->assertFalse($result); + } + +/** + * testDeleteCache method + * + * @access public + * @return void + */ + function testDeleteCache() { + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('delete_test', $data); + $this->assertTrue($result); + + $result = Cache::delete('delete_test'); + $this->assertTrue($result); + } + +/** + * testClearCache method + * + * @access public + * @return void + */ + function testClearCache() { + $data = 'this is a test of the emergency broadcasting system'; + $result = Cache::write('clear_test_1', $data); + $this->assertTrue($result); + + $result = Cache::write('clear_test_2', $data); + $this->assertTrue($result); + + $result = Cache::clear(); + $this->assertTrue($result); + } + +/** + * testDecrement method + * + * @access public + * @return void + */ + function testDecrement() { + $result = Cache::write('test_decrement', 5); + $this->assertTrue($result); + + $result = Cache::decrement('test_decrement'); + $this->assertEqual(4, $result); + + $result = Cache::read('test_decrement'); + $this->assertEqual(4, $result); + + $result = Cache::decrement('test_decrement', 2); + $this->assertEqual(2, $result); + + $result = Cache::read('test_decrement'); + $this->assertEqual(2, $result); + } + +/** + * testIncrement method + * + * @access public + * @return void + */ + function testIncrement() { + $result = Cache::write('test_increment', 5); + $this->assertTrue($result); + + $result = Cache::increment('test_increment'); + $this->assertEqual(6, $result); + + $result = Cache::read('test_increment'); + $this->assertEqual(6, $result); + + $result = Cache::increment('test_increment', 2); + $this->assertEqual(8, $result); + + $result = Cache::read('test_increment'); + $this->assertEqual(8, $result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_log.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_log.test.php new file mode 100644 index 000000000..7394eddf5 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_log.test.php @@ -0,0 +1,183 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Log'); +App::import('Core', 'log/FileLog'); + +/** + * CakeLogTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class CakeLogTest extends CakeTestCase { + +/** + * Start test callback, clears all streams enabled. + * + * @return void + */ + function startTest() { + $streams = CakeLog::configured(); + foreach ($streams as $stream) { + CakeLog::drop($stream); + } + } + +/** + * test importing loggers from app/libs and plugins. + * + * @return void + */ + function testImportingLoggers() { + App::build(array( + 'libs' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'libs' . DS), + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + ), true); + + $result = CakeLog::config('libtest', array( + 'engine' => 'TestAppLog' + )); + $this->assertTrue($result); + $this->assertEqual(CakeLog::configured(), array('libtest')); + + $result = CakeLog::config('plugintest', array( + 'engine' => 'TestPlugin.TestPluginLog' + )); + $this->assertTrue($result); + $this->assertEqual(CakeLog::configured(), array('libtest', 'plugintest')); + + App::build(); + } + +/** + * test all the errors from failed logger imports + * + * @return void + */ + function testImportingLoggerFailure() { + $this->expectError('Missing logger classname'); + CakeLog::config('fail', array()); + + $this->expectError('Could not load logger class born to fail'); + CakeLog::config('fail', array('engine' => 'born to fail')); + + $this->expectError('logger class stdClass does not implement a write method.'); + CakeLog::config('fail', array('engine' => 'stdClass')); + } + +/** + * Test that CakeLog autoconfigures itself to use a FileLogger with the LOGS dir. + * When no streams are there. + * + * @return void + */ + function testAutoConfig() { + @unlink(LOGS . 'error.log'); + CakeLog::write(LOG_WARNING, 'Test warning'); + $this->assertTrue(file_exists(LOGS . 'error.log')); + + $result = CakeLog::configured(); + $this->assertEqual($result, array('default')); + unlink(LOGS . 'error.log'); + } + +/** + * test configuring log streams + * + * @return void + */ + function testConfig() { + CakeLog::config('file', array( + 'engine' => 'FileLog', + 'path' => LOGS + )); + $result = CakeLog::configured(); + $this->assertEqual($result, array('file')); + + @unlink(LOGS . 'error.log'); + CakeLog::write(LOG_WARNING, 'Test warning'); + $this->assertTrue(file_exists(LOGS . 'error.log')); + + $result = file_get_contents(LOGS . 'error.log'); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning/', $result); + unlink(LOGS . 'error.log'); + } + +/** + * explict tests for drop() + * + * @return void + **/ + function testDrop() { + CakeLog::config('file', array( + 'engine' => 'FileLog', + 'path' => LOGS + )); + $result = CakeLog::configured(); + $this->assertEqual($result, array('file')); + + CakeLog::drop('file'); + $result = CakeLog::configured(); + $this->assertEqual($result, array()); + } + +/** + * testLogFileWriting method + * + * @access public + * @return void + */ + function testLogFileWriting() { + @unlink(LOGS . 'error.log'); + $result = CakeLog::write(LOG_WARNING, 'Test warning'); + $this->assertTrue($result); + $this->assertTrue(file_exists(LOGS . 'error.log')); + unlink(LOGS . 'error.log'); + + CakeLog::write(LOG_WARNING, 'Test warning 1'); + CakeLog::write(LOG_WARNING, 'Test warning 2'); + $result = file_get_contents(LOGS . 'error.log'); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning 1/', $result); + $this->assertPattern('/2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning 2$/', $result); + unlink(LOGS . 'error.log'); + } + +/** + * Test logging with the error handler. + * + * @return void + */ + function testLoggingWithErrorHandling() { + @unlink(LOGS . 'debug.log'); + Configure::write('log', E_ALL & ~E_DEPRECATED); + Configure::write('debug', 0); + + set_error_handler(array('CakeLog', 'handleError')); + $out .= ''; + + $result = file(LOGS . 'debug.log'); + $this->assertEqual(count($result), 1); + $this->assertPattern( + '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} Notice: Notice \(8\): Undefined variable:\s+out in \[.+ line \d+\]$/', + $result[0] + ); + @unlink(LOGS . 'debug.log'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_session.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_session.test.php new file mode 100644 index 000000000..b6bc12248 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_session.test.php @@ -0,0 +1,508 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!class_exists('CakeSession')) { + App::import('Core', 'CakeSession'); +} + +/** + * CakeSessionTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class CakeSessionTest extends CakeTestCase { + +/** + * Fixtures used in the SessionTest + * + * @var array + * @access public + */ + var $fixtures = array('core.session'); + +/** + * startCase method + * + * @access public + * @return void + */ + function startCase() { + // Make sure garbage colector will be called + $this->__gc_divisor = ini_get('session.gc_divisor'); + ini_set('session.gc_divisor', '1'); + } + +/** + * endCase method + * + * @access public + * @return void + */ + function endCase() { + // Revert to the default setting + ini_set('session.gc_divisor', $this->__gc_divisor); + } + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->Session =& new CakeSession(); + $this->Session->start(); + $this->Session->_checkValid(); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + unset($_SESSION); + session_destroy(); + } + +/** + * testSessionPath + * + * @access public + * @return void + */ + function testSessionPath() { + $Session = new CakeSession('/index.php'); + $this->assertEqual('/', $Session->path); + + $Session = new CakeSession('/sub_dir/index.php'); + $this->assertEqual('/sub_dir/', $Session->path); + + $Session = new CakeSession(''); + $this->assertEqual('/', $Session->path, 'Session path is empty, with "" as $base needs to be / %s'); + } + +/** + * testCheck method + * + * @access public + * @return void + */ + function testCheck() { + $this->Session->write('SessionTestCase', 'value'); + $this->assertTrue($this->Session->check('SessionTestCase')); + + $this->assertFalse($this->Session->check('NotExistingSessionTestCase'), false); + } + +/** + * testSimpleRead method + * + * @access public + * @return void + */ + function testSimpleRead() { + $this->Session->write('testing', '1,2,3'); + $result = $this->Session->read('testing'); + $this->assertEqual($result, '1,2,3'); + + $this->Session->write('testing', array('1' => 'one', '2' => 'two','3' => 'three')); + $result = $this->Session->read('testing.1'); + $this->assertEqual($result, 'one'); + + $result = $this->Session->read('testing'); + $this->assertEqual($result, array('1' => 'one', '2' => 'two', '3' => 'three')); + + $result = $this->Session->read(); + $this->assertTrue(isset($result['testing'])); + $this->assertTrue(isset($result['Config'])); + $this->assertTrue(isset($result['Config']['userAgent'])); + + $this->Session->write('This.is.a.deep.array.my.friend', 'value'); + $result = $this->Session->read('This.is.a.deep.array.my.friend'); + $this->assertEqual('value', $result); + } + +/** + * testId method + * + * @access public + * @return void + */ + function testId() { + $expected = session_id(); + $result = $this->Session->id(); + $this->assertEqual($result, $expected); + + $this->Session->id('MySessionId'); + $result = $this->Session->id(); + $this->assertEqual($result, 'MySessionId'); + } + +/** + * testStarted method + * + * @access public + * @return void + */ + function testStarted() { + $this->assertTrue($this->Session->started()); + + unset($_SESSION); + $_SESSION = null; + $this->assertFalse($this->Session->started()); + $this->assertTrue($this->Session->start()); + + $session = new CakeSession(null, false); + $this->assertTrue($session->started()); + unset($session); + } + +/** + * testError method + * + * @access public + * @return void + */ + function testError() { + $this->Session->read('Does.not.exist'); + $result = $this->Session->error(); + $this->assertEqual($result, "Does.not.exist doesn't exist"); + + $this->Session->delete('Failing.delete'); + $result = $this->Session->error(); + $this->assertEqual($result, "Failing.delete doesn't exist"); + } + +/** + * testDel method + * + * @access public + * @return void + */ + function testDelete() { + $this->assertTrue($this->Session->write('Delete.me', 'Clearing out')); + $this->assertTrue($this->Session->delete('Delete.me')); + $this->assertFalse($this->Session->check('Delete.me')); + $this->assertTrue($this->Session->check('Delete')); + + $this->assertTrue($this->Session->write('Clearing.sale', 'everything must go')); + $this->assertTrue($this->Session->delete('Clearing')); + $this->assertFalse($this->Session->check('Clearing.sale')); + $this->assertFalse($this->Session->check('Clearing')); + } + +/** + * testWatchVar method + * + * @access public + * @return void + */ + function testWatchVar() { + $this->assertFalse($this->Session->watch(null)); + + $this->Session->write('Watching', "I'm watching you"); + $this->Session->watch('Watching'); + $this->expectError('Writing session key {Watching}: "They found us!"'); + $this->Session->write('Watching', 'They found us!'); + + $this->expectError('Deleting session key {Watching}'); + $this->Session->delete('Watching'); + + $this->assertFalse($this->Session->watch('Invalid.key')); + } + +/** + * testIgnore method + * + * @access public + * @return void + */ + function testIgnore() { + $this->Session->write('Watching', "I'm watching you"); + $this->Session->watch('Watching'); + $this->Session->ignore('Watching'); + $this->assertTrue($this->Session->write('Watching', 'They found us!')); + } + +/** + * testDestroy method + * + * @access public + * @return void + */ + function testDestroy() { + $this->Session->write('bulletProof', 'invicible'); + $id = $this->Session->id(); + $this->Session->destroy(); + $this->assertFalse($this->Session->check('bulletProof')); + $this->assertNotEqual($id, $this->Session->id()); + $this->assertTrue($this->Session->started()); + + $this->Session->cookieLifeTime = 'test'; + $this->Session->destroy(); + $this->assertNotEqual('test', $this->Session->cookieLifeTime); + } + +/** + * testCheckingSavedEmpty method + * + * @access public + * @return void + */ + function testCheckingSavedEmpty() { + $this->assertTrue($this->Session->write('SessionTestCase', 0)); + $this->assertTrue($this->Session->check('SessionTestCase')); + + $this->assertTrue($this->Session->write('SessionTestCase', '0')); + $this->assertTrue($this->Session->check('SessionTestCase')); + + $this->assertTrue($this->Session->write('SessionTestCase', false)); + $this->assertTrue($this->Session->check('SessionTestCase')); + + $this->assertTrue($this->Session->write('SessionTestCase', null)); + $this->assertFalse($this->Session->check('SessionTestCase')); + } + +/** + * testCheckKeyWithSpaces method + * + * @access public + * @return void + */ + function testCheckKeyWithSpaces() { + $this->assertTrue($this->Session->write('Session Test', "test")); + $this->assertEqual($this->Session->check('Session Test'), 'test'); + $this->Session->delete('Session Test'); + + $this->assertTrue($this->Session->write('Session Test.Test Case', "test")); + $this->assertTrue($this->Session->check('Session Test.Test Case')); + } + +/** + * test key exploitation + * + * @return void + */ + function testKeyExploit() { + $key = "a'] = 1; phpinfo(); \$_SESSION['a"; + $result = $this->Session->write($key, 'haxored'); + $this->assertTrue($result); + + $result = $this->Session->read($key); + $this->assertEqual($result, 'haxored'); + } + +/** + * testReadingSavedEmpty method + * + * @access public + * @return void + */ + function testReadingSavedEmpty() { + $this->Session->write('SessionTestCase', 0); + $this->assertEqual($this->Session->read('SessionTestCase'), 0); + + $this->Session->write('SessionTestCase', '0'); + $this->assertEqual($this->Session->read('SessionTestCase'), '0'); + $this->assertFalse($this->Session->read('SessionTestCase') === 0); + + $this->Session->write('SessionTestCase', false); + $this->assertFalse($this->Session->read('SessionTestCase')); + + $this->Session->write('SessionTestCase', null); + $this->assertEqual($this->Session->read('SessionTestCase'), null); + } + +/** + * testCheckUserAgentFalse method + * + * @access public + * @return void + */ + function testCheckUserAgentFalse() { + Configure::write('Session.checkAgent', false); + $this->Session->_userAgent = md5('http://randomdomainname.com' . Configure::read('Security.salt')); + $this->assertTrue($this->Session->valid()); + } + +/** + * testCheckUserAgentTrue method + * + * @access public + * @return void + */ + function testCheckUserAgentTrue() { + Configure::write('Session.checkAgent', true); + $this->Session->_userAgent = md5('http://randomdomainname.com' . Configure::read('Security.salt')); + $this->assertFalse($this->Session->valid()); + } + +/** + * testReadAndWriteWithDatabaseStorage method + * + * @access public + * @return void + */ + function testReadAndWriteWithCakeStorage() { + unset($_SESSION); + session_destroy(); + ini_set('session.save_handler', 'files'); + Configure::write('Session.save', 'cake'); + $this->setUp(); + + $this->Session->write('SessionTestCase', 0); + $this->assertEqual($this->Session->read('SessionTestCase'), 0); + + $this->Session->write('SessionTestCase', '0'); + $this->assertEqual($this->Session->read('SessionTestCase'), '0'); + $this->assertFalse($this->Session->read('SessionTestCase') === 0); + + $this->Session->write('SessionTestCase', false); + $this->assertFalse($this->Session->read('SessionTestCase')); + + $this->Session->write('SessionTestCase', null); + $this->assertEqual($this->Session->read('SessionTestCase'), null); + + $this->Session->write('SessionTestCase', 'This is a Test'); + $this->assertEqual($this->Session->read('SessionTestCase'), 'This is a Test'); + + $this->Session->write('SessionTestCase', 'This is a Test'); + $this->Session->write('SessionTestCase', 'This was updated'); + $this->assertEqual($this->Session->read('SessionTestCase'), 'This was updated'); + + $this->Session->destroy(); + $this->assertFalse($this->Session->read('SessionTestCase')); + } + +/** + * testReadAndWriteWithDatabaseStorage method + * + * @access public + * @return void + */ + function testReadAndWriteWithCacheStorage() { + unset($_SESSION); + session_destroy(); + ini_set('session.save_handler', 'files'); + Configure::write('Session.save', 'cache'); + $this->setUp(); + + $this->Session->write('SessionTestCase', 0); + $this->assertEqual($this->Session->read('SessionTestCase'), 0); + + $this->Session->write('SessionTestCase', '0'); + $this->assertEqual($this->Session->read('SessionTestCase'), '0'); + $this->assertFalse($this->Session->read('SessionTestCase') === 0); + + $this->Session->write('SessionTestCase', false); + $this->assertFalse($this->Session->read('SessionTestCase')); + + $this->Session->write('SessionTestCase', null); + $this->assertEqual($this->Session->read('SessionTestCase'), null); + + $this->Session->write('SessionTestCase', 'This is a Test'); + $this->assertEqual($this->Session->read('SessionTestCase'), 'This is a Test'); + + $this->Session->write('SessionTestCase', 'This is a Test'); + $this->Session->write('SessionTestCase', 'This was updated'); + $this->assertEqual($this->Session->read('SessionTestCase'), 'This was updated'); + + $this->Session->destroy(); + $this->assertFalse($this->Session->read('SessionTestCase')); + } + +/** + * testReadAndWriteWithDatabaseStorage method + * + * @access public + * @return void + */ + function testReadAndWriteWithDatabaseStorage() { + unset($_SESSION); + session_destroy(); + Configure::write('Session.table', 'sessions'); + Configure::write('Session.model', 'Session'); + Configure::write('Session.database', 'test_suite'); + Configure::write('Session.save', 'database'); + $this->setUp(); + + $this->Session->write('SessionTestCase', 0); + $this->assertEqual($this->Session->read('SessionTestCase'), 0); + + $this->Session->write('SessionTestCase', '0'); + $this->assertEqual($this->Session->read('SessionTestCase'), '0'); + $this->assertFalse($this->Session->read('SessionTestCase') === 0); + + $this->Session->write('SessionTestCase', false); + $this->assertFalse($this->Session->read('SessionTestCase')); + + $this->Session->write('SessionTestCase', null); + $this->assertEqual($this->Session->read('SessionTestCase'), null); + + $this->Session->write('SessionTestCase', 'This is a Test'); + $this->assertEqual($this->Session->read('SessionTestCase'), 'This is a Test'); + + $this->Session->write('SessionTestCase', 'Some additional data'); + $this->assertEqual($this->Session->read('SessionTestCase'), 'Some additional data'); + + $this->Session->destroy(); + $this->assertFalse($this->Session->read('SessionTestCase')); + session_write_close(); + + unset($_SESSION); + ini_set('session.save_handler', 'files'); + Configure::write('Session.save', 'php'); + $this->setUp(); + } + +/** + * testReadAndWriteWithDatabaseStorage method + * + * @access public + * @return void + */ + function testDatabaseStorageEmptySessionId() { + unset($_SESSION); + session_destroy(); + Configure::write('Session.table', 'sessions'); + Configure::write('Session.model', 'Session'); + Configure::write('Session.database', 'test_suite'); + Configure::write('Session.save', 'database'); + $this->setUp(); + $id = $this->Session->id(); + + $this->Session->id = ''; + session_id(''); + + $this->Session->write('SessionTestCase', 'This is a Test'); + $this->assertEqual($this->Session->read('SessionTestCase'), 'This is a Test'); + + session_write_close(); + + unset($_SESSION); + ini_set('session.save_handler', 'files'); + Configure::write('Session.save', 'php'); + session_id($id); + $this->setUp(); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_socket.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_socket.test.php new file mode 100644 index 000000000..152c9e0fd --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_socket.test.php @@ -0,0 +1,192 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'CakeSocket'); + +/** + * SocketTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class CakeSocketTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->Socket = new CakeSocket(); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Socket); + } + +/** + * testConstruct method + * + * @access public + * @return void + */ + function testConstruct() { + $this->Socket->__construct(); + $baseConfig = $this->Socket->_baseConfig; + $this->assertIdentical($baseConfig, array( + 'persistent' => false, + 'host' => 'localhost', + 'protocol' => 'tcp', + 'port' => 80, + 'timeout' => 30 + )); + + $this->Socket->reset(); + $this->Socket->__construct(array('host' => 'foo-bar')); + $baseConfig['host'] = 'foo-bar'; + $baseConfig['protocol'] = getprotobyname($baseConfig['protocol']); + $this->assertIdentical($this->Socket->config, $baseConfig); + + $this->Socket = new CakeSocket(array('host' => 'www.cakephp.org', 'port' => 23, 'protocol' => 'udp')); + $baseConfig = $this->Socket->_baseConfig; + + $baseConfig['host'] = 'www.cakephp.org'; + $baseConfig['port'] = 23; + $baseConfig['protocol'] = 17; + + $this->assertIdentical($this->Socket->config, $baseConfig); + } + +/** + * testSocketConnection method + * + * @access public + * @return void + */ + function testSocketConnection() { + $this->assertFalse($this->Socket->connected); + $this->Socket->disconnect(); + $this->assertFalse($this->Socket->connected); + $this->Socket->connect(); + $this->assertTrue($this->Socket->connected); + $this->Socket->connect(); + $this->assertTrue($this->Socket->connected); + + $this->Socket->disconnect(); + $config = array('persistent' => true); + $this->Socket = new CakeSocket($config); + $this->Socket->connect(); + $this->assertTrue($this->Socket->connected); + } + +/** + * testSocketHost method + * + * @access public + * @return void + */ + function testSocketHost() { + $this->Socket = new CakeSocket(); + $this->Socket->connect(); + $this->assertEqual($this->Socket->address(), '127.0.0.1'); + $this->assertEqual(gethostbyaddr('127.0.0.1'), $this->Socket->host()); + $this->assertEqual($this->Socket->lastError(), null); + $this->assertTrue(in_array('127.0.0.1', $this->Socket->addresses())); + + $this->Socket = new CakeSocket(array('host' => '127.0.0.1')); + $this->Socket->connect(); + $this->assertEqual($this->Socket->address(), '127.0.0.1'); + $this->assertEqual(gethostbyaddr('127.0.0.1'), $this->Socket->host()); + $this->assertEqual($this->Socket->lastError(), null); + $this->assertTrue(in_array('127.0.0.1', $this->Socket->addresses())); + } + +/** + * testSocketWriting method + * + * @access public + * @return void + */ + function testSocketWriting() { + $request = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"; + $this->assertTrue($this->Socket->write($request)); + } + +/** + * testSocketReading method + * + * @access public + * @return void + */ + function testSocketReading() { + $this->Socket = new CakeSocket(array('timeout' => 5)); + $this->Socket->connect(); + $this->assertEqual($this->Socket->read(26), null); + + $config = array('host' => 'www.cakephp.org', 'timeout' => 1); + $this->Socket = new CakeSocket($config); + $this->assertTrue($this->Socket->connect()); + $this->assertFalse($this->Socket->read(1024 * 1024)); + $this->assertEqual($this->Socket->lastError(), '2: ' . __('Connection timed out', true)); + + $config = array('host' => 'www.cakephp.org', 'timeout' => 30); + $this->Socket = new CakeSocket($config); + $this->assertTrue($this->Socket->connect()); + $this->assertEqual($this->Socket->read(26), null); + $this->assertEqual($this->Socket->lastError(), null); + } + +/** + * testLastError method + * + * @access public + * @return void + */ + function testLastError() { + $this->Socket = new CakeSocket(); + $this->Socket->setLastError(4, 'some error here'); + $this->assertEqual($this->Socket->lastError(), '4: some error here'); + } + +/** + * testReset method + * + * @access public + * @return void + */ + function testReset() { + $config = array( + 'persistent' => true, + 'host' => '127.0.0.1', + 'protocol' => 'udp', + 'port' => 80, + 'timeout' => 20 + ); + $anotherSocket = new CakeSocket($config); + $anotherSocket->reset(); + $this->assertEqual(array(), $anotherSocket->config); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_test_case.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_test_case.test.php new file mode 100644 index 000000000..e9f885fab --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_test_case.test.php @@ -0,0 +1,502 @@ +_reporter = &$reporter; + } + +/** + * testDummy method + * + * @return void + * @access public + */ + function testDummy() { + } +} + +/** + * CakeTestCaseTest + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class CakeTestCaseTest extends CakeTestCase { + +/** + * setUp + * + * @access public + * @return void + */ + function setUp() { + $this->_debug = Configure::read('debug'); + $this->Case =& new SubjectCakeTestCase(); + $reporter =& new MockCakeHtmlReporter(); + $this->Case->setReporter($reporter); + $this->Reporter = $reporter; + } + +/** + * tearDown + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('debug', $this->_debug); + unset($this->Case); + unset($this->Reporter); + } + +/** + * endTest + * + * @access public + * @return void + */ + function endTest() { + App::build(); + } + +/** + * testAssertGoodTags + * + * @access public + * @return void + */ + function testAssertGoodTags() { + $this->Reporter->expectAtLeastOnce('paintPass'); + $this->Reporter->expectNever('paintFail'); + + $input = '

Text

'; + $pattern = array( + 'assertTrue($this->Case->assertTags($input, $pattern)); + + $input = 'My link'; + $pattern = array( + 'a' => array('href' => '/test.html', 'class' => 'active'), + 'My link', + '/a' + ); + $this->assertTrue($this->Case->assertTags($input, $pattern)); + + $pattern = array( + 'a' => array('class' => 'active', 'href' => '/test.html'), + 'My link', + '/a' + ); + $this->assertTrue($this->Case->assertTags($input, $pattern), 'Attributes in wrong order. %s'); + + $input = "\tMy link"; + $pattern = array( + 'a' => array('id' => 'primary', 'href' => '/test.html', 'class' => 'active'), + 'assertTrue($this->Case->assertTags($input, $pattern), 'Whitespace consumption %s'); + + $input = '

My link

'; + $pattern = array( + 'p' => array('class' => 'info'), + 'a' => array('class' => 'active', 'href' => '/test.html' ), + 'strong' => array('onClick' => 'alert(\'hey\');'), + 'My link', + '/strong', + '/a', + '/p' + ); + $this->assertTrue($this->Case->assertTags($input, $pattern)); + } + +/** + * test that assertTags knows how to handle correct quoting. + * + * @return void + */ + function testAssertTagsQuotes() { + $input = 'My link'; + $pattern = array( + 'a' => array('href' => '/test.html', 'class' => 'active'), + 'My link', + '/a' + ); + $this->assertTrue($this->Case->assertTags($input, $pattern), 'Double quoted attributes %s'); + + $input = "My link"; + $pattern = array( + 'a' => array('href' => '/test.html', 'class' => 'active'), + 'My link', + '/a' + ); + $this->assertTrue($this->Case->assertTags($input, $pattern), 'Single quoted attributes %s'); + + $input = "My link"; + $pattern = array( + 'a' => array('href' => 'preg:/.*\.html/', 'class' => 'active'), + 'My link', + '/a' + ); + $this->assertTrue($this->Case->assertTags($input, $pattern), 'Single quoted attributes %s'); + } + +/** + * testNumericValuesInExpectationForAssertTags + * + * @access public + * @return void + */ + function testNumericValuesInExpectationForAssertTags() { + $value = 220985; + + $input = '

' . $value . '

'; + $pattern = array( + 'assertTrue($this->Case->assertTags($input, $pattern)); + + $input = '

' . $value . '

' . $value . '

'; + $pattern = array( + 'assertTrue($this->Case->assertTags($input, $pattern)); + + $input = '

' . $value . '

' . $value . '

'; + $pattern = array( + ' array('id' => $value), + 'assertTrue($this->Case->assertTags($input, $pattern)); + } + + /** + * testBadAssertTags + * + * @access public + * @return void + */ + function testBadAssertTags() { + $this->Reporter->expectAtLeastOnce('paintFail'); + $this->Reporter->expectNever('paintPass'); + + $input = 'My link'; + $pattern = array( + 'a' => array('hRef' => '/test.html', 'clAss' => 'active'), + 'My link', + '/a' + ); + $this->assertFalse($this->Case->assertTags($input, $pattern)); + + $input = 'My link'; + $pattern = array( + ' array('href' => '/test.html', 'class' => 'active'), + 'My link', + '/a' + ); + $this->assertFalse($this->Case->assertTags($input, $pattern)); + } + +/** + * testBefore + * + * @access public + * @return void + */ + function testBefore() { + $this->Case->before('testDummy'); + $this->assertFalse(isset($this->Case->db)); + + $this->Case->fixtures = array('core.post'); + $this->Case->before('start'); + $this->assertTrue(isset($this->Case->db)); + $this->assertTrue(isset($this->Case->_fixtures['core.post'])); + $this->assertTrue(is_a($this->Case->_fixtures['core.post'], 'CakeTestFixture')); + $this->assertEqual($this->Case->_fixtureClassMap['Post'], 'core.post'); + } + +/** + * testAfter + * + * @access public + * @return void + */ + function testAfter() { + $this->Case->after('testDummy'); + $this->assertFalse($this->Case->__truncated); + + $this->Case->fixtures = array('core.post'); + $this->Case->before('start'); + $this->Case->start(); + $this->Case->after('testDummy'); + $this->assertTrue($this->Case->__truncated); + } + +/** + * testLoadFixtures + * + * @access public + * @return void + */ + function testLoadFixtures() { + $this->Case->fixtures = array('core.post'); + $this->Case->autoFixtures = false; + $this->Case->before('start'); + $this->expectError(); + $this->Case->loadFixtures('Wrong!'); + $this->Case->end(); + } + +/** + * testGetTests Method + * + * @return void + * @access public + */ + function testGetTests() { + $result = $this->Case->getTests(); + $this->assertEqual(array_slice($result, 0, 2), array('start', 'startCase')); + $this->assertEqual(array_slice($result, -2), array('endCase', 'end')); + } + +/** + * TestTestAction + * + * @access public + * @return void + */ + function testTestAction() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'models' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS), + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS), + 'controllers' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'controllers' . DS) + ), true); + + $result = $this->Case->testAction('/tests_apps/index', array('return' => 'view')); + $this->assertPattern('/^\s*This is the TestsAppsController index view\s*$/i', $result); + + $result = $this->Case->testAction('/tests_apps/index', array('return' => 'contents')); + $this->assertPattern('/\bThis is the TestsAppsController index view\b/i', $result); + $this->assertPattern('/assertPattern('/<\/html>/', $result); + + $result = $this->Case->testAction('/tests_apps/some_method', array('return' => 'result')); + $this->assertEqual($result, 5); + + $result = $this->Case->testAction('/tests_apps/set_action', array('return' => 'vars')); + $this->assertEqual($result, array('var' => 'string')); + + $db =& ConnectionManager::getDataSource('test_suite'); + $fixture =& new PostFixture(); + $fixture->create($db); + + $result = $this->Case->testAction('/tests_apps_posts/add', array('return' => 'vars')); + $this->assertTrue(array_key_exists('posts', $result)); + $this->assertEqual(count($result['posts']), 1); + + $result = $this->Case->testAction('/tests_apps_posts/url_var/var1:value1/var2:val2', array( + 'return' => 'vars', + 'method' => 'get', + )); + $this->assertTrue(isset($result['params']['url']['url'])); + $this->assertEqual(array_keys($result['params']['named']), array('var1', 'var2')); + + $result = $this->Case->testAction('/tests_apps_posts/url_var/gogo/val2', array( + 'return' => 'vars', + 'method' => 'get', + )); + $this->assertEqual($result['params']['pass'], array('gogo', 'val2')); + + + $result = $this->Case->testAction('/tests_apps_posts/url_var', array( + 'return' => 'vars', + 'method' => 'get', + 'data' => array( + 'red' => 'health', + 'blue' => 'mana' + ) + )); + $this->assertTrue(isset($result['params']['url']['red'])); + $this->assertTrue(isset($result['params']['url']['blue'])); + $this->assertTrue(isset($result['params']['url']['url'])); + + $result = $this->Case->testAction('/tests_apps_posts/post_var', array( + 'return' => 'vars', + 'method' => 'post', + 'data' => array( + 'name' => 'is jonas', + 'pork' => 'and beans', + ) + )); + $this->assertEqual(array_keys($result['data']), array('name', 'pork')); + $fixture->drop($db); + + $db =& ConnectionManager::getDataSource('test_suite'); + $_backPrefix = $db->config['prefix']; + $db->config['prefix'] = 'cake_testaction_test_suite_'; + + $config = $db->config; + $config['prefix'] = 'cake_testcase_test_'; + + ConnectionManager::create('cake_test_case', $config); + $db2 =& ConnectionManager::getDataSource('cake_test_case'); + + $fixture =& new PostFixture($db2); + $fixture->create($db2); + $fixture->insert($db2); + + $result = $this->Case->testAction('/tests_apps_posts/fixtured', array( + 'return' => 'vars', + 'fixturize' => true, + 'connection' => 'cake_test_case', + )); + $this->assertTrue(isset($result['posts'])); + $this->assertEqual(count($result['posts']), 3); + $tables = $db2->listSources(); + $this->assertFalse(in_array('cake_testaction_test_suite_posts', $tables)); + + $fixture->drop($db2); + + $db =& ConnectionManager::getDataSource('test_suite'); + + //test that drop tables behaves as exepected with testAction + $db =& ConnectionManager::getDataSource('test_suite'); + $_backPrefix = $db->config['prefix']; + $db->config['prefix'] = 'cake_testaction_test_suite_'; + + $config = $db->config; + $config['prefix'] = 'cake_testcase_test_'; + + ConnectionManager::create('cake_test_case', $config); + $db =& ConnectionManager::getDataSource('cake_test_case'); + $fixture =& new PostFixture($db); + $fixture->create($db); + $fixture->insert($db); + + $this->Case->dropTables = false; + $result = $this->Case->testAction('/tests_apps_posts/fixtured', array( + 'return' => 'vars', + 'fixturize' => true, + 'connection' => 'cake_test_case', + )); + + $tables = $db->listSources(); + $this->assertTrue(in_array('cake_testaction_test_suite_posts', $tables)); + + $fixture->drop($db); + $db =& ConnectionManager::getDataSource('test_suite'); + $db->config['prefix'] = $_backPrefix; + $fixture->drop($db); + } + +/** + * testSkipIf + * + * @return void + */ + function testSkipIf() { + $this->assertTrue($this->Case->skipIf(true)); + $this->assertFalse($this->Case->skipIf(false)); + } + +/** + * testTestDispatcher + * + * @access public + * @return void + */ + function testTestDispatcher() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'models' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS), + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS), + 'controllers' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'controllers' . DS) + ), true); + + $Dispatcher =& new CakeTestDispatcher(); + $Case =& new CakeDispatcherMockTestCase(); + + $Case->expectOnce('startController'); + $Case->expectOnce('endController'); + + $Dispatcher->testCase($Case); + $this->assertTrue(isset($Dispatcher->testCase)); + + $return = $Dispatcher->dispatch('/tests_apps/index', array('autoRender' => 0, 'return' => 1, 'requested' => 1)); + } +} \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_test_fixture.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_test_fixture.test.php new file mode 100644 index 000000000..40bacc56c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/cake_test_fixture.test.php @@ -0,0 +1,504 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.cake.tests.libs + * @since CakePHP(tm) v 1.2.0.4667 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Datasource', 'DboSource', false); + +/** + * CakeTestFixtureTestFixture class + * + * @package cake + * @subpackage cake.cake.tests.cases.libs + */ +class CakeTestFixtureTestFixture extends CakeTestFixture { + +/** + * name Property + * + * @var string + */ + var $name = 'FixtureTest'; + +/** + * table property + * + * @var string + */ + var $table = 'fixture_tests'; + +/** + * Fields array + * + * @var array + */ + var $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'name' => array('type' => 'string', 'length' => '255'), + 'created' => array('type' => 'datetime') + ); + +/** + * Records property + * + * @var array + */ + var $records = array( + array('name' => 'Gandalf', 'created' => '2009-04-28 19:20:00'), + array('name' => 'Captain Picard', 'created' => '2009-04-28 19:20:00'), + array('name' => 'Chewbacca', 'created' => '2009-04-28 19:20:00') + ); +} + +/** + * StringFieldsTestFixture class + * + * @package cake + * @subpackage cake.cake.tests.cases.libs + */ +class StringsTestFixture extends CakeTestFixture { + +/** + * name Property + * + * @var string + */ + var $name = 'Strings'; + +/** + * table property + * + * @var string + */ + var $table = 'strings'; + +/** + * Fields array + * + * @var array + */ + var $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'name' => array('type' => 'string', 'length' => '255'), + 'email' => array('type' => 'string', 'length' => '255'), + 'age' => array('type' => 'integer', 'default' => 10) + ); + +/** + * Records property + * + * @var array + */ + var $records = array( + array('name' => 'John Doe', 'email' => 'john.doe@email.com', 'age' => 20), + array('email' => 'jane.doe@email.com', 'name' => 'Jane Doe', 'age' => 30), + array('name' => 'Mark Doe', 'email' => 'mark.doe@email.com') + ); +} + + +/** + * CakeTestFixtureImportFixture class + * + * @package cake + * @subpackage cake.cake.tests.cases.libs + */ +class CakeTestFixtureImportFixture extends CakeTestFixture { + +/** + * Name property + * + * @var string + */ + var $name = 'ImportFixture'; + +/** + * Import property + * + * @var mixed + */ + var $import = array('table' => 'fixture_tests', 'connection' => 'test_suite'); +} + +/** + * CakeTestFixtureDefaultImportFixture class + * + * @package cake + * @subpackage cake.cake.tests.cases.libs + */ +class CakeTestFixtureDefaultImportFixture extends CakeTestFixture { + +/** + * Name property + * + * @var string + */ + var $name = 'ImportFixture'; +} + +/** + * FixtureImportTestModel class + * + * @package default + * @subpackage cake.cake.tests.cases.libs. + */ +class FixtureImportTestModel extends Model { + var $name = 'FixtureImport'; + var $useTable = 'fixture_tests'; + var $useDbConfig = 'test_suite'; +} + +class FixturePrefixTest extends Model { + var $name = 'FixturePrefix'; + var $useTable = '_tests'; + var $tablePrefix = 'fixture'; + var $useDbConfig = 'test_suite'; +} + +Mock::generate('DboSource', 'BaseFixtureMockDboSource'); + +class FixtureMockDboSource extends BaseFixtureMockDboSource { + var $insertMulti; + + function value($string) { + return is_string($string) ? '\'' . $string . '\'' : $string; + } + + function insertMulti($table, $fields, $values) { + $this->insertMulti = compact('table', 'fields', 'values'); + return true; + } +} + +/** + * Test case for CakeTestFixture + * + * @package cake + * @subpackage cake.cake.tests.cases.libs + */ +class CakeTestFixtureTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->criticDb =& new FixtureMockDboSource(); + $this->criticDb->fullDebug = true; + } + +/** + * tearDown + * + * @access public + * @return void + */ + function tearDown() { + unset($this->criticDb); + } + +/** + * testInit + * + * @access public + * @return void + */ + function testInit() { + $Fixture =& new CakeTestFixtureTestFixture(); + unset($Fixture->table); + $Fixture->init(); + $this->assertEqual($Fixture->table, 'fixture_tests'); + $this->assertEqual($Fixture->primaryKey, 'id'); + + $Fixture =& new CakeTestFixtureTestFixture(); + $Fixture->primaryKey = 'my_random_key'; + $Fixture->init(); + $this->assertEqual($Fixture->primaryKey, 'my_random_key'); + } + +/** + * test that init() correctly sets the fixture table when the connection or model have prefixes defined. + * + * @return void + */ + function testInitDbPrefix() { + $this->_initDb(); + $Source =& new CakeTestFixtureTestFixture(); + $Source->create($this->db); + $Source->insert($this->db); + + $Fixture =& new CakeTestFixtureImportFixture(); + $expected = array('id', 'name', 'created'); + $this->assertEqual(array_keys($Fixture->fields), $expected); + + $db =& ConnectionManager::getDataSource('test_suite'); + $config = $db->config; + $config['prefix'] = 'fixture_test_suite_'; + ConnectionManager::create('fixture_test_suite', $config); + + $Fixture->fields = $Fixture->records = null; + $Fixture->import = array('table' => 'fixture_tests', 'connection' => 'test_suite', 'records' => true); + $Fixture->init(); + $this->assertEqual(count($Fixture->records), count($Source->records)); + + $Fixture =& new CakeTestFixtureImportFixture(); + $Fixture->fields = $Fixture->records = $Fixture->table = null; + $Fixture->import = array('model' => 'FixtureImportTestModel', 'connection' => 'test_suite'); + $Fixture->init(); + $this->assertEqual(array_keys($Fixture->fields), array('id', 'name', 'created')); + $this->assertEqual($Fixture->table, 'fixture_tests'); + + $keys = array_flip(ClassRegistry::keys()); + $this->assertFalse(array_key_exists('fixtureimporttestmodel', $keys)); + + $Source->drop($this->db); + } + +/** + * test that fixtures don't duplicate the test db prefix. + * + * @return void + */ + function testInitDbPrefixDuplication() { + $this->_initDb(); + $backPrefix = $this->db->config['prefix']; + $this->db->config['prefix'] = 'cake_fixture_test_'; + + $Source =& new CakeTestFixtureTestFixture(); + $Source->create($this->db); + $Source->insert($this->db); + + $Fixture =& new CakeTestFixtureImportFixture(); + $Fixture->fields = $Fixture->records = $Fixture->table = null; + $Fixture->import = array('model' => 'FixtureImportTestModel', 'connection' => 'test_suite'); + + $Fixture->init(); + $this->assertEqual(array_keys($Fixture->fields), array('id', 'name', 'created')); + $this->assertEqual($Fixture->table, 'fixture_tests'); + + $Source->drop($this->db); + $this->db->config['prefix'] = $backPrefix; + } + +/** + * test init with a model that has a tablePrefix declared. + * + * @return void + */ + function testInitModelTablePrefix() { + $this->_initDb(); + $hasPrefix = !empty($this->db->config['prefix']); + if ($this->skipIf($hasPrefix, 'Cannot run this test, you have a database connection prefix.')) { + return; + } + $Source =& new CakeTestFixtureTestFixture(); + $Source->create($this->db); + $Source->insert($this->db); + + $Fixture =& new CakeTestFixtureImportFixture(); + unset($Fixture->table); + $Fixture->fields = $Fixture->records = null; + $Fixture->import = array('model' => 'FixturePrefixTest', 'connection' => 'test_suite', 'records' => false); + $Fixture->init(); + $this->assertEqual($Fixture->table, 'fixture_tests'); + + $keys = array_flip(ClassRegistry::keys()); + $this->assertFalse(array_key_exists('fixtureimporttestmodel', $keys)); + + $Source->drop($this->db); + } + +/** + * testImport + * + * @access public + * @return void + */ + function testImport() { + $this->_initDb(); + + $defaultDb =& ConnectionManager::getDataSource('default'); + $testSuiteDb =& ConnectionManager::getDataSource('test_suite'); + $defaultConfig = $defaultDb->config; + $testSuiteConfig = $testSuiteDb->config; + ConnectionManager::create('new_test_suite', array_merge($testSuiteConfig, array('prefix' => 'new_' . $testSuiteConfig['prefix']))); + $newTestSuiteDb =& ConnectionManager::getDataSource('new_test_suite'); + + $Source =& new CakeTestFixtureTestFixture(); + $Source->create($newTestSuiteDb); + $Source->insert($newTestSuiteDb); + + $defaultDb->config = $newTestSuiteDb->config; + + $Fixture =& new CakeTestFixtureDefaultImportFixture(); + $Fixture->fields = $Fixture->records = null; + $Fixture->import = array('model' => 'FixtureImportTestModel', 'connection' => 'new_test_suite'); + $Fixture->init(); + $this->assertEqual(array_keys($Fixture->fields), array('id', 'name', 'created')); + + $defaultDb->config = $defaultConfig; + + $keys = array_flip(ClassRegistry::keys()); + $this->assertFalse(array_key_exists('fixtureimporttestmodel', $keys)); + + $Source->drop($newTestSuiteDb); + } + +/** + * test that importing with records works. Make sure to try with postgres as its + * handling of aliases is a workaround at best. + * + * @return void + */ + function testImportWithRecords() { + $this->_initDb(); + + $defaultDb =& ConnectionManager::getDataSource('default'); + $testSuiteDb =& ConnectionManager::getDataSource('test_suite'); + $defaultConfig = $defaultDb->config; + $testSuiteConfig = $testSuiteDb->config; + ConnectionManager::create('new_test_suite', array_merge($testSuiteConfig, array('prefix' => 'new_' . $testSuiteConfig['prefix']))); + $newTestSuiteDb =& ConnectionManager::getDataSource('new_test_suite'); + + $Source =& new CakeTestFixtureTestFixture(); + $Source->create($newTestSuiteDb); + $Source->insert($newTestSuiteDb); + + $defaultDb->config = $newTestSuiteDb->config; + + $Fixture =& new CakeTestFixtureDefaultImportFixture(); + $Fixture->fields = $Fixture->records = null; + $Fixture->import = array( + 'model' => 'FixtureImportTestModel', 'connection' => 'new_test_suite', 'records' => true + ); + $Fixture->init(); + $this->assertEqual(array_keys($Fixture->fields), array('id', 'name', 'created')); + $this->assertFalse(empty($Fixture->records[0]), 'No records loaded on importing fixture.'); + $this->assertTrue(isset($Fixture->records[0]['name']), 'No name loaded for first record'); + + $defaultDb->config = $defaultConfig; + + $Source->drop($newTestSuiteDb); + } + +/** + * test create method + * + * @access public + * @return void + */ + function testCreate() { + $Fixture =& new CakeTestFixtureTestFixture(); + $this->criticDb->expectAtLeastOnce('execute'); + $this->criticDb->expectAtLeastOnce('createSchema'); + $return = $Fixture->create($this->criticDb); + $this->assertTrue($this->criticDb->fullDebug); + $this->assertTrue($return); + + unset($Fixture->fields); + $return = $Fixture->create($this->criticDb); + $this->assertFalse($return); + } + +/** + * test the insert method + * + * @access public + * @return void + */ + function testInsert() { + $Fixture =& new CakeTestFixtureTestFixture(); + + $this->criticDb->insertMulti = array(); + $return = $Fixture->insert($this->criticDb); + $this->assertTrue(!empty($this->criticDb->insertMulti)); + $this->assertTrue($this->criticDb->fullDebug); + $this->assertTrue($return); + $this->assertEqual('fixture_tests', $this->criticDb->insertMulti['table']); + $this->assertEqual(array('name', 'created'), $this->criticDb->insertMulti['fields']); + $expected = array( + '(\'Gandalf\', \'2009-04-28 19:20:00\')', + '(\'Captain Picard\', \'2009-04-28 19:20:00\')', + '(\'Chewbacca\', \'2009-04-28 19:20:00\')' + ); + $this->assertEqual($expected, $this->criticDb->insertMulti['values']); + } + +/** + * test the insert method + * + * @access public + * @return void + */ + function testInsertStrings() { + $Fixture =& new StringsTestFixture(); + + $this->criticDb->insertMulti = array(); + $return = $Fixture->insert($this->criticDb); + $this->assertTrue(!empty($this->criticDb->insertMulti)); + $this->assertTrue($this->criticDb->fullDebug); + $this->assertTrue($return); + $this->assertEqual('strings', $this->criticDb->insertMulti['table']); + $this->assertEqual(array('name', 'email', 'age'), $this->criticDb->insertMulti['fields']); + $expected = array( + '(\'John Doe\', \'john.doe@email.com\', 20)', + '(\'Jane Doe\', \'jane.doe@email.com\', 30)', + '(\'Mark Doe\', \'mark.doe@email.com\', NULL)', + ); + $this->assertEqual($expected, $this->criticDb->insertMulti['values']); + } + +/** + * Test the drop method + * + * @access public + * @return void + */ + function testDrop() { + $Fixture =& new CakeTestFixtureTestFixture(); + $this->criticDb->setReturnValueAt(0, 'execute', true); + $this->criticDb->expectAtLeastOnce('execute'); + $this->criticDb->expectAtLeastOnce('dropSchema'); + + $return = $Fixture->drop($this->criticDb); + $this->assertTrue($this->criticDb->fullDebug); + $this->assertTrue($return); + + $this->criticDb->setReturnValueAt(1, 'execute', false); + $return = $Fixture->drop($this->criticDb); + $this->assertFalse($return); + + unset($Fixture->fields); + $return = $Fixture->drop($this->criticDb); + $this->assertFalse($return); + } + +/** + * Test the truncate method. + * + * @access public + * @return void + */ + function testTruncate() { + $Fixture =& new CakeTestFixtureTestFixture(); + $this->criticDb->expectAtLeastOnce('truncate'); + $Fixture->truncate($this->criticDb); + $this->assertTrue($this->criticDb->fullDebug); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/class_registry.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/class_registry.test.php new file mode 100644 index 000000000..252c840e8 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/class_registry.test.php @@ -0,0 +1,325 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'ClassRegistry'); + +/** + * ClassRegisterModel class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ClassRegisterModel extends CakeTestModel { + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; +} + +/** + * RegisterArticle class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class RegisterArticle extends ClassRegisterModel { + +/** + * name property + * + * @var string 'RegisterArticle' + * @access public + */ + var $name = 'RegisterArticle'; +} + +/** + * RegisterArticleFeatured class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class RegisterArticleFeatured extends ClassRegisterModel { + +/** + * name property + * + * @var string 'RegisterArticleFeatured' + * @access public + */ + var $name = 'RegisterArticleFeatured'; +} + +/** + * RegisterArticleTag class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class RegisterArticleTag extends ClassRegisterModel { + +/** + * name property + * + * @var string 'RegisterArticleTag' + * @access public + */ + var $name = 'RegisterArticleTag'; +} + +/** + * RegistryPluginAppModel class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class RegistryPluginAppModel extends ClassRegisterModel { + +/** + * tablePrefix property + * + * @var string 'something_' + * @access public + */ + var $tablePrefix = 'something_'; +} + +/** + * TestRegistryPluginModel class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class TestRegistryPluginModel extends RegistryPluginAppModel { + +/** + * name property + * + * @var string 'TestRegistryPluginModel' + * @access public + */ + var $name = 'TestRegistryPluginModel'; +} + +/** + * RegisterCategory class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class RegisterCategory extends ClassRegisterModel { + +/** + * name property + * + * @var string 'RegisterCategory' + * @access public + */ + var $name = 'RegisterCategory'; +} + +/** + * ClassRegistryTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ClassRegistryTest extends CakeTestCase { + +/** + * testAddModel method + * + * @access public + * @return void + */ + function testAddModel() { + if (PHP5) { + $Tag = ClassRegistry::init('RegisterArticleTag'); + } else { + $Tag =& ClassRegistry::init('RegisterArticleTag'); + } + $this->assertTrue(is_a($Tag, 'RegisterArticleTag')); + + $TagCopy = ClassRegistry::isKeySet('RegisterArticleTag'); + $this->assertTrue($TagCopy); + + $Tag->name = 'SomeNewName'; + + if (PHP5) { + $TagCopy = ClassRegistry::getObject('RegisterArticleTag'); + } else { + $TagCopy =& ClassRegistry::getObject('RegisterArticleTag'); + } + + $this->assertTrue(is_a($TagCopy, 'RegisterArticleTag')); + $this->assertIdentical($Tag, $TagCopy); + + if (PHP5) { + $NewTag = ClassRegistry::init(array('class' => 'RegisterArticleTag', 'alias' => 'NewTag')); + } else { + $NewTag =& ClassRegistry::init(array('class' => 'RegisterArticleTag', 'alias' => 'NewTag')); + } + $this->assertTrue(is_a($Tag, 'RegisterArticleTag')); + + if (PHP5) { + $NewTagCopy = ClassRegistry::init(array('class' => 'RegisterArticleTag', 'alias' => 'NewTag')); + } else { + $NewTagCopy =& ClassRegistry::init(array('class' => 'RegisterArticleTag', 'alias' => 'NewTag')); + } + + $this->assertNotIdentical($Tag, $NewTag); + $this->assertIdentical($NewTag, $NewTagCopy); + + $NewTag->name = 'SomeOtherName'; + $this->assertNotIdentical($Tag, $NewTag); + $this->assertIdentical($NewTag, $NewTagCopy); + + $Tag->name = 'SomeOtherName'; + $this->assertNotIdentical($Tag, $NewTag); + + $this->assertTrue($TagCopy->name === 'SomeOtherName'); + + if (PHP5) { + $User = ClassRegistry::init(array('class' => 'RegisterUser', 'alias' => 'User', 'table' => false)); + } else { + $User =& ClassRegistry::init(array('class' => 'RegisterUser', 'alias' => 'User', 'table' => false)); + } + $this->assertTrue(is_a($User, 'AppModel')); + + if (PHP5) { + $UserCopy = ClassRegistry::init(array('class' => 'RegisterUser', 'alias' => 'User', 'table' => false)); + } else { + $UserCopy =& ClassRegistry::init(array('class' => 'RegisterUser', 'alias' => 'User', 'table' => false)); + } + $this->assertTrue(is_a($UserCopy, 'AppModel')); + $this->assertIdentical($User, $UserCopy); + + if (PHP5) { + $Category = ClassRegistry::init(array('class' => 'RegisterCategory')); + } else { + $Category =& ClassRegistry::init(array('class' => 'RegisterCategory')); + } + $this->assertTrue(is_a($Category, 'RegisterCategory')); + + if (PHP5) { + $ParentCategory = ClassRegistry::init(array('class' => 'RegisterCategory', 'alias' => 'ParentCategory')); + } else { + $ParentCategory =& ClassRegistry::init(array('class' => 'RegisterCategory', 'alias' => 'ParentCategory')); + } + $this->assertTrue(is_a($ParentCategory, 'RegisterCategory')); + $this->assertNotIdentical($Category, $ParentCategory); + + $this->assertNotEqual($Category->alias, $ParentCategory->alias); + $this->assertEqual('RegisterCategory', $Category->alias); + $this->assertEqual('ParentCategory', $ParentCategory->alias); + } + +/** + * testClassRegistryFlush method + * + * @access public + * @return void + */ + function testClassRegistryFlush() { + $ArticleTag = ClassRegistry::getObject('RegisterArticleTag'); + $this->assertTrue(is_a($ArticleTag, 'RegisterArticleTag')); + ClassRegistry::flush(); + + $NoArticleTag = ClassRegistry::isKeySet('RegisterArticleTag'); + $this->assertFalse($NoArticleTag); + $this->assertTrue(is_a($ArticleTag, 'RegisterArticleTag')); + } + +/** + * testAddMultipleModels method + * + * @access public + * @return void + */ + function testAddMultipleModels() { + $Article = ClassRegistry::isKeySet('Article'); + $this->assertFalse($Article); + + $Featured = ClassRegistry::isKeySet('Featured'); + $this->assertFalse($Featured); + + $Tag = ClassRegistry::isKeySet('Tag'); + $this->assertFalse($Tag); + + $models = array(array('class' => 'RegisterArticle', 'alias' => 'Article'), + array('class' => 'RegisterArticleFeatured', 'alias' => 'Featured'), + array('class' => 'RegisterArticleTag', 'alias' => 'Tag')); + + $added = ClassRegistry::init($models); + $this->assertTrue($added); + + $Article = ClassRegistry::isKeySet('Article'); + $this->assertTrue($Article); + + $Featured = ClassRegistry::isKeySet('Featured'); + $this->assertTrue($Featured); + + $Tag = ClassRegistry::isKeySet('Tag'); + $this->assertTrue($Tag); + + $Article = ClassRegistry::getObject('Article'); + $this->assertTrue(is_a($Article, 'RegisterArticle')); + + $Featured = ClassRegistry::getObject('Featured'); + $this->assertTrue(is_a($Featured, 'RegisterArticleFeatured')); + + $Tag = ClassRegistry::getObject('Tag'); + $this->assertTrue(is_a($Tag, 'RegisterArticleTag')); + } + +/** + * testPluginAppModel method + * + * @access public + * @return void + */ + function testPluginAppModel() { + $TestRegistryPluginModel = ClassRegistry::isKeySet('TestRegistryPluginModel'); + $this->assertFalse($TestRegistryPluginModel); + + $TestRegistryPluginModel = ClassRegistry::init('RegistryPlugin.TestRegistryPluginModel'); + $this->assertTrue(is_a($TestRegistryPluginModel, 'TestRegistryPluginModel')); + + $this->assertEqual($TestRegistryPluginModel->tablePrefix, 'something_'); + + if (PHP5) { + $PluginUser = ClassRegistry::init(array('class' => 'RegistryPlugin.RegisterUser', 'alias' => 'RegistryPluginUser', 'table' => false)); + } else { + $PluginUser =& ClassRegistry::init(array('class' => 'RegistryPlugin.RegisterUser', 'alias' => 'RegistryPluginUser', 'table' => false)); + } + $this->assertTrue(is_a($PluginUser, 'RegistryPluginAppModel')); + + if (PHP5) { + $PluginUserCopy = ClassRegistry::getObject('RegistryPluginUser'); + } else { + $PluginUserCopy =& ClassRegistry::getObject('RegistryPluginUser'); + } + $this->assertTrue(is_a($PluginUserCopy, 'RegistryPluginAppModel')); + $this->assertIdentical($PluginUser, $PluginUserCopy); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/code_coverage_manager.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/code_coverage_manager.test.php new file mode 100644 index 000000000..d0b5cf2c5 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/code_coverage_manager.test.php @@ -0,0 +1,516 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once CAKE . 'tests' . DS . 'lib' . DS . 'code_coverage_manager.php'; +require_once CAKE . 'tests' . DS . 'lib' . DS . 'reporter' . DS . 'cake_cli_reporter.php'; + +/** + * CodeCoverageManagerTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class CodeCoverageManagerTest extends CakeTestCase { + +/** + * Skip if XDebug not installed + * + * @access public + */ + function skip() { + $this->skipIf(!extension_loaded('xdebug'), '%s XDebug not installed'); + } + +/** + * startTest Method + * Store reference of $_GET to restore later. + * + * @return void + */ + function startCase() { + $this->_get = $_GET; + } + +/** + * End Case - restore GET vars. + * + * @return void + */ + function endCase() { + $_GET = $this->_get; + } + +/** + * testNoTestCaseSupplied method + * + * @access public + * @return void + */ + function testNoTestCaseSupplied() { + if ($this->skipIf(PHP_SAPI == 'cli', 'Is cli, cannot run this test %s')) { + return; + } + $reporter =& new CakeHtmlReporter(null, array('group' => false, 'app' => false, 'plugin' => false)); + + CodeCoverageManager::init(substr(md5(microtime()), 0, 5), $reporter); + CodeCoverageManager::report(false); + $this->assertError(); + + CodeCoverageManager::init('tests' . DS . 'lib' . DS . basename(__FILE__), $reporter); + CodeCoverageManager::report(false); + $this->assertError(); + } + +/** + * Test that test cases don't cause errors + * + * @return void + */ + function testNoTestCaseSuppliedNoErrors() { + if ($this->skipIf(PHP_SAPI == 'cli', 'Is cli, cannot run this test %s')) { + return; + } + $reporter =& new CakeHtmlReporter(null, array('group' => false, 'app' => false, 'plugin' => false)); + $path = LIBS; + if (strpos(LIBS, ROOT) === false) { + $path = ROOT.DS.LIBS; + } + App::import('Core', 'Folder'); + $folder = new Folder(); + $folder->cd($path); + $contents = $folder->read(); + + $contents[1] = array_filter($contents[1], array(&$this, '_basenameFilter')); + + foreach ($contents[1] as $file) { + CodeCoverageManager::init('libs' . DS . $file, $reporter); + CodeCoverageManager::report(false); + $this->assertNoErrors('libs' . DS . $file); + } + } + +/** + * Remove file names that don't share a basename with the current file. + * + * @return void + */ + function _basenameFilter($var) { + return ($var != basename(__FILE__)); + } + +/** + * testGetTestObjectFileNameFromTestCaseFile method + * + * @access public + * @return void + */ + function testGetTestObjectFileNameFromTestCaseFile() { + $manager =& CodeCoverageManager::getInstance(); + $manager->reporter = new CakeHtmlReporter(); + + $expected = $manager->__testObjectFileFromCaseFile('models/some_file.test.php', true); + $this->assertIdentical(APP.'models'.DS.'some_file.php', $expected); + + $expected = $manager->__testObjectFileFromCaseFile('models/datasources/some_file.test.php', true); + $this->assertIdentical(APP.'models'.DS.'datasources'.DS.'some_file.php', $expected); + + $expected = $manager->__testObjectFileFromCaseFile('controllers/some_file.test.php', true); + $this->assertIdentical(APP.'controllers'.DS.'some_file.php', $expected); + + $expected = $manager->__testObjectFileFromCaseFile('views/some_file.test.php', true); + $this->assertIdentical(APP.'views'.DS.'some_file.php', $expected); + + $expected = $manager->__testObjectFileFromCaseFile('behaviors/some_file.test.php', true); + $this->assertIdentical(APP.'models'.DS.'behaviors'.DS.'some_file.php', $expected); + + $expected = $manager->__testObjectFileFromCaseFile('components/some_file.test.php', true); + $this->assertIdentical(APP.'controllers'.DS.'components'.DS.'some_file.php', $expected); + + $expected = $manager->__testObjectFileFromCaseFile('helpers/some_file.test.php', true); + $this->assertIdentical(APP.'views'.DS.'helpers'.DS.'some_file.php', $expected); + + $manager->pluginTest = 'bugs'; + $expected = $manager->__testObjectFileFromCaseFile('models/some_file.test.php', false); + $this->assertIdentical(APP.'plugins'.DS.'bugs'.DS.'models'.DS.'some_file.php', $expected); + + $manager->pluginTest = false; + $manager->reporter = new CakeCliReporter; + $expected = $manager->__testObjectFileFromCaseFile('libs/set.test.php', false); + $this->assertIdentical(ROOT.DS.'cake'.DS.'libs'.DS.'set.php', $expected); + } + +/** + * testOfHtmlDiffReport method + * + * @access public + * @return void + */ + function testOfHtmlDiffReport() { + $manager =& CodeCoverageManager::getInstance(); + $code = <<value = func_get_arg(0); + } else { + \$this->value = func_get_args(); + } + } + +/** + * Returns the contents of the Set object + * + * @return array + * @access public + */ + function &get() { + return \$this->value; + } + +/** + * This function can be thought of as a hybrid between PHP's array_merge and array_merge_recursive. The difference + * to the two is that if an array key contains another array then the function behaves recursive (unlike array_merge) + * but does not do if for keys containing strings (unlike array_merge_recursive). See the unit test for more information. + * + * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays. + * + * @param array \$arr1 Array to be merged + * @param array \$arr2 Array to merge with + * @return array Merged array + * @access public + */ + function merge(\$arr1, \$arr2 = null) { + \$args = func_get_args(); + + if (isset(\$this) && is_a(\$this, 'set')) { + \$backtrace = debug_backtrace(); + \$previousCall = strtolower(\$backtrace[1]['class'].'::'.\$backtrace[1]['function']); + if (\$previousCall != 'set::merge') { + \$r =& \$this->value; + array_unshift(\$args, null); + } + } + if (!isset(\$r)) { + \$r = (array)current(\$args); + } + + while ((\$arg = next(\$args)) !== false) { + if (is_a(\$arg, 'set')) { + \$arg = \$arg->get(); + } + + foreach ((array)\$arg as \$key => \$val) { + if (is_array(\$val) && isset(\$r[\$key]) && is_array(\$r[\$key])) { + \$r[\$key] = Set::merge(\$r[\$key], \$val); + } elseif (is_int(\$key)) { + + } else { + \$r[\$key] = \$val; + } + } + } + return \$r; + } +PHP; + + $testObjectFile = explode("\n", $code); + $coverageData = array( + 0 => 1, + 1 => 1, + 2 => -2, + 3 => -2, + 4 => -2, + 5 => -2, + 6 => -2, + 7 => -2, + 8 => -1, + 9 => -2, + 10 => -2, + 11 => -2, + 12 => -2, + 13 => -2, + 14 => 1, + 15 => 1, + 16 => -1, + 17 => 1, + 18 => 1, + 19 => -1, + 20 => 1, + 21 => -2, + 22 => -2, + 23 => -2, + 24 => -2, + 25 => -2, + 26 => -2, + 27 => 1, + 28 => -1, + 29 => 1, + 30 => 1, + 31 => -2, + 32 => -2, + 33 => -2, + 34 => -2, + 35 => -2, + 36 => -2, + 37 => -2, + 38 => -2, + 39 => -2, + 40 => -2, + 41 => -2, + 42 => -2, + 43 => -1, + 44 => -2, + 45 => -2, + 46 => -2, + 47 => -2, + 48 => 1, + 49 => 1, + 50 => -1, + 51 => 1, + 52 => 1, + 53 => -2, + 54 => -2, + 55 => 1, + 56 => 1, + 57 => 1, + 58 => 1, + 59 => -1, + 60 => 1, + 61 => 1, + 62 => -2, + 63 => -2, + 64 => 1, + 65 => -2, + 66 => 1, + 67 => -1, + 68 => -2, + 69 => -1, + 70 => -1, + 71 => 1, + 72 => -2, + ); + $expected = array( + 0 => 'ignored', + 1 => 'ignored', + 2 => 'ignored', + 3 => 'ignored', + 4 => 'ignored', + 5 => 'ignored show start realstart', + 6 => 'ignored show', + 7 => 'ignored show', + 8 => 'uncovered show', + 9 => 'ignored show', + 10 => 'ignored show', + 11 => 'ignored show end', + 12 => 'ignored', + 13 => 'ignored show start', + 14 => 'covered show', + 15 => 'covered show', + 16 => 'uncovered show', + 17 => 'covered show show', + 18 => 'covered show show', + 19 => 'uncovered show', + 20 => 'covered show', + 21 => 'ignored show', + 22 => 'ignored show end', + 23 => 'ignored', + 24 => 'ignored', + 25 => 'ignored show start', + 26 => 'ignored show', + 27 => 'covered show', + 28 => 'uncovered show', + 29 => 'covered show', + 30 => 'covered show', + 31 => 'ignored show end', + 32 => 'ignored', + 33 => 'ignored', + 34 => 'ignored', + 35 => 'ignored', + 36 => 'ignored', + 37 => 'ignored', + 38 => 'ignored', + 39 => 'ignored', + 40 => 'ignored show start', + 41 => 'ignored show', + 42 => 'ignored show', + 43 => 'uncovered show', + 41 => 'ignored show', + 42 => 'ignored show', + 43 => 'uncovered show', + 44 => 'ignored show', + 45 => 'ignored show', + 46 => 'ignored show', + 47 => 'ignored show', + 48 => 'covered show', + 49 => 'covered show', + 50 => 'uncovered show', + 51 => 'covered show', + 52 => 'covered show', + 53 => 'ignored show end', + 54 => 'ignored', + 55 => 'covered', + 56 => 'covered show start', + 57 => 'covered show', + 58 => 'covered show', + 59 => 'uncovered show', + 60 => 'covered show', + 61 => 'covered show', + 62 => 'ignored show end', + 63 => 'ignored', + 64 => 'covered show start', + 65 => 'ignored show', + 66 => 'covered show show', + 67 => 'uncovered show', + 68 => 'ignored show', + 69 => 'uncovered show', + 70 => 'uncovered show', + 71 => 'covered show', + 72 => 'ignored show', + 73 => 'ignored show end end', + ); + $execCodeLines = range(0, 72); + $result = explode("", $report = $manager->reportCaseHtmlDiff($testObjectFile, $coverageData, $execCodeLines, 3)); + + foreach ($result as $line) { + preg_match('/(.*?)<\/span>/', $line, $matches); + if (!isset($matches[1])) { + continue; + } + + $num = $matches[1]; + $class = $expected[$num]; + $pattern = '/
/'; + $this->assertPattern($pattern, $line, $num.': '.$line." fails"); + } + } + +/** + * testArrayStrrpos method + * + * @access public + * @return void + */ + function testArrayStrrpos() { + $manager =& CodeCoverageManager::getInstance(); + + $a = array( + 'apples', + 'bananas', + 'oranges' + ); + $this->assertEqual(1, $manager->__array_strpos($a, 'ba', true)); + $this->assertEqual(2, $manager->__array_strpos($a, 'range', true)); + $this->assertEqual(0, $manager->__array_strpos($a, 'pp', true)); + $this->assertFalse($manager->__array_strpos('', 'ba', true)); + $this->assertFalse($manager->__array_strpos(false, 'ba', true)); + $this->assertFalse($manager->__array_strpos(array(), 'ba', true)); + + $a = array( + 'rang', + 'orange', + 'oranges' + ); + $this->assertEqual(0, $manager->__array_strpos($a, 'rang')); + $this->assertEqual(2, $manager->__array_strpos($a, 'rang', true)); + $this->assertEqual(1, $manager->__array_strpos($a, 'orange', false)); + $this->assertEqual(1, $manager->__array_strpos($a, 'orange')); + $this->assertEqual(2, $manager->__array_strpos($a, 'orange', true)); + } + +/** + * testGetExecutableLines method + * + * @access public + * @return void + */ + function testGetExecutableLines() { + $manager =& CodeCoverageManager::getInstance(); + $code = <<__getExecutableLines($code); + foreach ($result as $line) { + $this->assertNotIdentical($line, ''); + } + + $code = << + ?> + __getExecutableLines($code); + foreach ($result as $line) { + $this->assertIdentical(trim($line), ''); + } + } + +/** + * testCalculateCodeCoverage method + * + * @access public + * @return void + */ + function testCalculateCodeCoverage() { + $manager =& CodeCoverageManager::getInstance(); + $data = array( + '25' => array(100, 25), + '50' => array(100, 50), + '0' => array(0, 0), + '0' => array(100, 0), + '100' => array(100, 100), + ); + foreach ($data as $coverage => $lines) { + $this->assertEqual($coverage, $manager->__calcCoverage($lines[0], $lines[1])); + } + + $manager->__calcCoverage(100, 1000); + $this->assertError(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/configure.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/configure.test.php new file mode 100644 index 000000000..d1b36754d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/configure.test.php @@ -0,0 +1,833 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Configure'); + +/** + * ConfigureTest + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ConfigureTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_cacheDisable = Configure::read('Cache.disable'); + $this->_debug = Configure::read('debug'); + + Configure::write('Cache.disable', true); + } + +/** + * endTest + * + * @access public + * @return void + */ + function endTest() { + App::build(); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_core_paths')) { + unlink(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_core_paths'); + } + if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_dir_map')) { + unlink(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_dir_map'); + } + if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_file_map')) { + unlink(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_file_map'); + } + if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_object_map')) { + unlink(TMP . 'cache' . DS . 'persistent' . DS . 'cake_core_object_map'); + } + if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'test.config.php')) { + unlink(TMP . 'cache' . DS . 'persistent' . DS . 'test.config.php'); + } + if (file_exists(TMP . 'cache' . DS . 'persistent' . DS . 'test.php')) { + unlink(TMP . 'cache' . DS . 'persistent' . DS . 'test.php'); + } + Configure::write('debug', $this->_debug); + Configure::write('Cache.disable', $this->_cacheDisable); + } + +/** + * testRead method + * + * @access public + * @return void + */ + function testRead() { + $expected = 'ok'; + Configure::write('level1.level2.level3_1', $expected); + Configure::write('level1.level2.level3_2', 'something_else'); + $result = Configure::read('level1.level2.level3_1'); + $this->assertEqual($expected, $result); + + $result = Configure::read('level1.level2.level3_2'); + $this->assertEqual($result, 'something_else'); + + $result = Configure::read('debug'); + $this->assertTrue($result >= 0); + } + +/** + * testWrite method + * + * @access public + * @return void + */ + function testWrite() { + $writeResult = Configure::write('SomeName.someKey', 'myvalue'); + $this->assertTrue($writeResult); + $result = Configure::read('SomeName.someKey'); + $this->assertEqual($result, 'myvalue'); + + $writeResult = Configure::write('SomeName.someKey', null); + $this->assertTrue($writeResult); + $result = Configure::read('SomeName.someKey'); + $this->assertEqual($result, null); + + $expected = array('One' => array('Two' => array('Three' => array('Four' => array('Five' => 'cool'))))); + $writeResult = Configure::write('Key', $expected); + $this->assertTrue($writeResult); + + $result = Configure::read('Key'); + $this->assertEqual($expected, $result); + + $result = Configure::read('Key.One'); + $this->assertEqual($expected['One'], $result); + + $result = Configure::read('Key.One.Two'); + $this->assertEqual($expected['One']['Two'], $result); + + $result = Configure::read('Key.One.Two.Three.Four.Five'); + $this->assertEqual('cool', $result); + } + +/** + * testSetErrorReporting Level + * + * @return void + */ + function testSetErrorReportingLevel() { + Configure::write('log', false); + + Configure::write('debug', 0); + $result = ini_get('error_reporting'); + $this->assertEqual($result, 0); + + Configure::write('debug', 2); + $result = ini_get('error_reporting'); + $this->assertEqual($result, E_ALL & ~E_DEPRECATED); + + $result = ini_get('display_errors'); + $this->assertEqual($result, 1); + + Configure::write('debug', 0); + $result = ini_get('error_reporting'); + $this->assertEqual($result, 0); + } + +/** + * test that log and debug configure values interact well. + * + * @return void + */ + function testInteractionOfDebugAndLog() { + Configure::write('log', false); + + Configure::write('debug', 0); + $this->assertEqual(ini_get('error_reporting'), 0); + $this->assertEqual(ini_get('display_errors'), 0); + + Configure::write('log', E_WARNING); + Configure::write('debug', 0); + $this->assertEqual(ini_get('error_reporting'), E_WARNING); + $this->assertEqual(ini_get('display_errors'), 0); + + Configure::write('debug', 2); + $this->assertEqual(ini_get('error_reporting'), E_ALL & ~E_DEPRECATED); + $this->assertEqual(ini_get('display_errors'), 1); + + Configure::write('debug', 0); + Configure::write('log', false); + $this->assertEqual(ini_get('error_reporting'), 0); + $this->assertEqual(ini_get('display_errors'), 0); + } + +/** + * testDelete method + * + * @access public + * @return void + */ + function testDelete() { + Configure::write('SomeName.someKey', 'myvalue'); + $result = Configure::read('SomeName.someKey'); + $this->assertEqual($result, 'myvalue'); + + Configure::delete('SomeName.someKey'); + $result = Configure::read('SomeName.someKey'); + $this->assertTrue($result === null); + + Configure::write('SomeName', array('someKey' => 'myvalue', 'otherKey' => 'otherValue')); + + $result = Configure::read('SomeName.someKey'); + $this->assertEqual($result, 'myvalue'); + + $result = Configure::read('SomeName.otherKey'); + $this->assertEqual($result, 'otherValue'); + + Configure::delete('SomeName'); + + $result = Configure::read('SomeName.someKey'); + $this->assertTrue($result === null); + + $result = Configure::read('SomeName.otherKey'); + $this->assertTrue($result === null); + } + +/** + * testLoad method + * + * @access public + * @return void + */ + function testLoad() { + $result = Configure::load('non_existing_configuration_file'); + $this->assertFalse($result); + + $result = Configure::load('config'); + $this->assertTrue($result); + + $result = Configure::load('../../index'); + $this->assertFalse($result); + } + +/** + * testLoad method + * + * @access public + * @return void + */ + function testLoadPlugin() { + App::build(array('plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS)), true); + $result = Configure::load('test_plugin.load'); + $this->assertTrue($result); + $expected = '/test_app/plugins/test_plugin/config/load.php'; + $config = Configure::read('plugin_load'); + $this->assertEqual($config, $expected); + + $result = Configure::load('test_plugin.more.load'); + $this->assertTrue($result); + $expected = '/test_app/plugins/test_plugin/config/more.load.php'; + $config = Configure::read('plugin_more_load'); + $this->assertEqual($config, $expected); + } + +/** + * testStore method + * + * @access public + * @return void + */ + function testStoreAndLoad() { + Configure::write('Cache.disable', false); + + $expected = array('data' => 'value with backslash \, \'singlequote\' and "doublequotes"'); + Configure::store('SomeExample', 'test', $expected); + + Configure::load('test'); + $config = Configure::read('SomeExample'); + $this->assertEqual($config, $expected); + + $expected = array( + 'data' => array('first' => 'value with backslash \, \'singlequote\' and "doublequotes"', 'second' => 'value2'), + 'data2' => 'value' + ); + Configure::store('AnotherExample', 'test_config', $expected); + + Configure::load('test_config'); + $config = Configure::read('AnotherExample'); + $this->assertEqual($config, $expected); + } + +/** + * testVersion method + * + * @access public + * @return void + */ + function testVersion() { + $result = Configure::version(); + $this->assertTrue(version_compare($result, '1.2', '>=')); + } +} + +/** + * AppImportTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class AppImportTest extends CakeTestCase { + +/** + * testBuild method + * + * @access public + * @return void + */ + function testBuild() { + $old = App::path('models'); + $expected = array( + APP . 'models' . DS, + APP, + ROOT . DS . LIBS . 'model' . DS + ); + $this->assertEqual($expected, $old); + + App::build(array('models' => array('/path/to/models/'))); + + $new = App::path('models'); + + $expected = array( + '/path/to/models/', + APP . 'models' . DS, + APP, + ROOT . DS . LIBS . 'model' . DS + ); + $this->assertEqual($expected, $new); + + App::build(); //reset defaults + $defaults = App::path('models'); + $this->assertEqual($old, $defaults); + } + +/** + * testBuildWithReset method + * + * @access public + * @return void + */ + function testBuildWithReset() { + $old = App::path('models'); + $expected = array( + APP . 'models' . DS, + APP, + ROOT . DS . LIBS . 'model' . DS + ); + $this->assertEqual($expected, $old); + + App::build(array('models' => array('/path/to/models/')), true); + + $new = App::path('models'); + + $expected = array( + '/path/to/models/' + ); + $this->assertEqual($expected, $new); + + App::build(); //reset defaults + $defaults = App::path('models'); + $this->assertEqual($old, $defaults); + } + +/** + * testCore method + * + * @access public + * @return void + */ + function testCore() { + $model = App::core('models'); + $this->assertEqual(array(ROOT . DS . LIBS . 'model' . DS), $model); + + $view = App::core('views'); + $this->assertEqual(array(ROOT . DS . LIBS . 'view' . DS), $view); + + $controller = App::core('controllers'); + $this->assertEqual(array(ROOT . DS . LIBS . 'controller' . DS), $controller); + + } + +/** + * testListObjects method + * + * @access public + * @return void + */ + function testListObjects() { + $result = App::objects('class', TEST_CAKE_CORE_INCLUDE_PATH . 'libs'); + $this->assertTrue(in_array('Xml', $result)); + $this->assertTrue(in_array('Cache', $result)); + $this->assertTrue(in_array('HttpSocket', $result)); + + $result = App::objects('behavior'); + $this->assertTrue(in_array('Tree', $result)); + + $result = App::objects('controller'); + $this->assertTrue(in_array('Pages', $result)); + + $result = App::objects('component'); + $this->assertTrue(in_array('Auth', $result)); + + $result = App::objects('view'); + $this->assertTrue(in_array('Media', $result)); + + $result = App::objects('helper'); + $this->assertTrue(in_array('Html', $result)); + + $result = App::objects('model'); + $notExpected = array('AppModel', 'ModelBehavior', 'ConnectionManager', 'DbAcl', 'Model', 'CakeSchema'); + foreach ($notExpected as $class) { + $this->assertFalse(in_array($class, $result)); + } + + $result = App::objects('file'); + $this->assertFalse($result); + + $result = App::objects('file', 'non_existing_configure'); + $expected = array(); + $this->assertEqual($result, $expected); + + $result = App::objects('NonExistingType'); + $this->assertFalse($result); + + App::build(array( + 'plugins' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'libs' . DS + ) + )); + $result = App::objects('plugin', null, false); + $this->assertTrue(in_array('Cache', $result)); + $this->assertTrue(in_array('Log', $result)); + + App::build(); + } + +/** + * test that pluginPath can find paths for plugins. + * + * @return void + */ + function testPluginPath() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + $path = App::pluginPath('test_plugin'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS; + $this->assertEqual($path, $expected); + + $path = App::pluginPath('TestPlugin'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS; + $this->assertEqual($path, $expected); + + $path = App::pluginPath('TestPluginTwo'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin_two' . DS; + $this->assertEqual($path, $expected); + App::build(); + } + +/** + * test that pluginPath can find paths for plugins. + * + * @return void + */ + function testThemePath() { + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS) + )); + $path = App::themePath('test_theme'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'themed' . DS . 'test_theme' . DS; + $this->assertEqual($path, $expected); + + $path = App::themePath('TestTheme'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'themed' . DS . 'test_theme' . DS; + $this->assertEqual($path, $expected); + + App::build(); + } + +/** + * testClassLoading method + * + * @access public + * @return void + */ + function testClassLoading() { + $file = App::import(); + $this->assertTrue($file); + + $file = App::import('Model', 'Model', false); + $this->assertTrue($file); + $this->assertTrue(class_exists('Model')); + + $file = App::import('Controller', 'Controller', false); + $this->assertTrue($file); + $this->assertTrue(class_exists('Controller')); + + $file = App::import('Component', 'Component', false); + $this->assertTrue($file); + $this->assertTrue(class_exists('Component')); + + $file = App::import('Shell', 'Shell', false); + $this->assertTrue($file); + $this->assertTrue(class_exists('Shell')); + + $file = App::import('Model', 'SomeRandomModelThatDoesNotExist', false); + $this->assertFalse($file); + + $file = App::import('Model', 'AppModel', false); + $this->assertTrue($file); + $this->assertTrue(class_exists('AppModel')); + + $file = App::import('WrongType', null, true, array(), ''); + $this->assertTrue($file); + + $file = App::import('Model', 'NonExistingPlugin.NonExistingModel', false); + $this->assertFalse($file); + + $file = App::import('Core', 'NonExistingPlugin.NonExistingModel', false); + $this->assertFalse($file); + + $file = App::import('Model', array('NonExistingPlugin.NonExistingModel'), false); + $this->assertFalse($file); + + $file = App::import('Core', array('NonExistingPlugin.NonExistingModel'), false); + $this->assertFalse($file); + + $file = App::import('Core', array('NonExistingPlugin.NonExistingModel.AnotherChild'), false); + $this->assertFalse($file); + + if (!class_exists('AppController')) { + $classes = array_flip(get_declared_classes()); + + if (PHP5) { + $this->assertFalse(isset($classes['PagesController'])); + $this->assertFalse(isset($classes['AppController'])); + } else { + $this->assertFalse(isset($classes['pagescontroller'])); + $this->assertFalse(isset($classes['appcontroller'])); + } + + $file = App::import('Controller', 'Pages'); + $this->assertTrue($file); + $this->assertTrue(class_exists('PagesController')); + + $classes = array_flip(get_declared_classes()); + + if (PHP5) { + $this->assertTrue(isset($classes['PagesController'])); + $this->assertTrue(isset($classes['AppController'])); + } else { + $this->assertTrue(isset($classes['pagescontroller'])); + $this->assertTrue(isset($classes['appcontroller'])); + } + + $file = App::import('Behavior', 'Containable'); + $this->assertTrue($file); + $this->assertTrue(class_exists('ContainableBehavior')); + + $file = App::import('Component', 'RequestHandler'); + $this->assertTrue($file); + $this->assertTrue(class_exists('RequestHandlerComponent')); + + $file = App::import('Helper', 'Form'); + $this->assertTrue($file); + $this->assertTrue(class_exists('FormHelper')); + + $file = App::import('Model', 'NonExistingModel'); + $this->assertFalse($file); + + $file = App::import('Datasource', 'DboSource'); + $this->assertTrue($file); + $this->assertTrue(class_exists('DboSource')); + } + App::build(); + } + +/** + * test import() with plugins + * + * @return void + */ + function testPluginImporting() { + App::build(array( + 'libs' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'libs' . DS), + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + + $result = App::import('Controller', 'TestPlugin.Tests'); + $this->assertTrue($result); + $this->assertTrue(class_exists('TestPluginAppController')); + $this->assertTrue(class_exists('TestsController')); + + $result = App::import('Lib', 'TestPlugin.TestPluginLibrary'); + $this->assertTrue($result); + $this->assertTrue(class_exists('TestPluginLibrary')); + + $result = App::import('Lib', 'Library'); + $this->assertTrue($result); + $this->assertTrue(class_exists('Library')); + + $result = App::import('Helper', 'TestPlugin.OtherHelper'); + $this->assertTrue($result); + $this->assertTrue(class_exists('OtherHelperHelper')); + + $result = App::import('Helper', 'TestPlugin.TestPluginApp'); + $this->assertTrue($result); + $this->assertTrue(class_exists('TestPluginAppHelper')); + + $result = App::import('Datasource', 'TestPlugin.TestSource'); + $this->assertTrue($result); + $this->assertTrue(class_exists('TestSource')); + + App::build(); + } + +/** + * test that building helper paths actually works. + * + * @return void + * @link http://cakephp.lighthouseapp.com/projects/42648/tickets/410 + */ + function testImportingHelpersFromAlternatePaths() { + App::build(); + $this->assertFalse(class_exists('BananaHelper'), 'BananaHelper exists, cannot test importing it.'); + App::import('Helper', 'Banana'); + $this->assertFalse(class_exists('BananaHelper'), 'BananaHelper was not found because the path does not exist.'); + + App::build(array( + 'helpers' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'helpers' . DS) + )); + App::build(array('vendors' => array(TEST_CAKE_CORE_INCLUDE_PATH))); + $this->assertFalse(class_exists('BananaHelper'), 'BananaHelper exists, cannot test importing it.'); + App::import('Helper', 'Banana'); + $this->assertTrue(class_exists('BananaHelper'), 'BananaHelper was not loaded.'); + + App::build(); + } + +/** + * testFileLoading method + * + * @access public + * @return void + */ + function testFileLoading () { + $file = App::import('File', 'RealFile', false, array(), TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'config.php'); + $this->assertTrue($file); + + $file = App::import('File', 'NoFile', false, array(), TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'cake' . DS . 'config.php'); + $this->assertFalse($file); + } + // import($type = null, $name = null, $parent = true, $file = null, $search = array(), $return = false) { + +/** + * testFileLoadingWithArray method + * + * @access public + * @return void + */ + function testFileLoadingWithArray() { + $type = array('type' => 'File', 'name' => 'SomeName', 'parent' => false, + 'file' => TEST_CAKE_CORE_INCLUDE_PATH . DS . 'config' . DS . 'config.php'); + $file = App::import($type); + $this->assertTrue($file); + + $type = array('type' => 'File', 'name' => 'NoFile', 'parent' => false, + 'file' => TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'cake' . DS . 'config.php'); + $file = App::import($type); + $this->assertFalse($file); + } + +/** + * testFileLoadingReturnValue method + * + * @access public + * @return void + */ + function testFileLoadingReturnValue () { + $file = App::import('File', 'Name', false, array(), TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'config.php', true); + $this->assertTrue($file); + + $this->assertTrue(isset($file['Cake.version'])); + + $type = array('type' => 'File', 'name' => 'OtherName', 'parent' => false, + 'file' => TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'config.php', 'return' => true); + $file = App::import($type); + $this->assertTrue($file); + + $this->assertTrue(isset($file['Cake.version'])); + } + +/** + * testLoadingWithSearch method + * + * @access public + * @return void + */ + function testLoadingWithSearch () { + $file = App::import('File', 'NewName', false, array(TEST_CAKE_CORE_INCLUDE_PATH ), 'config.php'); + $this->assertTrue($file); + + $file = App::import('File', 'AnotherNewName', false, array(LIBS), 'config.php'); + $this->assertFalse($file); + } + +/** + * testLoadingWithSearchArray method + * + * @access public + * @return void + */ + function testLoadingWithSearchArray () { + $type = array('type' => 'File', 'name' => 'RandomName', 'parent' => false, 'file' => 'config.php', 'search' => array(TEST_CAKE_CORE_INCLUDE_PATH )); + $file = App::import($type); + $this->assertTrue($file); + + $type = array('type' => 'File', 'name' => 'AnotherRandomName', 'parent' => false, 'file' => 'config.php', 'search' => array(LIBS)); + $file = App::import($type); + $this->assertFalse($file); + } + +/** + * testMultipleLoading method + * + * @access public + * @return void + */ + function testMultipleLoading() { + $toLoad = array('I18n', 'CakeSocket'); + + $classes = array_flip(get_declared_classes()); + $this->assertFalse(isset($classes['i18n'])); + $this->assertFalse(isset($classes['CakeSocket'])); + + $load = App::import($toLoad); + $this->assertTrue($load); + + $classes = array_flip(get_declared_classes()); + + if (PHP5) { + $this->assertTrue(isset($classes['I18n'])); + } else { + $this->assertTrue(isset($classes['i18n'])); + } + + $load = App::import(array('I18n', 'SomeNotFoundClass', 'CakeSocket')); + $this->assertFalse($load); + + $load = App::import($toLoad); + $this->assertTrue($load); + } + +/** + * This test only works if you have plugins/my_plugin set up. + * plugins/my_plugin/models/my_plugin.php and other_model.php + */ + +/* + function testMultipleLoadingByType() { + $classes = array_flip(get_declared_classes()); + $this->assertFalse(isset($classes['OtherPlugin'])); + $this->assertFalse(isset($classes['MyPlugin'])); + + + $load = App::import('Model', array('MyPlugin.OtherPlugin', 'MyPlugin.MyPlugin')); + $this->assertTrue($load); + + $classes = array_flip(get_declared_classes()); + $this->assertTrue(isset($classes['OtherPlugin'])); + $this->assertTrue(isset($classes['MyPlugin'])); + } +*/ + function testLoadingVendor() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'vendors' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors'. DS), + ), true); + + ob_start(); + $result = App::import('Vendor', 'TestPlugin.TestPluginAsset', array('ext' => 'css')); + $text = ob_get_clean(); + $this->assertTrue($result); + $this->assertEqual($text, 'this is the test plugin asset css file'); + + ob_start(); + $result = App::import('Vendor', 'TestAsset', array('ext' => 'css')); + $text = ob_get_clean(); + $this->assertTrue($result); + $this->assertEqual($text, 'this is the test asset css file'); + + $result = App::import('Vendor', 'TestPlugin.SamplePlugin'); + $this->assertTrue($result); + $this->assertTrue(class_exists('SamplePluginClassTestName')); + + $result = App::import('Vendor', 'ConfigureTestVendorSample'); + $this->assertTrue($result); + $this->assertTrue(class_exists('ConfigureTestVendorSample')); + + ob_start(); + $result = App::import('Vendor', 'SomeName', array('file' => 'some.name.php')); + $text = ob_get_clean(); + $this->assertTrue($result); + $this->assertEqual($text, 'This is a file with dot in file name'); + + ob_start(); + $result = App::import('Vendor', 'TestHello', array('file' => 'Test'.DS.'hello.php')); + $text = ob_get_clean(); + $this->assertTrue($result); + $this->assertEqual($text, 'This is the hello.php file in Test directory'); + + ob_start(); + $result = App::import('Vendor', 'MyTest', array('file' => 'Test'.DS.'MyTest.php')); + $text = ob_get_clean(); + $this->assertTrue($result); + $this->assertEqual($text, 'This is the MyTest.php file'); + + ob_start(); + $result = App::import('Vendor', 'Welcome'); + $text = ob_get_clean(); + $this->assertTrue($result); + $this->assertEqual($text, 'This is the welcome.php file in vendors directory'); + + ob_start(); + $result = App::import('Vendor', 'TestPlugin.Welcome'); + $text = ob_get_clean(); + $this->assertTrue($result); + $this->assertEqual($text, 'This is the welcome.php file in test_plugin/vendors directory'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/component.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/component.test.php new file mode 100644 index 000000000..770aacc2c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/component.test.php @@ -0,0 +1,587 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller + * @since CakePHP(tm) v 1.2.0.5436 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Controller', 'Controller', false); +App::import('Controller', 'Component', false); + +if (!class_exists('AppController')) { + +/** + * AppController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ + class AppController extends Controller { + +/** + * name property + * + * @var string 'App' + * @access public + */ + var $name = 'App'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * helpers property + * + * @var array + * @access public + */ + var $helpers = array(); + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Orange' => array('colour' => 'blood orange')); + } +} elseif (!defined('APP_CONTROLLER_EXISTS')){ + define('APP_CONTROLLER_EXISTS', true); +} + +/** + * ParamTestComponent + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ParamTestComponent extends Object { + +/** + * name property + * + * @var string 'ParamTest' + * @access public + */ + var $name = 'ParamTest'; + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Banana' => array('config' => 'value')); + +/** + * initialize method + * + * @param mixed $controller + * @param mixed $settings + * @access public + * @return void + */ + function initialize(&$controller, $settings) { + foreach ($settings as $key => $value) { + if (is_numeric($key)) { + $this->{$value} = true; + } else { + $this->{$key} = $value; + } + } + } +} + +/** + * ComponentTestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ComponentTestController extends AppController { + +/** + * name property + * + * @var string 'ComponentTest' + * @access public + */ + var $name = 'ComponentTest'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); +} + +/** + * AppleComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class AppleComponent extends Object { + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Orange'); + +/** + * testName property + * + * @var mixed null + * @access public + */ + var $testName = null; + +/** + * startup method + * + * @param mixed $controller + * @access public + * @return void + */ + function startup(&$controller) { + $this->testName = $controller->name; + } +} + +/** + * OrangeComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class OrangeComponent extends Object { + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Banana'); + +/** + * initialize method + * + * @param mixed $controller + * @access public + * @return void + */ + function initialize(&$controller, $settings) { + $this->Controller = $controller; + $this->Banana->testField = 'OrangeField'; + $this->settings = $settings; + } + +/** + * startup method + * + * @param Controller $controller + * @return string + * @access public + */ + function startup(&$controller) { + $controller->foo = 'pass'; + } +} + +/** + * BananaComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class BananaComponent extends Object { + +/** + * testField property + * + * @var string 'BananaField' + * @access public + */ + var $testField = 'BananaField'; + +/** + * startup method + * + * @param Controller $controller + * @return string + * @access public + */ + function startup(&$controller) { + $controller->bar = 'fail'; + } +} + +/** + * MutuallyReferencingOneComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class MutuallyReferencingOneComponent extends Object { + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('MutuallyReferencingTwo'); +} + +/** + * MutuallyReferencingTwoComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class MutuallyReferencingTwoComponent extends Object { + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('MutuallyReferencingOne'); +} + +/** + * SomethingWithEmailComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class SomethingWithEmailComponent extends Object { + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Email'); +} + +Mock::generate('Object', 'ComponentMockComponent', array('startup', 'beforeFilter', 'beforeRender', 'other')); + +/** + * ComponentTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ComponentTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_pluginPaths = App::path('plugins'); + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + App::build(); + ClassRegistry::flush(); + } + +/** + * testLoadComponents method + * + * @access public + * @return void + */ + function testLoadComponents() { + $Controller =& new ComponentTestController(); + $Controller->components = array('RequestHandler'); + + $Component =& new Component(); + $Component->init($Controller); + + $this->assertTrue(is_a($Controller->RequestHandler, 'RequestHandlerComponent')); + + $Controller =& new ComponentTestController(); + $Controller->plugin = 'test_plugin'; + $Controller->components = array('RequestHandler', 'TestPluginComponent'); + + $Component =& new Component(); + $Component->init($Controller); + + $this->assertTrue(is_a($Controller->RequestHandler, 'RequestHandlerComponent')); + $this->assertTrue(is_a($Controller->TestPluginComponent, 'TestPluginComponentComponent')); + $this->assertTrue(is_a( + $Controller->TestPluginComponent->TestPluginOtherComponent, + 'TestPluginOtherComponentComponent' + )); + $this->assertFalse(isset($Controller->TestPluginOtherComponent)); + + $Controller =& new ComponentTestController(); + $Controller->components = array('Security'); + + $Component =& new Component(); + $Component->init($Controller); + + $this->assertTrue(is_a($Controller->Security, 'SecurityComponent')); + $this->assertTrue(is_a($Controller->Security->Session, 'SessionComponent')); + + $Controller =& new ComponentTestController(); + $Controller->components = array('Security', 'Cookie', 'RequestHandler'); + + $Component =& new Component(); + $Component->init($Controller); + + $this->assertTrue(is_a($Controller->Security, 'SecurityComponent')); + $this->assertTrue(is_a($Controller->Security->RequestHandler, 'RequestHandlerComponent')); + $this->assertTrue(is_a($Controller->RequestHandler, 'RequestHandlerComponent')); + $this->assertTrue(is_a($Controller->Cookie, 'CookieComponent')); + } + +/** + * test component loading + * + * @return void + */ + function testNestedComponentLoading() { + $Controller =& new ComponentTestController(); + $Controller->components = array('Apple'); + $Controller->uses = false; + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + + $this->assertTrue(is_a($Controller->Apple, 'AppleComponent')); + $this->assertTrue(is_a($Controller->Apple->Orange, 'OrangeComponent')); + $this->assertTrue(is_a($Controller->Apple->Orange->Banana, 'BananaComponent')); + $this->assertTrue(is_a($Controller->Apple->Orange->Controller, 'ComponentTestController')); + $this->assertTrue(empty($Controller->Apple->Session)); + $this->assertTrue(empty($Controller->Apple->Orange->Session)); + } + +/** + * Tests Component::startup() and only running callbacks for components directly attached to + * the controller. + * + * @return void + */ + function testComponentStartup() { + $Controller =& new ComponentTestController(); + $Controller->components = array('Apple'); + $Controller->uses = false; + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + $Controller->beforeFilter(); + $Controller->Component->startup($Controller); + + $this->assertTrue(is_a($Controller->Apple, 'AppleComponent')); + $this->assertEqual($Controller->Apple->testName, 'ComponentTest'); + + $expected = !(defined('APP_CONTROLLER_EXISTS') && APP_CONTROLLER_EXISTS); + $this->assertEqual(isset($Controller->foo), $expected); + $this->assertFalse(isset($Controller->bar)); + } + +/** + * test that triggerCallbacks fires methods on all the components, and can trigger any method. + * + * @return void + */ + function testTriggerCallback() { + $Controller =& new ComponentTestController(); + $Controller->components = array('ComponentMock'); + $Controller->uses = null; + $Controller->constructClasses(); + + $Controller->ComponentMock->expectOnce('beforeRender'); + $Controller->Component->triggerCallback('beforeRender', $Controller); + + $Controller->ComponentMock->expectNever('beforeFilter'); + $Controller->ComponentMock->enabled = false; + $Controller->Component->triggerCallback('beforeFilter', $Controller); + } + +/** + * test a component being used more than once. + * + * @return void + */ + function testMultipleComponentInitialize() { + $Controller =& new ComponentTestController(); + $Controller->uses = false; + $Controller->components = array('Orange', 'Banana'); + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + + $this->assertEqual($Controller->Banana->testField, 'OrangeField'); + $this->assertEqual($Controller->Orange->Banana->testField, 'OrangeField'); + } + +/** + * Test Component declarations with Parameters + * tests merging of component parameters and merging / construction of components. + * + * @return void + */ + function testComponentsWithParams() { + if ($this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController')) { + return; + } + + $Controller =& new ComponentTestController(); + $Controller->components = array('ParamTest' => array('test' => 'value', 'flag'), 'Apple'); + $Controller->uses = false; + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + + $this->assertTrue(is_a($Controller->ParamTest, 'ParamTestComponent')); + $this->assertTrue(is_a($Controller->ParamTest->Banana, 'BananaComponent')); + $this->assertTrue(is_a($Controller->Orange, 'OrangeComponent')); + $this->assertFalse(isset($Controller->Session)); + $this->assertEqual($Controller->Orange->settings, array('colour' => 'blood orange')); + $this->assertEqual($Controller->ParamTest->test, 'value'); + $this->assertEqual($Controller->ParamTest->flag, true); + + //Settings are merged from app controller and current controller. + $Controller =& new ComponentTestController(); + $Controller->components = array( + 'ParamTest' => array('test' => 'value'), + 'Orange' => array('ripeness' => 'perfect') + ); + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + + $expected = array('colour' => 'blood orange', 'ripeness' => 'perfect'); + $this->assertEqual($Controller->Orange->settings, $expected); + $this->assertEqual($Controller->ParamTest->test, 'value'); + } + +/** + * Ensure that settings are not duplicated when passed into component initialize. + * + * @return void + */ + function testComponentParamsNoDuplication() { + if ($this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController')) { + return; + } + $Controller =& new ComponentTestController(); + $Controller->components = array('Orange' => array('setting' => array('itemx'))); + $Controller->uses = false; + + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + $expected = array('setting' => array('itemx'), 'colour' => 'blood orange'); + $this->assertEqual($Controller->Orange->settings, $expected, 'Params duplication has occured %s'); + } + +/** + * Test mutually referencing components. + * + * @return void + */ + function testMutuallyReferencingComponents() { + $Controller =& new ComponentTestController(); + $Controller->components = array('MutuallyReferencingOne'); + $Controller->uses = false; + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + + $this->assertTrue(is_a( + $Controller->MutuallyReferencingOne, + 'MutuallyReferencingOneComponent' + )); + $this->assertTrue(is_a( + $Controller->MutuallyReferencingOne->MutuallyReferencingTwo, + 'MutuallyReferencingTwoComponent' + )); + $this->assertTrue(is_a( + $Controller->MutuallyReferencingOne->MutuallyReferencingTwo->MutuallyReferencingOne, + 'MutuallyReferencingOneComponent' + )); + } + +/** + * Test mutually referencing components. + * + * @return void + */ + function testSomethingReferencingEmailComponent() { + $Controller =& new ComponentTestController(); + $Controller->components = array('SomethingWithEmail'); + $Controller->uses = false; + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + $Controller->beforeFilter(); + $Controller->Component->startup($Controller); + + $this->assertTrue(is_a( + $Controller->SomethingWithEmail, + 'SomethingWithEmailComponent' + )); + $this->assertTrue(is_a( + $Controller->SomethingWithEmail->Email, + 'EmailComponent' + )); + $this->assertTrue(is_a( + $Controller->SomethingWithEmail->Email->Controller, + 'ComponentTestController' + )); + } + +/** + * Test that SessionComponent doesn't get added if its already in the components array. + * + * @return void + * @access public + */ + function testDoubleLoadingOfSessionComponent() { + if ($this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController')) { + return; + } + + $Controller =& new ComponentTestController(); + $Controller->uses = false; + $Controller->components = array('Session'); + $Controller->constructClasses(); + + $this->assertEqual($Controller->components, array('Session' => '', 'Orange' => array('colour' => 'blood orange'))); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/acl.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/acl.test.php new file mode 100644 index 000000000..a1f12c6df --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/acl.test.php @@ -0,0 +1,646 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + * @since CakePHP(tm) v 1.2.0.5435 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + define('CAKEPHP_UNIT_TEST_EXECUTION', 1); +} +App::import(array('controller' .DS . 'components' . DS . 'acl', 'model' . DS . 'db_acl')); + +/** + * AclNodeTwoTestBase class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class AclNodeTwoTestBase extends AclNode { + +/** + * useDbConfig property + * + * @var string 'test_suite' + * @access public + */ + var $useDbConfig = 'test_suite'; + +/** + * cacheSources property + * + * @var bool false + * @access public + */ + var $cacheSources = false; +} + +/** + * AroTwoTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class AroTwoTest extends AclNodeTwoTestBase { + +/** + * name property + * + * @var string 'AroTwoTest' + * @access public + */ + var $name = 'AroTwoTest'; + +/** + * useTable property + * + * @var string 'aro_twos' + * @access public + */ + var $useTable = 'aro_twos'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('AcoTwoTest' => array('with' => 'PermissionTwoTest')); +} + +/** + * AcoTwoTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class AcoTwoTest extends AclNodeTwoTestBase { + +/** + * name property + * + * @var string 'AcoTwoTest' + * @access public + */ + var $name = 'AcoTwoTest'; + +/** + * useTable property + * + * @var string 'aco_twos' + * @access public + */ + var $useTable = 'aco_twos'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('AroTwoTest' => array('with' => 'PermissionTwoTest')); +} + +/** + * PermissionTwoTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class PermissionTwoTest extends CakeTestModel { + +/** + * name property + * + * @var string 'PermissionTwoTest' + * @access public + */ + var $name = 'PermissionTwoTest'; + +/** + * useTable property + * + * @var string 'aros_aco_twos' + * @access public + */ + var $useTable = 'aros_aco_twos'; + +/** + * cacheQueries property + * + * @var bool false + * @access public + */ + var $cacheQueries = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('AroTwoTest' => array('foreignKey' => 'aro_id'), 'AcoTwoTest' => array('foreignKey' => 'aco_id')); + +/** + * actsAs property + * + * @var mixed null + * @access public + */ + var $actsAs = null; +} + +/** + * DbAclTwoTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class DbAclTwoTest extends DbAcl { + +/** + * construct method + * + * @access private + * @return void + */ + function __construct() { + $this->Aro =& new AroTwoTest(); + $this->Aro->Permission =& new PermissionTwoTest(); + $this->Aco =& new AcoTwoTest(); + $this->Aro->Permission =& new PermissionTwoTest(); + } +} + +/** + * IniAclTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class IniAclTest extends IniAcl { +} + +/** + * ACL Component Text case + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class AclComponentTest extends CakeTestCase { + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.aro_two', 'core.aco_two', 'core.aros_aco_two'); + +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->Acl =& new AclComponent(); + } + +/** + * before method + * + * @param mixed $method + * @access public + * @return void + */ + function before($method) { + Configure::write('Acl.classname', 'DbAclTwoTest'); + Configure::write('Acl.database', 'test_suite'); + parent::before($method); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Acl); + } + +/** + * testAclCreate method + * + * @access public + * @return void + */ + function testAclCreate() { + $this->Acl->Aro->create(array('alias' => 'Chotchkey')); + $this->assertTrue($this->Acl->Aro->save()); + + $parent = $this->Acl->Aro->id; + + $this->Acl->Aro->create(array('parent_id' => $parent, 'alias' => 'Joanna')); + $this->assertTrue($this->Acl->Aro->save()); + + $this->Acl->Aro->create(array('parent_id' => $parent, 'alias' => 'Stapler')); + $this->assertTrue($this->Acl->Aro->save()); + + $root = $this->Acl->Aco->node('ROOT'); + $parent = $root[0]['AcoTwoTest']['id']; + + $this->Acl->Aco->create(array('parent_id' => $parent, 'alias' => 'Drinks')); + $this->assertTrue($this->Acl->Aco->save()); + + $this->Acl->Aco->create(array('parent_id' => $parent, 'alias' => 'PiecesOfFlair')); + $this->assertTrue($this->Acl->Aco->save()); + } + +/** + * testAclCreateWithParent method + * + * @access public + * @return void + */ + function testAclCreateWithParent() { + $parent = $this->Acl->Aro->findByAlias('Peter', null, null, -1); + $this->Acl->Aro->create(); + $this->Acl->Aro->save(array( + 'alias' => 'Subordinate', + 'model' => 'User', + 'foreign_key' => 7, + 'parent_id' => $parent['AroTwoTest']['id'] + )); + $result = $this->Acl->Aro->findByAlias('Subordinate', null, null, -1); + $this->assertEqual($result['AroTwoTest']['lft'], 16); + $this->assertEqual($result['AroTwoTest']['rght'], 17); + } + +/** + * testDbAclAllow method + * + * @access public + * @return void + */ + function testDbAclAllow() { + $this->assertFalse($this->Acl->check('Micheal', 'tpsReports', 'read')); + $this->assertTrue($this->Acl->allow('Micheal', 'tpsReports', array('read', 'delete', 'update'))); + $this->assertTrue($this->Acl->check('Micheal', 'tpsReports', 'update')); + $this->assertTrue($this->Acl->check('Micheal', 'tpsReports', 'read')); + $this->assertTrue($this->Acl->check('Micheal', 'tpsReports', 'delete')); + + $this->assertFalse($this->Acl->check('Micheal', 'tpsReports', 'create')); + $this->assertTrue($this->Acl->allow('Micheal', 'ROOT/tpsReports', 'create')); + $this->assertTrue($this->Acl->check('Micheal', 'tpsReports', 'create')); + $this->assertTrue($this->Acl->check('Micheal', 'tpsReports', 'delete')); + $this->assertTrue($this->Acl->allow('Micheal', 'printers', 'create')); + // Michael no longer has his delete permission for tpsReports! + $this->assertTrue($this->Acl->check('Micheal', 'tpsReports', 'delete')); + $this->assertTrue($this->Acl->check('Micheal', 'printers', 'create')); + + $this->assertFalse($this->Acl->check('root/users/Samir', 'ROOT/tpsReports/view')); + $this->assertTrue($this->Acl->allow('root/users/Samir', 'ROOT/tpsReports/view', '*')); + $this->assertTrue($this->Acl->check('Samir', 'view', 'read')); + $this->assertTrue($this->Acl->check('root/users/Samir', 'ROOT/tpsReports/view', 'update')); + + $this->assertFalse($this->Acl->check('root/users/Samir', 'ROOT/tpsReports/update','*')); + $this->assertTrue($this->Acl->allow('root/users/Samir', 'ROOT/tpsReports/update', '*')); + $this->assertTrue($this->Acl->check('Samir', 'update', 'read')); + $this->assertTrue($this->Acl->check('root/users/Samir', 'ROOT/tpsReports/update', 'update')); + // Samir should still have his tpsReports/view permissions, but does not + $this->assertTrue($this->Acl->check('root/users/Samir', 'ROOT/tpsReports/view', 'update')); + + $this->expectError('DbAcl::allow() - Invalid node'); + $this->assertFalse($this->Acl->allow('Lumbergh', 'ROOT/tpsReports/DoesNotExist', 'create')); + + $this->expectError('DbAcl::allow() - Invalid node'); + $this->assertFalse($this->Acl->allow('Homer', 'tpsReports', 'create')); + } + +/** + * testDbAclCheck method + * + * @access public + * @return void + */ + function testDbAclCheck() { + $this->assertTrue($this->Acl->check('Samir', 'print', 'read')); + $this->assertTrue($this->Acl->check('Lumbergh', 'current', 'read')); + $this->assertFalse($this->Acl->check('Milton', 'smash', 'read')); + $this->assertFalse($this->Acl->check('Milton', 'current', 'update')); + + $this->expectError("DbAcl::check() - Failed ARO/ACO node lookup in permissions check. Node references:\nAro: WRONG\nAco: tpsReports"); + $this->assertFalse($this->Acl->check('WRONG', 'tpsReports', 'read')); + + $this->expectError("ACO permissions key foobar does not exist in DbAcl::check()"); + $this->assertFalse($this->Acl->check('Lumbergh', 'smash', 'foobar')); + + $this->expectError("DbAcl::check() - Failed ARO/ACO node lookup in permissions check. Node references:\nAro: users\nAco: NonExistant"); + $this->assertFalse($this->Acl->check('users', 'NonExistant', 'read')); + + $this->assertFalse($this->Acl->check(null, 'printers', 'create')); + $this->assertFalse($this->Acl->check('managers', null, 'read')); + + $this->assertTrue($this->Acl->check('Bobs', 'ROOT/tpsReports/view/current', 'read')); + $this->assertFalse($this->Acl->check('Samir', 'ROOT/tpsReports/update', 'read')); + + $this->assertFalse($this->Acl->check('root/users/Milton', 'smash', 'delete')); + } + +/** + * testDbAclCascadingDeny function + * + * Setup the acl permissions such that Bobs inherits from admin. + * deny Admin delete access to a specific resource, check the permisssions are inherited. + * + * @access public + * @return void + */ + function testDbAclCascadingDeny() { + $this->Acl->inherit('Bobs', 'ROOT', '*'); + $this->assertTrue($this->Acl->check('admin', 'tpsReports', 'delete')); + $this->assertTrue($this->Acl->check('Bobs', 'tpsReports', 'delete')); + $this->Acl->deny('admin', 'tpsReports', 'delete'); + $this->assertFalse($this->Acl->check('admin', 'tpsReports', 'delete')); + $this->assertFalse($this->Acl->check('Bobs', 'tpsReports', 'delete')); + } + +/** + * testDbAclDeny method + * + * @access public + * @return void + */ + function testDbAclDeny() { + $this->assertTrue($this->Acl->check('Micheal', 'smash', 'delete')); + $this->Acl->deny('Micheal', 'smash', 'delete'); + $this->assertFalse($this->Acl->check('Micheal', 'smash', 'delete')); + $this->assertTrue($this->Acl->check('Micheal', 'smash', 'read')); + $this->assertTrue($this->Acl->check('Micheal', 'smash', 'create')); + $this->assertTrue($this->Acl->check('Micheal', 'smash', 'update')); + $this->assertFalse($this->Acl->check('Micheal', 'smash', '*')); + + $this->assertTrue($this->Acl->check('Samir', 'refill', '*')); + $this->Acl->deny('Samir', 'refill', '*'); + $this->assertFalse($this->Acl->check('Samir', 'refill', 'create')); + $this->assertFalse($this->Acl->check('Samir', 'refill', 'update')); + $this->assertFalse($this->Acl->check('Samir', 'refill', 'read')); + $this->assertFalse($this->Acl->check('Samir', 'refill', 'delete')); + + $result = $this->Acl->Aro->Permission->find('all', array('conditions' => array('AroTwoTest.alias' => 'Samir'))); + $expected = '-1'; + $this->assertEqual($result[0]['PermissionTwoTest']['_delete'], $expected); + + $this->expectError('DbAcl::allow() - Invalid node'); + $this->assertFalse($this->Acl->deny('Lumbergh', 'ROOT/tpsReports/DoesNotExist', 'create')); + } + +/** + * testAclNodeLookup method + * + * @access public + * @return void + */ + function testAclNodeLookup() { + $result = $this->Acl->Aro->node('root/users/Samir'); + $expected = array( + array('AroTwoTest' => array('id' => '7', 'parent_id' => '4', 'model' => 'User', 'foreign_key' => 3, 'alias' => 'Samir')), + array('AroTwoTest' => array('id' => '4', 'parent_id' => '1', 'model' => 'Group', 'foreign_key' => 3, 'alias' => 'users')), + array('AroTwoTest' => array('id' => '1', 'parent_id' => null, 'model' => null, 'foreign_key' => null, 'alias' => 'root')) + ); + $this->assertEqual($result, $expected); + + $result = $this->Acl->Aco->node('ROOT/tpsReports/view/current'); + $expected = array( + array('AcoTwoTest' => array('id' => '4', 'parent_id' => '3', 'model' => null, 'foreign_key' => null, 'alias' => 'current')), + array('AcoTwoTest' => array('id' => '3', 'parent_id' => '2', 'model' => null, 'foreign_key' => null, 'alias' => 'view')), + array('AcoTwoTest' => array('id' => '2', 'parent_id' => '1', 'model' => null, 'foreign_key' => null, 'alias' => 'tpsReports')), + array('AcoTwoTest' => array('id' => '1', 'parent_id' => null, 'model' => null, 'foreign_key' => null, 'alias' => 'ROOT')), + ); + $this->assertEqual($result, $expected); + } + +/** + * testDbInherit method + * + * @access public + * @return void + */ + function testDbInherit() { + //parent doesn't have access inherit should still deny + $this->assertFalse($this->Acl->check('Milton', 'smash', 'delete')); + $this->Acl->inherit('Milton', 'smash', 'delete'); + $this->assertFalse($this->Acl->check('Milton', 'smash', 'delete')); + + //inherit parent + $this->assertFalse($this->Acl->check('Milton', 'smash', 'read')); + $this->Acl->inherit('Milton', 'smash', 'read'); + $this->assertTrue($this->Acl->check('Milton', 'smash', 'read')); + } + +/** + * testDbGrant method + * + * @access public + * @return void + */ + function testDbGrant() { + $this->assertFalse($this->Acl->check('Samir', 'tpsReports', 'create')); + $this->Acl->grant('Samir', 'tpsReports', 'create'); + $this->assertTrue($this->Acl->check('Samir', 'tpsReports', 'create')); + + $this->assertFalse($this->Acl->check('Micheal', 'view', 'read')); + $this->Acl->grant('Micheal', 'view', array('read', 'create', 'update')); + $this->assertTrue($this->Acl->check('Micheal', 'view', 'read')); + $this->assertTrue($this->Acl->check('Micheal', 'view', 'create')); + $this->assertTrue($this->Acl->check('Micheal', 'view', 'update')); + $this->assertFalse($this->Acl->check('Micheal', 'view', 'delete')); + + $this->expectError('DbAcl::allow() - Invalid node'); + $this->assertFalse($this->Acl->grant('Peter', 'ROOT/tpsReports/DoesNotExist', 'create')); + } + +/** + * testDbRevoke method + * + * @access public + * @return void + */ + function testDbRevoke() { + $this->assertTrue($this->Acl->check('Bobs', 'tpsReports', 'read')); + $this->Acl->revoke('Bobs', 'tpsReports', 'read'); + $this->assertFalse($this->Acl->check('Bobs', 'tpsReports', 'read')); + + $this->assertTrue($this->Acl->check('users', 'printers', 'read')); + $this->Acl->revoke('users', 'printers', 'read'); + $this->assertFalse($this->Acl->check('users', 'printers', 'read')); + $this->assertFalse($this->Acl->check('Samir', 'printers', 'read')); + $this->assertFalse($this->Acl->check('Peter', 'printers', 'read')); + + $this->expectError('DbAcl::allow() - Invalid node'); + $this->assertFalse($this->Acl->deny('Bobs', 'ROOT/printers/DoesNotExist', 'create')); + } + +/** + * testStartup method + * + * @access public + * @return void + */ + function testStartup() { + $controller = new Controller(); + $this->assertTrue($this->Acl->startup($controller)); + } + +/** + * testIniReadConfigFile + * + * @access public + * @return void + */ + function testIniReadConfigFile() { + Configure::write('Acl.classname', 'IniAclTest'); + unset($this->Acl); + $this->Acl = new AclComponent(); + $iniFile = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'config'. DS . 'acl.ini.php'; + $result = $this->Acl->_Instance->readConfigFile($iniFile); + $expected = array( + 'admin' => array( + 'groups' => 'administrators', + 'allow' => '', + 'deny' => 'ads', + ), + 'paul' => array( + 'groups' => 'users', + 'allow' =>'', + 'deny' => '', + ), + 'jenny' => array( + 'groups' => 'users', + 'allow' => 'ads', + 'deny' => 'images, files', + ), + 'nobody' => array( + 'groups' => 'anonymous', + 'allow' => '', + 'deny' => '', + ), + 'administrators' => array( + 'deny' => '', + 'allow' => 'posts, comments, images, files, stats, ads', + ), + 'users' => array( + 'allow' => 'posts, comments, images, files', + 'deny' => 'stats, ads', + ), + 'anonymous' => array( + 'allow' => '', + 'deny' => 'posts, comments, images, files, stats, ads', + ), + ); + $this->assertEqual($result, $expected); + } + +/** + * testIniCheck method + * + * @access public + * @return void + */ + function testIniCheck() { + Configure::write('Acl.classname', 'IniAclTest'); + unset($this->Acl); + $iniFile = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'config'. DS . 'acl.ini.php'; + + $this->Acl = new AclComponent(); + $this->Acl->_Instance->config= $this->Acl->_Instance->readConfigFile($iniFile); + + $this->assertFalse($this->Acl->check('admin', 'ads')); + $this->assertTrue($this->Acl->check('admin', 'posts')); + + $this->assertTrue($this->Acl->check('jenny', 'posts')); + $this->assertTrue($this->Acl->check('jenny', 'ads')); + + $this->assertTrue($this->Acl->check('paul', 'posts')); + $this->assertFalse($this->Acl->check('paul', 'ads')); + + $this->assertFalse($this->Acl->check('nobody', 'comments')); + } + +/** + * debug function - to help editing/creating test cases for the ACL component + * + * To check the overal ACL status at any time call $this->__debug(); + * Generates a list of the current aro and aco structures and a grid dump of the permissions that are defined + * Only designed to work with the db based ACL + * + * @param bool $treesToo + * @access private + * @return void + */ + function __debug ($printTreesToo = false) { + $this->Acl->Aro->displayField = 'alias'; + $this->Acl->Aco->displayField = 'alias'; + $aros = $this->Acl->Aro->find('list', array('order' => 'lft')); + $acos = $this->Acl->Aco->find('list', array('order' => 'lft')); + $rights = array('*', 'create', 'read', 'update', 'delete'); + $permissions['Aros v Acos >'] = $acos; + foreach ($aros as $aro) { + $row = array(); + foreach ($acos as $aco) { + $perms = ''; + foreach ($rights as $right) { + if ($this->Acl->check($aro, $aco, $right)) { + if ($right == '*') { + $perms .= '****'; + break; + } + $perms .= $right[0]; + } elseif ($right != '*') { + $perms .= ' '; + } + } + $row[] = $perms; + } + $permissions[$aro] = $row; + } + foreach ($permissions as $key => $values) { + array_unshift($values, $key); + $values = array_map(array(&$this, '__pad'), $values); + $permissions[$key] = implode (' ', $values); + } + $permisssions = array_map(array(&$this, '__pad'), $permissions); + array_unshift($permissions, 'Current Permissions :'); + if ($printTreesToo) { + debug (array('aros' => $this->Acl->Aro->generateTreeList(), 'acos' => $this->Acl->Aco->generateTreeList())); + } + debug (implode("\r\n", $permissions)); + } + +/** + * pad function + * Used by debug to format strings used in the data dump + * + * @param string $string + * @param int $len + * @access private + * @return void + */ + function __pad($string = '', $len = 14) { + return str_pad($string, $len); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/auth.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/auth.test.php new file mode 100644 index 000000000..c626d2ccf --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/auth.test.php @@ -0,0 +1,1657 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.cake.tests.cases.libs.controller.components + * @since CakePHP(tm) v 1.2.0.5347 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Component', array('Auth', 'Acl')); +App::import('Model', 'DbAcl'); +App::import('Core', 'Xml'); + +Mock::generate('AclComponent', 'AuthTestMockAclComponent'); + +/** +* TestAuthComponent class +* +* @package cake +* @subpackage cake.tests.cases.libs.controller.components +*/ +class TestAuthComponent extends AuthComponent { + +/** + * testStop property + * + * @var bool false + * @access public + */ + var $testStop = false; + +/** + * Sets default login state + * + * @var bool true + * @access protected + */ + var $_loggedIn = true; + +/** + * stop method + * + * @access public + * @return void + */ + function _stop() { + $this->testStop = true; + } +} + +/** +* AuthUser class +* +* @package cake +* @subpackage cake.tests.cases.libs.controller.components +*/ +class AuthUser extends CakeTestModel { + +/** + * name property + * + * @var string 'AuthUser' + * @access public + */ + var $name = 'AuthUser'; + +/** + * useDbConfig property + * + * @var string 'test_suite' + * @access public + */ + var $useDbConfig = 'test_suite'; + +/** + * parentNode method + * + * @access public + * @return void + */ + function parentNode() { + return true; + } + +/** + * bindNode method + * + * @param mixed $object + * @access public + * @return void + */ + function bindNode($object) { + return 'Roles/Admin'; + } + +/** + * isAuthorized method + * + * @param mixed $user + * @param mixed $controller + * @param mixed $action + * @access public + * @return void + */ + function isAuthorized($user, $controller = null, $action = null) { + if (!empty($user)) { + return true; + } + return false; + } +} + +/** + * AuthUserCustomField class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class AuthUserCustomField extends AuthUser { + +/** + * name property + * + * @var string 'AuthUser' + * @access public + */ + var $name = 'AuthUserCustomField'; +} + +/** +* UuidUser class +* +* @package cake +* @subpackage cake.tests.cases.libs.controller.components +*/ +class UuidUser extends CakeTestModel { + +/** + * name property + * + * @var string 'AuthUser' + * @access public + */ + var $name = 'UuidUser'; + +/** + * useDbConfig property + * + * @var string 'test_suite' + * @access public + */ + var $useDbConfig = 'test_suite'; + +/** + * useTable property + * + * @var string 'uuid' + * @access public + */ + var $useTable = 'uuids'; + +/** + * parentNode method + * + * @access public + * @return void + */ + function parentNode() { + return true; + } + +/** + * bindNode method + * + * @param mixed $object + * @access public + * @return void + */ + function bindNode($object) { + return 'Roles/Admin'; + } + +/** + * isAuthorized method + * + * @param mixed $user + * @param mixed $controller + * @param mixed $action + * @access public + * @return void + */ + function isAuthorized($user, $controller = null, $action = null) { + if (!empty($user)) { + return true; + } + return false; + } +} + +/** +* AuthTestController class +* +* @package cake +* @subpackage cake.tests.cases.libs.controller.components +*/ +class AuthTestController extends Controller { + +/** + * name property + * + * @var string 'AuthTest' + * @access public + */ + var $name = 'AuthTest'; + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array('AuthUser'); + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Session', 'Auth', 'Acl'); + +/** + * testUrl property + * + * @var mixed null + * @access public + */ + var $testUrl = null; + +/** + * construct method + * + * @access private + * @return void + */ + function __construct() { + $this->params = Router::parse('/auth_test'); + Router::setRequestInfo(array($this->params, array('base' => null, 'here' => '/auth_test', 'webroot' => '/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array()))); + parent::__construct(); + } + +/** + * beforeFilter method + * + * @access public + * @return void + */ + function beforeFilter() { + $this->Auth->userModel = 'AuthUser'; + } + +/** + * login method + * + * @access public + * @return void + */ + function login() { + } + +/** + * admin_login method + * + * @access public + * @return void + */ + function admin_login() { + } + +/** + * logout method + * + * @access public + * @return void + */ + function logout() { + // $this->redirect($this->Auth->logout()); + } + +/** + * add method + * + * @access public + * @return void + */ + function add() { + echo "add"; + } + +/** + * add method + * + * @access public + * @return void + */ + function camelCase() { + echo "camelCase"; + } + +/** + * redirect method + * + * @param mixed $url + * @param mixed $status + * @param mixed $exit + * @access public + * @return void + */ + function redirect($url, $status = null, $exit = true) { + $this->testUrl = Router::url($url); + return false; + } + +/** + * isAuthorized method + * + * @access public + * @return void + */ + function isAuthorized() { + if (isset($this->params['testControllerAuth'])) { + return false; + } + return true; + } + +/** + * Mock delete method + * + * @param mixed $url + * @param mixed $status + * @param mixed $exit + * @access public + * @return void + */ + function delete($id = null) { + if ($this->TestAuth->testStop !== true && $id !== null) { + echo 'Deleted Record: ' . var_export($id, true); + } + } +} + +/** + * AjaxAuthController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class AjaxAuthController extends Controller { + +/** + * name property + * + * @var string 'AjaxAuth' + * @access public + */ + var $name = 'AjaxAuth'; + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Session', 'TestAuth'); + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * testUrl property + * + * @var mixed null + * @access public + */ + var $testUrl = null; + +/** + * beforeFilter method + * + * @access public + * @return void + */ + function beforeFilter() { + $this->TestAuth->ajaxLogin = 'test_element'; + $this->TestAuth->userModel = 'AuthUser'; + $this->TestAuth->RequestHandler->ajaxLayout = 'ajax2'; + } + +/** + * add method + * + * @access public + * @return void + */ + function add() { + if ($this->TestAuth->testStop !== true) { + echo 'Added Record'; + } + } + +/** + * redirect method + * + * @param mixed $url + * @param mixed $status + * @param mixed $exit + * @access public + * @return void + */ + function redirect($url, $status = null, $exit = true) { + $this->testUrl = Router::url($url); + return false; + } +} + +/** +* AuthTest class +* +* @package cake +* @subpackage cake.tests.cases.libs.controller.components +*/ +class AuthTest extends CakeTestCase { + +/** + * name property + * + * @var string 'Auth' + * @access public + */ + var $name = 'Auth'; + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.uuid', 'core.auth_user', 'core.auth_user_custom_field', 'core.aro', 'core.aco', 'core.aros_aco', 'core.aco_action'); + +/** + * initialized property + * + * @var bool false + * @access public + */ + var $initialized = false; + +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->_server = $_SERVER; + $this->_env = $_ENV; + + $this->_securitySalt = Configure::read('Security.salt'); + Configure::write('Security.salt', 'JfIxfs2guVoUubWDYhG93b0qyJfIxfs2guwvniR2G0FgaC9mi'); + + $this->_acl = Configure::read('Acl'); + Configure::write('Acl.database', 'test_suite'); + Configure::write('Acl.classname', 'DbAcl'); + + $this->Controller =& new AuthTestController(); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->beforeFilter(); + + ClassRegistry::addObject('view', new View($this->Controller)); + + $this->Controller->Session->delete('Auth'); + $this->Controller->Session->delete('Message.auth'); + + Router::reload(); + + $this->initialized = true; + } + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + $_SERVER = $this->_server; + $_ENV = $this->_env; + Configure::write('Acl', $this->_acl); + Configure::write('Security.salt', $this->_securitySalt); + + $this->Controller->Session->delete('Auth'); + $this->Controller->Session->delete('Message.auth'); + ClassRegistry::flush(); + unset($this->Controller, $this->AuthUser); + } + +/** + * testNoAuth method + * + * @access public + * @return void + */ + function testNoAuth() { + $this->assertFalse($this->Controller->Auth->isAuthorized()); + } + +/** + * testIsErrorOrTests + * + * @access public + * @return void + */ + function testIsErrorOrTests() { + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->name = 'CakeError'; + $this->assertTrue($this->Controller->Auth->startup($this->Controller)); + + $this->Controller->name = 'Post'; + $this->Controller->params['action'] = 'thisdoesnotexist'; + $this->assertTrue($this->Controller->Auth->startup($this->Controller)); + + $this->Controller->scaffold = null; + $this->Controller->params['action'] = 'index'; + $this->assertFalse($this->Controller->Auth->startup($this->Controller)); + } + +/** + * testIdentify method + * + * @access public + * @return void + */ + function testIdentify() { + $this->AuthUser =& new AuthUser(); + $user['id'] = 1; + $user['username'] = 'mariano'; + $user['password'] = Security::hash(Configure::read('Security.salt') . 'cake'); + $this->AuthUser->save($user, false); + + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($this->Controller->Auth->identify($user)); + } + +/** + * testIdentifyWithConditions method + * + * @access public + * @return void + */ + function testIdentifyWithConditions() { + $this->AuthUser =& new AuthUser(); + $user['id'] = 1; + $user['username'] = 'mariano'; + $user['password'] = Security::hash(Configure::read('Security.salt') . 'cake'); + $this->AuthUser->save($user, false); + + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->startup($this->Controller); + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->assertFalse($this->Controller->Auth->identify($user, array('AuthUser.id >' => 2))); + + $this->Controller->Auth->userScope = array('id >' => 2); + $this->assertFalse($this->Controller->Auth->identify($user)); + $this->assertTrue($this->Controller->Auth->identify($user, false)); + } + +/** + * testLogin method + * + * @access public + * @return void + */ + function testLogin() { + $this->AuthUser =& new AuthUser(); + $user['id'] = 1; + $user['username'] = 'mariano'; + $user['password'] = Security::hash(Configure::read('Security.salt') . 'cake'); + $this->AuthUser->save($user, false); + + $authUser = $this->AuthUser->find(); + + $this->Controller->data['AuthUser']['username'] = $authUser['AuthUser']['username']; + $this->Controller->data['AuthUser']['password'] = 'cake'; + + $this->Controller->params = Router::parse('auth_test/login'); + $this->Controller->params['url']['url'] = 'auth_test/login'; + + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->loginAction = 'auth_test/login'; + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->Controller->Auth->startup($this->Controller); + $user = $this->Controller->Auth->user(); + $expected = array('AuthUser' => array( + 'id' => 1, 'username' => 'mariano', 'created' => '2007-03-17 01:16:23', 'updated' => date('Y-m-d H:i:s') + )); + $this->assertEqual($user, $expected); + $this->Controller->Session->delete('Auth'); + + $this->Controller->data['AuthUser']['username'] = 'blah'; + $this->Controller->data['AuthUser']['password'] = ''; + + $this->Controller->Auth->startup($this->Controller); + + $user = $this->Controller->Auth->user(); + $this->assertFalse($user); + $this->Controller->Session->delete('Auth'); + + $this->Controller->data['AuthUser']['username'] = 'now() or 1=1 --'; + $this->Controller->data['AuthUser']['password'] = ''; + + $this->Controller->Auth->startup($this->Controller); + + $user = $this->Controller->Auth->user(); + $this->assertFalse($user); + $this->Controller->Session->delete('Auth'); + + $this->Controller->data['AuthUser']['username'] = 'now() or 1=1 # something'; + $this->Controller->data['AuthUser']['password'] = ''; + + $this->Controller->Auth->startup($this->Controller); + + $user = $this->Controller->Auth->user(); + $this->assertFalse($user); + $this->Controller->Session->delete('Auth'); + + $this->Controller->Auth->userModel = 'UuidUser'; + $this->Controller->Auth->login('47c36f9c-bc00-4d17-9626-4e183ca6822b'); + + $user = $this->Controller->Auth->user(); + $expected = array('UuidUser' => array( + 'id' => '47c36f9c-bc00-4d17-9626-4e183ca6822b', 'title' => 'Unique record 1', 'count' => 2, 'created' => '2008-03-13 01:16:23', 'updated' => '2008-03-13 01:18:31' + )); + $this->assertEqual($user, $expected); + $this->Controller->Session->delete('Auth'); + } + +/** + * test that being redirected to the login page, with no post data does + * not set the session value. Saving the session value in this circumstance + * can cause the user to be redirected to an already public page. + * + * @return void + */ + function testLoginActionNotSettingAuthRedirect() { + $_referer = $_SERVER['HTTP_REFERER']; + $_SERVER['HTTP_REFERER'] = '/pages/display/about'; + + $this->Controller->data = array(); + $this->Controller->params = Router::parse('auth_test/login'); + $this->Controller->params['url']['url'] = 'auth_test/login'; + $this->Controller->Session->delete('Auth'); + + $this->Controller->Auth->loginRedirect = '/users/dashboard'; + $this->Controller->Auth->loginAction = 'auth_test/login'; + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->Controller->Auth->startup($this->Controller); + $redirect = $this->Controller->Session->read('Auth.redirect'); + $this->assertNull($redirect); + } + +/** + * testAuthorizeFalse method + * + * @access public + * @return void + */ + function testAuthorizeFalse() { + $this->AuthUser =& new AuthUser(); + $user = $this->AuthUser->find(); + $this->Controller->Session->write('Auth', $user); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->authorize = false; + $this->Controller->params = Router::parse('auth_test/add'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($result); + + $this->Controller->Session->delete('Auth'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertFalse($result); + $this->assertTrue($this->Controller->Session->check('Message.auth')); + + $this->Controller->params = Router::parse('auth_test/camelCase'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertFalse($result); + } + +/** + * testAuthorizeController method + * + * @access public + * @return void + */ + function testAuthorizeController() { + $this->AuthUser =& new AuthUser(); + $user = $this->AuthUser->find(); + $this->Controller->Session->write('Auth', $user); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->authorize = 'controller'; + $this->Controller->params = Router::parse('auth_test/add'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($result); + + $this->Controller->params['testControllerAuth'] = 1; + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($this->Controller->Session->check('Message.auth')); + $this->assertFalse($result); + + $this->Controller->Session->delete('Auth'); + } + +/** + * testAuthorizeModel method + * + * @access public + * @return void + */ + function testAuthorizeModel() { + $this->AuthUser =& new AuthUser(); + $user = $this->AuthUser->find(); + $this->Controller->Session->write('Auth', $user); + + $this->Controller->params['controller'] = 'auth_test'; + $this->Controller->params['action'] = 'add'; + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->authorize = array('model'=>'AuthUser'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($result); + + $this->Controller->Session->delete('Auth'); + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($this->Controller->Session->check('Message.auth')); + $result = $this->Controller->Auth->isAuthorized(); + $this->assertFalse($result); + } + +/** + * testAuthorizeCrud method + * + * @access public + * @return void + */ + function testAuthorizeCrud() { + $this->AuthUser =& new AuthUser(); + $user = $this->AuthUser->find(); + $this->Controller->Session->write('Auth', $user); + + $this->Controller->params['controller'] = 'auth_test'; + $this->Controller->params['action'] = 'add'; + + $this->Controller->Acl->name = 'DbAclTest'; + + $this->Controller->Acl->Aro->id = null; + $this->Controller->Acl->Aro->create(array('alias' => 'Roles')); + $result = $this->Controller->Acl->Aro->save(); + $this->assertTrue($result); + + $parent = $this->Controller->Acl->Aro->id; + + $this->Controller->Acl->Aro->create(array('parent_id' => $parent, 'alias' => 'Admin')); + $result = $this->Controller->Acl->Aro->save(); + $this->assertTrue($result); + + $parent = $this->Controller->Acl->Aro->id; + + $this->Controller->Acl->Aro->create(array( + 'model' => 'AuthUser', 'parent_id' => $parent, 'foreign_key' => 1, 'alias'=> 'mariano' + )); + $result = $this->Controller->Acl->Aro->save(); + $this->assertTrue($result); + + $this->Controller->Acl->Aco->create(array('alias' => 'Root')); + $result = $this->Controller->Acl->Aco->save(); + $this->assertTrue($result); + + $parent = $this->Controller->Acl->Aco->id; + + $this->Controller->Acl->Aco->create(array('parent_id' => $parent, 'alias' => 'AuthTest')); + $result = $this->Controller->Acl->Aco->save(); + $this->assertTrue($result); + + $this->Controller->Acl->allow('Roles/Admin', 'Root'); + $this->Controller->Acl->allow('Roles/Admin', 'Root/AuthTest'); + + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->authorize = 'crud'; + $this->Controller->Auth->actionPath = 'Root/'; + + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($this->Controller->Auth->isAuthorized()); + + $this->Controller->Session->delete('Auth'); + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($this->Controller->Session->check('Message.auth')); + } + +/** + * test authorize = 'actions' setting. + * + * @return void + */ + function testAuthorizeActions() { + $this->AuthUser =& new AuthUser(); + $user = $this->AuthUser->find(); + $this->Controller->Session->write('Auth', $user); + $this->Controller->params['controller'] = 'auth_test'; + $this->Controller->params['action'] = 'add'; + + $this->Controller->Acl =& new AuthTestMockAclComponent(); + $this->Controller->Acl->setReturnValue('check', true); + + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->authorize = 'actions'; + $this->Controller->Auth->actionPath = 'Root/'; + + $this->Controller->Acl->expectAt(0, 'check', array( + $user, 'Root/AuthTest/add' + )); + + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($this->Controller->Auth->isAuthorized()); + } + +/** + * Tests that deny always takes precedence over allow + * + * @access public + * @return void + */ + function testAllowDenyAll() { + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->allow('*'); + $this->Controller->Auth->deny('add', 'camelcase'); + + $this->Controller->params['action'] = 'delete'; + $this->assertTrue($this->Controller->Auth->startup($this->Controller)); + + $this->Controller->params['action'] = 'add'; + $this->assertFalse($this->Controller->Auth->startup($this->Controller)); + + $this->Controller->params['action'] = 'Add'; + $this->assertFalse($this->Controller->Auth->startup($this->Controller)); + + $this->Controller->params['action'] = 'camelCase'; + $this->assertFalse($this->Controller->Auth->startup($this->Controller)); + + $this->Controller->Auth->allow('*'); + $this->Controller->Auth->deny(array('add', 'camelcase')); + + $this->Controller->params['action'] = 'camelCase'; + $this->assertFalse($this->Controller->Auth->startup($this->Controller)); + } + +/** + * test the action() method + * + * @return void + */ + function testActionMethod() { + $this->Controller->params['controller'] = 'auth_test'; + $this->Controller->params['action'] = 'add'; + + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->actionPath = 'ROOT/'; + + $result = $this->Controller->Auth->action(); + $this->assertEqual($result, 'ROOT/AuthTest/add'); + + $result = $this->Controller->Auth->action(':controller'); + $this->assertEqual($result, 'ROOT/AuthTest'); + + $result = $this->Controller->Auth->action(':controller'); + $this->assertEqual($result, 'ROOT/AuthTest'); + + $this->Controller->params['plugin'] = 'test_plugin'; + $this->Controller->params['controller'] = 'auth_test'; + $this->Controller->params['action'] = 'add'; + $this->Controller->Auth->initialize($this->Controller); + $result = $this->Controller->Auth->action(); + $this->assertEqual($result, 'ROOT/TestPlugin/AuthTest/add'); + } + +/** + * test that deny() converts camel case inputs to lowercase. + * + * @return void + */ + function testDenyWithCamelCaseMethods() { + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->allow('*'); + $this->Controller->Auth->deny('add', 'camelCase'); + + $url = '/auth_test/camelCase'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + + $this->assertFalse($this->Controller->Auth->startup($this->Controller)); + } + +/** + * test that allow() and allowedActions work with camelCase method names. + * + * @return void + */ + function testAllowedActionsWithCamelCaseMethods() { + $url = '/auth_test/camelCase'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->allow('*'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($result, 'startup() should return true, as action is allowed. %s'); + + $url = '/auth_test/camelCase'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->allowedActions = array('delete', 'camelCase', 'add'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($result, 'startup() should return true, as action is allowed. %s'); + + $this->Controller->Auth->allowedActions = array('delete', 'add'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertFalse($result, 'startup() should return false, as action is not allowed. %s'); + + $url = '/auth_test/delete'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->Controller->Auth->allow(array('delete', 'add')); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($result, 'startup() should return true, as action is allowed. %s'); + } + + function testAllowedActionsSetWithAllowMethod() { + $url = '/auth_test/action_name'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->allow('action_name', 'anotherAction'); + $this->assertEqual($this->Controller->Auth->allowedActions, array('action_name', 'anotheraction')); + } + +/** + * testLoginRedirect method + * + * @access public + * @return void + */ + function testLoginRedirect() { + if (isset($_SERVER['HTTP_REFERER'])) { + $backup = $_SERVER['HTTP_REFERER']; + } else { + $backup = null; + } + + $_SERVER['HTTP_REFERER'] = false; + + $this->Controller->Session->write('Auth', array( + 'AuthUser' => array('id' => '1', 'username' => 'nate') + )); + + $this->Controller->params = Router::parse('users/login'); + $this->Controller->params['url']['url'] = 'users/login'; + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->loginRedirect = array( + 'controller' => 'pages', 'action' => 'display', 'welcome' + ); + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize($this->Controller->Auth->loginRedirect); + $this->assertEqual($expected, $this->Controller->Auth->redirect()); + + $this->Controller->Session->delete('Auth'); + + $this->Controller->params['url']['url'] = 'admin/'; + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->loginRedirect = null; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('admin/'); + $this->assertTrue($this->Controller->Session->check('Message.auth')); + $this->assertEqual($expected, $this->Controller->Auth->redirect()); + + $this->Controller->Session->delete('Auth'); + + //empty referer no session + $_SERVER['HTTP_REFERER'] = false; + $_ENV['HTTP_REFERER'] = false; + putenv('HTTP_REFERER='); + $url = '/posts/view/1'; + + $this->Controller->Session->write('Auth', array( + 'AuthUser' => array('id' => '1', 'username' => 'nate')) + ); + $this->Controller->testUrl = null; + $this->Controller->params = Router::parse($url); + array_push($this->Controller->methods, 'view', 'edit', 'index'); + + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->authorize = 'controller'; + $this->Controller->params['testControllerAuth'] = true; + + $this->Controller->Auth->loginAction = array( + 'controller' => 'AuthTest', 'action' => 'login' + ); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('/'); + $this->assertEqual($expected, $this->Controller->testUrl); + + + $this->Controller->Session->delete('Auth'); + $_SERVER['HTTP_REFERER'] = Router::url('/admin/', true); + + $this->Controller->Session->write('Auth', array( + 'AuthUser' => array('id'=>'1', 'username'=>'nate')) + ); + $this->Controller->params['url']['url'] = 'auth_test/login'; + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = 'auth_test/login'; + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->loginRedirect = false; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('/admin'); + $this->assertEqual($expected, $this->Controller->Auth->redirect()); + + //Ticket #4750 + //named params + $this->Controller->Session->delete('Auth'); + $url = '/posts/index/year:2008/month:feb'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('posts/index/year:2008/month:feb'); + $this->assertEqual($expected, $this->Controller->Session->read('Auth.redirect')); + + //passed args + $this->Controller->Session->delete('Auth'); + $url = '/posts/view/1'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('posts/view/1'); + $this->assertEqual($expected, $this->Controller->Session->read('Auth.redirect')); + + // QueryString parameters + $_back = $_GET; + $_GET = array( + 'url' => '/posts/index/29', + 'print' => 'true', + 'refer' => 'menu' + ); + $this->Controller->Session->delete('Auth'); + $url = '/posts/index/29?print=true&refer=menu'; + $this->Controller->params = Dispatcher::parseParams($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('posts/index/29?print=true&refer=menu'); + $this->assertEqual($expected, $this->Controller->Session->read('Auth.redirect')); + + $_GET = array( + 'url' => '/posts/index/29', + 'print' => 'true', + 'refer' => 'menu', + 'ext' => 'html' + ); + $this->Controller->Session->delete('Auth'); + $url = '/posts/index/29?print=true&refer=menu'; + $this->Controller->params = Dispatcher::parseParams($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('posts/index/29?print=true&refer=menu'); + $this->assertEqual($expected, $this->Controller->Session->read('Auth.redirect')); + $_GET = $_back; + + //external authed action + $_SERVER['HTTP_REFERER'] = 'http://webmail.example.com/view/message'; + $this->Controller->Session->delete('Auth'); + $url = '/posts/edit/1'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('/posts/edit/1'); + $this->assertEqual($expected, $this->Controller->Session->read('Auth.redirect')); + + //external direct login link + $_SERVER['HTTP_REFERER'] = 'http://webmail.example.com/view/message'; + $this->Controller->Session->delete('Auth'); + $url = '/AuthTest/login'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = Router::normalize($url); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'AuthTest', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $expected = Router::normalize('/'); + $this->assertEqual($expected, $this->Controller->Session->read('Auth.redirect')); + + $_SERVER['HTTP_REFERER'] = $backup; + $this->Controller->Session->delete('Auth'); + } + +/** + * Ensure that no redirect is performed when a 404 is reached + * And the user doesn't have a session. + * + * @return void + */ + function testNoRedirectOn404() { + $this->Controller->Session->delete('Auth'); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->params = Router::parse('auth_test/something_totally_wrong'); + $result = $this->Controller->Auth->startup($this->Controller); + $this->assertTrue($result, 'Auth redirected a missing action %s'); + } + +/** + * testEmptyUsernameOrPassword method + * + * @access public + * @return void + */ + function testEmptyUsernameOrPassword() { + $this->AuthUser =& new AuthUser(); + $user['id'] = 1; + $user['username'] = 'mariano'; + $user['password'] = Security::hash(Configure::read('Security.salt') . 'cake'); + $this->AuthUser->save($user, false); + + $authUser = $this->AuthUser->find(); + + $this->Controller->data['AuthUser']['username'] = ''; + $this->Controller->data['AuthUser']['password'] = ''; + + $this->Controller->params = Router::parse('auth_test/login'); + $this->Controller->params['url']['url'] = 'auth_test/login'; + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = 'auth_test/login'; + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->Controller->Auth->startup($this->Controller); + $user = $this->Controller->Auth->user(); + $this->assertTrue($this->Controller->Session->check('Message.auth')); + $this->assertEqual($user, false); + $this->Controller->Session->delete('Auth'); + } + +/** + * testInjection method + * + * @access public + * @return void + */ + function testInjection() { + $this->AuthUser =& new AuthUser(); + $this->AuthUser->id = 2; + $this->AuthUser->saveField('password', Security::hash(Configure::read('Security.salt') . 'cake')); + + $this->Controller->data['AuthUser']['username'] = 'nate'; + $this->Controller->data['AuthUser']['password'] = 'cake'; + $this->Controller->params = Router::parse('auth_test/login'); + $this->Controller->params['url']['url'] = 'auth_test/login'; + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->loginAction = 'auth_test/login'; + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue(is_array($this->Controller->Auth->user())); + + $this->Controller->Session->delete($this->Controller->Auth->sessionKey); + + $this->Controller->data['AuthUser']['username'] = 'nate'; + $this->Controller->data['AuthUser']['password'] = 'cake1'; + $this->Controller->params['url']['url'] = 'auth_test/login'; + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->loginAction = 'auth_test/login'; + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue(is_null($this->Controller->Auth->user())); + + $this->Controller->Session->delete($this->Controller->Auth->sessionKey); + + $this->Controller->data['AuthUser']['username'] = '> n'; + $this->Controller->data['AuthUser']['password'] = 'cake'; + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue(is_null($this->Controller->Auth->user())); + + unset($this->Controller->data['AuthUser']['password']); + $this->Controller->data['AuthUser']['username'] = "1'1"; + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue(is_null($this->Controller->Auth->user())); + + unset($this->Controller->data['AuthUser']['username']); + $this->Controller->data['AuthUser']['password'] = "1'1"; + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->startup($this->Controller); + $this->assertTrue(is_null($this->Controller->Auth->user())); + } + +/** + * test Hashing of passwords + * + * @return void + */ + function testHashPasswords() { + $this->Controller->Auth->userModel = 'AuthUser'; + + $data['AuthUser']['password'] = 'superSecret'; + $data['AuthUser']['username'] = 'superman@dailyplanet.com'; + $return = $this->Controller->Auth->hashPasswords($data); + $expected = $data; + $expected['AuthUser']['password'] = Security::hash($expected['AuthUser']['password'], null, true); + $this->assertEqual($return, $expected); + + $data['Wrong']['password'] = 'superSecret'; + $data['Wrong']['username'] = 'superman@dailyplanet.com'; + $data['AuthUser']['password'] = 'IcantTellYou'; + $return = $this->Controller->Auth->hashPasswords($data); + $expected = $data; + $expected['AuthUser']['password'] = Security::hash($expected['AuthUser']['password'], null, true); + $this->assertEqual($return, $expected); + + if (PHP5) { + $xml = array( + 'User' => array( + 'username' => 'batman@batcave.com', + 'password' => 'bruceWayne', + ) + ); + $data =& new Xml($xml); + $return = $this->Controller->Auth->hashPasswords($data); + $expected = $data; + $this->assertEqual($return, $expected); + } + } + +/** + * testCustomRoute method + * + * @access public + * @return void + */ + function testCustomRoute() { + Router::reload(); + Router::connect( + '/:lang/:controller/:action/*', + array('lang' => null), + array('lang' => '[a-z]{2,3}') + ); + + $url = '/en/users/login'; + $this->Controller->params = Router::parse($url); + Router::setRequestInfo(array($this->Controller->passedArgs, array( + 'base' => null, 'here' => $url, 'webroot' => '/', 'passedArgs' => array(), + 'argSeparator' => ':', 'namedArgs' => array() + ))); + + $this->AuthUser =& new AuthUser(); + $user = array( + 'id' => 1, 'username' => 'felix', + 'password' => Security::hash(Configure::read('Security.salt') . 'cake' + )); + $user = $this->AuthUser->save($user, false); + + $this->Controller->data['AuthUser'] = array('username' => 'felix', 'password' => 'cake'); + $this->Controller->params['url']['url'] = substr($url, 1); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('lang' => 'en', 'controller' => 'users', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->Controller->Auth->startup($this->Controller); + $user = $this->Controller->Auth->user(); + $this->assertTrue(!!$user); + + $this->Controller->Session->delete('Auth'); + Router::reload(); + Router::connect('/', array('controller' => 'people', 'action' => 'login')); + $url = '/'; + $this->Controller->params = Router::parse($url); + Router::setRequestInfo(array($this->Controller->passedArgs, array( + 'base' => null, 'here' => $url, 'webroot' => '/', 'passedArgs' => array(), + 'argSeparator' => ':', 'namedArgs' => array() + ))); + $this->Controller->data['AuthUser'] = array('username' => 'felix', 'password' => 'cake'); + $this->Controller->params['url']['url'] = substr($url, 1); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->loginAction = array('controller' => 'people', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->Controller->Auth->startup($this->Controller); + $user = $this->Controller->Auth->user(); + $this->assertTrue(!!$user); + } + +/** + * testCustomField method + * + * @access public + * @return void + */ + function testCustomField() { + Router::reload(); + + $this->AuthUserCustomField =& new AuthUserCustomField(); + $user = array( + 'id' => 1, 'email' => 'harking@example.com', + 'password' => Security::hash(Configure::read('Security.salt') . 'cake' + )); + $user = $this->AuthUserCustomField->save($user, false); + + Router::connect('/', array('controller' => 'people', 'action' => 'login')); + $url = '/'; + $this->Controller->params = Router::parse($url); + Router::setRequestInfo(array($this->Controller->passedArgs, array( + 'base' => null, 'here' => $url, 'webroot' => '/', 'passedArgs' => array(), + 'argSeparator' => ':', 'namedArgs' => array() + ))); + $this->Controller->data['AuthUserCustomField'] = array('email' => 'harking@example.com', 'password' => 'cake'); + $this->Controller->params['url']['url'] = substr($url, 1); + $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->fields = array('username' => 'email', 'password' => 'password'); + $this->Controller->Auth->loginAction = array('controller' => 'people', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUserCustomField'; + + $this->Controller->Auth->startup($this->Controller); + $user = $this->Controller->Auth->user(); + $this->assertTrue(!!$user); + } + +/** + * testAdminRoute method + * + * @access public + * @return void + */ + function testAdminRoute() { + $prefixes = Configure::read('Routing.prefixes'); + Configure::write('Routing.prefixes', array('admin')); + Router::reload(); + + $url = '/admin/auth_test/add'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = ltrim($url, '/'); + Router::setRequestInfo(array( + array( + 'pass' => array(), 'action' => 'add', 'plugin' => null, + 'controller' => 'auth_test', 'admin' => true, + 'url' => array('url' => $this->Controller->params['url']['url']) + ), + array( + 'base' => null, 'here' => $url, + 'webroot' => '/', 'passedArgs' => array(), + ) + )); + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->loginAction = array( + 'admin' => true, 'controller' => 'auth_test', 'action' => 'login' + ); + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->Controller->Auth->startup($this->Controller); + $this->assertEqual($this->Controller->testUrl, '/admin/auth_test/login'); + + Configure::write('Routing.prefixes', $prefixes); + } + +/** + * testPluginModel method + * + * @access public + * @return void + */ + function testPluginModel() { + // Adding plugins + Cache::delete('object_map', '_cake_core_'); + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'models' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS) + ), true); + App::objects('plugin', null, false); + + $PluginModel =& ClassRegistry::init('TestPlugin.TestPluginAuthUser'); + $user['id'] = 1; + $user['username'] = 'gwoo'; + $user['password'] = Security::hash(Configure::read('Security.salt') . 'cake'); + $PluginModel->save($user, false); + + $authUser = $PluginModel->find(); + + $this->Controller->data['TestPluginAuthUser']['username'] = $authUser['TestPluginAuthUser']['username']; + $this->Controller->data['TestPluginAuthUser']['password'] = 'cake'; + + $this->Controller->params = Router::parse('auth_test/login'); + $this->Controller->params['url']['url'] = 'auth_test/login'; + + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->loginAction = 'auth_test/login'; + $this->Controller->Auth->userModel = 'TestPlugin.TestPluginAuthUser'; + + $this->Controller->Auth->startup($this->Controller); + $user = $this->Controller->Auth->user(); + $expected = array('TestPluginAuthUser' => array( + 'id' => 1, 'username' => 'gwoo', 'created' => '2007-03-17 01:16:23', 'updated' => date('Y-m-d H:i:s') + )); + $this->assertEqual($user, $expected); + $sessionKey = $this->Controller->Auth->sessionKey; + $this->assertEqual('Auth.TestPluginAuthUser', $sessionKey); + + $this->Controller->Auth->loginAction = null; + $this->Controller->Auth->__setDefaults(); + $loginAction = $this->Controller->Auth->loginAction; + $expected = array( + 'controller' => 'test_plugin_auth_users', + 'action' => 'login', + 'plugin' => 'test_plugin' + ); + $this->assertEqual($loginAction, $expected); + + // Reverting changes + Cache::delete('object_map', '_cake_core_'); + App::build(); + App::objects('plugin', null, false); + } + +/** + * testAjaxLogin method + * + * @access public + * @return void + */ + function testAjaxLogin() { + App::build(array('views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS))); + $_SERVER['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest"; + + if (!class_exists('dispatcher')) { + require CAKE . 'dispatcher.php'; + } + + ob_start(); + $Dispatcher =& new Dispatcher(); + $Dispatcher->dispatch('/ajax_auth/add', array('return' => 1)); + $result = ob_get_clean(); + $this->assertEqual("Ajax!\nthis is the test element", str_replace("\r\n", "\n", $result)); + unset($_SERVER['HTTP_X_REQUESTED_WITH']); + } + +/** + * testLoginActionRedirect method + * + * @access public + * @return void + */ + function testLoginActionRedirect() { + $admin = Configure::read('Routing.admin'); + Configure::write('Routing.admin', 'admin'); + Router::reload(); + + $url = '/admin/auth_test/login'; + $this->Controller->params = Router::parse($url); + $this->Controller->params['url']['url'] = ltrim($url, '/'); + Router::setRequestInfo(array( + array( + 'pass' => array(), 'action' => 'admin_login', 'plugin' => null, 'controller' => 'auth_test', + 'admin' => true, 'url' => array('url' => $this->Controller->params['url']['url']), + ), + array( + 'base' => null, 'here' => $url, + 'webroot' => '/', 'passedArgs' => array(), + ) + )); + + $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->loginAction = array('admin' => true, 'controller' => 'auth_test', 'action' => 'login'); + $this->Controller->Auth->userModel = 'AuthUser'; + + $this->Controller->Auth->startup($this->Controller); + + $this->assertNull($this->Controller->testUrl); + + Configure::write('Routing.admin', $admin); + } + +/** + * Tests that shutdown destroys the redirect session var + * + * @access public + * @return void + */ + function testShutDown() { + $this->Controller->Session->write('Auth.redirect', 'foo'); + $this->Controller->Auth->_loggedIn = true; + $this->Controller->Auth->shutdown($this->Controller); + $this->assertFalse($this->Controller->Session->read('Auth.redirect')); + } + +/** + * test the initialize callback and its interactions with Router::prefixes() + * + * @return void + */ + function testInitializeAndRoutingPrefixes() { + $restore = Configure::read('Routing'); + Configure::write('Routing.prefixes', array('admin', 'super_user')); + Router::reload(); + $this->Controller->Auth->initialize($this->Controller); + + $this->assertTrue(isset($this->Controller->Auth->actionMap['delete'])); + $this->assertTrue(isset($this->Controller->Auth->actionMap['view'])); + $this->assertTrue(isset($this->Controller->Auth->actionMap['add'])); + $this->assertTrue(isset($this->Controller->Auth->actionMap['admin_view'])); + $this->assertTrue(isset($this->Controller->Auth->actionMap['super_user_delete'])); + + Configure::write('Routing', $restore); + } + +/** + * test $settings in Controller::$components + * + * @access public + * @return void + */ + function testComponentSettings() { + $this->Controller =& new AuthTestController(); + $this->Controller->components = array( + 'Auth' => array( + 'fields' => array('username' => 'email', 'password' => 'password'), + 'loginAction' => array('controller' => 'people', 'action' => 'login'), + 'userModel' => 'AuthUserCustomField', + 'sessionKey' => 'AltAuth.AuthUserCustomField' + ), + 'Session' + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + Router::reload(); + + $this->AuthUserCustomField =& new AuthUserCustomField(); + $user = array( + 'id' => 1, 'email' => 'harking@example.com', + 'password' => Security::hash(Configure::read('Security.salt') . 'cake' + )); + $user = $this->AuthUserCustomField->save($user, false); + + Router::connect('/', array('controller' => 'people', 'action' => 'login')); + $url = '/'; + $this->Controller->params = Router::parse($url); + Router::setRequestInfo(array($this->Controller->passedArgs, array( + 'base' => null, 'here' => $url, 'webroot' => '/', 'passedArgs' => array(), + 'argSeparator' => ':', 'namedArgs' => array() + ))); + $this->Controller->data['AuthUserCustomField'] = array('email' => 'harking@example.com', 'password' => 'cake'); + $this->Controller->params['url']['url'] = substr($url, 1); + $this->Controller->Auth->startup($this->Controller); + + $user = $this->Controller->Auth->user(); + $this->assertTrue(!!$user); + + $expected = array( + 'fields' => array('username' => 'email', 'password' => 'password'), + 'loginAction' => array('controller' => 'people', 'action' => 'login'), + 'logoutRedirect' => array('controller' => 'people', 'action' => 'login'), + 'userModel' => 'AuthUserCustomField', + 'sessionKey' => 'AltAuth.AuthUserCustomField' + ); + $this->assertEqual($expected['fields'], $this->Controller->Auth->fields); + $this->assertEqual($expected['loginAction'], $this->Controller->Auth->loginAction); + $this->assertEqual($expected['logoutRedirect'], $this->Controller->Auth->logoutRedirect); + $this->assertEqual($expected['userModel'], $this->Controller->Auth->userModel); + $this->assertEqual($expected['sessionKey'], $this->Controller->Auth->sessionKey); + } + +/** + * test that logout deletes the session variables. and returns the correct url + * + * @return void + */ + function testLogout() { + $this->Controller->Session->write('Auth.User.id', '1'); + $this->Controller->Session->write('Auth.redirect', '/users/login'); + $this->Controller->Auth->logoutRedirect = '/'; + $result = $this->Controller->Auth->logout(); + + $this->assertEqual($result, '/'); + $this->assertNull($this->Controller->Session->read('Auth.AuthUser')); + $this->assertNull($this->Controller->Session->read('Auth.redirect')); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/cookie.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/cookie.test.php new file mode 100644 index 000000000..868eee124 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/cookie.test.php @@ -0,0 +1,499 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + * @since CakePHP(tm) v 1.2.0.5435 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Controller', array('Component', 'Controller'), false); +App::import('Component', 'Cookie'); + +/** + * CookieComponentTestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class CookieComponentTestController extends Controller { + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Cookie'); + +/** + * beforeFilter method + * + * @access public + * @return void + */ + function beforeFilter() { + $this->Cookie->name = 'CakeTestCookie'; + $this->Cookie->time = 10; + $this->Cookie->path = '/'; + $this->Cookie->domain = ''; + $this->Cookie->secure = false; + $this->Cookie->key = 'somerandomhaskey'; + } +} + +/** + * CookieComponentTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class CookieComponentTest extends CakeTestCase { + +/** + * Controller property + * + * @var CookieComponentTestController + * @access public + */ + var $Controller; + +/** + * start + * + * @access public + * @return void + */ + function start() { + $this->Controller = new CookieComponentTestController(); + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->beforeFilter(); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Cookie->destroy(); + } + +/** + * end + * + * @access public + * @return void + */ + function end() { + $this->Controller->Cookie->destroy(); + } + +/** + * test that initialize sets settings from components array + * + * @return void + */ + function testInitialize() { + $settings = array( + 'time' => '5 days', + 'path' => '/' + ); + $this->Controller->Cookie->initialize($this->Controller, $settings); + $this->assertEqual($this->Controller->Cookie->time, $settings['time']); + $this->assertEqual($this->Controller->Cookie->path, $settings['path']); + } + +/** + * testCookieName + * + * @access public + * @return void + */ + function testCookieName() { + $this->assertEqual($this->Controller->Cookie->name, 'CakeTestCookie'); + } + +/** + * testSettingEncryptedCookieData + * + * @access public + * @return void + */ + function testSettingEncryptedCookieData() { + $this->Controller->Cookie->write('Encrytped_array', array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!')); + $this->Controller->Cookie->write('Encrytped_multi_cookies.name', 'CakePHP'); + $this->Controller->Cookie->write('Encrytped_multi_cookies.version', '1.2.0.x'); + $this->Controller->Cookie->write('Encrytped_multi_cookies.tag', 'CakePHP Rocks!'); + } + +/** + * testReadEncryptedCookieData + * + * @access public + * @return void + */ + function testReadEncryptedCookieData() { + $data = $this->Controller->Cookie->read('Encrytped_array'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + } + +/** + * testSettingPlainCookieData + * + * @access public + * @return void + */ + function testSettingPlainCookieData() { + $this->Controller->Cookie->write('Plain_array', array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'), false); + $this->Controller->Cookie->write('Plain_multi_cookies.name', 'CakePHP', false); + $this->Controller->Cookie->write('Plain_multi_cookies.version', '1.2.0.x', false); + $this->Controller->Cookie->write('Plain_multi_cookies.tag', 'CakePHP Rocks!', false); + } + +/** + * testReadPlainCookieData + * + * @access public + * @return void + */ + function testReadPlainCookieData() { + $data = $this->Controller->Cookie->read('Plain_array'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_multi_cookies'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + } + +/** + * testWritePlainCookieArray + * + * @access public + * @return void + */ + function testWritePlainCookieArray() { + $this->Controller->Cookie->write(array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' => 'CakePHP Rocks!'), null, false); + + $this->assertEqual($this->Controller->Cookie->read('name'), 'CakePHP'); + $this->assertEqual($this->Controller->Cookie->read('version'), '1.2.0.x'); + $this->assertEqual($this->Controller->Cookie->read('tag'), 'CakePHP Rocks!'); + + $this->Controller->Cookie->delete('name'); + $this->Controller->Cookie->delete('version'); + $this->Controller->Cookie->delete('tag'); + } + +/** + * testReadingCookieValue + * + * @access public + * @return void + */ + function testReadingCookieValue() { + $data = $this->Controller->Cookie->read(); + $expected = array( + 'Encrytped_array' => array( + 'name' => 'CakePHP', + 'version' => '1.2.0.x', + 'tag' => 'CakePHP Rocks!'), + 'Encrytped_multi_cookies' => array( + 'name' => 'CakePHP', + 'version' => '1.2.0.x', + 'tag' => 'CakePHP Rocks!'), + 'Plain_array' => array( + 'name' => 'CakePHP', + 'version' => '1.2.0.x', + 'tag' => 'CakePHP Rocks!'), + 'Plain_multi_cookies' => array( + 'name' => 'CakePHP', + 'version' => '1.2.0.x', + 'tag' => 'CakePHP Rocks!')); + $this->assertEqual($data, $expected); + } + +/** + * testDeleteCookieValue + * + * @access public + * @return void + */ + function testDeleteCookieValue() { + $this->Controller->Cookie->delete('Encrytped_multi_cookies.name'); + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies'); + $expected = array('version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $this->Controller->Cookie->delete('Encrytped_array'); + $data = $this->Controller->Cookie->read('Encrytped_array'); + $expected = array(); + $this->assertEqual($data, $expected); + + $this->Controller->Cookie->delete('Plain_multi_cookies.name'); + $data = $this->Controller->Cookie->read('Plain_multi_cookies'); + $expected = array('version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $this->Controller->Cookie->delete('Plain_array'); + $data = $this->Controller->Cookie->read('Plain_array'); + $expected = array(); + $this->assertEqual($data, $expected); + } + +/** + * testSettingCookiesWithArray + * + * @access public + * @return void + */ + function testSettingCookiesWithArray() { + $this->Controller->Cookie->destroy(); + + $this->Controller->Cookie->write(array('Encrytped_array' => array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'))); + $this->Controller->Cookie->write(array('Encrytped_multi_cookies.name' => 'CakePHP')); + $this->Controller->Cookie->write(array('Encrytped_multi_cookies.version' => '1.2.0.x')); + $this->Controller->Cookie->write(array('Encrytped_multi_cookies.tag' => 'CakePHP Rocks!')); + + $this->Controller->Cookie->write(array('Plain_array' => array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!')), null, false); + $this->Controller->Cookie->write(array('Plain_multi_cookies.name' => 'CakePHP'), null, false); + $this->Controller->Cookie->write(array('Plain_multi_cookies.version' => '1.2.0.x'), null, false); + $this->Controller->Cookie->write(array('Plain_multi_cookies.tag' => 'CakePHP Rocks!'), null, false); + } + +/** + * testReadingCookieArray + * + * @access public + * @return void + */ + function testReadingCookieArray() { + $data = $this->Controller->Cookie->read('Encrytped_array.name'); + $expected = 'CakePHP'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_array.version'); + $expected = '1.2.0.x'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_array.tag'); + $expected = 'CakePHP Rocks!'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies.name'); + $expected = 'CakePHP'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies.version'); + $expected = '1.2.0.x'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies.tag'); + $expected = 'CakePHP Rocks!'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_array.name'); + $expected = 'CakePHP'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_array.version'); + $expected = '1.2.0.x'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_array.tag'); + $expected = 'CakePHP Rocks!'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_multi_cookies.name'); + $expected = 'CakePHP'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_multi_cookies.version'); + $expected = '1.2.0.x'; + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_multi_cookies.tag'); + $expected = 'CakePHP Rocks!'; + $this->assertEqual($data, $expected); + } + +/** + * testReadingCookieDataOnStartup + * + * @access public + * @return void + */ + function testReadingCookieDataOnStartup() { + $this->Controller->Cookie->destroy(); + + $data = $this->Controller->Cookie->read('Encrytped_array'); + $expected = array(); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies'); + $expected = array(); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_array'); + $expected = array(); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_multi_cookies'); + $expected = array(); + $this->assertEqual($data, $expected); + + $_COOKIE['CakeTestCookie'] = array( + 'Encrytped_array' => $this->__encrypt(array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!')), + 'Encrytped_multi_cookies' => array( + 'name' => $this->__encrypt('CakePHP'), + 'version' => $this->__encrypt('1.2.0.x'), + 'tag' => $this->__encrypt('CakePHP Rocks!')), + 'Plain_array' => 'name|CakePHP,version|1.2.0.x,tag|CakePHP Rocks!', + 'Plain_multi_cookies' => array( + 'name' => 'CakePHP', + 'version' => '1.2.0.x', + 'tag' => 'CakePHP Rocks!')); + $this->Controller->Cookie->startup(); + + $data = $this->Controller->Cookie->read('Encrytped_array'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_array'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_multi_cookies'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + $this->Controller->Cookie->destroy(); + unset($_COOKIE['CakeTestCookie']); + } + +/** + * testReadingCookieDataWithoutStartup + * + * @access public + * @return void + */ + function testReadingCookieDataWithoutStartup() { + $data = $this->Controller->Cookie->read('Encrytped_array'); + $expected = array(); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies'); + $expected = array(); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_array'); + $expected = array(); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_multi_cookies'); + $expected = array(); + $this->assertEqual($data, $expected); + + $_COOKIE['CakeTestCookie'] = array( + 'Encrytped_array' => $this->__encrypt(array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!')), + 'Encrytped_multi_cookies' => array( + 'name' => $this->__encrypt('CakePHP'), + 'version' => $this->__encrypt('1.2.0.x'), + 'tag' => $this->__encrypt('CakePHP Rocks!')), + 'Plain_array' => 'name|CakePHP,version|1.2.0.x,tag|CakePHP Rocks!', + 'Plain_multi_cookies' => array( + 'name' => 'CakePHP', + 'version' => '1.2.0.x', + 'tag' => 'CakePHP Rocks!')); + + $data = $this->Controller->Cookie->read('Encrytped_array'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Encrytped_multi_cookies'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_array'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + + $data = $this->Controller->Cookie->read('Plain_multi_cookies'); + $expected = array('name' => 'CakePHP', 'version' => '1.2.0.x', 'tag' =>'CakePHP Rocks!'); + $this->assertEqual($data, $expected); + $this->Controller->Cookie->destroy(); + unset($_COOKIE['CakeTestCookie']); + } + + +/** + * test that no error is issued for non array data. + * + * @return void + */ + function testNoErrorOnNonArrayData() { + $this->Controller->Cookie->destroy(); + $_COOKIE['CakeTestCookie'] = 'kaboom'; + + $this->assertNull($this->Controller->Cookie->read('value')); + } + +/** + * test that deleting a top level keys kills the child elements too. + * + * @return void + */ + function testDeleteRemovesChildren() { + $_COOKIE['CakeTestCookie'] = array( + 'User' => array('email' => 'example@example.com', 'name' => 'mark'), + 'other' => 'value' + ); + $this->Controller->Cookie->startup(); + $this->assertEqual('mark', $this->Controller->Cookie->read('User.name')); + + $this->Controller->Cookie->delete('User'); + $this->assertFalse($this->Controller->Cookie->read('User.email')); + $this->Controller->Cookie->destroy(); + } + +/** + * encrypt method + * + * @param mixed $value + * @return string + * @access private + */ + function __encrypt($value) { + if (is_array($value)) { + $value = $this->__implode($value); + } + return "Q2FrZQ==." . base64_encode(Security::cipher($value, $this->Controller->Cookie->key)); + } + +/** + * implode method + * + * @param array $value + * @return string + * @access private + */ + function __implode($array) { + $string = ''; + foreach ($array as $key => $value) { + $string .= ',' . $key . '|' . $value; + } + return substr($string, 1); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/email.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/email.test.php new file mode 100755 index 000000000..78fb7b45c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/email.test.php @@ -0,0 +1,1310 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.cake.tests.cases.libs.controller.components + * @since CakePHP(tm) v 1.2.0.5347 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Component', 'Email'); +App::import('Core', 'CakeSocket'); + +Mock::generate('CakeSocket', 'MockEmailSocket'); + +/** + * EmailTestComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class EmailTestComponent extends EmailComponent { + + var $smtpSend = ''; +/** + * smtpSend method override for testing + * + * @access public + * @return mixed + */ + function smtpSend($data, $code = '250') { + return parent::_smtpSend($data, $code); + } + +/** + * undocumented function + * + * @return void + */ + function _smtpSend($data, $code = '250') { + if ($this->_debug) { + $this->smtpSend .= $data . "\n"; + return true; + } + return parent::_smtpSend($data, $code); + } + +/** + * Convenience setter method for testing. + * + * @access public + * @return void + */ + function setConnectionSocket(&$socket) { + $this->__smtpConnection = $socket; + } + +/** + * Allows mocks to be used with tests. + * + * @param array $config + * @return void + */ + function _getSocket($config) { + if (empty($this->__smtpConnection)) { + parent::_getSocket($config); + } + } + +/** + * Convenience getter method for testing. + * + * @access public + * @return mixed + */ + function getConnectionSocket() { + return $this->__smtpConnection; + } + +/** + * Convenience setter for testing. + * + * @access public + * @return void + */ + function setHeaders($headers) { + $this->__header += $headers; + } + +/** + * Convenience getter for testing. + * + * @access public + * @return array + */ + function getHeaders() { + return $this->__header; + } + +/** + * Convenience setter for testing. + * + * @access public + * @return void + */ + function setBoundary() { + $this->__createBoundary(); + } + +/** + * Convenience getter for testing. + * + * @access public + * @return string + */ + function getBoundary() { + return $this->__boundary; + } + +/** + * Convenience getter for testing. + * + * @access public + * @return string + */ + function getMessage() { + return $this->__message; + } + +/** + * Convenience getter for testing. + * + * @access protected + * @return string + */ + function _getMessage() { + return $this->__message; + } + +/** + * Convenience method for testing. + * + * @access public + * @return string + */ + function strip($content, $message = false) { + return parent::_strip($content, $message); + } + +/** + * Wrapper for testing. + * + * @return void + */ + function formatAddress($string, $smtp = false) { + return parent::_formatAddress($string, $smtp); + } +} + +/** + * EmailTestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class EmailTestController extends Controller { + +/** + * name property + * + * @var string 'EmailTest' + * @access public + */ + var $name = 'EmailTest'; + +/** + * uses property + * + * @var mixed null + * @access public + */ + var $uses = null; + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Session', 'EmailTest'); + +/** + * pageTitle property + * + * @var string + * @access public + */ + var $pageTitle = 'EmailTest'; +} + +/** + * EmailTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class EmailComponentTest extends CakeTestCase { + +/** + * Controller property + * + * @var EmailTestController + * @access public + */ + var $Controller; + +/** + * name property + * + * @var string 'Email' + * @access public + */ + var $name = 'Email'; + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_appEncoding = Configure::read('App.encoding'); + Configure::write('App.encoding', 'UTF-8'); + + $this->Controller =& new EmailTestController(); + + restore_error_handler(); + @$this->Controller->Component->init($this->Controller); + set_error_handler('simpleTestErrorHandler'); + + $this->Controller->EmailTest->initialize($this->Controller, array()); + ClassRegistry::addObject('view', new View($this->Controller)); + + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + )); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('App.encoding', $this->_appEncoding); + App::build(); + $this->Controller->Session->delete('Message'); + restore_error_handler(); + ClassRegistry::flush(); + } + +/** + * osFix method + * + * @param string $string + * @access private + * @return string + */ + function __osFix($string) { + return str_replace(array("\r\n", "\r"), "\n", $string); + } + +/** + * testSmtpConfig method + * + * @access public + * @return void + */ + function testSmtpConfig() { + if ($this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost')) { + return; + } + $this->Controller->EmailTest->delivery = 'smtp'; + $this->Controller->EmailTest->smtpOptions = array(); + $this->Controller->EmailTest->send('anything'); + $config = array( + 'host' => 'localhost', + 'port' => 25, + 'protocol' => 'smtp', + 'timeout' => 30 + ); + $this->assertEqual($config, $this->Controller->EmailTest->smtpOptions); + + $this->Controller->EmailTest->smtpOptions = array('port' => 80); + $this->Controller->EmailTest->send('anything'); + $config['port'] = 80; + $this->assertEqual($config, $this->Controller->EmailTest->smtpOptions); + } + +/** + * testBadSmtpSend method + * + * @access public + * @return void + */ + function testBadSmtpSend() { + if ($this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost')) { + return; + } + $this->Controller->EmailTest->smtpOptions['host'] = 'blah'; + $this->Controller->EmailTest->delivery = 'smtp'; + $this->assertFalse($this->Controller->EmailTest->send('Should not work')); + } + +/** + * testSmtpSend method + * + * @access public + * @return void + */ + function testSmtpSend() { + if ($this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost')) { + return; + } + + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake SMTP test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + + $this->Controller->EmailTest->delivery = 'smtp'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + + $this->Controller->EmailTest->_debug = true; + $this->Controller->EmailTest->sendAs = 'text'; + $expect = <<Host: localhost +Port: 25 +Timeout: 30 +To: postmaster@localhost +From: noreply@example.com +Subject: Cake SMTP test +Header: + +To: postmaster@localhost +From: noreply@example.com +Reply-To: noreply@example.com +Subject: Cake SMTP test +X-Mailer: CakePHP Email Component +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 7bitParameters: + +Message: + +This is the body of the message + + +TEMPDOC; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + } + +/** + * testSmtpEhlo method + * + * @access public + * @return void + */ + function testSmtpEhlo() { + if ($this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost')) { + return; + } + + $connection =& new CakeSocket(array('protocol'=>'smtp', 'host' => 'localhost', 'port' => 25)); + $this->Controller->EmailTest->setConnectionSocket($connection); + $this->Controller->EmailTest->smtpOptions['timeout'] = 10; + $this->assertTrue($connection->connect()); + $this->assertTrue($this->Controller->EmailTest->smtpSend(null, '220') !== false); + $this->skipIf($this->Controller->EmailTest->smtpSend('EHLO locahost', '250') === false, '%s do not support EHLO.'); + $connection->disconnect(); + + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake SMTP test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + + $this->Controller->EmailTest->delivery = 'smtp'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + + $this->Controller->EmailTest->_debug = true; + $this->Controller->EmailTest->sendAs = 'text'; + $expect = <<Host: localhost +Port: 25 +Timeout: 30 +To: postmaster@localhost +From: noreply@example.com +Subject: Cake SMTP test +Header: + +To: postmaster@localhost +From: noreply@example.com +Reply-To: noreply@example.com +Subject: Cake SMTP test +X-Mailer: CakePHP Email Component +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 7bitParameters: + +Message: + +This is the body of the message + + +TEMPDOC; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + } + +/** + * testSmtpSendMultipleTo method + * + * @access public + * @return void + */ + function testSmtpSendMultipleTo() { + if ($this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost')) { + return; + } + $this->Controller->EmailTest->reset(); + $this->Controller->EmailTest->to = array('postmaster@localhost', 'root@localhost'); + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake SMTP multiple To test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->_debug = true; + $this->Controller->EmailTest->sendAs = 'text'; + $this->Controller->EmailTest->delivery = 'smtp'; + + $socket = new MockEmailSocket(); + $socket->setReturnValue('connect', true); + $this->Controller->EmailTest->setConnectionSocket($socket); + + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + + $this->assertPattern('/EHLO localhost\n/', $this->Controller->EmailTest->smtpSend); + $this->assertPattern('/MAIL FROM: \n/', $this->Controller->EmailTest->smtpSend); + $this->assertPattern('/RCPT TO: \n/', $this->Controller->EmailTest->smtpSend); + $this->assertPattern('/RCPT TO: \n/', $this->Controller->EmailTest->smtpSend); + $this->assertPattern( + '/To: postmaster@localhost, root@localhost[\n\r]/', + $this->Controller->EmailTest->smtpSend + ); + } + +/** + * test sending smtp from a host using a port. + * + * @return void + */ + function testSmtpSendHostWithPort() { + $bkp = env('HTTP_HOST'); + $_SERVER['HTTP_HOST'] = 'localhost:8080'; + + $this->Controller->EmailTest->reset(); + $this->Controller->EmailTest->to = array('root@localhost'); + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake SMTP host test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'smtp'; + $this->Controller->EmailTest->sendAs = 'text'; + $this->Controller->EmailTest->_debug = true; + + $socket = new MockEmailSocket(); + $socket->setReturnValue('connect', true); + + $this->Controller->EmailTest->setConnectionSocket($socket); + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + + $this->assertPattern('/EHLO localhost\n/', $this->Controller->EmailTest->smtpSend); + + $_SERVER['HTTP_HOST'] = $bkp; + } + +/** + * testAuthenticatedSmtpSend method + * + * @access public + * @return void + */ + function testAuthenticatedSmtpSend() { + if ($this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost')) { + return; + } + + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake SMTP test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->smtpOptions['username'] = 'test'; + $this->Controller->EmailTest->smtpOptions['password'] = 'testing'; + + $this->Controller->EmailTest->delivery = 'smtp'; + $result = $this->Controller->EmailTest->send('This is the body of the message'); + $code = substr($this->Controller->EmailTest->smtpError, 0, 3); + $this->skipIf(!$code, '%s Authentication not enabled on server'); + + $this->assertFalse($result); + $this->assertEqual($code, '535'); + } + +/** + * testSendFormats method + * + * @access public + * @return void + */ + function testSendFormats() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake SMTP test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'debug'; + $this->Controller->EmailTest->messageId = false; + + $date = date(DATE_RFC2822); + $message = <<To: postmaster@localhost +From: noreply@example.com +Subject: Cake SMTP test +Header: + +From: noreply@example.com +Reply-To: noreply@example.com +Date: $date +X-Mailer: CakePHP Email Component +Content-Type: {CONTENTTYPE} +Content-Transfer-Encoding: 7bitParameters: + +Message: + +This is the body of the message + + +MSGBLOC; + $this->Controller->EmailTest->sendAs = 'text'; + $expect = str_replace('{CONTENTTYPE}', 'text/plain; charset=UTF-8', $message); + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + + $this->Controller->EmailTest->sendAs = 'html'; + $expect = str_replace('{CONTENTTYPE}', 'text/html; charset=UTF-8', $message); + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + + // TODO: better test for format of message sent? + $this->Controller->EmailTest->sendAs = 'both'; + $expect = str_replace('{CONTENTTYPE}', 'multipart/alternative; boundary="alt-"', $message); + + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + } + +/** + * testTemplates method + * + * @access public + * @return void + */ + function testTemplates() { + ClassRegistry::flush(); + + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake SMTP test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + + $this->Controller->EmailTest->delivery = 'debug'; + $this->Controller->EmailTest->messageId = false; + + $date = date(DATE_RFC2822); + $header = <<Controller->EmailTest->layout = 'default'; + $this->Controller->EmailTest->template = 'default'; + + $text = << + + + + Email Test + + + +

This is the body of the message

+

This email was sent using the CakePHP Framework

+ + +HTMLBLOC; + + $this->Controller->EmailTest->sendAs = 'text'; + $expect = '
' . str_replace('{CONTENTTYPE}', 'text/plain; charset=UTF-8', $header) . $text . "\n" . '
'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + + $this->Controller->EmailTest->sendAs = 'html'; + $expect = '
' . str_replace('{CONTENTTYPE}', 'text/html; charset=UTF-8', $header) . $html . "\n" . '
'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + + $this->Controller->EmailTest->sendAs = 'both'; + $expect = str_replace('{CONTENTTYPE}', 'multipart/alternative; boundary="alt-"', $header); + $expect .= '--alt-' . "\n" . 'Content-Type: text/plain; charset=UTF-8' . "\n" . 'Content-Transfer-Encoding: 7bit' . "\n\n" . $text . "\n\n"; + $expect .= '--alt-' . "\n" . 'Content-Type: text/html; charset=UTF-8' . "\n" . 'Content-Transfer-Encoding: 7bit' . "\n\n" . $html . "\n\n"; + $expect = '
' . $expect . '--alt---' . "\n\n" . '
'; + + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + + $html = << + + + + Email Test + + + +

This is the body of the message

+

This email was sent using the CakePHP Framework

+ + + +HTMLBLOC; + + $this->Controller->EmailTest->sendAs = 'html'; + $expect = '
' . str_replace('{CONTENTTYPE}', 'text/html; charset=UTF-8', $header) . $html . '
'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message', 'default', 'thin')); + $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect)); + + $result = ClassRegistry::getObject('view'); + $this->assertFalse($result); + } + +/** + * test that elements used in email templates get helpers. + * + * @return void + */ + function testTemplateNestedElements() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake SMTP test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + + $this->Controller->EmailTest->delivery = 'debug'; + $this->Controller->EmailTest->messageId = false; + $this->Controller->EmailTest->layout = 'default'; + $this->Controller->EmailTest->template = 'nested_element'; + $this->Controller->EmailTest->sendAs = 'html'; + $this->Controller->helpers = array('Html'); + + $this->Controller->EmailTest->send(); + $result = $this->Controller->Session->read('Message.email.message'); + $this->assertPattern('/Test/', $result); + $this->assertPattern('/http\:\/\/example\.com/', $result); + } + +/** + * testSmtpSendSocket method + * + * @access public + * @return void + */ + function testSmtpSendSocket() { + if ($this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost')) { + return; + } + + $this->Controller->EmailTest->smtpOptions['timeout'] = 10; + $socket =& new CakeSocket(array_merge(array('protocol'=>'smtp'), $this->Controller->EmailTest->smtpOptions)); + $this->Controller->EmailTest->setConnectionSocket($socket); + + $this->assertTrue($this->Controller->EmailTest->getConnectionSocket()); + + $response = $this->Controller->EmailTest->smtpSend('HELO', '250'); + $this->assertPattern('/501 Syntax: HELO hostname/', $this->Controller->EmailTest->smtpError); + + $this->Controller->EmailTest->reset(); + $response = $this->Controller->EmailTest->smtpSend('HELO somehostname', '250'); + $this->assertNoPattern('/501 Syntax: HELO hostname/', $this->Controller->EmailTest->smtpError); + } + +/** + * testSendDebug method + * + * @access public + * @return void + */ + function testSendDebug() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->cc = 'cc@example.com'; + $this->Controller->EmailTest->bcc = 'bcc@example.com'; + $this->Controller->EmailTest->subject = 'Cake Debug Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + + $this->Controller->EmailTest->delivery = 'debug'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $result = $this->Controller->Session->read('Message.email.message'); + + $this->assertPattern('/To: postmaster@localhost\n/', $result); + $this->assertPattern('/Subject: Cake Debug Test\n/', $result); + $this->assertPattern('/Reply-To: noreply@example.com\n/', $result); + $this->assertPattern('/From: noreply@example.com\n/', $result); + $this->assertPattern('/Cc: cc@example.com\n/', $result); + $this->assertPattern('/Bcc: bcc@example.com\n/', $result); + $this->assertPattern('/Date: ' . preg_quote(date(DATE_RFC2822)) . '\n/', $result); + $this->assertPattern('/X-Mailer: CakePHP Email Component\n/', $result); + $this->assertPattern('/Content-Type: text\/plain; charset=UTF-8\n/', $result); + $this->assertPattern('/Content-Transfer-Encoding: 7bitParameters:\n/', $result); + $this->assertPattern('/This is the body of the message/', $result); + } + +/** + * test send with delivery = debug and not using sessions. + * + * @return void + */ + function testSendDebugWithNoSessions() { + $session =& $this->Controller->Session; + unset($this->Controller->Session); + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake Debug Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + + $this->Controller->EmailTest->delivery = 'debug'; + $result = $this->Controller->EmailTest->send('This is the body of the message'); + + $this->assertPattern('/To: postmaster@localhost\n/', $result); + $this->assertPattern('/Subject: Cake Debug Test\n/', $result); + $this->assertPattern('/Reply-To: noreply@example.com\n/', $result); + $this->assertPattern('/From: noreply@example.com\n/', $result); + $this->assertPattern('/Date: ' . preg_quote(date(DATE_RFC2822)) . '\n/', $result); + $this->assertPattern('/X-Mailer: CakePHP Email Component\n/', $result); + $this->assertPattern('/Content-Type: text\/plain; charset=UTF-8\n/', $result); + $this->assertPattern('/Content-Transfer-Encoding: 7bitParameters:\n/', $result); + $this->assertPattern('/This is the body of the message/', $result); + $this->Controller->Session = $session; + } + +/** + * testMessageRetrievalWithoutTemplate method + * + * @access public + * @return void + */ + function testMessageRetrievalWithoutTemplate() { + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + )); + + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake Debug Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->layout = 'default'; + $this->Controller->EmailTest->template = null; + + $this->Controller->EmailTest->delivery = 'debug'; + + $text = $html = 'This is the body of the message'; + + $this->Controller->EmailTest->sendAs = 'both'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->EmailTest->textMessage, $this->__osFix($text)); + $this->assertEqual($this->Controller->EmailTest->htmlMessage, $this->__osFix($html)); + + $this->Controller->EmailTest->sendAs = 'text'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertEqual($this->Controller->EmailTest->textMessage, $this->__osFix($text)); + $this->assertNull($this->Controller->EmailTest->htmlMessage); + + $this->Controller->EmailTest->sendAs = 'html'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $this->assertNull($this->Controller->EmailTest->textMessage); + $this->assertEqual($this->Controller->EmailTest->htmlMessage, $this->__osFix($html)); + } + +/** + * testMessageRetrievalWithTemplate method + * + * @access public + * @return void + */ + function testMessageRetrievalWithTemplate() { + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + )); + + $this->Controller->set('value', 22091985); + $this->Controller->set('title_for_layout', 'EmailTest'); + + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake Debug Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->layout = 'default'; + $this->Controller->EmailTest->template = 'custom'; + + $this->Controller->EmailTest->delivery = 'debug'; + + $text = << + + + + EmailTest + + + +

Here is your value: 22091985

+

This email was sent using the CakePHP Framework

+ + +HTMLBLOC; + + $this->Controller->EmailTest->sendAs = 'both'; + $this->assertTrue($this->Controller->EmailTest->send()); + $this->assertEqual($this->Controller->EmailTest->textMessage, $this->__osFix($text)); + $this->assertEqual($this->Controller->EmailTest->htmlMessage, $this->__osFix($html)); + + $this->Controller->EmailTest->sendAs = 'text'; + $this->assertTrue($this->Controller->EmailTest->send()); + $this->assertEqual($this->Controller->EmailTest->textMessage, $this->__osFix($text)); + $this->assertNull($this->Controller->EmailTest->htmlMessage); + + $this->Controller->EmailTest->sendAs = 'html'; + $this->assertTrue($this->Controller->EmailTest->send()); + $this->assertNull($this->Controller->EmailTest->textMessage); + $this->assertEqual($this->Controller->EmailTest->htmlMessage, $this->__osFix($html)); + } + +/** + * testContentArray method + * + * @access public + * @return void + */ + function testSendContentArray() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake Debug Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'debug'; + + $content = array('First line', 'Second line', 'Third line'); + $this->assertTrue($this->Controller->EmailTest->send($content)); + $result = $this->Controller->Session->read('Message.email.message'); + + $this->assertPattern('/To: postmaster@localhost\n/', $result); + $this->assertPattern('/Subject: Cake Debug Test\n/', $result); + $this->assertPattern('/Reply-To: noreply@example.com\n/', $result); + $this->assertPattern('/From: noreply@example.com\n/', $result); + $this->assertPattern('/X-Mailer: CakePHP Email Component\n/', $result); + $this->assertPattern('/Content-Type: text\/plain; charset=UTF-8\n/', $result); + $this->assertPattern('/Content-Transfer-Encoding: 7bitParameters:\n/', $result); + $this->assertPattern('/First line\n/', $result); + $this->assertPattern('/Second line\n/', $result); + $this->assertPattern('/Third line\n/', $result); + } + +/** + * test setting a custom date. + * + * @return void + */ + function testDateProperty() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake Debug Test'; + $this->Controller->EmailTest->date = 'Today!'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'debug'; + + $this->assertTrue($this->Controller->EmailTest->send('test message')); + $result = $this->Controller->Session->read('Message.email.message'); + $this->assertPattern('/Date: Today!\n/', $result); + } + +/** + * testContentStripping method + * + * @access public + * @return void + */ + function testContentStripping() { + $content = "Previous content\n--alt-\nContent-TypeContent-Type:: text/html; charsetcharset==utf-8\nContent-Transfer-Encoding: 7bit"; + $content .= "\n\n

My own html content

"; + + $result = $this->Controller->EmailTest->strip($content, true); + $expected = "Previous content\n--alt-\n text/html; utf-8\n 7bit\n\n

My own html content

"; + $this->assertEqual($result, $expected); + + $content = '

Some HTML content with an email link'; + $result = $this->Controller->EmailTest->strip($content, true); + $expected = $content; + $this->assertEqual($result, $expected); + + $content = '

Some HTML content with an '; + $content .= 'email link'; + $result = $this->Controller->EmailTest->strip($content, true); + $expected = $content; + $this->assertEqual($result, $expected); + } + +/** + * test that the _encode() will set mb_internal_encoding. + * + * @return void + */ + function test_encodeSettingInternalCharset() { + $skip = !function_exists('mb_internal_encoding'); + if ($this->skipIf($skip, 'Missing mb_* functions, cannot run test.')) { + return; + } + mb_internal_encoding('ISO-8859-1'); + + $this->Controller->charset = 'UTF-8'; + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'هذه رسالة بعنوان طويل مرسل للمستلم'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'debug'; + + $this->Controller->EmailTest->sendAs = 'text'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + + $subject = '=?UTF-8?B?2YfYsNmHINix2LPYp9mE2Kkg2KjYudmG2YjYp9mGINi32YjZitmEINmF2LE=?=' . "\r\n" . ' =?UTF-8?B?2LPZhCDZhNmE2YXYs9iq2YTZhQ==?='; + + preg_match('/Subject: (.*)Header:/s', $this->Controller->Session->read('Message.email.message'), $matches); + $this->assertEqual(trim($matches[1]), $subject); + + $result = mb_internal_encoding(); + $this->assertEqual($result, 'ISO-8859-1'); + } + +/** + * testMultibyte method + * + * @access public + * @return void + */ + function testMultibyte() { + $this->Controller->charset = 'UTF-8'; + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'هذه رسالة بعنوان طويل مرسل للمستلم'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'debug'; + + $subject = '=?UTF-8?B?2YfYsNmHINix2LPYp9mE2Kkg2KjYudmG2YjYp9mGINi32YjZitmEINmF2LE=?=' . "\r\n" . ' =?UTF-8?B?2LPZhCDZhNmE2YXYs9iq2YTZhQ==?='; + + $this->Controller->EmailTest->sendAs = 'text'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + preg_match('/Subject: (.*)Header:/s', $this->Controller->Session->read('Message.email.message'), $matches); + $this->assertEqual(trim($matches[1]), $subject); + + $this->Controller->EmailTest->sendAs = 'html'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + preg_match('/Subject: (.*)Header:/s', $this->Controller->Session->read('Message.email.message'), $matches); + $this->assertEqual(trim($matches[1]), $subject); + + $this->Controller->EmailTest->sendAs = 'both'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + preg_match('/Subject: (.*)Header:/s', $this->Controller->Session->read('Message.email.message'), $matches); + $this->assertEqual(trim($matches[1]), $subject); + } + +/** + * undocumented function + * + * @return void + * @access public + */ + function testSendWithAttachments() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Attachment Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'debug'; + $this->Controller->EmailTest->attachments = array( + __FILE__, + 'some-name.php' => __FILE__ + ); + $body = '

This is the body of the message

'; + + $this->Controller->EmailTest->sendAs = 'text'; + $this->assertTrue($this->Controller->EmailTest->send($body)); + $msg = $this->Controller->Session->read('Message.email.message'); + $this->assertPattern('/' . preg_quote('Content-Disposition: attachment; filename="email.test.php"') . '/', $msg); + $this->assertPattern('/' . preg_quote('Content-Disposition: attachment; filename="some-name.php"') . '/', $msg); + } + +/** + * testSendAsIsNotIgnoredIfAttachmentsPresent method + * + * @return void + * @access public + */ + function testSendAsIsNotIgnoredIfAttachmentsPresent() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Attachment Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'debug'; + $this->Controller->EmailTest->attachments = array(__FILE__); + $body = '

This is the body of the message

'; + + $this->Controller->EmailTest->sendAs = 'html'; + $this->assertTrue($this->Controller->EmailTest->send($body)); + $msg = $this->Controller->Session->read('Message.email.message'); + $this->assertNoPattern('/text\/plain/', $msg); + $this->assertPattern('/text\/html/', $msg); + + $this->Controller->EmailTest->sendAs = 'text'; + $this->assertTrue($this->Controller->EmailTest->send($body)); + $msg = $this->Controller->Session->read('Message.email.message'); + $this->assertPattern('/text\/plain/', $msg); + $this->assertNoPattern('/text\/html/', $msg); + + $this->Controller->EmailTest->sendAs = 'both'; + $this->assertTrue($this->Controller->EmailTest->send($body)); + $msg = $this->Controller->Session->read('Message.email.message'); + + $this->assertNoPattern('/text\/plain/', $msg); + $this->assertNoPattern('/text\/html/', $msg); + $this->assertPattern('/multipart\/alternative/', $msg); + } + +/** + * testNoDoubleNewlinesInHeaders function + * + * @return void + * @access public + */ + function testNoDoubleNewlinesInHeaders() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Attachment Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + $this->Controller->EmailTest->delivery = 'debug'; + $body = '

This is the body of the message

'; + + $this->Controller->EmailTest->sendAs = 'both'; + $this->assertTrue($this->Controller->EmailTest->send($body)); + $msg = $this->Controller->Session->read('Message.email.message'); + + $this->assertNoPattern('/\n\nContent-Transfer-Encoding/', $msg); + $this->assertPattern('/\nContent-Transfer-Encoding/', $msg); + } + +/** + * testReset method + * + * @access public + * @return void + */ + function testReset() { + $this->Controller->EmailTest->template = 'test_template'; + $this->Controller->EmailTest->to = 'test.recipient@example.com'; + $this->Controller->EmailTest->from = 'test.sender@example.com'; + $this->Controller->EmailTest->replyTo = 'test.replyto@example.com'; + $this->Controller->EmailTest->return = 'test.return@example.com'; + $this->Controller->EmailTest->cc = array('cc1@example.com', 'cc2@example.com'); + $this->Controller->EmailTest->bcc = array('bcc1@example.com', 'bcc2@example.com'); + $this->Controller->EmailTest->date = 'Today!'; + $this->Controller->EmailTest->subject = 'Test subject'; + $this->Controller->EmailTest->additionalParams = 'X-additional-header'; + $this->Controller->EmailTest->delivery = 'smtp'; + $this->Controller->EmailTest->smtpOptions['host'] = 'blah'; + $this->Controller->EmailTest->smtpOptions['timeout'] = 0.5; + $this->Controller->EmailTest->attachments = array('attachment1', 'attachment2'); + $this->Controller->EmailTest->textMessage = 'This is the body of the message'; + $this->Controller->EmailTest->htmlMessage = 'This is the body of the message'; + $this->Controller->EmailTest->messageId = false; + + $this->assertFalse($this->Controller->EmailTest->send('Should not work')); + + $this->Controller->EmailTest->reset(); + + $this->assertNull($this->Controller->EmailTest->template); + $this->assertIdentical($this->Controller->EmailTest->to, array()); + $this->assertNull($this->Controller->EmailTest->from); + $this->assertNull($this->Controller->EmailTest->replyTo); + $this->assertNull($this->Controller->EmailTest->return); + $this->assertIdentical($this->Controller->EmailTest->cc, array()); + $this->assertIdentical($this->Controller->EmailTest->bcc, array()); + $this->assertNull($this->Controller->EmailTest->date); + $this->assertNull($this->Controller->EmailTest->subject); + $this->assertNull($this->Controller->EmailTest->additionalParams); + $this->assertIdentical($this->Controller->EmailTest->getHeaders(), array()); + $this->assertNull($this->Controller->EmailTest->getBoundary()); + $this->assertIdentical($this->Controller->EmailTest->getMessage(), array()); + $this->assertNull($this->Controller->EmailTest->smtpError); + $this->assertIdentical($this->Controller->EmailTest->attachments, array()); + $this->assertNull($this->Controller->EmailTest->textMessage); + $this->assertTrue($this->Controller->EmailTest->messageId); + } + + function testPluginCustomViewClass() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + )); + + $this->Controller->view = 'TestPlugin.Email'; + + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'CustomViewClass test'; + $this->Controller->EmailTest->delivery = 'debug'; + $body = 'Body of message'; + + $this->assertTrue($this->Controller->EmailTest->send($body)); + $result = $this->Controller->Session->read('Message.email.message'); + + $this->assertPattern('/Body of message/', $result); + + } + +/** + * testStartup method + * + * @access public + * @return void + */ + function testStartup() { + $this->assertNull($this->Controller->EmailTest->startup($this->Controller)); + } + +/** + * testMessageId method + * + * @access public + * @return void + */ + function testMessageId() { + $this->Controller->EmailTest->to = 'postmaster@localhost'; + $this->Controller->EmailTest->from = 'noreply@example.com'; + $this->Controller->EmailTest->subject = 'Cake Debug Test'; + $this->Controller->EmailTest->replyTo = 'noreply@example.com'; + $this->Controller->EmailTest->template = null; + + $this->Controller->EmailTest->delivery = 'debug'; + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $result = $this->Controller->Session->read('Message.email.message'); + + $this->assertPattern('/Message-ID: \<[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}@' . env('HTTP_HOST') . '\>\n/', $result); + + $this->Controller->EmailTest->messageId = '<22091985.998877@localhost>'; + + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $result = $this->Controller->Session->read('Message.email.message'); + + $this->assertPattern('/Message-ID: <22091985.998877@localhost>\n/', $result); + + $this->Controller->EmailTest->messageId = false; + + $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); + $result = $this->Controller->Session->read('Message.email.message'); + + $this->assertNoPattern('/Message-ID:/', $result); + } + +/** + * testSendMessage method + * + * @access public + * @return void + */ + function testSendMessage() { + $this->Controller->EmailTest->delivery = 'getMessage'; + $this->Controller->EmailTest->lineLength = 70; + + $text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; + $this->Controller->EmailTest->sendAs = 'text'; + $result = $this->Controller->EmailTest->send($text); + $expected = array( + 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do', + 'eiusmod tempor incididunt ut labore et dolore magna aliqua.', + '', + '' + ); + $this->assertEqual($expected, $result); + + $text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; + $this->Controller->EmailTest->sendAs = 'html'; + $result = $this->Controller->EmailTest->send($text); + $expected = array( + $text, + '', + '' + ); + $this->assertEqual($expected, $result); + } + +/** + * Test that _formatName doesn't jack up email addresses with alias parts. + * + * @return void + */ + function testFormatAddressAliases() { + $result = $this->Controller->EmailTest->formatAddress('email@example.com'); + $this->assertEqual($result, 'email@example.com'); + + $result = $this->Controller->EmailTest->formatAddress('alias '); + $this->assertEqual($result, 'alias '); + + $result = $this->Controller->EmailTest->formatAddress('alias'); + $this->assertEqual($result, 'alias '); + + $result = $this->Controller->EmailTest->formatAddress('email@example.com'); + $this->assertEqual($result, 'email@example.com'); + + $result = $this->Controller->EmailTest->formatAddress(''); + $this->assertEqual($result, ''); + + $result = $this->Controller->EmailTest->formatAddress('email@example.com', true); + $this->assertEqual($result, ''); + + $result = $this->Controller->EmailTest->formatAddress('', true); + $this->assertEqual($result, ''); + + $result = $this->Controller->EmailTest->formatAddress('alias name ', true); + $this->assertEqual($result, ''); + } + +/** + * test formatting addresses with multibyte chars + * + * @return void + */ + function testFormatAddressMultibyte() { + $this->Controller->EmailTest->charset = 'UTF-8'; + $result = $this->Controller->EmailTest->formatAddress('ÄÖÜTest '); + $this->assertEqual($result, '=?UTF-8?B?w4TDlsOcVGVzdCA=?= '); + + $result = $this->Controller->EmailTest->formatAddress('ÄÖÜTest'); + $this->assertEqual($result, '=?UTF-8?B?w4TDlsOcVGVzdA==?= '); + + $result = $this->Controller->EmailTest->formatAddress('ÄÖÜTest ', true); + $this->assertEqual($result, ''); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/request_handler.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/request_handler.test.php new file mode 100644 index 000000000..8851dc58b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/request_handler.test.php @@ -0,0 +1,731 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + * @since CakePHP(tm) v 1.2.0.5435 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Controller', 'Controller', false); +App::import('Component', array('RequestHandler')); + +Mock::generatePartial('RequestHandlerComponent', 'NoStopRequestHandler', array('_stop', '_header')); +Mock::generatePartial('Controller', 'RequestHandlerMockController', array('header')); + +/** + * RequestHandlerTestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class RequestHandlerTestController extends Controller { + +/** + * name property + * + * @var string + * @access public + */ + var $name = 'RequestHandlerTest'; + +/** + * uses property + * + * @var mixed null + * @access public + */ + var $uses = null; + +/** + * construct method + * + * @param array $params + * @access private + * @return void + */ + function __construct($params = array()) { + foreach ($params as $key => $val) { + $this->{$key} = $val; + } + parent::__construct(); + } + +/** + * test method for ajax redirection + * + * @return void + */ + function destination() { + $this->viewPath = 'posts'; + $this->render('index'); + } +/** + * test method for ajax redirection + parameter parsing + * + * @return void + */ + function param_method($one = null, $two = null) { + echo "one: $one two: $two"; + $this->autoRender = false; + } + +/** + * test method for testing layout rendering when isAjax() + * + * @return void + */ + function ajax2_layout() { + if ($this->autoLayout) { + $this->layout = 'ajax2'; + } + $this->destination(); + } +} + +/** + * RequestHandlerTestDisabledController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class RequestHandlerTestDisabledController extends Controller { + +/** + * uses property + * + * @var mixed null + * @access public + */ + var $uses = null; + +/** + * construct method + * + * @param array $params + * @access private + * @return void + */ + function __construct($params = array()) { + foreach ($params as $key => $val) { + $this->{$key} = $val; + } + parent::__construct(); + } + +/** + * beforeFilter method + * + * @return void + * @access public + */ + function beforeFilter() { + $this->RequestHandler->enabled = false; + } +} + +/** + * RequestHandlerComponentTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class RequestHandlerComponentTest extends CakeTestCase { + +/** + * Controller property + * + * @var RequestHandlerTestController + * @access public + */ + var $Controller; + +/** + * RequestHandler property + * + * @var RequestHandlerComponent + * @access public + */ + var $RequestHandler; + +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->_init(); + } + +/** + * init method + * + * @access protected + * @return void + */ + function _init() { + $this->Controller = new RequestHandlerTestController(array('components' => array('RequestHandler'))); + $this->Controller->constructClasses(); + $this->RequestHandler =& $this->Controller->RequestHandler; + } + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + unset($this->RequestHandler); + unset($this->Controller); + if (!headers_sent()) { + header('Content-type: text/html'); //reset content type. + } + App::build(); + } + +/** + * testInitializeCallback method + * + * @access public + * @return void + */ + function testInitializeCallback() { + $this->assertNull($this->RequestHandler->ext); + + $this->_init(); + $this->Controller->params['url']['ext'] = 'rss'; + $this->RequestHandler->initialize($this->Controller); + $this->assertEqual($this->RequestHandler->ext, 'rss'); + + $settings = array( + 'ajaxLayout' => 'test_ajax' + ); + $this->RequestHandler->initialize($this->Controller, $settings); + $this->assertEqual($this->RequestHandler->ajaxLayout, 'test_ajax'); + } + +/** + * testDisabling method + * + * @access public + * @return void + */ + function testDisabling() { + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $this->_init(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->beforeFilter(); + $this->Controller->Component->startup($this->Controller); + $this->assertEqual($this->Controller->params, array('isAjax' => true)); + + $this->Controller = new RequestHandlerTestDisabledController(array('components' => array('RequestHandler'))); + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->beforeFilter(); + $this->Controller->Component->startup($this->Controller); + $this->assertEqual($this->Controller->params, array()); + unset($_SERVER['HTTP_X_REQUESTED_WITH']); + } + +/** + * testAutoResponseType method + * + * @access public + * @return void + */ + function testAutoResponseType() { + $this->Controller->ext = '.thtml'; + $this->Controller->params['url']['ext'] = 'rss'; + $this->RequestHandler->initialize($this->Controller); + $this->RequestHandler->startup($this->Controller); + $this->assertEqual($this->Controller->ext, '.ctp'); + } + + +/** + * testAutoAjaxLayout method + * + * @access public + * @return void + */ + function testAutoAjaxLayout() { + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $this->RequestHandler->startup($this->Controller); + $this->assertTrue($this->Controller->layout, $this->RequestHandler->ajaxLayout); + + $this->_init(); + $this->Controller->params['url']['ext'] = 'js'; + $this->RequestHandler->initialize($this->Controller); + $this->RequestHandler->startup($this->Controller); + $this->assertNotEqual($this->Controller->layout, 'ajax'); + + unset($_SERVER['HTTP_X_REQUESTED_WITH']); + } + +/** + * testStartupCallback method + * + * @access public + * @return void + */ + function testStartupCallback() { + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $_SERVER['CONTENT_TYPE'] = 'application/xml'; + $this->RequestHandler->startup($this->Controller); + $this->assertTrue(is_array($this->Controller->data)); + $this->assertFalse(is_object($this->Controller->data)); + } + +/** + * testStartupCallback with charset. + * + * @return void + */ + function testStartupCallbackCharset() { + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $_SERVER['CONTENT_TYPE'] = 'application/xml; charset=UTF-8'; + $this->RequestHandler->startup($this->Controller); + $this->assertTrue(is_array($this->Controller->data)); + $this->assertFalse(is_object($this->Controller->data)); + } + +/** + * testNonAjaxRedirect method + * + * @access public + * @return void + */ + function testNonAjaxRedirect() { + $this->RequestHandler->initialize($this->Controller); + $this->RequestHandler->startup($this->Controller); + $this->assertNull($this->RequestHandler->beforeRedirect($this->Controller, '/')); + } + +/** + * testRenderAs method + * + * @access public + * @return void + */ + function testRenderAs() { + $this->assertFalse(in_array('Xml', $this->Controller->helpers)); + $this->RequestHandler->renderAs($this->Controller, 'xml'); + $this->assertTrue(in_array('Xml', $this->Controller->helpers)); + + $this->Controller->viewPath = 'request_handler_test\\xml'; + $this->RequestHandler->renderAs($this->Controller, 'js'); + $this->assertEqual($this->Controller->viewPath, 'request_handler_test' . DS . 'js'); + } + +/** + * test that respondAs works as expected. + * + * @return void + */ + function testRespondAs() { + $RequestHandler = new NoStopRequestHandler(); + $RequestHandler->expectAt(0, '_header', array('Content-Type: application/json')); + $RequestHandler->expectAt(1, '_header', array('Content-Type: text/xml')); + + $result = $RequestHandler->respondAs('json'); + $this->assertTrue($result); + + $result = $RequestHandler->respondAs('text/xml'); + $this->assertTrue($result); + } + +/** + * test that attachment headers work with respondAs + * + * @return void + */ + function testRespondAsWithAttachment() { + $RequestHandler = new NoStopRequestHandler(); + $RequestHandler->expectAt(0, '_header', array('Content-Disposition: attachment; filename="myfile.xml"')); + $RequestHandler->expectAt(1, '_header', array('Content-Type: text/xml')); + + $result = $RequestHandler->respondAs('xml', array('attachment' => 'myfile.xml')); + $this->assertTrue($result); + } + +/** + * test that calling renderAs() more than once continues to work. + * + * @link #6466 + * @return void + */ + function testRenderAsCalledTwice() { + $this->RequestHandler->renderAs($this->Controller, 'xml'); + $this->assertEqual($this->Controller->viewPath, 'request_handler_test' . DS . 'xml'); + $this->assertEqual($this->Controller->layoutPath, 'xml'); + + $this->assertTrue(in_array('Xml', $this->Controller->helpers)); + + $this->RequestHandler->renderAs($this->Controller, 'js'); + $this->assertEqual($this->Controller->viewPath, 'request_handler_test' . DS . 'js'); + $this->assertEqual($this->Controller->layoutPath, 'js'); + $this->assertTrue(in_array('Js', $this->Controller->helpers)); + } + +/** + * testRequestClientTypes method + * + * @access public + * @return void + */ + function testRequestClientTypes() { + $this->assertFalse($this->RequestHandler->isFlash()); + $_SERVER['HTTP_USER_AGENT'] = 'Shockwave Flash'; + $this->assertTrue($this->RequestHandler->isFlash()); + unset($_SERVER['HTTP_USER_AGENT'], $_SERVER['HTTP_X_REQUESTED_WITH']); + + $this->assertFalse($this->RequestHandler->isAjax()); + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $_SERVER['HTTP_X_PROTOTYPE_VERSION'] = '1.5'; + $this->assertTrue($this->RequestHandler->isAjax()); + $this->assertEqual($this->RequestHandler->getAjaxVersion(), '1.5'); + + unset($_SERVER['HTTP_X_REQUESTED_WITH'], $_SERVER['HTTP_X_PROTOTYPE_VERSION']); + $this->assertFalse($this->RequestHandler->isAjax()); + $this->assertFalse($this->RequestHandler->getAjaxVersion()); + } + +/** + * Tests the detection of various Flash versions + * + * @access public + * @return void + */ + function testFlashDetection() { + $_agent = env('HTTP_USER_AGENT'); + $_SERVER['HTTP_USER_AGENT'] = 'Shockwave Flash'; + $this->assertTrue($this->RequestHandler->isFlash()); + + $_SERVER['HTTP_USER_AGENT'] = 'Adobe Flash'; + $this->assertTrue($this->RequestHandler->isFlash()); + + $_SERVER['HTTP_USER_AGENT'] = 'Adobe Flash Player 9'; + $this->assertTrue($this->RequestHandler->isFlash()); + + $_SERVER['HTTP_USER_AGENT'] = 'Adobe Flash Player 10'; + $this->assertTrue($this->RequestHandler->isFlash()); + + $_SERVER['HTTP_USER_AGENT'] = 'Shock Flash'; + $this->assertFalse($this->RequestHandler->isFlash()); + + $_SERVER['HTTP_USER_AGENT'] = $_agent; + } + +/** + * testRequestContentTypes method + * + * @access public + * @return void + */ + function testRequestContentTypes() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->assertNull($this->RequestHandler->requestedWith()); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $_SERVER['CONTENT_TYPE'] = 'application/json'; + $this->assertEqual($this->RequestHandler->requestedWith(), 'json'); + + $result = $this->RequestHandler->requestedWith(array('json', 'xml')); + $this->assertEqual($result, 'json'); + + $result =$this->RequestHandler->requestedWith(array('rss', 'atom')); + $this->assertFalse($result); + + $_SERVER['HTTP_ACCEPT'] = 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'; + $this->_init(); + $this->assertTrue($this->RequestHandler->isXml()); + $this->assertFalse($this->RequestHandler->isAtom()); + $this->assertFalse($this->RequestHandler->isRSS()); + + $_SERVER['HTTP_ACCEPT'] = 'application/atom+xml,text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'; + $this->_init(); + $this->assertTrue($this->RequestHandler->isAtom()); + $this->assertFalse($this->RequestHandler->isRSS()); + + $_SERVER['HTTP_ACCEPT'] = 'application/rss+xml,text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'; + $this->_init(); + $this->assertFalse($this->RequestHandler->isAtom()); + $this->assertTrue($this->RequestHandler->isRSS()); + + $this->assertFalse($this->RequestHandler->isWap()); + $_SERVER['HTTP_ACCEPT'] = 'text/vnd.wap.wml,text/html,text/plain,image/png,*/*'; + $this->_init(); + $this->assertTrue($this->RequestHandler->isWap()); + + $_SERVER['HTTP_ACCEPT'] = 'application/rss+xml,text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'; + } + +/** + * testResponseContentType method + * + * @access public + * @return void + */ + function testResponseContentType() { + $this->assertNull($this->RequestHandler->responseType()); + $this->assertTrue($this->RequestHandler->respondAs('atom')); + $this->assertEqual($this->RequestHandler->responseType(), 'atom'); + } + +/** + * testMobileDeviceDetection method + * + * @access public + * @return void + */ + function testMobileDeviceDetection() { + $this->assertFalse($this->RequestHandler->isMobile()); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3'; + $this->assertTrue($this->RequestHandler->isMobile()); + + $_SERVER['HTTP_USER_AGENT'] = 'Some imaginary UA'; + $this->RequestHandler->mobileUA []= 'imaginary'; + $this->assertTrue($this->RequestHandler->isMobile()); + array_pop($this->RequestHandler->mobileUA); + } + +/** + * testRequestProperties method + * + * @access public + * @return void + */ + function testRequestProperties() { + $_SERVER['HTTPS'] = 'on'; + $this->assertTrue($this->RequestHandler->isSSL()); + + unset($_SERVER['HTTPS']); + $this->assertFalse($this->RequestHandler->isSSL()); + + $_ENV['SCRIPT_URI'] = 'https://localhost/'; + $s = $_SERVER; + $_SERVER = array(); + $this->assertTrue($this->RequestHandler->isSSL()); + $_SERVER = $s; + } + +/** + * testRequestMethod method + * + * @access public + * @return void + */ + function testRequestMethod() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->assertTrue($this->RequestHandler->isGet()); + $this->assertFalse($this->RequestHandler->isPost()); + $this->assertFalse($this->RequestHandler->isPut()); + $this->assertFalse($this->RequestHandler->isDelete()); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->assertFalse($this->RequestHandler->isGet()); + $this->assertTrue($this->RequestHandler->isPost()); + $this->assertFalse($this->RequestHandler->isPut()); + $this->assertFalse($this->RequestHandler->isDelete()); + + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $this->assertFalse($this->RequestHandler->isGet()); + $this->assertFalse($this->RequestHandler->isPost()); + $this->assertTrue($this->RequestHandler->isPut()); + $this->assertFalse($this->RequestHandler->isDelete()); + + $_SERVER['REQUEST_METHOD'] = 'DELETE'; + $this->assertFalse($this->RequestHandler->isGet()); + $this->assertFalse($this->RequestHandler->isPost()); + $this->assertFalse($this->RequestHandler->isPut()); + $this->assertTrue($this->RequestHandler->isDelete()); + } + +/** + * testClientContentPreference method + * + * @access public + * @return void + */ + function testClientContentPreference() { + $_SERVER['HTTP_ACCEPT'] = 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,*/*'; + $this->_init(); + $this->assertNotEqual($this->RequestHandler->prefers(), 'rss'); + $this->RequestHandler->ext = 'rss'; + $this->assertEqual($this->RequestHandler->prefers(), 'rss'); + $this->assertFalse($this->RequestHandler->prefers('xml')); + $this->assertEqual($this->RequestHandler->prefers(array('js', 'xml', 'xhtml')), 'xml'); + $this->assertTrue($this->RequestHandler->accepts('xml')); + + $_SERVER['HTTP_ACCEPT'] = 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5'; + $this->_init(); + $this->assertEqual($this->RequestHandler->prefers(), 'xml'); + $this->assertEqual($this->RequestHandler->accepts(array('js', 'xml', 'html')), 'xml'); + $this->assertFalse($this->RequestHandler->accepts(array('gif', 'jpeg', 'foo'))); + + $_SERVER['HTTP_ACCEPT'] = '*/*;q=0.5'; + $this->_init(); + $this->assertEqual($this->RequestHandler->prefers(), 'html'); + $this->assertFalse($this->RequestHandler->prefers('rss')); + $this->assertFalse($this->RequestHandler->accepts('rss')); + } + +/** + * testCustomContent method + * + * @access public + * @return void + */ + function testCustomContent() { + $_SERVER['HTTP_ACCEPT'] = 'text/x-mobile,text/html;q=0.9,text/plain;q=0.8,*/*;q=0.5'; + $this->_init(); + $this->RequestHandler->setContent('mobile', 'text/x-mobile'); + $this->RequestHandler->startup($this->Controller); + $this->assertEqual($this->RequestHandler->prefers(), 'mobile'); + + $this->_init(); + $this->RequestHandler->setContent(array('mobile' => 'text/x-mobile')); + $this->RequestHandler->startup($this->Controller); + $this->assertEqual($this->RequestHandler->prefers(), 'mobile'); + } + +/** + * testClientProperties method + * + * @access public + * @return void + */ + function testClientProperties() { + $_SERVER['HTTP_HOST'] = 'localhost:80'; + $this->assertEqual($this->RequestHandler->getReferer(), 'localhost'); + $_SERVER['HTTP_HOST'] = null; + $_SERVER['HTTP_X_FORWARDED_HOST'] = 'cakephp.org'; + $this->assertEqual($this->RequestHandler->getReferer(), 'cakephp.org'); + + $_SERVER['HTTP_X_FORWARDED_FOR'] = '192.168.1.5, 10.0.1.1, proxy.com'; + $_SERVER['HTTP_CLIENT_IP'] = '192.168.1.2'; + $_SERVER['REMOTE_ADDR'] = '192.168.1.3'; + $this->assertEqual($this->RequestHandler->getClientIP(false), '192.168.1.5'); + $this->assertEqual($this->RequestHandler->getClientIP(), '192.168.1.2'); + + unset($_SERVER['HTTP_X_FORWARDED_FOR']); + $this->assertEqual($this->RequestHandler->getClientIP(), '192.168.1.2'); + + unset($_SERVER['HTTP_CLIENT_IP']); + $this->assertEqual($this->RequestHandler->getClientIP(), '192.168.1.3'); + + $_SERVER['HTTP_CLIENTADDRESS'] = '10.0.1.2, 10.0.1.1'; + $this->assertEqual($this->RequestHandler->getClientIP(), '10.0.1.2'); + } + +/** + * test that ajax requests involving redirects trigger requestAction instead. + * + * @return void + */ + function testAjaxRedirectAsRequestAction() { + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $this->_init(); + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + ), true); + + $this->Controller->RequestHandler = new NoStopRequestHandler($this); + $this->Controller->RequestHandler->expectOnce('_stop'); + + ob_start(); + $this->Controller->RequestHandler->beforeRedirect( + $this->Controller, array('controller' => 'request_handler_test', 'action' => 'destination') + ); + $result = ob_get_clean(); + $this->assertPattern('/posts index/', $result, 'RequestAction redirect failed.'); + + unset($_SERVER['HTTP_X_REQUESTED_WITH']); + App::build(); + } + +/** + * test that ajax requests involving redirects don't force no layout + * this would cause the ajax layout to not be rendered. + * + * @return void + */ + function testAjaxRedirectAsRequestActionStillRenderingLayout() { + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $this->_init(); + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + ), true); + + $this->Controller->RequestHandler = new NoStopRequestHandler($this); + $this->Controller->RequestHandler->expectOnce('_stop'); + + ob_start(); + $this->Controller->RequestHandler->beforeRedirect( + $this->Controller, array('controller' => 'request_handler_test', 'action' => 'ajax2_layout') + ); + $result = ob_get_clean(); + $this->assertPattern('/posts index/', $result, 'RequestAction redirect failed.'); + $this->assertPattern('/Ajax!/', $result, 'Layout was not rendered.'); + + unset($_SERVER['HTTP_X_REQUESTED_WITH']); + App::build(); + } + +/** + * test that the beforeRedirect callback properly converts + * array urls into their correct string ones, and adds base => false so + * the correct urls are generated. + * + * @link http://cakephp.lighthouseapp.com/projects/42648-cakephp-1x/tickets/276 + * @return void + */ + function testBeforeRedirectCallbackWithArrayUrl() { + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'accounts', 'action' => 'index', 'pass' => array(), 'named' => array(), 'form' => array(), 'url' => array('url' => 'accounts/')), + array('base' => '/officespace', 'here' => '/officespace/accounts/', 'webroot' => '/officespace/') + )); + + $RequestHandler =& new NoStopRequestHandler(); + + ob_start(); + $RequestHandler->beforeRedirect( + $this->Controller, + array('controller' => 'request_handler_test', 'action' => 'param_method', 'first', 'second') + ); + $result = ob_get_clean(); + $this->assertEqual($result, 'one: first two: second'); + } + +/** + * assure that beforeRedirect with a status code will correctly set the status header + * + * @return void + */ + function testBeforeRedirectCallingHeader() { + $controller =& new RequestHandlerMockController(); + $RequestHandler =& new NoStopRequestHandler(); + + $controller->expectOnce('header', array('HTTP/1.1 403 Forbidden')); + + ob_start(); + $RequestHandler->beforeRedirect($controller, 'request_handler_test/param_method/first/second', 403); + $result = ob_get_clean(); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/security.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/security.test.php new file mode 100644 index 000000000..66cff58ba --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/security.test.php @@ -0,0 +1,1286 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + * @since CakePHP(tm) v 1.2.0.5435 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Component', 'Security'); + +/** +* TestSecurityComponent +* +* @package cake +* @subpackage cake.tests.cases.libs.controller.components +*/ +class TestSecurityComponent extends SecurityComponent { + +/** + * validatePost method + * + * @param Controller $controller + * @return unknown + */ + function validatePost(&$controller) { + return $this->_validatePost($controller); + } +} + +/** +* SecurityTestController +* +* @package cake +* @subpackage cake.tests.cases.libs.controller.components +*/ +class SecurityTestController extends Controller { + +/** + * name property + * + * @var string 'SecurityTest' + * @access public + */ + var $name = 'SecurityTest'; + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Session', 'TestSecurity'); + +/** + * failed property + * + * @var bool false + * @access public + */ + var $failed = false; + +/** + * Used for keeping track of headers in test + * + * @var array + * @access public + */ + var $testHeaders = array(); + +/** + * fail method + * + * @access public + * @return void + */ + function fail() { + $this->failed = true; + } + +/** + * redirect method + * + * @param mixed $option + * @param mixed $code + * @param mixed $exit + * @access public + * @return void + */ + function redirect($option, $code, $exit) { + return $code; + } + +/** + * Conveinence method for header() + * + * @param string $status + * @return void + * @access public + */ + function header($status) { + $this->testHeaders[] = $status; + } +} + +/** + * SecurityComponentTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class SecurityComponentTest extends CakeTestCase { + +/** + * Controller property + * + * @var SecurityTestController + * @access public + */ + var $Controller; + +/** + * oldSalt property + * + * @var string + * @access public + */ + var $oldSalt; + +/** + * setUp method + * + * @access public + * @return void + */ + function startTest() { + $this->Controller =& new SecurityTestController(); + $this->Controller->Component->init($this->Controller); + $this->Controller->Security =& $this->Controller->TestSecurity; + $this->Controller->Security->blackHoleCallback = 'fail'; + $this->oldSalt = Configure::read('Security.salt'); + Configure::write('Security.salt', 'foo!'); + } + +/** + * Tear-down method. Resets environment state. + * + * @access public + * @return void + */ + function endTest() { + Configure::write('Security.salt', $this->oldSalt); + $this->Controller->Session->delete('_Token'); + unset($this->Controller->Security); + unset($this->Controller->Component); + unset($this->Controller); + } + +/** + * test that initalize can set properties. + * + * @return void + */ + function testInitialize() { + $settings = array( + 'requirePost' => array('edit', 'update'), + 'requireSecure' => array('update_account'), + 'requireGet' => array('index'), + 'validatePost' => false, + 'loginUsers' => array( + 'mark' => 'password' + ), + 'requireLogin' => array('login'), + ); + $this->Controller->Security->initialize($this->Controller, $settings); + $this->assertEqual($this->Controller->Security->requirePost, $settings['requirePost']); + $this->assertEqual($this->Controller->Security->requireSecure, $settings['requireSecure']); + $this->assertEqual($this->Controller->Security->requireGet, $settings['requireGet']); + $this->assertEqual($this->Controller->Security->validatePost, $settings['validatePost']); + $this->assertEqual($this->Controller->Security->loginUsers, $settings['loginUsers']); + $this->assertEqual($this->Controller->Security->requireLogin, $settings['requireLogin']); + } + +/** + * testStartup method + * + * @access public + * @return void + */ + function testStartup() { + $this->Controller->Security->startup($this->Controller); + $result = $this->Controller->params['_Token']['key']; + $this->assertNotNull($result); + $this->assertTrue($this->Controller->Session->check('_Token')); + } + +/** + * testRequirePostFail method + * + * @access public + * @return void + */ + function testRequirePostFail() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requirePost(array('posted')); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + +/** + * testRequirePostSucceed method + * + * @access public + * @return void + */ + function testRequirePostSucceed() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requirePost('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequireSecureFail method + * + * @access public + * @return void + */ + function testRequireSecureFail() { + $_SERVER['HTTPS'] = 'off'; + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requireSecure(array('posted')); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + +/** + * testRequireSecureSucceed method + * + * @access public + * @return void + */ + function testRequireSecureSucceed() { + $_SERVER['REQUEST_METHOD'] = 'Secure'; + $this->Controller->action = 'posted'; + $_SERVER['HTTPS'] = 'on'; + $this->Controller->Security->requireSecure('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequireAuthFail method + * + * @access public + * @return void + */ + function testRequireAuthFail() { + $_SERVER['REQUEST_METHOD'] = 'AUTH'; + $this->Controller->action = 'posted'; + $this->Controller->data = array('username' => 'willy', 'password' => 'somePass'); + $this->Controller->Security->requireAuth(array('posted')); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + + $this->Controller->Session->write('_Token', serialize(array('allowedControllers' => array()))); + $this->Controller->data = array('username' => 'willy', 'password' => 'somePass'); + $this->Controller->action = 'posted'; + $this->Controller->Security->requireAuth('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + + $this->Controller->Session->write('_Token', serialize(array( + 'allowedControllers' => array('SecurityTest'), 'allowedActions' => array('posted2') + ))); + $this->Controller->data = array('username' => 'willy', 'password' => 'somePass'); + $this->Controller->action = 'posted'; + $this->Controller->Security->requireAuth('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + +/** + * testRequireAuthSucceed method + * + * @access public + * @return void + */ + function testRequireAuthSucceed() { + $_SERVER['REQUEST_METHOD'] = 'AUTH'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requireAuth('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + + $this->Controller->Security->Session->write('_Token', serialize(array( + 'allowedControllers' => array('SecurityTest'), 'allowedActions' => array('posted') + ))); + $this->Controller->params['controller'] = 'SecurityTest'; + $this->Controller->params['action'] = 'posted'; + + $this->Controller->data = array( + 'username' => 'willy', 'password' => 'somePass', '_Token' => '' + ); + $this->Controller->action = 'posted'; + $this->Controller->Security->requireAuth('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequirePostSucceedWrongMethod method + * + * @access public + * @return void + */ + function testRequirePostSucceedWrongMethod() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->Controller->action = 'getted'; + $this->Controller->Security->requirePost('posted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequireGetFail method + * + * @access public + * @return void + */ + function testRequireGetFail() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'getted'; + $this->Controller->Security->requireGet(array('getted')); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + +/** + * testRequireGetSucceed method + * + * @access public + * @return void + */ + function testRequireGetSucceed() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->Controller->action = 'getted'; + $this->Controller->Security->requireGet('getted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequireLogin method + * + * @access public + * @return void + */ + function testRequireLogin() { + $this->Controller->action = 'posted'; + $this->Controller->Security->requireLogin( + 'posted', + array('type' => 'basic', 'users' => array('admin' => 'password')) + ); + $_SERVER['PHP_AUTH_USER'] = 'admin'; + $_SERVER['PHP_AUTH_PW'] = 'password'; + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + + + $this->Controller->action = 'posted'; + $this->Controller->Security->requireLogin( + array('posted'), + array('type' => 'basic', 'users' => array('admin' => 'password')) + ); + $_SERVER['PHP_AUTH_USER'] = 'admin2'; + $_SERVER['PHP_AUTH_PW'] = 'password'; + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + + $this->Controller->action = 'posted'; + $this->Controller->Security->requireLogin( + 'posted', + array('type' => 'basic', 'users' => array('admin' => 'password')) + ); + $_SERVER['PHP_AUTH_USER'] = 'admin'; + $_SERVER['PHP_AUTH_PW'] = 'password2'; + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + +/** + * testDigestAuth method + * + * @access public + * @return void + */ + function testDigestAuth() { + $skip = $this->skipIf((version_compare(PHP_VERSION, '5.1') == -1) XOR (!function_exists('apache_request_headers')), + "%s Cannot run Digest Auth test for PHP versions < 5.1" + ); + + if ($skip) { + return; + } + + $this->Controller->action = 'posted'; + $_SERVER['PHP_AUTH_DIGEST'] = $digest = <<Controller->Security->requireLogin('posted', array( + 'type' => 'digest', 'users' => array('Mufasa' => 'password'), + 'realm' => 'testrealm@host.com' + )); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequireGetSucceedWrongMethod method + * + * @access public + * @return void + */ + function testRequireGetSucceedWrongMethod() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requireGet('getted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequirePutFail method + * + * @access public + * @return void + */ + function testRequirePutFail() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'putted'; + $this->Controller->Security->requirePut(array('putted')); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + +/** + * testRequirePutSucceed method + * + * @access public + * @return void + */ + function testRequirePutSucceed() { + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $this->Controller->action = 'putted'; + $this->Controller->Security->requirePut('putted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequirePutSucceedWrongMethod method + * + * @access public + * @return void + */ + function testRequirePutSucceedWrongMethod() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requirePut('putted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequireDeleteFail method + * + * @access public + * @return void + */ + function testRequireDeleteFail() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'deleted'; + $this->Controller->Security->requireDelete(array('deleted', 'other_method')); + $this->Controller->Security->startup($this->Controller); + $this->assertTrue($this->Controller->failed); + } + +/** + * testRequireDeleteSucceed method + * + * @access public + * @return void + */ + function testRequireDeleteSucceed() { + $_SERVER['REQUEST_METHOD'] = 'DELETE'; + $this->Controller->action = 'deleted'; + $this->Controller->Security->requireDelete('deleted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequireDeleteSucceedWrongMethod method + * + * @access public + * @return void + */ + function testRequireDeleteSucceedWrongMethod() { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->Controller->action = 'posted'; + $this->Controller->Security->requireDelete('deleted'); + $this->Controller->Security->startup($this->Controller); + $this->assertFalse($this->Controller->failed); + } + +/** + * testRequireLoginSettings method + * + * @access public + * @return void + */ + function testRequireLoginSettings() { + $this->Controller->Security->requireLogin( + 'add', 'edit', + array('type' => 'basic', 'users' => array('admin' => 'password')) + ); + $this->assertEqual($this->Controller->Security->requireLogin, array('add', 'edit')); + $this->assertEqual($this->Controller->Security->loginUsers, array('admin' => 'password')); + } + +/** + * testRequireLoginAllActions method + * + * @access public + * @return void + */ + function testRequireLoginAllActions() { + $this->Controller->Security->requireLogin( + array('type' => 'basic', 'users' => array('admin' => 'password')) + ); + $this->assertEqual($this->Controller->Security->requireLogin, array('*')); + $this->assertEqual($this->Controller->Security->loginUsers, array('admin' => 'password')); + } + +/** + * Simple hash validation test + * + * @access public + * @return void + */ + function testValidatePost() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid'; + + $this->Controller->data = array( + 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'), + '_Token' => compact('key', 'fields') + ); + $this->assertTrue($this->Controller->Security->validatePost($this->Controller)); + } + +/** + * test that validatePost fails if any of its required fields are missing. + * + * @return void + */ + function testValidatePostFormHacking() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid'; + + $this->Controller->data = array( + 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'), + '_Token' => compact('key') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertFalse($result, 'validatePost passed when fields were missing. %s'); + + $this->Controller->data = array( + 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'), + '_Token' => compact('fields') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertFalse($result, 'validatePost passed when key was missing. %s'); + } + +/** + * Test that objects can't be passed into the serialized string. This was a vector for RFI and LFI + * attacks. Thanks to Felix Wilhelm + * + * @return void + */ + function testValidatePostObjectDeserialize() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877'; + + // a corrupted serialized object, so we can see if it ever gets to deserialize + $attack = 'O:3:"App":1:{s:5:"__map";a:1:{s:3:"foo";s:7:"Hacked!";s:1:"fail"}}'; + $fields .= urlencode(':' . str_rot13($attack)); + + $this->Controller->data = array( + 'Model' => array('username' => 'mark', 'password' => 'foo', 'valid' => '0'), + '_Token' => compact('key', 'fields') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertFalse($result, 'validatePost passed when key was missing. %s'); + } + +/** + * Tests validation of checkbox arrays + * + * @access public + * @return void + */ + function testValidatePostArray() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = 'f7d573650a295b94e0938d32b323fde775e5f32b%3A'; + + $this->Controller->data = array( + 'Model' => array('multi_field' => array('1', '3')), + '_Token' => compact('key', 'fields') + ); + $this->assertTrue($this->Controller->Security->validatePost($this->Controller)); + } + +/** + * testValidatePostNoModel method + * + * @access public + * @return void + */ + function testValidatePostNoModel() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '540ac9c60d323c22bafe997b72c0790f39a8bdef%3A'; + + $this->Controller->data = array( + 'anything' => 'some_data', + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testValidatePostSimple method + * + * @access public + * @return void + */ + function testValidatePostSimple() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '69f493434187b867ea14b901fdf58b55d27c935d%3A'; + + $this->Controller->data = $data = array( + 'Model' => array('username' => '', 'password' => ''), + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * Tests hash validation for multiple records, including locked fields + * + * @access public + * @return void + */ + function testValidatePostComplex() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = 'c9118120e680a7201b543f562e5301006ccfcbe2%3AAddresses.0.id%7CAddresses.1.id'; + + $this->Controller->data = array( + 'Addresses' => array( + '0' => array( + 'id' => '123456', 'title' => '', 'first_name' => '', 'last_name' => '', + 'address' => '', 'city' => '', 'phone' => '', 'primary' => '' + ), + '1' => array( + 'id' => '654321', 'title' => '', 'first_name' => '', 'last_name' => '', + 'address' => '', 'city' => '', 'phone' => '', 'primary' => '' + ) + ), + '_Token' => compact('key', 'fields') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * test ValidatePost with multiple select elements. + * + * @return void + */ + function testValidatePostMultipleSelect() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '422cde416475abc171568be690a98cad20e66079%3A'; + + $this->Controller->data = array( + 'Tag' => array('Tag' => array(1, 2)), + '_Token' => compact('key', 'fields'), + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + + $this->Controller->data = array( + 'Tag' => array('Tag' => array(1, 2, 3)), + '_Token' => compact('key', 'fields'), + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + + $this->Controller->data = array( + 'Tag' => array('Tag' => array(1, 2, 3, 4)), + '_Token' => compact('key', 'fields'), + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + + $fields = '19464422eafe977ee729c59222af07f983010c5f%3A'; + $this->Controller->data = array( + 'User.password' => 'bar', 'User.name' => 'foo', 'User.is_valid' => '1', + 'Tag' => array('Tag' => array(1)), '_Token' => compact('key', 'fields'), + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testValidatePostCheckbox method + * + * First block tests un-checked checkbox + * Second block tests checked checkbox + * + * @access public + * @return void + */ + function testValidatePostCheckbox() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid'; + + $this->Controller->data = array( + 'Model' => array('username' => '', 'password' => '', 'valid' => '0'), + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + + $fields = '874439ca69f89b4c4a5f50fb9c36ff56a28f5d42%3A'; + + $this->Controller->data = array( + 'Model' => array('username' => '', 'password' => '', 'valid' => '0'), + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + + + $this->Controller->data = array(); + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + + $this->Controller->data = $data = array( + 'Model' => array('username' => '', 'password' => '', 'valid' => '0'), + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testValidatePostHidden method + * + * @access public + * @return void + */ + function testValidatePostHidden() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '51ccd8cb0997c7b3d4523ecde5a109318405ef8c%3AModel.hidden%7CModel.other_hidden'; + $fields .= ''; + + $this->Controller->data = array( + 'Model' => array( + 'username' => '', 'password' => '', 'hidden' => '0', + 'other_hidden' => 'some hidden value' + ), + '_Token' => compact('key', 'fields') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testValidatePostWithDisabledFields method + * + * @access public + * @return void + */ + function testValidatePostWithDisabledFields() { + $this->Controller->Security->disabledFields = array('Model.username', 'Model.password'); + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = 'ef1082968c449397bcd849f963636864383278b1%3AModel.hidden'; + + $this->Controller->data = array( + 'Model' => array( + 'username' => '', 'password' => '', 'hidden' => '0' + ), + '_Token' => compact('fields', 'key') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testValidateHiddenMultipleModel method + * + * @access public + * @return void + */ + function testValidateHiddenMultipleModel() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = 'a2d01072dc4660eea9d15007025f35a7a5b58e18%3AModel.valid%7CModel2.valid%7CModel3.valid'; + + $this->Controller->data = array( + 'Model' => array('username' => '', 'password' => '', 'valid' => '0'), + 'Model2' => array('valid' => '0'), + 'Model3' => array('valid' => '0'), + '_Token' => compact('key', 'fields') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testLoginValidation method + * + * @access public + * @return void + */ + function testLoginValidation() { + + } + +/** + * testValidateHasManyModel method + * + * @access public + * @return void + */ + function testValidateHasManyModel() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '51e3b55a6edd82020b3f29c9ae200e14bbeb7ee5%3AModel.0.hidden%7CModel.0.valid'; + $fields .= '%7CModel.1.hidden%7CModel.1.valid'; + + $this->Controller->data = array( + 'Model' => array( + array( + 'username' => 'username', 'password' => 'password', + 'hidden' => 'value', 'valid' => '0' + ), + array( + 'username' => 'username', 'password' => 'password', + 'hidden' => 'value', 'valid' => '0' + ) + ), + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testValidateHasManyRecordsPass method + * + * @access public + * @return void + */ + function testValidateHasManyRecordsPass() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C'; + $fields .= 'Address.1.id%7CAddress.1.primary'; + + $this->Controller->data = array( + 'Address' => array( + 0 => array( + 'id' => '123', + 'title' => 'home', + 'first_name' => 'Bilbo', + 'last_name' => 'Baggins', + 'address' => '23 Bag end way', + 'city' => 'the shire', + 'phone' => 'N/A', + 'primary' => '1', + ), + 1 => array( + 'id' => '124', + 'title' => 'home', + 'first_name' => 'Frodo', + 'last_name' => 'Baggins', + 'address' => '50 Bag end way', + 'city' => 'the shire', + 'phone' => 'N/A', + 'primary' => '1' + ) + ), + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testValidateHasManyRecords method + * + * validatePost should fail, hidden fields have been changed. + * + * @access public + * @return void + */ + function testValidateHasManyRecordsFail() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C'; + $fields .= 'Address.1.id%7CAddress.1.primary'; + + $this->Controller->data = array( + 'Address' => array( + 0 => array( + 'id' => '123', + 'title' => 'home', + 'first_name' => 'Bilbo', + 'last_name' => 'Baggins', + 'address' => '23 Bag end way', + 'city' => 'the shire', + 'phone' => 'N/A', + 'primary' => '5', + ), + 1 => array( + 'id' => '124', + 'title' => 'home', + 'first_name' => 'Frodo', + 'last_name' => 'Baggins', + 'address' => '50 Bag end way', + 'city' => 'the shire', + 'phone' => 'N/A', + 'primary' => '1' + ) + ), + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertFalse($result); + } + +/** + * testLoginRequest method + * + * @access public + * @return void + */ + function testLoginRequest() { + $this->Controller->Security->startup($this->Controller); + $realm = 'cakephp.org'; + $options = array('realm' => $realm, 'type' => 'basic'); + $result = $this->Controller->Security->loginRequest($options); + $expected = 'WWW-Authenticate: Basic realm="'.$realm.'"'; + $this->assertEqual($result, $expected); + + $this->Controller->Security->startup($this->Controller); + $options = array('realm' => $realm, 'type' => 'digest'); + $result = $this->Controller->Security->loginRequest($options); + $this->assertPattern('/realm="'.$realm.'"/', $result); + $this->assertPattern('/qop="auth"/', $result); + } + +/** + * testGenerateDigestResponseHash method + * + * @access public + * @return void + */ + function testGenerateDigestResponseHash() { + $this->Controller->Security->startup($this->Controller); + $realm = 'cakephp.org'; + $loginData = array('realm' => $realm, 'users' => array('Willy Smith' => 'password')); + $this->Controller->Security->requireLogin($loginData); + + $data = array( + 'username' => 'Willy Smith', + 'password' => 'password', + 'nonce' => String::uuid(), + 'nc' => 1, + 'cnonce' => 1, + 'realm' => $realm, + 'uri' => 'path_to_identifier', + 'qop' => 'testme' + ); + $_SERVER['REQUEST_METHOD'] = 'POST'; + + $result = $this->Controller->Security->generateDigestResponseHash($data); + $expected = md5( + md5($data['username'] . ':' . $loginData['realm'] . ':' . $data['password']) . ':' . + $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . + md5(env('REQUEST_METHOD') . ':' . $data['uri']) + ); + $this->assertIdentical($result, $expected); + } + +/** + * testLoginCredentials method + * + * @access public + * @return void + */ + function testLoginCredentials() { + $this->Controller->Security->startup($this->Controller); + $_SERVER['PHP_AUTH_USER'] = $user = 'Willy Test'; + $_SERVER['PHP_AUTH_PW'] = $pw = 'some password for the nice test'; + + $result = $this->Controller->Security->loginCredentials('basic'); + $expected = array('username' => $user, 'password' => $pw); + $this->assertIdentical($result, $expected); + + if (version_compare(PHP_VERSION, '5.1') != -1) { + $_SERVER['PHP_AUTH_DIGEST'] = $digest = << 'Mufasa', + 'realm' => 'testrealm@host.com', + 'nonce' => 'dcd98b7102dd2f0e8b11d0f600bfb0c093', + 'uri' => '/dir/index.html', + 'qop' => 'auth', + 'nc' => '00000001', + 'cnonce' => '0a4f113b', + 'response' => '6629fae49393a05397450978507c4ef1', + 'opaque' => '5ccc069c403ebaf9f0171e9517f40e41' + ); + $result = $this->Controller->Security->loginCredentials('digest'); + $this->assertIdentical($result, $expected); + } + } + +/** + * testParseDigestAuthData method + * + * @access public + * @return void + */ + function testParseDigestAuthData() { + $this->Controller->Security->startup($this->Controller); + $digest = << 'Mufasa', + 'realm' => 'testrealm@host.com', + 'nonce' => 'dcd98b7102dd2f0e8b11d0f600bfb0c093', + 'uri' => '/dir/index.html', + 'qop' => 'auth', + 'nc' => '00000001', + 'cnonce' => '0a4f113b', + 'response' => '6629fae49393a05397450978507c4ef1', + 'opaque' => '5ccc069c403ebaf9f0171e9517f40e41' + ); + $result = $this->Controller->Security->parseDigestAuthData($digest); + $this->assertIdentical($result, $expected); + + $result = $this->Controller->Security->parseDigestAuthData(''); + $this->assertNull($result); + } + +/** + * test parsing digest information with email addresses + * + * @return void + */ + function testParseDigestAuthEmailAddress() { + $this->Controller->Security->startup($this->Controller); + $digest = << 'mark@example.com', + 'realm' => 'testrealm@host.com', + 'nonce' => 'dcd98b7102dd2f0e8b11d0f600bfb0c093', + 'uri' => '/dir/index.html', + 'qop' => 'auth', + 'nc' => '00000001', + 'cnonce' => '0a4f113b', + 'response' => '6629fae49393a05397450978507c4ef1', + 'opaque' => '5ccc069c403ebaf9f0171e9517f40e41' + ); + $result = $this->Controller->Security->parseDigestAuthData($digest); + $this->assertIdentical($result, $expected); + } + +/** + * testFormDisabledFields method + * + * @access public + * @return void + */ + function testFormDisabledFields() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '11842060341b9d0fc3808b90ba29fdea7054d6ad%3An%3A0%3A%7B%7D'; + + $this->Controller->data = array( + 'MyModel' => array('name' => 'some data'), + '_Token' => compact('key', 'fields') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertFalse($result); + + $this->Controller->Security->startup($this->Controller); + $this->Controller->Security->disabledFields = array('MyModel.name'); + $key = $this->Controller->params['_Token']['key']; + + $this->Controller->data = array( + 'MyModel' => array('name' => 'some data'), + '_Token' => compact('key', 'fields') + ); + + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testRadio method + * + * @access public + * @return void + */ + function testRadio() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + $fields = '575ef54ca4fc8cab468d6d898e9acd3a9671c17e%3An%3A0%3A%7B%7D'; + + $this->Controller->data = array( + '_Token' => compact('key', 'fields') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertFalse($result); + + $this->Controller->data = array( + '_Token' => compact('key', 'fields'), + 'Test' => array('test' => '') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + + $this->Controller->data = array( + '_Token' => compact('key', 'fields'), + 'Test' => array('test' => '1') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + + $this->Controller->data = array( + '_Token' => compact('key', 'fields'), + 'Test' => array('test' => '2') + ); + $result = $this->Controller->Security->validatePost($this->Controller); + $this->assertTrue($result); + } + +/** + * testInvalidAuthHeaders method + * + * @access public + * @return void + */ + function testInvalidAuthHeaders() { + $this->Controller->Security->blackHoleCallback = null; + $_SERVER['PHP_AUTH_USER'] = 'admin'; + $_SERVER['PHP_AUTH_PW'] = 'password'; + $realm = 'cakephp.org'; + $loginData = array('type' => 'basic', 'realm' => $realm); + $this->Controller->Security->requireLogin($loginData); + $this->Controller->Security->startup($this->Controller); + + $expected = 'WWW-Authenticate: Basic realm="'.$realm.'"'; + $this->assertEqual(count($this->Controller->testHeaders), 1); + $this->assertEqual(current($this->Controller->testHeaders), $expected); + } + +/** + * test that a requestAction's controller will have the _Token appended to + * the params. + * + * @return void + * @see http://cakephp.lighthouseapp.com/projects/42648/tickets/68 + */ + function testSettingTokenForRequestAction() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + + $this->Controller->params['requested'] = 1; + unset($this->Controller->params['_Token']); + + $this->Controller->Security->startup($this->Controller); + $this->assertEqual($this->Controller->params['_Token']['key'], $key); + } + +/** + * test that blackhole doesn't delete the _Token session key so repeat data submissions + * stay blackholed. + * + * @link http://cakephp.lighthouseapp.com/projects/42648/tickets/214 + * @return void + */ + function testBlackHoleNotDeletingSessionInformation() { + $this->Controller->Security->startup($this->Controller); + + $this->Controller->Security->blackHole($this->Controller, 'auth'); + $this->assertTrue($this->Controller->Security->Session->check('_Token'), '_Token was deleted by blackHole %s'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/session.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/session.test.php new file mode 100644 index 000000000..78fe1ad10 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/components/session.test.php @@ -0,0 +1,385 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + * @since CakePHP(tm) v 1.2.0.5436 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Controller', 'Controller', false); +App::import('Component', 'Session'); + +/** + * SessionTestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class SessionTestController extends Controller { + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * session_id method + * + * @return string + * @access public + */ + function session_id() { + return $this->Session->id(); + } +} + +/** + * OrangeSessionTestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class OrangeSessionTestController extends Controller { + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * session_id method + * + * @return string + * @access public + */ + function session_id() { + return $this->Session->id(); + } +} + +/** + * SessionComponentTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class SessionComponentTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_session = Configure::read('Session'); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('Session', $this->_session); + } + +/** + * testSessionAutoStart method + * + * @access public + * @return void + */ + function testSessionAutoStart() { + Configure::write('Session.start', false); + $Session =& new SessionComponent(); + $this->assertFalse($Session->__active); + $this->assertFalse($Session->started()); + $Session->startup(new SessionTestController()); + + Configure::write('Session.start', true); + $Session =& new SessionComponent(); + $this->assertTrue($Session->__active); + $this->assertFalse($Session->started()); + $Session->startup(new SessionTestController()); + $this->assertTrue(isset($_SESSION)); + + $Object = new Object(); + $Session =& new SessionComponent(); + $Session->start(); + $expected = $Session->id(); + + $result = $Object->requestAction('/session_test/session_id'); + $this->assertEqual($result, $expected); + + $result = $Object->requestAction('/orange_session_test/session_id'); + $this->assertEqual($result, $expected); + } + +/** + * testSessionActivate method + * + * @access public + * @return void + */ + function testSessionActivate() { + $Session =& new SessionComponent(); + + $this->assertTrue($Session->__active); + $this->assertNull($Session->activate()); + $this->assertTrue($Session->__active); + + Configure::write('Session.start', false); + $Session =& new SessionComponent(); + $this->assertFalse($Session->__active); + $this->assertNull($Session->activate()); + $this->assertTrue($Session->__active); + Configure::write('Session.start', true); + $Session->destroy(); + } + +/** + * testSessionValid method + * + * @access public + * @return void + */ + function testSessionValid() { + $Session =& new SessionComponent(); + + $this->assertTrue($Session->valid()); + + $Session->_userAgent = 'rweerw'; + $this->assertFalse($Session->valid()); + + Configure::write('Session.start', false); + $Session =& new SessionComponent(); + $this->assertFalse($Session->__active); + $this->assertFalse($Session->valid()); + Configure::write('Session.start', true); + + $Session =& new SessionComponent(); + $Session->time = $Session->read('Config.time') + 1; + $this->assertFalse($Session->valid()); + + Configure::write('Session.checkAgent', false); + $Session =& new SessionComponent(); + $Session->time = $Session->read('Config.time') + 1; + $this->assertFalse($Session->valid()); + Configure::write('Session.checkAgent', true); + } + +/** + * testSessionError method + * + * @access public + * @return void + */ + function testSessionError() { + $Session =& new SessionComponent(); + + $this->assertFalse($Session->error()); + + Configure::write('Session.start', false); + $Session =& new SessionComponent(); + $this->assertFalse($Session->__active); + $this->assertFalse($Session->error()); + Configure::write('Session.start', true); + } + +/** + * testSessionReadWrite method + * + * @access public + * @return void + */ + function testSessionReadWrite() { + $Session =& new SessionComponent(); + + $this->assertFalse($Session->read('Test')); + + $this->assertTrue($Session->write('Test', 'some value')); + $this->assertEqual($Session->read('Test'), 'some value'); + $this->assertFalse($Session->write('Test.key', 'some value')); + $Session->delete('Test'); + + $this->assertTrue($Session->write('Test.key.path', 'some value')); + $this->assertEqual($Session->read('Test.key.path'), 'some value'); + $this->assertEqual($Session->read('Test.key'), array('path' => 'some value')); + $this->assertTrue($Session->write('Test.key.path2', 'another value')); + $this->assertEqual($Session->read('Test.key'), array('path' => 'some value', 'path2' => 'another value')); + $Session->delete('Test'); + + $array = array('key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3'); + $this->assertTrue($Session->write('Test', $array)); + $this->assertEqual($Session->read('Test'), $array); + $Session->delete('Test'); + + $this->assertFalse($Session->write(array('Test'), 'some value')); + $this->assertTrue($Session->write(array('Test' => 'some value'))); + $this->assertEqual($Session->read('Test'), 'some value'); + $Session->delete('Test'); + + Configure::write('Session.start', false); + $Session =& new SessionComponent(); + $this->assertFalse($Session->write('Test', 'some value')); + $Session->write('Test', 'some value'); + $this->assertFalse($Session->read('Test')); + Configure::write('Session.start', true); + } + +/** + * testSessionDelete method + * + * @access public + * @return void + */ + function testSessionDelete() { + $Session =& new SessionComponent(); + + $this->assertFalse($Session->delete('Test')); + + $Session->write('Test', 'some value'); + $this->assertTrue($Session->delete('Test')); + + Configure::write('Session.start', false); + $Session =& new SessionComponent(); + $Session->write('Test', 'some value'); + $this->assertFalse($Session->delete('Test')); + Configure::write('Session.start', true); + } + +/** + * testSessionCheck method + * + * @access public + * @return void + */ + function testSessionCheck() { + $Session =& new SessionComponent(); + + $this->assertFalse($Session->check('Test')); + + $Session->write('Test', 'some value'); + $this->assertTrue($Session->check('Test')); + $Session->delete('Test'); + + Configure::write('Session.start', false); + $Session =& new SessionComponent(); + $Session->write('Test', 'some value'); + $this->assertFalse($Session->check('Test')); + Configure::write('Session.start', true); + } + +/** + * testSessionFlash method + * + * @access public + * @return void + */ + function testSessionFlash() { + $Session =& new SessionComponent(); + + $this->assertNull($Session->read('Message.flash')); + + $Session->setFlash('This is a test message'); + $this->assertEqual($Session->read('Message.flash'), array('message' => 'This is a test message', 'element' => 'default', 'params' => array())); + + $Session->setFlash('This is a test message', 'test', array('name' => 'Joel Moss')); + $this->assertEqual($Session->read('Message.flash'), array('message' => 'This is a test message', 'element' => 'test', 'params' => array('name' => 'Joel Moss'))); + + $Session->setFlash('This is a test message', 'default', array(), 'myFlash'); + $this->assertEqual($Session->read('Message.myFlash'), array('message' => 'This is a test message', 'element' => 'default', 'params' => array())); + + $Session->setFlash('This is a test message', 'non_existing_layout'); + $this->assertEqual($Session->read('Message.myFlash'), array('message' => 'This is a test message', 'element' => 'default', 'params' => array())); + + $Session->delete('Message'); + } + +/** + * testSessionId method + * + * @access public + * @return void + */ + function testSessionId() { + unset($_SESSION); + $Session =& new SessionComponent(); + $this->assertNull($Session->id()); + } + +/** + * testSessionDestroy method + * + * @access public + * @return void + */ + function testSessionDestroy() { + $Session =& new SessionComponent(); + + $Session->write('Test', 'some value'); + $this->assertEqual($Session->read('Test'), 'some value'); + $Session->destroy('Test'); + $this->assertNull($Session->read('Test')); + } + +/** + * testSessionTimeout method + * + * @access public + * @return void + */ + function testSessionTimeout() { + Configure::write('debug', 2); + Configure::write('Security.level', 'low'); + + session_destroy(); + $Session =& new SessionComponent(); + $Session->destroy(); + $Session->write('Test', 'some value'); + $this->assertEqual($Session->sessionTime, time() + (300 * Configure::read('Session.timeout'))); + $this->assertEqual($_SESSION['Config']['timeout'], 10); + $this->assertEqual($_SESSION['Config']['time'], $Session->sessionTime); + $this->assertEqual($Session->time, time()); + $this->assertEqual($_SESSION['Config']['time'], $Session->time + (300 * Configure::read('Session.timeout'))); + + Configure::write('Security.level', 'medium'); + $Session =& new SessionComponent(); + $Session->destroy(); + $Session->write('Test', 'some value'); + $this->assertEqual($Session->sessionTime, mktime() + (100 * Configure::read('Session.timeout'))); + $this->assertEqual($_SESSION['Config']['timeout'], 10); + $this->assertEqual($_SESSION['Config']['time'], $Session->sessionTime); + $this->assertEqual($Session->time, time()); + $this->assertEqual($_SESSION['Config']['time'], $Session->time + (Security::inactiveMins() * Configure::read('Session.timeout'))); + + Configure::write('Security.level', 'high'); + $Session =& new SessionComponent(); + $Session->destroy(); + $Session->write('Test', 'some value'); + $this->assertEqual($Session->sessionTime, time() + (10 * Configure::read('Session.timeout'))); + $this->assertEqual($_SESSION['Config']['timeout'], 10); + $this->assertEqual($_SESSION['Config']['time'], $Session->sessionTime); + $this->assertEqual($Session->time, time()); + $this->assertEqual($_SESSION['Config']['time'], $Session->time + (Security::inactiveMins() * Configure::read('Session.timeout'))); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/controller.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/controller.test.php new file mode 100644 index 000000000..55a9e53cd --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/controller.test.php @@ -0,0 +1,1498 @@ + 'error_msg'); + +/** + * lastQuery property + * + * @var mixed null + * @access public + */ + var $lastQuery = null; + +/** + * beforeFind method + * + * @param mixed $query + * @access public + * @return void + */ + function beforeFind($query) { + $this->lastQuery = $query; + } + +/** + * find method + * + * @param mixed $type + * @param array $options + * @access public + * @return void + */ + function find($type, $options = array()) { + if ($type == 'popular') { + $conditions = array($this->name . '.' . $this->primaryKey .' > ' => '1'); + $options = Set::merge($options, compact('conditions')); + return parent::find('all', $options); + } + return parent::find($type, $options); + } +} + +/** + * ControllerPostsController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ControllerCommentsController extends AppController { + +/** + * name property + * + * @var string 'ControllerPost' + * @access public + */ + var $name = 'ControllerComments'; +} + +/** + * ControllerComment class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ControllerComment extends CakeTestModel { + +/** + * name property + * + * @var string 'ControllerComment' + * @access public + */ + var $name = 'Comment'; + +/** + * useTable property + * + * @var string 'comments' + * @access public + */ + var $useTable = 'comments'; + +/** + * data property + * + * @var array + * @access public + */ + var $data = array('name' => 'Some Name'); + +/** + * alias property + * + * @var string 'ControllerComment' + * @access public + */ + var $alias = 'ControllerComment'; +} + +/** + * ControllerAlias class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ControllerAlias extends CakeTestModel { + +/** + * name property + * + * @var string 'ControllerAlias' + * @access public + */ + var $name = 'ControllerAlias'; + +/** + * alias property + * + * @var string 'ControllerSomeAlias' + * @access public + */ + var $alias = 'ControllerSomeAlias'; + +/** + * useTable property + * + * @var string 'posts' + * @access public + */ + var $useTable = 'posts'; +} + +/** + * ControllerPaginateModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ControllerPaginateModel extends CakeTestModel { + +/** + * name property + * + * @var string + * @access public + */ + var $name = 'ControllerPaginateModel'; + +/** + * useTable property + * + * @var string' + * @access public + */ + var $useTable = 'comments'; + +/** + * paginate method + * + * @return void + * @access public + */ + function paginate($conditions, $fields, $order, $limit, $page, $recursive, $extra) { + $this->extra = $extra; + } + +/** + * paginateCount + * + * @access public + * @return void + */ + function paginateCount($conditions, $recursive, $extra) { + $this->extraCount = $extra; + } +} + +/** + * NameTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class NameTest extends CakeTestModel { + +/** + * name property + * @var string 'Name' + * @access public + */ + var $name = 'Name'; + +/** + * useTable property + * @var string 'names' + * @access public + */ + var $useTable = 'comments'; + +/** + * alias property + * + * @var string 'ControllerComment' + * @access public + */ + var $alias = 'Name'; +} + +/** + * TestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class TestController extends AppController { + +/** + * name property + * @var string 'Name' + * @access public + */ + var $name = 'TestController'; + +/** + * helpers property + * + * @var array + * @access public + */ + var $helpers = array('Session', 'Xml'); + +/** + * components property + * + * @var array + * @access public + */ + var $components = array('Security'); + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array('ControllerComment', 'ControllerAlias'); + +/** + * index method + * + * @param mixed $testId + * @param mixed $test2Id + * @access public + * @return void + */ + function index($testId, $test2Id) { + $this->data['testId'] = $testId; + $this->data['test2Id'] = $test2Id; + } +} + +/** + * TestComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class TestComponent extends Object { + +/** + * beforeRedirect method + * + * @access public + * @return void + */ + function beforeRedirect() { + } +/** + * initialize method + * + * @access public + * @return void + */ + function initialize(&$controller) { + } + +/** + * startup method + * + * @access public + * @return void + */ + function startup(&$controller) { + } +/** + * shutdown method + * + * @access public + * @return void + */ + function shutdown(&$controller) { + } +/** + * beforeRender callback + * + * @return void + */ + function beforeRender(&$controller) { + if ($this->viewclass) { + $controller->view = $this->viewclass; + } + } +} + +/** + * AnotherTestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class AnotherTestController extends AppController { + +/** + * name property + * @var string 'Name' + * @access public + */ + var $name = 'AnotherTest'; +/** + * uses property + * + * @var array + * @access public + */ + var $uses = null; +} + +/** + * ControllerTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ControllerTest extends CakeTestCase { + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.post', 'core.comment', 'core.name'); + +/** + * endTest + * + * @access public + * @return void + */ + function endTest() { + App::build(); + } + +/** + * testLoadModel method + * + * @access public + * @return void + */ + function testLoadModel() { + $Controller =& new Controller(); + + $this->assertFalse(isset($Controller->ControllerPost)); + + $result = $Controller->loadModel('ControllerPost'); + $this->assertTrue($result); + $this->assertTrue(is_a($Controller->ControllerPost, 'ControllerPost')); + $this->assertTrue(in_array('ControllerPost', $Controller->modelNames)); + + ClassRegistry::flush(); + unset($Controller); + } + +/** + * testConstructClasses method + * + * @access public + * @return void + */ + function testConstructClasses() { + $Controller =& new Controller(); + $Controller->modelClass = 'ControllerPost'; + $Controller->passedArgs[] = '1'; + $Controller->constructClasses(); + $this->assertEqual($Controller->ControllerPost->id, 1); + + unset($Controller); + + $Controller =& new Controller(); + $Controller->uses = array('ControllerPost', 'ControllerComment'); + $Controller->passedArgs[] = '1'; + $Controller->constructClasses(); + $this->assertTrue(is_a($Controller->ControllerPost, 'ControllerPost')); + $this->assertTrue(is_a($Controller->ControllerComment, 'ControllerComment')); + + $this->assertEqual($Controller->ControllerComment->name, 'Comment'); + + unset($Controller); + + App::build(array('plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS))); + + $Controller =& new Controller(); + $Controller->uses = array('TestPlugin.TestPluginPost'); + $Controller->constructClasses(); + + $this->assertEqual($Controller->modelClass, 'TestPluginPost'); + $this->assertTrue(isset($Controller->TestPluginPost)); + $this->assertTrue(is_a($Controller->TestPluginPost, 'TestPluginPost')); + + unset($Controller); + } + +/** + * testAliasName method + * + * @access public + * @return void + */ + function testAliasName() { + $Controller =& new Controller(); + $Controller->uses = array('NameTest'); + $Controller->constructClasses(); + + $this->assertEqual($Controller->NameTest->name, 'Name'); + $this->assertEqual($Controller->NameTest->alias, 'Name'); + + unset($Controller); + } + +/** + * testPersistent method + * + * @access public + * @return void + */ + function testPersistent() { + Configure::write('Cache.disable', false); + $Controller =& new Controller(); + $Controller->modelClass = 'ControllerPost'; + $Controller->persistModel = true; + $Controller->constructClasses(); + $this->assertTrue(file_exists(CACHE . 'persistent' . DS .'controllerpost.php')); + $this->assertTrue(is_a($Controller->ControllerPost, 'ControllerPost')); + @unlink(CACHE . 'persistent' . DS . 'controllerpost.php'); + @unlink(CACHE . 'persistent' . DS . 'controllerpostregistry.php'); + + unset($Controller); + Configure::write('Cache.disable', true); + } + +/** + * testPaginate method + * + * @access public + * @return void + */ + function testPaginate() { + $Controller =& new Controller(); + $Controller->uses = array('ControllerPost', 'ControllerComment'); + $Controller->passedArgs[] = '1'; + $Controller->params['url'] = array(); + $Controller->constructClasses(); + + $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); + $this->assertEqual($results, array(1, 2, 3)); + + $results = Set::extract($Controller->paginate('ControllerComment'), '{n}.ControllerComment.id'); + $this->assertEqual($results, array(1, 2, 3, 4, 5, 6)); + + $Controller->modelClass = null; + + $Controller->uses[0] = 'Plugin.ControllerPost'; + $results = Set::extract($Controller->paginate(), '{n}.ControllerPost.id'); + $this->assertEqual($results, array(1, 2, 3)); + + $Controller->passedArgs = array('page' => '-1'); + $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertEqual($results, array(1, 2, 3)); + + $Controller->passedArgs = array('sort' => 'ControllerPost.id', 'direction' => 'asc'); + $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertEqual($results, array(1, 2, 3)); + + $Controller->passedArgs = array('sort' => 'ControllerPost.id', 'direction' => 'desc'); + $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertEqual($results, array(3, 2, 1)); + + $Controller->passedArgs = array('sort' => 'id', 'direction' => 'desc'); + $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertEqual($results, array(3, 2, 1)); + + $Controller->passedArgs = array('sort' => 'NotExisting.field', 'direction' => 'desc'); + $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['page'], 1, 'Invalid field in query %s'); + $this->assertEqual($results, array(1, 2, 3)); + + $Controller->passedArgs = array('sort' => 'ControllerPost.author_id', 'direction' => 'allYourBase'); + $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); + $this->assertEqual($Controller->ControllerPost->lastQuery['order'][0], array('ControllerPost.author_id' => 'asc')); + $this->assertEqual($results, array(1, 3, 2)); + + $Controller->passedArgs = array('page' => '1 " onclick="alert(\'xss\');">'); + $Controller->paginate = array('limit' => 1); + $Controller->paginate('ControllerPost'); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['page'], 1, 'XSS exploit opened %s'); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['options']['page'], 1, 'XSS exploit opened %s'); + + $Controller->passedArgs = array(); + $Controller->paginate = array('limit' => 0); + $Controller->paginate('ControllerPost'); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['pageCount'], 3); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['prevPage'], false); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['nextPage'], true); + + $Controller->passedArgs = array(); + $Controller->paginate = array('limit' => 'garbage!'); + $Controller->paginate('ControllerPost'); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['pageCount'], 3); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['prevPage'], false); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['nextPage'], true); + + $Controller->passedArgs = array(); + $Controller->paginate = array('limit' => '-1'); + $Controller->paginate('ControllerPost'); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['pageCount'], 3); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['prevPage'], false); + $this->assertIdentical($Controller->params['paging']['ControllerPost']['nextPage'], true); + } + +/** + * testPaginateExtraParams method + * + * @access public + * @return void + */ + function testPaginateExtraParams() { + $Controller =& new Controller(); + $Controller->uses = array('ControllerPost', 'ControllerComment'); + $Controller->passedArgs[] = '1'; + $Controller->params['url'] = array(); + $Controller->constructClasses(); + + $Controller->passedArgs = array('page' => '-1', 'contain' => array('ControllerComment')); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertEqual(Set::extract($result, '{n}.ControllerPost.id'), array(1, 2, 3)); + $this->assertTrue(!isset($Controller->ControllerPost->lastQuery['contain'])); + + $Controller->passedArgs = array('page' => '-1'); + $Controller->paginate = array('ControllerPost' => array('contain' => array('ControllerComment'))); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['page'], 1); + $this->assertEqual(Set::extract($result, '{n}.ControllerPost.id'), array(1, 2, 3)); + $this->assertTrue(isset($Controller->ControllerPost->lastQuery['contain'])); + + $Controller->paginate = array('ControllerPost' => array('popular', 'fields' => array('id', 'title'))); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual(Set::extract($result, '{n}.ControllerPost.id'), array(2, 3)); + $this->assertEqual($Controller->ControllerPost->lastQuery['conditions'], array('ControllerPost.id > ' => '1')); + + $Controller->passedArgs = array('limit' => 12); + $Controller->paginate = array('limit' => 30); + $result = $Controller->paginate('ControllerPost'); + $paging = $Controller->params['paging']['ControllerPost']; + + $this->assertEqual($Controller->ControllerPost->lastQuery['limit'], 12); + $this->assertEqual($paging['options']['limit'], 12); + + $Controller =& new Controller(); + $Controller->uses = array('ControllerPaginateModel'); + $Controller->params['url'] = array(); + $Controller->constructClasses(); + $Controller->paginate = array( + 'ControllerPaginateModel' => array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id') + ); + $result = $Controller->paginate('ControllerPaginateModel'); + $expected = array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id'); + $this->assertEqual($Controller->ControllerPaginateModel->extra, $expected); + $this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected); + + $Controller->paginate = array( + 'ControllerPaginateModel' => array('foo', 'contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id') + ); + $Controller->paginate('ControllerPaginateModel'); + $expected = array('contain' => array('ControllerPaginateModel'), 'group' => 'Comment.author_id', 'type' => 'foo'); + $this->assertEqual($Controller->ControllerPaginateModel->extra, $expected); + $this->assertEqual($Controller->ControllerPaginateModel->extraCount, $expected); + } + +/** + * testPaginateFieldsDouble method + * + * @return void + * @access public + */ + function testPaginateFieldsDouble(){ + $Controller =& new Controller(); + $Controller->uses = array('ControllerPost'); + $Controller->params['url'] = array(); + $Controller->constructClasses(); + + $Controller->paginate = array( + 'fields' => array( + 'ControllerPost.id', + 'radians(180.0) as floatvalue' + ), + 'order' => array('ControllerPost.created'=>'DESC'), + 'limit' => 1, + 'page' => 1, + 'recursive' => -1 + ); + $conditions = array(); + $result = $Controller->paginate('ControllerPost',$conditions); + $expected = array( + array( + 'ControllerPost' => array( + 'id' => 3, + ), + 0 => array( + 'floatvalue' => '3.14159265358979', + ), + ), + ); + $this->assertEqual($result, $expected); + } + + +/** + * testPaginatePassedArgs method + * + * @return void + * @access public + */ + function testPaginatePassedArgs() { + $Controller =& new Controller(); + $Controller->uses = array('ControllerPost'); + $Controller->passedArgs[] = array('1', '2', '3'); + $Controller->params['url'] = array(); + $Controller->constructClasses(); + + $Controller->paginate = array( + 'fields' => array(), + 'order' => '', + 'limit' => 5, + 'page' => 1, + 'recursive' => -1 + ); + $conditions = array(); + $Controller->paginate('ControllerPost',$conditions); + + $expected = array( + 'fields' => array(), + 'order' => '', + 'limit' => 5, + 'page' => 1, + 'recursive' => -1, + 'conditions' => array() + ); + $this->assertEqual($Controller->params['paging']['ControllerPost']['options'],$expected); + } + +/** + * Test that special paginate types are called and that the type param doesn't leak out into defaults or options. + * + * @return void + */ + function testPaginateSpecialType() { + $Controller =& new Controller(); + $Controller->uses = array('ControllerPost', 'ControllerComment'); + $Controller->passedArgs[] = '1'; + $Controller->params['url'] = array(); + $Controller->constructClasses(); + + $Controller->paginate = array('ControllerPost' => array('popular', 'fields' => array('id', 'title'))); + $result = $Controller->paginate('ControllerPost'); + + $this->assertEqual(Set::extract($result, '{n}.ControllerPost.id'), array(2, 3)); + $this->assertEqual($Controller->ControllerPost->lastQuery['conditions'], array('ControllerPost.id > ' => '1')); + $this->assertFalse(isset($Controller->params['paging']['ControllerPost']['defaults'][0])); + $this->assertFalse(isset($Controller->params['paging']['ControllerPost']['options'][0])); + } + +/** + * testDefaultPaginateParams method + * + * @access public + * @return void + */ + function testDefaultPaginateParams() { + $Controller =& new Controller(); + $Controller->modelClass = 'ControllerPost'; + $Controller->params['url'] = array(); + $Controller->paginate = array('order' => 'ControllerPost.id DESC'); + $Controller->constructClasses(); + $results = Set::extract($Controller->paginate('ControllerPost'), '{n}.ControllerPost.id'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['defaults']['order'], 'ControllerPost.id DESC'); + $this->assertEqual($Controller->params['paging']['ControllerPost']['options']['order'], 'ControllerPost.id DESC'); + $this->assertEqual($results, array(3, 2, 1)); + } + +/** + * test paginate() and virtualField interactions + * + * @return void + */ + function testPaginateOrderVirtualField() { + $Controller =& new Controller(); + $Controller->uses = array('ControllerPost', 'ControllerComment'); + $Controller->params['url'] = array(); + $Controller->constructClasses(); + $Controller->ControllerPost->virtualFields = array( + 'offset_test' => 'ControllerPost.id + 1' + ); + + $Controller->paginate = array( + 'fields' => array('id', 'title', 'offset_test'), + 'order' => array('offset_test' => 'DESC') + ); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual(Set::extract($result, '{n}.ControllerPost.offset_test'), array(4, 3, 2)); + + $Controller->passedArgs = array('sort' => 'offset_test', 'direction' => 'asc'); + $result = $Controller->paginate('ControllerPost'); + $this->assertEqual(Set::extract($result, '{n}.ControllerPost.offset_test'), array(2, 3, 4)); + } + +/** + * testFlash method + * + * @access public + * @return void + */ + function testFlash() { + $Controller =& new Controller(); + $Controller->flash('this should work', '/flash'); + $result = $Controller->output; + + $expected = ' + + + + this should work + + + +

this should work

+ + '; + $result = str_replace(array("\t", "\r\n", "\n"), "", $result); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $expected); + $this->assertEqual($result, $expected); + + App::build(array('views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS))); + $Controller =& new Controller(); + $Controller->flash('this should work', '/flash', 1, 'ajax2'); + $result = $Controller->output; + $this->assertPattern('/Ajax!/', $result); + App::build(); + } + +/** + * testControllerSet method + * + * @access public + * @return void + */ + function testControllerSet() { + $Controller =& new Controller(); + $Controller->set('variable_with_underscores', null); + $this->assertTrue(array_key_exists('variable_with_underscores', $Controller->viewVars)); + + $Controller->viewVars = array(); + $viewVars = array('ModelName' => array('id' => 1, 'name' => 'value')); + $Controller->set($viewVars); + $this->assertTrue(array_key_exists('ModelName', $Controller->viewVars)); + + $Controller->viewVars = array(); + $Controller->set('variable_with_underscores', 'value'); + $this->assertTrue(array_key_exists('variable_with_underscores', $Controller->viewVars)); + + $Controller->viewVars = array(); + $viewVars = array('ModelName' => 'name'); + $Controller->set($viewVars); + $this->assertTrue(array_key_exists('ModelName', $Controller->viewVars)); + + $Controller->set('title', 'someTitle'); + $this->assertIdentical($Controller->viewVars['title'], 'someTitle'); + $this->assertTrue(empty($Controller->pageTitle)); + + $Controller->viewVars = array(); + $expected = array('ModelName' => 'name', 'ModelName2' => 'name2'); + $Controller->set(array('ModelName', 'ModelName2'), array('name', 'name2')); + $this->assertIdentical($Controller->viewVars, $expected); + + $Controller->viewVars = array(); + $Controller->set(array(3 => 'three', 4 => 'four')); + $Controller->set(array(1 => 'one', 2 => 'two')); + $expected = array(3 => 'three', 4 => 'four', 1 => 'one', 2 => 'two'); + $this->assertEqual($Controller->viewVars, $expected); + + } + +/** + * testRender method + * + * @access public + * @return void + */ + function testRender() { + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + ), true); + + $Controller =& new Controller(); + $Controller->viewPath = 'posts'; + + $result = $Controller->render('index'); + $this->assertPattern('/posts index/', $result); + + $result = $Controller->render('/elements/test_element'); + $this->assertPattern('/this is the test element/', $result); + + $Controller = new TestController(); + $Controller->constructClasses(); + $Controller->ControllerComment->validationErrors = array('title' => 'tooShort'); + $expected = $Controller->ControllerComment->validationErrors; + + ClassRegistry::flush(); + $Controller->viewPath = 'posts'; + $result = $Controller->render('index'); + $View = ClassRegistry::getObject('view'); + $this->assertTrue(isset($View->validationErrors['ControllerComment'])); + $this->assertEqual($expected, $View->validationErrors['ControllerComment']); + + $Controller->ControllerComment->validationErrors = array(); + ClassRegistry::flush(); + App::build(); + } + +/** + * test that a component beforeRender can change the controller view class. + * + * @return void + */ + function testComponentBeforeRenderChangingViewClass() { + $core = App::core('views'); + App::build(array( + 'views' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, + $core[0] + ) + ), true); + $Controller =& new Controller(); + $Controller->uses = array(); + $Controller->components = array('Test'); + $Controller->constructClasses(); + $Controller->Test->viewclass = 'Theme'; + $Controller->viewPath = 'posts'; + $Controller->theme = 'test_theme'; + $result = $Controller->render('index'); + $this->assertPattern('/default test_theme layout/', $result); + App::build(); + } + +/** + * testToBeInheritedGuardmethods method + * + * @access public + * @return void + */ + function testToBeInheritedGuardmethods() { + $Controller =& new Controller(); + $this->assertTrue($Controller->_beforeScaffold('')); + $this->assertTrue($Controller->_afterScaffoldSave('')); + $this->assertTrue($Controller->_afterScaffoldSaveError('')); + $this->assertFalse($Controller->_scaffoldError('')); + } + +/** + * testRedirect method + * + * @access public + * @return void + */ + function testRedirect() { + $codes = array( + 100 => "Continue", + 101 => "Switching Protocols", + 200 => "OK", + 201 => "Created", + 202 => "Accepted", + 203 => "Non-Authoritative Information", + 204 => "No Content", + 205 => "Reset Content", + 206 => "Partial Content", + 300 => "Multiple Choices", + 301 => "Moved Permanently", + 302 => "Found", + 303 => "See Other", + 304 => "Not Modified", + 305 => "Use Proxy", + 307 => "Temporary Redirect", + 400 => "Bad Request", + 401 => "Unauthorized", + 402 => "Payment Required", + 403 => "Forbidden", + 404 => "Not Found", + 405 => "Method Not Allowed", + 406 => "Not Acceptable", + 407 => "Proxy Authentication Required", + 408 => "Request Time-out", + 409 => "Conflict", + 410 => "Gone", + 411 => "Length Required", + 412 => "Precondition Failed", + 413 => "Request Entity Too Large", + 414 => "Request-URI Too Large", + 415 => "Unsupported Media Type", + 416 => "Requested range not satisfiable", + 417 => "Expectation Failed", + 500 => "Internal Server Error", + 501 => "Not Implemented", + 502 => "Bad Gateway", + 503 => "Service Unavailable", + 504 => "Gateway Time-out" + ); + + Mock::generatePartial('Controller', 'MockController', array('header')); + Mock::generate('TestComponent', 'MockTestComponent'); + Mock::generate('TestComponent', 'MockTestBComponent'); + + App::import('Helper', 'Cache'); + + foreach ($codes as $code => $msg) { + $MockController =& new MockController(); + $MockController->Component =& new Component(); + $MockController->Component->init($MockController); + $MockController->expectAt(0, 'header', array("HTTP/1.1 {$code} {$msg}")); + $MockController->expectAt(1, 'header', array('Location: http://cakephp.org')); + $MockController->expectCallCount('header', 2); + $MockController->redirect('http://cakephp.org', (int)$code, false); + $this->assertFalse($MockController->autoRender); + } + foreach ($codes as $code => $msg) { + $MockController =& new MockController(); + $MockController->Component =& new Component(); + $MockController->Component->init($MockController); + $MockController->expectAt(0, 'header', array("HTTP/1.1 {$code} {$msg}")); + $MockController->expectAt(1, 'header', array('Location: http://cakephp.org')); + $MockController->expectCallCount('header', 2); + $MockController->redirect('http://cakephp.org', $msg, false); + $this->assertFalse($MockController->autoRender); + } + + $MockController =& new MockController(); + $MockController->Component =& new Component(); + $MockController->Component->init($MockController); + $MockController->expectAt(0, 'header', array('Location: http://www.example.org/users/login')); + $MockController->expectCallCount('header', 1); + $MockController->redirect('http://www.example.org/users/login', null, false); + + $MockController =& new MockController(); + $MockController->Component =& new Component(); + $MockController->Component->init($MockController); + $MockController->expectAt(0, 'header', array('HTTP/1.1 301 Moved Permanently')); + $MockController->expectAt(1, 'header', array('Location: http://www.example.org/users/login')); + $MockController->expectCallCount('header', 2); + $MockController->redirect('http://www.example.org/users/login', 301, false); + + $MockController =& new MockController(); + $MockController->components = array('MockTest'); + $MockController->Component =& new Component(); + $MockController->Component->init($MockController); + $MockController->MockTest->setReturnValue('beforeRedirect', null); + $MockController->expectAt(0, 'header', array('HTTP/1.1 301 Moved Permanently')); + $MockController->expectAt(1, 'header', array('Location: http://cakephp.org')); + $MockController->expectCallCount('header', 2); + $MockController->redirect('http://cakephp.org', 301, false); + + $MockController =& new MockController(); + $MockController->components = array('MockTest'); + $MockController->Component =& new Component(); + $MockController->Component->init($MockController); + $MockController->MockTest->setReturnValue('beforeRedirect', 'http://book.cakephp.org'); + $MockController->expectAt(0, 'header', array('HTTP/1.1 301 Moved Permanently')); + $MockController->expectAt(1, 'header', array('Location: http://book.cakephp.org')); + $MockController->expectCallCount('header', 2); + $MockController->redirect('http://cakephp.org', 301, false); + + $MockController =& new MockController(); + $MockController->components = array('MockTest'); + $MockController->Component =& new Component(); + $MockController->Component->init($MockController); + $MockController->MockTest->setReturnValue('beforeRedirect', false); + $MockController->expectNever('header'); + $MockController->redirect('http://cakephp.org', 301, false); + + $MockController =& new MockController(); + $MockController->components = array('MockTest', 'MockTestB'); + $MockController->Component =& new Component(); + $MockController->Component->init($MockController); + $MockController->MockTest->setReturnValue('beforeRedirect', 'http://book.cakephp.org'); + $MockController->MockTestB->setReturnValue('beforeRedirect', 'http://bakery.cakephp.org'); + $MockController->expectAt(0, 'header', array('HTTP/1.1 301 Moved Permanently')); + $MockController->expectAt(1, 'header', array('Location: http://bakery.cakephp.org')); + $MockController->expectCallCount('header', 2); + $MockController->redirect('http://cakephp.org', 301, false); + } + +/** + * testMergeVars method + * + * @access public + * @return void + */ + function testMergeVars() { + if ($this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController')) { + return; + } + + $TestController =& new TestController(); + $TestController->constructClasses(); + + $testVars = get_class_vars('TestController'); + $appVars = get_class_vars('AppController'); + + $components = is_array($appVars['components']) + ? array_merge($appVars['components'], $testVars['components']) + : $testVars['components']; + if (!in_array('Session', $components)) { + $components[] = 'Session'; + } + $helpers = is_array($appVars['helpers']) + ? array_merge($appVars['helpers'], $testVars['helpers']) + : $testVars['helpers']; + $uses = is_array($appVars['uses']) + ? array_merge($appVars['uses'], $testVars['uses']) + : $testVars['uses']; + + $this->assertEqual(count(array_diff_assoc(Set::normalize($TestController->helpers), Set::normalize($helpers))), 0); + $this->assertEqual(count(array_diff($TestController->uses, $uses)), 0); + $this->assertEqual(count(array_diff_assoc(Set::normalize($TestController->components), Set::normalize($components))), 0); + + $expected = array('ControllerComment', 'ControllerAlias', 'ControllerPost'); + $this->assertEqual($expected, $TestController->uses, '$uses was merged incorrectly, AppController models should be last.'); + + + $TestController =& new AnotherTestController(); + $TestController->constructClasses(); + + $appVars = get_class_vars('AppController'); + $testVars = get_class_vars('AnotherTestController'); + + + $this->assertTrue(in_array('ControllerPost', $appVars['uses'])); + $this->assertNull($testVars['uses']); + + $this->assertFalse(isset($TestController->ControllerPost)); + + + $TestController =& new ControllerCommentsController(); + $TestController->constructClasses(); + + $appVars = get_class_vars('AppController'); + $testVars = get_class_vars('ControllerCommentsController'); + + + $this->assertTrue(in_array('ControllerPost', $appVars['uses'])); + $this->assertEqual(array('ControllerPost'), $testVars['uses']); + + $this->assertTrue(isset($TestController->ControllerPost)); + $this->assertTrue(isset($TestController->ControllerComment)); + } + +/** + * test that options from child classes replace those in the parent classes. + * + * @access public + * @return void + */ + function testChildComponentOptionsSupercedeParents() { + if ($this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController')) { + return; + } + $TestController =& new TestController(); + $expected = array('foo'); + $TestController->components = array('Cookie' => $expected); + $TestController->constructClasses(); + $this->assertEqual($TestController->components['Cookie'], $expected); + } + +/** + * Ensure that __mergeVars is not being greedy and merging with + * AppController when you make an instance of Controller + * + * @return void + */ + function testMergeVarsNotGreedy() { + $Controller =& new Controller(); + $Controller->components = array(); + $Controller->uses = array(); + $Controller->constructClasses(); + + $this->assertFalse(isset($Controller->Session)); + } + +/** + * testReferer method + * + * @access public + * @return void + */ + function testReferer() { + $Controller =& new Controller(); + $_SERVER['HTTP_REFERER'] = 'http://cakephp.org'; + $result = $Controller->referer(null, false); + $expected = 'http://cakephp.org'; + $this->assertIdentical($result, $expected); + + $_SERVER['HTTP_REFERER'] = ''; + $result = $Controller->referer('http://cakephp.org', false); + $expected = 'http://cakephp.org'; + $this->assertIdentical($result, $expected); + + $_SERVER['HTTP_REFERER'] = ''; + $referer = array( + 'controller' => 'pages', + 'action' => 'display', + 'home' + ); + $result = $Controller->referer($referer, false); + $expected = 'http://' . env('HTTP_HOST') . '/pages/display/home'; + $this->assertIdentical($result, $expected); + + $_SERVER['HTTP_REFERER'] = ''; + $result = $Controller->referer(null, false); + $expected = '/'; + $this->assertIdentical($result, $expected); + + $_SERVER['HTTP_REFERER'] = FULL_BASE_URL.$Controller->webroot.'/some/path'; + $result = $Controller->referer(null, false); + $expected = '/some/path'; + $this->assertIdentical($result, $expected); + + $Controller->webroot .= '/'; + $_SERVER['HTTP_REFERER'] = FULL_BASE_URL.$Controller->webroot.'/some/path'; + $result = $Controller->referer(null, false); + $expected = '/some/path'; + $this->assertIdentical($result, $expected); + + $_SERVER['HTTP_REFERER'] = FULL_BASE_URL.$Controller->webroot.'some/path'; + $result = $Controller->referer(null, false); + $expected = '/some/path'; + $this->assertIdentical($result, $expected); + + $Controller->webroot = '/recipe/'; + + $_SERVER['HTTP_REFERER'] = FULL_BASE_URL.$Controller->webroot.'recipes/add'; + $result = $Controller->referer(); + $expected = '/recipes/add'; + $this->assertIdentical($result, $expected); + } + +/** + * testSetAction method + * + * @access public + * @return void + */ + function testSetAction() { + $TestController =& new TestController(); + $TestController->setAction('index', 1, 2); + $expected = array('testId' => 1, 'test2Id' => 2); + $this->assertidentical($TestController->data, $expected); + } + +/** + * testUnimplementedIsAuthorized method + * + * @access public + * @return void + */ + function testUnimplementedIsAuthorized() { + $TestController =& new TestController(); + $TestController->isAuthorized(); + $this->assertError(); + } + +/** + * testValidateErrors method + * + * @access public + * @return void + */ + function testValidateErrors() { + $TestController =& new TestController(); + $TestController->constructClasses(); + $this->assertFalse($TestController->validateErrors()); + $this->assertEqual($TestController->validate(), 0); + + $TestController->ControllerComment->invalidate('some_field', 'error_message'); + $TestController->ControllerComment->invalidate('some_field2', 'error_message2'); + $comment =& new ControllerComment(); + $comment->set('someVar', 'data'); + $result = $TestController->validateErrors($comment); + $expected = array('some_field' => 'error_message', 'some_field2' => 'error_message2'); + $this->assertIdentical($result, $expected); + $this->assertEqual($TestController->validate($comment), 2); + } + +/** + * test that validateErrors works with any old model. + * + * @return void + */ + function testValidateErrorsOnArbitraryModels() { + $TestController =& new TestController(); + + $Post = new ControllerPost(); + $Post->validate = array('title' => 'notEmpty'); + $Post->set('title', ''); + $result = $TestController->validateErrors($Post); + + $expected = array('title' => 'This field cannot be left blank'); + $this->assertEqual($result, $expected); + } + +/** + * testPostConditions method + * + * @access public + * @return void + */ + function testPostConditions() { + $Controller =& new Controller(); + + + $data = array( + 'Model1' => array('field1' => '23'), + 'Model2' => array('field2' => 'string'), + 'Model3' => array('field3' => '23'), + ); + $expected = array( + 'Model1.field1' => '23', + 'Model2.field2' => 'string', + 'Model3.field3' => '23', + ); + $result = $Controller->postConditions($data); + $this->assertIdentical($result, $expected); + + + $data = array(); + $Controller->data = array( + 'Model1' => array('field1' => '23'), + 'Model2' => array('field2' => 'string'), + 'Model3' => array('field3' => '23'), + ); + $expected = array( + 'Model1.field1' => '23', + 'Model2.field2' => 'string', + 'Model3.field3' => '23', + ); + $result = $Controller->postConditions($data); + $this->assertIdentical($result, $expected); + + + $data = array(); + $Controller->data = array(); + $result = $Controller->postConditions($data); + $this->assertNull($result); + + + $data = array(); + $Controller->data = array( + 'Model1' => array('field1' => '23'), + 'Model2' => array('field2' => 'string'), + 'Model3' => array('field3' => '23'), + ); + $ops = array( + 'Model1.field1' => '>', + 'Model2.field2' => 'LIKE', + 'Model3.field3' => '<=', + ); + $expected = array( + 'Model1.field1 >' => '23', + 'Model2.field2 LIKE' => "%string%", + 'Model3.field3 <=' => '23', + ); + $result = $Controller->postConditions($data, $ops); + $this->assertIdentical($result, $expected); + } + +/** + * testRequestHandlerPrefers method + * + * @access public + * @return void + */ + function testRequestHandlerPrefers(){ + Configure::write('debug', 2); + $Controller =& new Controller(); + $Controller->components = array("RequestHandler"); + $Controller->modelClass='ControllerPost'; + $Controller->params['url']['ext'] = 'rss'; + $Controller->constructClasses(); + $Controller->Component->initialize($Controller); + $Controller->beforeFilter(); + $Controller->Component->startup($Controller); + + $this->assertEqual($Controller->RequestHandler->prefers(), 'rss'); + unset($Controller); + } + +/** + * testControllerHttpCodes method + * + * @access public + * @return void + */ + function testControllerHttpCodes() { + $Controller =& new Controller(); + $result = $Controller->httpCodes(); + $this->assertEqual(count($result), 39); + + $result = $Controller->httpCodes(100); + $expected = array(100 => 'Continue'); + $this->assertEqual($result, $expected); + + $codes = array( + 1337 => 'Undefined Unicorn', + 1729 => 'Hardy-Ramanujan Located' + ); + + $result = $Controller->httpCodes($codes); + $this->assertTrue($result); + $this->assertEqual(count($Controller->httpCodes()), 41); + + $result = $Controller->httpCodes(1337); + $expected = array(1337 => 'Undefined Unicorn'); + $this->assertEqual($result, $expected); + + $codes = array(404 => 'Sorry Bro'); + $result = $Controller->httpCodes($codes); + $this->assertTrue($result); + $this->assertEqual(count($Controller->httpCodes()), 41); + + $result = $Controller->httpCodes(404); + $expected = array(404 => 'Sorry Bro'); + $this->assertEqual($result, $expected); + } + +/** + * Tests that the startup process calls the correct functions + * + * @access public + * @return void + */ + function testStartupProcess() { + Mock::generatePartial('AnotherTestController','MockedController', array('beforeFilter', 'afterFilter')); + Mock::generate('TestComponent', 'MockTestComponent', array('startup', 'initialize')); + $MockedController =& new MockedController(); + $MockedController->components = array('MockTest'); + $MockedController->Component =& new Component(); + $MockedController->Component->init($MockedController); + $MockedController->expectCallCount('beforeFilter', 1); + $MockedController->MockTest->expectCallCount('initialize', 1); + $MockedController->MockTest->expectCallCount('startup', 1); + $MockedController->startupProcess(); + } +/** + * Tests that the shutdown process calls the correct functions + * + * @access public + * @return void + */ + function testShutdownProcess() { + Mock::generate('TestComponent', 'MockTestComponent', array('shutdown')); + $MockedController =& new MockedController(); + $MockedController->components = array('MockTest'); + $MockedController->Component =& new Component(); + $MockedController->Component->init($MockedController); + $MockedController->expectCallCount('afterFilter', 1); + $MockedController->MockTest->expectCallCount('shutdown', 1); + $MockedController->shutdownProcess(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/controller_merge_vars.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/controller_merge_vars.test.php new file mode 100644 index 000000000..b98aff1ff --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/controller_merge_vars.test.php @@ -0,0 +1,257 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller + * @since CakePHP(tm) v 1.2.3 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!class_exists('AppController')) { + +/** + * Test case AppController + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ + class AppController extends Controller { + +/** + * components + * + * @var array + */ + var $components = array('MergeVar' => array('flag', 'otherFlag', 'redirect' => false)); +/** + * helpers + * + * @var array + */ + var $helpers = array('MergeVar' => array('format' => 'html', 'terse')); + } +} elseif (!defined('APP_CONTROLLER_EXISTS')) { + define('APP_CONTROLLER_EXISTS', true); +} + +/** + * MergeVar Component + * + * @package cake.tests.cases.libs.controller + */ +class MergeVarComponent extends Object { + +} + +/** + * Additional controller for testing + * + * @package cake.tests.cases.libs.controller + */ +class MergeVariablesController extends AppController { + +/** + * name + * + * @var string + */ + var $name = 'MergeVariables'; + +/** + * uses + * + * @var arrays + */ + var $uses = array(); +} + +/** + * MergeVarPlugin App Controller + * + * @package cake.tests.cases.libs.controller + */ +class MergeVarPluginAppController extends AppController { + +/** + * components + * + * @var array + */ + var $components = array('Auth' => array('setting' => 'val', 'otherVal')); + +/** + * helpers + * + * @var array + */ + var $helpers = array('Javascript'); +} + +/** + * MergePostsController + * + * @package cake.tests.cases.libs.controller + */ +class MergePostsController extends MergeVarPluginAppController { + +/** + * name + * + * @var string + */ + var $name = 'MergePosts'; + +/** + * uses + * + * @var array + */ + var $uses = array(); +} + + +/** + * Test Case for Controller Merging of Vars. + * + * @package cake.tests.cases.libs.controller + */ +class ControllerMergeVarsTestCase extends CakeTestCase { +/** + * Skips the case if APP_CONTROLLER_EXISTS is defined + * + * @return void + */ + function skip() { + $this->skipIf(defined('APP_CONTROLLER_EXISTS'), 'APP_CONTROLLER_EXISTS cannot run. %s'); + } +/** + * end test + * + * @return void + */ + function endTest() { + ClassRegistry::flush(); + } + +/** + * test that component settings are not duplicated when merging component settings + * + * @return void + */ + function testComponentParamMergingNoDuplication() { + $Controller =& new MergeVariablesController(); + $Controller->constructClasses(); + + $expected = array('MergeVar' => array('flag', 'otherFlag', 'redirect' => false)); + $this->assertEqual($Controller->components, $expected, 'Duplication of settings occured. %s'); + } + +/** + * test component merges with redeclared components + * + * @return void + */ + function testComponentMergingWithRedeclarations() { + $Controller =& new MergeVariablesController(); + $Controller->components['MergeVar'] = array('remote', 'redirect' => true); + $Controller->constructClasses(); + + $expected = array('MergeVar' => array('flag', 'otherFlag', 'redirect' => true, 'remote')); + $this->assertEqual($Controller->components, $expected, 'Merging of settings is wrong. %s'); + } + +/** + * test merging of helpers array, ensure no duplication occurs + * + * @return void + */ + function testHelperSettingMergingNoDuplication() { + $Controller =& new MergeVariablesController(); + $Controller->constructClasses(); + + $expected = array('MergeVar' => array('format' => 'html', 'terse')); + $this->assertEqual($Controller->helpers, $expected, 'Duplication of settings occured. %s'); + } + +/** + * Test that helpers declared in appcontroller come before those in the subclass + * orderwise + * + * @return void + */ + function testHelperOrderPrecedence() { + $Controller =& new MergeVariablesController(); + $Controller->helpers = array('Custom', 'Foo' => array('something')); + $Controller->constructClasses(); + + $expected = array( + 'MergeVar' => array('format' => 'html', 'terse'), + 'Custom' => null, + 'Foo' => array('something') + ); + $this->assertIdentical($Controller->helpers, $expected, 'Order is incorrect. %s'); + } + +/** + * test merging of vars with plugin + * + * @return void + */ + function testMergeVarsWithPlugin() { + $Controller =& new MergePostsController(); + $Controller->components = array('Email' => array('ports' => 'open')); + $Controller->plugin = 'MergeVarPlugin'; + $Controller->constructClasses(); + + $expected = array( + 'MergeVar' => array('flag', 'otherFlag', 'redirect' => false), + 'Auth' => array('setting' => 'val', 'otherVal'), + 'Email' => array('ports' => 'open') + ); + $this->assertEqual($Controller->components, $expected, 'Components are unexpected %s'); + + $expected = array( + 'MergeVar' => array('format' => 'html', 'terse'), + 'Javascript' => null + ); + $this->assertEqual($Controller->helpers, $expected, 'Helpers are unexpected %s'); + + $Controller =& new MergePostsController(); + $Controller->components = array(); + $Controller->plugin = 'MergeVarPlugin'; + $Controller->constructClasses(); + + $expected = array( + 'MergeVar' => array('flag', 'otherFlag', 'redirect' => false), + 'Auth' => array('setting' => 'val', 'otherVal'), + ); + $this->assertEqual($Controller->components, $expected, 'Components are unexpected %s'); + } + +/** + * Ensure that __mergeVars is not being greedy and merging with + * AppController when you make an instance of Controller + * + * @return void + */ + function testMergeVarsNotGreedy() { + $Controller =& new Controller(); + $Controller->components = array(); + $Controller->uses = array(); + $Controller->constructClasses(); + + $this->assertFalse(isset($Controller->Session)); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/pages_controller.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/pages_controller.test.php new file mode 100644 index 000000000..a80cf56ed --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/pages_controller.test.php @@ -0,0 +1,72 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller + * @since CakePHP(tm) v 1.2.0.5436 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!class_exists('AppController')) { + require_once LIBS . 'controller' . DS . 'app_controller.php'; +} elseif (!defined('APP_CONTROLLER_EXISTS')) { + define('APP_CONTROLLER_EXISTS', true); +} +App::import('Controller', 'Pages'); + +/** + * PagesControllerTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class PagesControllerTest extends CakeTestCase { + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + App::build(); + } + +/** + * testDisplay method + * + * @access public + * @return void + */ + function testDisplay() { + if ($this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController')) { + return; + } + + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS) + )); + $Pages =& new PagesController(); + + $Pages->viewPath = 'posts'; + $Pages->display('index'); + $this->assertPattern('/posts index/', $Pages->output); + $this->assertEqual($Pages->viewVars['page'], 'index'); + + $Pages->viewPath = 'themed'; + $Pages->display('test_theme', 'posts', 'index'); + $this->assertPattern('/posts index themed view/', $Pages->output); + $this->assertEqual($Pages->viewVars['page'], 'test_theme'); + $this->assertEqual($Pages->viewVars['subpage'], 'posts'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/scaffold.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/scaffold.test.php new file mode 100644 index 000000000..808fa1caa --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/controller/scaffold.test.php @@ -0,0 +1,897 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller + * @since CakePHP(tm) v 1.2.0.5436 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Scaffold'); + +/** + * ScaffoldMockController class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ScaffoldMockController extends Controller { + +/** + * name property + * + * @var string 'ScaffoldMock' + * @access public + */ + var $name = 'ScaffoldMock'; + +/** + * scaffold property + * + * @var mixed + * @access public + */ + var $scaffold; +} + +/** + * ScaffoldMockControllerWithFields class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ScaffoldMockControllerWithFields extends Controller { + +/** + * name property + * + * @var string 'ScaffoldMock' + * @access public + */ + var $name = 'ScaffoldMock'; + +/** + * scaffold property + * + * @var mixed + * @access public + */ + var $scaffold; + +/** + * function _beforeScaffold + * + * @param string method + */ + function _beforeScaffold($method) { + $this->set('scaffoldFields', array('title')); + return true; + } +} + +/** + * TestScaffoldMock class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class TestScaffoldMock extends Scaffold { + +/** + * Overload __scaffold + * + * @param unknown_type $params + */ + function __scaffold($params) { + $this->_params = $params; + } + +/** + * Get Params from the Controller. + * + * @return unknown + */ + function getParams() { + return $this->_params; + } +} + +/** + * ScaffoldMock class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ScaffoldMock extends CakeTestModel { + +/** + * useTable property + * + * @var string 'posts' + * @access public + */ + var $useTable = 'articles'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'User' => array( + 'className' => 'ScaffoldUser', + 'foreignKey' => 'user_id', + ) + ); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array( + 'Comment' => array( + 'className' => 'ScaffoldComment', + 'foreignKey' => 'article_id', + ) + ); +/** + * hasAndBelongsToMany property + * + * @var string + */ + var $hasAndBelongsToMany = array( + 'ScaffoldTag' => array( + 'className' => 'ScaffoldTag', + 'foreignKey' => 'something_id', + 'associationForeignKey' => 'something_else_id', + 'joinTable' => 'join_things' + ) + ); +} + +/** + * ScaffoldUser class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ScaffoldUser extends CakeTestModel { + +/** + * useTable property + * + * @var string 'posts' + * @access public + */ + var $useTable = 'users'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array( + 'Article' => array( + 'className' => 'ScaffoldMock', + 'foreignKey' => 'article_id', + ) + ); +} + +/** + * ScaffoldComment class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ScaffoldComment extends CakeTestModel { + +/** + * useTable property + * + * @var string 'posts' + * @access public + */ + var $useTable = 'comments'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'Article' => array( + 'className' => 'ScaffoldMock', + 'foreignKey' => 'article_id', + ) + ); +} + +/** + * ScaffoldTag class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ScaffoldTag extends CakeTestModel { +/** + * useTable property + * + * @var string 'posts' + * @access public + */ + var $useTable = 'tags'; +} +/** + * TestScaffoldView class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class TestScaffoldView extends ScaffoldView { + +/** + * testGetFilename method + * + * @param mixed $action + * @access public + * @return void + */ + function testGetFilename($action) { + return $this->_getViewFileName($action); + } +} + +/** + * ScaffoldViewTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ScaffoldViewTest extends CakeTestCase { + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.article', 'core.user', 'core.comment', 'core.join_thing', 'core.tag'); + +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->Controller =& new ScaffoldMockController(); + + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS), + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + } + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + unset($this->Controller); + + App::build(); + } + +/** + * testGetViewFilename method + * + * @access public + * @return void + */ + function testGetViewFilename() { + $_admin = Configure::read('Routing.prefixes'); + Configure::write('Routing.prefixes', array('admin')); + + $this->Controller->action = 'index'; + $ScaffoldView =& new TestScaffoldView($this->Controller); + $result = $ScaffoldView->testGetFilename('index'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS . 'scaffolds' . DS . 'index.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('edit'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS . 'scaffolds' . DS . 'edit.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('add'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS . 'scaffolds' . DS . 'edit.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('view'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS . 'scaffolds' . DS . 'view.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('admin_index'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS . 'scaffolds' . DS . 'index.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('admin_view'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS . 'scaffolds' . DS . 'view.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('admin_edit'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS . 'scaffolds' . DS . 'edit.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('admin_add'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS . 'scaffolds' . DS . 'edit.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('error'); + $expected = 'cake' . DS . 'libs' . DS . 'view' . DS . 'errors' . DS . 'scaffold_error.ctp'; + $this->assertEqual($result, $expected); + + $Controller =& new ScaffoldMockController(); + $Controller->scaffold = 'admin'; + $Controller->viewPath = 'posts'; + $Controller->action = 'admin_edit'; + $ScaffoldView =& new TestScaffoldView($Controller); + $result = $ScaffoldView->testGetFilename('admin_edit'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' .DS . 'views' . DS . 'posts' . DS . 'scaffold.edit.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('edit'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' .DS . 'views' . DS . 'posts' . DS . 'scaffold.edit.ctp'; + $this->assertEqual($result, $expected); + + $Controller =& new ScaffoldMockController(); + $Controller->scaffold = 'admin'; + $Controller->viewPath = 'tests'; + $Controller->plugin = 'test_plugin'; + $Controller->action = 'admin_add'; + $ScaffoldView =& new TestScaffoldView($Controller); + $result = $ScaffoldView->testGetFilename('admin_add'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' + . DS .'test_plugin' . DS . 'views' . DS . 'tests' . DS . 'scaffold.edit.ctp'; + $this->assertEqual($result, $expected); + + $result = $ScaffoldView->testGetFilename('add'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' + . DS .'test_plugin' . DS . 'views' . DS . 'tests' . DS . 'scaffold.edit.ctp'; + $this->assertEqual($result, $expected); + + Configure::write('Routing.prefixes', $_admin); + } + +/** + * test getting the view file name for themed scaffolds. + * + * @return void + */ + function testGetViewFileNameWithTheme() { + $this->Controller->action = 'index'; + $this->Controller->viewPath = 'posts'; + $this->Controller->theme = 'test_theme'; + $ScaffoldView =& new TestScaffoldView($this->Controller); + + $result = $ScaffoldView->testGetFilename('index'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS + . 'themed' . DS . 'test_theme' . DS . 'posts' . DS . 'scaffold.index.ctp'; + $this->assertEqual($result, $expected); + } + +/** + * test default index scaffold generation + * + * @access public + * @return void + */ + function testIndexScaffold() { + $this->Controller->action = 'index'; + $this->Controller->here = '/scaffold_mock'; + $this->Controller->webroot = '/'; + $params = array( + 'plugin' => null, + 'pass' => array(), + 'form' => array(), + 'named' => array(), + 'url' => array('url' =>'scaffold_mock'), + 'controller' => 'scaffold_mock', + 'action' => 'index', + ); + //set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/scaffold_mock', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->constructClasses(); + ob_start(); + new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + + $this->assertPattern('#

Scaffold Mock

#', $result); + $this->assertPattern('##', $result); + + $this->assertPattern('#1#', $result); //belongsTo links + $this->assertPattern('#
  • New Scaffold Mock
  • #', $result); + $this->assertPattern('#
  • List Scaffold Users
  • #', $result); + $this->assertPattern('#
  • New Comment
  • #', $result); + } + +/** + * test default view scaffold generation + * + * @access public + * @return void + */ + function testViewScaffold() { + $this->Controller->action = 'view'; + $this->Controller->here = '/scaffold_mock'; + $this->Controller->webroot = '/'; + $params = array( + 'plugin' => null, + 'pass' => array(1), + 'form' => array(), + 'named' => array(), + 'url' => array('url' =>'scaffold_mock'), + 'controller' => 'scaffold_mock', + 'action' => 'view', + ); + //set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/scaffold_mock', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->constructClasses(); + + ob_start(); + new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + + $this->assertPattern('/

    View Scaffold Mock<\/h2>/', $result); + $this->assertPattern('/
    /', $result); + //TODO: add specific tests for fields. + $this->assertPattern('/1<\/a>/', $result); //belongsTo links + $this->assertPattern('/
  • Edit Scaffold Mock<\/a>\s<\/li>/', $result); + $this->assertPattern('/
  • ]*>Delete Scaffold Mock<\/a>\s*<\/li>/', $result); + //check related table + $this->assertPattern('/
  • /', $result); + $this->assertPattern('/
  • New Comment<\/a><\/li>/', $result); + $this->assertNoPattern('/
  • JoinThing<\/th>/', $result); + } + +/** + * test default view scaffold generation + * + * @access public + * @return void + */ + function testEditScaffold() { + $this->Controller->action = 'edit'; + $this->Controller->here = '/scaffold_mock'; + $this->Controller->webroot = '/'; + $params = array( + 'plugin' => null, + 'pass' => array(1), + 'form' => array(), + 'named' => array(), + 'url' => array('url' =>'scaffold_mock'), + 'controller' => 'scaffold_mock', + 'action' => 'edit', + ); + //set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/scaffold_mock', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->constructClasses(); + ob_start(); + new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + + $this->assertPattern('/
    assertPattern('/Edit Scaffold Mock<\/legend>/', $result); + + $this->assertPattern('/input type="hidden" name="data\[ScaffoldMock\]\[id\]" value="1" id="ScaffoldMockId"/', $result); + $this->assertPattern('/select name="data\[ScaffoldMock\]\[user_id\]" id="ScaffoldMockUserId"/', $result); + $this->assertPattern('/input name="data\[ScaffoldMock\]\[title\]" type="text" maxlength="255" value="First Article" id="ScaffoldMockTitle"/', $result); + $this->assertPattern('/input name="data\[ScaffoldMock\]\[published\]" type="text" maxlength="1" value="Y" id="ScaffoldMockPublished"/', $result); + $this->assertPattern('/textarea name="data\[ScaffoldMock\]\[body\]" cols="30" rows="6" id="ScaffoldMockBody"/', $result); + $this->assertPattern('/
  • ]*>Delete<\/a>\s*<\/li>/', $result); + } + +/** + * Test Admin Index Scaffolding. + * + * @access public + * @return void + */ + function testAdminIndexScaffold() { + $_backAdmin = Configure::read('Routing.prefixes'); + + Configure::write('Routing.prefixes', array('admin')); + $params = array( + 'plugin' => null, + 'pass' => array(), + 'form' => array(), + 'named' => array(), + 'prefix' => 'admin', + 'url' => array('url' =>'admin/scaffold_mock'), + 'controller' => 'scaffold_mock', + 'action' => 'admin_index', + 'admin' => 1, + ); + //reset, and set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/admin/scaffold_mock', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->action = 'admin_index'; + $this->Controller->here = '/tests/admin/scaffold_mock'; + $this->Controller->webroot = '/'; + $this->Controller->scaffold = 'admin'; + $this->Controller->constructClasses(); + + ob_start(); + $Scaffold = new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + + $this->assertPattern('/

    Scaffold Mock<\/h2>/', $result); + $this->assertPattern('//', $result); + //TODO: add testing for table generation + $this->assertPattern('/
  • New Scaffold Mock<\/a><\/li>/', $result); + + Configure::write('Routing.prefixes', $_backAdmin); + } + +/** + * Test Admin Index Scaffolding. + * + * @access public + * @return void + */ + function testAdminEditScaffold() { + $_backAdmin = Configure::read('Routing.prefixes'); + + Configure::write('Routing.prefixes', array('admin')); + $params = array( + 'plugin' => null, + 'pass' => array(), + 'form' => array(), + 'named' => array(), + 'prefix' => 'admin', + 'url' => array('url' =>'admin/scaffold_mock/edit'), + 'controller' => 'scaffold_mock', + 'action' => 'admin_edit', + 'admin' => 1, + ); + //reset, and set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/admin/scaffold_mock/edit', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->action = 'admin_index'; + $this->Controller->here = '/tests/admin/scaffold_mock'; + $this->Controller->webroot = '/'; + $this->Controller->scaffold = 'admin'; + $this->Controller->constructClasses(); + + ob_start(); + $Scaffold = new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + + $this->assertPattern('#admin/scaffold_mock/edit/1#', $result); + $this->assertPattern('#Scaffold Mock#', $result); + + Configure::write('Routing.prefixes', $_backAdmin); + } + +/** + * Test Admin Index Scaffolding. + * + * @access public + * @return void + */ + function testMultiplePrefixScaffold() { + $_backAdmin = Configure::read('Routing.prefixes'); + + Configure::write('Routing.prefixes', array('admin', 'member')); + $params = array( + 'plugin' => null, + 'pass' => array(), + 'form' => array(), + 'named' => array(), + 'prefix' => 'member', + 'url' => array('url' =>'member/scaffold_mock'), + 'controller' => 'scaffold_mock', + 'action' => 'member_index', + 'member' => 1, + ); + //reset, and set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/member/scaffold_mock', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->action = 'member_index'; + $this->Controller->here = '/tests/member/scaffold_mock'; + $this->Controller->webroot = '/'; + $this->Controller->scaffold = 'member'; + $this->Controller->constructClasses(); + + ob_start(); + $Scaffold = new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + + $this->assertPattern('/

    Scaffold Mock<\/h2>/', $result); + $this->assertPattern('/

  • /', $result); + //TODO: add testing for table generation + $this->assertPattern('/
  • New Scaffold Mock<\/a><\/li>/', $result); + + Configure::write('Routing.prefixes', $_backAdmin); + } + +} + +/** + * Scaffold Test class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller + */ +class ScaffoldTest extends CakeTestCase { + +/** + * Controller property + * + * @var SecurityTestController + * @access public + */ + var $Controller; + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.article', 'core.user', 'core.comment', 'core.join_thing', 'core.tag'); +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->Controller =& new ScaffoldMockController(); + } + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + unset($this->Controller); + } + +/** + * Test the correct Generation of Scaffold Params. + * This ensures that the correct action and view will be generated + * + * @access public + * @return void + */ + function testScaffoldParams() { + $this->Controller->action = 'admin_edit'; + $this->Controller->here = '/admin/scaffold_mock/edit'; + $this->Controller->webroot = '/'; + $params = array( + 'plugin' => null, + 'pass' => array(), + 'form' => array(), + 'named' => array(), + 'url' => array('url' =>'admin/scaffold_mock/edit'), + 'controller' => 'scaffold_mock', + 'action' => 'admin_edit', + 'admin' => true, + ); + //set router. + Router::setRequestInfo(array($params, array('base' => '/', 'here' => 'admin/scaffold_mock', 'webroot' => '/'))); + + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->constructClasses(); + $Scaffold =& new TestScaffoldMock($this->Controller, $params); + $result = $Scaffold->getParams(); + $this->assertEqual($result['action'], 'admin_edit'); + } + +/** + * test that the proper names and variable values are set by Scaffold + * + * @return void + */ + function testScaffoldVariableSetting() { + $this->Controller->action = 'admin_edit'; + $this->Controller->here = '/admin/scaffold_mock/edit'; + $this->Controller->webroot = '/'; + $params = array( + 'plugin' => null, + 'pass' => array(), + 'form' => array(), + 'named' => array(), + 'url' => array('url' =>'admin/scaffold_mock/edit'), + 'controller' => 'scaffold_mock', + 'action' => 'admin_edit', + 'admin' => true, + ); + //set router. + Router::setRequestInfo(array($params, array('base' => '/', 'here' => 'admin/scaffold_mock', 'webroot' => '/'))); + + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->constructClasses(); + $Scaffold =& new TestScaffoldMock($this->Controller, $params); + $result = $Scaffold->controller->viewVars; + + $this->assertEqual($result['title_for_layout'], 'Scaffold :: Admin Edit :: Scaffold Mock'); + $this->assertEqual($result['singularHumanName'], 'Scaffold Mock'); + $this->assertEqual($result['pluralHumanName'], 'Scaffold Mock'); + $this->assertEqual($result['modelClass'], 'ScaffoldMock'); + $this->assertEqual($result['primaryKey'], 'id'); + $this->assertEqual($result['displayField'], 'title'); + $this->assertEqual($result['singularVar'], 'scaffoldMock'); + $this->assertEqual($result['pluralVar'], 'scaffoldMock'); + $this->assertEqual($result['scaffoldFields'], array('id', 'user_id', 'title', 'body', 'published', 'created', 'updated')); + } + function getTests() { + return array('start', 'startCase', 'testScaffoldChangingViewProperty', 'endCase', 'end'); + } + +/** + * test that Scaffold overrides the view property even if its set to 'Theme' + * + * @return void + */ + function testScaffoldChangingViewProperty() { + $this->Controller->action = 'edit'; + $this->Controller->theme = 'test_theme'; + $this->Controller->view = 'Theme'; + $this->Controller->constructClasses(); + $Scaffold =& new TestScaffoldMock($this->Controller, array()); + + $this->assertEqual($this->Controller->view, 'Scaffold'); + } + +/** + * test that scaffold outputs flash messages when sessions are unset. + * + * @return void + */ + function testScaffoldFlashMessages() { + $this->Controller->action = 'edit'; + $this->Controller->here = '/scaffold_mock'; + $this->Controller->webroot = '/'; + $params = array( + 'plugin' => null, + 'pass' => array(1), + 'form' => array(), + 'named' => array(), + 'url' => array('url' =>'scaffold_mock'), + 'controller' => 'scaffold_mock', + 'action' => 'edit', + ); + //set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/scaffold_mock', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->data = array( + 'ScaffoldMock' => array( + 'id' => 1, + 'title' => 'New title', + 'body' => 'new body' + ) + ); + $this->Controller->constructClasses(); + unset($this->Controller->Session); + + ob_start(); + new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + $this->assertPattern('/Scaffold Mock has been updated/', $result); + } +/** + * test that habtm relationship keys get added to scaffoldFields. + * + * @see http://code.cakephp.org/tickets/view/48 + * @return void + */ + function testHabtmFieldAdditionWithScaffoldForm() { + $this->Controller->action = 'edit'; + $this->Controller->here = '/scaffold_mock'; + $this->Controller->webroot = '/'; + $params = array( + 'plugin' => null, + 'pass' => array(1), + 'form' => array(), + 'named' => array(), + 'url' => array('url' =>'scaffold_mock'), + 'controller' => 'scaffold_mock', + 'action' => 'edit', + ); + //set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/scaffold_mock', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->constructClasses(); + ob_start(); + $Scaffold = new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + $this->assertPattern('/name="data\[ScaffoldTag\]\[ScaffoldTag\]"/', $result); + + $result = $Scaffold->controller->viewVars; + $this->assertEqual($result['scaffoldFields'], array('id', 'user_id', 'title', 'body', 'published', 'created', 'updated', 'ScaffoldTag')); + } +/** + * test that the proper names and variable values are set by Scaffold + * + * @return void + */ + function testEditScaffoldWithScaffoldFields() { + $this->Controller = new ScaffoldMockControllerWithFields(); + $this->Controller->action = 'edit'; + $this->Controller->here = '/scaffold_mock'; + $this->Controller->webroot = '/'; + $params = array( + 'plugin' => null, + 'pass' => array(1), + 'form' => array(), + 'named' => array(), + 'url' => array('url' =>'scaffold_mock'), + 'controller' => 'scaffold_mock', + 'action' => 'edit', + ); + //set router. + Router::reload(); + Router::setRequestInfo(array($params, array('base' => '/', 'here' => '/scaffold_mock', 'webroot' => '/'))); + $this->Controller->params = $params; + $this->Controller->controller = 'scaffold_mock'; + $this->Controller->base = '/'; + $this->Controller->constructClasses(); + ob_start(); + new Scaffold($this->Controller, $params); + $result = ob_get_clean(); + + $this->assertNoPattern('/textarea name="data\[ScaffoldMock\]\[body\]" cols="30" rows="6" id="ScaffoldMockBody"/', $result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/debugger.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/debugger.test.php new file mode 100644 index 000000000..ad8596305 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/debugger.test.php @@ -0,0 +1,336 @@ +assertEqual(ini_get('docref_root'), ''); + $debugger = new Debugger(); + $this->assertEqual(ini_get('docref_root'), 'http://php.net/'); + } + +/** + * test Excerpt writing + * + * @access public + * @return void + */ + function testExcerpt() { + $result = Debugger::excerpt(__FILE__, __LINE__, 2); + $this->assertTrue(is_array($result)); + $this->assertEqual(count($result), 5); + $this->assertPattern('/function(.+)testExcerpt/', $result[1]); + + $result = Debugger::excerpt(__FILE__, 2, 2); + $this->assertTrue(is_array($result)); + $this->assertEqual(count($result), 4); + + $expected = '<?php'; + $expected .= ''; + $this->assertEqual($result[0], $expected); + + $return = Debugger::excerpt('[internal]', 2, 2); + $this->assertTrue(empty($return)); + } + +/** + * testOutput method + * + * @access public + * @return void + */ + function testOutput() { + Debugger::invoke(Debugger::getInstance()); + $result = Debugger::output(false); + $this->assertEqual($result, ''); + $out .= ''; + $result = Debugger::output(true); + + $this->assertEqual($result[0]['error'], 'Notice'); + $this->assertPattern('/Undefined variable\:\s+out/', $result[0]['description']); + $this->assertPattern('/DebuggerTest::testOutput/i', $result[0]['trace']); + $this->assertPattern('/SimpleInvoker::invoke/i', $result[0]['trace']); + + ob_start(); + Debugger::output('txt'); + $other .= ''; + $result = ob_get_clean(); + + $this->assertPattern('/Undefined variable:\s+other/', $result); + $this->assertPattern('/Context:/', $result); + $this->assertPattern('/DebuggerTest::testOutput/i', $result); + $this->assertPattern('/SimpleInvoker::invoke/i', $result); + + ob_start(); + Debugger::output('html'); + $wrong .= ''; + $result = ob_get_clean(); + $this->assertPattern('/
    .+<\/pre>/', $result);
    +		$this->assertPattern('/Notice<\/b>/', $result);
    +		$this->assertPattern('/variable:\s+wrong/', $result);
    +
    +		ob_start();
    +		Debugger::output('js');
    +		$buzz .= '';
    +		$result = explode('', ob_get_clean());
    +		$this->assertTags($result[0], array(
    +			'pre' => array('class' => 'cake-debug'),
    +			'a' => array(
    +				'href' => "javascript:void(0);",
    +				'onclick' => "document.getElementById('cakeErr4-trace').style.display = " .
    +				             "(document.getElementById('cakeErr4-trace').style.display == 'none'" .
    +				             " ? '' : 'none');"
    +			),
    +			'b' => array(), 'Notice', '/b', ' (8)',
    +		));
    +
    +		$this->assertPattern('/Undefined variable:\s+buzz/', $result[1]);
    +		$this->assertPattern('/]+>Code/', $result[1]);
    +		$this->assertPattern('/]+>Context/', $result[2]);
    +		set_error_handler('simpleTestErrorHandler');
    +	}
    +
    +/**
    + * Tests that changes in output formats using Debugger::output() change the templates used.
    + *
    + * @return void
    + */
    +	function testChangeOutputFormats() {
    +		Debugger::invoke(Debugger::getInstance());
    +		Debugger::output('js', array(
    +			'traceLine' => '{:reference} - {:path}, line {:line}'
    +		));
    +		$result = Debugger::trace();
    +		$this->assertPattern('/' . preg_quote('txmt://open?url=file:///', '/') . '/', $result);
    +
    +		Debugger::output('xml', array(
    +			'error' => '{:code}{:file}{:line}' .
    +			           '{:description}',
    +			'context' => "{:context}",
    +			'trace' => "{:trace}",
    +		));
    +		Debugger::output('xml');
    +
    +		ob_start();
    +		$foo .= '';
    +		$result = ob_get_clean();
    +		set_error_handler('SimpleTestErrorHandler');
    +
    +		$data = array(
    +			'error' => array(),
    +			'code' => array(), '8', '/code',
    +			'file' => array(), 'preg:/[^<]+/', '/file',
    +			'line' => array(), '' . (intval(__LINE__) + -8), '/line',
    +			'preg:/Undefined variable:\s+foo/',
    +			'/error'
    +		);
    +		$this->assertTags($result, $data, true);
    +	}
    +
    +/**
    + * testTrimPath method
    + *
    + * @access public
    + * @return void
    + */
    +	function testTrimPath() {
    +		$this->assertEqual(Debugger::trimPath(APP), 'APP' . DS);
    +		$this->assertEqual(Debugger::trimPath(CAKE_CORE_INCLUDE_PATH), 'CORE');
    +	}
    +
    +/**
    + * testExportVar method
    + *
    + * @access public
    + * @return void
    + */
    +	function testExportVar() {
    +		App::import('Controller');
    +		$Controller = new Controller();
    +		$Controller->helpers = array('Html', 'Form');
    +		$View = new View($Controller);
    +		$result = Debugger::exportVar($View);
    +		$expected = 'ViewView::$base = NULL
    +		View::$here = NULL
    +		View::$plugin = NULL
    +		View::$name = ""
    +		View::$action = NULL
    +		View::$params = array
    +		View::$passedArgs = array
    +		View::$data = array
    +		View::$helpers = array
    +		View::$viewPath = ""
    +		View::$viewVars = array
    +		View::$layout = "default"
    +		View::$layoutPath = NULL
    +		View::$autoRender = true
    +		View::$autoLayout = true
    +		View::$ext = ".ctp"
    +		View::$subDir = NULL
    +		View::$theme = NULL
    +		View::$cacheAction = false
    +		View::$validationErrors = array
    +		View::$hasRendered = false
    +		View::$loaded = array
    +		View::$modelScope = false
    +		View::$model = NULL
    +		View::$association = NULL
    +		View::$field = NULL
    +		View::$fieldSuffix = NULL
    +		View::$modelId = NULL
    +		View::$uuids = array
    +		View::$output = false
    +		View::$__passedVars = array
    +		View::$__scripts = array
    +		View::$__paths = array
    +		View::$webroot = NULL';
    +		$result = str_replace(array("\t", "\r\n", "\n"), "", strtolower($result));
    +		$expected =  str_replace(array("\t", "\r\n", "\n"), "", strtolower($expected));
    +		$this->assertEqual($result, $expected);
    +	}
    +
    +/**
    + * testLog method
    + *
    + * @access public
    + * @return void
    + */
    +	function testLog() {
    +		if (file_exists(LOGS . 'debug.log')) {
    +			unlink(LOGS . 'debug.log');
    +		}
    +
    +		Debugger::log('cool');
    +		$result = file_get_contents(LOGS . 'debug.log');
    +		$this->assertPattern('/DebuggerTest\:\:testLog/i', $result);
    +		$this->assertPattern('/"cool"/', $result);
    +
    +		unlink(TMP . 'logs' . DS . 'debug.log');
    +
    +		Debugger::log(array('whatever', 'here'));
    +		$result = file_get_contents(TMP . 'logs' . DS . 'debug.log');
    +		$this->assertPattern('/DebuggerTest\:\:testLog/i', $result);
    +		$this->assertPattern('/\[main\]/', $result);
    +		$this->assertPattern('/array/', $result);
    +		$this->assertPattern('/"whatever",/', $result);
    +		$this->assertPattern('/"here"/', $result);
    +	}
    +
    +/**
    + * testDump method
    + *
    + * @access public
    + * @return void
    + */
    +	function testDump() {
    +		$var = array('People' => array(
    +					array(
    +					'name' => 'joeseph',
    +					'coat' => 'technicolor',
    +					'hair_color' => 'brown'
    +					),
    +					array(
    +					'name' => 'Shaft',
    +					'coat' => 'black',
    +					'hair' => 'black'
    +					)
    +				)
    +			);
    +		ob_start();
    +		Debugger::dump($var);
    +		$result = ob_get_clean();
    +		$expected = "
    array(\n\t\"People\" => array()\n)
    "; + $this->assertEqual($expected, $result); + } + +/** + * test getInstance. + * + * @access public + * @return void + */ + function testGetInstance() { + $result =& Debugger::getInstance(); + $this->assertIsA($result, 'Debugger'); + + $result =& Debugger::getInstance('DebuggerTestCaseDebugger'); + $this->assertIsA($result, 'DebuggerTestCaseDebugger'); + + $result =& Debugger::getInstance(); + $this->assertIsA($result, 'DebuggerTestCaseDebugger'); + + $result =& Debugger::getInstance('Debugger'); + $this->assertIsA($result, 'Debugger'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/error.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/error.test.php new file mode 100644 index 000000000..ec90a57e2 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/error.test.php @@ -0,0 +1,630 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (class_exists('TestErrorHandler')) { + return; +} +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + define('CAKEPHP_UNIT_TEST_EXECUTION', 1); +} + +/** + * BlueberryComponent class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class BlueberryComponent extends Object { + +/** + * testName property + * + * @access public + * @return void + */ + var $testName = null; + +/** + * initialize method + * + * @access public + * @return void + */ + function initialize(&$controller) { + $this->testName = 'BlueberryComponent'; + } +} + +/** + * BlueberryDispatcher class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class BlueberryDispatcher extends Dispatcher { + +/** + * cakeError method + * + * @access public + * @return void + */ + function cakeError($method, $messages = array()) { + $error = new TestErrorHandler($method, $messages); + return $error; + } +} + +/** + * Short description for class. + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class AuthBlueberryUser extends CakeTestModel { + +/** + * name property + * + * @var string 'AuthBlueberryUser' + * @access public + */ + var $name = 'AuthBlueberryUser'; + +/** + * useTable property + * + * @var string + * @access public + */ + var $useTable = false; +} +if (!class_exists('AppController')) { + /** + * AppController class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ + class AppController extends Controller { + /** + * components property + * + * @access public + * @return void + */ + var $components = array('Blueberry'); + /** + * beforeRender method + * + * @access public + * @return void + */ + function beforeRender() { + echo $this->Blueberry->testName; + } + /** + * header method + * + * @access public + * @return void + */ + function header($header) { + echo $header; + } + /** + * _stop method + * + * @access public + * @return void + */ + function _stop($status = 0) { + echo 'Stopped with status: ' . $status; + } + } +} elseif (!defined('APP_CONTROLLER_EXISTS')){ + define('APP_CONTROLLER_EXISTS', true); +} +App::import('Core', array('Error', 'Controller')); + +/** + * TestErrorController class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class TestErrorController extends AppController { + +/** + * uses property + * + * @var array + * @access public + */ + var $uses = array(); + +/** + * index method + * + * @access public + * @return void + */ + function index() { + $this->autoRender = false; + return 'what up'; + } +} + +/** + * BlueberryController class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class BlueberryController extends AppController { + +/** + * name property + * + * @access public + * @return void + */ + var $name = 'BlueberryController'; + +/** + * uses property + * + * @access public + * @return void + */ + var $uses = array('AuthBlueberryUser'); + +/** + * components property + * + * @access public + * @return void + */ + var $components = array('Auth'); +} + +/** + * MyCustomErrorHandler class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class MyCustomErrorHandler extends ErrorHandler { + +/** + * custom error message type. + * + * @return void + */ + function missingWidgetThing() { + echo 'widget thing is missing'; + } + +/** + * stop method + * + * @access public + * @return void + */ + function _stop() { + return; + } +} + +/** + * TestErrorHandler class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class TestErrorHandler extends ErrorHandler { + +/** + * stop method + * + * @access public + * @return void + */ + function _stop() { + return; + } +} + +/** + * ErrorHandlerTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ErrorHandlerTest extends CakeTestCase { + +/** + * skip method + * + * @access public + * @return void + */ + function skip() { + $this->skipIf(PHP_SAPI === 'cli', '%s Cannot be run from console'); + } + +/** + * test that methods declared in an ErrorHandler subclass are not converted + * into error404 when debug == 0 + * + * @return void + */ + function testSubclassMethodsNotBeingConvertedToError() { + $back = Configure::read('debug'); + Configure::write('debug', 2); + ob_start(); + $ErrorHandler =& new MyCustomErrorHandler('missingWidgetThing', array('message' => 'doh!')); + $result = ob_get_clean(); + $this->assertEqual($result, 'widget thing is missing'); + + Configure::write('debug', 0); + ob_start(); + $ErrorHandler =& new MyCustomErrorHandler('missingWidgetThing', array('message' => 'doh!')); + $result = ob_get_clean(); + $this->assertEqual($result, 'widget thing is missing', 'Method declared in subclass converted to error404. %s'); + + Configure::write('debug', 0); + ob_start(); + $ErrorHandler =& new MyCustomErrorHandler('missingController', array( + 'className' => 'Missing', 'message' => 'Page not found' + )); + $result = ob_get_clean(); + $this->assertPattern('/Not Found/', $result, 'Method declared in error handler not converted to error404. %s'); + + Configure::write('debug', $back); + } + +/** + * testError method + * + * @access public + * @return void + */ + function testError() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('error404', array('message' => 'Page not found')); + ob_clean(); + ob_start(); + $TestErrorHandler->error(array( + 'code' => 404, + 'message' => 'Page not Found', + 'name' => "Couldn't find what you were looking for" + )); + $result = ob_get_clean(); + $this->assertPattern("/

    Couldn't find what you were looking for<\/h2>/", $result); + $this->assertPattern('/Page not Found/', $result); + } + +/** + * testError404 method + * + * @access public + * @return void + */ + function testError404() { + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS) + ), true); + + ob_start(); + $TestErrorHandler = new TestErrorHandler('error404', array('message' => 'Page not found', 'url' => '/test_error')); + $result = ob_get_clean(); + $this->assertPattern('/

    Not Found<\/h2>/', $result); + $this->assertPattern("/'\/test_error'<\/strong>/", $result); + + ob_start(); + $TestErrorHandler =& new TestErrorHandler('error404', array('message' => 'Page not found')); + ob_get_clean(); + ob_start(); + $TestErrorHandler->error404(array( + 'url' => 'pages/pink', + 'message' => 'Page not found' + )); + $result = ob_get_clean(); + $this->assertNoPattern('##', $result); + + App::build(); + } + +/** + * testError500 method + * + * @access public + * @return void + */ + function testError500() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('error500', array( + 'message' => 'An Internal Error Has Occurred' + )); + $result = ob_get_clean(); + $this->assertPattern('/

    An Internal Error Has Occurred<\/h2>/', $result); + + ob_start(); + $TestErrorHandler = new TestErrorHandler('error500', array( + 'message' => 'An Internal Error Has Occurred', + 'code' => '500' + )); + $result = ob_get_clean(); + $this->assertPattern('/

    An Internal Error Has Occurred<\/h2>/', $result); + } + +/** + * testMissingController method + * + * @access public + * @return void + */ + function testMissingController() { + $this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController'); + + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingController', array('className' => 'PostsController')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Controller<\/h2>/', $result); + $this->assertPattern('/PostsController<\/em>/', $result); + $this->assertPattern('/BlueberryComponent/', $result); + } + +/** + * testMissingAction method + * + * @access public + * @return void + */ + function testMissingAction() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingAction', array('className' => 'PostsController', 'action' => 'index')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Method in PostsController<\/h2>/', $result); + $this->assertPattern('/PostsController::<\/em>index\(\)<\/em>/', $result); + + ob_start(); + $dispatcher = new BlueberryDispatcher('/blueberry/inexistent'); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Method in BlueberryController<\/h2>/', $result); + $this->assertPattern('/BlueberryController::<\/em>inexistent\(\)<\/em>/', $result); + $this->assertNoPattern('/Location: (.*)\/users\/login/', $result); + $this->assertNoPattern('/Stopped with status: 0/', $result); + } + +/** + * testPrivateAction method + * + * @access public + * @return void + */ + function testPrivateAction() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('privateAction', array('className' => 'PostsController', 'action' => '_secretSauce')); + $result = ob_get_clean(); + $this->assertPattern('/

    Private Method in PostsController<\/h2>/', $result); + $this->assertPattern('/PostsController::<\/em>_secretSauce\(\)<\/em>/', $result); + } + +/** + * testMissingTable method + * + * @access public + * @return void + */ + function testMissingTable() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingTable', array('className' => 'Article', 'table' => 'articles')); + $result = ob_get_clean(); + $this->assertPattern('/HTTP\/1\.0 500 Internal Server Error/', $result); + $this->assertPattern('/

    Missing Database Table<\/h2>/', $result); + $this->assertPattern('/table articles<\/em> for model Article<\/em>/', $result); + } + +/** + * testMissingDatabase method + * + * @access public + * @return void + */ + function testMissingDatabase() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingDatabase', array()); + $result = ob_get_clean(); + $this->assertPattern('/HTTP\/1\.0 500 Internal Server Error/', $result); + $this->assertPattern('/

    Missing Database Connection<\/h2>/', $result); + $this->assertPattern('/Confirm you have created the file/', $result); + } + +/** + * testMissingView method + * + * @access public + * @return void + */ + function testMissingView() { + restore_error_handler(); + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingView', array('className' => 'Pages', 'action' => 'display', 'file' => 'pages/about.ctp', 'base' => '')); + $expected = ob_get_clean(); + set_error_handler('simpleTestErrorHandler'); + $this->assertPattern("/PagesController::/", $expected); + $this->assertPattern("/pages\/about.ctp/", $expected); + } + +/** + * testMissingLayout method + * + * @access public + * @return void + */ + function testMissingLayout() { + restore_error_handler(); + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingLayout', array( 'layout' => 'my_layout', 'file' => 'layouts/my_layout.ctp', 'base' => '')); + $expected = ob_get_clean(); + set_error_handler('simpleTestErrorHandler'); + $this->assertPattern("/Missing Layout/", $expected); + $this->assertPattern("/layouts\/my_layout.ctp/", $expected); + } + +/** + * testMissingConnection method + * + * @access public + * @return void + */ + function testMissingConnection() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingConnection', array('className' => 'Article')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Database Connection<\/h2>/', $result); + $this->assertPattern('/Article requires a database connection/', $result); + } + +/** + * testMissingHelperFile method + * + * @access public + * @return void + */ + function testMissingHelperFile() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingHelperFile', array('helper' => 'MyCustom', 'file' => 'my_custom.php')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Helper File<\/h2>/', $result); + $this->assertPattern('/Create the class below in file:/', $result); + $this->assertPattern('/(\/|\\\)my_custom.php/', $result); + } + +/** + * testMissingHelperClass method + * + * @access public + * @return void + */ + function testMissingHelperClass() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingHelperClass', array('helper' => 'MyCustom', 'file' => 'my_custom.php')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Helper Class<\/h2>/', $result); + $this->assertPattern('/The helper class MyCustomHelper<\/em> can not be found or does not exist./', $result); + $this->assertPattern('/(\/|\\\)my_custom.php/', $result); + } + +/** + * test missingBehaviorFile method + * + * @access public + * @return void + */ + function testMissingBehaviorFile() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingBehaviorFile', array('behavior' => 'MyCustom', 'file' => 'my_custom.php')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Behavior File<\/h2>/', $result); + $this->assertPattern('/Create the class below in file:/', $result); + $this->assertPattern('/(\/|\\\)my_custom.php/', $result); + } + +/** + * test MissingBehaviorClass method + * + * @access public + * @return void + */ + function testMissingBehaviorClass() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingBehaviorClass', array('behavior' => 'MyCustom', 'file' => 'my_custom.php')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Behavior Class<\/h2>/', $result); + $this->assertPattern('/The behavior class MyCustomBehavior<\/em> can not be found or does not exist./', $result); + $this->assertPattern('/(\/|\\\)my_custom.php/', $result); + } + +/** + * testMissingComponentFile method + * + * @access public + * @return void + */ + function testMissingComponentFile() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingComponentFile', array('className' => 'PostsController', 'component' => 'Sidebox', 'file' => 'sidebox.php')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Component File<\/h2>/', $result); + $this->assertPattern('/Create the class SideboxComponent<\/em> in file:/', $result); + $this->assertPattern('/(\/|\\\)sidebox.php/', $result); + } + +/** + * testMissingComponentClass method + * + * @access public + * @return void + */ + function testMissingComponentClass() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingComponentClass', array('className' => 'PostsController', 'component' => 'Sidebox', 'file' => 'sidebox.php')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Component Class<\/h2>/', $result); + $this->assertPattern('/Create the class SideboxComponent<\/em> in file:/', $result); + $this->assertPattern('/(\/|\\\)sidebox.php/', $result); + } + +/** + * testMissingModel method + * + * @access public + * @return void + */ + function testMissingModel() { + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingModel', array('className' => 'Article', 'file' => 'article.php')); + $result = ob_get_clean(); + $this->assertPattern('/

    Missing Model<\/h2>/', $result); + $this->assertPattern('/Article<\/em> could not be found./', $result); + $this->assertPattern('/(\/|\\\)article.php/', $result); + } + +/** + * testing that having a code => 500 in the cakeError call makes an + * internal server error. + * + * @return void + */ + function testThatCode500Works() { + Configure::write('debug', 0); + ob_start(); + $TestErrorHandler = new TestErrorHandler('missingTable', array( + 'className' => 'Article', + 'table' => 'articles', + 'code' => 500 + )); + $result = ob_get_clean(); + $this->assertPattern('/

    An Internal Error Has Occurred<\/h2>/', $result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/file.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/file.test.php new file mode 100644 index 000000000..978c68c43 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/file.test.php @@ -0,0 +1,472 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'File'); + +/** + * FileTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class FileTest extends CakeTestCase { + +/** + * File property + * + * @var mixed null + * @access public + */ + var $File = null; + +/** + * testBasic method + * + * @access public + * @return void + */ + function testBasic() { + $file = __FILE__; + $this->File =& new File($file); + + $result = $this->File->pwd(); + $expecting = $file; + $this->assertEqual($result, $expecting); + + $result = $this->File->name; + $expecting = basename(__FILE__); + $this->assertEqual($result, $expecting); + + $result = $this->File->info(); + $expecting = array( + 'dirname' => dirname(__FILE__), 'basename' => basename(__FILE__), + 'extension' => 'php', 'filename' =>'file.test' + ); + $this->assertEqual($result, $expecting); + + $result = $this->File->ext(); + $expecting = 'php'; + $this->assertEqual($result, $expecting); + + $result = $this->File->name(); + $expecting = 'file.test'; + $this->assertEqual($result, $expecting); + + $result = $this->File->md5(); + $expecting = md5_file($file); + $this->assertEqual($result, $expecting); + + $result = $this->File->md5(true); + $expecting = md5_file($file); + $this->assertEqual($result, $expecting); + + $result = $this->File->size(); + $expecting = filesize($file); + $this->assertEqual($result, $expecting); + + $result = $this->File->owner(); + $expecting = fileowner($file); + $this->assertEqual($result, $expecting); + + $result = $this->File->group(); + $expecting = filegroup($file); + $this->assertEqual($result, $expecting); + + $result = $this->File->Folder(); + $this->assertIsA($result, 'Folder'); + + $this->skipIf(DIRECTORY_SEPARATOR === '\\', '%s File permissions tests not supported on Windows'); + $result = $this->File->perms(); + $expecting = '0644'; + $this->assertEqual($result, $expecting); + } + +/** + * testRead method + * + * @access public + * @return void + */ + function testRead() { + $file = __FILE__; + $this->File =& new File($file); + + $result = $this->File->read(); + $expecting = file_get_contents(__FILE__); + $this->assertEqual($result, $expecting); + $this->assertTrue(!is_resource($this->File->handle)); + + $this->File->lock = true; + $result = $this->File->read(); + $expecting = file_get_contents(__FILE__); + $this->assertEqual($result, trim($expecting)); + $this->File->lock = null; + + $data = $expecting; + $expecting = substr($data, 0, 3); + $result = $this->File->read(3); + $this->assertEqual($result, $expecting); + $this->assertTrue(is_resource($this->File->handle)); + + $expecting = substr($data, 3, 3); + $result = $this->File->read(3); + $this->assertEqual($result, $expecting); + } + +/** + * testOffset method + * + * @access public + * @return void + */ + function testOffset() { + $this->File->close(); + + $result = $this->File->offset(); + $this->assertFalse($result); + + $this->assertFalse(is_resource($this->File->handle)); + $success = $this->File->offset(0); + $this->assertTrue($success); + $this->assertTrue(is_resource($this->File->handle)); + + $result = $this->File->offset(); + $expecting = 0; + $this->assertIdentical($result, $expecting); + + $data = file_get_contents(__FILE__); + $success = $this->File->offset(5); + $expecting = substr($data, 5, 3); + $result = $this->File->read(3); + $this->assertTrue($success); + $this->assertEqual($result, $expecting); + + $result = $this->File->offset(); + $expecting = 5+3; + $this->assertIdentical($result, $expecting); + } + +/** + * testOpen method + * + * @access public + * @return void + */ + function testOpen() { + $this->File->handle = null; + + $r = $this->File->open(); + $this->assertTrue(is_resource($this->File->handle)); + $this->assertTrue($r); + + $handle = $this->File->handle; + $r = $this->File->open(); + $this->assertTrue($r); + $this->assertTrue($handle === $this->File->handle); + $this->assertTrue(is_resource($this->File->handle)); + + $r = $this->File->open('r', true); + $this->assertTrue($r); + $this->assertFalse($handle === $this->File->handle); + $this->assertTrue(is_resource($this->File->handle)); + } + +/** + * testClose method + * + * @access public + * @return void + */ + function testClose() { + $this->File->handle = null; + $this->assertFalse(is_resource($this->File->handle)); + $this->assertTrue($this->File->close()); + $this->assertFalse(is_resource($this->File->handle)); + + $this->File->handle = fopen(__FILE__, 'r'); + $this->assertTrue(is_resource($this->File->handle)); + $this->assertTrue($this->File->close()); + $this->assertFalse(is_resource($this->File->handle)); + } + +/** + * testCreate method + * + * @access public + * @return void + */ + function testCreate() { + $tmpFile = TMP.'tests'.DS.'cakephp.file.test.tmp'; + $File =& new File($tmpFile, true, 0777); + $this->assertTrue($File->exists()); + } + +/** + * testOpeningNonExistantFileCreatesIt method + * + * @access public + * @return void + */ + function testOpeningNonExistantFileCreatesIt() { + $someFile =& new File(TMP . 'some_file.txt', false); + $this->assertTrue($someFile->open()); + $this->assertEqual($someFile->read(), ''); + $someFile->close(); + $someFile->delete(); + } + +/** + * testPrepare method + * + * @access public + * @return void + */ + function testPrepare() { + $string = "some\nvery\ncool\r\nteststring here\n\n\nfor\r\r\n\n\r\n\nhere"; + if (DS == '\\') { + $expected = "some\r\nvery\r\ncool\r\nteststring here\r\n\r\n\r\n"; + $expected .= "for\r\n\r\n\r\n\r\n\r\nhere"; + } else { + $expected = "some\nvery\ncool\nteststring here\n\n\nfor\n\n\n\n\nhere"; + } + $this->assertIdentical(File::prepare($string), $expected); + + $expected = "some\r\nvery\r\ncool\r\nteststring here\r\n\r\n\r\n"; + $expected .= "for\r\n\r\n\r\n\r\n\r\nhere"; + $this->assertIdentical(File::prepare($string, true), $expected); + } + +/** + * testReadable method + * + * @access public + * @return void + */ + function testReadable() { + $someFile =& new File(TMP . 'some_file.txt', false); + $this->assertTrue($someFile->open()); + $this->assertTrue($someFile->readable()); + $someFile->close(); + $someFile->delete(); + } + +/** + * testWritable method + * + * @access public + * @return void + */ + function testWritable() { + $someFile =& new File(TMP . 'some_file.txt', false); + $this->assertTrue($someFile->open()); + $this->assertTrue($someFile->writable()); + $someFile->close(); + $someFile->delete(); + } + +/** + * testExecutable method + * + * @access public + * @return void + */ + function testExecutable() { + $someFile =& new File(TMP . 'some_file.txt', false); + $this->assertTrue($someFile->open()); + $this->assertFalse($someFile->executable()); + $someFile->close(); + $someFile->delete(); + } + +/** + * testLastAccess method + * + * @access public + * @return void + */ + function testLastAccess() { + $someFile =& new File(TMP . 'some_file.txt', false); + $this->assertFalse($someFile->lastAccess()); + $this->assertTrue($someFile->open()); + $this->assertEqual($someFile->lastAccess(), time()); + $someFile->close(); + $someFile->delete(); + } + +/** + * testLastChange method + * + * @access public + * @return void + */ + function testLastChange() { + $someFile =& new File(TMP . 'some_file.txt', false); + $this->assertFalse($someFile->lastChange()); + $this->assertTrue($someFile->open('r+')); + $this->assertEqual($someFile->lastChange(), time()); + $someFile->write('something'); + $this->assertEqual($someFile->lastChange(), time()); + $someFile->close(); + $someFile->delete(); + } + +/** + * testWrite method + * + * @access public + * @return void + */ + function testWrite() { + if (!$tmpFile = $this->_getTmpFile()) { + return false; + }; + if (file_exists($tmpFile)) { + unlink($tmpFile); + } + + $TmpFile =& new File($tmpFile); + $this->assertFalse(file_exists($tmpFile)); + $this->assertFalse(is_resource($TmpFile->handle)); + + $testData = array('CakePHP\'s', ' test suite', ' was here ...', ''); + foreach ($testData as $data) { + $r = $TmpFile->write($data); + $this->assertTrue($r); + $this->assertTrue(file_exists($tmpFile)); + $this->assertEqual($data, file_get_contents($tmpFile)); + $this->assertTrue(is_resource($TmpFile->handle)); + $TmpFile->close(); + + } + unlink($tmpFile); + } + +/** + * testAppend method + * + * @access public + * @return void + */ + function testAppend() { + if (!$tmpFile = $this->_getTmpFile()) { + return false; + }; + if (file_exists($tmpFile)) { + unlink($tmpFile); + } + + $TmpFile =& new File($tmpFile); + $this->assertFalse(file_exists($tmpFile)); + + $fragments = array('CakePHP\'s', ' test suite', ' was here ...', ''); + $data = null; + foreach ($fragments as $fragment) { + $r = $TmpFile->append($fragment); + $this->assertTrue($r); + $this->assertTrue(file_exists($tmpFile)); + $data = $data.$fragment; + $this->assertEqual($data, file_get_contents($tmpFile)); + $TmpFile->close(); + } + } + +/** + * testDelete method + * + * @access public + * @return void + */ + function testDelete() { + if (!$tmpFile = $this->_getTmpFile()) { + return false; + }; + + if (!file_exists($tmpFile)) { + touch($tmpFile); + } + $TmpFile =& new File($tmpFile); + $this->assertTrue(file_exists($tmpFile)); + $result = $TmpFile->delete(); + $this->assertTrue($result); + $this->assertFalse(file_exists($tmpFile)); + + $TmpFile =& new File('/this/does/not/exist'); + $result = $TmpFile->delete(); + $this->assertFalse($result); + } + +/** + * testCopy method + * + * @access public + * @return void + */ + function testCopy() { + $dest = TMP . 'tests' . DS . 'cakephp.file.test.tmp'; + $file = __FILE__; + $this->File =& new File($file); + $result = $this->File->copy($dest); + $this->assertTrue($result); + + $result = $this->File->copy($dest, true); + $this->assertTrue($result); + + $result = $this->File->copy($dest, false); + $this->assertFalse($result); + + $this->File->close(); + unlink($dest); + + $TmpFile =& new File('/this/does/not/exist'); + $result = $TmpFile->copy($dest); + $this->assertFalse($result); + + $TmpFile->close(); + } + +/** + * getTmpFile method + * + * @param bool $paintSkip + * @access protected + * @return void + */ + function _getTmpFile($paintSkip = true) { + $tmpFile = TMP . 'tests' . DS . 'cakephp.file.test.tmp'; + if (is_writable(dirname($tmpFile)) && (!file_exists($tmpFile) || is_writable($tmpFile))) { + return $tmpFile; + }; + + if ($paintSkip) { + $caller = 'test'; + if (function_exists('debug_backtrace')) { + $trace = debug_backtrace(); + $caller = $trace[1]['function'] . '()'; + } + $assertLine = new SimpleStackTrace(array(__FUNCTION__)); + $assertLine = $assertLine->traceMethod(); + $shortPath = substr($tmpFile, strlen(ROOT)); + + $message = '[FileTest] Skipping %s because "%s" not writeable!'; + $message = sprintf(__($message, true), $caller, $shortPath) . $assertLine; + $this->_reporter->paintSkip($message); + } + return false; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/folder.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/folder.test.php new file mode 100644 index 000000000..3b214642a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/folder.test.php @@ -0,0 +1,794 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'File'); + +/** + * FolderTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class FolderTest extends CakeTestCase { + +/** + * testBasic method + * + * @access public + * @return void + */ + function testBasic() { + $path = dirname(__FILE__); + $Folder =& new Folder($path); + + $result = $Folder->pwd(); + $this->assertEqual($result, $path); + + $result = Folder::addPathElement($path, 'test'); + $expected = $path . DS . 'test'; + $this->assertEqual($result, $expected); + + $result = $Folder->cd(ROOT); + $expected = ROOT; + $this->assertEqual($result, $expected); + + $result = $Folder->cd(ROOT . DS . 'non-existent'); + $this->assertFalse($result); + } + +/** + * testInPath method + * + * @access public + * @return void + */ + function testInPath() { + $path = dirname(dirname(__FILE__)); + $inside = dirname($path) . DS; + + $Folder =& new Folder($path); + + $result = $Folder->pwd(); + $this->assertEqual($result, $path); + + $result = Folder::isSlashTerm($inside); + $this->assertTrue($result); + + $result = $Folder->realpath('tests/'); + $this->assertEqual($result, $path . DS .'tests' . DS); + + $result = $Folder->inPath('tests' . DS); + $this->assertTrue($result); + + $result = $Folder->inPath(DS . 'non-existing' . $inside); + $this->assertFalse($result); + } + +/** + * test creation of single and mulitple paths. + * + * @return void + */ + function testCreation() { + $folder =& new Folder(TMP . 'tests'); + $result = $folder->create(TMP . 'tests' . DS . 'first' . DS . 'second' . DS . 'third'); + $this->assertTrue($result); + + rmdir(TMP . 'tests' . DS . 'first' . DS . 'second' . DS . 'third'); + rmdir(TMP . 'tests' . DS . 'first' . DS . 'second'); + rmdir(TMP . 'tests' . DS . 'first'); + + $folder =& new Folder(TMP . 'tests'); + $result = $folder->create(TMP . 'tests' . DS . 'first'); + $this->assertTrue($result); + rmdir(TMP . 'tests' . DS . 'first'); + } + +/** + * test that creation of folders with trailing ds works + * + * @return void + */ + function testCreateWithTrailingDs() { + $folder =& new Folder(TMP); + $path = TMP . 'tests' . DS . 'trailing' . DS . 'dir' . DS; + $result = $folder->create($path); + $this->assertTrue($result); + + $this->assertTrue(is_dir($path), 'Folder was not made'); + + $folder =& new Folder(TMP . 'tests' . DS . 'trailing'); + $this->assertTrue($folder->delete()); + } + +/** + * test recurisve directory create failure. + * + * @return void + */ + function testRecursiveCreateFailure() { + if ($this->skipIf(DS == '\\', 'Cant perform operations using permissions on windows. %s')) { + return; + } + $path = TMP . 'tests' . DS . 'one'; + mkdir($path); + chmod($path, '0444'); + + $this->expectError(); + + $folder =& new Folder($path); + $result = $folder->create($path . DS . 'two' . DS . 'three'); + $this->assertFalse($result); + + chmod($path, '0777'); + rmdir($path); + } +/** + * testOperations method + * + * @access public + * @return void + */ + function testOperations() { + $path = TEST_CAKE_CORE_INCLUDE_PATH . 'console' . DS . 'templates' . DS . 'skel'; + $Folder =& new Folder($path); + + $result = is_dir($Folder->pwd()); + $this->assertTrue($result); + + $new = TMP . 'test_folder_new'; + $result = $Folder->create($new); + $this->assertTrue($result); + + $copy = TMP . 'test_folder_copy'; + $result = $Folder->copy($copy); + $this->assertTrue($result); + + $copy = TMP . 'test_folder_copy'; + $result = $Folder->copy($copy); + $this->assertTrue($result); + + $copy = TMP . 'test_folder_copy'; + $result = $Folder->chmod($copy, 0755, false); + $this->assertTrue($result); + + $result = $Folder->cd($copy); + $this->assertTrue($result); + + $mv = TMP . 'test_folder_mv'; + $result = $Folder->move($mv); + $this->assertTrue($result); + + $mv = TMP . 'test_folder_mv_2'; + $result = $Folder->move($mv); + $this->assertTrue($result); + + $result = $Folder->delete($new); + $this->assertTrue($result); + + $result = $Folder->delete($mv); + $this->assertTrue($result); + + $result = $Folder->delete($mv); + $this->assertTrue($result); + + $new = APP . 'index.php'; + $result = $Folder->create($new); + $this->assertFalse($result); + + $expected = $new . ' is a file'; + $result = array_pop($Folder->errors()); + $this->assertEqual($result, $expected); + + $new = TMP . 'test_folder_new'; + $result = $Folder->create($new); + $this->assertTrue($result); + + $result = $Folder->cd($new); + $this->assertTrue($result); + + $result = $Folder->delete(); + $this->assertTrue($result); + + $Folder =& new Folder('non-existent'); + $result = $Folder->pwd(); + $this->assertNull($result); + } + +/** + * testChmod method + * + * @return void + * @access public + */ + function testChmod() { + $this->skipIf(DIRECTORY_SEPARATOR === '\\', '%s Folder permissions tests not supported on Windows'); + + $path = TEST_CAKE_CORE_INCLUDE_PATH . 'console' . DS . 'templates' . DS . 'skel'; + $Folder =& new Folder($path); + + $subdir = 'test_folder_new'; + $new = TMP . $subdir; + + $this->assertTrue($Folder->create($new)); + $this->assertTrue($Folder->create($new . DS . 'test1')); + $this->assertTrue($Folder->create($new . DS . 'test2')); + + $filePath = $new . DS . 'test1.php'; + $File =& new File($filePath); + $this->assertTrue($File->create()); + $copy = TMP . 'test_folder_copy'; + + $this->assertTrue($Folder->chmod($new, 0777, true)); + $this->assertEqual($File->perms(), '0777'); + + $Folder->delete($new); + } + +/** + * testRealPathForWebroot method + * + * @access public + * @return void + */ + function testRealPathForWebroot() { + $Folder = new Folder('files/'); + $this->assertEqual(realpath('files/'), $Folder->path); + } + +/** + * testZeroAsDirectory method + * + * @access public + * @return void + */ + function testZeroAsDirectory() { + $Folder =& new Folder(TMP); + $new = TMP . '0'; + $this->assertTrue($Folder->create($new)); + + $result = $Folder->read(true, true); + $expected = array('0', 'cache', 'logs', 'sessions', 'tests'); + $this->assertEqual($expected, $result[0]); + + $result = $Folder->read(true, array('.', '..', 'logs', '.svn')); + $expected = array('0', 'cache', 'sessions', 'tests'); + $this->assertEqual($expected, $result[0]); + + $result = $Folder->delete($new); + $this->assertTrue($result); + } + +/** + * test Adding path elements to a path + * + * @return void + */ + function testAddPathElement() { + $result = Folder::addPathElement(DS . 'some' . DS . 'dir', 'another_path'); + $this->assertEqual($result, DS . 'some' . DS . 'dir' . DS . 'another_path'); + + $result = Folder::addPathElement(DS . 'some' . DS . 'dir' . DS, 'another_path'); + $this->assertEqual($result, DS . 'some' . DS . 'dir' . DS . 'another_path'); + } +/** + * testFolderRead method + * + * @access public + * @return void + */ + function testFolderRead() { + $Folder =& new Folder(TMP); + + $expected = array('cache', 'logs', 'sessions', 'tests'); + $result = $Folder->read(true, true); + $this->assertEqual($result[0], $expected); + + $Folder->path = TMP . DS . 'non-existent'; + $expected = array(array(), array()); + $result = $Folder->read(true, true); + $this->assertEqual($result, $expected); + } + +/** + * testFolderTree method + * + * @access public + * @return void + */ + function testFolderTree() { + $Folder =& new Folder(); + $expected = array( + array( + TEST_CAKE_CORE_INCLUDE_PATH . 'config', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' + ), + array( + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'config.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'paths.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '0080_00ff.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '0100_017f.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '0180_024F.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '0250_02af.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '0370_03ff.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '0400_04ff.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '0500_052f.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '0530_058f.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '1e00_1eff.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '1f00_1fff.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '2100_214f.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '2150_218f.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '2460_24ff.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '2c00_2c5f.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '2c60_2c7f.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . '2c80_2cff.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'unicode' . DS . 'casefolding' . DS . 'ff00_ffef.php' + ) + ); + + $result = $Folder->tree(TEST_CAKE_CORE_INCLUDE_PATH . 'config', false); + $this->assertIdentical(array_diff($expected[0], $result[0]), array()); + $this->assertIdentical(array_diff($result[0], $expected[0]), array()); + + $result = $Folder->tree(TEST_CAKE_CORE_INCLUDE_PATH . 'config', false, 'dir'); + $this->assertIdentical(array_diff($expected[0], $result), array()); + $this->assertIdentical(array_diff($result, $expected[0]), array()); + + $result = $Folder->tree(TEST_CAKE_CORE_INCLUDE_PATH . 'config', false, 'files'); + $this->assertIdentical(array_diff($expected[1], $result), array()); + $this->assertIdentical(array_diff($result, $expected[1]), array()); + } + +/** + * testWindowsPath method + * + * @access public + * @return void + */ + function testWindowsPath() { + $this->assertFalse(Folder::isWindowsPath('0:\\cake\\is\\awesome')); + $this->assertTrue(Folder::isWindowsPath('C:\\cake\\is\\awesome')); + $this->assertTrue(Folder::isWindowsPath('d:\\cake\\is\\awesome')); + $this->assertTrue(Folder::isWindowsPath('\\\\vmware-host\\Shared Folders\\file')); + } + +/** + * testIsAbsolute method + * + * @access public + * @return void + */ + function testIsAbsolute() { + $this->assertFalse(Folder::isAbsolute('path/to/file')); + $this->assertFalse(Folder::isAbsolute('cake/')); + $this->assertFalse(Folder::isAbsolute('path\\to\\file')); + $this->assertFalse(Folder::isAbsolute('0:\\path\\to\\file')); + $this->assertFalse(Folder::isAbsolute('\\path/to/file')); + $this->assertFalse(Folder::isAbsolute('\\path\\to\\file')); + + $this->assertTrue(Folder::isAbsolute('/usr/local')); + $this->assertTrue(Folder::isAbsolute('//path/to/file')); + $this->assertTrue(Folder::isAbsolute('C:\\cake')); + $this->assertTrue(Folder::isAbsolute('C:\\path\\to\\file')); + $this->assertTrue(Folder::isAbsolute('d:\\path\\to\\file')); + $this->assertTrue(Folder::isAbsolute('\\\\vmware-host\\Shared Folders\\file')); + } + +/** + * testIsSlashTerm method + * + * @access public + * @return void + */ + function testIsSlashTerm() { + $this->assertFalse(Folder::isSlashTerm('cake')); + + $this->assertTrue(Folder::isSlashTerm('C:\\cake\\')); + $this->assertTrue(Folder::isSlashTerm('/usr/local/')); + } + +/** + * testStatic method + * + * @access public + * @return void + */ + function testSlashTerm() { + $result = Folder::slashTerm('/path/to/file'); + $this->assertEqual($result, '/path/to/file/'); + } + +/** + * testNormalizePath method + * + * @access public + * @return void + */ + function testNormalizePath() { + $path = '/path/to/file'; + $result = Folder::normalizePath($path); + $this->assertEqual($result, '/'); + + $path = '\\path\\\to\\\file'; + $result = Folder::normalizePath($path); + $this->assertEqual($result, '/'); + + $path = 'C:\\path\\to\\file'; + $result = Folder::normalizePath($path); + $this->assertEqual($result, '\\'); + } + +/** + * correctSlashFor method + * + * @access public + * @return void + */ + function testCorrectSlashFor() { + $path = '/path/to/file'; + $result = Folder::correctSlashFor($path); + $this->assertEqual($result, '/'); + + $path = '\\path\\to\\file'; + $result = Folder::correctSlashFor($path); + $this->assertEqual($result, '/'); + + $path = 'C:\\path\to\\file'; + $result = Folder::correctSlashFor($path); + $this->assertEqual($result, '\\'); + } + +/** + * testInCakePath method + * + * @access public + * @return void + */ + function testInCakePath() { + $Folder =& new Folder(); + $Folder->cd(ROOT); + $path = 'C:\\path\\to\\file'; + $result = $Folder->inCakePath($path); + $this->assertFalse($result); + + $path = ROOT; + $Folder->cd(ROOT); + $result = $Folder->inCakePath($path); + $this->assertFalse($result); + + // WHY DOES THIS FAIL ?? + $path = DS . 'cake' . DS . 'config'; + $Folder->cd(ROOT . DS . 'cake' . DS . 'config'); + $result = $Folder->inCakePath($path); + $this->assertTrue($result); + } + +/** + * testFind method + * + * @access public + * @return void + */ + function testFind() { + $Folder =& new Folder(); + $Folder->cd(TEST_CAKE_CORE_INCLUDE_PATH . 'config'); + $result = $Folder->find(); + $expected = array('config.php', 'paths.php'); + $this->assertIdentical(array_diff($expected, $result), array()); + $this->assertIdentical(array_diff($result, $expected), array()); + + $result = $Folder->find('.*', true); + $expected = array('config.php', 'paths.php'); + $this->assertIdentical($result, $expected); + + $result = $Folder->find('.*\.php'); + $expected = array('config.php', 'paths.php'); + $this->assertIdentical(array_diff($expected, $result), array()); + $this->assertIdentical(array_diff($result, $expected), array()); + + $result = $Folder->find('.*\.php', true); + $expected = array('config.php', 'paths.php'); + $this->assertIdentical($result, $expected); + + $result = $Folder->find('.*ig\.php'); + $expected = array('config.php'); + $this->assertIdentical($result, $expected); + + $result = $Folder->find('paths\.php'); + $expected = array('paths.php'); + $this->assertIdentical($result, $expected); + + $Folder->cd(TMP); + $file = new File($Folder->pwd() . DS . 'paths.php', true); + $Folder->create($Folder->pwd() . DS . 'testme'); + $Folder->cd('testme'); + $result = $Folder->find('paths\.php'); + $expected = array(); + $this->assertIdentical($result, $expected); + + $Folder->cd($Folder->pwd() . '/..'); + $result = $Folder->find('paths\.php'); + $expected = array('paths.php'); + $this->assertIdentical($result, $expected); + + $Folder->cd(TMP); + $Folder->delete($Folder->pwd() . DS . 'testme'); + $file->delete(); + } + +/** + * testFindRecursive method + * + * @access public + * @return void + */ + function testFindRecursive() { + $Folder =& new Folder(); + $Folder->cd(TEST_CAKE_CORE_INCLUDE_PATH); + $result = $Folder->findRecursive('(config|paths)\.php'); + $expected = array( + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'config.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'paths.php' + ); + $this->assertIdentical(array_diff($expected, $result), array()); + $this->assertIdentical(array_diff($result, $expected), array()); + + $result = $Folder->findRecursive('(config|paths)\.php', true); + $expected = array( + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'config.php', + TEST_CAKE_CORE_INCLUDE_PATH . 'config' . DS . 'paths.php' + ); + $this->assertIdentical($result, $expected); + + $Folder->cd(TMP); + $Folder->create($Folder->pwd() . DS . 'testme'); + $Folder->cd('testme'); + $File =& new File($Folder->pwd() . DS . 'paths.php'); + $File->create(); + $Folder->cd(TMP . 'sessions'); + $result = $Folder->findRecursive('paths\.php'); + $expected = array(); + $this->assertIdentical($result, $expected); + + $Folder->cd(TMP . 'testme'); + $File =& new File($Folder->pwd() . DS . 'my.php'); + $File->create(); + $Folder->cd($Folder->pwd() . '/../..'); + + $result = $Folder->findRecursive('(paths|my)\.php'); + $expected = array( + TMP . 'testme' . DS . 'my.php', + TMP . 'testme' . DS . 'paths.php' + ); + $this->assertIdentical(array_diff($expected, $result), array()); + $this->assertIdentical(array_diff($result, $expected), array()); + + $result = $Folder->findRecursive('(paths|my)\.php', true); + $expected = array( + TMP . 'testme' . DS . 'my.php', + TMP . 'testme' . DS . 'paths.php' + ); + $this->assertIdentical($result, $expected); + + $Folder->cd(TEST_CAKE_CORE_INCLUDE_PATH . 'config'); + $Folder->cd(TMP); + $Folder->delete($Folder->pwd() . DS . 'testme'); + $File->delete(); + } + +/** + * testConstructWithNonExistantPath method + * + * @access public + * @return void + */ + function testConstructWithNonExistantPath() { + $Folder =& new Folder(TMP . 'config_non_existant', true); + $this->assertTrue(is_dir(TMP . 'config_non_existant')); + $Folder->cd(TMP); + $Folder->delete($Folder->pwd() . 'config_non_existant'); + } + +/** + * testDirSize method + * + * @access public + * @return void + */ + function testDirSize() { + $Folder =& new Folder(TMP . 'config_non_existant', true); + $this->assertEqual($Folder->dirSize(), 0); + + $File =& new File($Folder->pwd() . DS . 'my.php', true, 0777); + $File->create(); + $File->write('something here'); + $File->close(); + $this->assertEqual($Folder->dirSize(), 14); + + $Folder->cd(TMP); + $Folder->delete($Folder->pwd() . 'config_non_existant'); + } + +/** + * testDelete method + * + * @access public + * @return void + */ + function testDelete() { + $path = TMP . 'folder_delete_test'; + $Folder =& new Folder($path, true); + touch(TMP . 'folder_delete_test' . DS . 'file1'); + touch(TMP . 'folder_delete_test' . DS . 'file2'); + + $return = $Folder->delete(); + $this->assertTrue($return); + + $messages = $Folder->messages(); + $errors = $Folder->errors(); + $this->assertEqual($errors, array()); + + $expected = array( + $path . ' created', + $path . DS . 'file1 removed', + $path . DS . 'file2 removed', + $path . ' removed' + ); + $this->assertEqual($expected, $messages); + } + +/** + * testCopy method + * + * Verify that directories and files are copied recursively + * even if the destination directory already exists. + * Subdirectories existing in both destination and source directory + * are skipped and not merged or overwritten. + * + * @return void + * @access public + * @link https://trac.cakephp.org/ticket/6259 + */ + function testCopy() { + $path = TMP . 'folder_test'; + $folder1 = $path . DS . 'folder1'; + $folder2 = $folder1 . DS . 'folder2'; + $folder3 = $path . DS . 'folder3'; + $file1 = $folder1 . DS . 'file1.php'; + $file2 = $folder2 . DS . 'file2.php'; + + new Folder($path, true); + new Folder($folder1, true); + new Folder($folder2, true); + new Folder($folder3, true); + touch($file1); + touch($file2); + + $Folder =& new Folder($folder1); + $result = $Folder->copy($folder3); + $this->assertTrue($result); + $this->assertTrue(file_exists($folder3 . DS . 'file1.php')); + $this->assertTrue(file_exists($folder3 . DS . 'folder2' . DS . 'file2.php')); + + $Folder =& new Folder($folder3); + $Folder->delete(); + + $Folder =& new Folder($folder1); + $result = $Folder->copy($folder3); + $this->assertTrue($result); + $this->assertTrue(file_exists($folder3 . DS . 'file1.php')); + $this->assertTrue(file_exists($folder3 . DS . 'folder2' . DS . 'file2.php')); + + $Folder =& new Folder($folder3); + $Folder->delete(); + + new Folder($folder3, true); + new Folder($folder3 . DS . 'folder2', true); + file_put_contents($folder3 . DS . 'folder2' . DS . 'file2.php', 'untouched'); + + $Folder =& new Folder($folder1); + $result = $Folder->copy($folder3); + $this->assertTrue($result); + $this->assertTrue(file_exists($folder3 . DS . 'file1.php')); + $this->assertEqual(file_get_contents($folder3 . DS . 'folder2' . DS . 'file2.php'), 'untouched'); + + $Folder =& new Folder($path); + $Folder->delete(); + } + +/** + * testMove method + * + * Verify that directories and files are moved recursively + * even if the destination directory already exists. + * Subdirectories existing in both destination and source directory + * are skipped and not merged or overwritten. + * + * @return void + * @access public + * @link https://trac.cakephp.org/ticket/6259 + */ + function testMove() { + $path = TMP . 'folder_test'; + $folder1 = $path . DS . 'folder1'; + $folder2 = $folder1 . DS . 'folder2'; + $folder3 = $path . DS . 'folder3'; + $file1 = $folder1 . DS . 'file1.php'; + $file2 = $folder2 . DS . 'file2.php'; + + new Folder($path, true); + new Folder($folder1, true); + new Folder($folder2, true); + new Folder($folder3, true); + touch($file1); + touch($file2); + + $Folder =& new Folder($folder1); + $result = $Folder->move($folder3); + $this->assertTrue($result); + $this->assertTrue(file_exists($folder3 . DS . 'file1.php')); + $this->assertTrue(is_dir($folder3 . DS . 'folder2')); + $this->assertTrue(file_exists($folder3 . DS . 'folder2' . DS . 'file2.php')); + $this->assertFalse(file_exists($file1)); + $this->assertFalse(file_exists($folder2)); + $this->assertFalse(file_exists($file2)); + + $Folder =& new Folder($folder3); + $Folder->delete(); + + new Folder($folder1, true); + new Folder($folder2, true); + touch($file1); + touch($file2); + + $Folder =& new Folder($folder1); + $result = $Folder->move($folder3); + $this->assertTrue($result); + $this->assertTrue(file_exists($folder3 . DS . 'file1.php')); + $this->assertTrue(is_dir($folder3 . DS . 'folder2')); + $this->assertTrue(file_exists($folder3 . DS . 'folder2' . DS . 'file2.php')); + $this->assertFalse(file_exists($file1)); + $this->assertFalse(file_exists($folder2)); + $this->assertFalse(file_exists($file2)); + + $Folder =& new Folder($folder3); + $Folder->delete(); + + new Folder($folder1, true); + new Folder($folder2, true); + new Folder($folder3, true); + new Folder($folder3 . DS . 'folder2', true); + touch($file1); + touch($file2); + file_put_contents($folder3 . DS . 'folder2' . DS . 'file2.php', 'untouched'); + + $Folder =& new Folder($folder1); + $result = $Folder->move($folder3); + $this->assertTrue($result); + $this->assertTrue(file_exists($folder3 . DS . 'file1.php')); + $this->assertEqual(file_get_contents($folder3 . DS . 'folder2' . DS . 'file2.php'), 'untouched'); + $this->assertFalse(file_exists($file1)); + $this->assertFalse(file_exists($folder2)); + $this->assertFalse(file_exists($file2)); + + $Folder =& new Folder($path); + $Folder->delete(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/http_socket.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/http_socket.test.php new file mode 100644 index 000000000..ec024bc76 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/http_socket.test.php @@ -0,0 +1,1558 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'HttpSocket'); + +class TestHttpSocket extends HttpSocket { + +/** + * Convenience method for testing protected method + * + * @param mixed $uri URI (see {@link _parseUri()}) + * @return array Current configuration settings + */ + function configUri($uri = null) { + return parent::_configUri($uri); + } + +/** + * Convenience method for testing protected method + * + * @param string $uri URI to parse + * @param mixed $base If true use default URI config, otherwise indexed array to set 'scheme', 'host', 'port', etc. + * @return array Parsed URI + */ + function parseUri($uri = null, $base = array()) { + return parent::_parseUri($uri, $base); + } + +/** + * Convenience method for testing protected method + * + * @param array $uri A $uri array, or uses $this->config if left empty + * @param string $uriTemplate The Uri template/format to use + * @return string A fully qualified URL formated according to $uriTemplate + */ + function buildUri($uri = array(), $uriTemplate = '%scheme://%user:%pass@%host:%port/%path?%query#%fragment') { + return parent::_buildUri($uri, $uriTemplate); + } + +/** + * Convenience method for testing protected method + * + * @param array $header Header to build + * @return string Header built from array + */ + function buildHeader($header, $mode = 'standard') { + return parent::_buildHeader($header, $mode); + } + +/** + * Convenience method for testing protected method + * + * @param string $message Message to parse + * @return array Parsed message (with indexed elements such as raw, status, header, body) + */ + function parseResponse($message) { + return parent::_parseResponse($message); + } + +/** + * Convenience method for testing protected method + * + * @param array $header Header as an indexed array (field => value) + * @return array Parsed header + */ + function parseHeader($header) { + return parent::_parseHeader($header); + } + +/** + * Convenience method for testing protected method + * + * @param mixed $query A query string to parse into an array or an array to return directly "as is" + * @return array The $query parsed into a possibly multi-level array. If an empty $query is given, an empty array is returned. + */ + function parseQuery($query) { + return parent::_parseQuery($query); + } + +/** + * Convenience method for testing protected method + * + * @param string $body A string continaing the body to decode + * @param mixed $encoding Can be false in case no encoding is being used, or a string representing the encoding + * @return mixed Array or false + */ + function decodeBody($body, $encoding = 'chunked') { + return parent::_decodeBody($body, $encoding); + } + +/** + * Convenience method for testing protected method + * + * @param string $body A string continaing the chunked body to decode + * @return mixed Array or false + */ + function decodeChunkedBody($body) { + return parent::_decodeChunkedBody($body); + } + +/** + * Convenience method for testing protected method + * + * @param array $request Needs to contain a 'uri' key. Should also contain a 'method' key, otherwise defaults to GET. + * @param string $versionToken The version token to use, defaults to HTTP/1.1 + * @return string Request line + */ + function buildRequestLine($request = array(), $versionToken = 'HTTP/1.1') { + return parent::_buildRequestLine($request, $versionToken); + } + +/** + * Convenience method for testing protected method + * + * @param boolean $hex true to get them as HEX values, false otherwise + * @return array Escape chars + */ + function tokenEscapeChars($hex = true, $chars = null) { + return parent::_tokenEscapeChars($hex, $chars); + } + +/** + * Convenience method for testing protected method + * + * @param string $token Token to escape + * @return string Escaped token + */ + function EscapeToken($token, $chars = null) { + return parent::_escapeToken($token, $chars); + } + +/** + * Convenience method for testing protected method + * + * @param string $token Token to unescape + * @return string Unescaped token + */ + function unescapeToken($token, $chars = null) { + return parent::_unescapeToken($token, $chars); + } +} + +/** + * HttpSocketTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class HttpSocketTest extends CakeTestCase { + +/** + * Socket property + * + * @var mixed null + * @access public + */ + var $Socket = null; + +/** + * RequestSocket property + * + * @var mixed null + * @access public + */ + var $RequestSocket = null; + +/** + * This function sets up a TestHttpSocket instance we are going to use for testing + * + * @access public + * @return void + */ + function setUp() { + if (!class_exists('MockHttpSocket')) { + Mock::generatePartial('TestHttpSocket', 'MockHttpSocket', array('read', 'write', 'connect')); + Mock::generatePartial('TestHttpSocket', 'MockHttpSocketRequests', array('read', 'write', 'connect', 'request')); + } + + $this->Socket =& new MockHttpSocket(); + $this->RequestSocket =& new MockHttpSocketRequests(); + } + +/** + * We use this function to clean up after the test case was executed + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Socket, $this->RequestSocket); + } + +/** + * Test that HttpSocket::__construct does what one would expect it to do + * + * @access public + * @return void + */ + function testConstruct() { + $this->Socket->reset(); + $baseConfig = $this->Socket->config; + $this->Socket->expectNever('connect'); + $this->Socket->__construct(array('host' => 'foo-bar')); + $baseConfig['host'] = 'foo-bar'; + $baseConfig['protocol'] = getprotobyname($baseConfig['protocol']); + $this->assertIdentical($this->Socket->config, $baseConfig); + $this->Socket->reset(); + $baseConfig = $this->Socket->config; + $this->Socket->__construct('http://www.cakephp.org:23/'); + $baseConfig['host'] = 'www.cakephp.org'; + $baseConfig['request']['uri']['host'] = 'www.cakephp.org'; + $baseConfig['port'] = 23; + $baseConfig['request']['uri']['port'] = 23; + $baseConfig['protocol'] = getprotobyname($baseConfig['protocol']); + $this->assertIdentical($this->Socket->config, $baseConfig); + + $this->Socket->reset(); + $this->Socket->__construct(array('request' => array('uri' => 'http://www.cakephp.org:23/'))); + $this->assertIdentical($this->Socket->config, $baseConfig); + } + +/** + * Test that HttpSocket::configUri works properly with different types of arguments + * + * @access public + * @return void + */ + function testConfigUri() { + $this->Socket->reset(); + $r = $this->Socket->configUri('https://bob:secret@www.cakephp.org:23/?query=foo'); + $expected = array( + 'persistent' => false, + 'host' => 'www.cakephp.org', + 'protocol' => 'tcp', + 'port' => 23, + 'timeout' => 30, + 'request' => array( + 'uri' => array( + 'scheme' => 'https' + , 'host' => 'www.cakephp.org' + , 'port' => 23 + ), + 'auth' => array( + 'method' => 'Basic' + , 'user' => 'bob' + , 'pass' => 'secret' + ), + 'cookies' => array(), + ) + ); + $this->assertIdentical($this->Socket->config, $expected); + $this->assertIdentical($r, $expected); + $r = $this->Socket->configUri(array('host' => 'www.foo-bar.org')); + $expected['host'] = 'www.foo-bar.org'; + $expected['request']['uri']['host'] = 'www.foo-bar.org'; + $this->assertIdentical($this->Socket->config, $expected); + $this->assertIdentical($r, $expected); + + $r = $this->Socket->configUri('http://www.foo.com'); + $expected = array( + 'persistent' => false, + 'host' => 'www.foo.com', + 'protocol' => 'tcp', + 'port' => 80, + 'timeout' => 30, + 'request' => array( + 'uri' => array( + 'scheme' => 'http' + , 'host' => 'www.foo.com' + , 'port' => 80 + ), + 'auth' => array( + 'method' => 'Basic' + , 'user' => null + , 'pass' => null + ), + 'cookies' => array() + ) + ); + $this->assertIdentical($this->Socket->config, $expected); + $this->assertIdentical($r, $expected); + $r = $this->Socket->configUri('/this-is-broken'); + $this->assertIdentical($this->Socket->config, $expected); + $this->assertIdentical($r, false); + $r = $this->Socket->configUri(false); + $this->assertIdentical($this->Socket->config, $expected); + $this->assertIdentical($r, false); + } + +/** + * Tests that HttpSocket::request (the heart of the HttpSocket) is working properly. + * + * @access public + * @return void + */ + function testRequest() { + $this->Socket->reset(); + + $this->Socket->reset(); + $response = $this->Socket->request(true); + $this->assertFalse($response); + + $tests = array( + 0 => array( + 'request' => 'http://www.cakephp.org/?foo=bar' + , 'expectation' => array( + 'config' => array( + 'persistent' => false + , 'host' => 'www.cakephp.org' + , 'protocol' => 'tcp' + , 'port' => 80 + , 'timeout' => 30 + , 'request' => array( + 'uri' => array ( + 'scheme' => 'http' + , 'host' => 'www.cakephp.org' + , 'port' => 80, + ) + , 'auth' => array( + 'method' => 'Basic' + ,'user' => null + ,'pass' => null + ), + 'cookies' => array(), + ), + ) + , 'request' => array( + 'method' => 'GET' + , 'uri' => array( + 'scheme' => 'http' + , 'host' => 'www.cakephp.org' + , 'port' => 80 + , 'user' => null + , 'pass' => null + , 'path' => '/' + , 'query' => array('foo' => 'bar') + , 'fragment' => null + ) + , 'auth' => array( + 'method' => 'Basic' + , 'user' => null + , 'pass' => null + ) + , 'version' => '1.1' + , 'body' => '' + , 'line' => "GET /?foo=bar HTTP/1.1\r\n" + , 'header' => "Host: www.cakephp.org\r\nConnection: close\r\nUser-Agent: CakePHP\r\n" + , 'raw' => "" + , 'cookies' => array(), + ) + ) + ) + , 1 => array( + 'request' => array( + 'uri' => array( + 'host' => 'www.cakephp.org' + , 'query' => '?foo=bar' + ) + ) + ) + , 2 => array( + 'request' => 'www.cakephp.org/?foo=bar' + ) + , 3 => array( + 'request' => array('host' => '192.168.0.1', 'uri' => 'http://www.cakephp.org/?foo=bar') + , 'expectation' => array( + 'request' => array( + 'uri' => array('host' => 'www.cakephp.org') + ) + , 'config' => array( + 'request' => array( + 'uri' => array('host' => 'www.cakephp.org') + ) + , 'host' => '192.168.0.1' + ) + ) + ) + , 'reset4' => array( + 'request.uri.query' => array() + ) + , 4 => array( + 'request' => array('header' => array('Foo@woo' => 'bar-value')) + , 'expectation' => array( + 'request' => array( + 'header' => "Host: www.cakephp.org\r\nConnection: close\r\nUser-Agent: CakePHP\r\nFoo\"@\"woo: bar-value\r\n" + , 'line' => "GET / HTTP/1.1\r\n" + ) + ) + ) + , 5 => array( + 'request' => array('header' => array('Foo@woo' => 'bar-value', 'host' => 'foo.com'), 'uri' => 'http://www.cakephp.org/') + , 'expectation' => array( + 'request' => array( + 'header' => "Host: foo.com\r\nConnection: close\r\nUser-Agent: CakePHP\r\nFoo\"@\"woo: bar-value\r\n" + ) + , 'config' => array( + 'host' => 'www.cakephp.org' + ) + ) + ) + , 6 => array( + 'request' => array('header' => "Foo: bar\r\n") + , 'expectation' => array( + 'request' => array( + 'header' => "Foo: bar\r\n" + ) + ) + ) + , 7 => array( + 'request' => array('header' => "Foo: bar\r\n", 'uri' => 'http://www.cakephp.org/search?q=http_socket#ignore-me') + , 'expectation' => array( + 'request' => array( + 'uri' => array( + 'path' => '/search' + , 'query' => array('q' => 'http_socket') + , 'fragment' => 'ignore-me' + ) + , 'line' => "GET /search?q=http_socket HTTP/1.1\r\n" + ) + ) + ) + , 'reset8' => array( + 'request.uri.query' => array() + ) + , 8 => array( + 'request' => array('method' => 'POST', 'uri' => 'http://www.cakephp.org/posts/add', 'body' => array('name' => 'HttpSocket-is-released', 'date' => 'today')) + , 'expectation' => array( + 'request' => array( + 'method' => 'POST' + , 'uri' => array( + 'path' => '/posts/add' + , 'fragment' => null + ) + , 'body' => "name=HttpSocket-is-released&date=today" + , 'line' => "POST /posts/add HTTP/1.1\r\n" + , 'header' => "Host: www.cakephp.org\r\nConnection: close\r\nUser-Agent: CakePHP\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 38\r\n" + , 'raw' => "name=HttpSocket-is-released&date=today" + ) + ) + ) + , 9 => array( + 'request' => array('method' => 'POST', 'uri' => 'http://www.cakephp.org:8080/posts/add', 'body' => array('name' => 'HttpSocket-is-released', 'date' => 'today')) + , 'expectation' => array( + 'config' => array( + 'port' => 8080 + , 'request' => array( + 'uri' => array( + 'port' => 8080 + ) + ) + ) + , 'request' => array( + 'uri' => array( + 'port' => 8080 + ) + , 'header' => "Host: www.cakephp.org:8080\r\nConnection: close\r\nUser-Agent: CakePHP\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 38\r\n" + ) + ) + ) + , 10 => array( + 'request' => array('method' => 'POST', 'uri' => 'https://www.cakephp.org/posts/add', 'body' => array('name' => 'HttpSocket-is-released', 'date' => 'today')) + , 'expectation' => array( + 'config' => array( + 'port' => 443 + , 'request' => array( + 'uri' => array( + 'scheme' => 'https' + , 'port' => 443 + ) + ) + ) + , 'request' => array( + 'uri' => array( + 'scheme' => 'https' + , 'port' => 443 + ) + , 'header' => "Host: www.cakephp.org\r\nConnection: close\r\nUser-Agent: CakePHP\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 38\r\n" + ) + ) + ) + , 11 => array( + 'request' => array( + 'method' => 'POST', + 'uri' => 'https://www.cakephp.org/posts/add', + 'body' => array('name' => 'HttpSocket-is-released', 'date' => 'today'), + 'cookies' => array('foo' => array('value' => 'bar')) + ) + , 'expectation' => array( + 'request' => array( + 'header' => "Host: www.cakephp.org\r\nConnection: close\r\nUser-Agent: CakePHP\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 38\r\nCookie: foo=bar\r\n", + 'cookies' => array( + 'foo' => array('value' => 'bar'), + ) + ) + ) + ) + ); + + $expectation = array(); + foreach ($tests as $i => $test) { + if (strpos($i, 'reset') === 0) { + foreach ($test as $path => $val) { + $expectation = Set::insert($expectation, $path, $val); + } + continue; + } + + if (isset($test['expectation'])) { + $expectation = Set::merge($expectation, $test['expectation']); + } + $this->Socket->request($test['request']); + + $raw = $expectation['request']['raw']; + $expectation['request']['raw'] = $expectation['request']['line'].$expectation['request']['header']."\r\n".$raw; + + $r = array('config' => $this->Socket->config, 'request' => $this->Socket->request); + $v = $this->assertIdentical($r, $expectation, '%s in test #'.$i.' '); + $expectation['request']['raw'] = $raw; + } + + $this->Socket->reset(); + $request = array('method' => 'POST', 'uri' => 'http://www.cakephp.org/posts/add', 'body' => array('name' => 'HttpSocket-is-released', 'date' => 'today')); + $response = $this->Socket->request($request); + $this->assertIdentical($this->Socket->request['body'], "name=HttpSocket-is-released&date=today"); + + $request = array('uri' => '*', 'method' => 'GET'); + $this->expectError(new PatternExpectation('/activate quirks mode/i')); + $response = $this->Socket->request($request); + $this->assertFalse($response); + $this->assertFalse($this->Socket->response); + + $this->Socket->reset(); + $request = array('uri' => 'htpp://www.cakephp.org/'); + $this->Socket->setReturnValue('connect', true); + $this->Socket->setReturnValue('read', false); + $this->Socket->_mock->_call_counts['read'] = 0; + $number = mt_rand(0, 9999999); + $serverResponse = "HTTP/1.x 200 OK\r\nDate: Mon, 16 Apr 2007 04:14:16 GMT\r\nServer: CakeHttp Server\r\nContent-Type: text/html\r\n\r\n

    Hello, your lucky number is " . $number . "

    "; + $this->Socket->setReturnValueAt(0, 'read', $serverResponse); + $this->Socket->expect('write', array("GET / HTTP/1.1\r\nHost: www.cakephp.org\r\nConnection: close\r\nUser-Agent: CakePHP\r\n\r\n")); + $this->Socket->expectCallCount('read', 2); + $response = $this->Socket->request($request); + $this->assertIdentical($response, "

    Hello, your lucky number is " . $number . "

    "); + + $this->Socket->reset(); + $serverResponse = "HTTP/1.x 200 OK\r\nSet-Cookie: foo=bar\r\nDate: Mon, 16 Apr 2007 04:14:16 GMT\r\nServer: CakeHttp Server\r\nContent-Type: text/html\r\n\r\n

    This is a cookie test!

    "; + unset($this->Socket->_mock->_actions->_at['read']); + unset($this->Socket->_mock->_return_sequence['read']); + $this->Socket->_mock->_call_counts['read'] = 0; + $this->Socket->setReturnValueAt(0, 'read', $serverResponse); + + $this->Socket->connected = true; + $this->Socket->request($request); + $result = $this->Socket->response['cookies']; + $expect = array( + 'foo' => array( + 'value' => 'bar' + ) + ); + $this->assertEqual($result, $expect); + $this->assertEqual($this->Socket->config['request']['cookies'], $expect); + $this->assertFalse($this->Socket->connected); + } + +/** + * testUrl method + * + * @access public + * @return void + */ + function testUrl() { + $this->Socket->reset(true); + + $this->assertIdentical($this->Socket->url(true), false); + + $url = $this->Socket->url('www.cakephp.org'); + $this->assertIdentical($url, 'http://www.cakephp.org/'); + + $url = $this->Socket->url('https://www.cakephp.org/posts/add'); + $this->assertIdentical($url, 'https://www.cakephp.org/posts/add'); + $url = $this->Socket->url('http://www.cakephp/search?q=socket', '/%path?%query'); + $this->assertIdentical($url, '/search?q=socket'); + + $this->Socket->config['request']['uri']['host'] = 'bakery.cakephp.org'; + $url = $this->Socket->url(); + $this->assertIdentical($url, 'http://bakery.cakephp.org/'); + + $this->Socket->configUri('http://www.cakephp.org'); + $url = $this->Socket->url('/search?q=bar'); + $this->assertIdentical($url, 'http://www.cakephp.org/search?q=bar'); + + $url = $this->Socket->url(array('host' => 'www.foobar.org', 'query' => array('q' => 'bar'))); + $this->assertIdentical($url, 'http://www.foobar.org/?q=bar'); + + $url = $this->Socket->url(array('path' => '/supersearch', 'query' => array('q' => 'bar'))); + $this->assertIdentical($url, 'http://www.cakephp.org/supersearch?q=bar'); + + $this->Socket->configUri('http://www.google.com'); + $url = $this->Socket->url('/search?q=socket'); + $this->assertIdentical($url, 'http://www.google.com/search?q=socket'); + + $url = $this->Socket->url(); + $this->assertIdentical($url, 'http://www.google.com/'); + + $this->Socket->configUri('https://www.google.com'); + $url = $this->Socket->url('/search?q=socket'); + $this->assertIdentical($url, 'https://www.google.com/search?q=socket'); + + $this->Socket->reset(); + $this->Socket->configUri('www.google.com:443'); + $url = $this->Socket->url('/search?q=socket'); + $this->assertIdentical($url, 'https://www.google.com/search?q=socket'); + + $this->Socket->reset(); + $this->Socket->configUri('www.google.com:8080'); + $url = $this->Socket->url('/search?q=socket'); + $this->assertIdentical($url, 'http://www.google.com:8080/search?q=socket'); + } + +/** + * testGet method + * + * @access public + * @return void + */ + function testGet() { + $this->RequestSocket->reset(); + + $this->RequestSocket->expect('request', a(array('method' => 'GET', 'uri' => 'http://www.google.com/'))); + $this->RequestSocket->get('http://www.google.com/'); + + $this->RequestSocket->expect('request', a(array('method' => 'GET', 'uri' => 'http://www.google.com/?foo=bar'))); + $this->RequestSocket->get('http://www.google.com/', array('foo' => 'bar')); + + $this->RequestSocket->expect('request', a(array('method' => 'GET', 'uri' => 'http://www.google.com/?foo=bar'))); + $this->RequestSocket->get('http://www.google.com/', 'foo=bar'); + + $this->RequestSocket->expect('request', a(array('method' => 'GET', 'uri' => 'http://www.google.com/?foo=23&foobar=42'))); + $this->RequestSocket->get('http://www.google.com/?foo=bar', array('foobar' => '42', 'foo' => '23')); + + $this->RequestSocket->expect('request', a(array('method' => 'GET', 'uri' => 'http://www.google.com/', 'auth' => array('user' => 'foo', 'pass' => 'bar')))); + $this->RequestSocket->get('http://www.google.com/', null, array('auth' => array('user' => 'foo', 'pass' => 'bar'))); + } + +/** + * test that two consecutive get() calls reset the authentication credentials. + * + * @return void + */ + function testConsecutiveGetResetsAuthCredentials() { + $socket = new MockHttpSocket(); + $socket->config['request']['auth'] = array( + 'method' => 'Basic', + 'user' => 'mark', + 'pass' => 'secret' + ); + $socket->get('http://mark:secret@example.com/test'); + $this->assertEqual($socket->request['uri']['user'], 'mark'); + $this->assertEqual($socket->request['uri']['pass'], 'secret'); + + $socket->get('/test2'); + $this->assertEqual($socket->request['auth']['user'], 'mark'); + $this->assertEqual($socket->request['auth']['pass'], 'secret'); + + $socket->get('/test3'); + $this->assertEqual($socket->request['auth']['user'], 'mark'); + $this->assertEqual($socket->request['auth']['pass'], 'secret'); + } + +/** + * testPostPutDelete method + * + * @access public + * @return void + */ + function testPostPutDelete() { + $this->RequestSocket->reset(); + + foreach (array('POST', 'PUT', 'DELETE') as $method) { + $this->RequestSocket->expect('request', a(array('method' => $method, 'uri' => 'http://www.google.com/', 'body' => array()))); + $this->RequestSocket->{low($method)}('http://www.google.com/'); + + $this->RequestSocket->expect('request', a(array('method' => $method, 'uri' => 'http://www.google.com/', 'body' => array('Foo' => 'bar')))); + $this->RequestSocket->{low($method)}('http://www.google.com/', array('Foo' => 'bar')); + + $this->RequestSocket->expect('request', a(array('method' => $method, 'uri' => 'http://www.google.com/', 'body' => null, 'line' => 'Hey Server'))); + $this->RequestSocket->{low($method)}('http://www.google.com/', null, array('line' => 'Hey Server')); + } + } + +/** + * testParseResponse method + * + * @access public + * @return void + */ + function testParseResponse() { + $this->Socket->reset(); + + $r = $this->Socket->parseResponse(array('foo' => 'bar')); + $this->assertIdentical($r, array('foo' => 'bar')); + + $r = $this->Socket->parseResponse(true); + $this->assertIdentical($r, false); + + $r = $this->Socket->parseResponse("HTTP Foo\r\nBar: La"); + $this->assertIdentical($r, false); + + $tests = array( + 'simple-request' => array( + 'response' => array( + 'status-line' => "HTTP/1.x 200 OK\r\n", + 'header' => "Date: Mon, 16 Apr 2007 04:14:16 GMT\r\nServer: CakeHttp Server\r\n", + 'body' => "

    Hello World

    \r\n

    It's good to be html

    " + ) + , 'expectations' => array( + 'status.http-version' => 'HTTP/1.x', + 'status.code' => 200, + 'status.reason-phrase' => 'OK', + 'header' => $this->Socket->parseHeader("Date: Mon, 16 Apr 2007 04:14:16 GMT\r\nServer: CakeHttp Server\r\n"), + 'body' => "

    Hello World

    \r\n

    It's good to be html

    " + ) + ), + 'no-header' => array( + 'response' => array( + 'status-line' => "HTTP/1.x 404 OK\r\n", + 'header' => null, + ) + , 'expectations' => array( + 'status.code' => 404, + 'header' => array() + ) + ), + 'chunked' => array( + 'response' => array( + 'header' => "Transfer-Encoding: chunked\r\n", + 'body' => "19\r\nThis is a chunked message\r\n0\r\n" + ), + 'expectations' => array( + 'body' => "This is a chunked message", + 'header' => $this->Socket->parseHeader("Transfer-Encoding: chunked\r\n") + ) + ), + 'enitity-header' => array( + 'response' => array( + 'body' => "19\r\nThis is a chunked message\r\n0\r\nFoo: Bar\r\n" + ), + 'expectations' => array( + 'header' => $this->Socket->parseHeader("Transfer-Encoding: chunked\r\nFoo: Bar\r\n") + ) + ), + 'enitity-header-combine' => array( + 'response' => array( + 'header' => "Transfer-Encoding: chunked\r\nFoo: Foobar\r\n" + ), + 'expectations' => array( + 'header' => $this->Socket->parseHeader("Transfer-Encoding: chunked\r\nFoo: Foobar\r\nFoo: Bar\r\n") + ) + ) + ); + + $testResponse = array(); + $expectations = array(); + + foreach ($tests as $name => $test) { + + $testResponse = array_merge($testResponse, $test['response']); + $testResponse['response'] = $testResponse['status-line'].$testResponse['header']."\r\n".$testResponse['body']; + $r = $this->Socket->parseResponse($testResponse['response']); + $expectations = array_merge($expectations, $test['expectations']); + + foreach ($expectations as $property => $expectedVal) { + $val = Set::extract($r, $property); + $this->assertIdentical($val, $expectedVal, 'Test "'.$name.'": response.'.$property.' - %s'); + } + + foreach (array('status-line', 'header', 'body', 'response') as $field) { + $this->assertIdentical($r['raw'][$field], $testResponse[$field], 'Test response.raw.'.$field.': %s'); + } + } + } + +/** + * testDecodeBody method + * + * @access public + * @return void + */ + function testDecodeBody() { + $this->Socket->reset(); + + $r = $this->Socket->decodeBody(true); + $this->assertIdentical($r, false); + + $r = $this->Socket->decodeBody('Foobar', false); + $this->assertIdentical($r, array('body' => 'Foobar', 'header' => false)); + + $encodings = array( + 'chunked' => array( + 'encoded' => "19\r\nThis is a chunked message\r\n0\r\n", + 'decoded' => array('body' => "This is a chunked message", 'header' => false) + ), + 'foo-coded' => array( + 'encoded' => '!Foobar!', + 'decoded' => array('body' => '!Foobar!', 'header' => false), + 'error' => new PatternExpectation('/unknown encoding: foo-coded/i') + ) + ); + + foreach ($encodings as $encoding => $sample) { + if (isset($sample['error'])) { + $this->expectError($sample['error']); + } + + $r = $this->Socket->decodeBody($sample['encoded'], $encoding); + $this->assertIdentical($r, $sample['decoded']); + + if (isset($sample['error'])) { + $this->Socket->quirksMode = true; + $r = $this->Socket->decodeBody($sample['encoded'], $encoding); + $this->assertIdentical($r, $sample['decoded']); + $this->Socket->quirksMode = false; + } + } + } + +/** + * testDecodeChunkedBody method + * + * @access public + * @return void + */ + function testDecodeChunkedBody() { + $this->Socket->reset(); + + $r = $this->Socket->decodeChunkedBody(true); + $this->assertIdentical($r, false); + + $encoded = "19\r\nThis is a chunked message\r\n0\r\n"; + $decoded = "This is a chunked message"; + $r = $this->Socket->decodeChunkedBody($encoded); + $this->assertIdentical($r['body'], $decoded); + $this->assertIdentical($r['header'], false); + + $encoded = "19 \r\nThis is a chunked message\r\n0\r\n"; + $r = $this->Socket->decodeChunkedBody($encoded); + $this->assertIdentical($r['body'], $decoded); + + $encoded = "19\r\nThis is a chunked message\r\nE\r\n\nThat is cool\n\r\n0\r\n"; + $decoded = "This is a chunked message\nThat is cool\n"; + $r = $this->Socket->decodeChunkedBody($encoded); + $this->assertIdentical($r['body'], $decoded); + $this->assertIdentical($r['header'], false); + + $encoded = "19\r\nThis is a chunked message\r\nE;foo-chunk=5\r\n\nThat is cool\n\r\n0\r\n"; + $r = $this->Socket->decodeChunkedBody($encoded); + $this->assertIdentical($r['body'], $decoded); + $this->assertIdentical($r['header'], false); + + $encoded = "19\r\nThis is a chunked message\r\nE\r\n\nThat is cool\n\r\n0\r\nfoo-header: bar\r\ncake: PHP\r\n\r\n"; + $r = $this->Socket->decodeChunkedBody($encoded); + $this->assertIdentical($r['body'], $decoded); + $this->assertIdentical($r['header'], array('Foo-Header' => 'bar', 'Cake' => 'PHP')); + + $encoded = "19\r\nThis is a chunked message\r\nE\r\n\nThat is cool\n\r\n"; + $this->expectError(new PatternExpectation('/activate quirks mode/i')); + $r = $this->Socket->decodeChunkedBody($encoded); + $this->assertIdentical($r, false); + + $this->Socket->quirksMode = true; + $r = $this->Socket->decodeChunkedBody($encoded); + $this->assertIdentical($r['body'], $decoded); + $this->assertIdentical($r['header'], false); + + $encoded = "19\r\nThis is a chunked message\r\nE\r\n\nThat is cool\n\r\nfoo-header: bar\r\ncake: PHP\r\n\r\n"; + $r = $this->Socket->decodeChunkedBody($encoded); + $this->assertIdentical($r['body'], $decoded); + $this->assertIdentical($r['header'], array('Foo-Header' => 'bar', 'Cake' => 'PHP')); + } + +/** + * testBuildRequestLine method + * + * @access public + * @return void + */ + function testBuildRequestLine() { + $this->Socket->reset(); + + $this->expectError(new PatternExpectation('/activate quirks mode/i')); + $r = $this->Socket->buildRequestLine('Foo'); + $this->assertIdentical($r, false); + + $this->Socket->quirksMode = true; + $r = $this->Socket->buildRequestLine('Foo'); + $this->assertIdentical($r, 'Foo'); + $this->Socket->quirksMode = false; + + $r = $this->Socket->buildRequestLine(true); + $this->assertIdentical($r, false); + + $r = $this->Socket->buildRequestLine(array('foo' => 'bar', 'method' => 'foo')); + $this->assertIdentical($r, false); + + $r = $this->Socket->buildRequestLine(array('method' => 'GET', 'uri' => 'http://www.cakephp.org/search?q=socket')); + $this->assertIdentical($r, "GET /search?q=socket HTTP/1.1\r\n"); + + $request = array( + 'method' => 'GET', + 'uri' => array( + 'path' => '/search', + 'query' => array('q' => 'socket') + ) + ); + $r = $this->Socket->buildRequestLine($request); + $this->assertIdentical($r, "GET /search?q=socket HTTP/1.1\r\n"); + + unset($request['method']); + $r = $this->Socket->buildRequestLine($request); + $this->assertIdentical($r, "GET /search?q=socket HTTP/1.1\r\n"); + + $r = $this->Socket->buildRequestLine($request, 'CAKE-HTTP/0.1'); + $this->assertIdentical($r, "GET /search?q=socket CAKE-HTTP/0.1\r\n"); + + $request = array('method' => 'OPTIONS', 'uri' => '*'); + $r = $this->Socket->buildRequestLine($request); + $this->assertIdentical($r, "OPTIONS * HTTP/1.1\r\n"); + + $request['method'] = 'GET'; + $this->expectError(new PatternExpectation('/activate quirks mode/i')); + $r = $this->Socket->buildRequestLine($request); + $this->assertIdentical($r, false); + + $this->expectError(new PatternExpectation('/activate quirks mode/i')); + $r = $this->Socket->buildRequestLine("GET * HTTP/1.1\r\n"); + $this->assertIdentical($r, false); + + $this->Socket->quirksMode = true; + $r = $this->Socket->buildRequestLine($request); + $this->assertIdentical($r, "GET * HTTP/1.1\r\n"); + + $r = $this->Socket->buildRequestLine("GET * HTTP/1.1\r\n"); + $this->assertIdentical($r, "GET * HTTP/1.1\r\n"); + } + +/** + * Asserts that HttpSocket::parseUri is working properly + * + * @access public + * @return void + */ + function testParseUri() { + $this->Socket->reset(); + + $uri = $this->Socket->parseUri(array('invalid' => 'uri-string')); + $this->assertIdentical($uri, false); + + $uri = $this->Socket->parseUri(array('invalid' => 'uri-string'), array('host' => 'somehost')); + $this->assertIdentical($uri, array('host' => 'somehost', 'invalid' => 'uri-string')); + + $uri = $this->Socket->parseUri(false); + $this->assertIdentical($uri, false); + + $uri = $this->Socket->parseUri('/my-cool-path'); + $this->assertIdentical($uri, array('path' => '/my-cool-path')); + + $uri = $this->Socket->parseUri('http://bob:foo123@www.cakephp.org:40/search?q=dessert#results'); + $this->assertIdentical($uri, array( + 'scheme' => 'http', + 'host' => 'www.cakephp.org', + 'port' => 40, + 'user' => 'bob', + 'pass' => 'foo123', + 'path' => '/search', + 'query' => array('q' => 'dessert'), + 'fragment' => 'results' + )); + + $uri = $this->Socket->parseUri('http://www.cakephp.org/'); + $this->assertIdentical($uri, array( + 'scheme' => 'http', + 'host' => 'www.cakephp.org', + 'path' => '/', + )); + + $uri = $this->Socket->parseUri('http://www.cakephp.org', true); + $this->assertIdentical($uri, array( + 'scheme' => 'http', + 'host' => 'www.cakephp.org', + 'port' => 80, + 'user' => null, + 'pass' => null, + 'path' => '/', + 'query' => array(), + 'fragment' => null + )); + + $uri = $this->Socket->parseUri('https://www.cakephp.org', true); + $this->assertIdentical($uri, array( + 'scheme' => 'https', + 'host' => 'www.cakephp.org', + 'port' => 443, + 'user' => null, + 'pass' => null, + 'path' => '/', + 'query' => array(), + 'fragment' => null + )); + + $uri = $this->Socket->parseUri('www.cakephp.org:443/query?foo', true); + $this->assertIdentical($uri, array( + 'scheme' => 'https', + 'host' => 'www.cakephp.org', + 'port' => 443, + 'user' => null, + 'pass' => null, + 'path' => '/query', + 'query' => array('foo' => ""), + 'fragment' => null + )); + + $uri = $this->Socket->parseUri('http://www.cakephp.org', array('host' => 'piephp.org', 'user' => 'bob', 'fragment' => 'results')); + $this->assertIdentical($uri, array( + 'host' => 'www.cakephp.org', + 'user' => 'bob', + 'fragment' => 'results', + 'scheme' => 'http' + )); + + $uri = $this->Socket->parseUri('https://www.cakephp.org', array('scheme' => 'http', 'port' => 23)); + $this->assertIdentical($uri, array( + 'scheme' => 'https', + 'port' => 23, + 'host' => 'www.cakephp.org' + )); + + $uri = $this->Socket->parseUri('www.cakephp.org:59', array('scheme' => array('http', 'https'), 'port' => 80)); + $this->assertIdentical($uri, array( + 'scheme' => 'http', + 'port' => 59, + 'host' => 'www.cakephp.org' + )); + + $uri = $this->Socket->parseUri(array('scheme' => 'http', 'host' => 'www.google.com', 'port' => 8080), array('scheme' => array('http', 'https'), 'host' => 'www.google.com', 'port' => array(80, 443))); + $this->assertIdentical($uri, array( + 'scheme' => 'http', + 'host' => 'www.google.com', + 'port' => 8080, + )); + + $uri = $this->Socket->parseUri('http://www.cakephp.org/?param1=value1¶m2=value2%3Dvalue3'); + $this->assertIdentical($uri, array( + 'scheme' => 'http', + 'host' => 'www.cakephp.org', + 'path' => '/', + 'query' => array( + 'param1' => 'value1', + 'param2' => 'value2=value3' + ) + )); + + $uri = $this->Socket->parseUri('http://www.cakephp.org/?param1=value1¶m2=value2=value3'); + $this->assertIdentical($uri, array( + 'scheme' => 'http', + 'host' => 'www.cakephp.org', + 'path' => '/', + 'query' => array( + 'param1' => 'value1', + 'param2' => 'value2=value3' + ) + )); + } + +/** + * Tests that HttpSocket::buildUri can turn all kinds of uri arrays (and strings) into fully or partially qualified URI's + * + * @access public + * @return void + */ + function testBuildUri() { + $this->Socket->reset(); + + $r = $this->Socket->buildUri(true); + $this->assertIdentical($r, false); + + $r = $this->Socket->buildUri('foo.com'); + $this->assertIdentical($r, 'http://foo.com/'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org')); + $this->assertIdentical($r, 'http://www.cakephp.org/'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org', 'scheme' => 'https')); + $this->assertIdentical($r, 'https://www.cakephp.org/'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org', 'port' => 23)); + $this->assertIdentical($r, 'http://www.cakephp.org:23/'); + + $r = $this->Socket->buildUri(array('path' => 'www.google.com/search', 'query' => 'q=cakephp')); + $this->assertIdentical($r, 'http://www.google.com/search?q=cakephp'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org', 'scheme' => 'https', 'port' => 79)); + $this->assertIdentical($r, 'https://www.cakephp.org:79/'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org', 'path' => 'foo')); + $this->assertIdentical($r, 'http://www.cakephp.org/foo'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org', 'path' => '/foo')); + $this->assertIdentical($r, 'http://www.cakephp.org/foo'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org', 'path' => '/search', 'query' => array('q' => 'HttpSocket'))); + $this->assertIdentical($r, 'http://www.cakephp.org/search?q=HttpSocket'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org', 'fragment' => 'bar')); + $this->assertIdentical($r, 'http://www.cakephp.org/#bar'); + + $r = $this->Socket->buildUri(array( + 'scheme' => 'https', + 'host' => 'www.cakephp.org', + 'port' => 25, + 'user' => 'bob', + 'pass' => 'secret', + 'path' => '/cool', + 'query' => array('foo' => 'bar'), + 'fragment' => 'comment' + )); + $this->assertIdentical($r, 'https://bob:secret@www.cakephp.org:25/cool?foo=bar#comment'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org', 'fragment' => 'bar'), '%fragment?%host'); + $this->assertIdentical($r, 'bar?www.cakephp.org'); + + $r = $this->Socket->buildUri(array('host' => 'www.cakephp.org'), '%fragment???%host'); + $this->assertIdentical($r, '???www.cakephp.org'); + + $r = $this->Socket->buildUri(array('path' => '*'), '/%path?%query'); + $this->assertIdentical($r, '*'); + + $r = $this->Socket->buildUri(array('scheme' => 'foo', 'host' => 'www.cakephp.org')); + $this->assertIdentical($r, 'foo://www.cakephp.org:80/'); + } + +/** + * Asserts that HttpSocket::parseQuery is working properly + * + * @access public + * @return void + */ + function testParseQuery() { + $this->Socket->reset(); + + $query = $this->Socket->parseQuery(array('framework' => 'cakephp')); + $this->assertIdentical($query, array('framework' => 'cakephp')); + + $query = $this->Socket->parseQuery(''); + $this->assertIdentical($query, array()); + + $query = $this->Socket->parseQuery('framework=cakephp'); + $this->assertIdentical($query, array('framework' => 'cakephp')); + + $query = $this->Socket->parseQuery('?framework=cakephp'); + $this->assertIdentical($query, array('framework' => 'cakephp')); + + $query = $this->Socket->parseQuery('a&b&c'); + $this->assertIdentical($query, array('a' => '', 'b' => '', 'c' => '')); + + $query = $this->Socket->parseQuery('value=12345'); + $this->assertIdentical($query, array('value' => '12345')); + + $query = $this->Socket->parseQuery('a[0]=foo&a[1]=bar&a[2]=cake'); + $this->assertIdentical($query, array('a' => array(0 => 'foo', 1 => 'bar', 2 => 'cake'))); + + $query = $this->Socket->parseQuery('a[]=foo&a[]=bar&a[]=cake'); + $this->assertIdentical($query, array('a' => array(0 => 'foo', 1 => 'bar', 2 => 'cake'))); + + $query = $this->Socket->parseQuery('a]][[=foo&[]=bar&]]][]=cake'); + $this->assertIdentical($query, array('a]][[' => 'foo', 0 => 'bar', ']]]' => array('cake'))); + + $query = $this->Socket->parseQuery('a[][]=foo&a[][]=bar&a[][]=cake'); + $expectedQuery = array( + 'a' => array( + 0 => array( + 0 => 'foo' + ), + 1 => array( + 0 => 'bar' + ), + array( + 0 => 'cake' + ) + ) + ); + $this->assertIdentical($query, $expectedQuery); + + $query = $this->Socket->parseQuery('a[][]=foo&a[bar]=php&a[][]=bar&a[][]=cake'); + $expectedQuery = array( + 'a' => array( + 0 => array( + 0 => 'foo' + ), + 'bar' => 'php', + 1 => array( + 0 => 'bar' + ), + array( + 0 => 'cake' + ) + ) + ); + $this->assertIdentical($query, $expectedQuery); + + $query = $this->Socket->parseQuery('user[]=jim&user[3]=tom&user[]=bob'); + $expectedQuery = array( + 'user' => array( + 0 => 'jim', + 3 => 'tom', + 4 => 'bob' + ) + ); + $this->assertIdentical($query, $expectedQuery); + + $queryStr = 'user[0]=foo&user[0][items][]=foo&user[0][items][]=bar&user[][name]=jim&user[1][items][personal][]=book&user[1][items][personal][]=pen&user[1][items][]=ball&user[count]=2&empty'; + $query = $this->Socket->parseQuery($queryStr); + $expectedQuery = array( + 'user' => array( + 0 => array( + 'items' => array( + 'foo', + 'bar' + ) + ), + 1 => array( + 'name' => 'jim', + 'items' => array( + 'personal' => array( + 'book' + , 'pen' + ), + 'ball' + ) + ), + 'count' => '2' + ), + 'empty' => '' + ); + $this->assertIdentical($query, $expectedQuery); + } + +/** + * Tests that HttpSocket::buildHeader can turn a given $header array into a proper header string according to + * HTTP 1.1 specs. + * + * @access public + * @return void + */ + function testBuildHeader() { + $this->Socket->reset(); + + $r = $this->Socket->buildHeader(true); + $this->assertIdentical($r, false); + + $r = $this->Socket->buildHeader('My raw header'); + $this->assertIdentical($r, 'My raw header'); + + $r = $this->Socket->buildHeader(array('Host' => 'www.cakephp.org')); + $this->assertIdentical($r, "Host: www.cakephp.org\r\n"); + + $r = $this->Socket->buildHeader(array('Host' => 'www.cakephp.org', 'Connection' => 'Close')); + $this->assertIdentical($r, "Host: www.cakephp.org\r\nConnection: Close\r\n"); + + $r = $this->Socket->buildHeader(array('People' => array('Bob', 'Jim', 'John'))); + $this->assertIdentical($r, "People: Bob,Jim,John\r\n"); + + $r = $this->Socket->buildHeader(array('Multi-Line-Field' => "This is my\r\nMulti Line field")); + $this->assertIdentical($r, "Multi-Line-Field: This is my\r\n Multi Line field\r\n"); + + $r = $this->Socket->buildHeader(array('Multi-Line-Field' => "This is my\r\n Multi Line field")); + $this->assertIdentical($r, "Multi-Line-Field: This is my\r\n Multi Line field\r\n"); + + $r = $this->Socket->buildHeader(array('Multi-Line-Field' => "This is my\r\n\tMulti Line field")); + $this->assertIdentical($r, "Multi-Line-Field: This is my\r\n\tMulti Line field\r\n"); + + $r = $this->Socket->buildHeader(array('Test@Field' => "My value")); + $this->assertIdentical($r, "Test\"@\"Field: My value\r\n"); + + } + +/** + * Test that HttpSocket::parseHeader can take apart a given (and valid) $header string and turn it into an array. + * + * @access public + * @return void + */ + function testParseHeader() { + $this->Socket->reset(); + + $r = $this->Socket->parseHeader(array('foo' => 'Bar', 'fOO-bAr' => 'quux')); + $this->assertIdentical($r, array('Foo' => 'Bar', 'Foo-Bar' => 'quux')); + + $r = $this->Socket->parseHeader(true); + $this->assertIdentical($r, false); + + $header = "Host: cakephp.org\t\r\n"; + $r = $this->Socket->parseHeader($header); + $expected = array( + 'Host' => 'cakephp.org' + ); + $this->assertIdentical($r, $expected); + + $header = "Date:Sat, 07 Apr 2007 10:10:25 GMT\r\nX-Powered-By: PHP/5.1.2\r\n"; + $r = $this->Socket->parseHeader($header); + $expected = array( + 'Date' => 'Sat, 07 Apr 2007 10:10:25 GMT' + , 'X-Powered-By' => 'PHP/5.1.2' + ); + $this->assertIdentical($r, $expected); + + $header = "people: Jim,John\r\nfoo-LAND: Bar\r\ncAKe-PHP: rocks\r\n"; + $r = $this->Socket->parseHeader($header); + $expected = array( + 'People' => 'Jim,John' + , 'Foo-Land' => 'Bar' + , 'Cake-Php' => 'rocks' + ); + $this->assertIdentical($r, $expected); + + $header = "People: Jim,John,Tim\r\nPeople: Lisa,Tina,Chelsea\r\n"; + $r = $this->Socket->parseHeader($header); + $expected = array( + 'People' => array('Jim,John,Tim', 'Lisa,Tina,Chelsea') + ); + $this->assertIdentical($r, $expected); + + $header = "Multi-Line: I am a \r\nmulti line\t\r\nfield value.\r\nSingle-Line: I am not\r\n"; + $r = $this->Socket->parseHeader($header); + $expected = array( + 'Multi-Line' => "I am a\r\nmulti line\r\nfield value." + , 'Single-Line' => 'I am not' + ); + $this->assertIdentical($r, $expected); + + $header = "Esc\"@\"ped: value\r\n"; + $r = $this->Socket->parseHeader($header); + $expected = array( + 'Esc@ped' => 'value' + ); + $this->assertIdentical($r, $expected); + } + +/** + * testParseCookies method + * + * @access public + * @return void + */ + function testParseCookies() { + $header = array( + 'Set-Cookie' => array( + 'foo=bar', + 'people=jim,jack,johnny";";Path=/accounts', + 'google=not=nice' + ), + 'Transfer-Encoding' => 'chunked', + 'Date' => 'Sun, 18 Nov 2007 18:57:42 GMT', + ); + $cookies = $this->Socket->parseCookies($header); + $expected = array( + 'foo' => array( + 'value' => 'bar' + ), + 'people' => array( + 'value' => 'jim,jack,johnny";"', + 'path' => '/accounts', + ), + 'google' => array( + 'value' => 'not=nice', + ) + ); + $this->assertEqual($cookies, $expected); + + $header['Set-Cookie'][] = 'cakephp=great; Secure'; + $expected['cakephp'] = array('value' => 'great', 'secure' => true); + $cookies = $this->Socket->parseCookies($header); + $this->assertEqual($cookies, $expected); + + $header['Set-Cookie'] = 'foo=bar'; + unset($expected['people'], $expected['cakephp'], $expected['google']); + $cookies = $this->Socket->parseCookies($header); + $this->assertEqual($cookies, $expected); + } + +/** + * testBuildCookies method + * + * @return void + * @access public + * @todo Test more scenarios + */ + function testBuildCookies() { + $cookies = array( + 'foo' => array( + 'value' => 'bar' + ), + 'people' => array( + 'value' => 'jim,jack,johnny;', + 'path' => '/accounts' + ) + ); + $expect = "Cookie: foo=bar; people=jim,jack,johnny\";\"\r\n"; + $result = $this->Socket->buildCookies($cookies); + $this->assertEqual($result, $expect); + } + +/** + * Tests that HttpSocket::_tokenEscapeChars() returns the right characters. + * + * @access public + * @return void + */ + function testTokenEscapeChars() { + $this->Socket->reset(); + + $expected = array( + '\x22','\x28','\x29','\x3c','\x3e','\x40','\x2c','\x3b','\x3a','\x5c','\x2f','\x5b','\x5d','\x3f','\x3d','\x7b', + '\x7d','\x20','\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0a','\x0b','\x0c','\x0d', + '\x0e','\x0f','\x10','\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1a','\x1b','\x1c','\x1d', + '\x1e','\x1f','\x7f' + ); + $r = $this->Socket->tokenEscapeChars(); + $this->assertEqual($r, $expected); + + foreach ($expected as $key => $char) { + $expected[$key] = chr(hexdec(substr($char, 2))); + } + + $r = $this->Socket->tokenEscapeChars(false); + $this->assertEqual($r, $expected); + } + +/** + * Test that HttpSocket::escapeToken is escaping all characters as descriped in RFC 2616 (HTTP 1.1 specs) + * + * @access public + * @return void + */ + function testEscapeToken() { + $this->Socket->reset(); + + $this->assertIdentical($this->Socket->escapeToken('Foo'), 'Foo'); + + $escape = $this->Socket->tokenEscapeChars(false); + foreach ($escape as $char) { + $token = 'My-special-'.$char.'-Token'; + $escapedToken = $this->Socket->escapeToken($token); + $expectedToken = 'My-special-"'.$char.'"-Token'; + + $this->assertIdentical($escapedToken, $expectedToken, 'Test token escaping for ASCII '.ord($char)); + } + + $token = 'Extreme-:Token- -"@-test'; + $escapedToken = $this->Socket->escapeToken($token); + $expectedToken = 'Extreme-":"Token-" "-""""@"-test'; + $this->assertIdentical($expectedToken, $escapedToken); + } + +/** + * Test that escaped token strings are properly unescaped by HttpSocket::unescapeToken + * + * @access public + * @return void + */ + function testUnescapeToken() { + $this->Socket->reset(); + + $this->assertIdentical($this->Socket->unescapeToken('Foo'), 'Foo'); + + $escape = $this->Socket->tokenEscapeChars(false); + foreach ($escape as $char) { + $token = 'My-special-"'.$char.'"-Token'; + $unescapedToken = $this->Socket->unescapeToken($token); + $expectedToken = 'My-special-'.$char.'-Token'; + + $this->assertIdentical($unescapedToken, $expectedToken, 'Test token unescaping for ASCII '.ord($char)); + } + + $token = 'Extreme-":"Token-" "-""""@"-test'; + $escapedToken = $this->Socket->unescapeToken($token); + $expectedToken = 'Extreme-:Token- -"@-test'; + $this->assertIdentical($expectedToken, $escapedToken); + } + +/** + * This tests asserts HttpSocket::reset() resets a HttpSocket instance to it's initial state (before Object::__construct + * got executed) + * + * @access public + * @return void + */ + function testReset() { + $this->Socket->reset(); + + $initialState = get_class_vars('HttpSocket'); + foreach ($initialState as $property => $value) { + $this->Socket->{$property} = 'Overwritten'; + } + + $return = $this->Socket->reset(); + + foreach ($initialState as $property => $value) { + $this->assertIdentical($this->Socket->{$property}, $value); + } + + $this->assertIdentical($return, true); + } + +/** + * This tests asserts HttpSocket::reset(false) resets certain HttpSocket properties to their initial state (before + * Object::__construct got executed). + * + * @access public + * @return void + */ + function testPartialReset() { + $this->Socket->reset(); + + $partialResetProperties = array('request', 'response'); + $initialState = get_class_vars('HttpSocket'); + + foreach ($initialState as $property => $value) { + $this->Socket->{$property} = 'Overwritten'; + } + + $return = $this->Socket->reset(false); + + foreach ($initialState as $property => $originalValue) { + if (in_array($property, $partialResetProperties)) { + $this->assertIdentical($this->Socket->{$property}, $originalValue); + } else { + $this->assertIdentical($this->Socket->{$property}, 'Overwritten'); + } + } + $this->assertIdentical($return, true); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/i18n.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/i18n.test.php new file mode 100644 index 000000000..31b58e901 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/i18n.test.php @@ -0,0 +1,2777 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'i18n'); + +/** + * I18nTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class I18nTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + Cache::delete('object_map', '_cake_core_'); + App::build(array( + 'locales' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'locale' . DS), + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + ), true); + App::objects('plugin', null, false); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Cache::delete('object_map', '_cake_core_'); + App::build(); + App::objects('plugin', null, false); + } + + + function testTranslationCaching() { + Configure::write('Config.language', 'cache_test_po'); + $i18n =& i18n::getInstance(); + + // reset internally stored entries + I18n::clear(); + + Cache::clear(false, '_cake_core_'); + $lang = Configure::read('Config.language');#$i18n->l10n->locale; + + Cache::config('_cake_core_', Cache::config('default')); + + // make some calls to translate using different domains + $this->assertEqual(I18n::translate('dom1.foo', false, 'dom1'), 'Dom 1 Foo'); + $this->assertEqual(I18n::translate('dom1.bar', false, 'dom1'), 'Dom 1 Bar'); + $this->assertEqual($i18n->__domains['dom1']['cache_test_po']['LC_MESSAGES']['dom1.foo'], 'Dom 1 Foo'); + + // reset internally stored entries + I18n::clear(); + + // now only dom1 should be in cache + $cachedDom1 = Cache::read('dom1_' . $lang, '_cake_core_'); + $this->assertEqual($cachedDom1['LC_MESSAGES']['dom1.foo'], 'Dom 1 Foo'); + $this->assertEqual($cachedDom1['LC_MESSAGES']['dom1.bar'], 'Dom 1 Bar'); + // dom2 not in cache + $this->assertFalse(Cache::read('dom2_' . $lang, '_cake_core_')); + + // translate a item of dom2 (adds dom2 to cache) + $this->assertEqual(I18n::translate('dom2.foo', false, 'dom2'), 'Dom 2 Foo'); + + // verify dom2 was cached through manual read from cache + $cachedDom2 = Cache::read('dom2_' . $lang, '_cake_core_'); + $this->assertEqual($cachedDom2['LC_MESSAGES']['dom2.foo'], 'Dom 2 Foo'); + $this->assertEqual($cachedDom2['LC_MESSAGES']['dom2.bar'], 'Dom 2 Bar'); + + // modify cache entry manually to verify that dom1 entries now will be read from cache + $cachedDom1['LC_MESSAGES']['dom1.foo'] = 'FOO'; + Cache::write('dom1_' . $lang, $cachedDom1, '_cake_core_'); + $this->assertEqual(I18n::translate('dom1.foo', false, 'dom1'), 'FOO'); + } + + +/** + * testDefaultStrings method + * + * @access public + * @return void + */ + function testDefaultStrings() { + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 1', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('1 = 1', $plurals)); + $this->assertTrue(in_array('2 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('3 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('4 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('5 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('6 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('7 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('8 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('9 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('10 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('11 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('12 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('13 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('14 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('15 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('16 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('17 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('18 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('19 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('20 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('21 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('22 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('23 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('24 = 0 or > 1', $plurals)); + $this->assertTrue(in_array('25 = 0 or > 1', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 1 (from core)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('1 = 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('2 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('3 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('4 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('5 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('6 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('7 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('8 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('9 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('10 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('11 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('12 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('13 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('14 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('15 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('16 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('17 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('18 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('19 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('20 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('21 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('22 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('23 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('24 = 0 or > 1 (from core)', $corePlurals)); + $this->assertTrue(in_array('25 = 0 or > 1 (from core)', $corePlurals)); + } + +/** + * testPoRulesZero method + * + * @access public + * @return void + */ + function testPoRulesZero() { + Configure::write('Config.language', 'rule_0_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 0 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('1 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('2 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('3 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('4 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('5 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('6 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('7 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('8 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('9 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('10 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('11 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('12 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('13 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('14 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('15 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('16 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('17 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('18 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('19 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('20 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('21 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('22 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('23 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('24 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('25 ends with any # (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 0 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 ends with any # (from core translated)', $corePlurals)); + } + +/** + * testMoRulesZero method + * + * @access public + * @return void + */ + function testMoRulesZero() { + Configure::write('Config.language', 'rule_0_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 0 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('1 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('2 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('3 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('4 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('5 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('6 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('7 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('8 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('9 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('10 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('11 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('12 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('13 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('14 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('15 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('16 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('17 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('18 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('19 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('20 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('21 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('22 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('23 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('24 ends with any # (translated)', $plurals)); + $this->assertTrue(in_array('25 ends with any # (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 0 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 ends with any # (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 ends with any # (from core translated)', $corePlurals)); + } + +/** + * testPoRulesOne method + * + * @access public + * @return void + */ + function testPoRulesOne() { + Configure::write('Config.language', 'rule_1_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 1 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('3 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('4 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('5 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('6 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('7 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('8 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('9 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('10 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('11 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('12 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('13 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('14 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('15 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('16 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('17 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('18 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('19 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('20 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('21 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('22 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('23 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('24 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('25 = 0 or > 1 (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 1 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 = 0 or > 1 (from core translated)', $corePlurals)); + } + +/** + * testMoRulesOne method + * + * @access public + * @return void + */ + function testMoRulesOne() { + Configure::write('Config.language', 'rule_1_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 1 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('3 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('4 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('5 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('6 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('7 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('8 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('9 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('10 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('11 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('12 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('13 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('14 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('15 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('16 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('17 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('18 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('19 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('20 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('21 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('22 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('23 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('24 = 0 or > 1 (translated)', $plurals)); + $this->assertTrue(in_array('25 = 0 or > 1 (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 1 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 = 0 or > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 = 0 or > 1 (from core translated)', $corePlurals)); + } + +/** + * testPoRulesTwo method + * + * @access public + * @return void + */ + function testPoRulesTwo() { + Configure::write('Config.language', 'rule_2_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 2 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 or 1 (translated)', $plurals)); + $this->assertTrue(in_array('1 = 0 or 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('3 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('4 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('5 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('6 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('7 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('8 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('9 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('10 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('11 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('12 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('13 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('14 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('15 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('16 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('17 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('18 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('19 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('20 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('21 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('22 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('23 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('24 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('25 > 1 (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 2 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 or 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 = 0 or 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 > 1 (from core translated)', $corePlurals)); + } + +/** + * testMoRulesTwo method + * + * @access public + * @return void + */ + function testMoRulesTwo() { + Configure::write('Config.language', 'rule_2_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 2 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 or 1 (translated)', $plurals)); + $this->assertTrue(in_array('1 = 0 or 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('3 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('4 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('5 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('6 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('7 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('8 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('9 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('10 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('11 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('12 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('13 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('14 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('15 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('16 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('17 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('18 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('19 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('20 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('21 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('22 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('23 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('24 > 1 (translated)', $plurals)); + $this->assertTrue(in_array('25 > 1 (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 2 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 or 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 = 0 or 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 > 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 > 1 (from core translated)', $corePlurals)); + } + +/** + * testPoRulesThree method + * + * @access public + * @return void + */ + function testPoRulesThree() { + Configure::write('Config.language', 'rule_3_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 3 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 (translated)', $plurals)); + $this->assertTrue(in_array('1 ends 1 but not 11 (translated)', $plurals)); + $this->assertTrue(in_array('2 everything else (translated)', $plurals)); + $this->assertTrue(in_array('3 everything else (translated)', $plurals)); + $this->assertTrue(in_array('4 everything else (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 ends 1 but not 11 (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 3 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends 1 but not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends 1 but not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesThree method + * + * @access public + * @return void + */ + function testMoRulesThree() { + Configure::write('Config.language', 'rule_3_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 3 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 (translated)', $plurals)); + $this->assertTrue(in_array('1 ends 1 but not 11 (translated)', $plurals)); + $this->assertTrue(in_array('2 everything else (translated)', $plurals)); + $this->assertTrue(in_array('3 everything else (translated)', $plurals)); + $this->assertTrue(in_array('4 everything else (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 ends 1 but not 11 (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 3 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends 1 but not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends 1 but not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesFour method + * + * @access public + * @return void + */ + function testPoRulesFour() { + Configure::write('Config.language', 'rule_4_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 4 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 = 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 everything else (translated)', $plurals)); + $this->assertTrue(in_array('4 everything else (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 4 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 = 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesFour method + * + * @access public + * @return void + */ + function testMoRulesFour() { + Configure::write('Config.language', 'rule_4_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 4 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 = 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 everything else (translated)', $plurals)); + $this->assertTrue(in_array('4 everything else (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 4 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 = 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesFive method + * + * @access public + * @return void + */ + function testPoRulesFive() { + Configure::write('Config.language', 'rule_5_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 5 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('0 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('3 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('4 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('5 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('6 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('7 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('8 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('9 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('10 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('11 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('12 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('13 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('14 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('15 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('16 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('17 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('18 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('19 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 5 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('0 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesFive method + * + * @access public + * @return void + */ + function testMoRulesFive() { + Configure::write('Config.language', 'rule_5_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 5 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('0 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('3 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('4 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('5 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('6 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('7 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('8 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('9 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('10 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('11 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('12 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('13 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('14 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('15 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('16 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('17 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('18 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('19 = 0 or ends in 01-19 (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 5 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('0 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 = 0 or ends in 01-19 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesSix method + * + * @access public + * @return void + */ + function testPoRulesSix() { + Configure::write('Config.language', 'rule_6_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 6 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('1 ends in 1, not 11 (translated)', $plurals)); + $this->assertTrue(in_array('2 everything else (translated)', $plurals)); + $this->assertTrue(in_array('3 everything else (translated)', $plurals)); + $this->assertTrue(in_array('4 everything else (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('11 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('12 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('13 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('14 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('15 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('16 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('17 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('18 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('19 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('20 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('21 ends in 1, not 11 (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 6 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends in 1, not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends in 1, not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesSix method + * + * @access public + * @return void + */ + function testMoRulesSix() { + Configure::write('Config.language', 'rule_6_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 6 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('1 ends in 1, not 11 (translated)', $plurals)); + $this->assertTrue(in_array('2 everything else (translated)', $plurals)); + $this->assertTrue(in_array('3 everything else (translated)', $plurals)); + $this->assertTrue(in_array('4 everything else (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('11 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('12 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('13 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('14 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('15 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('16 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('17 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('18 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('19 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('20 ends in 0 or ends in 10-20 (translated)', $plurals)); + $this->assertTrue(in_array('21 ends in 1, not 11 (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 6 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends in 1, not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends in 1, not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesSeven method + * + * @access public + * @return void + */ + function testPoRulesSeven() { + Configure::write('Config.language', 'rule_7_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 7 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 ends in 1, not 11 (translated)', $plurals)); + $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 ends in 1, not 11 (translated)', $plurals)); + $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 7 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends in 1, not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends in 1, not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesSeven method + * + * @access public + * @return void + */ + function testMoRulesSeven() { + Configure::write('Config.language', 'rule_7_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 7 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 ends in 1, not 11 (translated)', $plurals)); + $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 ends in 1, not 11 (translated)', $plurals)); + $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 7 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends in 1, not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends in 1, not 11 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesEight method + * + * @access public + * @return void + */ + function testPoRulesEight() { + Configure::write('Config.language', 'rule_8_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 8 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 is 2-4 (translated)', $plurals)); + $this->assertTrue(in_array('3 is 2-4 (translated)', $plurals)); + $this->assertTrue(in_array('4 is 2-4 (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 8 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 is 2-4 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 is 2-4 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 is 2-4 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesEight method + * + * @access public + * @return void + */ + function testMoRulesEight() { + Configure::write('Config.language', 'rule_8_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 8 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 is 2-4 (translated)', $plurals)); + $this->assertTrue(in_array('3 is 2-4 (translated)', $plurals)); + $this->assertTrue(in_array('4 is 2-4 (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 8 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 is 2-4 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 is 2-4 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 is 2-4 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesNine method + * + * @access public + * @return void + */ + function testPoRulesNine() { + Configure::write('Config.language', 'rule_9_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 9 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 9 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesNine method + * + * @access public + * @return void + */ + function testMoRulesNine() { + Configure::write('Config.language', 'rule_9_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 9 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 9 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesTen method + * + * @access public + * @return void + */ + function testPoRulesTen() { + Configure::write('Config.language', 'rule_10_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 10 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 ends in 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 ends in 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 ends in 03-04 (translated)', $plurals)); + $this->assertTrue(in_array('4 ends in 03-04 (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 10 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends in 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends in 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 ends in 03-04 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 ends in 03-04 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesTen method + * + * @access public + * @return void + */ + function testMoRulesTen() { + Configure::write('Config.language', 'rule_10_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 10 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 ends in 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 ends in 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 ends in 03-04 (translated)', $plurals)); + $this->assertTrue(in_array('4 ends in 03-04 (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 10 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends in 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends in 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 ends in 03-04 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 ends in 03-04 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesEleven method + * + * @access public + * @return void + */ + function testPoRulesEleven() { + Configure::write('Config.language', 'rule_11_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 11 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 is 3-6 (translated)', $plurals)); + $this->assertTrue(in_array('4 is 3-6 (translated)', $plurals)); + $this->assertTrue(in_array('5 is 3-6 (translated)', $plurals)); + $this->assertTrue(in_array('6 is 3-6 (translated)', $plurals)); + $this->assertTrue(in_array('7 is 7-10 (translated)', $plurals)); + $this->assertTrue(in_array('8 is 7-10 (translated)', $plurals)); + $this->assertTrue(in_array('9 is 7-10 (translated)', $plurals)); + $this->assertTrue(in_array('10 is 7-10 (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 11 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 is 3-6 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 is 3-6 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 is 3-6 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 is 3-6 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 is 7-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 is 7-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 is 7-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 is 7-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesEleven method + * + * @access public + * @return void + */ + function testMoRulesEleven() { + Configure::write('Config.language', 'rule_11_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 11 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 is 3-6 (translated)', $plurals)); + $this->assertTrue(in_array('4 is 3-6 (translated)', $plurals)); + $this->assertTrue(in_array('5 is 3-6 (translated)', $plurals)); + $this->assertTrue(in_array('6 is 3-6 (translated)', $plurals)); + $this->assertTrue(in_array('7 is 7-10 (translated)', $plurals)); + $this->assertTrue(in_array('8 is 7-10 (translated)', $plurals)); + $this->assertTrue(in_array('9 is 7-10 (translated)', $plurals)); + $this->assertTrue(in_array('10 is 7-10 (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 11 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 is 3-6 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 is 3-6 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 is 3-6 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 is 3-6 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 is 7-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 is 7-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 is 7-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 is 7-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesTwelve method + * + * @access public + * @return void + */ + function testPoRulesTwelve() { + Configure::write('Config.language', 'rule_12_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 12 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('4 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('5 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('6 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('7 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('8 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('9 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('10 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 12 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesTwelve method + * + * @access public + * @return void + */ + function testMoRulesTwelve() { + Configure::write('Config.language', 'rule_12_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 12 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('4 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('5 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('6 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('7 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('8 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('9 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('10 is 0 or 3-10 (translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 12 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 is 0 or 3-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesThirteen method + * + * @access public + * @return void + */ + function testPoRulesThirteen() { + Configure::write('Config.language', 'rule_13_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 13 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('3 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('4 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('5 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('6 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('7 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('8 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('9 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('10 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('11 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('12 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('13 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('14 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('15 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('16 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('17 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('18 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('19 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('20 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 13 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesThirteen method + * + * @access public + * @return void + */ + function testMoRulesThirteen() { + Configure::write('Config.language', 'rule_13_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 13 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('3 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('4 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('5 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('6 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('7 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('8 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('9 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('10 is 0 or ends in 01-10 (translated)', $plurals)); + $this->assertTrue(in_array('11 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('12 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('13 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('14 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('15 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('16 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('17 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('18 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('19 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('20 ends in 11-20 (translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 13 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 is 0 or ends in 01-10 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 ends in 11-20 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPoRulesFourteen method + * + * @access public + * @return void + */ + function testPoRulesFourteen() { + Configure::write('Config.language', 'rule_14_po'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 14 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 ends in 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 ends in 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 everything else (translated)', $plurals)); + $this->assertTrue(in_array('4 everything else (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 ends in 1 (translated)', $plurals)); + $this->assertTrue(in_array('12 ends in 2 (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 ends in 1 (translated)', $plurals)); + $this->assertTrue(in_array('22 ends in 2 (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 14 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends in 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends in 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 ends in 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 ends in 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends in 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 ends in 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testMoRulesFourteen method + * + * @access public + * @return void + */ + function testMoRulesFourteen() { + Configure::write('Config.language', 'rule_14_mo'); + + $singular = $this->__singular(); + $this->assertEqual('Plural Rule 14 (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (translated)', $plurals)); + $this->assertTrue(in_array('1 ends in 1 (translated)', $plurals)); + $this->assertTrue(in_array('2 ends in 2 (translated)', $plurals)); + $this->assertTrue(in_array('3 everything else (translated)', $plurals)); + $this->assertTrue(in_array('4 everything else (translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (translated)', $plurals)); + $this->assertTrue(in_array('11 ends in 1 (translated)', $plurals)); + $this->assertTrue(in_array('12 ends in 2 (translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (translated)', $plurals)); + $this->assertTrue(in_array('21 ends in 1 (translated)', $plurals)); + $this->assertTrue(in_array('22 ends in 2 (translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (translated)', $plurals)); + + $coreSingular = $this->__singularFromCore(); + $this->assertEqual('Plural Rule 14 (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('1 ends in 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('2 ends in 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('3 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('4 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('11 ends in 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('12 ends in 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('21 ends in 1 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('22 ends in 2 (from core translated)', $corePlurals)); + $this->assertTrue(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertTrue(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testSetLanguageWithSession method + * + * @access public + * @return void + */ + function testSetLanguageWithSession () { + $_SESSION['Config']['language'] = 'po'; + $singular = $this->__singular(); + $this->assertEqual('Po (translated)', $singular); + + $plurals = $this->__plural(); + $this->assertTrue(in_array('0 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('1 is 1 (po translated)', $plurals)); + $this->assertTrue(in_array('2 is 2-4 (po translated)', $plurals)); + $this->assertTrue(in_array('3 is 2-4 (po translated)', $plurals)); + $this->assertTrue(in_array('4 is 2-4 (po translated)', $plurals)); + $this->assertTrue(in_array('5 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('6 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('7 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('8 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('9 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('10 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('11 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('12 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('13 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('14 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('15 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('16 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('17 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('18 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('19 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('20 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('21 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('22 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('23 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('24 everything else (po translated)', $plurals)); + $this->assertTrue(in_array('25 everything else (po translated)', $plurals)); + unset($_SESSION['Config']['language']); + } + +/** + * testNoCoreTranslation method + * + * @access public + * @return void + */ + function testNoCoreTranslation () { + Configure::write('Config.language', 'po'); + $singular = $this->__singular(); + $this->assertEqual('Po (translated)', $singular); + + $coreSingular = $this->__singularFromCore(); + $this->assertNotEqual('Po (from core translated)', $coreSingular); + + $corePlurals = $this->__pluralFromCore(); + $this->assertFalse(in_array('0 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('1 is 1 (from core translated)', $corePlurals)); + $this->assertFalse(in_array('2 is 2-4 (from core translated)', $corePlurals)); + $this->assertFalse(in_array('3 is 2-4 (from core translated)', $corePlurals)); + $this->assertFalse(in_array('4 is 2-4 (from core translated)', $corePlurals)); + $this->assertFalse(in_array('5 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('6 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('7 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('8 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('9 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('10 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('11 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('12 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('13 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('14 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('15 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('16 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('17 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('18 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('19 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('20 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('21 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('22 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('23 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('24 everything else (from core translated)', $corePlurals)); + $this->assertFalse(in_array('25 everything else (from core translated)', $corePlurals)); + } + +/** + * testPluginTranslation method + * + * @access public + * @return void + */ + function testPluginTranslation() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + + Configure::write('Config.language', 'po'); + $singular = $this->__domainSingular(); + $this->assertEqual('Plural Rule 1 (from plugin)', $singular); + + $plurals = $this->__domainPlural(); + $this->assertTrue(in_array('0 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('1 = 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('2 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('3 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('4 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('5 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('6 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('7 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('8 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('9 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('10 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('11 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('12 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('13 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('14 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('15 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('16 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('17 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('18 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('19 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('20 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('21 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('22 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('23 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('24 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('25 = 0 or > 1 (from plugin)', $plurals)); + } + +/** + * testPoMultipleLineTranslation method + * + * @access public + * @return void + */ + function testPoMultipleLineTranslation () { + Configure::write('Config.language', 'po'); + + $string = "This is a multiline translation\n"; + $string .= "broken up over multiple lines.\n"; + $string .= "This is the third line.\n"; + $string .= "This is the forth line."; + $result = __($string, true); + + $expected = "This is a multiline translation\n"; + $expected .= "broken up over multiple lines.\n"; + $expected .= "This is the third line.\n"; + $expected .= "This is the forth line. (translated)"; + $this->assertEqual($result, $expected); + + // Windows Newline is \r\n + $string = "This is a multiline translation\r\n"; + $string .= "broken up over multiple lines.\r\n"; + $string .= "This is the third line.\r\n"; + $string .= "This is the forth line."; + $result = __($string, true); + $this->assertEqual($result, $expected); + + $singular = "valid\nsecond line"; + $plural = "valids\nsecond line"; + + $result = __n($singular, $plural, 1, true); + $expected = "v\nsecond line"; + $this->assertEqual($result, $expected); + + $result = __n($singular, $plural, 2, true); + $expected = "vs\nsecond line"; + $this->assertEqual($result, $expected); + + $string = "This is a multiline translation\n"; + $string .= "broken up over multiple lines.\n"; + $string .= "This is the third line.\n"; + $string .= "This is the forth line."; + + $singular = "%d = 1\n" . $string; + $plural = "%d = 0 or > 1\n" . $string; + + $result = __n($singular, $plural, 1, true); + $expected = "%d is 1\n" . $string; + $this->assertEqual($result, $expected); + + $result = __n($singular, $plural, 2, true); + $expected = "%d is 2-4\n" . $string; + $this->assertEqual($result, $expected); + + // Windows Newline is \r\n + $string = "This is a multiline translation\r\n"; + $string .= "broken up over multiple lines.\r\n"; + $string .= "This is the third line.\r\n"; + $string .= "This is the forth line."; + + $singular = "%d = 1\r\n" . $string; + $plural = "%d = 0 or > 1\r\n" . $string; + + $result = __n($singular, $plural, 1, true); + $expected = "%d is 1\n" . str_replace("\r\n", "\n", $string); + $this->assertEqual($result, $expected); + + $result = __n($singular, $plural, 2, true); + $expected = "%d is 2-4\n" . str_replace("\r\n", "\n", $string); + $this->assertEqual($result, $expected); + } + +/** + * testPoNoTranslationNeeded method + * + * @access public + * @return void + */ + function testPoNoTranslationNeeded () { + Configure::write('Config.language', 'po'); + $result = __('No Translation needed', true); + $this->assertEqual($result, 'No Translation needed'); + } + +/** + * testPoQuotedString method + * + * @access public + * @return void + */ + function testPoQuotedString () { + $expected = 'this is a "quoted string" (translated)'; + $this->assertEqual(__('this is a "quoted string"', true), $expected); + } + +/** + * testFloatValue method + * + * @access public + * @return void + */ + function testFloatValue() { + Configure::write('Config.language', 'rule_9_po'); + + $result = __n('%d = 1', '%d = 0 or > 1', (float)1, true); + $expected = '%d is 1 (translated)'; + $this->assertEqual($result, $expected); + + $result = __n('%d = 1', '%d = 0 or > 1', (float)2, true); + $expected = "%d ends in 2-4, not 12-14 (translated)"; + $this->assertEqual($result, $expected); + + $result = __n('%d = 1', '%d = 0 or > 1', (float)5, true); + $expected = "%d everything else (translated)"; + $this->assertEqual($result, $expected); + } + +/** + * testCategory method + * + * @access public + * @return void + */ + function testCategory() { + Configure::write('Config.language', 'po'); + $category = $this->__category(); + $this->assertEqual('Monetary Po (translated)', $category); + } + +/** + * testPluginCategory method + * + * @access public + * @return void + */ + function testPluginCategory() { + Configure::write('Config.language', 'po'); + + $singular = $this->__domainCategorySingular(); + $this->assertEqual('Monetary Plural Rule 1 (from plugin)', $singular); + + $plurals = $this->__domainCategoryPlural(); + $this->assertTrue(in_array('Monetary 0 = 0 or > 1 (from plugin)', $plurals)); + $this->assertTrue(in_array('Monetary 1 = 1 (from plugin)', $plurals)); + } + +/** + * testCategoryThenSingular method + * + * @access public + * @return void + */ + function testCategoryThenSingular() { + Configure::write('Config.language', 'po'); + $category = $this->__category(); + $this->assertEqual('Monetary Po (translated)', $category); + + $singular = $this->__singular(); + $this->assertEqual('Po (translated)', $singular); + } + + function testTimeDefinition() { + Configure::write('Config.language', 'po'); + $result = __c('d_fmt', 5, true); + $expected = '%m/%d/%Y'; + $this->assertEqual($result, $expected); + + $result = __c('am_pm', 5, true); + $expected = array('AM', 'PM'); + $this->assertEqual($result, $expected); + + $result = __c('abmon', 5, true); + $expected = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); + $this->assertEqual($result, $expected); + } + + function testTimeDefinitionJapanese(){ + Configure::write('Config.language', 'ja_jp'); + $result = __c('d_fmt', 5, true); + + $expected = "%Yå¹´%m月%dæ—¥"; + + $this->assertEqual($result, $expected); + + $result = __c('am_pm', 5, true); + $expected = array("åˆå‰", "åˆå¾Œ"); + $this->assertEqual($result, $expected); + + $result = __c('abmon', 5, true); + $expected = array(" 1月", " 2月", " 3月", " 4月", " 5月", " 6月", " 7月", " 8月", " 9月", "10月", "11月", "12月"); + $this->assertEqual($result, $expected); + } + +/** + * Singular method + * + * @access private + * @return void + */ + function __domainCategorySingular($domain = 'test_plugin', $category = 3) { + $singular = __dc($domain, 'Plural Rule 1', $category, true); + return $singular; + } + +/** + * Plural method + * + * @access private + * @return void + */ + function __domainCategoryPlural($domain = 'test_plugin', $category = 3) { + $plurals = array(); + for ($number = 0; $number <= 25; $number++) { + $plurals[] = sprintf(__dcn($domain, '%d = 1', '%d = 0 or > 1', (float)$number, $category, true), (float)$number); + } + return $plurals; + } + +/** + * Singular method + * + * @access private + * @return void + */ + function __domainSingular($domain = 'test_plugin') { + $singular = __d($domain, 'Plural Rule 1', true); + return $singular; + } + +/** + * Plural method + * + * @access private + * @return void + */ + function __domainPlural($domain = 'test_plugin') { + $plurals = array(); + for ($number = 0; $number <= 25; $number++) { + $plurals[] = sprintf(__dn($domain, '%d = 1', '%d = 0 or > 1', (float)$number, true), (float)$number ); + } + return $plurals; + } + +/** + * category method + * + * @access private + * @return void + */ + function __category($category = 3) { + $singular = __c('Plural Rule 1', $category, true); + return $singular; + } + +/** + * Singular method + * + * @access private + * @return void + */ + function __singular() { + $singular = __('Plural Rule 1', true); + return $singular; + } + +/** + * Plural method + * + * @access private + * @return void + */ + function __plural() { + $plurals = array(); + for ($number = 0; $number <= 25; $number++) { + $plurals[] = sprintf(__n('%d = 1', '%d = 0 or > 1', (float)$number, true), (float)$number ); + } + return $plurals; + } + +/** + * singularFromCore method + * + * @access private + * @return void + */ + function __singularFromCore() { + $singular = __('Plural Rule 1 (from core)', true); + return $singular; + } + +/** + * pluralFromCore method + * + * @access private + * @return void + */ + function __pluralFromCore() { + $plurals = array(); + for ($number = 0; $number <= 25; $number++) { + $plurals[] = sprintf(__n('%d = 1 (from core)', '%d = 0 or > 1 (from core)', (float)$number, true), (float)$number ); + } + return $plurals; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/inflector.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/inflector.test.php new file mode 100644 index 000000000..3cec57e12 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/inflector.test.php @@ -0,0 +1,465 @@ +assertEqual(Inflector::getInstance(), $Inflector); + } + +/** + * testInflectingSingulars method + * + * @access public + * @return void + */ + function testInflectingSingulars() { + $this->assertEqual(Inflector::singularize('categorias'), 'categoria'); + $this->assertEqual(Inflector::singularize('menus'), 'menu'); + $this->assertEqual(Inflector::singularize('news'), 'news'); + $this->assertEqual(Inflector::singularize('food_menus'), 'food_menu'); + $this->assertEqual(Inflector::singularize('Menus'), 'Menu'); + $this->assertEqual(Inflector::singularize('FoodMenus'), 'FoodMenu'); + $this->assertEqual(Inflector::singularize('houses'), 'house'); + $this->assertEqual(Inflector::singularize('powerhouses'), 'powerhouse'); + $this->assertEqual(Inflector::singularize('quizzes'), 'quiz'); + $this->assertEqual(Inflector::singularize('Buses'), 'Bus'); + $this->assertEqual(Inflector::singularize('buses'), 'bus'); + $this->assertEqual(Inflector::singularize('matrix_rows'), 'matrix_row'); + $this->assertEqual(Inflector::singularize('matrices'), 'matrix'); + $this->assertEqual(Inflector::singularize('vertices'), 'vertex'); + $this->assertEqual(Inflector::singularize('indices'), 'index'); + $this->assertEqual(Inflector::singularize('Aliases'), 'Alias'); + $this->assertEqual(Inflector::singularize('Alias'), 'Alias'); + $this->assertEqual(Inflector::singularize('Media'), 'Media'); + $this->assertEqual(Inflector::singularize('alumni'), 'alumnus'); + $this->assertEqual(Inflector::singularize('bacilli'), 'bacillus'); + $this->assertEqual(Inflector::singularize('cacti'), 'cactus'); + $this->assertEqual(Inflector::singularize('foci'), 'focus'); + $this->assertEqual(Inflector::singularize('fungi'), 'fungus'); + $this->assertEqual(Inflector::singularize('nuclei'), 'nucleus'); + $this->assertEqual(Inflector::singularize('octopuses'), 'octopus'); + $this->assertEqual(Inflector::singularize('radii'), 'radius'); + $this->assertEqual(Inflector::singularize('stimuli'), 'stimulus'); + $this->assertEqual(Inflector::singularize('syllabi'), 'syllabus'); + $this->assertEqual(Inflector::singularize('termini'), 'terminus'); + $this->assertEqual(Inflector::singularize('viri'), 'virus'); + $this->assertEqual(Inflector::singularize('people'), 'person'); + $this->assertEqual(Inflector::singularize('gloves'), 'glove'); + $this->assertEqual(Inflector::singularize('doves'), 'dove'); + $this->assertEqual(Inflector::singularize('lives'), 'life'); + $this->assertEqual(Inflector::singularize('knives'), 'knife'); + $this->assertEqual(Inflector::singularize('wolves'), 'wolf'); + $this->assertEqual(Inflector::singularize('slaves'), 'slave'); + $this->assertEqual(Inflector::singularize('shelves'), 'shelf'); + $this->assertEqual(Inflector::singularize('taxis'), 'taxi'); + $this->assertEqual(Inflector::singularize('taxes'), 'tax'); + $this->assertEqual(Inflector::singularize('Taxes'), 'Tax'); + $this->assertEqual(Inflector::singularize('AwesomeTaxes'), 'AwesomeTax'); + $this->assertEqual(Inflector::singularize('faxes'), 'fax'); + $this->assertEqual(Inflector::singularize('waxes'), 'wax'); + $this->assertEqual(Inflector::singularize('niches'), 'niche'); + $this->assertEqual(Inflector::singularize('waves'), 'wave'); + $this->assertEqual(Inflector::singularize('bureaus'), 'bureau'); + $this->assertEqual(Inflector::singularize('genetic_analyses'), 'genetic_analysis'); + $this->assertEqual(Inflector::singularize('doctor_diagnoses'), 'doctor_diagnosis'); + $this->assertEqual(Inflector::singularize('parantheses'), 'paranthesis'); + $this->assertEqual(Inflector::singularize('Causes'), 'Cause'); + $this->assertEqual(Inflector::singularize('colossuses'), 'colossus'); + $this->assertEqual(Inflector::singularize('diagnoses'), 'diagnosis'); + $this->assertEqual(Inflector::singularize('bases'), 'basis'); + $this->assertEqual(Inflector::singularize('analyses'), 'analysis'); + + $this->assertEqual(Inflector::singularize(''), ''); + } + +/** + * testInflectingPlurals method + * + * @access public + * @return void + */ + function testInflectingPlurals() { + $this->assertEqual(Inflector::pluralize('categoria'), 'categorias'); + $this->assertEqual(Inflector::pluralize('house'), 'houses'); + $this->assertEqual(Inflector::pluralize('powerhouse'), 'powerhouses'); + $this->assertEqual(Inflector::pluralize('Bus'), 'Buses'); + $this->assertEqual(Inflector::pluralize('bus'), 'buses'); + $this->assertEqual(Inflector::pluralize('menu'), 'menus'); + $this->assertEqual(Inflector::pluralize('news'), 'news'); + $this->assertEqual(Inflector::pluralize('food_menu'), 'food_menus'); + $this->assertEqual(Inflector::pluralize('Menu'), 'Menus'); + $this->assertEqual(Inflector::pluralize('FoodMenu'), 'FoodMenus'); + $this->assertEqual(Inflector::pluralize('quiz'), 'quizzes'); + $this->assertEqual(Inflector::pluralize('matrix_row'), 'matrix_rows'); + $this->assertEqual(Inflector::pluralize('matrix'), 'matrices'); + $this->assertEqual(Inflector::pluralize('vertex'), 'vertices'); + $this->assertEqual(Inflector::pluralize('index'), 'indices'); + $this->assertEqual(Inflector::pluralize('Alias'), 'Aliases'); + $this->assertEqual(Inflector::pluralize('Aliases'), 'Aliases'); + $this->assertEqual(Inflector::pluralize('Media'), 'Media'); + $this->assertEqual(Inflector::pluralize('alumnus'), 'alumni'); + $this->assertEqual(Inflector::pluralize('bacillus'), 'bacilli'); + $this->assertEqual(Inflector::pluralize('cactus'), 'cacti'); + $this->assertEqual(Inflector::pluralize('focus'), 'foci'); + $this->assertEqual(Inflector::pluralize('fungus'), 'fungi'); + $this->assertEqual(Inflector::pluralize('nucleus'), 'nuclei'); + $this->assertEqual(Inflector::pluralize('octopus'), 'octopuses'); + $this->assertEqual(Inflector::pluralize('radius'), 'radii'); + $this->assertEqual(Inflector::pluralize('stimulus'), 'stimuli'); + $this->assertEqual(Inflector::pluralize('syllabus'), 'syllabi'); + $this->assertEqual(Inflector::pluralize('terminus'), 'termini'); + $this->assertEqual(Inflector::pluralize('virus'), 'viri'); + $this->assertEqual(Inflector::pluralize('person'), 'people'); + $this->assertEqual(Inflector::pluralize('people'), 'people'); + $this->assertEqual(Inflector::pluralize('glove'), 'gloves'); + $this->assertEqual(Inflector::pluralize('crisis'), 'crises'); + $this->assertEqual(Inflector::pluralize('tax'), 'taxes'); + $this->assertEqual(Inflector::pluralize('wave'), 'waves'); + $this->assertEqual(Inflector::pluralize('bureau'), 'bureaus'); + $this->assertEqual(Inflector::pluralize(''), ''); + } + +/** + * testInflectorSlug method + * + * @access public + * @return void + */ + function testInflectorSlug() { + $result = Inflector::slug('Foo Bar: Not just for breakfast any-more'); + $expected = 'Foo_Bar_Not_just_for_breakfast_any_more'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('this/is/a/path'); + $expected = 'this_is_a_path'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('Foo Bar: Not just for breakfast any-more', "-"); + $expected = 'Foo-Bar-Not-just-for-breakfast-any-more'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('Foo Bar: Not just for breakfast any-more', "+"); + $expected = 'Foo+Bar+Not+just+for+breakfast+any+more'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('Äpfel Ãœber Öl grün ärgert groß öko', '-'); + $expected = 'Aepfel-Ueber-Oel-gruen-aergert-gross-oeko'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('The truth - and- more- news', '-'); + $expected = 'The-truth-and-more-news'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('The truth: and more news', '-'); + $expected = 'The-truth-and-more-news'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('La langue française est un attribut de souveraineté en France', '-'); + $expected = 'La-langue-francaise-est-un-attribut-de-souverainete-en-France'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('!@$#exciting stuff! - what !@-# was that?', '-'); + $expected = 'exciting-stuff-what-was-that'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('20% of profits went to me!', '-'); + $expected = '20-of-profits-went-to-me'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('#this melts your face1#2#3', '-'); + $expected = 'this-melts-your-face1-2-3'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('controller/action/ã‚Šã‚“ã”/1'); + $expected = 'controller_action_ã‚Šã‚“ã”_1'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('ã®è©±ãŒå‡ºãŸã®ã§å¤§ä¸ˆå¤«ã‹ãªã‚ã¨'); + $expected = 'ã®è©±ãŒå‡ºãŸã®ã§å¤§ä¸ˆå¤«ã‹ãªã‚ã¨'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('posts/view/한국어/page:1/sort:asc'); + $expected = 'posts_view_한국어_page_1_sort_asc'; + $this->assertEqual($result, $expected); + } + +/** + * testInflectorSlugWithMap method + * + * @access public + * @return void + */ + function testInflectorSlugWithMap() { + $result = Inflector::slug('replace every r', array('/r/' => '1')); + $expected = '1eplace_eve1y_1'; + $this->assertEqual($result, $expected); + + $result = Inflector::slug('replace every r', '_', array('/r/' => '1')); + $expected = '1eplace_eve1y_1'; + $this->assertEqual($result, $expected); + } + +/** + * testInflectorSlugWithMapOverridingDefault method + * + * @access public + * @return void + */ + function testInflectorSlugWithMapOverridingDefault() { + $result = Inflector::slug('Testing æ ø Ã¥', '-', array('/Ã¥/' => 'aa', '/ø/' => 'oe')); + $expected = 'Testing-ae-oe-aa'; + $this->assertEqual($result, $expected); + } + +/** + * testInflectorUnderscore method + * + * @return void + * @access public + */ + function testInflectorUnderscore() { + $this->assertIdentical(Inflector::underscore('TestThing'), 'test_thing'); + $this->assertIdentical(Inflector::underscore('testThing'), 'test_thing'); + $this->assertIdentical(Inflector::underscore('TestThingExtra'), 'test_thing_extra'); + $this->assertIdentical(Inflector::underscore('testThingExtra'), 'test_thing_extra'); + + // Identical checks test the cache code path. + $this->assertIdentical(Inflector::underscore('TestThing'), 'test_thing'); + $this->assertIdentical(Inflector::underscore('testThing'), 'test_thing'); + $this->assertIdentical(Inflector::underscore('TestThingExtra'), 'test_thing_extra'); + $this->assertIdentical(Inflector::underscore('testThingExtra'), 'test_thing_extra'); + + // Test stupid values + $this->assertIdentical(Inflector::underscore(''), ''); + $this->assertIdentical(Inflector::underscore(0), '0'); + $this->assertIdentical(Inflector::underscore(false), ''); + } + +/** + * testVariableNaming method + * + * @access public + * @return void + */ + function testVariableNaming() { + $this->assertEqual(Inflector::variable('test_field'), 'testField'); + $this->assertEqual(Inflector::variable('test_fieLd'), 'testFieLd'); + $this->assertEqual(Inflector::variable('test field'), 'testField'); + $this->assertEqual(Inflector::variable('Test_field'), 'testField'); + } + +/** + * testClassNaming method + * + * @access public + * @return void + */ + function testClassNaming() { + $this->assertEqual(Inflector::classify('artists_genres'), 'ArtistsGenre'); + $this->assertEqual(Inflector::classify('file_systems'), 'FileSystem'); + $this->assertEqual(Inflector::classify('news'), 'News'); + $this->assertEqual(Inflector::classify('bureaus'), 'Bureau'); + } + +/** + * testTableNaming method + * + * @access public + * @return void + */ + function testTableNaming() { + $this->assertEqual(Inflector::tableize('ArtistsGenre'), 'artists_genres'); + $this->assertEqual(Inflector::tableize('FileSystem'), 'file_systems'); + $this->assertEqual(Inflector::tableize('News'), 'news'); + $this->assertEqual(Inflector::tableize('Bureau'), 'bureaus'); + } + +/** + * testHumanization method + * + * @access public + * @return void + */ + function testHumanization() { + $this->assertEqual(Inflector::humanize('posts'), 'Posts'); + $this->assertEqual(Inflector::humanize('posts_tags'), 'Posts Tags'); + $this->assertEqual(Inflector::humanize('file_systems'), 'File Systems'); + } + +/** + * This test if run in isolation should not cause errors in PHP4. + * + * @return void + */ + function testRulesNoErrorPHP4() { + Inflector::rules('plural', array( + 'rules' => array(), + 'irregular' => array(), + 'uninflected' => array('pays') + )); + } + +/** + * testCustomPluralRule method + * + * @access public + * @return void + */ + function testCustomPluralRule() { + Inflector::rules('plural', array('/^(custom)$/i' => '\1izables')); + $this->assertEqual(Inflector::pluralize('custom'), 'customizables'); + + Inflector::rules('plural', array('uninflected' => array('uninflectable'))); + $this->assertEqual(Inflector::pluralize('uninflectable'), 'uninflectable'); + + Inflector::rules('plural', array( + 'rules' => array('/^(alert)$/i' => '\1ables'), + 'uninflected' => array('noflect', 'abtuse'), + 'irregular' => array('amaze' => 'amazable', 'phone' => 'phonezes') + )); + $this->assertEqual(Inflector::pluralize('noflect'), 'noflect'); + $this->assertEqual(Inflector::pluralize('abtuse'), 'abtuse'); + $this->assertEqual(Inflector::pluralize('alert'), 'alertables'); + $this->assertEqual(Inflector::pluralize('amaze'), 'amazable'); + $this->assertEqual(Inflector::pluralize('phone'), 'phonezes'); + } + +/** + * testCustomSingularRule method + * + * @access public + * @return void + */ + function testCustomSingularRule() { + Inflector::rules('singular', array('/(eple)r$/i' => '\1', '/(jente)r$/i' => '\1')); + + $this->assertEqual(Inflector::singularize('epler'), 'eple'); + $this->assertEqual(Inflector::singularize('jenter'), 'jente'); + + Inflector::rules('singular', array( + 'rules' => array('/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta'), + 'uninflected' => array('singulars'), + 'irregular' => array('spins' => 'spinor') + )); + + $this->assertEqual(Inflector::singularize('inflectors'), 'inflecta'); + $this->assertEqual(Inflector::singularize('contributors'), 'contributa'); + $this->assertEqual(Inflector::singularize('spins'), 'spinor'); + $this->assertEqual(Inflector::singularize('singulars'), 'singulars'); + } + +/** + * testCustomTransliterationRule method + * + * @access public + * @return void + */ + function testCustomTransliterationRule() { + $this->assertEqual(Inflector::slug('Testing æ ø Ã¥'), 'Testing_ae_o_a'); + + Inflector::rules('transliteration', array('/Ã¥/' => 'aa', '/ø/' => 'oe')); + $this->assertEqual(Inflector::slug('Testing æ ø Ã¥'), 'Testing_ae_oe_aa'); + + Inflector::rules('transliteration', array('/ä|æ/' => 'ae', '/Ã¥/' => 'aa'), true); + $this->assertEqual(Inflector::slug('Testing æ ø Ã¥'), 'Testing_ae_ø_aa'); + + $this->assertEqual(Inflector::slug('Testing æ ø Ã¥', '-', array('/ø/' => 'oe')), 'Testing-ae-oe-aa'); + } + +/** + * test that setting new rules clears the inflector caches. + * + * @return void + */ + function testRulesClearsCaches() { + $this->assertEqual(Inflector::singularize('Bananas'), 'Banana'); + $this->assertEqual(Inflector::tableize('Banana'), 'bananas'); + $this->assertEqual(Inflector::pluralize('Banana'), 'Bananas'); + + Inflector::rules('singular', array( + 'rules' => array('/(.*)nas$/i' => '\1zzz') + )); + $this->assertEqual(Inflector::singularize('Bananas'), 'Banazzz', 'Was inflected with old rules. %s'); + + Inflector::rules('plural', array( + 'rules' => array('/(.*)na$/i' => '\1zzz') + )); + $this->assertEqual(Inflector::pluralize('Banana'), 'Banazzz', 'Was inflected with old rules. %s'); + } + +/** + * Test resetting inflection rules. + * + * @return void + */ + function testCustomRuleWithReset() { + $uninflected = array('atlas', 'lapis', 'onibus', 'pires', 'virus', '.*x'); + $pluralIrregular = array('as' => 'ases'); + + Inflector::rules('singular', array( + 'rules' => array('/^(.*)(a|e|o|u)is$/i' => '\1\2l'), + 'uninflected' => $uninflected, + ), true); + + Inflector::rules('plural', array( + 'rules' => array( + '/^(.*)(a|e|o|u)l$/i' => '\1\2is', + ), + 'uninflected' => $uninflected, + 'irregular' => $pluralIrregular + ), true); + + $this->assertEqual(Inflector::pluralize('Alcool'), 'Alcoois'); + $this->assertEqual(Inflector::pluralize('Atlas'), 'Atlas'); + $this->assertEqual(Inflector::singularize('Alcoois'), 'Alcool'); + $this->assertEqual(Inflector::singularize('Atlas'), 'Atlas'); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/l10n.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/l10n.test.php new file mode 100644 index 000000000..4b8483d4b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/l10n.test.php @@ -0,0 +1,958 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'l10n'); + +/** + * L10nTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class L10nTest extends CakeTestCase { + +/** + * testGet method + * + * @access public + * @return void + */ + function testGet() { + $l10n =& new L10n(); + + // Catalog Entry + $l10n->get('en'); + + $this->assertEqual($l10n->language, 'English'); + $this->assertEqual($l10n->languagePath, array('eng', 'eng')); + $this->assertEqual($l10n->locale, 'eng'); + + // Map Entry + $l10n->get('eng'); + + $this->assertEqual($l10n->language, 'English'); + $this->assertEqual($l10n->languagePath, array('eng', 'eng')); + $this->assertEqual($l10n->locale, 'eng'); + + // Catalog Entry + $l10n->get('en-ca'); + + $this->assertEqual($l10n->language, 'English (Canadian)'); + $this->assertEqual($l10n->languagePath, array('en_ca', 'eng')); + $this->assertEqual($l10n->locale, 'en_ca'); + + // Default Entry + define('DEFAULT_LANGUAGE', 'en-us'); + + $l10n->get('use_default'); + + $this->assertEqual($l10n->language, 'English (United States)'); + $this->assertEqual($l10n->languagePath, array('en_us', 'eng')); + $this->assertEqual($l10n->locale, 'en_us'); + + $l10n->get('es'); + $l10n->get(''); + $this->assertEqual($l10n->lang, 'en-us'); + + + // Using $this->default + $l10n = new L10n(); + + $l10n->get('use_default'); + $this->assertEqual($l10n->language, 'English (United States)'); + $this->assertEqual($l10n->languagePath, array('en_us', 'eng', 'eng')); + $this->assertEqual($l10n->locale, 'en_us'); + } + +/** + * testGetAutoLanguage method + * + * @access public + * @return void + */ + function testGetAutoLanguage() { + $__SERVER = $_SERVER; + $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'inexistent,en-ca'; + + $l10n =& new L10n(); + $l10n->get(); + + $this->assertEqual($l10n->language, 'English (Canadian)'); + $this->assertEqual($l10n->languagePath, array('en_ca', 'eng', 'eng')); + $this->assertEqual($l10n->locale, 'en_ca'); + + $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'es_mx'; + $l10n->get(); + + $this->assertEqual($l10n->language, 'Spanish (Mexican)'); + $this->assertEqual($l10n->languagePath, array('es_mx', 'spa', 'eng')); + $this->assertEqual($l10n->locale, 'es_mx'); + + $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en_xy,en_ca'; + $l10n->get(); + + $this->assertEqual($l10n->language, 'English'); + $this->assertEqual($l10n->languagePath, array('eng', 'eng', 'eng')); + $this->assertEqual($l10n->locale, 'eng'); + + $_SERVER = $__SERVER; + } + +/** + * testMap method + * + * @access public + * @return void + */ + function testMap() { + $l10n =& new L10n(); + + $result = $l10n->map(array('afr', 'af')); + $expected = array('afr' => 'af', 'af' => 'afr'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('alb', 'sq')); + $expected = array('alb' => 'sq', 'sq' => 'alb'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ara', 'ar')); + $expected = array('ara' => 'ar', 'ar' => 'ara'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('hye', 'hy')); + $expected = array('hye' => 'hy', 'hy' => 'hye'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('baq', 'eu')); + $expected = array('baq' => 'eu', 'eu' => 'baq'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('baq', 'eu')); + $expected = array('baq' => 'eu', 'eu' => 'baq'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('bos', 'bs')); + $expected = array('bos' => 'bs', 'bs' => 'bos'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('bul', 'bg')); + $expected = array('bul' => 'bg', 'bg' => 'bul'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('bel', 'be')); + $expected = array('bel' => 'be', 'be' => 'bel'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('cat', 'ca')); + $expected = array('cat' => 'ca', 'ca' => 'cat'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('chi', 'zh')); + $expected = array('chi' => 'zh', 'zh' => 'chi'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('zho', 'zh')); + $expected = array('zho' => 'zh', 'zh' => 'chi'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('hrv', 'hr')); + $expected = array('hrv' => 'hr', 'hr' => 'hrv'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ces', 'cs')); + $expected = array('ces' => 'cs', 'cs' => 'cze'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('cze', 'cs')); + $expected = array('cze' => 'cs', 'cs' => 'cze'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('dan', 'da')); + $expected = array('dan' => 'da', 'da' => 'dan'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('dut', 'nl')); + $expected = array('dut' => 'nl', 'nl' => 'dut'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('nld', 'nl')); + $expected = array('nld' => 'nl', 'nl' => 'dut'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('nld')); + $expected = array('nld' => 'nl'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('eng', 'en')); + $expected = array('eng' => 'en', 'en' => 'eng'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('est', 'et')); + $expected = array('est' => 'et', 'et' => 'est'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('fao', 'fo')); + $expected = array('fao' => 'fo', 'fo' => 'fao'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('fas', 'fa')); + $expected = array('fas' => 'fa', 'fa' => 'fas'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('per', 'fa')); + $expected = array('per' => 'fa', 'fa' => 'fas'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('fin', 'fi')); + $expected = array('fin' => 'fi', 'fi' => 'fin'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('fra', 'fr')); + $expected = array('fra' => 'fr', 'fr' => 'fre'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('fre', 'fr')); + $expected = array('fre' => 'fr', 'fr' => 'fre'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('gla', 'gd')); + $expected = array('gla' => 'gd', 'gd' => 'gla'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('glg', 'gl')); + $expected = array('glg' => 'gl', 'gl' => 'glg'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('deu', 'de')); + $expected = array('deu' => 'de', 'de' => 'deu'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ger', 'de')); + $expected = array('ger' => 'de', 'de' => 'deu'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ell', 'el')); + $expected = array('ell' => 'el', 'el' => 'gre'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('gre', 'el')); + $expected = array('gre' => 'el', 'el' => 'gre'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('heb', 'he')); + $expected = array('heb' => 'he', 'he' => 'heb'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('hin', 'hi')); + $expected = array('hin' => 'hi', 'hi' => 'hin'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('hun', 'hu')); + $expected = array('hun' => 'hu', 'hu' => 'hun'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ice', 'is')); + $expected = array('ice' => 'is', 'is' => 'ice'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('isl', 'is')); + $expected = array('isl' => 'is', 'is' => 'ice'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ind', 'id')); + $expected = array('ind' => 'id', 'id' => 'ind'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('gle', 'ga')); + $expected = array('gle' => 'ga', 'ga' => 'gle'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ita', 'it')); + $expected = array('ita' => 'it', 'it' => 'ita'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('jpn', 'ja')); + $expected = array('jpn' => 'ja', 'ja' => 'jpn'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('kor', 'ko')); + $expected = array('kor' => 'ko', 'ko' => 'kor'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('lav', 'lv')); + $expected = array('lav' => 'lv', 'lv' => 'lav'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('lit', 'lt')); + $expected = array('lit' => 'lt', 'lt' => 'lit'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('mac', 'mk')); + $expected = array('mac' => 'mk', 'mk' => 'mac'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('mkd', 'mk')); + $expected = array('mkd' => 'mk', 'mk' => 'mac'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('may', 'ms')); + $expected = array('may' => 'ms', 'ms' => 'may'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('msa', 'ms')); + $expected = array('msa' => 'ms', 'ms' => 'may'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('mlt', 'mt')); + $expected = array('mlt' => 'mt', 'mt' => 'mlt'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('nor', 'no')); + $expected = array('nor' => 'no', 'no' => 'nor'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('nob', 'nb')); + $expected = array('nob' => 'nb', 'nb' => 'nob'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('nno', 'nn')); + $expected = array('nno' => 'nn', 'nn' => 'nno'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('pol', 'pl')); + $expected = array('pol' => 'pl', 'pl' => 'pol'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('por', 'pt')); + $expected = array('por' => 'pt', 'pt' => 'por'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('roh', 'rm')); + $expected = array('roh' => 'rm', 'rm' => 'roh'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ron', 'ro')); + $expected = array('ron' => 'ro', 'ro' => 'rum'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('rum', 'ro')); + $expected = array('rum' => 'ro', 'ro' => 'rum'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('rus', 'ru')); + $expected = array('rus' => 'ru', 'ru' => 'rus'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('smi', 'sz')); + $expected = array('smi' => 'sz', 'sz' => 'smi'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('scc', 'sr')); + $expected = array('scc' => 'sr', 'sr' => 'scc'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('srp', 'sr')); + $expected = array('srp' => 'sr', 'sr' => 'scc'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('slk', 'sk')); + $expected = array('slk' => 'sk', 'sk' => 'slo'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('slo', 'sk')); + $expected = array('slo' => 'sk', 'sk' => 'slo'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('slv', 'sl')); + $expected = array('slv' => 'sl', 'sl' => 'slv'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('wen', 'sb')); + $expected = array('wen' => 'sb', 'sb' => 'wen'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('spa', 'es')); + $expected = array('spa' => 'es', 'es' => 'spa'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('swe', 'sv')); + $expected = array('swe' => 'sv', 'sv' => 'swe'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('tha', 'th')); + $expected = array('tha' => 'th', 'th' => 'tha'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('tso', 'ts')); + $expected = array('tso' => 'ts', 'ts' => 'tso'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('tsn', 'tn')); + $expected = array('tsn' => 'tn', 'tn' => 'tsn'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('tur', 'tr')); + $expected = array('tur' => 'tr', 'tr' => 'tur'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ukr', 'uk')); + $expected = array('ukr' => 'uk', 'uk' => 'ukr'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('urd', 'ur')); + $expected = array('urd' => 'ur', 'ur' => 'urd'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('ven', 've')); + $expected = array('ven' => 've', 've' => 'ven'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('vie', 'vi')); + $expected = array('vie' => 'vi', 'vi' => 'vie'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('xho', 'xh')); + $expected = array('xho' => 'xh', 'xh' => 'xho'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('cy', 'cym')); + $expected = array('cym' => 'cy', 'cy' => 'cym'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('yid', 'yi')); + $expected = array('yid' => 'yi', 'yi' => 'yid'); + $this->assertEqual($result, $expected); + + $result = $l10n->map(array('zul', 'zu')); + $expected = array('zul' => 'zu', 'zu' => 'zul'); + $this->assertEqual($result, $expected); + } + +/** + * testCatalog method + * + * @access public + * @return void + */ + function testCatalog() { + $l10n =& new L10n(); + + $result = $l10n->catalog(array('af')); + $expected = array( + 'af' => array('language' => 'Afrikaans', 'locale' => 'afr', 'localeFallback' => 'afr', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ar', 'ar-ae', 'ar-bh', 'ar-dz', 'ar-eg', 'ar-iq', 'ar-jo', 'ar-kw', 'ar-lb', 'ar-ly', 'ar-ma', + 'ar-om', 'ar-qa', 'ar-sa', 'ar-sy', 'ar-tn', 'ar-ye')); + $expected = array( + 'ar' => array('language' => 'Arabic', 'locale' => 'ara', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ae' => array('language' => 'Arabic (U.A.E.)', 'locale' => 'ar_ae', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-bh' => array('language' => 'Arabic (Bahrain)', 'locale' => 'ar_bh', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-dz' => array('language' => 'Arabic (Algeria)', 'locale' => 'ar_dz', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-eg' => array('language' => 'Arabic (Egypt)', 'locale' => 'ar_eg', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-iq' => array('language' => 'Arabic (Iraq)', 'locale' => 'ar_iq', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-jo' => array('language' => 'Arabic (Jordan)', 'locale' => 'ar_jo', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-kw' => array('language' => 'Arabic (Kuwait)', 'locale' => 'ar_kw', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ly' => array('language' => 'Arabic (Libya)', 'locale' => 'ar_ly', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ma' => array('language' => 'Arabic (Morocco)', 'locale' => 'ar_ma', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-om' => array('language' => 'Arabic (Oman)', 'locale' => 'ar_om', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-qa' => array('language' => 'Arabic (Qatar)', 'locale' => 'ar_qa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-sa' => array('language' => 'Arabic (Saudi Arabia)', 'locale' => 'ar_sa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-sy' => array('language' => 'Arabic (Syria)', 'locale' => 'ar_sy', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-tn' => array('language' => 'Arabic (Tunisia)', 'locale' => 'ar_tn', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ye' => array('language' => 'Arabic (Yemen)', 'locale' => 'ar_ye', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('be')); + $expected = array( + 'be' => array('language' => 'Byelorussian', 'locale' => 'bel', 'localeFallback' => 'bel', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('bg')); + $expected = array( + 'bg' => array('language' => 'Bulgarian', 'locale' => 'bul', 'localeFallback' => 'bul', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('bs')); + $expected = array( + 'bs' => array('language' => 'Bosnian', 'locale' => 'bos', 'localeFallback' => 'bos', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ca')); + $expected = array( + 'ca' => array('language' => 'Catalan', 'locale' => 'cat', 'localeFallback' => 'cat', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('cs')); + $expected = array( + 'cs' => array('language' => 'Czech', 'locale' => 'cze', 'localeFallback' => 'cze', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('da')); + $expected = array( + 'da' => array('language' => 'Danish', 'locale' => 'dan', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('de', 'de-at', 'de-ch', 'de-de', 'de-li', 'de-lu')); + $expected = array( + 'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('e', 'el')); + $expected = array( + 'e' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'el' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('en', 'en-au', 'en-bz', 'en-ca', 'en-gb', 'en-ie', 'en-jm', 'en-nz', 'en-tt', 'en-us', 'en-za')); + $expected = array( + 'en' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-au' => array('language' => 'English (Australian)', 'locale' => 'en_au', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-bz' => array('language' => 'English (Belize)', 'locale' => 'en_bz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-ca' => array('language' => 'English (Canadian)', 'locale' => 'en_ca', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-gb' => array('language' => 'English (British)', 'locale' => 'en_gb', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-ie' => array('language' => 'English (Ireland)', 'locale' => 'en_ie', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-jm' => array('language' => 'English (Jamaica)', 'locale' => 'en_jm', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-tt' => array('language' => 'English (Trinidad)', 'locale' => 'en_tt', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-us' => array('language' => 'English (United States)', 'locale' => 'en_us', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-za' => array('language' => 'English (South Africa)', 'locale' => 'en_za', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('es', 'es-ar', 'es-bo', 'es-cl', 'es-co', 'es-cr', 'es-do', 'es-ec', 'es-es', 'es-gt', 'es-hn', + 'es-mx', 'es-ni', 'es-pa', 'es-pe', 'es-pr', 'es-py', 'es-sv', 'es-uy', 'es-ve')); + $expected = array( + 'es' => array('language' => 'Spanish (Spain - Traditional)', 'locale' => 'spa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ar' => array('language' => 'Spanish (Argentina)', 'locale' => 'es_ar', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-bo' => array('language' => 'Spanish (Bolivia)', 'locale' => 'es_bo', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-cl' => array('language' => 'Spanish (Chile)', 'locale' => 'es_cl', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-co' => array('language' => 'Spanish (Colombia)', 'locale' => 'es_co', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-cr' => array('language' => 'Spanish (Costa Rica)', 'locale' => 'es_cr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ec' => array('language' => 'Spanish (Ecuador)', 'locale' => 'es_ec', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-es' => array('language' => 'Spanish (Spain)', 'locale' => 'es_es', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-gt' => array('language' => 'Spanish (Guatemala)', 'locale' => 'es_gt', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-hn' => array('language' => 'Spanish (Honduras)', 'locale' => 'es_hn', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-mx' => array('language' => 'Spanish (Mexican)', 'locale' => 'es_mx', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ni' => array('language' => 'Spanish (Nicaragua)', 'locale' => 'es_ni', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pa' => array('language' => 'Spanish (Panama)', 'locale' => 'es_pa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pe' => array('language' => 'Spanish (Peru)', 'locale' => 'es_pe', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pr' => array('language' => 'Spanish (Puerto Rico)', 'locale' => 'es_pr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-py' => array('language' => 'Spanish (Paraguay)', 'locale' => 'es_py', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-sv' => array('language' => 'Spanish (El Salvador)', 'locale' => 'es_sv', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-uy' => array('language' => 'Spanish (Uruguay)', 'locale' => 'es_uy', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ve' => array('language' => 'Spanish (Venezuela)', 'locale' => 'es_ve', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('et')); + $expected = array( + 'et' => array('language' => 'Estonian', 'locale' => 'est', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('eu')); + $expected = array( + 'eu' => array('language' => 'Basque', 'locale' => 'baq', 'localeFallback' => 'baq', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('fa')); + $expected = array( + 'fa' => array('language' => 'Farsi', 'locale' => 'per', 'localeFallback' => 'per', 'charset' => 'utf-8', 'direction' => 'rtl') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('fi')); + $expected = array( + 'fi' => array('language' => 'Finnish', 'locale' => 'fin', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('fo')); + $expected = array( + 'fo' => array('language' => 'Faeroese', 'locale' => 'fao', 'localeFallback' => 'fao', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('fr', 'fr-be', 'fr-ca', 'fr-ch', 'fr-fr', 'fr-lu')); + $expected = array( + 'fr' => array('language' => 'French (Standard)', 'locale' => 'fre', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-be' => array('language' => 'French (Belgium)', 'locale' => 'fr_be', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-ca' => array('language' => 'French (Canadian)', 'locale' => 'fr_ca', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-ch' => array('language' => 'French (Swiss)', 'locale' => 'fr_ch', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-fr' => array('language' => 'French (France)', 'locale' => 'fr_fr', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-lu' => array('language' => 'French (Luxembourg)', 'locale' => 'fr_lu', 'localeFallback' => 'fre', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ga')); + $expected = array( + 'ga' => array('language' => 'Irish', 'locale' => 'gle', 'localeFallback' => 'gle', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('gd', 'gd-ie')); + $expected = array( + 'gd' => array('language' => 'Gaelic (Scots)', 'locale' => 'gla', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'gd-ie' => array('language' => 'Gaelic (Irish)', 'locale' => 'gd_ie', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('gl')); + $expected = array( + 'gl' => array('language' => 'Galician', 'locale' => 'glg', 'localeFallback' => 'glg', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('he')); + $expected = array( + 'he' => array('language' => 'Hebrew', 'locale' => 'heb', 'localeFallback' => 'heb', 'charset' => 'utf-8', 'direction' => 'rtl') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('hi')); + $expected = array( + 'hi' => array('language' => 'Hindi', 'locale' => 'hin', 'localeFallback' => 'hin', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('hr')); + $expected = array( + 'hr' => array('language' => 'Croatian', 'locale' => 'hrv', 'localeFallback' => 'hrv', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('hu')); + $expected = array( + 'hu' => array('language' => 'Hungarian', 'locale' => 'hun', 'localeFallback' => 'hun', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('hy')); + $expected = array( + 'hy' => array('language' => 'Armenian - Armenia', 'locale' => 'hye', 'localeFallback' => 'hye', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('id', 'in')); + $expected = array( + 'id' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'in' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('is')); + $expected = array( + 'is' => array('language' => 'Icelandic', 'locale' => 'ice', 'localeFallback' => 'ice', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('it', 'it-ch')); + $expected = array( + 'it' => array('language' => 'Italian', 'locale' => 'ita', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'it-ch' => array('language' => 'Italian (Swiss) ', 'locale' => 'it_ch', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ja')); + $expected = array( + 'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ko', 'ko-kp', 'ko-kr')); + $expected = array( + 'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), + 'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), + 'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('koi8-r', 'ru', 'ru-mo')); + $expected = array( + 'koi8-r' => array('language' => 'Russian', 'locale' => 'koi8_r', 'localeFallback' => 'rus', 'charset' => 'koi8-r', 'direction' => 'ltr'), + 'ru' => array('language' => 'Russian', 'locale' => 'rus', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ru-mo' => array('language' => 'Russian (Moldavia)', 'locale' => 'ru_mo', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('lt')); + $expected = array( + 'lt' => array('language' => 'Lithuanian', 'locale' => 'lit', 'localeFallback' => 'lit', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('lv')); + $expected = array( + 'lv' => array('language' => 'Latvian', 'locale' => 'lav', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('mk', 'mk-mk')); + $expected = array( + 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mk', 'localeFallback' => 'mac', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mk-mk' => array('language' => 'Macedonian', 'locale' => 'mk_mk', 'localeFallback' => 'mac', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ms')); + $expected = array( + 'ms' => array('language' => 'Malaysian', 'locale' => 'may', 'localeFallback' => 'may', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('mt')); + $expected = array( + 'mt' => array('language' => 'Maltese', 'locale' => 'mlt', 'localeFallback' => 'mlt', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('n', 'nl', 'nl-be')); + $expected = array( + 'n' => array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog('nl'); + $expected = array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr'); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog('nld'); + $expected = array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr'); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog('dut'); + $expected = array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8', 'direction' => 'ltr'); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('nb')); + $expected = array( + 'nb' => array('language' => 'Norwegian Bokmal', 'locale' => 'nob', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('nn', 'no')); + $expected = array( + 'nn' => array('language' => 'Norwegian Nynorsk', 'locale' => 'nno', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'no' => array('language' => 'Norwegian', 'locale' => 'nor', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('p', 'pl')); + $expected = array( + 'p' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('pt', 'pt-br')); + $expected = array( + 'pt' => array('language' => 'Portuguese (Portugal)', 'locale' => 'por', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('rm')); + $expected = array( + 'rm' => array('language' => 'Rhaeto-Romanic', 'locale' => 'roh', 'localeFallback' => 'roh', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ro', 'ro-mo')); + $expected = array( + 'ro' => array('language' => 'Romanian', 'locale' => 'rum', 'localeFallback' => 'rum', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ro-mo' => array('language' => 'Romanian (Moldavia)', 'locale' => 'ro_mo', 'localeFallback' => 'rum', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('sb')); + $expected = array( + 'sb' => array('language' => 'Sorbian', 'locale' => 'wen', 'localeFallback' => 'wen', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('sk')); + $expected = array( + 'sk' => array('language' => 'Slovak', 'locale' => 'slo', 'localeFallback' => 'slo', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('sl')); + $expected = array( + 'sl' => array('language' => 'Slovenian', 'locale' => 'slv', 'localeFallback' => 'slv', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('sq')); + $expected = array( + 'sq' => array('language' => 'Albanian', 'locale' => 'alb', 'localeFallback' => 'alb', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('sr')); + $expected = array( + 'sr' => array('language' => 'Serbian', 'locale' => 'scc', 'localeFallback' => 'scc', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('sv', 'sv-fi')); + $expected = array( + 'sv' => array('language' => 'Swedish', 'locale' => 'swe', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sv-fi' => array('language' => 'Swedish (Finland)', 'locale' => 'sv_fi', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('sx')); + $expected = array( + 'sx' => array('language' => 'Sutu', 'locale' => 'sx', 'localeFallback' => 'sx', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('sz')); + $expected = array( + 'sz' => array('language' => 'Sami (Lappish)', 'locale' => 'smi', 'localeFallback' => 'smi', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('th')); + $expected = array( + 'th' => array('language' => 'Thai', 'locale' => 'tha', 'localeFallback' => 'tha', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('tn')); + $expected = array( + 'tn' => array('language' => 'Tswana', 'locale' => 'tsn', 'localeFallback' => 'tsn', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('tr')); + $expected = array( + 'tr' => array('language' => 'Turkish', 'locale' => 'tur', 'localeFallback' => 'tur', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ts')); + $expected = array( + 'ts' => array('language' => 'Tsonga', 'locale' => 'tso', 'localeFallback' => 'tso', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('uk')); + $expected = array( + 'uk' => array('language' => 'Ukrainian', 'locale' => 'ukr', 'localeFallback' => 'ukr', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ur')); + $expected = array( + 'ur' => array('language' => 'Urdu', 'locale' => 'urd', 'localeFallback' => 'urd', 'charset' => 'utf-8', 'direction' => 'rtl') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('ve')); + $expected = array( + 've' => array('language' => 'Venda', 'locale' => 'ven', 'localeFallback' => 'ven', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('vi')); + $expected = array( + 'vi' => array('language' => 'Vietnamese', 'locale' => 'vie', 'localeFallback' => 'vie', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('cy')); + $expected = array( + 'cy' => array('language' => 'Welsh', 'locale' => 'cym', 'localeFallback' => 'cym', 'charset' => 'utf-8', +'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('xh')); + $expected = array( + 'xh' => array('language' => 'Xhosa', 'locale' => 'xho', 'localeFallback' => 'xho', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('yi')); + $expected = array( + 'yi' => array('language' => 'Yiddish', 'locale' => 'yid', 'localeFallback' => 'yid', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('zh', 'zh-cn', 'zh-hk', 'zh-sg', 'zh-tw')); + $expected = array( + 'zh' => array('language' => 'Chinese', 'locale' => 'chi', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-cn' => array('language' => 'Chinese (PRC)', 'locale' => 'zh_cn', 'localeFallback' => 'chi', 'charset' => 'GB2312', 'direction' => 'ltr'), + 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-sg' => array('language' => 'Chinese (Singapore)', 'locale' => 'zh_sg', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-tw' => array('language' => 'Chinese (Taiwan)', 'locale' => 'zh_tw', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('zu')); + $expected = array( + 'zu' => array('language' => 'Zulu', 'locale' => 'zul', 'localeFallback' => 'zul', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('en-nz', 'es-do', 'sz', 'ar-lb', 'zh-hk', 'pt-br')); + $expected = array( + 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sz' => array('language' => 'Sami (Lappish)', 'locale' => 'smi', 'localeFallback' => 'smi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + + $result = $l10n->catalog(array('eng', 'deu', 'zho', 'rum', 'zul', 'yid')); + $expected = array( + 'eng' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'deu' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zho' => array('language' => 'Chinese', 'locale' => 'chi', 'localeFallback' => 'chi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'rum' => array('language' => 'Romanian', 'locale' => 'rum', 'localeFallback' => 'rum', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zul' => array('language' => 'Zulu', 'locale' => 'zul', 'localeFallback' => 'zul', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'yid' => array('language' => 'Yiddish', 'locale' => 'yid', 'localeFallback' => 'yid', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEqual($result, $expected); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/log/file_log.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/log/file_log.test.php new file mode 100644 index 000000000..d14eee9b9 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/log/file_log.test.php @@ -0,0 +1,79 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.log + * @since CakePHP(tm) v 1.3 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once LIBS . 'log' . DS . 'file_log.php'; + +/** + * CakeLogTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class FileLogTest extends CakeTestCase { + +/** + * testLogFileWriting method + * + * @access public + * @return void + */ + function testLogFileWriting() { + @unlink(LOGS . 'error.log'); + $log =& new FileLog(); + $log->write('warning', 'Test warning'); + $this->assertTrue(file_exists(LOGS . 'error.log')); + + $result = file_get_contents(LOGS . 'error.log'); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning/', $result); + unlink(LOGS . 'error.log'); + + @unlink(LOGS . 'debug.log'); + $log->write('debug', 'Test warning'); + $this->assertTrue(file_exists(LOGS . 'debug.log')); + + $result = file_get_contents(LOGS . 'debug.log'); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: Test warning/', $result); + unlink(LOGS . 'debug.log'); + + @unlink(LOGS . 'random.log'); + $log->write('random', 'Test warning'); + $this->assertTrue(file_exists(LOGS . 'random.log')); + + $result = file_get_contents(LOGS . 'random.log'); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Random: Test warning/', $result); + unlink(LOGS . 'random.log'); + } + +/** + * test using the path setting to write logs in other places. + * + * @return void + */ + function testPathSetting() { + $path = TMP . 'tests' . DS; + @unlink($path . 'error.log'); + + $log =& new FileLog(compact('path')); + $log->write('warning', 'Test warning'); + $this->assertTrue(file_exists($path . 'error.log')); + unlink($path . 'error.log'); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/magic_db.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/magic_db.test.php new file mode 100644 index 000000000..c25ee3c63 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/magic_db.test.php @@ -0,0 +1,203 @@ +Db =& new MagicDb(); + } +/** + * MagicDb::analyze should properly detect the file type and output additional info as requested. + * + * @access public + */ + function testAnalyze() { + $r = $this->Db->read(MagicDbTestData::get('magic.db')); + $this->assertTrue($r === true); + + $r = $this->Db->analyze(array()); + $this->assertTrue($r === false); + + $r = $this->Db->analyze(WWW_ROOT.'img'.DS.'cake.icon.gif'); + // TODO: Check several serialized file samples for accurate detection + } +/** + * MagicDb::read should properly read MagicDb databases from .php-/.db-files and plain data arguments passed in and return false if the file wasn't found or + * if the readed data did not validate. + * + * @access public + */ + function testRead() { + $this->Db->db = array(); + + $r = $this->Db->read(true); + $this->assertTrue($r === false); + $r = $this->Db->read(5); + $this->assertTrue($r === false); + + $this->Db->db = array('a'); + $r = $this->Db->read(array('foo' => 'bar')); + $this->assertTrue($r === false); + $this->assertTrue($this->Db->db === array('a')); + + $magicDb = array('header' => array(), 'database' => array()); + $r = $this->Db->read($magicDb); + $this->assertTrue($r === true); + $this->assertTrue($this->Db->db === $magicDb); + + // @TODO: Test parsing an actual magic.db file + + $r = $this->Db->read('does-not-exist.db'); + $this->assertTrue($r === false); + $this->assertTrue($this->Db->db === $magicDb); + + if (file_exists(VENDORS.'magic.php')) { + $r = $this->Db->read(VENDORS.'magic.php'); + $this->assertTrue($r === true); + $this->assertTrue($this->Db->db === array('header' => array(), 'database' => array())); + } + + $r = $this->Db->read(MagicDbTestData::get('magic.snippet.db')); + $this->assertTrue($r === true); + } +/** + * MagicDb::toArray should either return the MagicDb::db property, or the parsed array data if a magic.db dump is passed in as the first argument + * + * @access public + */ + function testToArray() { + $this->Db->db = array(); + + $r = $this->Db->toArray(); + $this->assertTrue($r === array()); + $this->Db->db = array('foo' => 'bar'); + $r = $this->Db->toArray(); + $this->assertTrue($r === array('foo' => 'bar')); + + $r = $this->Db->toArray(array('yeah')); + $this->assertTrue($r === array('yeah')); + + $r = $this->Db->toArray("# FILE_ID DB\r\n# Date:2009-10-10\r\n# Source:xxx.php"); + $this->assertTrue($r === array()); + + $r = $this->Db->toArray('foo'); + $this->assertTrue($r === array()); + + $r = $this->Db->toArray(MagicDbTestData::get('magic.snippet.db')); + $this->assertTrue($r === MagicDbTestData::get('magic.snippet.db.result')); + } +/** + * The MagicDb::validates function should return if the array passed to it or the local db property contains a valid MagicDb record set + * + * @access public + */ + function testValidates() { + $r = $this->Db->validates(array()); + $this->assertTrue($r === false); + + $r = $this->Db->validates(array('header' => true, 'database' => true)); + $this->assertTrue($r === false); + $magicDb = array('header' => array(), 'database' => array()); + $r = $this->Db->validates($magicDb); + $this->assertTrue($r === true); + + $this->Db->db = array(); + $r = $this->Db->validates(); + $this->assertTrue($r === false); + + $this->Db->db = $magicDb; + $r = $this->Db->validates(); + $this->assertTrue($r === true); + } +} +/** + * Test data holding object for MagicDb tests + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +/** + * MagicDbTestData class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class MagicDbTestData extends Object { +/** + * Base64 encoded data + * + * @var array + * @access public + */ + var $data = array( + 'magic.snippet.db' => 'IyBGSUxFX0lEIERCDQojIERhdGU6MjAwNS0wMy0yOQ0KIyBTb3VyY2U6aHR0cDovL3d3dy5tYWdpY2RiLm9yZw0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBoZWxwIGZpbGUNCiY5CWJ5dGUJMHgwMgkNCj4xMCBieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBhcHBsaWNhdGlvbiByZXNvdXJjZSBsaWJyYXJ5DQomOQlieXRlCTUxCQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwMDAxO2V4dD07bWltZT07XVdvcmRwZXJmZWN0IGJsb2NrIGZpbGUNCiY5CWJ5dGUJMTMJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQ=', + 'magic.snippet.db.result' => 'YToyOntzOjY6ImhlYWRlciI7YToyOntzOjQ6IkRhdGUiO3M6MTA6IjIwMDUtMDMtMjkiO3M6NjoiU291cmNlIjtzOjIyOiJodHRwOi8vd3d3Lm1hZ2ljZGIub3JnIjt9czo4OiJkYXRhYmFzZSI7YToyOntpOjA7YTo0OntpOjA7YTo0OntpOjA7czoxOiIwIjtpOjE7czo2OiJzdHJpbmciO2k6MjtzOjg6IlxceEZGV1BDIjtpOjM7czo1OToiW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBoZWxwIGZpbGUiO31pOjE7YTo0OntpOjA7czoyOiImOSI7aToxO3M6NDoiYnl0ZSI7aToyO3M6NDoiMHgwMiI7aTozO3M6MDoiIjt9aToyO2E6Mzp7aTowO3M6ODoiPjEwIGJ5dGUiO2k6MTtzOjE6IngiO2k6MjtzOjEyOiIsIHZlcnNpb24gJWQiO31pOjM7YTo0OntpOjA7czozOiI+MTEiO2k6MTtzOjQ6ImJ5dGUiO2k6MjtzOjE6IngiO2k6MztzOjM6Ii4lZCI7fX1pOjE7YTo0OntpOjA7YTo0OntpOjA7czoxOiIwIjtpOjE7czo2OiJzdHJpbmciO2k6MjtzOjg6IlxceEZGV1BDIjtpOjM7czo3ODoiW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBhcHBsaWNhdGlvbiByZXNvdXJjZSBsaWJyYXJ5Ijt9aToxO2E6NDp7aTowO3M6MjoiJjkiO2k6MTtzOjQ6ImJ5dGUiO2k6MjtzOjI6IjUxIjtpOjM7czowOiIiO31pOjI7YTo0OntpOjA7czozOiI+MTAiO2k6MTtzOjQ6ImJ5dGUiO2k6MjtzOjE6IngiO2k6MztzOjEyOiIsIHZlcnNpb24gJWQiO31pOjM7YTo0OntpOjA7czozOiI+MTEiO2k6MTtzOjQ6ImJ5dGUiO2k6MjtzOjE6IngiO2k6MztzOjM6Ii4lZCI7fX19fQ==', + 'magic.db' => 'IyBGSUxFX0lEIERCDQojIERhdGU6MjAwNS0wMy0yOQ0KIyBTb3VyY2U6aHR0cDovL3d3dy5tYWdpY2RiLm9yZw0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBoZWxwIGZpbGUNCiY5CWJ5dGUJMHgwMgkNCj4xMCBieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBhcHBsaWNhdGlvbiByZXNvdXJjZSBsaWJyYXJ5DQomOQlieXRlCTUxCQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwMDAxO2V4dD07bWltZT07XVdvcmRwZXJmZWN0IGJsb2NrIGZpbGUNCiY5CWJ5dGUJMTMJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDAwMDE7ZXh0PTttaW1lPTtdV29yZHBlcmZlY3QgY29sdW1uIGJsb2NrDQomOQlieXRlCTE1CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwMDAxO2V4dD07bWltZT07XVdvcmRwZXJmZWN0IGRpY3Rpb25hcnkgZmlsZQ0KJjkJYnl0ZQkweDBCCQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwMDAxO2V4dD07bWltZT07XVdvcmRwZXJmZWN0IGRpY3Rpb25hcnkgcnVsZXMgZmlsZQ0KJjkJYnl0ZQkzNAkNCj4xMAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBleHRlcm5hbCBzcGVsbCBjb2RlIG1vZHVsZSBmaWxlDQomOQlieXRlCTQ2CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwMDAxO2V4dD07bWltZT07XVdvcmRwZXJmZWN0IGV4dGVybmFsIHNwZWxsIGRpY3Rpb25hcnkgZmlsZQ0KJjkJYnl0ZQk0NwkNCj4xMAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBHcmFwaGljcyBzY3JlZW4gZHJpdmVyIGZpbGUNCiY5CWJ5dGUJMjYJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDAwMDE7ZXh0PTttaW1lPTtdV29yZHBlcmZlY3QgaHlwaGVuYXRpb24gY29kZSBtb2R1bGUgZmlsZQ0KJjkJYnl0ZQkyMwkNCj4xMAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBoeXBoZW5hdGlvbiBkYXRhIG1vZHVsZSBmaWxlDQomOQlieXRlCTI0CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwMDAxO2V4dD07bWltZT07XVdvcmRwZXJmZWN0IGh5cGhlbmF0aW9uIGxleCBtb2R1bGUNCiY5CWJ5dGUJMjcJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDAwMDE7ZXh0PTttaW1lPTtdV29yZHBlcmZlY3QgaW5zdGFsbGF0aW9uIGluZm9ybWF0aW9uIGZpbGUNCiY5CWJ5dGUJNDEJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDAwMDE7ZXh0PTttaW1lPTtdV29yZHBlcmZlY3Qga2V5Ym9hcmQgZGVmaW5pdGlvbiBmaWxlDQomOQlieXRlCTB4MDMJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDAwMDE7ZXh0PTttaW1lPTtdV29yZHBlcmZlY3QgbWFjcm8gZGF0YSBmaWxlDQomOQlieXRlCTB4MDEJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDAwMDE7ZXh0PTttaW1lPTtdV29yZHBlcmZlY3QgbWFjcm8gcmVzb3VyY2UgZmlsZQ0KJjkJYnl0ZQkyNQkNCj4xMAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCBwcmludGVyIFEgY29kZXMgKHVzZWQgYnkgVkFYL0RHKQ0KJjkJYnl0ZQkyOAkNCj4xMAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMDAwMTtleHQ9O21pbWU9O11Xb3JkcGVyZmVjdCByZWN0YW5ndWxhciBibG9jayBmaWxlDQomOQlieXRlCTE0CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwMDAxO2V4dD07bWltZT07XVdvcmRwZXJmZWN0IHNwZWxsIGNvZGUgbW9kdWxlIHJ1bGVzIGZpbGUNCiY5CWJ5dGUJMzMJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDAwMDE7ZXh0PTttaW1lPTtdV29yZHBlcmZlY3Qgc3BlbGwgY29kZSBtb2R1bGUgd29yZCBsaXN0DQomOQlieXRlCTI5CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwMDAxO2V4dD07bWltZT07XVdvcmRwZXJmZWN0IHRoZXNhcnVzIGZpbGUNCiY5CWJ5dGUJMTIJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDAwMDE7ZXh0PTttaW1lPTtdV29yZHBlcmZlY3QgVkFYIGtleWJvYXJkIGRlZmluaXRpb24gZmlsZQ0KJjkJYnl0ZQkweDA0CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwQUxMO2V4dD1hbGw7bWltZT07XVdvcmRwZXJmZWN0IHByaW50ZXIgcmVzb3VyY2UgZmlsZQ0KJjkJYnl0ZQkxOQkNCj4xMAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCVJJRkYJW2ZpZD0wMDAwMDEwMDEtMDAtMDAwMENPTjtleHQ9Y29uO21pbWU9O11NaWNyb3NvZnQgQW5pbWF0ZWQgY3Vyc29yLCBsaXR0bGUtZW5kaWFuDQomOAlzdHJpbmcJQUNPTgkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlSSUZYCVtmaWQ9MDAwMDAxMDAxLTAwLTAwMDBDT047ZXh0PWNvbjttaW1lPTtdTWljcm9zb2Z0IEFuaW1hdGVkIGN1cnNvciwgYmlnLWVuZGlhbg0KJjgJc3RyaW5nCUFDT04JDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwRE9DO2V4dD1kb2M7bWltZT07XU1hY2ludG9zaCBXb3JkcGVyZmVjdCBkb2N1bWVudCBmaWxlDQomOQlieXRlCTQ0CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwRE9DO2V4dD1kb2M7bWltZT07XVZBWCBXb3JkcGVyZmVjdCBkb2N1bWVudCBmaWxlDQomOQlieXRlCTQ1CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwRFJTO2V4dD1kcnM7bWltZT07XVdvcmRwZXJmZWN0IGRpc3BsYXkgcmVzb3VyY2UgZmlsZQ0KJjkJYnl0ZQkyMAkNCj4xMAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTEJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBXb3JkcGVyZmVjdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZXUEMJW2ZpZD0wMDAwMDEwMDgtMDAtMDAwMEZJTDtleHQ9ZmlsO21pbWU9O11Xb3JkcGVyZmVjdCBvdmVybGF5IGZpbGUNCiY5CWJ5dGUJMjEJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlQTUNDCVtmaWQ9MDAwMDAxMDAxLTAwLTAwMDBHUlA7ZXh0PWdycDttaW1lPTtdTWljcm9zb2Z0IHdpbmRvd3MgZ3JvdXAgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJYmVzaG9ydAkweEUzMTAJW2ZpZD0wMDAwMDEwMDctMDAtMDAwSU5GTztleHQ9aW5mbzttaW1lPTtdQW1pZ2Egc2hvcnRjdXQgLyBpY29uIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDBJTlM7ZXh0PWluczttaW1lPTtdV29yZHBlcmZlY3QgaW5zdGFsbGF0aW9uIGluZm9ybWF0aW9uIGZpbGUNCiY5CWJ5dGUJNDMJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCWxlbG9uZwkweDAwMDAwMDRDCVtmaWQ9MDAwMDAxMDAxLTAwLTAwMDBMTks7ZXh0PWxuazttaW1lPTtdTWljcm9zb2Z0IFdpbmRvd3Mgc2hvcnRjdXQgZmlsZQ0KJjQJc3RyaW5nCVxceDAxXFx4MTRcXHgwMgkNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTAwLTAwMDBQUlM7ZXh0PXByczttaW1lPTtdV29yZHBlcmZlY3QgcHJpbnRlciByZXNvdXJjZSBmaWxlDQomOQlieXRlCTE2CQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwUVJTO2V4dD1xcnM7bWltZT07XVdvcmRwZXJmZWN0IDUuMSBlcXVhdGlvbiByZXNvdXJjZSBmaWxlDQomOQlieXRlCTMwCQ0KPjEwCWJ5dGUJeAksIHZlcnNpb24gJWQNCj4xMQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlcXHhGRldQQwlbZmlkPTAwMDAwMTAwOC0wMC0wMDAwU0VUO2V4dD1zZXQ7bWltZT07XVdvcmRwZXJmZWN0IHNldHVwIGRhdGENCiY5CWJ5dGUJMTcJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlSSUZGCVtmaWQ9MDAwMDAxMDAxLTBFLTAwMDBQQUw7ZXh0PXBhbCxyaWZmO21pbWU9O11NaWNyb3NvZnQgUGFsZXR0ZSwgbGl0dGxlLWVuZGlhbg0KJjgJc3RyaW5nCVBBTAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlSSUZYCVtmaWQ9MDAwMDAxMDAxLTBFLTAwMDBQQUw7ZXh0PXBhbCxyaWZ4O21pbWU9O11NaWNyb3NvZnQgUGFsZXR0ZSwgYmlnLWVuZGlhbg0KJjgJc3RyaW5nCVBBTAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQowCXN0cmluZwlET1MJW2ZpZD0wMDAwMDEwMDctMEYtMDAwMEFERjtleHQ9YWRmO21pbWU9O11BbWlnYU9TIEZpbGUgc3lzdGVtDQomMwlieXRlJjB4ZjgJMAkNCj4zCWJ5dGUmMQkwCSwgT0ZTDQo+MwlieXRlJjEJMQksIEZGUw0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTExIGJ5IENhcmwNCjAJYmVsb25nCTB4MDNGMwlbZmlkPTAwMDAwMTAwNy0xMC1MSUJSQVJZO2V4dD0sbGlicmFyeTttaW1lPTtdQW1pZ2EgQ2xhc3NpYyBleGVjdXRhYmxlIGZpbGUgKDY4MHgwIGZhbWlseSkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlcXHg3ZkVMRglbZmlkPTAwMDAwMDAwMy0xMC0wMDAwMDBPO2V4dD0sbyxzbyxvdXQ7bWltZT07XUV4ZWN1dGFibGUgbGlua2FibGUgZmlsZSAoRUxGKQ0KJjQJYnl0ZQk9MQksIDMyLWJpdA0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCVxceDdmRUxGCVtmaWQ9MDAwMDAwMDAzLTEwLTAwMDAwME87ZXh0PSxvLHNvLG91dDttaW1lPTtdRXhlY3V0YWJsZSBsaW5rYWJsZSBmaWxlIChFTEYpDQomNAlieXRlCT0yCSwgNjQtYml0DQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTQgYnkgQ2FybA0KMAlzdHJpbmcJTVoJW2ZpZD0wMDAwMDEwMDEtMTAtMDAwMEVYRTtleHQ9ZXhlLGRsbDttaW1lPTtdTmV3IGV4ZWN1dGFibGUgZmlsZQ0KJjB4MTgJbGVzaG9ydAk+MHgzRgkNCiYoNjAubCkJc3RyaW5nCU5FCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTE0IGJ5IENhcmwNCjAJc3RyaW5nCVpNCVtmaWQ9MDAwMDAxMDAxLTEwLTAwMDBFWEU7ZXh0PWV4ZSxkbGw7bWltZT07XU5ldyBleGVjdXRhYmxlIGZpbGUNCiYweDE4CWxlc2hvcnQJPjB4M0YJDQomKDYwLmwpCXN0cmluZwlORQkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xNCBieSBDYXJsDQowCXN0cmluZwlNWglbZmlkPTAwMDAwMTAwMS0xMC0wMDAwRVhFO2V4dD1leGUsZGxsO21pbWU9O11NaWNyb3NvZnQgV2luZG93cyAzLnggTmV3IEV4ZWN1dGFibGUgZmlsZQ0KJjB4MTgJbGVzaG9ydAk+MHgzRgkNCiYoNjAubCkJc3RyaW5nCU5FCQ0KJig2MC5sKzU0KQlieXRlCTB4MDIJDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTQgYnkgQ2FybA0KMAlzdHJpbmcJWk0JW2ZpZD0wMDAwMDEwMDEtMTAtMDAwMEVYRTtleHQ9ZXhlLGRsbDttaW1lPTtdTWljcm9zb2Z0IFdpbmRvd3MgMy54IE5ldyBFeGVjdXRhYmxlIGZpbGUNCiYweDE4CWxlc2hvcnQJPjB4M0YJDQomKDYwLmwpCXN0cmluZwlORQkNCiYoNjAubCs1NCkJYnl0ZQkweDAyCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTE0IGJ5IENhcmwNCjAJc3RyaW5nCU1aCVtmaWQ9MDAwMDAxMDA5LTEwLTAwMDBFWEU7ZXh0PWV4ZSxkbGw7bWltZT07XUlCTSBPUy8yIE5ldyBFeGVjdXRhYmxlIGZpbGUNCiYweDE4CWxlc2hvcnQJPjB4M0YJDQomKDYwLmwpCXN0cmluZwlORQkNCiYoNjAubCs1NCkJYnl0ZQkweDAxCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTE0IGJ5IENhcmwNCjAJc3RyaW5nCVpNCVtmaWQ9MDAwMDAxMDA5LTEwLTAwMDBFWEU7ZXh0PWV4ZSxkbGw7bWltZT07XUlCTSBPUy8yIE5ldyBFeGVjdXRhYmxlIGZpbGUNCiYweDE4CWxlc2hvcnQJPjB4M0YJDQomKDYwLmwpCXN0cmluZwlORQkNCiYoNjAubCs1NCkJYnl0ZQkweDAxCQ0KDQojIE1hZ2ljIElEIGZvciBNaWNyb3NvZnQgV2luZG93cyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTMgYnkgQ2FybA0KMAlzdHJpbmcJTVoJW2ZpZD0wMDAwMDEwMDEtMTAtMDAwMEVYRTtleHQ9ZXhlLGRsbDttaW1lPTtdTWljcm9zb2Z0IFdpbmRvd3MgTlQgUG9ydGFibGUgRXhlY3V0YWJsZSBmaWxlDQomMHgxOAlsZXNob3J0CTB4NDAJDQomKDYwLmwpCXN0cmluZwlQRVxceDAwXFx4MDAJDQoNCiMgTWFnaWMgSUQgZm9yIE1pY3Jvc29mdCBXaW5kb3dzIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMyBieSBDYXJsDQowCXN0cmluZwlaTQlbZmlkPTAwMDAwMTAwMS0xMC0wMDAwRVhFO2V4dD1leGUsZGxsO21pbWU9O11NaWNyb3NvZnQgV2luZG93cyBOVCBQb3J0YWJsZSBFeGVjdXRhYmxlIGZpbGUNCiYweDE4CWxlc2hvcnQJMHg0MAkNCiYoNjAubCkJc3RyaW5nCVBFXFx4MDBcXHgwMAkNCg0KIyBNYWdpYyBJRCBmb3IgTWljcm9zb2Z0IFdpbmRvd3MsRE9TNEdXIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xNCBieSBDYXJsDQowCXN0cmluZwlNWglbZmlkPTAwMDAwMTAwMS0xMC0wMDAwRVhFO2V4dD1leGUsZGxsLGRydjttaW1lPTtdTWljcm9zb2Z0IFdpbmRvd3MgTGluZWFyIGV4ZWN1dGFibGUNCiYweDE4CWxlc2hvcnQJPjB4M0YJDQomKDYwLmwpCXN0cmluZwlMRQkNCg0KIyBNYWdpYyBJRCBmb3IgTWljcm9zb2Z0IFdpbmRvd3MsRE9TNEdXIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xNCBieSBDYXJsDQowCXN0cmluZwlaTQlbZmlkPTAwMDAwMTAwMS0xMC0wMDAwRVhFO2V4dD1leGUsZGxsLGRydjttaW1lPTtdTWljcm9zb2Z0IFdpbmRvd3MgTGluZWFyIGV4ZWN1dGFibGUNCiYweDE4CWxlc2hvcnQJPjB4M0YJDQomKDYwLmwpCXN0cmluZwlMRQkNCg0KIyBNYWdpYyBJRCBmb3IgT1MvMixET1M0R1cgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTE0IGJ5IENhcmwNCjAJc3RyaW5nCU1aCVtmaWQ9MDAwMDAxMDA5LTEwLTAwMDBFWEU7ZXh0PWV4ZSxkbGwsZHJ2O21pbWU9O11PUy8yIExpbmVhciBleGVjdXRhYmxlDQomMHgxOAlsZXNob3J0CT4weDNGCQ0KJig2MC5sKQlzdHJpbmcJTFgJDQoNCiMgTWFnaWMgSUQgZm9yIE9TLzIsRE9TNEdXIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xNCBieSBDYXJsDQowCXN0cmluZwlaTQlbZmlkPTAwMDAwMTAwOS0xMC0wMDAwRVhFO2V4dD1leGUsZGxsLGRydjttaW1lPTtdT1MvMiBMaW5lYXIgZXhlY3V0YWJsZQ0KJjB4MTgJbGVzaG9ydAk+MHgzRgkNCiYoNjAubCkJc3RyaW5nCUxYCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA3LTMwIGJ5IENhcmwNCjAJc3RyaW5nCU1TRlQJW2ZpZD0wMDAwMDEwMDEtMTAtMDAwMFRMQjtleHQ9dGxiO21pbWU9O11NaWNyb3NvZnQgY29tcG9uZW50IHR5cGUgbGlicmFyeQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTExIGJ5IENhcmwNCjAJYmVzaG9ydAkweDYwMUEJW2ZpZD0wMDAwMDEwMDYtMTAtMDAwMFRUUDtleHQ9dHRwLGdlbSxwcmc7bWltZT07XUF0YXJpIE1pTlQgZXhlY3V0YWJsZS9vYmplY3QgZmlsZQ0KJjB4MTIJc3RyaW5nCU1pTlQJDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMTEgYnkgQ2FybA0KMAliZXNob3J0CTB4NjAxQQlbZmlkPTAwMDAwMTAwNi0xMC0wMDAwVFRQO2V4dD10dHAsZ2VtLHByZzttaW1lPTtdQXRhcmkgVE9TIGV4ZWN1dGFibGUvb2JqZWN0IGZpbGUNCiYweDEyCWJlbG9uZwkweDAwMDAJDQoNCiMgTWFnaWMgSUQgZm9yIFZpcnR1YWwgUGFzY2FsIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNy0zMCBieSBDYXJsDQowCXN0cmluZwlWUEkJW2ZpZD0wMDAwMDAwMDAtMTAtMDAwMFZQSTtleHQ9dnBpO21pbWU9O11WaXJ0dWFsIHBhc2NhbCB1bml0IGZpbGUNCiYweDAzCWJ5dGUJPjQ3CQ0KJjB4MDMJYnl0ZQk8NTgJDQoNCiMgTWFnaWMgSUQgZm9yIEphdmEgY29tcGlsZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTExIGJ5IENhcmwNCjAJYmVsb25nCTB4Q0FGRUJBQkUJW2ZpZD0wMDAwMDEwMTEtMTEtMDBDTEFTUztleHQ9Y2xhc3M7bWltZT07XUphdmFsIHZpcnR1YWwgbWFjaGluZSBjbGFzcyBmaWxlDQo+NgliZXNob3J0CXgJLCB2ZXJzaW9uICVkDQo+NAliZXNob3J0CXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIEJvcmxhbmQgRGVscGhpIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNy0zMCBieSBDYXJsDQowCXN0cmluZwlQS0cJW2ZpZD0wMDAwMDEwMDUtMTEtMDAwMERDUDtleHQ9ZGNwO21pbWU9O11Cb3JsYW5kIERlbHBoaSBjb21waWxlZCBwYWNrYWdlIGNvZGUgZmlsZQ0KJjB4MDMJYnl0ZQk+NDcJDQomMHgwMwlieXRlCTw1OAkNCg0KIyBNYWdpYyBJRCBmb3IgQm9ybGFuZCBEZWxwaGkgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA3LTMwIGJ5IENhcmwNCjAJc3RyaW5nCVxceERGXFx4MDBcXHgwMFxceDBGCVtmaWQ9MDAwMDAxMDA1LTExLTAwMDBEQ1U7ZXh0PWRjdTttaW1lPTtdQm9ybGFuZCBEZWxwaGkgY29kZSB1bml0IGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgVHVyYm8gUGFzY2FsIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNy0yOSBieSBDYXJsDQowCXN0cmluZwlUUFU5CVtmaWQ9MDAwMDAxMDA1LTExLTAwMDBUUFU7ZXh0PXRwdTttaW1lPTtdVHVyYm8gUGFzY2FsIDYuMCBjb2RlIHVuaXQgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBUdXJibyBQYXNjYWwsIEJvcmxhbmQgUGFzY2FsIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNy0yOSBieSBDYXJsDQowCXN0cmluZwlUUFVRCVtmaWQ9MDAwMDAxMDA1LTExLTAwMDBUUFU7ZXh0PXRwdSx0cHAsdHB3O21pbWU9O11Cb3JsYW5kIFBhc2NhbCA3LjAgY29kZSB1bml0IGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQoyCXN0cmluZwlBRExJQi0JW2ZpZD0wMDAwMDEwMTYtMjAtMDAwMEJOSztleHQ9Ym5rO21pbWU9O11BZGxpYiBGTSBpbnN0cnVtZW50IGJhbmsgZmlsZQ0KPjAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjEJYnl0ZQl4CS4lZA0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE2IGJ5IENhcmwNCjAJc3RyaW5nCUlCS1xceDFBCVtmaWQ9MDAwMDAxMDEzLTIwLTAwMDBJQks7ZXh0PWliazttaW1lPTtdQ3JlYXRpdmUgTGFicyBGTSBpbnN0cnVtZW50IGJhbmsgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBNaWNyb3NvZnQgSW5zdHJ1bWVudCBEZWZpbml0aW9uIGZpbGUgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA0IGJ5IENhcmwNCjAJc3RyaW5nCVJJRkYJW2ZpZD0wMDAwMDEwMDEtMjAtMDAwMElERjtleHQ9aWRmO21pbWU9O11NaWNyb3NvZnQgaW5zdHJ1bWVudCBkZWZpbml0aW9uIGZpbGUsIGxpdHRsZS1lbmRpYW4NCiY4CXN0cmluZwlJREZcXCAJDQoNCiMgTWFnaWMgSUQgZm9yIE1pY3Jvc29mdCBJbnN0cnVtZW50IERlZmluaXRpb24gZmlsZSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJUklGWAlbZmlkPTAwMDAwMTAwMS0yMC0wMDAwSURGO2V4dD1pZGY7bWltZT07XU1pY3Jvc29mdCBpbnN0cnVtZW50IGRlZmluaXRpb24gZmlsZSwgYmlnLWVuZGlhbg0KJjgJc3RyaW5nCUlERlxcIAkNCg0KIyBNYWdpYyBJRCBmb3IgRGlnaXRyYWtrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA0IGJ5IENhcmwNCjAJc3RyaW5nCURJU1QJW2ZpZD0wMDAxMDAwODgtMjAtMDAwMElTVDtleHQ9aXN0O21pbWU9O11EaWdpdHJha2tlciBJbnN0cnVtZW50IGZpbGUNCiY0CWJ5dGUJPDIJDQoNCiMgTWFnaWMgSUQgZm9yIEltcHVsc2UgdHJhY2tlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJSU1QSQlbZmlkPTAwMDEwMDAzMi0yMC0wMDAwSVRJO2V4dD1pdGk7bWltZT07XUltcHVsc2UgdHJhY2tlciBpbnN0cnVtZW50IGZpbGUNCj4weDIwCXN0cmluZwl4CVt0aXRsZT0lLjI2c10NCg0KIyBNYWdpYyBJRCBmb3IgTWFkdHJhY2tlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDUgYnkgQ2FybA0KMAlzdHJpbmcJTUkyMQlbZmlkPTAwMDEwMDA5MS0yMC0wMDAwTVRJO2V4dD1tdGk7bWltZT07XU1hZHRyYWNrZXIgaW5zdHJ1bWVudCBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEdyYXZpcyBVbHRyYXNvdW5kIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlHRjFQQVRDSDEwMFxcMElEIzAwMDAwMlxcMAlbZmlkPTAwMDAwMTAxOC0yMC0wMDAwUEFUO2V4dD1wYXQ7bWltZT07XUdyYXZpcyBVbHRyYXNvdW5kIFBhdGNoIChvbGQgaW5zdHJ1bWVudCBkYXRhKQ0KDQojIE1hZ2ljIElEIGZvciBHcmF2aXMgVWx0cmFzb3VuZCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMAlzdHJpbmcJR0YxUEFUQ0gxMTBcXDBJRCMwMDAwMDJcXDAJW2ZpZD0wMDAwMDEwMTgtMjAtMDAwMFBBVDtleHQ9cGF0O21pbWU9O11HcmF2aXMgVWx0cmFzb3VuZCBQYXRjaCAoaW5zdHJ1bWVudCBkYXRhKQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJc3RyaW5nCVNCSVxceDFBCVtmaWQ9MDAwMDAxMDEzLTIwLTAwMDBTQkk7ZXh0PXNiaTttaW1lPTtdQ3JlYXRpdmUgTGFicyBGTSBpbnN0cnVtZW50IGRhdGEgZmlsZQ0KPjQJc3RyaW5nCT4wCVt0aXRsZT0lLjMyc10NCg0KIyBNYWdpYyBJRCBmb3IgU2lkcGxheSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDUgYnkgQ2FybA0KMAlzdHJpbmcJU0lEUExBWVxcIElORk9GSUxFCVtmaWQ9MDAwMDAwMDAwLTIwLTAwMDBTSUQ7ZXh0PXNpZDttaW1lPTtdU0lEUGxheWVyIG11c2ljIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgRmFzdHRyYWNrZXIgMi4wIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlFeHRlbmRlZFxcIEluc3RydW1lbnQ6XFwgCVtmaWQ9MDAwMTAwMDI2LTIwLTAwMDAwWEk7ZXh0PXhpO21pbWU9O11GYXN0VHJhY2tlciBJSSBpbnN0cnVtZW50IGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlSSUZGCVtmaWQ9MDAwMDAxMDAxLTIxLTAwMDAwMDA7ZXh0PTttaW1lPTtdTUlESSBtdXNpYyBmaWxlLCBsaXR0bGUtZW5kaWFuDQomOAlzdHJpbmcJUk1JRAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlSSUZYCVtmaWQ9MDAwMDAxMDAxLTIxLTAwMDAwMDA7ZXh0PTttaW1lPTtdTUlESSBtdXNpYyBmaWxlLCBiaWctZW5kaWFuDQomOAlzdHJpbmcJUk1JRAkNCg0KIyBNYWdpYyBJRCBmb3IgQWJ5c3NcJ3MgaGlnaGVzdCBleHBlcmllbmNlIChBSFgpIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlUSFgJW2ZpZD0wMDAxMDAwMjktMjEtMDAwMEFIWDtleHQ9YWh4O21pbWU9O11BSFggbW9kdWxlIG11c2ljIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQW11c2ljIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQoxMDYyCXN0cmluZwlcXHgzY1xceDZmXFx4ZWZcXHg1MVxceDU1XFx4RUVcXHg1MlxceDZGXFx4NTIJW2ZpZD0wMDAxMDAwMzQtMjEtMDAwMEFNRDtleHQ9YW1kO21pbWU9O11BbXVzaWMgQWRsaWIgdHJhY2tlciBtdXNpYyBmaWxlDQo+MAlzdHJpbmcJPlxceDAwCVt0aXRsZT0lLjIzc10NCj4yNAlzdHJpbmcJPlxcMAlbY3JlYXRvcj0lLjI0c10NCg0KIyBNYWdpYyBJRCBmb3IgVmVsdmV0IFN0dWRpbyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMAlzdHJpbmcJQU1TaGRyXFx4MWEJW2ZpZD0wMDAwMDEyNzYtMjEtMDAwMEFNUztleHQ9YW1zO21pbWU9O11WZWx2ZXQgU3R1ZGlvIG1vZHVsZSBtdXNpYyBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEF1ZGlvIHZpc3VhbCByZXNlYXJjaCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJMkJJVAlbZmlkPTAwMDAwMTMwMS0yMS0wMDAwQVZSO2V4dD1hdnI7bWltZT07XUF1ZGlvIHZpc3VhbCByZXNlYXJjaCBhdWRpbyBmaWxlDQo+NAlzdHJpbmcJeAlbdGl0bGU9JS44c10NCg0KIyBNYWdpYyBJRCBmb3IgU291bmRtb24gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjI2CXN0cmluZwlWLjIJW2ZpZD0wMDAxMDAwMjgtMjEtMDAwMDBCUDtleHQ9YnA7bWltZT07XVNvdW5kbW9uIG1vZHVsZSBtdXNpYyBmaWxlLCB2ZXJzaW9uIDIueA0KPjAJc3RyaW5nCT5cXDAJW3RpdGxlPSUuMjZzXQ0KDQojIE1hZ2ljIElEIGZvciBTb3VuZG1vbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMjYJc3RyaW5nCVYuMwlbZmlkPTAwMDEwMDAyOC0yMS0wMDAwQlAzO2V4dD1icDM7bWltZT07XVNvdW5kbW9uIG1vZHVsZSBtdXNpYyBmaWxlLCB2ZXJzaW9uIDMueA0KPjAJc3RyaW5nCT5cXDAJW3RpdGxlPSUuMjZzXQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJc3RyaW5nCUNUTUYJW2ZpZD0wMDAwMDEwMTMtMjEtMDAwMENNRjtleHQ9Y21mO21pbWU9O11DcmVhdGl2ZSBMYWJzIG11c2ljIGZpbGUNCj40CWJ5dGUJeAksIHZlcnNpb24gJWQNCj41CWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgRGlnaWJvb3N0ZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA0IGJ5IENhcmwNCjAJc3RyaW5nCURJR0kgQm9vc3RlciBtb2R1bGVcXDAJW2ZpZD0wMDAwMDEzMDItMjEtMDAwRElHSTtleHQ9ZGlnaTttaW1lPTtdRGlnaWJvb3N0ZXIgbXVzaWMgZmlsZQ0KPjYxMAlzdHJpbmcJeAlbdGl0bGU9JS4zMnNdDQoNCiMgTWFnaWMgSUQgZm9yIERlbHVzaW9uIFh0cmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlERE1GCVtmaWQ9MDAwMDAwMDAwLTIxLTAwMDBETUY7ZXh0PWRtZjttaW1lPTtdRGVsdXNpb24gdHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZQ0KPjQJYnl0ZQl4CSwgdmVyc2lvbiAlZC4wDQo+MTMJc3RyaW5nCT5cXDAJW3RpdGxlPSUuMzBzXQ0KPjQzCXN0cmluZwk+XFwwCVtjcmVhdG9yPSUuMjBzXQ0KDQojIE1hZ2ljIElEIGZvciBET1MgU291bmQgaW50ZXJmYWNlIGtpdCAoRFNJSykgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJc3RyaW5nCVJJRkYJW2ZpZD0wMDAwMDAwMDAtMjEtMDAwMERTTTtleHQ9ZHNtO21pbWU9O11ET1MgU291bmQgaW50ZXJmYWNlIGtpdCBtb2R1bGUgbXVzaWMgZmlsZQ0KJjgJc3RyaW5nCURTTUYJDQoNCiMgTWFnaWMgSUQgZm9yIEVkbGliIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNyBieSBDYXJsDQowCXN0cmluZwlcXHgwMFxceDA2XFx4RkVcXHhGRAlbZmlkPTAwMDEwMDAyNy0yMS0wMDAwRURMO2V4dD1lZGw7bWltZT07XUVkbGliIEZNIHRyYWNrZXIgbXVzaWMgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBRdWFkcmEgQ29tcG9zZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA0IGJ5IENhcmwNCjAJc3RyaW5nCUZPUk0JW2ZpZD0wMDAxMDAwODUtMjEtMDAwMEVNRDtleHQ9ZW1kO21pbWU9O11FbmhhbmNlZCBtb2R1bGUgbXVzaWMgZmlsZSAoSUZGKQ0KJjgJc3RyaW5nCUVNT0QJDQoNCiMgTWFnaWMgSUQgZm9yIEZhcmFuZG9sZSBjb21wb3NlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMAlzdHJpbmcJRkFSXFx4RkUJW2ZpZD0wMDAxMDAwODctMjEtMDAwMEZBUjtleHQ9ZmFyO21pbWU9O11GYXJhbmRvbGUgY29tcG9zZXIgbW9kdWxlIG11c2ljIGZpbGUNCj40CXN0cmluZwk+XFwwCVt0aXRsZT0lLjQwc10NCg0KIyBNYWdpYyBJRCBmb3IgRnVua3RyYWNrZXIgR29sZCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJRnVuawlbZmlkPTAwMDEwMDA4Ni0yMS0wMDAwRk5LO2V4dD1mbms7bWltZT07XUZ1bmt0cmFja2VyIEdvbGQgbXVzaWMgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBCZWxscywgV2hpc3RsZXMsIGFuZCBTb3VuZCBCb2FyZHMgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE2IGJ5IENhcmwNCjAJc3RyaW5nCUdETVxceEZFCVtmaWQ9MDAwMDAxMjgwLTIxLTAwMDBHRE07ZXh0PWdkbTttaW1lPTtdR2VuZXJhbCBEaWdpTXVzaWMgbW9kdWxlIG11c2ljIGZpbGUNCj40CXN0cmluZwk+XFx4MDAJW3RpdGxlPSUuMzJzXQ0KDQojIE1hZ2ljIElEIGZvciBHcmFvdW1mIFRyYWNrZXIgMiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMAlzdHJpbmcJR1QyCVtmaWQ9MDAwMTAwMDMxLTIxLTAwMDBHVDI7ZXh0PWd0MjttaW1lPTtdR3Jhb3VtZiBUcmFja2VyIG1vZHVsZSBtdXNpYyBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEltYWdvIE1vcnBoZXVzIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQoweDNDCXN0cmluZwlJTTEwCVtmaWQ9MDAwMDAxMjc5LTIxLTAwMDBJTUY7ZXh0PWltZjttaW1lPTtdSW1hZ28gbW9ycGhldXMgbXVzaWMgZmlsZSwgMzIgY2hhbm5lbHMNCj4wCXN0cmluZwl4CVt0aXRsZT0lLjMxc10NCg0KIyBNYWdpYyBJRCBmb3IgSW1wdWxzZSB0cmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlJTVBNCVtmaWQ9MDAwMTAwMDMyLTIxLTAwMDAwSVQ7ZXh0PWl0O21pbWU9O11JbXB1bHNlIFRyYWNrZXIgbW9kdWxlIG11c2ljIGZpbGUNCj40CXN0cmluZwk+XFx4MDAJW3RpdGxlPSUuMjZzXQ0KDQojIE1hZ2ljIElEIGZvciBKYW1jcmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQowCXN0cmluZwlCZUVwCVtmaWQ9MDAwMDAwMDAwLTIxLTAwMDBKQU07ZXh0PWphbTttaW1lPTtdSmFtY3JhY2tlciB0cmFja2VyIG1vZHVsZSBtdXNpYyBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIExpcXVpZCB0cmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQowCXN0cmluZwlMaXF1aWQgTW9kdWxlOglbZmlkPTAwMDEwMDA5MC0yMS0wMDAwTElRO2V4dD1saXE7bWltZT07XUxpcXVpZCB0cmFja2VyIG1vZHVsZSBtdXNpYyBmaWxlDQomMHg0MAlieXRlCTB4MUEJDQo+MHgwRQlzdHJpbmcJPlxcMAlbdGl0bGU9JS4zMHNdDQo+MHgyYwlzdHJpbmcJPlxcMAlbY3JlYXRvcj0lLjIwc10NCg0KIyBNYWdpYyBJRCBmb3IgRGlnaXRyYWtrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA0IGJ5IENhcmwNCjAJc3RyaW5nCURNREwJW2ZpZD0wMDAxMDAwODgtMjEtMDAwME1ETDtleHQ9bWRsO21pbWU9O11EaWdpdHJha2tlciBtb2R1bGUgbXVzaWMgZmlsZQ0KJjQJYnl0ZQk8MHgxMgkNCg0KIyBNYWdpYyBJRCBmb3IgTUVEIFNvdW5kc3R1ZGlvIC8gT2N0YU1FRCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMAlzdHJpbmcJTU1EMAlbZmlkPTAwMDAwMTI3OC0yMS0wMDAwTUVEO2V4dD1tZWQ7bWltZT07XU9jdGFtZWQgdHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBNRUQgU291bmRzdHVkaW8gLyBPY3RhTUVEIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlNTUQxCVtmaWQ9MDAwMDAxMjc4LTIxLTAwMDBNRUQ7ZXh0PW1lZDttaW1lPTtdT2N0YW1lZCBQcm8gVHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBNRUQgU291bmRzdHVkaW8gLyBPY3RhTUVEIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlNTUQyCVtmaWQ9MDAwMDAxMjc4LTIxLTAwMDBNRUQ7ZXh0PW1lZDttaW1lPTtdT2N0YW1lZCBQcm8gVHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBNRUQgU291bmRzdHVkaW8gLyBPY3RhTUVEIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlNTUQzCVtmaWQ9MDAwMDAxMjc4LTIxLTAwMDBNRUQ7ZXh0PW1lZDttaW1lPTtdT2N0YW1lZCBTb3VuZCBTdHVkaW8gbW9kdWxlIG11c2ljIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgTXVzaWNsaW5lIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNSBieSBDYXJsDQowCXN0cmluZwlNTEVETU9ETAlbZmlkPTAwMDAwMTMwNC0yMS0wMDAwME1MO2V4dD1tbDttaW1lPTtdTXVzaWNsaW5lIG1vZHVsZSBtdXNpYyBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFByb3RyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjEwODAJc3RyaW5nCU0hSyEJW2ZpZD0wMDAxMDAwMjItMjEtMDAwME1PRDtleHQ9bW9kO21pbWU9O11Qcm90cmFja2VyIDIuMyBtb2R1bGUgbXVzaWMgZmlsZQ0KPjAJc3RyaW5nCT5cXDAJW3RpdGxlPSUuMjBzXQ0KDQojIE1hZ2ljIElEIGZvciBQcm90cmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQoxMDgwCXN0cmluZwlNLksuCVtmaWQ9MDAwMTAwMDIyLTIxLTAwMDBNT0Q7ZXh0PW1vZDttaW1lPTtdUHJvdHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZQ0KPjAJc3RyaW5nCT5cXDAJW3RpdGxlPSUuMjBzXQ0KDQojIE1hZ2ljIElEIGZvciBQcm90cmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNSBieSBDYXJsDQowCXN0cmluZwlGT1JNCVtmaWQ9MDAwMTAwMDIyLTIxLTAwMDBNT0Q7ZXh0PW1vZDttaW1lPTtdUHJvdHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZSwgdmVyc2lvbiAzLngNCiY4CXN0cmluZwlNT0RMCQ0KDQojIE1hZ2ljIElEIGZvciBTdGFydHJla2tlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMTA4MAlzdHJpbmcJRkxUNAlbZmlkPTAwMDEwMDAyNC0yMS0wMDAwTU9EO2V4dD1tb2Q7bWltZT07XVN0YXJ0cmVra2VyIG1vZHVsZSBtdXNpYyBmaWxlLCA0IGNoYW5uZWxzDQo+MAlzdHJpbmcJPlxcMAlbdGl0bGU9JS4yMHNdDQoNCiMgTWFnaWMgSUQgZm9yIFN0YXJ0cmVra2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQoxMDgwCXN0cmluZwlGTFQ4CVtmaWQ9MDAwMTAwMDI0LTIxLTAwMDBNT0Q7ZXh0PW1vZDttaW1lPTtdU3RhcnRyZWtrZXIgbW9kdWxlIG11c2ljIGZpbGUsIDggY2hhbm5lbHMNCj4wCXN0cmluZwk+XFwwCVt0aXRsZT0lLjIwc10NCg0KIyBNYWdpYyBJRCBmb3IgRmFzdHRyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjEwODAJc3RyaW5nCTZDSE4JW2ZpZD0wMDAwMDEyNzUtMjEtMDAwME1PRDtleHQ9bW9kO21pbWU9O11GYXN0dHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZSwgNiBjaGFubmVscw0KPjAJc3RyaW5nCT5cXDAJW3RpdGxlPSUuMjBzXQ0KDQojIE1hZ2ljIElEIGZvciBGYXN0dHJhY2tlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMTA4MAlzdHJpbmcJOENITglbZmlkPTAwMDAwMTI3NS0yMS0wMDAwTU9EO2V4dD1tb2Q7bWltZT07XUZhc3R0cmFja2VyIG1vZHVsZSBtdXNpYyBmaWxlLCA2IGNoYW5uZWxzDQo+MAlzdHJpbmcJPlxcMAlbdGl0bGU9JS4yMHNdDQoNCiMgTWFnaWMgSUQgZm9yIE1hZHRyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA1IGJ5IENhcmwNCjAJc3RyaW5nCU1UMjAJW2ZpZD0wMDAxMDAwOTEtMjEtMDAwME1UMjtleHQ9bXQyO21pbWU9O11NYWR0cmFja2VyIG1vZHVsZSBtdXNpYyBmaWxlDQo+NDIJc3RyaW5nCXgJW3RpdGxlPSUuNjRzXQ0KPjExMglsZXNob3J0CXgJW2Nobj0lZF0NCg0KIyBNYWdpYyBJRCBmb3IgTXVsdGl0cmFja2VyIE1vZHVsZSBlZGl0b3IgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJc3RyaW5nCU1UTQlbZmlkPTAwMDEwMDA4OS0yMS0wMDAwTVRNO2V4dD1tdG07bWltZT07XU11bHRpVHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZQ0KPjQJc3RyaW5nCT5cXHgwMAlbdGl0bGU9JS4yMHNdDQoNCiMgTWFnaWMgSUQgZm9yIE1hZHRyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA1IGJ5IENhcmwNCjAJc3RyaW5nCU1UUDIJW2ZpZD0wMDAxMDAwOTEtMjEtMDAwME1UUDtleHQ9bXRwO21pbWU9O11NYWR0cmFja2VyIHBhdHRlcm4gZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBBL05FUyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDcgYnkgQ2FybA0KMAlzdHJpbmcJTkVTQQlbZmlkPTAwMDEwMDA5NC0yMS0wMDAwTlNBO2V4dD1uc2E7bWltZT07XUEvTkVTIHJpcHBlZCBhdWRpbyBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDcgYnkgQ2FybA0KMAlzdHJpbmcJTkVTTVxceDFBCVtmaWQ9MDAwMTAwMDkzLTIxLTAwMDBOU0Y7ZXh0PW5zZjttaW1lPTtdTkVTIHJpcHBlZCBhdWRpbyBmaWxlDQo+NQlieXRlCXgJLCB2ZXJzaW9uICVkLjANCj4weDBFCXN0cmluZwl4CVt0aXRsZT0lLjMyc10NCj4weDJFCXN0cmluZwl4CVtjcmVhdG9yPSUuMzJzXQ0KDQojIE1hZ2ljIElEIGZvciBOb2lzZXRyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjEwODAJc3RyaW5nCU0mSyEJW2ZpZD0wMDAxMDAwMjMtMjEtMDAwME5TVDtleHQ9bnN0O21pbWU9O11Ob2lzZXRyYWNrZXIgbW9kdWxlIG11c2ljIGZpbGUNCj4wCXN0cmluZwk+XFwwCVt0aXRsZT0lLjIwc10NCg0KIyBNYWdpYyBJRCBmb3IgT2t0YWx5emVyIHRyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJc3RyaW5nCU9LVEFTT05HCVtmaWQ9MDAwMTAwMDMwLTIxLTAwMDBPS1Q7ZXh0PW9rdDttaW1lPTtdT2t0YWx5emVyIG1vZHVsZSBtdXNpYyBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFNCU3R1ZGlvIHNvdW5kIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNSBieSBDYXJsDQowCXN0cmluZwlQQUNHCVtmaWQ9MDAwMTAwMDIwLTIxLTAwMDBQQUM7ZXh0PXBhYzttaW1lPTtdU0JTdHVkaW8gbW9kdWxlIG11c2ljIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgUG9seXRyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjQ0CXN0cmluZwlQVE1GCVtmaWQ9MDAwMDAwMDAwLTIxLTAwMDBQVE07ZXh0PXB0bTttaW1lPTtdUG9seSBUcmFja2VyIG1vZHVsZSBtdXNpYyBmaWxlDQo+MzgJbGVzaG9ydAk+MAlbY2huPSVkXQ0KPjAJc3RyaW5nCT5cXDAJW3RpdGxlPSUuMjhzXQ0KDQojIE1hZ2ljIElEIGZvciBSZWFsaXR5IEFkbGliIHRyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJc3RyaW5nCVJBRFxcIGJ5CVtmaWQ9MDAwMDAwMDAwLTIxLTAwMDBSQUQ7ZXh0PXJhZDttaW1lPTtdUmVhbGl0eSBBZGxpYiB0cmFja2VyIG11c2ljIGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlSSUZGCVtmaWQ9MDAwMDAwMDAwLTIxLTAwMDBSTUk7ZXh0PXJtaTttaW1lPWFwcGxpY2F0aW9uL3ZuZC5tdXNpYy1uaWZmO11Tb25nIG5vdGF0aW9uIGRhdGEgZmlsZSwgbGl0dGxlLWVuZGlhbg0KJjgJc3RyaW5nCU5JRkYJDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMAlzdHJpbmcJUklGWAlbZmlkPTAwMDAwMDAwMC0yMS0wMDAwUk1JO2V4dD1ybWk7bWltZT1hcHBsaWNhdGlvbi92bmQubXVzaWMtbmlmZjtdU29uZyBub3RhdGlvbiBkYXRhIGZpbGUsIGJpZy1lbmRpYW4NCiY4CXN0cmluZwlOSUZGCQ0KDQojIE1hZ2ljIElEIGZvciBBZGxpYiBWaXN1YWwgQ29tcG9zZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJbGVzaG9ydAkweDAwMDAJW2ZpZD0wMDAwMDEwMTYtMjEtMDAwMFJPTDtleHQ9cm9sO21pbWU9O11BZGxpYiBtdXNpYyBmaWxlDQomMglsZXNob3J0CTB4MDAwNAkNCg0KIyBNYWdpYyBJRCBmb3IgU2NyZWFtdHJhY2tlciAzIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQoweDJDCXN0cmluZwlTQ1JNCVtmaWQ9MDAwMTAwMDI1LTIxLTAwMDBTM007ZXh0PXMzbTttaW1lPTtdU2NyZWFtIHRyYWNrZXIgbW9kdWxlIG11c2ljIGZpbGUNCj4weDJBCWxlc2hvcnQJPjAJLCB2ZXJzaW9uICVkLjANCj4wCXN0cmluZwk+XFwwCVt0aXRsZT0lLjI4c10NCg0KIyBNYWdpYyBJRCBmb3IgU3VycHJpc2UhIEFkbGliIFRyYWNrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJc3RyaW5nCVNBZFQJW2ZpZD0wMDAxMDAwMjEtMjEtMDAwMFNBMjtleHQ9c2EyO21pbWU9O11TdXJwcmlzZSBQcm9kdWN0aW9ucyBBZGxpYiB0cmFja2VyIG11c2ljIGZpbGUNCj40CWJ5dGUJeAksIHZlcnNpb24gMC4lZA0KDQojIE1hZ2ljIElEIGZvciBTb3VuZEZYIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQo2MAlzdHJpbmcJU09ORwlbZmlkPTAwMDAwMTI3Ny0yMS0wMDAwU0ZYO2V4dD1zZng7bWltZT07XVNvdW5kRlggVHJhY2tlciBtb2R1bGUgbXVzaWMgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBQbGF5U0lELCBTaWRwbGF5IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNSBieSBDYXJsDQowCXN0cmluZwlQU0lECVtmaWQ9MDAwMDAwMDAwLTIxLTAwMDBTSUQ7ZXh0PXNpZDttaW1lPTtdUGxheVNJRCBtdXNpYyBmaWxlDQo+MTYJc3RyaW5nCXgJW3RpdGxlPSUuMjBzXQ0KPjM2CXN0cmluZwl4CVtjcmVhdG9yPSUuMjBzXQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE1IGJ5IENhcmwNCjAJc3RyaW5nCU1UaGQJW2ZpZD0wMDAwMDEwMTctMjEtMDAwMFNNRjtleHQ9c21mLG1pZGk7bWltZT07XVN0YW5kYXJkIE1JREkgbXVzaWMgZmlsZQ0KPjEwCWJlc2hvcnQJPjAJW2Nobj0lZF0NCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNSBieSBDYXJsDQowCXN0cmluZwlGT1JNCVtmaWQ9MDAwMDAxMDEwLTIxLTAwMFNNVVM7ZXh0PXNtdXMsbXVzO21pbWU9O11JRkYgU2ltcGxlIE11c2ljYWwgU2NvcmUgZmlsZQ0KJjgJc3RyaW5nCVNNVVMJDQoNCiMgTWFnaWMgSUQgZm9yIFNuZHRvb2wyLG5lenBsYXkgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA1IGJ5IENhcmwNCjAJc3RyaW5nCVNORFxceDFBCVtmaWQ9MDAwMDAwMDAwLTIxLTAwMDBTTkQ7ZXh0PXNuZDttaW1lPTtdTmludGVuZG8gRW50ZXJ0YWlubWVudCBTeXN0ZW0gYXVkaW8gZmlsZSAoTkVTKQ0KJjQJYnl0ZQkzCSwgdmVyc2lvbiAlMy4wDQo+NQlieXRlCXgJW2Nobj0lZF0NCg0KIyBNYWdpYyBJRCBmb3IgaU5FUyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDUgYnkgQ2FybA0KMAlzdHJpbmcJU05EXFx4MUEJW2ZpZD0wMDAxMDAwOTItMjEtMDAwMFNORDtleHQ9c25kO21pbWU9O11pTkVTIGVtdWxhdG9yIGF1ZGlvIGZpbGUNCiY0CWJ5dGUJMQksIHZlcnNpb24gJTEuMA0KPjUJYnl0ZQl4CVtjaG49JWRdDQoNCiMgTWFnaWMgSUQgZm9yIFNUTUlLIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQoweDE1CXN0cmluZwlTY3JlYW0hCVtmaWQ9MDAwMTAwMDI1LTIxLTAwMDBTVFg7ZXh0PXN0eDttaW1lPTtdU1RNSUsgbW9kdWxlIG11c2ljIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgVGhlIEZpbmFsIE11c2ljc3lzdGVtIGVYdGVuZGVkIChURk1YKSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDcgYnkgQ2FybA0KMAlzdHJpbmcJVEZNWC1TT05HXFwgCVtmaWQ9MDAwMTAwMDk2LTIxLTAwMDBURlg7ZXh0PXRmeCx0Zm14O21pbWU9O11URk1YIHRyYWNrZXIgbW9kdWxlIG11c2ljIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgVWx0cmEgVHJhY2tlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDcgYnkgQ2FybA0KMAlzdHJpbmcJTUFTX1VUcmFja19WMDAJW2ZpZD0wMDAxMDAwOTctMjEtMDAwMFVMVDtleHQ9dWx0O21pbWU9O11VbHRyYSBUcmFja2VyIG1vZHVsZSBtdXNpYyBmaWxlDQo+MTUJc3RyaW5nCXgJW3RpdGxlPSUuMzJzXQ0KDQojIE1hZ2ljIElEIGZvciBBUGxheWVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlBUFVOXFx4MDEJW2ZpZD0wMDAwMDAwMDAtMjEtMDAwMFVOSTtleHQ9dW5pO21pbWU9O11BUGxheWVyIG1vZHVsZSBtdXNpYyBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIE1pa21vZCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTUgYnkgQ2FybA0KMAlzdHJpbmcJVU4wCVtmaWQ9MDAwMDAwMDAwLTIxLTAwMDBVTkk7ZXh0PXVuaTttaW1lPTtdTWlrbW9kIG1vZHVsZSBtdXNpYyBmaWxlDQo+NAlieXRlCXgJW2Nobj0lZF0NCg0KIyBNYWdpYyBJRCBmb3IgRmFzdHRyYWNrZXIgMi4wIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNSBieSBDYXJsDQowCXN0cmluZwlFeHRlbmRlZFxcIE1vZHVsZTpcXCAJW2ZpZD0wMDAxMDAwMjYtMjEtMDAwMDBYTTtleHQ9eG07bWltZT07XUZhc3RUcmFja2VyIElJIG1vZHVsZSBtdXNpYyBmaWxlDQo+NTkJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjU4CWJ5dGUJeAkuMCVkDQo+MTcJc3RyaW5nCXgJW3RpdGxlPSUuMjBzXQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCUZPUk0JW2ZpZD0wMDAwMDEwMTAtMjItMDAwOFNWWDtleHQ9OHN2eDttaW1lPTtdQW1pZ2EgU2FtcGxlZCBhdWRpbyBmaWxlDQomOAlzdHJpbmcJOFNWWAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlGT1JNCVtmaWQ9MDAwMDAxMDAyLTIyLTAwMEFJRkM7ZXh0PWFpZmMsYWlmO21pbWU9O11BdWRpbyBDb21wcmVzc2VkIEludGVyY2hhbmdlIEZpbGUgRm9ybWF0DQomOAlzdHJpbmcJQUlGQwkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlGT1JNCVtmaWQ9MDAwMDAxMDAyLTIyLTAwMEFJRkY7ZXh0PWFpZmYsYWlmO21pbWU9O11BdWRpbyBJbnRlcmNoYW5nZSBGaWxlIEZvcm1hdA0KJjgJc3RyaW5nCUFJRkYJDQoNCiMgTWFnaWMgSUQgZm9yIE1vbmtleUF1ZGlvIHNvZnR3YXJlIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0yMCBieSBDYXJsDQowCXN0cmluZwlNQUNcXCAJW2ZpZD0wMDAxMDAxMjAtMjItMDAwMEFQRTtleHQ9YXBlO21pbWU9O11Nb25rZXlBdWRpbyBjb21wcmVzc2VkIGF1ZGlvIGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwkuc25kCVtmaWQ9MDAwMDAxMDExLTIyLTAwMDAwQVU7ZXh0PWF1LHNuZDttaW1lPTtdU3VuIC8gTmVYdCBzYW1wbGVkIGF1ZGlvIGZpbGUNCj4xNgliZWxvbmcJPjAJW2ZyZXE9JWRdDQoNCiMgTWFnaWMgSUQgZm9yIFNwcGFjayBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMjUyCWJlc2hvcnQJMHg0MEMzCVtmaWQ9MDAwMDAxMDE5LTIyLTAwMDAwMEQ7ZXh0PWQ7bWltZT07XVNwcGFjayBhdWRpbyBzYW1wbGUgZmlsZQ0KJjI1NAliZXNob3J0CTB4RkMwRQkNCg0KIyBNYWdpYyBJRCBmb3IgRmxhYyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDggYnkgQ2FybA0KMAlzdHJpbmcJZkxhQwlbZmlkPTAwMDEwMDA5OC0yMi0wMDBGTEFDO2V4dD1mbGFjO21pbWU9O11GcmVlIExvc3NsZXNzIEF1ZGlvIENvZGVjIHJhdyBhdWRpbyBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEZhcmFuZG9sZSBDb21wb3NlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJRlNNXFx4RkUJW2ZpZD0wMDAxMDAwODctMjItMDAwMEZTTTtleHQ9ZnNtO21pbWU9O11GYXJhbmRvbGUgY29tcG9zZXIgYXVkaW8gc2FtcGxlIGZpbGUNCj40CXN0cmluZwl4CVt0aXRsZT0lLjMyc10NCg0KIyBNYWdpYyBJRCBmb3IgTUFVRCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJRk9STQlbZmlkPTAwMDAwMDAwMC0yMi0wMDBNQVVEO2V4dD1tYXVkO21pbWU9O11NQVVEIGF1ZGlvIHNhbXBsZSBmaWxlDQomNAlzdHJpbmcJTUFVRAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0wNCBieSBDYXJsDQowCWJlc2hvcnQmMHhGRkUwCTB4ZmZlMAlbZmlkPTAwMDAwMDAwMS0yMi0wMDExMTcyO2V4dD1tcDEsbXAyLG1wMzttaW1lPWF1ZGlvL21wZWc7XU1QMyBBdWRpbyBzdHJlYW0gZmlsZQ0KJloxMjgJc3RyaW5nCVRBRwkNCj5aMTI1CXN0cmluZwl4CVt0aXRsZT0lLjMwc10NCj5aOTUJc3RyaW5nCXgJW2NyZWF0b3I9JS4zMHNdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMDQgYnkgQ2FybA0KMAlzdHJpbmcJSUQzCVtmaWQ9MDAwMDAwMDAxLTIyLTAwMTExNzI7ZXh0PW1wMSxtcDIsbXAzO21pbWU9YXVkaW8vbXBlZztdTVAzIEF1ZGlvIHN0cmVhbSBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJXFwwXFwwMDFcXDI0M1xcMTQ0CVtmaWQ9MDAwMDAxMDE0LTIyLTAwMDAwU0Y7ZXh0PXNmO21pbWU9O11JUkNBTSBhdWRpbyBzYW1wbGUgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCVxcMFxcMDAyXFwyNDNcXDE0NAlbZmlkPTAwMDAwMTAxNC0yMi0wMDAwMFNGO2V4dD1zZjttaW1lPTtdSVJDQU0gYXVkaW8gc2FtcGxlIGZpbGUsIGxpdHRsZS1lbmRpYW4NCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlcXDBcXDAwM1xcMjQzXFwxNDQJW2ZpZD0wMDAwMDEwMTQtMjItMDAwMDBTRjtleHQ9c2Y7bWltZT07XUlSQ0FNIGF1ZGlvIHNhbXBsZSBmaWxlLCBiaWctZW5kaWFuDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJXFwxNDRcXDI0M1xcMDAxXFwwCVtmaWQ9MDAwMDAxMDE0LTIyLTAwMDAwU0Y7ZXh0PXNmO21pbWU9O11JUkNBTSBhdWRpbyBzYW1wbGUgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCVxcMTQ0XFwyNDNcXDAwMlxcMAlbZmlkPTAwMDAwMTAxNC0yMi0wMDAwMFNGO2V4dD1zZjttaW1lPTtdSVJDQU0gYXVkaW8gc2FtcGxlIGZpbGUsIGJpZy1lbmRpYW4NCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlcXDE0NFxcMjQzXFwwMDNcXDAJW2ZpZD0wMDAwMDEwMTQtMjItMDAwMDBTRjtleHQ9c2Y7bWltZT07XUlSQ0FNIGF1ZGlvIHNhbXBsZSBmaWxlLCBsaXR0bGUtZW5kaWFuDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJXFwxNDRcXDI0M1xcMDA0XFwwCVtmaWQ9MDAwMDAxMDE0LTIyLTAwMDAwU0Y7ZXh0PXNmO21pbWU9O11JUkNBTSBhdWRpbyBzYW1wbGUgZmlsZSwgYmlnLWVuZGlhbg0KDQojIE1hZ2ljIElEIGZvciBTY3JlYW10cmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNSBieSBDYXJsDQoweDRDCXN0cmluZwlTQ1JTCVtmaWQ9MDAwMTAwMDI1LTIyLTAwMDBTTVA7ZXh0PXNtcDttaW1lPTtdU2NyZWFtdHJhY2tlciBhdWRpbyBzYW1wbGUNCj4weDMwCXN0cmluZwl4CVt0aXRsZT0lLjMwc10NCg0KIyBNYWdpYyBJRCBmb3IgU291bmRUb29sIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNyBieSBDYXJsDQowCXN0cmluZwlTT1VORFxceDFBCVtmaWQ9MDAwMTAwMDk1LTIyLTAwMDBTTkQ7ZXh0PXNuZDttaW1lPTtdU291bmQgdG9vbCBhdWRpbyBkYXRhIGZpbGUNCj4xNAlsZXNob3J0CXgJW2ZyZXE9JWRdDQoNCiMgTWFnaWMgSUQgZm9yIFNCU3R1ZGlvIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlTTkRcXCAJW2ZpZD0wMDAxMDAwMjAtMjItMDAwMFNPVTtleHQ9c291O21pbWU9O11TQlN0dWRpbyBzYW1wbGVkIGF1ZGlvIGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0xOCBieSBDYXJsDQowCXN0cmluZwlTcGVleAlbZmlkPTAwMDAwMDAwMC0yMi0wMFNQRUVYO2V4dD1zcGVleDttaW1lPTtdU3BlZXggTG9zc3kgQXVkaW8gQ29kZWMgcmF3IGF1ZGlvIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgU291bmQgQmxhc3RlciBTREsgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCUNyZWF0aXZlXFwgVm9pY2VcXCBGaWxlXFx4MUEJW2ZpZD0wMDAwMDEwMTMtMjItMDAwMFZPQztleHQ9dm9jO21pbWU9O11DcmVhdGl2ZSBWb2ljZSBhdWRpbyBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMTggYnkgQ2FybA0KMAlzdHJpbmcJdm9yYmlzCVtmaWQ9MDAwMDAwMDAwLTIyLTBWT1JCSVM7ZXh0PXZvcmJpczttaW1lPTtdVm9yYmlzIExvc3N5IEF1ZGlvIENvZGVjIHJhdyBhdWRpbyBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJUklGRglbZmlkPTAwMDAwMTAwMS0yMi0wMDAwV0FWO2V4dD13YXY7bWltZT07XU1pY3Jvc29mdCBXYXZlZm9ybSBBdWRpbyBmaWxlLCBsaXR0bGUtZW5kaWFuDQomOAlzdHJpbmcJV0FWRQkNCj4yNAlsZWxvbmcJPjAJW2ZyZXE9JWRdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJUklGWAlbZmlkPTAwMDAwMTAwMS0yMi0wMDAwV0FWO2V4dD13YXY7bWltZT07XU1pY3Jvc29mdCBXYXZlZm9ybSBBdWRpbyBmaWxlLCBiaWctZW5kaWFuDQomOAlzdHJpbmcJV0FWRQkNCj4yNAliZWxvbmcJPjAJW2ZyZXE9JWRdDQoNCiMgTWFnaWMgSUQgZm9yIE1heWEgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCUZPUjQJW2ZpZD0wMDAwMDEzMTItMzEtMDAwMDAwMDtleHQ9O21pbWU9O11NYXlhIGltYWdlIGZpbGUNCiY4CXN0cmluZwlDSU1HCQ0KDQojIE1hZ2ljIElEIGZvciBNYXlhIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlGT1I4CVtmaWQ9MDAwMDAxMzEyLTMxLTAwMDAwMDA7ZXh0PTttaW1lPTtdTWF5YSBpbWFnZSBmaWxlDQomOAlzdHJpbmcJQ0lNRwkNCg0KIyBNYWdpYyBJRCBmb3IgQU9MIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMiBieSBDYXJsDQowCXN0cmluZwlKR1xceDA0XFx4MEUJW2ZpZD0wMDAwMDEwMjMtMzEtMDAwMEFSVDtleHQ9YXJ0O21pbWU9O11BT0wvSm9obnNvbi1HcmFjZSBpbWFnZSBmaWxlLCB2ZXJzaW9uIDIuMA0KPjB4MEQJbGVzaG9ydAl4CVtyZXM9JWR4DQo+MHgwRglsZXNob3J0CXgJJWRdDQoNCiMgTWFnaWMgSUQgZm9yIEJNRiBpbWFnZSBjb21wcmVzc29yIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yNyBieSBDYXJsDQowCXN0cmluZwlcXHg4MVxceDhBMAlbZmlkPTAwMDEwMDA0OC0zMS0wMDAwQk1GO2V4dD1ibWY7bWltZT07XUJNRiBpbWFnZSBmaWxlDQo+MglzdHJpbmcJeAksIHZlcnNpb24gJS4xcw0KPjMJc3RyaW5nCXgJLiUuMXMNCg0KIyBNYWdpYyBJRCBmb3IgQk1GIGltYWdlIGNvbXByZXNzb3IgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTI3IGJ5IENhcmwNCjAJc3RyaW5nCVxceDgxXFx4OEEyCVtmaWQ9MDAwMTAwMDQ4LTMxLTAwMDBCTUY7ZXh0PWJtZjttaW1lPTtdQk1GIGltYWdlIGZpbGUNCj4yCXN0cmluZwl4CSwgdmVyc2lvbiAlLjFzDQo+MwlzdHJpbmcJeAkuJS4xcw0KDQojIE1hZ2ljIElEIGZvciBCTUYgaW1hZ2UgY29tcHJlc3NvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjcgYnkgQ2FybA0KMAlzdHJpbmcJXFx4ODFcXHg4QTEJW2ZpZD0wMDAxMDAwNDgtMzEtMDAwMEJNRjtleHQ9Ym1mO21pbWU9O11CTUYgaW1hZ2UgZmlsZQ0KPjIJc3RyaW5nCXgJLCB2ZXJzaW9uICUuMXMNCj4zCXN0cmluZwl4CS4lLjFzDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJQk0JW2ZpZD0wMDAwMDEwMDEtMzEtMDAwMEJNUDtleHQ9Ym1wO21pbWU9O11XaW5kb3dzIG9yIE9TLzIgQml0bWFwIGltYWdlIGZpbGUNCiY2CWxlbG9uZwkwCQ0KDQojIE1hZ2ljIElEIGZvciBBdXRvZGVzayBBbmltYXRvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDggYnkgQ2FybA0KMAlsZXNob3J0CTB4OTExOQlbZmlkPTAwMDAwMTI1NC0zMS0wMDAwQ0VMO2V4dD1jZWwscGljO21pbWU9O11BdXRvZGVzayBhbmltYXRvciBpbWFnZSBmaWxlDQomMTAJYnl0ZQk4CQ0KJjExCWJ5dGUJMAkNCj4yCWxlc2hvcnQJeAlbcmVzPSVkDQo+NAlsZXNob3J0CXgJeCVkeDhicHBdDQoNCiMgTWFnaWMgSUQgZm9yIEFuZHJldyBVc2VyIEludGVyZmFjZSBTeXN0ZW0gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAyIGJ5IENhcmwNCjEJc3RyaW5nCWJlZ2luZGF0YXtyYXN0ZXIJW2ZpZD0wMDAwMDEzMTUtMzEtMDAwMENNVTtleHQ9Y211O21pbWU9O11BbmRyZXcgdG9vbGtpdCByYXN0ZXIgaW1hZ2UgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBBbml2Z2EgdG9vbGtpdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDIgYnkgQ2FybA0KMzgJc3RyaW5nCUtSXFx4MDFcXHgwMAlbZmlkPTAwMDEwMDEwNC0zMS0wMDAwQ09EO2V4dD1jb2Q7bWltZT07XUFuaXZnYSBzcHJpdGUgaW1hZ2UgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBXaW5kb3dzIEN1cnNvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDBcXHgwMFxceDAyXFx4MDAJW2ZpZD0wMDAwMDEwMDEtMzEtMDAwMENVUjtleHQ9Y3VyO21pbWU9O11NaWNyb3NvZnQgd2luZG93cyBjdXJzb3IgaW1hZ2UgZmlsZQ0KJjB4MDgJYnl0ZQkwCQ0KPjQJbGVzaG9ydAl4CSwgJWQgY3Vyc29yKHMpDQo+NglieXRlCXgJW3Jlcz0lZHgNCj43CWJ5dGUJeAklZHg4YnBwXQ0KDQojIE1hZ2ljIElEIGZvciBXaW5kb3dzIEN1cnNvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDBcXHgwMFxceDAyXFx4MDAJW2ZpZD0wMDAwMDEwMDEtMzEtMDAwMENVUjtleHQ9Y3VyO21pbWU9O11NaWNyb3NvZnQgd2luZG93cyBjdXJzb3IgaW1hZ2UgZmlsZQ0KJjB4MDgJYnl0ZQkxNgkNCj40CWxlc2hvcnQJeAksICVkIGN1cnNvcihzKQ0KPjYJYnl0ZQl4CVtyZXM9JWR4DQo+NwlieXRlCXgJJWR4NGJwcF0NCg0KIyBNYWdpYyBJRCBmb3IgV2luZG93cyBDdXJzb3IgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjAJc3RyaW5nCVxceDAwXFx4MDBcXHgwMlxceDAwCVtmaWQ9MDAwMDAxMDAxLTMxLTAwMDBDVVI7ZXh0PWN1cjttaW1lPTtdTWljcm9zb2Z0IHdpbmRvd3MgY3Vyc29yIGltYWdlIGZpbGUNCiYweDA4CWJ5dGUJMgkNCj40CWxlc2hvcnQJeAksICVkIGN1cnNvcihzKQ0KPjYJYnl0ZQl4CVtyZXM9JWR4DQo+NwlieXRlCXgJJWR4MWJwcF0NCg0KIyBNYWdpYyBJRCBmb3IgV2luZG93cyBDdXJzb3IgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjAJc3RyaW5nCVxceDAwXFx4MDBcXHgwMlxceDAwCVtmaWQ9MDAwMDAxMDAxLTMxLTAwMDBDVVI7ZXh0PWN1cjttaW1lPTtdTWljcm9zb2Z0IHdpbmRvd3MgY3Vyc29yIGltYWdlIGZpbGUNCiYweDA4CWJ5dGUJMzIJDQo+NAlsZXNob3J0CXgJLCAlZCBjdXJzb3IocykNCj42CWJ5dGUJeAlbcmVzPSVkeA0KPjcJYnl0ZQl4CSVkeDVicHBdDQoNCiMgTWFnaWMgSUQgZm9yIFdpbmRvd3MgQ3Vyc29yIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMSBieSBDYXJsDQowCXN0cmluZwlcXHgwMFxceDAwXFx4MDJcXHgwMAlbZmlkPTAwMDAwMTAwMS0zMS0wMDAwQ1VSO2V4dD1jdXI7bWltZT07XU1pY3Jvc29mdCB3aW5kb3dzIGN1cnNvciBpbWFnZSBmaWxlDQomMHgwOAlieXRlCTY0CQ0KPjQJbGVzaG9ydAl4CSwgJWQgY3Vyc29yKHMpDQo+NglieXRlCXgJW3Jlcz0lZHgNCj43CWJ5dGUJeAklZHg2YnBwXQ0KDQojIE1hZ2ljIElEIGZvciBXaW5kb3dzIEN1cnNvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDBcXHgwMFxceDAyXFx4MDAJW2ZpZD0wMDAwMDEwMDEtMzEtMDAwMENVUjtleHQ9Y3VyO21pbWU9O11NaWNyb3NvZnQgd2luZG93cyBjdXJzb3IgaW1hZ2UgZmlsZQ0KJjB4MDgJYnl0ZQk4CQ0KPjQJbGVzaG9ydAl4CSwgJWQgY3Vyc29yKHMpDQo+NglieXRlCXgJW3Jlcz0lZHgNCj43CWJ5dGUJeAklZHgzYnBwXQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEzIGJ5IENhcmwNCjEyOAlzdHJpbmcJRElDTQlbZmlkPTAwMDAwMDAwNC0zMS0wMDBESUNNO2V4dD1kaWNtLGRjbTttaW1lPTtdRGlnaXRhbCBpbWFnaW5nIGFuZCBjb21tdW5pY2F0aW9uIGluIG1lZGVjaW5lIGltZy4NCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMSBieSBDYXJsDQowCXN0cmluZwlTRFBYCVtmaWQ9MDAwMDAxMzA5LTMxLTAwMDBEUFg7ZXh0PWRweDttaW1lPTtdRGlnaXRhbCBNb3ZpbmctUGljdHVyZSBFeGNoYW5nZSBpbWFnZSBmaWxlDQo+MTYwCXN0cmluZwk+XFx4MDAJW2NyZWF0b3I9JS4xMDBzXQ0KPjI2MAlzdHJpbmcJPlxceDAwCVt0aXRsZT0lLjIwMHNdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KMAlzdHJpbmcJWFBEUwlbZmlkPTAwMDAwMTMwOS0zMS0wMDAwRFBYO2V4dD1kcHg7bWltZT07XURpZ2l0YWwgTW92aW5nLVBpY3R1cmUgRXhjaGFuZ2UgaW1hZ2UgZmlsZQ0KPjE2MAlzdHJpbmcJPlxceDAwCVtjcmVhdG9yPSUuMTAwc10NCj4yNjAJc3RyaW5nCT5cXHgwMAlbdGl0bGU9JS4yMDBzXQ0KDQojIE1hZ2ljIElEIGZvciBMaWdodHdhdmUgM0QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCUZPUk0JW2ZpZD0wMDAwMDEyNTEtMzEtMDAwRlBCTTtleHQ9ZnBibTttaW1lPTtdRmxleGlibGUgUHJlY2lzaW9uIEJ1ZmZlciBNYXAgaW1hZ2UgZmlsZQ0KJjgJc3RyaW5nCUZQQk0JDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJR0lGOAlbZmlkPTAwMDAwMTI3NC0zMS0wMDAwR0lGO2V4dD1naWY7bWltZT1pbWFnZS9naWY7XUdJRiBpbWFnZSBmaWxlDQomMTAJYnl0ZSYweDcwCSEweDcwCQ0KPjQJc3RyaW5nCXgJLCB2ZXJzaW9uIDglLjJzDQo+NglsZXNob3J0CT4wCVtyZXM9JWR4DQo+OAlsZXNob3J0CT4wCSVkXQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCUdJRjgJW2ZpZD0wMDAwMDEyNzQtMzEtMDAwMEdJRjtleHQ9Z2lmO21pbWU9aW1hZ2UvZ2lmO11HSUYgaW1hZ2UgZmlsZQ0KJjEwCWJ5dGUmMHg3MAkweDcwCQ0KPjQJc3RyaW5nCXgJLCB2ZXJzaW9uIDglLjJzDQo+NglsZXNob3J0CT4wCVtyZXM9JWR4DQo+OAlsZXNob3J0CT4wCSVkeDhicHBdDQoNCiMgTWFnaWMgSUQgZm9yIFdpbmRvd3MgSWNvbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDBcXHgwMFxceDAxXFx4MDAJW2ZpZD0wMDAwMDEwMDEtMzEtMDAwMElDTztleHQ9aWNvO21pbWU9aW1hZ2Uvdm5kLm1pY3Jvc29mdC5pY29uO11NaWNyb3NvZnQgd2luZG93cyBJY29uIGltYWdlIGZpbGUNCiYweDA4CWJ5dGUJMAkNCj40CWxlc2hvcnQJeAksICVkIGljb24ocykNCj42CWJ5dGUJeAlbcmVzPSVkeA0KPjcJYnl0ZQl4CSVkeDhicHBdDQoNCiMgTWFnaWMgSUQgZm9yIFdpbmRvd3MgSWNvbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDBcXHgwMFxceDAxXFx4MDAJW2ZpZD0wMDAwMDEwMDEtMzEtMDAwMElDTztleHQ9aWNvO21pbWU9aW1hZ2Uvdm5kLm1pY3Jvc29mdC5pY29uO11NaWNyb3NvZnQgd2luZG93cyBJY29uIGltYWdlIGZpbGUNCiYweDA4CWJ5dGUJMTYJDQo+NAlsZXNob3J0CXgJLCAlZCBpY29uKHMpDQo+NglieXRlCXgJW3Jlcz0lZHgNCj43CWJ5dGUJeAklZHg0YnBwXQ0KDQojIE1hZ2ljIElEIGZvciBXaW5kb3dzIEljb24gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjAJc3RyaW5nCVxceDAwXFx4MDBcXHgwMVxceDAwCVtmaWQ9MDAwMDAxMDAxLTMxLTAwMDBJQ087ZXh0PWljbzttaW1lPWltYWdlL3ZuZC5taWNyb3NvZnQuaWNvbjtdTWljcm9zb2Z0IHdpbmRvd3MgSWNvbiBpbWFnZSBmaWxlDQomMHgwOAlieXRlCTIJDQo+NAlsZXNob3J0CXgJLCAlZCBpY29uKHMpDQo+NglieXRlCXgJW3Jlcz0lZHgNCj43CWJ5dGUJeAklZHgxYnBwXQ0KDQojIE1hZ2ljIElEIGZvciBXaW5kb3dzIEljb24gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjAJc3RyaW5nCVxceDAwXFx4MDBcXHgwMVxceDAwCVtmaWQ9MDAwMDAxMDAxLTMxLTAwMDBJQ087ZXh0PWljbzttaW1lPWltYWdlL3ZuZC5taWNyb3NvZnQuaWNvbjtdTWljcm9zb2Z0IHdpbmRvd3MgSWNvbiBpbWFnZSBmaWxlDQomMHgwOAlieXRlCTMyCQ0KPjQJbGVzaG9ydAl4CSwgJWQgaWNvbihzKQ0KPjYJYnl0ZQl4CVtyZXM9JWR4DQo+NwlieXRlCXgJJWR4NWJwcF0NCg0KIyBNYWdpYyBJRCBmb3IgV2luZG93cyBJY29uIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMSBieSBDYXJsDQowCXN0cmluZwlcXHgwMFxceDAwXFx4MDFcXHgwMAlbZmlkPTAwMDAwMTAwMS0zMS0wMDAwSUNPO2V4dD1pY287bWltZT1pbWFnZS92bmQubWljcm9zb2Z0Lmljb247XU1pY3Jvc29mdCB3aW5kb3dzIEljb24gaW1hZ2UgZmlsZQ0KJjB4MDgJYnl0ZQk2NAkNCj40CWxlc2hvcnQJeAksICVkIGljb24ocykNCj42CWJ5dGUJeAlbcmVzPSVkeA0KPjcJYnl0ZQl4CSVkeDZicHBdDQoNCiMgTWFnaWMgSUQgZm9yIFdpbmRvd3MgSWNvbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDBcXHgwMFxceDAxXFx4MDAJW2ZpZD0wMDAwMDEwMDEtMzEtMDAwMElDTztleHQ9aWNvO21pbWU9aW1hZ2Uvdm5kLm1pY3Jvc29mdC5pY29uO11NaWNyb3NvZnQgd2luZG93cyBJY29uIGltYWdlIGZpbGUNCiYweDA4CWJ5dGUJOAkNCj40CWxlc2hvcnQJeAksICVkIGljb24ocykNCj42CWJ5dGUJeAlbcmVzPSVkeA0KPjcJYnl0ZQl4CSVkeDNicHBdDQoNCiMgTWFnaWMgSUQgZm9yIFN1bk9TIEljb24gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAyIGJ5IENhcmwNCjAJc3RyaW5nCS8qXFwgRm9ybWF0X3ZlcnNpb249MSxcXCAJW2ZpZD0wMDAwMDEwMTEtMzEtMDAwSUNPTjtleHQ9aWNvbjttaW1lPTtdU3VuT1MgaWNvbiBpbWFnZSBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAliZXNob3J0CTB4MDEJW2ZpZD0wMDAwMDEyNzMtMzEtMDAwMElNRztleHQ9aW1nO21pbWU9O11HRU0gQml0IEltYWdlDQomMgliZXNob3J0CTB4MDgJDQo+MTIJYmVzaG9ydAk+MAlbcmVzPSVkeA0KPjE0CWJlc2hvcnQJPjAJJWQNCj40CWJlc2hvcnQJPjAJeCVkYnBwXQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAyIGJ5IENhcmwNCjAJc3RyaW5nCVxceDhiSk5HXFx4MGRcXHgwYVxceDFhXFx4MGEJW2ZpZD0wMDAwMDAwMDAtMzEtMDAwMEpORztleHQ9am5nO21pbWU9O11KUEVHIE5ldHdvcmsgZ3JhcGhpY3MgaW1hZ2UgZmlsZQ0KJjEyCXN0cmluZwlKSERSCQ0KPjE2CWJlbG9uZwl4CVtyZXM9JWQNCj4yMAliZWxvbmcJeAl4JWRdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTMgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDBcXHgwMFxceDAwXFx4MGNqUFxceDIwXFx4MjAJW2ZpZD0wMDAwMDAwMDEtMzEtMDAxNTQ0NDtleHQ9anAyO21pbWU9aW1hZ2UvanAyO11KUEVHIDIwMDAgaW1hZ2UgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEzIGJ5IENhcmwNCjAJc3RyaW5nCVxceGZmXFx4NGZcXHhmZlxceDUxCVtmaWQ9MDAwMDAwMDAxLTMxLTAwMTU0NDQ7ZXh0PWpwYzttaW1lPTtdSlBFRyAyMDAwIGNvZGUgc3RyZWFtIGltYWdlIGZpbGUNCiZaMgliZXNob3J0CTB4RkZEOQkNCj44CWJlbG9uZwl4CVtyZXM9JWR4DQo+MTIJYmVsb25nCXgJJWRdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAliZWxvbmcJMHhmZmQ4ZmZlMAlbZmlkPTAwMDAwMTMwNS0zMS0wMDBKUEVHO2V4dD1qcGVnLGpwZzttaW1lPWltYWdlL2pwZWc7XUpvaW50IFBob3RvZ3JhcGhpYyBFeHBlcnRzIEdyb3VwIEpGSUYgaW1hZ2UgZmlsZQ0KJjYJc3RyaW5nCUpGSUZcXHgwMAkNCj4xMQlieXRlCXgJLCB2ZXJzaW9uICVkDQo+MTIJYnl0ZQl4CS4wJWQNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMyBieSBDYXJsDQowCXN0cmluZwlcXHhmZlxceGQ4XFx4ZmZcXHhlMQlbZmlkPTAwMDAwMDAwNS0zMS0wMDBKUEVHO2V4dD1qcGcsanBlZzttaW1lPWltYWdlL2pwZWc7XURpZ2l0YWwgc3RpbGwgY2FtZXJhIGltYWdlIGZpbGUNCiY2CXN0cmluZwlFeGlmCQ0KDQojIE1hZ2ljIElEIGZvciBEZWx1eGUgUGFpbnQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTMxIGJ5IENhcmwNCjAJc3RyaW5nCUZPUk0JW2ZpZD0wMDAwMDEwMTAtMzEtMDAwMExCTTtleHQ9bGJtO21pbWU9O11JbnRlcmxlYXZlZCBiaXRtYXAgaW1hZ2UgZmlsZQ0KJjgJc3RyaW5nCUlMQk0JDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTMgYnkgQ2FybA0KMAlzdHJpbmcJVGhpc1xcIGlzXFwgYVxcIEJpdE1hcFxcIGZpbGUJW2ZpZD0wMDAwMDAwMDAtMzEtMDAwTElTUDtleHQ9bGlzcDttaW1lPTtdTGlzcCBtYWNoaW5lIGZvcm1hdCBpbWFnZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIE1pY3JvZGVzaWduMiwgTWljcm9kZXNpZ24zIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMiBieSBDYXJsDQowCXN0cmluZwkuTURBCVtmaWQ9MDAwMDAxMzE2LTMxLTAwMDBNREE7ZXh0PW1kYTttaW1lPTtdTWljcm9kZXNpZ24gQXJlYSBpbWFnZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIE1pY3JvZGVzaWduMiwgTWljcm9kZXNpZ24zIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMiBieSBDYXJsDQowCXN0cmluZwkuTURQCVtmaWQ9MDAwMDAxMzE2LTMxLTAwMDBNRFA7ZXh0PW1kcDttaW1lPTtdTWljcm9kZXNpZ24gcGFnZSBpbWFnZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEltYWdlbWFnaWNrIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMSBieSBDYXJsDQowCXN0cmluZwlpZD1JbWFnZU1hZ2ljawlbZmlkPTAwMDEwMDEwMS0zMS0wMDBNSUZGO2V4dD1taWZmLG1pZjttaW1lPTtdSW1hZ2VtYWdpY2sgaW1hZ2UgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBOZXRwYm0gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAyIGJ5IENhcmwNCjAJc3RyaW5nCU1SRjEJW2ZpZD0wMDAxMDAxMDUtMzEtMDAwME1SRjtleHQ9bXJmO21pbWU9O11Nb25vY2hyb21lIHJlY3Vyc2l2ZSBmb3JtYXQgaW1hZ2UgZmlsZQ0KPjQJYmVsb25nCXgJW3Jlcz0lZHgNCj44CWJlbG9uZwl4CSVkeDFicHBdDQoNCiMgTWFnaWMgSUQgZm9yIE1pY3Jvc29mdCBQYWludCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDIgYnkgQ2FybA0KMAlsZXNob3J0CTB4NjE0NAlbZmlkPTAwMDAwMTAwMS0zMS0wMDAwTVNQO2V4dD1tc3A7bWltZT07XU1pY3Jvc29mdCBwYWludCBpbWFnZSBmaWxlLCB2ZXJzaW9uIDEuMA0KJjIJbGVzaG9ydAkweDRkNmUJDQo+NAlsZXNob3J0CXgJW3Jlcz0lZHgNCj42CWxlc2hvcnQJeAklZHgxYnBwXQ0KDQojIE1hZ2ljIElEIGZvciBNaWNyb3NvZnQgUGFpbnQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAyIGJ5IENhcmwNCjAJbGVzaG9ydAkweDY5NGMJW2ZpZD0wMDAwMDEwMDEtMzEtMDAwME1TUDtleHQ9bXNwO21pbWU9O11NaWNyb3NvZnQgcGFpbnQgaW1hZ2UgZmlsZSwgdmVyc2lvbiAyLjANCiYyCWxlc2hvcnQJMHg1MzZlCQ0KPjQJbGVzaG9ydAl4CVtyZXM9JWR4DQo+NglsZXNob3J0CXgJJWR4MWJwcF0NCg0KIyBNYWdpYyBJRCBmb3IgTmV0cGJtIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0zMSBieSBDYXJsDQowCXN0cmluZwlQN1xceDBBCVtmaWQ9MDAwMTAwMTAwLTMxLTAwMDBQQU07ZXh0PXBhbTttaW1lPTtdUG9ydGFibGUgYXJiaXRyYXJ5IG1hcCBpbWFnZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIE5ldHBibSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAlzdHJpbmcJUDEJW2ZpZD0wMDAxMDAxMDAtMzEtMDAwMFBCTTtleHQ9cGJtO21pbWU9O11Qb3J0YWJsZSBiaXRtYXAgaW1hZ2UgZmlsZSwgYXNjaWkNCg0KIyBNYWdpYyBJRCBmb3IgTmV0cGJtIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0zMSBieSBDYXJsDQowCXN0cmluZwlQNAlbZmlkPTAwMDEwMDEwMC0zMS0wMDAwUEJNO2V4dD1wYm07bWltZT07XVBvcnRhYmxlIGJpdG1hcCBpbWFnZSBmaWxlLCBiaW5hcnkNCg0KIyBNYWdpYyBJRCBmb3IgUEMtUGFpbnRicnVzaCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAliZXNob3J0CTB4MEEwMAlbZmlkPTAwMDAwMTI1Ny0zMS0wMDAwUENYO2V4dD1wY3g7bWltZT07XVBDLVBhaW50YnJ1c2ggaW1hZ2UgZmlsZSwgdmVyc2lvbiAyLjUNCiYyCWJ5dGUJMQkNCg0KIyBNYWdpYyBJRCBmb3IgUEMtUGFpbnRicnVzaCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAliZXNob3J0CTB4MEEwMglbZmlkPTAwMDAwMTI1Ny0zMS0wMDAwUENYO2V4dD1wY3g7bWltZT07XVBDLVBhaW50YnJ1c2ggaW1hZ2UgZmlsZSwgdmVyc2lvbiAyLjgNCiYyCWJ5dGUJMQkNCg0KIyBNYWdpYyBJRCBmb3IgUEMtUGFpbnRicnVzaCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAliZXNob3J0CTB4MEEwMwlbZmlkPTAwMDAwMTI1Ny0zMS0wMDAwUENYO2V4dD1wY3g7bWltZT07XVBDLVBhaW50YnJ1c2ggaW1hZ2UgZmlsZSwgdmVyc2lvbiAyLjgNCiYyCWJ5dGUJMQkNCg0KIyBNYWdpYyBJRCBmb3IgUEMtUGFpbnRicnVzaCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAliZXNob3J0CTB4MEEwNAlbZmlkPTAwMDAwMTI1Ny0zMS0wMDAwUENYO2V4dD1wY3g7bWltZT07XVBDLVBhaW50YnJ1c2ggZm9yIHdpbmRvd3MgaW1hZ2UgZmlsZQ0KJjIJYnl0ZQkxCQ0KDQojIE1hZ2ljIElEIGZvciBQQy1QYWludGJydXNoIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0zMSBieSBDYXJsDQowCWJlc2hvcnQJMHgwQTA1CVtmaWQ9MDAwMDAxMjU3LTMxLTAwMDBQQ1g7ZXh0PXBjeDttaW1lPTtdUEMtUGFpbnRicnVzaCBpbWFnZSBmaWxlLCB2ZXJzaW9uIDMuMA0KJjIJYnl0ZQkxCQ0KDQojIE1hZ2ljIElEIGZvciBOZXRwYm0gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTMxIGJ5IENhcmwNCjAJc3RyaW5nCVAyCVtmaWQ9MDAwMTAwMTAwLTMxLTAwMDBQR007ZXh0PXBnbTttaW1lPTtdUG9ydGFibGUgZ3JheSBtYXAgaW1hZ2UgZmlsZSwgYXNjaWkNCg0KIyBNYWdpYyBJRCBmb3IgTmV0cGJtIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0zMSBieSBDYXJsDQowCXN0cmluZwlQNQlbZmlkPTAwMDEwMDEwMC0zMS0wMDAwUEdNO2V4dD1wZ207bWltZT07XVBvcnRhYmxlIGdyYXkgbWFwIGltYWdlIGZpbGUsIGJpbmFyeQ0KDQojIE1hZ2ljIElEIGZvciBQQyBQYWludCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDggYnkgQ2FybA0KMAlsZXNob3J0CTB4MTIzNAlbZmlkPTAwMDAwMTMxOC0zMS0wMDAwUElDO2V4dD1waWM7bWltZT07XVBpY3RvciBQQyBQYWludCBpbWFnZSBmaWxlDQomMTEJYnl0ZQkweEZGCQ0KJjEwCWJ5dGUJMHgwMgkNCj4yCWxlc2hvcnQJeAlbcmVzPSVkDQo+NAlsZXNob3J0CXgJeCVkXQ0KDQojIE1hZ2ljIElEIGZvciBQQyBQYWludCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDggYnkgQ2FybA0KMAlsZXNob3J0CTB4MTIzNAlbZmlkPTAwMDAwMTMxOC0zMS0wMDAwUElDO2V4dD1waWM7bWltZT07XVBpY3RvciBQQyBQYWludCBpbWFnZSBmaWxlDQomMTEJYnl0ZQkweEZGCQ0KJjEwCWJ5dGUJMHgwOAkNCj4yCWxlc2hvcnQJeAlbcmVzPSVkDQo+NAlsZXNob3J0CXgJeCVkXQ0KDQojIE1hZ2ljIElEIGZvciBQQyBQYWludCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDggYnkgQ2FybA0KMAlsZXNob3J0CTB4MTIzNAlbZmlkPTAwMDAwMTMxOC0zMS0wMDAwUElDO2V4dD1waWM7bWltZT07XVBpY3RvciBQQyBQYWludCBpbWFnZSBmaWxlDQomMTEJYnl0ZQkweEZGCQ0KJjEwCWJ5dGUJMHgzMQkNCj4yCWxlc2hvcnQJeAlbcmVzPSVkDQo+NAlsZXNob3J0CXgJeCVkXQ0KDQojIE1hZ2ljIElEIGZvciBTb2Z0aW1hZ2UgM0QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTA4IGJ5IENhcmwNCjAJYmVsb25nCTB4NTM4MGY2MzQJW2ZpZD0wMDAwMDEzMjEtMzEtMDAwMFBJQztleHQ9cGljO21pbWU9O11Tb2Z0aW1hZ2UgM0QgaW1hZ2UgZmlsZQ0KJjg4CXN0cmluZwlQSUNUCQ0KPjgJc3RyaW5nCT5cXHgwMAlbdGl0bGU9JS44MHNdDQo+OTIJYmVzaG9ydAl4CVtyZXM9JWQNCj45NAliZXNob3J0CXgJeCVkXQ0KDQojIE1hZ2ljIElEIGZvciBCaW8tcmFkIG1pY3Jvc2NvcGUgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTA4IGJ5IENhcmwNCjU0CWxlc2hvcnQJMTIzNDUJW2ZpZD0wMDAwMDEzMjItMzEtMDAwMFBJQztleHQ9cGljO21pbWU9O11CaW8tcmFkIGNvbmZvY2FsIG1pY3Jvc2NvcGUgaW1hZ2UgZmlsZQ0KJjE2CWxlc2hvcnQJMAkNCj4wCWxlc2hvcnQJPjAJW3Jlcz0lZA0KPjIJbGVzaG9ydAk+MAl4JWRdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDYgYnkgQ2FybA0KMAlzdHJpbmcJXFx4ODlQTkdcXHgwZFxceDBhXFx4MWFcXHgwYQlbZmlkPTAwMDAwMDAwMS0zMS0wMDE1OTQ4O2V4dD1wbmc7bWltZT1pbWFnZS9wbmc7XVBvcnRhYmxlIE5ldHdvcmsgR3JhcGhpYyBmaWxlDQomMTIJc3RyaW5nCUlIRFIJDQo+MTYJYmVsb25nCT4wCVtyZXM9JWQNCj4yMAliZWxvbmcJPjAJeCVkDQo+MjQJYnl0ZQl4CXglZGJwcF0NCg0KIyBNYWdpYyBJRCBmb3IgTmV0cGJtIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0zMSBieSBDYXJsDQowCXN0cmluZwlQMwlbZmlkPTAwMDEwMDEwMC0zMS0wMDAwUFBNO2V4dD1wcG07bWltZT07XVBvcnRhYmxlIHBpeGVsIG1hcCBpbWFnZSBmaWxlLCBhc2NpaQ0KDQojIE1hZ2ljIElEIGZvciBOZXRwYm0gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTMxIGJ5IENhcmwNCjAJc3RyaW5nCVA2CVtmaWQ9MDAwMTAwMTAwLTMxLTAwMDBQUE07ZXh0PXBwbTttaW1lPTtdUG9ydGFibGUgcGl4ZWwgbWFwIGltYWdlIGZpbGUsIGJpbmFyeQ0KDQojIE1hZ2ljIElEIGZvciBQaG90b3Nob3AgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjAJc3RyaW5nCThCUFNcXHgwMFxceDAxCVtmaWQ9MDAwMDAxMDAzLTMxLTAwMDBQU0Q7ZXh0PXBzZDttaW1lPTtdQWRvYmUgUGhvdG9zaG9wIGltYWdlIGZpbGUNCj4xOAliZWxvbmcJPjAJW3Jlcz0lZHgNCj4xNAliZWxvbmcJPjAJJWRdDQoNCiMgTWFnaWMgSUQgZm9yIFBhaW50IHNob3AgcHJvIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMSBieSBDYXJsDQowCXN0cmluZwlQYWludCBTaG9wIFBybyBJbWFnZSBGaWxlXFx4MGFcXHgxYQlbZmlkPTAwMDAwMTMxMC0zMS0wMDAwUFNQO2V4dD1wc3A7bWltZT07XVBhaW50c2hvcCBwcm8gaW1hZ2UgZmlsZQ0KPjUwCWxlbG9uZwk+MAlbcmVzPSVkeA0KPjU0CWxlbG9uZwk+MAklZA0KPjY5CWxlc2hvcnQJPjAJeCVkYnBwXQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJYmVsb25nCTB4NTlhNjZhOTUJW2ZpZD0wMDAwMDEwMTEtMzEtMDAwMFJBUztleHQ9cmFzO21pbWU9O11TdW4gcmFzdGVyIGltYWdlDQo+NAliZWxvbmcJPjAJW3Jlcz0lZHgNCj44CWJlbG9uZwk+MAklZA0KPjEyCWJlbG9uZwk+MAl4JWRicHBdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAlzdHJpbmcJXFwweDAxXFx4REEJW2ZpZD0wMDAwMDEwMDQtMzEtMDAwMFJHQjtleHQ9cmdiO21pbWU9O11TR0kgSW1hZ2UgZmlsZQ0KPjI0CXN0cmluZwk+XFx4MDAJW3RpdGxlPSUuODBzXQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTMxIGJ5IENhcmwNCjAJc3RyaW5nCVxceDAxXFx4REFcXHgwMFxceDAxCVtmaWQ9MDAwMDAxMDA0LTMxLTAwMDBSR0I7ZXh0PXJnYjttaW1lPTtdU0dJIEltYWdlIGZpbGUNCiYxMAliZXNob3J0CTEJDQo+NgliZXNob3J0CXgJW3Jlcz0lZHgNCj44CWJlc2hvcnQJeAklZHg4YnBwXQ0KPjI0CXN0cmluZwk+XFx4MAlbdGl0bGU9JS44MHNdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDFcXHhEQVxceDAwXFx4MDEJW2ZpZD0wMDAwMDEwMDQtMzEtMDAwMFJHQjtleHQ9cmdiO21pbWU9O11TR0kgSW1hZ2UgZmlsZQ0KJjEwCWJlc2hvcnQJMwkNCj42CWJlc2hvcnQJeAlbcmVzPSVkeA0KPjgJYmVzaG9ydAl4CSVkeDI0YnBwXQ0KPjI0CXN0cmluZwk+XFx4MAlbdGl0bGU9JS44MHNdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDFcXHhEQVxceDAwXFx4MDEJW2ZpZD0wMDAwMDEwMDQtMzEtMDAwMFJHQjtleHQ9cmdiO21pbWU9O11TR0kgSW1hZ2UgZmlsZQ0KJjEwCWJlc2hvcnQJNAkNCj42CWJlc2hvcnQJeAlbcmVzPSVkeA0KPjgJYmVzaG9ydAl4CSVkeDI0YnBwXQ0KPjI0CXN0cmluZwk+XFx4MAlbdGl0bGU9JS44MHNdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMzEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDFcXHhEQVxceDAxXFx4MDEJW2ZpZD0wMDAwMDEwMDQtMzEtMDAwMFJHQjtleHQ9cmdiO21pbWU9O11TR0kgSW1hZ2UgZmlsZSwgY29tcHJlc3NlZA0KJjEwCWJlc2hvcnQJMQkNCj42CWJlc2hvcnQJeAlbcmVzPSVkeA0KPjgJYmVzaG9ydAl4CSVkeDhicHBdDQo+MjQJc3RyaW5nCT5cXHgwCVt0aXRsZT0lLjgwc10NCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0zMSBieSBDYXJsDQowCXN0cmluZwlcXHgwMVxceERBXFx4MDFcXHgwMQlbZmlkPTAwMDAwMTAwNC0zMS0wMDAwUkdCO2V4dD1yZ2I7bWltZT07XVNHSSBJbWFnZSBmaWxlLCBjb21wcmVzc2VkDQomMTAJYmVzaG9ydAkzCQ0KPjYJYmVzaG9ydAl4CVtyZXM9JWR4DQo+OAliZXNob3J0CXgJJWR4MjRicHBdDQo+MjQJc3RyaW5nCT5cXHgwCVt0aXRsZT0lLjgwc10NCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0zMSBieSBDYXJsDQowCXN0cmluZwlcXHgwMVxceERBXFx4MDFcXHgwMQlbZmlkPTAwMDAwMTAwNC0zMS0wMDAwUkdCO2V4dD1yZ2I7bWltZT07XVNHSSBJbWFnZSBmaWxlLCBjb21wcmVzc2VkDQomMTAJYmVzaG9ydAk0CQ0KPjYJYmVzaG9ydAl4CVtyZXM9JWR4DQo+OAliZXNob3J0CXgJJWR4MjRicHBdDQo+MjQJc3RyaW5nCT5cXHgwCVt0aXRsZT0lLjgwc10NCg0KIyBNYWdpYyBJRCBmb3IgVHVyYm8gU2lsdmVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMyBieSBDYXJsDQowCXN0cmluZwlGT1JNCVtmaWQ9MDAwMDAxMjUyLTMxLTAwMFJHQjg7ZXh0PXJnYjgscmdiO21pbWU9O11UdXJibyBTaWx2ZXIgMjQtYml0IFJHQiBpbWFnZSBmaWxlDQomOAlzdHJpbmcJUkdCOAkNCg0KIyBNYWdpYyBJRCBmb3IgVHVyYm8gU2lsdmVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMyBieSBDYXJsDQowCXN0cmluZwlGT1JNCVtmaWQ9MDAwMDAxMjUyLTMxLTAwMFJHQk47ZXh0PXJnYm4scmdiO21pbWU9O11UdXJibyBTaWx2ZXIgMTItYml0IFJHQiBpbWFnZSBmaWxlDQomOAlzdHJpbmcJUkdCTgkNCg0KIyBNYWdpYyBJRCBmb3IgQ29sb1JJWCBWR0EgUGFpbnQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTA4IGJ5IENhcmwNCjAJc3RyaW5nCVJJWDMJW2ZpZD0wMDAwMDEzMjAtMzEtMDAwMFNDWjtleHQ9c2N6O21pbWU9O11Db2xvclJJWCBWR0EgUGFpbnQgaW1hZ2UgZmlsZQ0KPjQJbGVzaG9ydAl4CVtyZXM9JWQNCj42CWxlc2hvcnQJeAl4JWRdDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTMgYnkgQnJ5YW4gSGVuZGVyc29uDQowCXN0cmluZwlcXDExN1xcMDcyCVtmaWQ9MDAwMDAwMDAwLTMxLTAwMDBTSVI7ZXh0PXNpcjttaW1lPTtdU29saXRhaXJlIGltYWdlIHJlY29yZGVyIGltYWdlIGZpbGUsIE1HSSB0eXBlIDExDQomNAlzdHJpbmcJXFwwMTMJDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTMgYnkgQnJ5YW4gSGVuZGVyc29uDQowCXN0cmluZwlcXDExN1xcMDcyCVtmaWQ9MDAwMDAwMDAwLTMxLTAwMDBTSVI7ZXh0PXNpcjttaW1lPTtdU29saXRhaXJlIGltYWdlIHJlY29yZGVyIGltYWdlIGZpbGUsIE1HSSB0eXBlIDE3DQomNAlzdHJpbmcJXFwwMjEJDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KWjE4CXN0cmluZwlUUlVFVklTSU9OLVhGSUxFCVtmaWQ9MDAwMDAxMzA2LTMxLTAwMDBUR0E7ZXh0PXRnYTttaW1lPTtdVHJ1ZXZpc2lvbiBUYXJnYSBpbWFnZSBmaWxlDQo+MTIJbGVzaG9ydAl4CVtyZXM9JWR4DQo+MTQJbGVzaG9ydAl4CSVkDQo+MTYJYnl0ZQl4CXglZGJwcF0NCg0KIyBNYWdpYyBJRCBmb3IgR3JhcGhpY3MgV29ya3Nob3AgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjAJc3RyaW5nCVRITkwJW2ZpZD0wMDAwMDEyNTgtMzEtMDAwMFRITjtleHQ9dGhuO21pbWU9O11HcmFwaGljcyB3b3Jrc2hvcCB0aHVtYm5haWwgaW1hZ2UgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTMxIGJ5IENhcmwNCjAJc3RyaW5nCUlJCVtmaWQ9MDAwMDAxMDAzLTMxLTAwMDBUSUY7ZXh0PXRpZix0aWZmLGRuZzttaW1lPWltYWdlL3RpZmY7XVRhZ2dlZCBpbWFnZSBmaWxlIGZvcm1hdCBpbWFnZSBmaWxlLCBsaXR0bGUtZW5kaWFuDQomMglsZXNob3J0CTQyCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTMxIGJ5IENhcmwNCjAJc3RyaW5nCU1NCVtmaWQ9MDAwMDAxMDAzLTMxLTAwMDBUSUY7ZXh0PXRpZix0aWZmLGRuZzttaW1lPWltYWdlL3RpZmY7XVRhZ2dlZCBpbWFnZSBmaWxlIGZvcm1hdCBpbWFnZSBmaWxlLCBiaWctZW5kaWFuDQomMglsZXNob3J0CQkNCg0KIyBNYWdpYyBJRCBmb3IgVklDQVIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjAJc3RyaW5nCUxCTFNJWkU9CVtmaWQ9MDAwMDAxMDIyLTMxLTAwMDBWSUM7ZXh0PXZpYyx2aWNhcjttaW1lPTtdVmljYXIgaW1hZ2UgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBLaG9yb3MgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjAJc3RyaW5nCVxceEFCXFx4MDFcXHgwMVxceDAzCVtmaWQ9MDAwMDAxMzA4LTMxLTAwMDBWSUY7ZXh0PXZpZix2aWZmO21pbWU9O11LaG9yb3MgVmlzdWFsaXphdGlvbi9JbWFnZSBGaWxlIEZvcm1hdCwgdmVyc2lvbiAxLjMNCiY0CWJ5dGUJMgkNCj41MjAJYmVsb25nCT4wCVtyZXM9JWR4DQo+NTI0CWJlbG9uZwk+MAklZF0NCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMiBieSBDYXJsDQowCXN0cmluZwlGSUFTQ08JW2ZpZD0wMDAxMDAxMDctMzEtMDAwMFdGQTtleHQ9d2ZhO21pbWU9O11GcmFjdGFsIEltYWdlIEFuZCBTZXF1ZW5jZSBDb2RlYyBpbWFnZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFgtV2luZG93cyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDEgYnkgQ2FybA0KMAlzdHJpbmcJLyogWFBNICovCVtmaWQ9MDAwMDAxMDIwLTMxLTAwMDBYUE07ZXh0PXhwbTttaW1lPTtdWC1XaW5kb3dzIHBpeGVsIG1hcCBpbWFnZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFhWIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wMSBieSBDYXJsDQowCXN0cmluZwlQN1xcIDMzMglbZmlkPTAwMDEwMDEwMy0zMS0wMDAwMFhWO2V4dD14djttaW1lPTtdWFYgVGh1bWJuYWlsIGltYWdlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgWFdpbmRvd3MgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAyIGJ5IENhcmwNCjAJYmVsb25nCTB4NDAJW2ZpZD0wMDAwMDEwMjAtMzEtMDAwMFhXRDtleHQ9eHdkO21pbWU9O11YMTAgWFdpbmRvd3MgZHVtcCBpbWFnZSBmaWxlDQomNAliZWxvbmcJMHgwNgkNCj4yNAliZWxvbmcJeAlbcmVzPSVkDQo+MjgJYmVsb25nCXgJeCVkXQ0KDQojIE1hZ2ljIElEIGZvciBYV2luZG93cyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDIgYnkgQ2FybA0KMAlsZWxvbmcJMHg0MAlbZmlkPTAwMDAwMTAyMC0zMS0wMDAwWFdEO2V4dD14d2Q7bWltZT07XVgxMCBYV2luZG93cyBkdW1wIGltYWdlIGZpbGUNCiY0CWxlbG9uZwkweDA2CQ0KPjI0CWxlbG9uZwl4CVtyZXM9JWQNCj4yOAlsZWxvbmcJeAl4JWRdDQoNCiMgTWFnaWMgSUQgZm9yIFByb3ZlY3RvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDggYnkgQ2FybA0KMAlzdHJpbmcJRk9STQlbZmlkPTAwMDAwMTMxOS0zMi0wMDBEUjJEO2V4dD1kcjJkO21pbWU9O11Qcm92ZWN0b3IgMkQgaW1hZ2UgZmlsZQ0KJjgJc3RyaW5nCURSMkQJDQoNCiMgTWFnaWMgSUQgZm9yIFhGaWcsIFdpbkZpZywgakZpZyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJI0ZJRwlbZmlkPTAwMDEwMDAxMS0zMi0wMDAwRklHO2V4dD1maWc7bWltZT07XUZhY2lsaXR5IGZvciBJbnRlcmFjdGl2ZSBHZW5lcmF0aW9uIGZpbGUNCj41CXN0cmluZwl4CSwgdmVyc2lvbiAlLjFzLg0KPjcJc3RyaW5nCXgJJS4xcw0KDQojIE1hZ2ljIElEIGZvciBMb3R1cyAxLTItMyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMDIgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDFcXHgwMFxceDAwXFx4MDBcXHgwMVxceDAwXFx4MDhcXHgwMFxceDQ0CVtmaWQ9MDAwMDAxMDA5LTMyLTAwMDBQSUM7ZXh0PXBpYzttaW1lPTtdTG90dXMgMS0yLTMgaW1hZ2UgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBBdXRvY2FkIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlBdXRvQ0FEIFNsaWRlCVtmaWQ9MDAwMDAxMjU0LTMyLTAwMDBTTEQ7ZXh0PXNsZDttaW1lPTtdQXV0b2NhZCBzbGlkZSBpbWFnZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFdvcmRwZXJmZWN0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlcXHhmZldQQwlbZmlkPTAwMDAwMTAwOC0zMi0wMDAwV1BHO2V4dD13cGc7bWltZT07XVdvcmRwZXJmZWN0IEdyYXBoaWNzIHZlY3RvcnMNCiY4CWJ5dGUJMQkNCiY5CWJ5dGUJMHgxNgkNCj4xMAlieXRlCXgJLCB2ZXJzaW9uICVkLg0KPjExCWJ5dGUJeAklZA0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCUZPUk0JW2ZpZD0wMDAxMDAwMTktMzMtMDAwQU1GRjtleHQ9YW1mZjttaW1lPTtdQW1pZ2EgbWV0YWZpbGUNCiY4CXN0cmluZwlBTUZGCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJbGVsb25nCTB4OUFDNkNERDcJW2ZpZD0wMDAwMDEwMDMtMzMtMDAwMEFQTTtleHQ9YXBtO21pbWU9O11BbGR1cyBwbGFjZWFibGUgV2luZG93cyBtZXRhZmlsZQ0KJjQJbGVzaG9ydAkwCQ0KDQojIE1hZ2ljIElEIGZvciBDb3JlbERSQVcgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCVJJRkYJW2ZpZD0wMDAwMDEwMDgtMzMtMDAwMENEUjtleHQ9Y2RyO21pbWU9O11Db3JlbGRyYXcgIGxpdHRsZS1lbmRpYW4gbWV0YWZpbGUNCiY4CXN0cmluZwlDRFIJDQoNCiMgTWFnaWMgSUQgZm9yIENvcmVsRFJBVyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJUklGWAlbZmlkPTAwMDAwMTAwOC0zMy0wMDAwQ0RSO2V4dD1jZHI7bWltZT07XUNvcmVsZHJhdyAgYmlnLWVuZGlhbiBtZXRhZmlsZQ0KJjgJc3RyaW5nCUNEUgkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wOCBieSBDYXJsDQowCWJlc2hvcnQmMHhGRjIwCTB4MDAyMAlbZmlkPTAwMDAwMDAwMS0zMy0wMDA4NjMyO2V4dD1jZ207bWltZT1pbWFnZS9jZ207XUNvbXB1dGVyIGdyYXBoaWNzIG1ldGFmaWxlLCBiaW5hcnkgZW5jb2RlZA0KJloweDAyCWJlc2hvcnQmMHhGRjQwCTB4MDA0MAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0wOCBieSBDYXJsDQowCXN0cmluZwlCRUdNRglbZmlkPTAwMDAwMDAwMS0zMy0wMDA4NjMyO2V4dD1jZ207bWltZT1pbWFnZS9jZ207XUNvbXB1dGVyIGdyYXBoaWNzIG1ldGFmaWxlLCBhc2NpaSBlbmNvZGVkDQoNCiMgTWFnaWMgSUQgZm9yIENvcmVsRFJBVyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJUklGRglbZmlkPTAwMDAwMTAwOC0zMy0wMDAwQ01YO2V4dD1jbXg7bWltZT07XUNvcmVsIGxpdHRsZS1lbmRpYW4gbWV0YWZpbGUNCiY4CXN0cmluZwlDTVgxCQ0KDQojIE1hZ2ljIElEIGZvciBDb3JlbERSQVcgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCVJJRlgJW2ZpZD0wMDAwMDEwMDgtMzMtMDAwMENNWDtleHQ9Y214O21pbWU9O11Db3JlbCBiaWctZW5kaWFuIG1ldGFmaWxlDQomOAlzdHJpbmcJQ01YMQkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCWxlbG9uZwkweDAwMDAwMDAxCVtmaWQ9MDAwMDAxMDAxLTMzLTAwMDBFTUY7ZXh0PWVtZjttaW1lPTtdTWljcm9zb2Z0IFdpbmRvd3MgRW5oYW5jZWQgbWV0YWZpbGUNCiY0MAlsZWxvbmcJMHg0NjRENDUyMAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwklIVBTLUFkb2JlLTIuMFxcIEVQU0YtMS4yCVtmaWQ9MDAwMDAxMDAzLTMzLTAwMEVQU0Y7ZXh0PWVwc2Y7bWltZT1hcHBsaWNhdGlvbi9wb3N0c2NyaXB0O11BZG9iZSBFbmNhcHN1bGF0ZWQgUG9zdHNjcmlwdCBMZXZlbCAyLCB2ZXJzaW9uIDEuMg0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCSUhUFMtQWRvYmUtMi4wXFwgRVBTRi0yLjAJW2ZpZD0wMDAwMDEwMDMtMzMtMDAwRVBTRjtleHQ9ZXBzZjttaW1lPWFwcGxpY2F0aW9uL3Bvc3RzY3JpcHQ7XUFkb2JlIEVuY2Fwc3VsYXRlZCBQb3N0c2NyaXB0IExldmVsIDIsIHZlcnNpb24gMi4wDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJJSFQUy1BZG9iZS0zLjBcXCBFUFNGLTMuMAlbZmlkPTAwMDAwMTAwMy0zMy0wMDBFUFNGO2V4dD1lcHNmO21pbWU9YXBwbGljYXRpb24vcG9zdHNjcmlwdDtdQWRvYmUgRW5jYXBzdWxhdGVkIFBvc3RzY3JpcHQgTGV2ZWwgMywgdmVyc2lvbiAzLjANCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlcXHhDNVxceEQwXFx4RDNcXHhDNglbZmlkPTAwMDAwMTAwMy0zMy0wMDBFUFNGO2V4dD1lcHNmLGFpO21pbWU9YXBwbGljYXRpb24vcG9zdHNjcmlwdDtdQWRvYmUgRW5jYXBzdWxhdGVkIFBvc3RzY3JpcHQsIHZlcnNpb24gMy4wLCBiaW5hcnkNCg0KIyBNYWdpYyBJRCBmb3IgR0VNIFBhaW50IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0zMSBieSBDYXJsDQowCWJlc2hvcnQJMHhGRkZGCVtmaWQ9MDAwMDAxMjczLTMzLTAwMDBHRU07ZXh0PWdlbTttaW1lPTtdR2VtRE9TIE1vdG9yb2xhIE1ldGFmaWxlLCB2ZXJzaW9uIDEuMDENCiY0CWJlc2hvcnQJMTAxCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IGh0dHA6Ly93d3cuc2Vhc2lwLmluZm8vR2VtL2ZmX2dlbS5odG1sDQowCWxlc2hvcnQJMHhGRkZGCVtmaWQ9MDAwMDAxMjczLTMzLTAwMDBHRU07ZXh0PWdlbTttaW1lPTtdR2VtRE9TIE1ldGFmaWxlDQomNAlsZXNob3J0CTAJDQoNCiMgTWFnaWMgSUQgZm9yIEFydGxpbmUgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IGh0dHA6Ly93d3cuc2Vhc2lwLmluZm8vR2VtL2ZmX2dlbS5odG1sDQowCWxlc2hvcnQJMHhGRkZGCVtmaWQ9MDAwMDAxMjczLTMzLTAwMDBHRU07ZXh0PWdlbTttaW1lPTtdR2VtRE9TIEludGVsIE1ldGFmaWxlLCB2ZXJzaW9uIDQuMDANCiY0CWxlc2hvcnQJNDAwCQ0KDQojIE1hZ2ljIElEIGZvciBEZXNrcHJlc3MgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IGh0dHA6Ly93d3cuc2Vhc2lwLmluZm8vR2VtL2ZmX2dlbS5odG1sDQowCWxlc2hvcnQJMHhGRkZGCVtmaWQ9MDAwMDAxMjczLTMzLTAwMDBHRU07ZXh0PWdlbTttaW1lPTtdR2VtRE9TIEludGVsIE1ldGFmaWxlLCB2ZXJzaW9uIDMuMTANCiY0CWxlc2hvcnQJMzEwCQ0KDQojIE1hZ2ljIElEIGZvciBHRU0gUGFpbnQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IGh0dHA6Ly93d3cuc2Vhc2lwLmluZm8vR2VtL2ZmX2dlbS5odG1sDQowCWxlc2hvcnQJMHhGRkZGCVtmaWQ9MDAwMDAxMjczLTMzLTAwMDBHRU07ZXh0PWdlbTttaW1lPTtdR2VtRE9TIEludGVsIE1ldGFmaWxlLCB2ZXJzaW9uIDEuMDENCiY0CWxlc2hvcnQJMTAxCQ0KDQojIE1hZ2ljIElEIGZvciBRdWlja2RyYXcgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTAxIGJ5IENhcmwNCjB4MjBBCWJlbG9uZwkweDAwMTEwMkZGCVtmaWQ9MDAwMDAxMDAyLTMzLTAwMDBQQ1Q7ZXh0PXBjdDttaW1lPTtdTWFjaW50b3NoIFF1aWNrZHJhdyBtZXRhZmlsZSBcJ1BJQ1RcJywgdmVyc2lvbiAyLjANCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCWxlc2hvcnQJMHgwMDAxCVtmaWQ9MDAwMDAxMDAxLTMzLTAwMDBXTUY7ZXh0PXdtZjttaW1lPTtdTWljcm9zb2Z0IFdpbmRvd3MgbWV0YWZpbGUNCiYyCWxlc2hvcnQJOQkNCg0KIyBNYWdpYyBJRCBmb3IgQ2luZW1hIDREIFZlcnNpb24gNS54IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wNiBieSBDYXJsDQowCXN0cmluZwlNQzUwCVtmaWQ9MDAwMDAxMjU1LTQwLTAwMDAwMDA7ZXh0PTttaW1lPTtdTWF4b24gQ2luZW1hIDREIHZlcnNpb24gNSAzRCBkYXRhDQoNCiMgTWFnaWMgSUQgZm9yIFF1aWNrZHJhdyAzRCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDYgYnkgQ2FybA0KMAlzdHJpbmcJM0RNRglbZmlkPTAwMDAwMTAwMi00MC0wMDAzRE1GO2V4dD0zZG1mO21pbWU9O11BcHBsZSBRdWlja2RyYXcgM0QgbWV0YWZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgUmhpbm8gM2QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCTNEIEdlb21ldHJ5IEZpbGUgRm9ybWF0CVtmaWQ9MDAwMDAxMzMwLTQwLTAwMDNETUY7ZXh0PTNkbWY7bWltZT07XVJoaW5vM2QgLyBPcGVuTnVyYnMgM2QgbW9kZWwNCg0KIyBNYWdpYyBJRCBmb3IgUXVpY2tkcmF3IDNEIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMSBieSBDYXJsDQowCXN0cmluZwkzRE1ldGFmaWxlCVtmaWQ9MDAwMDAxMDAyLTQwLTAwMDNETUY7ZXh0PTNkbWYsYTNkO21pbWU9O11BcHBsZSBRdWlja2RyYXcgM0QgbWV0YWZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQUMzZCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTIgYnkgQ2FybA0KMAlzdHJpbmcJQUMzRAlbZmlkPTAwMDAwMDAwMC00MC0wMDAwMEFDO2V4dD1hYzttaW1lPTtdQWMzZCAzZCBtb2RlbA0KDQojIE1hZ2ljIElEIGZvciAzZFN0dWRpbyBNYXggZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCSozRFNNQVhfQVNDSUlFWFBPUlQJW2ZpZD0wMDAwMDEzMjYtNDAtMDAwMEFTRTtleHQ9YXNlO21pbWU9O10zZCBzdHVkaW8gbWF4IGFzY2lpIGV4cG9ydCAzRCBtb2RlbA0KDQojIE1hZ2ljIElEIGZvciBDYWxpZ2FyaSBUcnVlc3BhY2UgTW9kZWxlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDYgYnkgQ2FybA0KMAlzdHJpbmcJQ2FsaWdhcmlcXCBWCVtmaWQ9MDAwMDAxMjU2LTQwLTAwMDBDT0I7ZXh0PWNvYixzY247bWltZT07XUNhbGlnYXJpIFRydWVzcGFjZTIgM0QgbW9kZWwNCg0KIyBNYWdpYyBJRCBmb3IgVGFjaHlvbiBwYXJhbGxlbCByYXl0cmFjZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCUJFR0lOX1NDRU5FCVtmaWQ9MDAwMTAwMTE2LTQwLTAwMDBEQVQ7ZXh0PWRhdDttaW1lPTtdVGFjaHlvbiByYXktdHJhY2VyIDNkIG1vZGVsDQoNCiMgTWFnaWMgSUQgZm9yIEF1dG9jYWQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCUFDMTAJW2ZpZD0wMDAwMDEyNTQtNDAtMDAwMERXRztleHQ9ZHdnO21pbWU9aW1hZ2Uvdm5kLmR3ZztdQXV0b2NhZCBkcmF3aW5nIGZvcm1hdCAzZCBtb2RlbA0KDQojIE1hZ2ljIElEIGZvciBBdXRvY2FkIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlBdXRvQ0FEIEJpbmFyeSBEWEYJW2ZpZD0wMDAwMDEyNTQtNDAtMDAwMERYRjtleHQ9ZHhmO21pbWU9aW1hZ2Uvdm5kLmR4ZjtdQXV0b2NhZCBkcmF3aW5nIGludGVyY2hhbmdlIDNkIG1vZGVsLCBiaW5hcnkNCg0KIyBNYWdpYyBJRCBmb3IgTXVsdGlnZW4gY3JlYXRvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTIgYnkgQ2FybA0KMAliZXNob3J0CTEJW2ZpZD0wMDAwMDEzMzItNDAtMDAwMEZMVDtleHQ9Zmx0O21pbWU9O11PcGVuZmxpZ2h0IHNjZW5lIGRlc2NyaXB0aW9uIDNkIG1vZGVsDQomNAlzdHJpbmcJZGIJDQoNCiMgTWFnaWMgSUQgZm9yIFZpZGVvc2NhcGUgM0QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCUdPVVIJW2ZpZD0wMDAwMDEzMjgtNDAtMDAwMEdFTztleHQ9Z2VvO21pbWU9O11WaWRlb3NjYXBlIDNkIG1vZGVsIHdpdGggY29sb3JlZCB2ZXJ0aWNlcw0KDQojIE1hZ2ljIElEIGZvciBWaWRlb3NjYXBlIDNELCBCbGVuZGVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwkzREcxCVtmaWQ9MDAwMDAxMzI4LTQwLTAwMDBHRU87ZXh0PWdlbzttaW1lPTtdVmlkZW9zY2FwZSAzZCBtb2RlbCB3aXRoIGNvbG9yZWQgZmFjZXMNCg0KIyBNYWdpYyBJRCBmb3IgVmlkZW9zY2FwZSAzRCwgQmxlbmRlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTIgYnkgQ2FybA0KMAlzdHJpbmcJM0RHMglbZmlkPTAwMDAwMTMyOC00MC0wMDAwR0VPO2V4dD1nZW87bWltZT07XVZpZGVvc2NhcGUgM2QgbW9kZWwgbGlnaHQgc291cmNlDQoNCiMgTWFnaWMgSUQgZm9yIFZpZGVvc2NhcGUgM0QsIEJsZW5kZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCTNERzMJW2ZpZD0wMDAwMDEzMjgtNDAtMDAwMEdFTztleHQ9Z2VvO21pbWU9O11WaWRlb3NjYXBlIDNkIG1vZGVsIHdpdGggZ291cmF1ZCBjdXJ2ZXMNCg0KIyBNYWdpYyBJRCBmb3IgU29mdGltYWdlIDREIENyZWF0aXZlIGVudmlyb25tZW50IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlIUkNIOglbZmlkPTAwMDAwMTMyMS00MC0wMDAwSFJDO2V4dD1ocmM7bWltZT07XVNvZnRpbWFnZSA0ZCBtb2RlbCwgYXNjaWkgZW5jb2RlZA0KDQojIE1hZ2ljIElEIGZvciBPcGVuIEludmVudG9yIFRvb2xraXQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA2IGJ5IENhcmwNCjAJc3RyaW5nCSNJbnZlbnRvcglbZmlkPTAwMDAwMTAwNC00MC0wMDAwMElWO2V4dD1pdjttaW1lPTtdT3BlbiBJbnZlbnRvciAzZCBtb2RlbA0KPjExCXN0cmluZwl4CSwgdmVyc2lvbiAlLjFzLg0KPjEzCXN0cmluZwl4CSUuMXMNCj4xNQlzdHJpbmcJYmluYXJ5CSwgYmluYXJ5IGVuY29kZWQNCj4xNQlzdHJpbmcJYXNjaWkJLCBhc2NpaSBlbmNvZGVkDQoNCiMgTWFnaWMgSUQgZm9yIEdlb212aWV3IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlMSVNUCVtmaWQ9MDAwMTAwMTE4LTQwLTAwMExJU1Q7ZXh0PWxpc3Q7bWltZT07XUdlb212aWV3IGxpc3Qgb2YgM0QgbW9kZWxzIGFuZCBvYmplY3RzDQoNCiMgTWFnaWMgSUQgZm9yIExpZ2h0d2F2ZSAzRCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTIgYnkgQ2FybA0KMAlzdHJpbmcJRk9STQlbZmlkPTAwMDAwMTI1MS00MC0wMDAwTFdPO2V4dD1sd28sbHdvYjttaW1lPTtdTGlnaHR3YXZlIDNEIG9iamVjdA0KJjgJc3RyaW5nCUxXTzIJDQoNCiMgTWFnaWMgSUQgZm9yIExpZ2h0d2F2ZSAzRCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDYgYnkgQ2FybA0KMAlzdHJpbmcJRk9STQlbZmlkPTAwMDAwMTI1MS00MC0wMDAwTFdPO2V4dD1sd29iLGx3bzttaW1lPTtdTGlnaHR3YXZlIDNEIG9iamVjdA0KJjgJc3RyaW5nCUxXT0IJDQoNCiMgTWFnaWMgSUQgZm9yIExpZ2h0d2F2ZSAzRCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDYgYnkgQ2FybA0KMAlzdHJpbmcJTFdTQwlbZmlkPTAwMDAwMTI1MS00MC0wMDAwTFdTO2V4dD1sd3NjLGx3czttaW1lPTtdTGlnaHR3YXZlIDNEIHNjZW5lDQoNCiMgTWFnaWMgSUQgZm9yIE1heWEgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCS8vTWF5YQlbZmlkPTAwMDAwMTMxMi00MC0wMDAwME1BO2V4dD1tYTttaW1lPTtdTWF5YSAzZCBtb2RlbCwgYXNjaWkgZW5jb2RlZA0KDQojIE1hZ2ljIElEIGZvciBDaW5lbWEgNEQgVmVyc2lvbiA0LnggYW5kIGVhcmxpZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA2IGJ5IENhcmwNCjAJc3RyaW5nCUZPUk0JW2ZpZD0wMDAwMDEyNTUtNDAtMDAwTUM0RDtleHQ9bWM0ZDttaW1lPTtdTWF4b24gQ2luZW1hIDREIHY0LnggM0QgZGF0YQ0KJjgJc3RyaW5nCU1DNEQJDQoNCiMgTWFnaWMgSUQgZm9yIEdlb212aWV3IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlDTUVTSAlbZmlkPTAwMDEwMDExOC00MC0wMDBNRVNIO2V4dD1tZXNoO21pbWU9O11HZW9tdmlldyBwb2x5Z29uDQoNCiMgTWFnaWMgSUQgZm9yIEdlb212aWV3IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlNRVNICVtmaWQ9MDAwMTAwMTE4LTQwLTAwME1FU0g7ZXh0PW1lc2g7bWltZT07XUdlb212aWV3IHBvbHlnb24NCg0KIyBNYWdpYyBJRCBmb3IgTWlsc2hhcGUgM2QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCU1TM0QwMDAwMDAJW2ZpZD0wMDAxMDAxMTctNDAtMDAwTVMzRDtleHQ9bXMzZDttaW1lPTtdTWlsa3NoYXBlIDNkIG1vZGVsLCBiaW5hcnkgZW5jb2RlZA0KDQojIE1hZ2ljIElEIGZvciBXb3JsZHRvb2xraXQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA2IGJ5IENhcmwNCjAJc3RyaW5nCW5mZglbZmlkPTAwMDAwMTI1My00MC0wMDAwTkZGO2V4dD1uZmY7bWltZT07XVNlbnNlOCBXb3JsZHRvb2xraXQgM0Qgb2JqZWN0DQoNCiMgTWFnaWMgSUQgZm9yIEF1dG9kZXNrIEFuaW1hdG9yIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQoyCWxlbG9uZwkweDAwMDAJW2ZpZD0wMDAwMDEyNTQtNDAtMDAwMFBMWTtleHQ9cGx5O21pbWU9O11BdXRvZGVzayBhbmltYXRvciBwb2x5Z29uIGZpbGUNCiY2CWJ5dGUJMAkNCiY3CWJ5dGUJMHg5OQkNCg0KIyBNYWdpYyBJRCBmb3IgQXV0b2Rlc2sgQW5pbWF0b3IgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjIJbGVsb25nCTB4MDAwMAlbZmlkPTAwMDAwMTI1NC00MC0wMDAwUExZO2V4dD1wbHk7bWltZT07XUF1dG9kZXNrIGFuaW1hdG9yIHBvbHlnb24gZmlsZQ0KJjYJYnl0ZQkxCQ0KJjcJYnl0ZQkweDk5CQ0KDQojIE1hZ2ljIElEIGZvciBRdWljazNkIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlxdWljazNEbwlbZmlkPTAwMDAwMTMzNC00MC0wMDAwUTNPO2V4dD1xM287bWltZT07XVF1aWNrM2QgM0Qgb2JqZWN0DQoNCiMgTWFnaWMgSUQgZm9yIFF1aWNrM2QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCXF1aWNrM0RzCVtmaWQ9MDAwMDAxMzM0LTQwLTAwMDBRM1M7ZXh0PXEzczttaW1lPTtdUXVpY2szZCAzRCBzY2VuZQ0KDQojIE1hZ2ljIElEIGZvciBSZW5kZXJtYW4gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCSMjUmVuZGVyTWFuXFwgUklCLVN0cnVjdHVyZQlbZmlkPTAwMDAwMTMyMy00MC0wMDAwUklCO2V4dD1yaWI7bWltZT07XVJlbmRlcm1hbiBieXRlc3RyZWFtIDNEIG1vZGVsDQoNCiMgTWFnaWMgSUQgZm9yIFNjdWxwdCAzZCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMTIgYnkgQ2FybA0KMAlzdHJpbmcJRk9STQlbZmlkPTAwMDAwMDAwMC00MC0wMFNDRU5FO2V4dD1zY2VuZTttaW1lPTtdU2N1bHB0IDNkIHNjZW5lIG1vZGVsDQomOAlzdHJpbmcJU0MzRAkNCg0KIyBNYWdpYyBJRCBmb3IgSW1hZ2luZSAzRCBTdHVkaW8sIFR1cmJvIFNpbHZlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDYgYnkgQ2FybA0KMAlzdHJpbmcJRk9STQlbZmlkPTAwMDAwMTI1Mi00MC0wMDBUREREO2V4dD10ZGRkLG9iajttaW1lPTtdSW1hZ2luZSAzRCBvYmplY3QNCiY4CXN0cmluZwlURERECQ0KDQojIE1hZ2ljIElEIGZvciBNYWNyb21lZGlhIERpcmVjdG9yIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwlJRlgJW2ZpZD0wMDAwMDEyNTktNDAtMDAwMFczRDtleHQ9dzNkO21pbWU9O11TaG9ja3dhdmUgM0QgbW9kZWwNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wNiBieSBDYXJsDQowCXN0cmluZwkjVlJNTAlbZmlkPTAwMDAwMDAwMS00MC0wMDE0NzcyO2V4dD13cmw7bWltZT1tb2RlbC92cm1sO11WaXJ0dWFsIFJlYWxpdHkgbW9kZWxpbmcgbGFuZ3VhZ2UNCj43CXN0cmluZwl4CSwgdmVyc2lvbiAlLjFzLg0KPjkJc3RyaW5nCXgJJS4xcw0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTEyIGJ5IENhcmwNCjAJc3RyaW5nCTxXT1JMRD4JW2ZpZD0wMDAwMDAwMDAtNDAtMDAwMFhHTDtleHQ9eGdsO21pbWU9O11YR0wgM2QgbW9kZWwNCg0KIyBNYWdpYyBJRCBmb3IgRGlyZWN0M0QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA0LTExIGJ5IENhcmwNCjAJc3RyaW5nCXhvZlxcIAlbZmlkPTAwMDAwMTAwMS00MC0wMDAwWElFO2V4dD14aWU7bWltZT07XU1pY3Jvc29mdCBkaXJlY3QzZCAzRCBtb2RlbA0KDQojIE1hZ2ljIElEIGZvciBTb2Z0aW1hZ2UgWFNJIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMiBieSBDYXJsDQowCXN0cmluZwl4c2lcXCAJW2ZpZD0wMDAwMDEzMjEtNDAtMDAwMFhTSTtleHQ9eHNpO21pbWU9O11Tb2Z0aW1hZ2UgM2QgbW9kZWwNCg0KIyBNYWdpYyBJRCBmb3IgS2Fib29tIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlcXHhBOE1QXFx4QTgJW2ZpZD0wMDAwMDAwMDAtNTAtMDAwMDAwMDtleHQ9O21pbWU9O11LYm9vbSBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQ1RXIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQowCWxlc2hvcnQJMTIJW2ZpZD0wMDAxMDAwNzctNTAtMDAwMDAwMDtleHQ9O21pbWU9O11Db250ZXh0IHRyZWUgd2VpZ2hpbmcgKENUVykgYXJjaGl2ZSBmaWxlDQomMglsZXNob3J0CTAJDQoNCiMgTWFnaWMgSUQgZm9yIE1pY3Jvc29mdCBDb21wcmVzcyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJU1pERFxceDg4XFx4RjBcXHgyN1xceDMzCVtmaWQ9MDAwMDAxMDAxLTUwLTAwMDAwMDA7ZXh0PTttaW1lPTtdTWljcm9zb2Z0IExaU1MgY29tcHJlc3NlZCBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEFBWCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTggYnkgQ2FybA0KMAlzdHJpbmcJXFx4NDBcXHhGRVxceDAwXFx4MDAJW2ZpZD0wMDAwMDEyODEtNTAtMDAwMEFBWDtleHQ9YWF4O21pbWU9O11BQVggYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEFCQ29tcCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTggYnkgQ2FybA0KMAlzdHJpbmcJXFx4MDNBQjIJW2ZpZD0wMDAwMDEyODItNTAtMDAwMEFCUDtleHQ9YWJwO21pbWU9O11BQkNvbXAgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEFjZSAvIFdpbkFjZSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KNwlzdHJpbmcJKipBQ0UqKglbZmlkPTAwMDAwMTI2NS01MC0wMDAwQUNFO2V4dD1hY2U7bWltZT07XUFjZSAvIFdpbkFDRSBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQWkgQXJjaGl2ZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE4IGJ5IENhcmwNCjAJc3RyaW5nCUFpCVtmaWQ9MDAwMDAwMDAwLTUwLTAwMDAwQUk7ZXh0PWFpO21pbWU9O11BaSBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQWt0IGFyY2hpdmVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xOCBieSBDYXJsDQowCXN0cmluZwlBS1RcXHgwQQlbZmlkPTAwMDAwMDAwMC01MC0wMDAwQUtUO2V4dD1ha3Q7bWltZT07XUFLVCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQU1HIEFyY2hpdmVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xOCBieSBDYXJsDQowCXN0cmluZwlcXHhBRFxceDM2CVtmaWQ9MDAwMDAxMjg0LTUwLTAwMDBBTUc7ZXh0PWFtZzttaW1lPTtdQU1HIGFyY2hpdmUgZmlsZQ0KPjIJYnl0ZQl4CSwgdmVyc2lvbiAlYmguDQo+MglieXRlCXgJJWJsDQomMwlieXRlCTAJDQoNCiMgTWFnaWMgSUQgZm9yIGFyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQowCXN0cmluZwlcXHgyMTxhcmNoPlxceDBBCVtmaWQ9MDAwMDAwMDAzLTUwLTAwMDAwQVI7ZXh0PWFyO21pbWU9O11VTklYIGFyY2hpdmUgZmlsZSAoYXIpDQoNCiMgTWFnaWMgSUQgZm9yIEFSNyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTggYnkgQ2FybA0KMAlzdHJpbmcJLEFSNyBlLW1haWxhYmxlIGFyY2hpdmU6CVtmaWQ9MDAwMTAwMDM2LTUwLTAwMDBBUjc7ZXh0PWFyNzttaW1lPTtdQVI3IGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBTcXVhc2ggZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE4IGJ5IENhcmwNCjMJc3RyaW5nCU9jdFNxdQlbZmlkPTAwMDEwMDAzOC01MC0wMDAwQVJIO2V4dD1hcmg7bWltZT07XVNxdWFzaCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQXJqIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xOCBieSBDYXJsDQowCXN0cmluZwlcXHg2MFxceEVBCVtmaWQ9MDAwMDAxMjg1LTUwLTAwMDBBUko7ZXh0PWFyajttaW1lPTtdQVJKIGFyY2hpdmUgZmlsZQ0KJjEwCWJ5dGUJMgkNCg0KIyBNYWdpYyBJRCBmb3IgQVNEIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xOCBieSBDYXJsDQowCXN0cmluZwlBU0QwMVxceDFBCVtmaWQ9MDAwMDAxMjg3LTUwLTAwMDBBU0Q7ZXh0PWFzZDttaW1lPTtdQVNEIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBCb2EgY29uc3RyaWN0b3IgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE4IGJ5IENhcmwNCjAJc3RyaW5nCUJPQVxceDAwCVtmaWQ9MDAwMTAwMDQwLTUwLTAwMDBCNTg7ZXh0PWI1ODttaW1lPTtdQk9BIGNvbnN0cmljdG9yIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBCV0MgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE4IGJ5IENhcmwNCjAJc3RyaW5nCUJXQwlbZmlkPTAwMDEwMDA0Mi01MC0wMDAwMEJDO2V4dD1iYzttaW1lPTtdQldDIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBCaXggZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE4IGJ5IENhcmwNCjAJc3RyaW5nCUJJWDAJW2ZpZD0wMDAxMDAwMzctNTAtMDAwMEJJWDtleHQ9Yml4O21pbWU9O11CSVggYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEJ0b2EgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE4IGJ5IENhcmwNCjAJc3RyaW5nCXhidG9hNQlbZmlkPTAwMDEwMDA0My01MC0wMDAwQk9PO2V4dD1ib287bWltZT07XUJ0b2EgZW5jb2RlZCBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEJzYSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTggYnkgQ2FybA0KMAlzdHJpbmcJXFx4RkZCU0dcXHgwMFxceDAwXFx4RkZCU0EJW2ZpZD0wMDAwMDEyODktNTAtMDAwMEJTTjtleHQ9YnNuO21pbWU9O11Cc2EgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEJUUEMgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTAzIGJ5IENhcmwNCjAJc3RyaW5nCWJ0cGNcXCAJW2ZpZD0wMDAxMDAwNzQtNTAtMDAwQlRQQztleHQ9YnRwYzttaW1lPTtdQlRQQyBjb21wcmVzc2VkIGltYWdlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQlRTIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xOCBieSBDYXJsDQowCXN0cmluZwlcXHgxQVxceDAzRGVzY3JpcHQJW2ZpZD0wMDAxMDAwNDQtNTAtMDAwMEJUUztleHQ9YnRzO21pbWU9O11CVFNwayBhcmNoaXZlIGZpbGUNCiYweDUyMQlzdHJpbmcJQlRTUEshCQ0KDQojIE1hZ2ljIElEIGZvciBCemlwIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xOCBieSBDYXJsDQowCXN0cmluZwlCWjAJW2ZpZD0wMDAxMDAwMDgtNTAtMDAwMDBCWjtleHQ9Yno7bWltZT07XUJ6aXAgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEJ6aXAyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlCWmgJW2ZpZD0wMDAxMDAwMDgtNTAtMDAwMEJaMjtleHQ9YnoyO21pbWU9O11CemlwMiBhcmNoaXZlIGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlNU0NGCVtmaWQ9MDAwMDAxMDAxLTUwLTAwMDBDQUI7ZXh0PWNhYjttaW1lPTtdTWljcm9zb2Z0IENhYmluZXQgZmlsZQ0KPjI1CWJ5dGUJeAksIHZlcnNpb24gJWQuDQo+MjQJYnl0ZQl4CSVkDQoNCiMgTWFnaWMgSUQgZm9yIENydXNoIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yMyBieSBDYXJsDQowCXN0cmluZwlDUlVTSFxcIHYxLjgJW2ZpZD0wMDAwMDEyOTAtNTAtMDAwMENSVTtleHQ9Y3J1O21pbWU9O11DcnVzaCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQ3R4ZiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjMgYnkgQ2FybA0KMAlzdHJpbmcJQ1hGXFx4MUEJW2ZpZD0wMDAxMDAwNDUtNTAtMDAwMENYRjtleHQ9Y3hmO21pbWU9O11DdHggYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIERBWFdhdiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJRk9STQlbZmlkPTAwMDAwMDAwMC01MC0wMDAwREFYO2V4dD1kYXg7bWltZT07XURBWCBhdWRpbyBhcmNoaXZlIGZpbGUNCiY4CXN0cmluZwlkYXhBCQ0KDQojIE1hZ2ljIElEIGZvciBEaXNrbWFzaGVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yMyBieSBDYXJsDQowCXN0cmluZwlETVMhCVtmaWQ9MDAwMDAwMDAwLTUwLTAwMDBETVM7ZXh0PWRtczttaW1lPTtdRGlza21hc2hlciBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgRHBhZSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjMgYnkgQ2FybA0KMAlzdHJpbmcJRGlya1xcIFBhZWhsKGMpCVtmaWQ9MDAwMTAwMDQ2LTUwLTAwMDBEUEE7ZXh0PWRwYTttaW1lPTtdRHBhZSBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgRGlzaW50ZWdyYXRvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjMgYnkgQ2FybA0KMAlzdHJpbmcJRFNUYglbZmlkPTAwMDEwMDA0Ny01MC0wMDAwRFNUO2V4dD1kc3Q7bWltZT07XURpc2ludGVncmF0b3IgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEVuaGFuY2VkIGNvbXByZXNzb3IgKEVOQykgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTIzIGJ5IENhcmwNCjAJc3RyaW5nCUVuY2gJW2ZpZD0wMDAxMDAwNTEtNTAtMDAwMEVOQztleHQ9ZW5jO21pbWU9O11FbmhhbmNlZCBjb21wcmVzc29yIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBFU1AgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCUVTUD4JW2ZpZD0wMDAxMDAwMDItNTAtMDAwMEVTUDtleHQ9ZXNwO21pbWU9O11FU1AgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEVTUCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMHg1M2YJc3RyaW5nCUVTUAlbZmlkPTAwMDEwMDAwMi01MC0wMDAwRVhFO2V4dD1leGU7bWltZT07XUVTUCBTZWxmLWV4dHJhY3RpbmcgYXJjaGl2ZXcgKE1TLURPUykNCg0KIyBNYWdpYyBJRCBmb3IgRnJlZXplIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlcXHgxRlxceDlFCVtmaWQ9MDAwMDAwMDAwLTUwLTAwMDAwMEY7ZXh0PWY7bWltZT07XUZyZWV6ZSBhcmNoaXZlIGZpbGUsIHZlcnNpb24gMS4wDQoNCiMgTWFnaWMgSUQgZm9yIEZyZWV6ZSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MUZcXHg5RglbZmlkPTAwMDAwMDAwMC01MC0wMDAwMDBGO2V4dD1mO21pbWU9O11GcmVlemUgYXJjaGl2ZSBmaWxlLCB2ZXJzaW9uIDIuMA0KDQojIE1hZ2ljIElEIGZvciBRbGZjIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yMyBieSBDYXJsDQowCXN0cmluZwlcXHg0N1xceDY4XFx4NjlcXHg2NFxceDZmCVtmaWQ9MDAwMTAwMDU1LTUwLTAwMDAwR1E7ZXh0PWdxO21pbWU9O11RbGZjIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBHemlwIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlcXHgxRlxceDhCCVtmaWQ9MDAwMDAwMDAyLTUwLTAwMDE5NTI7ZXh0PWd6O21pbWU9O11HemlwIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBIYSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTAgYnkgQ2FybA0KMAlzdHJpbmcJSEEJW2ZpZD0wMDAxMDAwMTMtNTAtMDAwMDBIQTtleHQ9aGE7bWltZT07XUhBIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBIQVAgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTIzIGJ5IENhcmwNCjAJc3RyaW5nCVxceDkxM0hGCVtmaWQ9MDAwMDAxMjkxLTUwLTAwMDBIQVA7ZXh0PWhhcDttaW1lPTtdSEFQIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBIcGFjayBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJSFBBSwlbZmlkPTAwMDEwMDAxNi01MC0wMDAwSFBLO2V4dD1ocGs7bWltZT07XUhwYWNrIGFyY2hpdmUgZmlsZQ0KJlo0CXN0cmluZwlIUEFLCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTEwIGJ5IENhcmwNCjExCXN0cmluZwltdXN0XFwgYmVcXCBjb252ZXJ0ZWRcXCB3aXRoXFwgQmluSGV4CVtmaWQ9MDAwMDAxMDAyLTUwLTAwMDBIUVg7ZXh0PWhxeDttaW1lPTtdQmluSGV4IGFyY2hpdmUNCg0KIyBNYWdpYyBJRCBmb3IgSFlQIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yMyBieSBDYXJsDQowCXN0cmluZwlcXHgxQUhQXFx4MjUJW2ZpZD0wMDAxMDAwNTYtNTAtMDAwMEhZUDtleHQ9aHlwO21pbWU9O11IWVAgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEhZUCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjMgYnkgQ2FybA0KMAlzdHJpbmcJXFx4MUFTVFxceDI1CVtmaWQ9MDAwMTAwMDU2LTUwLTAwMDBIWVA7ZXh0PWh5cDttaW1lPTtdSFlQIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBXaW5pbXAgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJbGVsb25nCTB4QTUwNEQ0OQlbZmlkPTAwMDAwMTI3MC01MC0wMDAwSU1QO2V4dD1pbXA7bWltZT07XVdpbmltcCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgSlJjaGl2ZSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJSlJjaGl2ZQlbZmlkPTAwMDAwMTI2My01MC0wMDAwSlJDO2V4dD1qcmM7bWltZT07XUpSY2hpdmUgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIEFyY2hpdmUgSGFuZGxlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJTEcJW2ZpZD0wMDAxMDAwMDEtNTAtMDAwMDBMRztleHQ9bGc7bWltZT07XUFyaGFuZ2VsIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBMaW1pdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJTE1cXHgxQQlbZmlkPTAwMDEwMDAwMy01MC0wMDAwTElNO2V4dD1saW07bWltZT07XUxpbWl0IGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBMYXJjIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQoyCXN0cmluZwktbHo0LQlbZmlkPTAwMDEwMDAxNy01MC0wMDAwTFpIO2V4dD1semg7bWltZT07XUxhcmMgYXJjaGl2ZQ0KDQojIE1hZ2ljIElEIGZvciBMYXJjIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQoyCXN0cmluZwktbHo1LQlbZmlkPTAwMDEwMDAxNy01MC0wMDAwTFpIO2V4dD1semg7bWltZT07XUxhcmMgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIExhcmMgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjIJc3RyaW5nCS1senMtCVtmaWQ9MDAwMTAwMDE3LTUwLTAwMDBMWkg7ZXh0PWx6aDttaW1lPTtdTGFyYyBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgbGhhIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQoyCXN0cmluZwktbGhcXCAtCVtmaWQ9MDAwMTAwMDE3LTUwLTAwMDBMWkg7ZXh0PWx6aCxsaGE7bWltZT07XUxIYXJjIGFyY2hpdmUgZmlsZSwgdmVyc2lvbiAyLngNCg0KIyBNYWdpYyBJRCBmb3IgbGhhIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQoyCXN0cmluZwktbGgwLQlbZmlkPTAwMDEwMDAxNy01MC0wMDAwTFpIO2V4dD1semgsbGhhO21pbWU9O11MSGFyYyBhcmNoaXZlIGZpbGUsIHZlcnNpb24gMS54DQoNCiMgTWFnaWMgSUQgZm9yIGxoYSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMglzdHJpbmcJLWxoMS0JW2ZpZD0wMDAxMDAwMTctNTAtMDAwMExaSDtleHQ9bHpoLGxoYTttaW1lPTtdTEhhcmMgYXJjaGl2ZSBmaWxlLCB2ZXJzaW9uIDEueA0KDQojIE1hZ2ljIElEIGZvciBsaGEgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjIJc3RyaW5nCS1saDItCVtmaWQ9MDAwMTAwMDE3LTUwLTAwMDBMWkg7ZXh0PWx6aCxsaGE7bWltZT07XUxIYXJjIGFyY2hpdmUgZmlsZSwgdmVyc2lvbiAyLngNCg0KIyBNYWdpYyBJRCBmb3IgbGhhIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQoyCXN0cmluZwktbGgzLQlbZmlkPTAwMDEwMDAxNy01MC0wMDAwTFpIO2V4dD1semgsbGhhO21pbWU9O11MSGFyYyBhcmNoaXZlIGZpbGUsIHZlcnNpb24gMi54DQoNCiMgTWFnaWMgSUQgZm9yIGxoYSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMglzdHJpbmcJLWxoNC0JW2ZpZD0wMDAxMDAwMTctNTAtMDAwMExaSDtleHQ9bHpoLGxoYTttaW1lPTtdTEhhcmMgYXJjaGl2ZSBmaWxlLCB2ZXJzaW9uIDIueA0KDQojIE1hZ2ljIElEIGZvciBsaGEgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjIJc3RyaW5nCS1saDUtCVtmaWQ9MDAwMTAwMDE3LTUwLTAwMDBMWkg7ZXh0PWx6aCxsaGE7bWltZT07XUxIYXJjIGFyY2hpdmUgZmlsZSwgdmVyc2lvbiAyLngNCg0KIyBNYWdpYyBJRCBmb3IgbGhhIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQoyCXN0cmluZwktbGg2LQlbZmlkPTAwMDEwMDAxNy01MC0wMDAwTFpIO2V4dD1semgsbGhhO21pbWU9O11MSGFyYyBhcmNoaXZlIGZpbGUsIHZlcnNpb24gMi54DQoNCiMgTWFnaWMgSUQgZm9yIGxoYSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMglzdHJpbmcJLWxoNy0JW2ZpZD0wMDAxMDAwMTctNTAtMDAwMExaSDtleHQ9bHpoLGxoYTttaW1lPTtdTEhhcmMgYXJjaGl2ZSBmaWxlLCB2ZXJzaW9uIDIueA0KDQojIE1hZ2ljIElEIGZvciBsaGEgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjIJc3RyaW5nCS1saGQtCVtmaWQ9MDAwMTAwMDE3LTUwLTAwMDBMWkg7ZXh0PWx6aCxsaGE7bWltZT07XUxIYXJjIGFyY2hpdmUgZmlsZSwgdmVyc2lvbiAyLngNCg0KIyBNYWdpYyBJRCBmb3IgbHpvIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlcXHg4OVxceDRjXFx4NWFcXHg0ZlxceDAwXFx4MGRcXHgwYVxceDFhXFx4MGEJW2ZpZD0wMDAxMDAwMTgtNTAtMDAwMExaTztleHQ9bHpvO21pbWU9O11MWk9QIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBMenggZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTIzIGJ5IENhcmwNCjAJc3RyaW5nCUxaWAlbZmlkPTAwMDEwMDA1OC01MC0wMDAwTFpYO2V4dD1seng7bWltZT07XUxaWCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgTWRjZCAoTWlrZSBEYXZlbnBvcnQgY29tcHJlc3NvcikgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCU1EbWQJW2ZpZD0wMDAxMDAwMDQtNTAtMDAwMDBNRDtleHQ9bWQ7bWltZT07XU1pa2UgRGF2ZW5wb3J0IGFyY2hpdmUgZmlsZQ0KPjQJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KJjUJYnl0ZQkxCQ0KDQojIE1hZ2ljIElEIGZvciBOYXNocmluayBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjMgYnkgQ2FybA0KMAlzdHJpbmcJTlNLCVtmaWQ9MDAwMDAxMjkyLTUwLTAwMDBOU0s7ZXh0PW5zazttaW1lPTtdTmFTaHJpbmsgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFNlbW9uZSBhcmNoaXZlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjcgYnkgQ2FybA0KMAlzdHJpbmcJU0VNaAlbZmlkPTAwMDEwMDA2OC01MC0wMDAwT05FO2V4dD1vbmU7bWltZT07XVNlbW9uZSBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgTHBhYyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJTFBBQwlbZmlkPTAwMDEwMDA4NC01MC0wMDAwUEFDO2V4dD1wYWM7bWltZT07XUxQQUMgYXVkaW8gYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIENyb3NzZVBBQyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJRFNJR0RDQwlbZmlkPTAwMDAwMTI2Mi01MC0wMDAwUEFDO2V4dD1wYWM7bWltZT07XUNyb3NzZVBBQyBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgSGlnaCBDb21wcmVzc2lvbiBNYXJrb3YgUHJlZGljdGl2ZSBDb2RlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjMgYnkgQ2FybA0KMAlzdHJpbmcJUFBaMglbZmlkPTAwMDEwMDA2MS01MC0wMDAwUE1aO2V4dD1wbXo7bWltZT07XVBQTVoyIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBQb3dlcnBhY2tlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjcgYnkgQ2FybA0KMAlzdHJpbmcJUFAyMAlbZmlkPTAwMDEwMDA2Mi01MC0wMDAwMFBQO2V4dD1wcDttaW1lPTtdUG93ZXJwYWNrZXIgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFBBUTEgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTI3IGJ5IENhcmwNCjAJc3RyaW5nCVBBUTFcXHgwRFxceDBBCVtmaWQ9MDAwMTAwMDY0LTUwLTAwMDBQUTE7ZXh0PXBxMTttaW1lPTtdUEFRMSBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgUEFRMyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjcgYnkgQ2FybA0KMAlzdHJpbmcJUEFRM1xceDBEXFx4MEEJW2ZpZD0wMDAxMDAwNjQtNTAtMDAwMFBRMztleHQ9cHEzO21pbWU9O11QQVEzIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBQQVE2IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yNyBieSBDYXJsDQowCXN0cmluZwlQQVE2CVtmaWQ9MDAwMTAwMDY0LTUwLTAwMDBQUTY7ZXh0PXBxNjttaW1lPTtdUEFRNiBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgUHJldHR5IHNpbXBsZSBhcmNoaXZlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjcgYnkgQ2FybA0KMAlzdHJpbmcJUFNBXFx4MDFcXHgwMwlbZmlkPTAwMDEwMDA2NS01MC0wMDAwUFNBO2V4dD1wc2E7bWltZT07XVByZXR0eSBzaW1wbGUgYXJjaGl2ZXIgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFF1YW50dW0gY29tcHJlc3NvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjcgYnkgQ2FybA0KMAlzdHJpbmcJRFMJW2ZpZD0wMDAwMDEyOTQtNTAtMDAwMDAwUTtleHQ9cTttaW1lPTtdUXVhbnR1bSBhcmNoaXZlIGZpbGUNCiYyCWJ5dGUJPDIJDQo+MglieXRlCXgJLCB2ZXJzaW9uICVkDQo+MwlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFJhciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJXFx4NTJcXHg2MVxceDcyXFx4MjFcXHgxYVxceDA3XFx4MDAJW2ZpZD0wMDAwMDEyNjctNTAtMDAwMFJBUjtleHQ9cmFyO21pbWU9O11SQVIgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFJvbWFuaWFuIGFyY2hpdmVyIGVYcGVydCAoUkFYKSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjcgYnkgQ2FybA0KMAlzdHJpbmcJVUxFQglbZmlkPTAwMDAwMTI5NS01MC0wMDAwUkFYO2V4dD1yYXg7bWltZT07XVJBWCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgUmVkdXEgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTI3IGJ5IENhcmwNCjAJc3RyaW5nCXJkcXgJW2ZpZD0wMDAxMDAwNjYtNTAtMDAwMFJEUTtleHQ9cmRxO21pbWU9O11SZWR1cSBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgUlBNIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQowCXN0cmluZwlcXHhlZFxceGFiXFx4ZWVcXHhkYglbZmlkPTAwMDAwMDAwMC01MC0wMDAwUlBNO2V4dD1ycG07bWltZT07XVJQTSBhcmNoaXZlDQo+NAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+NQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIHJ6aXAgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA0IGJ5IENhcmwNCjAJc3RyaW5nCVJaSVAJW2ZpZD0wMDAxMDAwNzYtNTAtMDAwMDBSWjtleHQ9cno7bWltZT07XVJ6aXAgYXJjaGl2ZSBmaWxlDQo+NAlieXRlCXgJLCB2ZXJzaW9uICVkDQo+NQlieXRlCXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIFN0cmVhbWxpbmUgQXJjaGl2YWwgVXRpbGl0eSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMwlzdHJpbmcJTEgwCVtmaWQ9MDAwMDAxMjY0LTUwLTAwMDBTQVI7ZXh0PXNhcjttaW1lPTtdU3RyZWFtaW5nIEFyY2hpdmVyIFV0aWxpdHkgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFN0cmVhbWxpbmUgQXJjaGl2YWwgVXRpbGl0eSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMwlzdHJpbmcJTEg0CVtmaWQ9MDAwMDAxMjY0LTUwLTAwMDBTQVI7ZXh0PXNhcjttaW1lPTtdU3RyZWFtaW5nIEFyY2hpdmVyIFV0aWxpdHkgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFN0cmVhbWxpbmUgQXJjaGl2YWwgVXRpbGl0eSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMwlzdHJpbmcJTEg1CVtmaWQ9MDAwMDAxMjY0LTUwLTAwMDBTQVI7ZXh0PXNhcjttaW1lPTtdU3RyZWFtaW5nIEFyY2hpdmVyIFV0aWxpdHkgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFNCWCBBcmNoaXZlciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMjcgYnkgQ2FybA0KMAlzdHJpbmcJU0IxXFx4MDAJW2ZpZD0wMDAwMDEyOTctNTAtMDAwMDBTQjtleHQ9c2I7bWltZT07XVNCWCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgU0JDIEFyY2hpdmVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yNyBieSBDYXJsDQowCXN0cmluZwlTQkNcXHgxRQlbZmlkPTAwMDEwMDA2Ny01MC0wMDAwU0JDO2V4dD1zYmM7bWltZT07XVNCQyBhcmNoaXZlIGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMCBieSBDYXJsDQoxMAlzdHJpbmcJI1xcIFRoaXNcXCBpc1xcIGFcXCBzaGVsbFxcIGFyY2hpdmUJW2ZpZD0wMDAwMDAwMDAtNTAtMDAwU0hBUjtleHQ9c2hhcjttaW1lPTtdVU5JWCBzaGVsbCBhcmNoaXZlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTAgYnkgQ2FybA0KMTEJc3RyaW5nCSNcXCBUaGlzXFwgaXNcXCBhXFwgc2hlbGxcXCBhcmNoaXZlCVtmaWQ9MDAwMDAwMDAwLTUwLTAwMFNIQVI7ZXh0PXNoYXI7bWltZT07XVVOSVggc2hlbGwgYXJjaGl2ZQ0KDQojIE1hZ2ljIElEIGZvciBOdWxpYiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJXFx4NGVcXHhmNVxceDQ2XFx4ZDgJW2ZpZD0wMDAxMDAwMDUtNTAtMDAwMFNISztleHQ9c2hrO21pbWU9O11TaHJpbmtpdC9OdWxpYiBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgTnVsaWIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCVxceDRlXFx4ZjVcXHg0NlxceGU5XFx4NmNcXHhlNQlbZmlkPTAwMDEwMDAwNS01MC0wMDAwU0hLO2V4dD1zaGs7bWltZT07XVNocmlua2l0L051bGliIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBTdHVmZml0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQowCXN0cmluZwlTdHVmZkl0XFwgCVtmaWQ9MDAwMDAxMjcyLTUwLTAwMDBTSVQ7ZXh0PXNpdDttaW1lPTtdU3R1ZmZpdCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgU3R1ZmZpdCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTEgYnkgQ2FybA0KMAlzdHJpbmcJU0lUIQlbZmlkPTAwMDAwMTI3Mi01MC0wMDAwU0lUO2V4dD1zaXQ7bWltZT07XVN0dWZmaXQgYXJjaGl2ZSBmaWxlDQomMTAJc3RyaW5nCXJMYXUJDQo+MTQJYnl0ZQl4CSwgdmVyc2lvbiAlZC4wDQoNCiMgTWFnaWMgSUQgZm9yIFN0dWZmaXQgRXh0ZW5kZWQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAzLTA0IGJ5IENhcmwNCjAJc3RyaW5nCVN0dWZmSXQhCVtmaWQ9MDAwMDAxMjcyLTUwLTAwMFNJVFg7ZXh0PXNpdHg7bWltZT07XVN0dWZmaXQgZXh0ZW5kZWQgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFNvZiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDMtMDQgYnkgQ2FybA0KMAlzdHJpbmcJUEtcXHgwM1xceDA2CVtmaWQ9MDAwMDAwMDAwLTUwLTAwMDBTT0Y7ZXh0PXNvZjttaW1lPTtdU09GIGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBTcGxpbnQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCVxceDkzXFx4QjlcXHgwNglbZmlkPTAwMDEwMDAwNi01MC0wMDAwU1BMO2V4dD1zcGw7bWltZT07XVNwbGludCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgU3F3ZWV6IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMCBieSBDYXJsDQowCXN0cmluZwlTUVdFWlxcIAlbZmlkPTAwMDEwMDAxNC01MC0wMDAwU1FaO2V4dD1zcXo7bWltZT07XVNxd2V6IGFyY2hpdmUgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBTcXogZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTEwIGJ5IENhcmwNCjAJc3RyaW5nCUhMU1FaCVtmaWQ9MDAwMTAwMDE1LTUwLTAwMDBTUVo7ZXh0PXNxejttaW1lPTtdU3F1ZWV6ZSBhcmNoaXZlIGZpbGUNCj41CXN0cmluZwl4CSwgdmVyc2lvbiAlLjFzLjANCg0KIyBNYWdpYyBJRCBmb3IgU3RvbmVjcmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yNyBieSBDYXJsDQowCXN0cmluZwlTNDAxCVtmaWQ9MDAwMTAwMDYzLTUwLTAwMDBTVEM7ZXh0PXN0YzttaW1lPTtdU3RvbmVjcmFja2VyIGFyY2hpdmUgZmlsZQ0KPjAJc3RyaW5nCVM0MDEJLCB2ZXJzaW9uIDQuMDENCg0KIyBNYWdpYyBJRCBmb3IgU3RvbmVjcmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yNyBieSBDYXJsDQowCXN0cmluZwlTNDAzCVtmaWQ9MDAwMTAwMDYzLTUwLTAwMDBTVEM7ZXh0PXN0YzttaW1lPTtdU3RvbmVjcmFja2VyIGFyY2hpdmUgZmlsZQ0KPjAJc3RyaW5nCVM0MDMJLCB2ZXJzaW9uIDQuMDMNCg0KIyBNYWdpYyBJRCBmb3IgU3RvbmVjcmFja2VyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yNyBieSBDYXJsDQowCXN0cmluZwlTNDA0CVtmaWQ9MDAwMTAwMDYzLTUwLTAwMDBTVEM7ZXh0PXN0YzttaW1lPTtdU3RvbmVjcmFja2VyIGFyY2hpdmUgZmlsZQ0KPjAJc3RyaW5nCVM0MDQJLCB2ZXJzaW9uIDQuMDQNCg0KIyBNYWdpYyBJRCBmb3IgU3ppcCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJU1pcXHgwQVxceDA0CVtmaWQ9MDAwMTAwMDA5LTUwLTAwMDAwU1o7ZXh0PXN6O21pbWU9O11TWklQIGFyY2hpdmUgZmlsZQ0KPjQJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjUJYnl0ZQl4CS4lZA0KDQojIE1hZ2ljIElEIGZvciBUYXIscGF4IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMCBieSBDYXJsDQoyNTcJc3RyaW5nCXVzdGFyXFwwNDBcXDA0MFxcMAlbZmlkPTAwMDAwMDAwMC01MC0wMDAwVEFSO2V4dD10YXI7bWltZT07XUdOVSB0YXIgYXJjaGl2ZQ0KDQojIE1hZ2ljIElEIGZvciBUYXIscGF4IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMCBieSBDYXJsDQoyNTcJc3RyaW5nCXVzdGFyXFwwXFx4MDYJW2ZpZD0wMDAwMDAwMDMtNTAtMDAwMTAwMztleHQ9dGFyO21pbWU9O11PcGVuZ3JvdXAvUE9TSVggdGFyIGFyY2hpdmUNCg0KIyBNYWdpYyBJRCBmb3IgdWMyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMCBieSBDYXJsDQowCXN0cmluZwlVQzJcXHgxYQlbZmlkPTAwMDAwMTI3MS01MC0wMDAwVUMyO2V4dD11YzI7bWltZT07XVVsdHJhIENvbXByZXNzb3IgYXJjaGl2ZSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFVoYXJjIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wNCBieSBDYXJsDQowCXN0cmluZwlVSEEJW2ZpZD0wMDAxMDAwODMtNTAtMDAwMFVIQTtleHQ9dWhhO21pbWU9O11VSEFyYyBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgWUJTIGFyY2hpdmVyIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0yNyBieSBDYXJsDQowCXN0cmluZwlZQlMzCVtmaWQ9MDAwMTAwMDcxLTUwLTAwMDBZQlM7ZXh0PXliczttaW1lPTtdWUJTIGFyY2hpdmUgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCVxcMDM3XFwyMzUJW2ZpZD0wMDAwMDAwMDAtNTAtMDAwMDAwWjtleHQ9ejttaW1lPTtdVU5JWCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgUGt1bnppcCwgSW5mby16aXAgVW56aXAgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJbGVsb25nCTB4MDQwMzRiNTAJW2ZpZD0wMDAwMDEyNjYtNTAtMDAwMFpJUDtleHQ9emlwO21pbWU9YXBwbGljYXRpb24vemlwO11Qa3ppcCBhcmNoaXZlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3Igem9vIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMCBieSBDYXJsDQowCXN0cmluZwlaT08JW2ZpZD0wMDAxMDAwMTItNTAtMDAwMFpPTztleHQ9em9vO21pbWU9O11ab28gYXJjaGl2ZSBmaWxlDQomMHgxNAlsZWxvbmcJMHgwRkRDNEE3REMJDQo+MzIJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjMzCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgWnppcCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJWloJW2ZpZD0wMDAxMDAwMDctNTAtMDAwMDBaWjtleHQ9eno7bWltZT07XVpaaXAgYXJjaGl2ZSBmaWxlDQo+MglieXRlCXgJLCB2ZXJzaW9uICVkLjANCg0KIyBNYWdpYyBJRCBmb3IgNjI0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wMyBieSBDYXJsDQowCXN0cmluZwlbRVNQXVxceEI1XFx4NzgJW2ZpZD0wMDAxMDAwMDItNTEtMDAwMENPTTtleHQ9Y29tO21pbWU9O102MjQgZXhlY3V0YWJsZSBjb21wcmVzc2VkIGZpbGUgKE1TLURPUykNCg0KIyBNYWdpYyBJRCBmb3IgNjI0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMy0wMyBieSBDYXJsDQowCXN0cmluZwlQVUxQXFx4ODMJW2ZpZD0wMDAxMDAwMDItNTEtMDAwMENPTTtleHQ9Y29tO21pbWU9O102MjQgZXhlY3V0YWJsZSBjb21wcmVzc2VkIGZpbGUgKE1TLURPUykNCg0KIyBNYWdpYyBJRCBmb3IgTHpleGUgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTIzIGJ5IENhcmwNCjB4MUMJc3RyaW5nCUxaOTEJW2ZpZD0wMDAxMDAwNTMtNTEtMDAwMEVYRTtleHQ9ZXhlO21pbWU9O11MemV4ZSBjb21wcmVzc2VkIGV4ZWN1dGFibGUgZmlsZSAoTVMtRE9TKQ0KDQojIE1hZ2ljIElEIGZvciBIVE1MIEhlbHAgV29ya3Nob3AgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCUlUU0YJW2ZpZD0wMDAwMDEwMDEtNjAtMDAwMENITTtleHQ9Y2htO21pbWU9O11NaWNyb3NvZnQgY29tcGlsZWQgaHlwZXJ0ZXh0IGRvY3VtZW50DQoNCiMgTWFnaWMgSUQgZm9yIEJvcmxhbmQgRGVscGhpIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNy0zMCBieSBDYXJsDQoyCXN0cmluZwlERUxQSEkuRElBR1JBTS5QT1JURk9MSU8JW2ZpZD0wMDAwMDEwMDUtNjAtMDAwMEREUDtleHQ9ZGRwO21pbWU9O11EZWxwaGkgRGlhZ3JhbSBQb3J0Zm9saW8gZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBQYWdlc3RyZWFtIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlGT1JNCVtmaWQ9MDAwMDAxMjY4LTYwLTAwMDBET0M7ZXh0PWRvYzttaW1lPTtdUGFnZXN0cmVhbSBkb2N1bWVudA0KJjgJc3RyaW5nCURPQ1xcIAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlDQVRcXCAJW2ZpZD0wMDAwMDEwMTAtNjAtMDAwRlRYVDtleHQ9ZnR4dDttaW1lPTtdRm9ybWF0dGVkIHRleHQgaW50ZXJjaGFuZ2UgZmlsZQ0KJjgJc3RyaW5nCUZUWFQJDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJRk9STQlbZmlkPTAwMDAwMTAxMC02MC0wMDBGVFhUO2V4dD1mdHh0O21pbWU9O11Gb3JtYXR0ZWQgdGV4dCBpbnRlcmNoYW5nZSBmaWxlDQomOAlzdHJpbmcJRlRYVAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlMSVNUCVtmaWQ9MDAwMDAxMDEwLTYwLTAwMEZUWFQ7ZXh0PWZ0eHQ7bWltZT07XUZvcm1hdHRlZCB0ZXh0IGludGVyY2hhbmdlIGZpbGUNCiY4CXN0cmluZwlGVFhUCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCUBkYXRhYmFzZQlbZmlkPTAwMDAwMTAwNy02MC0wMEdVSURFO2V4dD1ndWlkZTttaW1lPTtdQW1pZ2FHdWlkZSBoeXBlcnRleHQgZG9jdW1lbnQNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCWxlbG9uZwkweDAwMDM1RjNGCVtmaWQ9MDAwMDAxMDAxLTYwLTAwMDBITFA7ZXh0PWhscDttaW1lPTtdTWljcm9zb2Z0IFdpbmRvd3MgSGVscCBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMTAtMDggYnkgQ2FybA0KMAlzdHJpbmcJPCFET0NUWVBFXFwgCVtmaWQ9MDAwMDAwMDAxLTYwLTAwMTU0NDU7ZXh0PWh0bWwsaHRtO21pbWU9dGV4dC9odG1sO11IeXBlclRleHQgTWFya3VwIExhbmd1YWdlIGRvY3VtZW50IChIVE1MKQ0KJjEwCXN0cmluZy9jCWh0bWwJDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJSERPQwlbZmlkPTAwMDAwMTAwNi02MC0wMDAwSFlQO2V4dD1oeXA7bWltZT07XUF0YXJpIFNULUd1aWRlIGh5cGVydGV4dCBkb2N1bWVudA0KDQojIE1hZ2ljIElEIGZvciBPUy8yIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCXN0cmluZwlIU1AJW2ZpZD0wMDAwMDEwMDktNjAtMDAwMElORjtleHQ9aW5mO21pbWU9O11PUy8yIEd1aWRlIGh5cGVydGV4dCBkb2N1bWVudA0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCVRoaXNcXCBpc1xcIEluZm9cXCBmaWxlCVtmaWQ9MDAwMTAwMDEwLTYwLTAwMElORk87ZXh0PWluZm87bWltZT07XUdOVSBJbmZvIGh5cGVydGV4dCBkb2N1bWVudA0KDQojIE1hZ2ljIElEIGZvciBBZG9iZSBBY3JvYmF0IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0xMC0wOCBieSBDYXJsDQowCXN0cmluZwklUERGLQlbZmlkPTAwMDAwMDAwMS02MC0wMDE1OTMwO2V4dD1wZGY7bWltZT1hcHBsaWNhdGlvbi9wZGY7XVBvcnRhYmxlIGRvY3VtZW50IGZvcm1hdCBkb2N1bWVudCBmaWxlIChQREYpDQo+NQlzdHJpbmcJeAksIHZlcnNpb24gJS4xcw0KPjcJc3RyaW5nCXgJLiUuMXMNCg0KIyBNYWdpYyBJRCBmb3IgTWljcm9zb2Z0IFdvcmRwYWQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCXtcXFxccnRmCVtmaWQ9MDAwMDAxMDAxLTYwLTAwMDBSVEY7ZXh0PXJ0ZjttaW1lPXRleHQvcnRmO11SaWNoIFRleHQgRm9ybWF0IGRvY3VtZW50DQo+NQlzdHJpbmcJeAksIHZlcnNpb24gJS4xcy54DQoNCiMgTWFnaWMgSUQgZm9yIERvY0Jvb2sgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTEwLTA4IGJ5IENhcmwNCjAJc3RyaW5nCTwhRE9DVFlQRVxcIAlbZmlkPTAwMDAwMDAwMS02MC0wMDA4ODc5O2V4dD1zZ21sLHNnbTttaW1lPXRleHQvc2dtbDtdU3RhbmRhcmQgZ2VuZXJhbCBtYXJrdXAgbGFuZ3VhZ2UgZG9jdW1lbnQgKFNHTUwpDQoNCiMgTWFnaWMgSUQgZm9yIFR1cmJvIEMgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVRVUkJPXFwgQ1xcIEhFTFBcXCBGSUxFXFx4MDAJW2ZpZD0wMDAwMDEwMDUtNjAtMDAwMFRDSDtleHQ9dGNoO21pbWU9O11UdXJibyBDIGhlbHAgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBUdXJibyBQYXNjYWwgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCVRVUkJPXFwgUEFTQ0FMXFwgSEVMUFxcIEZJTEVcXHgwMAlbZmlkPTAwMDAwMTAwNS02MC0wMDAwVFBIO2V4dD10cGg7bWltZT07XUJvcmxhbmQgUGFzY2FsIGhlbHAgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBUdXJibyBWaXNpb24gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJbGVsb25nCTB4NDY0ODQyNDYJW2ZpZD0wMDAwMDEwMDUtNjAtMDAwMFRWSDtleHQ9dHZoO21pbWU9O11Cb3JsYW5kIFR1cmJvIFZpc2lvbiBoZWxwIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgV29yZHBlcmZlY3QgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCVxceEZGV1BDCVtmaWQ9MDAwMDAxMDA4LTYwLTAwMDBXUEQ7ZXh0PXdwZCxkb2M7bWltZT1hcHBsaWNhdGlvbi92bmQud29yZHBlcmZlY3Q7XVdvcmRwZXJmZWN0IGRvY3VtZW50DQomOQlieXRlCTB4MEEJDQo+MTAJYnl0ZQl4CSwgdmVyc2lvbiAlZA0KPjExCWJ5dGUJeAkuJWQNCg0KIyBNYWdpYyBJRCBmb3IgTWljcm9zb2Z0IFdyaXRlIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCWxlc2hvcnQJMDEzNzA2MQlbZmlkPTAwMDAwMTAwMS02MC0wMDAwV1JJO2V4dD13cmk7bWltZT07XU1pY3Jvc29mdCBXcml0ZSBkb2N1bWVudA0KJjIJbGVzaG9ydAkweDAwMDAJDQoNCiMgTWFnaWMgSUQgZm9yIE1pY3Jvc29mdCBXcml0ZSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlsZXNob3J0CTAxMzcwNjIJW2ZpZD0wMDAwMDEwMDEtNjAtMDAwMFdSSTtleHQ9d3JpO21pbWU9O11NaWNyb3NvZnQgV3JpdGUgZG9jdW1lbnQNCiYyCWxlc2hvcnQJMHgwMDAwCQ0KDQojIE1hZ2ljIElEIGZvciBYLVdpbmRvd3MgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA5LTAxIGJ5IENhcmwNCjAJc3RyaW5nCVNUQVJURk9OVFxcIAlbZmlkPTAwMDAwMTAwMy03MC0wMDAwQkRGO2V4dD1iZGY7bWltZT07XUFkb2JlIGdseXBoIGJpdG1hcCBkaXN0cmlidXRpb24gZm9ybWF0IGZvbnQgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBEZWx1eGUgUGFpbnQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA5LTAxIGJ5IENhcmwNCjAJc3RyaW5nCUNGXFx4MDFcXHgwMAlbZmlkPTAwMDAwMTAxMC03MC0wMDAwMDBDO2V4dD1jO21pbWU9O11EZWx1eGUgcGFpbnQgbXVsdGktY29sb3VyIGZvbnQgZmlsZQ0KDQojIE1hZ2ljIElEIGZvciBGaWdsZXQgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA5LTAxIGJ5IENhcmwNCjAJc3RyaW5nCWZsZjIJW2ZpZD0wMDAxMDAxMjItNzAtMDAwMEZMRjtleHQ9ZmxmO21pbWU9O11GaWdsZXQgZm9udCBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFBDTCA1IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wOS0wMSBieSBDYXJsDQowCXN0cmluZwlcXHgwMFxceDQ0XFx4MDBcXHgwMVxceDAwXFx4MDBcXHgwMFxceDFBCVtmaWQ9MDAwMDAxMzM2LTcwLTAwMDBMSUI7ZXh0PWxpYix0eXBlO21pbWU9O11JbnRlbGxpZm9udCBTY2FsYWJsZSBUeXBlZmFjZSBGb3JtYXQgIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgRGVsdXhlIFBhaW50IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wOS0wMSBieSBDYXJsDQowCXN0cmluZwlcXHgxQlxceDI5XFx4NzNcXHgzNlxceDM0XFx4NTdcXHgwMFxceDQwCVtmaWQ9MDAwMDAxMDEwLTcwLTAwMDAwME07ZXh0PW07bWltZT07XURlbHV4ZSBwYWludCBtb25vIGNvbG91ciBmb250IGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0yMCBieSBDYXJsDQowCWJlbG9uZwkweDAwMDEwMDAwCVtmaWQ9MDAwMDAxMDAxLTcwLTAwMDBUVEY7ZXh0PW90Zix0dGY7bWltZT07XU9wZW50eXBlIGZvbnQgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTIwIGJ5IENhcmwNCjAJc3RyaW5nCU9UVE8JW2ZpZD0wMDAwMDEwMDEtNzAtMDAwMFRURjtleHQ9b3RmLHR0ZjttaW1lPTtdT3BlbnR5cGUgZm9udCBmaWxlLCBDRkYgdHlwZQ0KDQojIE1hZ2ljIElEIGZvciBYLVdpbmRvd3MgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA5LTAxIGJ5IENhcmwNCjAJYmVsb25nCTB4MDE2NjYzNzAJW2ZpZD0wMDAwMDAwMDAtNzAtMDAwMFBDRjtleHQ9cGNmO21pbWU9O11Qb3J0YWJsZSBjb21waWxlZCBmb3JtYXQgKFBDRikgZm9udCBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMjAgYnkgQ2FybA0KMAlzdHJpbmcJJSFGb250VHlwZTEJW2ZpZD0wMDAwMDAwMDEtNzAtMDAwOTU0MTtleHQ9cGZhO21pbWU9O11UeXBlIDEgZm9udCBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMjAgYnkgQ2FybA0KMAlzdHJpbmcJJSFQUy1BZG9iZUZvbnQtMS4wCVtmaWQ9MDAwMDAwMDAxLTcwLTAwMDk1NDE7ZXh0PXBmYTttaW1lPTtdVHlwZSAxIGZvbnQgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTIwIGJ5IENhcmwNCjYJc3RyaW5nCSUhRm9udFR5cGUxCVtmaWQ9MDAwMDAxMDAzLTcwLTAwMDBQRkI7ZXh0PXBmYjttaW1lPTtdVHlwZSAxIHByaW50ZXIgZm9udCBiaW5hcnkgZmlsZQ0KJjAJYnl0ZQkweDgwCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTIwIGJ5IENhcmwNCjYJc3RyaW5nCSUhUFMtQWRvYmVGb250LTEuMAlbZmlkPTAwMDAwMTAwMy03MC0wMDAwUEZCO2V4dD1wZmI7bWltZT07XVR5cGUgMSBwcmludGVyIGZvbnQgYmluYXJ5IGZpbGUNCiYwCWJ5dGUJMHg4MAkNCg0KIyBNYWdpYyBJRCBmb3IgWC1XaW5kb3dzLCBHZW1ET1MgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA5LTAxIGJ5IENhcmwNCjIJc3RyaW5nCVxceDJFXFx4MzBcXHgwRFxceDBBXFx4MDBcXHgwMAlbZmlkPTAwMDAwMTMzNy03MC0wMDAwU1BEO2V4dD1zcGQ7bWltZT07XUJpdHN0cmVhbSBTcGVlZG8gZm9udCBmaWxlDQomMAlzdHJpbmcJRAkNCj4yNAlzdHJpbmcJeAlbdGl0bGU9JS43MHNdDQo+MQlzdHJpbmcJeAksdmVyc2lvbiAlLjFzLjANCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0yMCBieSBDYXJsDQowCXN0cmluZwl0cnVlCVtmaWQ9MDAwMDAxMDAyLTcwLTAwMDBUVEY7ZXh0PXR0ZjttaW1lPTtdVHJ1ZXR5cGUgZm9udCBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMjAgYnkgQ2FybA0KMAlzdHJpbmcJdHlwMQlbZmlkPTAwMDAwMTAwMi03MC0wMDAwVFRGO2V4dD10dGY7bWltZT07XVRydWV0eXBlIGZvbnQgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTIwIGJ5IENhcmwNCjAJc3RyaW5nCVN0YXJ0Q29tcEZvbnRNZXRyaWNzCVtmaWQ9MDAwMDAxMDAzLTcwLTAwMDBBRk07ZXh0PWFmbTttaW1lPTtdQWRvYmUgY29tcG9zaXRlIGZvbnQgbWV0cmljcyBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMjAgYnkgQ2FybA0KMAlzdHJpbmcJU3RhcnRGb250TWV0cmljcwlbZmlkPTAwMDAwMTAwMy03MC0wMDAwQUZNO2V4dD1hZm07bWltZT07XUFkb2JlIGZvbnQgbWV0cmljcyBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMjAgYnkgQ2FybA0KMAlzdHJpbmcJU3RhcnRNYXN0ZXJGb250TWV0cmljcwlbZmlkPTAwMDAwMTAwMy03MC0wMDAwQUZNO2V4dD1hZm07bWltZT07XUFkb2JlIG1hc3RlciBmb250IG1ldHJpY3MgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTIwIGJ5IENhcmwNCjAJbGVzaG9ydAkweDAxMDAJW2ZpZD0wMDAwMDEwMDEtNzAtMDAwMFBGTTtleHQ9cGZtO21pbWU9O11QcmludGVyIEZvbnQgTWV0cmljcyBmaWxlIGZvciBUeXBlIDEgZm9udHMNCiY2NglsZXNob3J0CTB4MDA4MQkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCWxlbG9uZwkweDc1QjIyNjMwCVtmaWQ9MDAwMDAxMDAxLTgxLTAwMDBBU0Y7ZXh0PWFzZix3bWEsd212O21pbWU9O11NaWNyb3NvZnQgQWR2YW5jZWQgU3RyZWFtaW5nIGZvcm1hdCBtdWx0aW1lZGlhIGZpbGUNCiY2CWxlc2hvcnQJMHgxMUNGCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCVJJRkYJW2ZpZD0wMDAwMDEwMDEtODEtMDAwMEFWSTtleHQ9YXZpO21pbWU9O11BdWRpby12aWRlbyBpbnRlcmxlYXZlZCBtb3ZpZSwgbGl0dGxlLWVuZGlhbg0KJjgJc3RyaW5nCUFWSVxcIAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMyBieSBDYXJsDQowCXN0cmluZwlSSUZYCVtmaWQ9MDAwMDAxMDAxLTgxLTAwMDBBVkk7ZXh0PWF2aTttaW1lPTtdQXVkaW8tdmlkZW8gaW50ZXJsZWF2ZWQgbW92aWUsIGJpZy1lbmRpYW4NCiY4CXN0cmluZwlBVklcXCAJDQoNCiMgTWFnaWMgSUQgZm9yIFF1aWNrdGltZSBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KNAlzdHJpbmcJbWRhdAlbZmlkPTAwMDAwMTAwMi04MS0wMDAwTU9WO2V4dD1tb3YscXQ7bWltZT12aWRlby9xdWlja3RpbWU7XUFwcGxlIFF1aWNrVGltZSBtb3ZpZSBkYXRhIChtZGF0KQ0KDQojIE1hZ2ljIElEIGZvciBRdWlja3RpbWUgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjQJc3RyaW5nCW1vb3YJW2ZpZD0wMDAwMDEwMDItODEtMDAwME1PVjtleHQ9bW92LHF0O21pbWU9dmlkZW8vcXVpY2t0aW1lO11BcHBsZSBRdWlja1RpbWUgbW92aWUgcmVzb3VyY2UgKG1vb3YpDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMjAgYnkgQ2FybA0KMAliZWxvbmcJMHgxQjMJW2ZpZD0wMDAwMDAwMDEtODEtMDAwTVBFRztleHQ9bXBlLG1wZWcsbXBnO21pbWU9dmlkZW8vbXBlZztdTVBFRyBtdWx0aW1lZGlhIChhdWRpby92aWRlbykgc3RyZWFtIGZpbGUNCiZaNAliZWxvbmcJMHgwMUI5CQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTIwIGJ5IENhcmwNCjAJYmVsb25nCTB4MUJBCVtmaWQ9MDAwMDAwMDAxLTgxLTAwME1QRUc7ZXh0PW1wZSxtcGVnLG1wZzttaW1lPXZpZGVvL21wZWc7XU1QRUcgbXVsdGltZWRpYSAoYXVkaW8vdmlkZW8pIHN0cmVhbSBmaWxlDQomWjQJYmVsb25nCTB4MDFCOQkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0yMCBieSBDYXJsDQowCWJlbG9uZwkweDFFMAlbZmlkPTAwMDAwMDAwMS04MS0wMDBNUEVHO2V4dD1tcGUsbXBlZyxtcGc7bWltZT12aWRlby9tcGVnO11NUEVHIG11bHRpbWVkaWEgKGF1ZGlvL3ZpZGVvKSBzdHJlYW0gZmlsZQ0KJlo0CWJlbG9uZwkweDAxQjkJDQoNCiMgTWFnaWMgSUQgZm9yIEFsaWFzL1dhdmVmcm9udCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJTU9WSQlbZmlkPTAwMDAwMTAwNC04MS0wMDAwME1WO2V4dD1tdjttaW1lPTtdQWxpYXMvV2F2ZWZyb250IG1vdmllIGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0xOCBieSBDYXJsDQowCXN0cmluZwlPZ2dTCVtmaWQ9MDAwMDAwMDAwLTgxLTAwMDBPR0c7ZXh0PW9nZzttaW1lPWFwcGxpY2F0aW9uL29nZztdT0dHIE11bHRpbWVkaWEgY29udGFpbmVyIHN0cmVhbSBmaWxlDQoNCiMgTWFnaWMgSUQgZm9yIFJlYWxwbGF5ZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTE5IGJ5IENhcmwNCjAJc3RyaW5nCS5STUYJW2ZpZD0wMDAwMDEwMjctODEtMDAwMDBSTTtleHQ9cm0scmEscnQscnAscnBhO21pbWU9O11SZWFsTWVkaWEgbXVsdGltZWRpYSBjb250YWluZXIgc3RyZWFtIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgTWFjcm9tZWRpYSBGbGFzaCBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KMAlzdHJpbmcJRldTCVtmaWQ9MDAwMDAxMjU5LTgxLTAwMDBTV0Y7ZXh0PXN3ZjttaW1lPTtdU2hvY2t3YXZlIE1hY3JvbWVkaWEgYW5pbWF0aW9uDQo+MwlieXRlCXgJLCB2ZXJzaW9uICVkLjANCg0KIyBNYWdpYyBJRCBmb3IgVml2byBwbGF5ZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTIwIGJ5IENhcmwNCjUJc3RyaW5nCVZlcnNpb246Vml2bwlbZmlkPTAwMDAwMDAwMC04MS0wMDBWSVZPO2V4dD12aXYsdml2bzttaW1lPXZpZGVvL3ZuZC52aXZvO11WaXZvIG11bHRpbWVkaWEgc3RyZWFtIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgQWVnaXMgQW5pbWF0b3IgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCUZPUk0JW2ZpZD0wMDAwMDEyNjEtODItMDAwQU5JTTtleHQ9YW5pbTttaW1lPTtdQW1pZ2EgLyBTcGFydGEgc29mdHdhcmUgYW5pbWF0aW9uDQomOAlzdHJpbmcJQU5JTQkNCg0KIyBNYWdpYyBJRCBmb3IgRmFudGF2aXNpb24gbW92aWUgbWFrZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjAJc3RyaW5nCUZPUk0JW2ZpZD0wMDAwMDEyNjAtODItMDAwRkFOVDtleHQ9ZmFudDttaW1lPTtdRmFudGF2aXNpb24gbW92aWUNCiY4CXN0cmluZwlGQU5UCQ0KDQojIE1hZ2ljIElEIGZvciBBdXRvZGVzayBBbmltYXRvciBQcm8gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTA4IGJ5IENhcmwNCjQJbGVzaG9ydAkweEFGMTIJW2ZpZD0wMDAwMDEyNTQtODItMDAwMEZMQztleHQ9ZmxjO21pbWU9O11BdXRvZGVzayBBbmltYXRvciBQcm8gYW5pbWF0aW9uDQomMTIJbGVzaG9ydAkweDA4CQ0KPjgJbGVzaG9ydAl4CVtyZXM9JWR4DQo+MTAJbGVzaG9ydAl4CSVkDQo+MTIJbGVzaG9ydAl4CXg4YnBwXQ0KDQojIE1hZ2ljIElEIGZvciBBdXRvZGVzayBBbmltYXRvciBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMDggYnkgQ2FybA0KNAlsZXNob3J0CTB4QUYxMQlbZmlkPTAwMDAwMTI1NC04Mi0wMDAwRkxJO2V4dD1mbGk7bWltZT07XUF1dG9kZXNrIEFuaW1hdG9yIGFuaW1hdGlvbg0KJjEyCWxlc2hvcnQJMHgwOAkNCj44CWxlc2hvcnQJeAlbcmVzPSVkeA0KPjEwCWxlc2hvcnQJeAklZA0KPjEyCWxlc2hvcnQJeAl4OGJwcF0NCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0xMyBieSBDYXJsDQowCXN0cmluZwlcXHg4QU1OR1xceDBkXFx4MEFcXHgxQVxceDBBCVtmaWQ9MDAwMDAwMDAwLTgyLTAwMDBNTkc7ZXh0PW1uZzttaW1lPTtdTXVsdGktaW1hZ2UgbmV0d29yayBncmFwaGljcyBhbmltYXRpb24gZmlsZQ0KPjB4MTAJYmVsb25nCXgJW3Jlcz0lZHgNCj4weDE0CWJlbG9uZwl4CSVkXQ0KDQojIE1hZ2ljIElEIGZvciBDeWJlcnBhaW50IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0wOCBieSBDYXJsDQowCWJlc2hvcnQJMHhGRURCCVtmaWQ9MDAwMDAwMDAwLTgyLTAwMDBTRVE7ZXh0PXNlcTttaW1lPTtdQ3liZXJwYWludCBBbmltYXRpb24gU2VxdWVuY2UNCiYyCWJlc2hvcnQJMHgwMDAwCQ0KDQojIE1hZ2ljIElEIGZvciBRdWlja2VuLE1vbmV5IGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wOC0wMSBieSBDYXJsDQowCXN0cmluZwlPRlhIRUFERVI6CVtmaWQ9MDAwMDAwMDA4LTkxLTAwMDBRRlg7ZXh0PXFmeDttaW1lPTtdT3BlbiBmaW5hbmNpYWwgZXhjaGFuZ2UgZmlsZSAoU0dNTCkNCg0KIyBNYWdpYyBJRCBmb3IgUXVpY2tlbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDgtMDEgYnkgQ2FybA0KMQlzdHJpbmcJQWNjb3VudAlbZmlkPTAwMDAwMTMzNS05MS0wMDAwUUlGO2V4dD1xaWY7bWltZT07XVF1aWNrZW4gaW50ZXJjaGFuZ2UgZmlsZQ0KJjAJYnl0ZQkzMwkNCg0KIyBNYWdpYyBJRCBmb3IgUXVpY2tlbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDgtMDEgYnkgQ2FybA0KMQlzdHJpbmcJVHlwZTpCYW5rCVtmaWQ9MDAwMDAxMzM1LTkxLTAwMDBRSUY7ZXh0PXFpZjttaW1lPTtdUXVpY2tlbiBpbnRlcmNoYW5nZSBmaWxlDQomMAlieXRlCTMzCQ0KDQojIE1hZ2ljIElEIGZvciBRdWlja2VuIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wOC0wMSBieSBDYXJsDQoxCXN0cmluZwlUeXBlOkNhc2gJW2ZpZD0wMDAwMDEzMzUtOTEtMDAwMFFJRjtleHQ9cWlmO21pbWU9O11RdWlja2VuIGludGVyY2hhbmdlIGZpbGUNCiYwCWJ5dGUJMzMJDQoNCiMgTWFnaWMgSUQgZm9yIFF1aWNrZW4gZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA4LTAxIGJ5IENhcmwNCjEJc3RyaW5nCVR5cGU6Q2F0CVtmaWQ9MDAwMDAxMzM1LTkxLTAwMDBRSUY7ZXh0PXFpZjttaW1lPTtdUXVpY2tlbiBpbnRlcmNoYW5nZSBmaWxlDQomMAlieXRlCTMzCQ0KDQojIE1hZ2ljIElEIGZvciBRdWlja2VuIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wOC0wMSBieSBDYXJsDQoxCXN0cmluZwlUeXBlOkNDYXJkCVtmaWQ9MDAwMDAxMzM1LTkxLTAwMDBRSUY7ZXh0PXFpZjttaW1lPTtdUXVpY2tlbiBpbnRlcmNoYW5nZSBmaWxlDQomMAlieXRlCTMzCQ0KDQojIE1hZ2ljIElEIGZvciBRdWlja2VuIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wOC0wMSBieSBDYXJsDQoxCXN0cmluZwlUeXBlOkNsYXNzCVtmaWQ9MDAwMDAxMzM1LTkxLTAwMDBRSUY7ZXh0PXFpZjttaW1lPTtdUXVpY2tlbiBpbnRlcmNoYW5nZSBmaWxlDQomMAlieXRlCTMzCQ0KDQojIE1hZ2ljIElEIGZvciBRdWlja2VuIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wOC0wMSBieSBDYXJsDQoxCXN0cmluZwlUeXBlOkludnN0CVtmaWQ9MDAwMDAxMzM1LTkxLTAwMDBRSUY7ZXh0PXFpZjttaW1lPTtdUXVpY2tlbiBpbnRlcmNoYW5nZSBmaWxlDQomMAlieXRlCTMzCQ0KDQojIE1hZ2ljIElEIGZvciBRdWlja2VuIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wOC0wMSBieSBDYXJsDQoxCXN0cmluZwlUeXBlOk1lbW9yaXplZAlbZmlkPTAwMDAwMTMzNS05MS0wMDAwUUlGO2V4dD1xaWY7bWltZT07XVF1aWNrZW4gaW50ZXJjaGFuZ2UgZmlsZQ0KJjAJYnl0ZQkzMwkNCg0KIyBNYWdpYyBJRCBmb3IgUXVpY2tlbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDgtMDEgYnkgQ2FybA0KMQlzdHJpbmcJVHlwZTpPdGggQQlbZmlkPTAwMDAwMTMzNS05MS0wMDAwUUlGO2V4dD1xaWY7bWltZT07XVF1aWNrZW4gaW50ZXJjaGFuZ2UgZmlsZQ0KJjAJYnl0ZQkzMwkNCg0KIyBNYWdpYyBJRCBmb3IgUXVpY2tlbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMDgtMDEgYnkgQ2FybA0KMQlzdHJpbmcJVHlwZTpPdGggTAlbZmlkPTAwMDAwMTMzNS05MS0wMDAwUUlGO2V4dD1xaWY7bWltZT07XVF1aWNrZW4gaW50ZXJjaGFuZ2UgZmlsZQ0KJjAJYnl0ZQkzMwkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xMSBieSBDYXJsDQowCXN0cmluZwlSUkcJW2ZpZD0wMDAwMDEwMDEtQjAtMDAwMENSRDtleHQ9Y3JkO21pbWU9O11XaW5kb3dzIDMueCBjYXJkZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTExIGJ5IENhcmwNCjAJc3RyaW5nCU1HQwlbZmlkPTAwMDAwMTAwMS1CMC0wMDAwQ1JEO2V4dD1jcmQ7bWltZT07XVdpbmRvd3MgTlQgMy41MSBjYXJkIGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wMi0xNCBieSBDYXJsDQowCXN0cmluZwlCRUdJTjpWQ0FSRAlbZmlkPTAwMDAwMTAxNS1CMC0wMDAwVkNGO2V4dD12Y2Y7bWltZT07XXZDYXJkIEJ1c2luZXNzIENhcmQgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTAyLTE0IGJ5IENhcmwNCjAJc3RyaW5nCVxceGI1XFx4YTJcXHhiMFxceGIzXFx4YjNcXHhiMFxceGEyXFx4YjUJW2ZpZD0wMDAwMDEwMDEtQjEtMDAwMENBTDtleHQ9Y2FsO21pbWU9O11NaWNyb3NvZnQgd2luZG93cyBjYWxlbmRhciBmaWxlDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDItMTQgYnkgQ2FybA0KMAlzdHJpbmcJQkVHSU46VkNBTEVOREFSCVtmaWQ9MDAwMDAxMDE2LUIxLTAwMDBWQ1M7ZXh0PXZjczttaW1lPTtddkNhbGVuZGFyL2lDYWxlbmRhciBBZ2VuZGEgZmlsZQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTE3IGJ5IENhcmwNCjAJc3RyaW5nCVxceDAwXFx4MDBcXHgwMFxceDNDXFx4MDBcXHgwMFxceDAwXFx4M0YJW2ZpZD0wMDAwMDAwMDctRjMtMDAwMFhNTDtleHQ9eG1sO21pbWU9dGV4dC94bWw7XUV4dGVuc2libGUgTWFya3VwIGxhbmd1YWdlIChYTUwpIGZpbGUsIFVURi0zMkJFIGVuY29kZWQNCiY4CWJlbG9uZwkweDAwMDAwMDc4CQ0KJjEyCWJlbG9uZwkweDAwMDAwMDZkCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTE3IGJ5IENhcmwNCjAJc3RyaW5nCVxceDAwXFx4MDBcXHhmZVxceGZmXFx4MDBcXHgwMFxceDAwXFx4M0MJW2ZpZD0wMDAwMDAwMDctRjMtMDAwMFhNTDtleHQ9eG1sO21pbWU9dGV4dC94bWw7XUV4dGVuc2libGUgTWFya3VwIGxhbmd1YWdlIChYTUwpIGZpbGUsIFVURi0zMkJFIGVuY29kZWQNCiY4CWJlbG9uZwkweDAwMDAwMDNGCQ0KJjEyCWJlbG9uZwkweDAwMDAwMDc4CQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTE3IGJ5IENhcmwNCjAJc3RyaW5nCVxceDAwXFx4M0NcXHgwMFxceDNGXFx4MDBcXHg3OFxceDAwXFx4NkRcXHgwMFxceDZjCVtmaWQ9MDAwMDAwMDA3LUYzLTAwMDBYTUw7ZXh0PXhtbDttaW1lPXRleHQveG1sO11FeHRlbnNpYmxlIE1hcmt1cCBsYW5ndWFnZSAoWE1MKSBmaWxlLCBVVEYtMTZCRSBlbmNvZGVkDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMTcgYnkgQ2FybA0KMAlzdHJpbmcJXFx4M0NcXHgwMFxceDAwXFx4MDBcXHgzRlxceDAwXFx4MDBcXHgwMAlbZmlkPTAwMDAwMDAwNy1GMy0wMDAwWE1MO2V4dD14bWw7bWltZT10ZXh0L3htbDtdRXh0ZW5zaWJsZSBNYXJrdXAgbGFuZ3VhZ2UgKFhNTCkgZmlsZSwgVVRGLTMyTEUgZW5jb2RlZA0KJjgJYmVzaG9ydAkweDAwMDAwMDc4CQ0KJjEyCWJlc2hvcnQJMHgwMDAwMDA2ZAkNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0xNyBieSBDYXJsDQowCXN0cmluZwlcXHgzQ1xceDAwXFx4M0ZcXHgwMFxceDc4XFx4MDBcXHg2RFxceDAwXFx4NmNcXHgwMAlbZmlkPTAwMDAwMDAwNy1GMy0wMDAwWE1MO2V4dD14bWw7bWltZT10ZXh0L3htbDtdRXh0ZW5zaWJsZSBNYXJrdXAgbGFuZ3VhZ2UgKFhNTCkgZmlsZSwgVVRGLTE2TEUgZW5jb2RlZA0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTE3IGJ5IENhcmwNCjAJc3RyaW5nCVxceGVmXFx4YmJcXHhiZjw/eG1sCVtmaWQ9MDAwMDAwMDA3LUYzLTAwMDBYTUw7ZXh0PXhtbDttaW1lPXRleHQveG1sO11FeHRlbnNpYmxlIE1hcmt1cCBsYW5ndWFnZSAoWE1MKSBmaWxlLCBVVEYtOCBlbmNvZGVkDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMTcgYnkgQ2FybA0KMAlzdHJpbmcJXFx4ZmVcXHhmZlxceDAwXFx4M0NcXHgwMFxceDNGXFx4MDBcXHg3OFxceDAwXFx4NkQJW2ZpZD0wMDAwMDAwMDctRjMtMDAwMFhNTDtleHQ9eG1sO21pbWU9dGV4dC94bWw7XUV4dGVuc2libGUgTWFya3VwIGxhbmd1YWdlIChYTUwpIGZpbGUsIFVURi0xNkJFIGVuY29kZWQNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNS0xNyBieSBDYXJsDQowCXN0cmluZwlcXHhmZlxceGZlXFx4MDBcXHgwMFxceDNDXFx4MDBcXHgwMFxceDAwCVtmaWQ9MDAwMDAwMDA3LUYzLTAwMDBYTUw7ZXh0PXhtbDttaW1lPXRleHQveG1sO11FeHRlbnNpYmxlIE1hcmt1cCBsYW5ndWFnZSAoWE1MKSBmaWxlLCBVVEYtMzJMRSBlbmNvZGVkDQomOAliZXNob3J0CTB4M0YwMDAwMDAJDQomMTIJYmVzaG9ydAkweDc4MDAwMDAwCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTA1LTE3IGJ5IENhcmwNCjAJc3RyaW5nCVxceGZmXFx4ZmVcXHgzQ1xceDAwXFx4M0ZcXHgwMFxceDc4XFx4MDBcXHg2RFxceDAwCVtmaWQ9MDAwMDAwMDA3LUYzLTAwMDBYTUw7ZXh0PXhtbDttaW1lPXRleHQveG1sO11FeHRlbnNpYmxlIE1hcmt1cCBsYW5ndWFnZSAoWE1MKSBmaWxlLCBVVEYtMTZMRSBlbmNvZGVkDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDUtMTcgYnkgQ2FybA0KMAlzdHJpbmcJPD94bWwJW2ZpZD0wMDAwMDAwMDctRjMtMDAwMFhNTDtleHQ9eG1sO21pbWU9dGV4dC94bWw7XUV4dGVuc2libGUgTWFya3VwIGxhbmd1YWdlIChYTUwpIGZpbGUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0wNC0yMCBieSBDYXJsDQowCXN0cmluZwlcXHhkMFxceGNmXFx4MTFcXHhlMFxceGExXFx4YjFcXHgxYVxceGUxCVtmaWQ9MDAwMDAxMDAxLUY0LTAwMDBPTEU7ZXh0PW9sZTttaW1lPTtdTWljcm9zb2Z0IE9MRSBDb21wb3VuZCBmaWxlLCBiaWctZW5kaWFuDQomMHgxQwlsZXNob3J0CSEweEZGRkUJDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMDQtMjAgYnkgQ2FybA0KMAlzdHJpbmcJXFx4ZDBcXHhjZlxceDExXFx4ZTBcXHhhMVxceGIxXFx4MWFcXHhlMQlbZmlkPTAwMDAwMTAwMS1GNC0wMDAwT0xFO2V4dD1vbGU7bWltZT07XU1pY3Jvc29mdCBPTEUgQ29tcG91bmQgZmlsZSwgbGl0dGxlLWVuZGlhbg0KJjB4MUMJbGVzaG9ydAkweEZGRkUJDQo+MHgxQQlsZXNob3J0CXgJLCB2ZXJzaW9uICVkDQo+MHgxOAlsZXNob3J0CXgJLiVkDQoNCiMgTWFnaWMgSUQgZm9yIEZyZWVwYXNjYWwgY29tcGlsZXIgZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTEwLTI0IGJ5IENhcmwNCjAJc3RyaW5nCVBQVTAJW2ZpZD0wMDAxMDAxMjAtMTEtMDAwMFBQVTtleHQ9cHB1LHBwbCxwcG8scHB3LHBwYSxwcHQ7bWltZT07XUZyZWVwYXNjYWwgdW5pdCBmaWxlDQo+MHgwNAlzdHJpbmcJeAksIHZlcnNpb24gJS4ycw0KDQojIE1hZ2ljIElEIGZvciBTbWFydGNhcmRzIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0xMC0yOSBieSBDYXJsDQowCWJlbG9uZwkweDAwRkFDQURFCVtmaWQ9MDAwMDAxMDExLTEwLTAwMDBFWFA7ZXh0PWV4cDttaW1lPTtdSmF2YWNhcmQgQVBJIGV4cG9ydCBmaWxlDQo+MHgwNQlieXRlCXgJLCB2ZXJzaW9uICVkDQo+NAlieXRlCXgJLiVkDQoNCiMgU3VibWl0dGVkIG9uIDIwMDQtMTAtMjkgYnkgQ2FybA0KMAlzdHJpbmcJLXJvbTFmcy0JW2ZpZD0wMDAxMDAxMjEtMEYtMDAwMElNRztleHQ9aW1nO21pbWU9O11TaW1wbGUgUk9NIGZpbGVzeXN0ZW0gKFJPTUZTKQ0KPjE2CXN0cmluZwl4CVt0aXRsZT0lc10NCg0KIyBNYWdpYyBJRCBmb3IgVmlydHVhbCBQQyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMTAtMjkgYnkgQ2FybA0KMAlzdHJpbmcJY29uZWN0aXgJW2ZpZD0wMDAwMDEwMDEtMEYtMDAwMFZIRDtleHQ9dmhkO21pbWU9O11WaXJ0dWFsIFBDIEhhcmQgZHJpdmUNCg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0xMS0xMiBieSBDYXJsIEVyaWMgQ29kZXJlDQowCXN0cmluZwklIVBTLUFkb2JlLQlbZmlkPTAwMDAwMTAwMy0zMy0wMDAwMFBTO2V4dD1wcyxlcHM7bWltZT1hcHBsaWNhdGlvbi9wb3N0c2NyaXB0O11Qb3N0c2NyaXB0IGZpbGUNCj4xMQlzdHJpbmcJeAksIGxldmVsICUuMXMNCj4xMwlzdHJpbmcJeAkuJS4xcw0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTExLTEyIGJ5IENhcmwgRXJpYyBDb2RlcmUNCjAJbGVsb25nCTB4NTI2ZjZkMmUJW2ZpZD0wMDAwMDEzMzgtMEYtMDAwMFJPTTtleHQ9cm9tLGZzO21pbWU9O11lQ29zIFJPTSBGaWxlc3lzdGVtIGltYWdlIChST01GUykNCj4xNglzdHJpbmcJeAlbdGl0bGU9JS4xNnNdDQoNCiMgTWFnaWMgSUQgZm9yIFBBREdlbiBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDQtMTEtMjYgYnkgQ2FybCBFcmljIENvZGVyZQ0KMHgyOQlzdHJpbmcJPFhNTF9ESVpfSU5GTz4JW2ZpZD0wMDAwMDEzMzktQTAtMDAwMFhNTDtleHQ9eG1sO21pbWU9dGV4dC94bWw7XVBvcnRhYmxlIGFwcGxpY2F0aW9uIGRlc2NyaXB0aW9uIGZpbGUgKFhNTCkNCiYwCXN0cmluZwk8P3htbAkNCg0KIyBNYWdpYyBJRCBmb3IgUEFER2VuIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0xMS0yNiBieSBDYXJsIEVyaWMgQ29kZXJlDQoweDI4CXN0cmluZwk8WE1MX0RJWl9JTkZPPglbZmlkPTAwMDAwMTMzOS1BMC0wMDAwWE1MO2V4dD14bWw7bWltZT10ZXh0L3htbDtdUG9ydGFibGUgYXBwbGljYXRpb24gZGVzY3JpcHRpb24gZmlsZSAoWE1MKQ0KJjAJc3RyaW5nCTw/eG1sCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTExLTI2IGJ5IENhcmwgRXJpYyBDb2RlcmUNCjB4MjkJc3RyaW5nCTx4OnhtcG1ldGFcXCAJW2ZpZD0wMDAwMDEwMDMtQTAtMDAwMFhNUDtleHQ9eG1sLHhtcDttaW1lPXRleHQveG1sO11FeHRlbnNpYmxlIG1ldGFkYXRhIHBsYXRmb3JtIHNpZGVjYXIgZmlsZSAoWE1MKQ0KJjAJc3RyaW5nCTw/eG1sCQ0KDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTExLTI2IGJ5IENhcmwgRXJpYyBDb2RlcmUNCjB4MjgJc3RyaW5nCTx4OnhtcG1ldGFcXCAJW2ZpZD0wMDAwMDEwMDMtQTAtMDAwMFhNUDtleHQ9eG1sLHhtcDttaW1lPXRleHQveG1sO11FeHRlbnNpYmxlIG1ldGFkYXRhIHBsYXRmb3JtIHNpZGVjYXIgZmlsZSAoWE1MKQ0KJjAJc3RyaW5nCTw/eG1sCQ0KDQojIE1hZ2ljIElEIGZvciBUZXhpbmZvIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0xMS0yNiBieSBDYXJsIEVyaWMgQ29kZXJlDQoxCXN0cmluZwlpbnB1dCB0ZXhpbmZvCVtmaWQ9MDAwMDAxMzQwLTAwLTAwMFRFWEk7ZXh0PXRleGk7bWltZT07XVRleGluZm8gc291cmNlIGZpbGUNCg0KIyBNYWdpYyBJRCBmb3IgTVMtRE9TIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNC0xMS0yNiBieSBDYXJsIEVyaWMgQ29kZXJlDQowCXN0cmluZwlcXHhGRkZPTlQJW2ZpZD0wMDAwMDEwMDEtNzAtMDAwMENQSTtleHQ9Y3BpO21pbWU9O11NUy1ET1MgY29kZSBwYWdlIHJhc3RlciBmb250DQoNCiMgTWFnaWMgSUQgZm9yIFVOSVggZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA0LTExLTI2IGJ5IENhcmwgRXJpYyBDb2RlcmUNCjAJc3RyaW5nCVRaaWYJW2ZpZD0wMDAwMDAwMDAtOTAtMDAwMDAwMDtleHQ9O21pbWU9O11UaW1lem9uZSBpbmZvcm1hdGlvbiBkYXRhYmFzZSBmaWxlIChjb21waWxlZCkNCg0KIyBNYWdpYyBJRCBmb3IgVGVsaXggZmlsZXMuDQojIFN1Ym1pdHRlZCBvbiAyMDA1LTAzLTA0IGJ5IENhcmwgRXJpYyBDb2RlcmUNCjAJbGVsb25nCTB4MmUyYjI5MWEJW2ZpZD0wMDAxMDAxMjYtQjAtMDAwMEZPTjtleHQ9Zm9uO21pbWU9O11UZWxpeCBwaG9uZSBib29rDQomNAlsZXNob3J0CTEJDQoNCiMgTWFnaWMgSUQgZm9yIE1pY3Jvc29mdCBWaXN1YWwgQysrIGZpbGVzLg0KIyBTdWJtaXR0ZWQgb24gMjAwNS0wMy0wNCBieSBDYXJsIEVyaWMgQ29kZXJlDQowCXN0cmluZwlWQ1BDSDBcXHgwMFxceDAwCVtmaWQ9MDAwMDAxMDAxLTkwLTAwMDBQQ0g7ZXh0PXBjaDttaW1lPTtdTWljcm9zb2Z0IFZpc3VhbCBDKysgcHJlLWNvbXBpbGVkIGhlYWRlcihzKQ0KDQojIE1hZ2ljIElEIGZvciBNaWNyb3NvZnQgVmlzdWFsIEMrKyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDUtMDMtMDQgYnkgQ2FybCBFcmljIENvZGVyZQ0KMAlzdHJpbmcJTWljcm9zb2Z0XFwgQy9DKytcXCBwcm9ncmFtXFwgZGF0YWJhc2UJW2ZpZD0wMDAwMDEwMDEtOTAtMDAwMFBEQjtleHQ9cGRiO21pbWU9O11NaWNyb3NvZnQgVmlzdWFsIEMrKyBkZWJ1ZyBpbmZvcm1hdGlvbiBkYXRhYmFzZQ0KJjB4MjUJc3RyaW5nCVxceDBEXFx4MEFcXHgxQQkNCiYweDI4CXN0cmluZwlKR1xceDAwCQ0KDQojIE1hZ2ljIElEIGZvciBNaWNyb3NvZnQgVmlzdWFsIEMrKyBmaWxlcy4NCiMgU3VibWl0dGVkIG9uIDIwMDUtMDMtMDQgYnkgQ2FybCBFcmljIENvZGVyZQ0KMAlzdHJpbmcJTWljcm9zb2Z0XFwgTGlua2VyXFwgRGF0YWJhc2VcXHgwQVxceDA3XFx4MUEJW2ZpZD0wMDAwMDEwMDEtOTAtMDAwMElMSztleHQ9aWxrO21pbWU9O11NaWNyb3NvZnQgVmlzdWFsIEMrKyBsaW5rZXIgZGF0YWJhc2UNCg0KDQojIENSQzMyOjhCQzNCQTQ2DQo=' + ); +/** + * Returns the test data for a given key + * + * @param string $key + * @access public + */ + function get($key) { +/** + * data property + * + * @var array + * @access public + */ + static $data = array(); + + if (empty($data)) { + $vars = get_class_vars(__CLASS__); + foreach ($vars['data'] as $k => $v) { + $data[$k] = base64_decode($v); + if (preg_match('/^[ais]:[0-9]+/', $data[$k])) { + $data[$k] = unserialize($data[$k]); + } + } + } + + if (!isset($data[$key])) { + return false; + } + return $data[$key]; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/acl.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/acl.test.php new file mode 100644 index 000000000..a2469b051 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/acl.test.php @@ -0,0 +1,462 @@ + 'requester'); + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'Mother' => array( + 'className' => 'AclPerson', + 'foreignKey' => 'mother_id', + ) + ); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array( + 'Child' => array( + 'className' => 'AclPerson', + 'foreignKey' => 'mother_id' + ) + ); + +/** + * parentNode method + * + * @return void + * @access public + */ + function parentNode() { + if (!$this->id && empty($this->data)) { + return null; + } + if (isset($this->data['AclPerson']['mother_id'])) { + $motherId = $this->data['AclPerson']['mother_id']; + } else { + $motherId = $this->field('mother_id'); + } + if (!$motherId) { + return null; + } else { + return array('AclPerson' => array('id' => $motherId)); + } + } +} + +/** +* AclUser class +* +* @package cake +* @subpackage cake.tests.cases.libs.model.behaviors +*/ +class AclUser extends CakeTestModel { + +/** + * name property + * + * @var string + * @access public + */ + var $name = 'User'; + +/** + * useTable property + * + * @var string + * @access public + */ + var $useTable = 'users'; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Acl'); + +/** + * parentNode + * + * @access public + */ + function parentNode() { + return null; + } +} + +/** +* AclPost class +* +* @package cake +* @subpackage cake.tests.cases.libs.model.behaviors +*/ +class AclPost extends CakeTestModel { + +/** + * name property + * + * @var string + * @access public + */ + var $name = 'Post'; + +/** + * useTable property + * + * @var string + * @access public + */ + var $useTable = 'posts'; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Acl' => 'Controlled'); + +/** + * parentNode + * + * @access public + */ + function parentNode() { + return null; + } +} + +/** +* AclBehaviorTest class +* +* @package cake +* @subpackage cake.tests.cases.libs.controller.components +*/ +class AclBehaviorTestCase extends CakeTestCase { + +/** + * Aco property + * + * @var Aco + * @access public + */ + var $Aco; + +/** + * Aro property + * + * @var Aro + * @access public + */ + var $Aro; + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.person', 'core.user', 'core.post', 'core.aco', 'core.aro', 'core.aros_aco'); + +/** + * Set up the test + * + * @return void + * @access public + */ + function startTest() { + Configure::write('Acl.database', 'test_suite'); + + $this->Aco =& new Aco(); + $this->Aro =& new Aro(); + } + +/** + * tearDown method + * + * @return void + * @access public + */ + function tearDown() { + ClassRegistry::flush(); + unset($this->Aro, $this->Aco); + } + +/** + * Test Setup of AclBehavior + * + * @return void + * @access public + */ + function testSetup() { + $User =& new AclUser(); + $this->assertTrue(isset($User->Behaviors->Acl->settings['User'])); + $this->assertEqual($User->Behaviors->Acl->settings['User']['type'], 'requester'); + $this->assertTrue(is_object($User->Aro)); + + $Post =& new AclPost(); + $this->assertTrue(isset($Post->Behaviors->Acl->settings['Post'])); + $this->assertEqual($Post->Behaviors->Acl->settings['Post']['type'], 'controlled'); + $this->assertTrue(is_object($Post->Aco)); + } + +/** + * test After Save + * + * @return void + * @access public + */ + function testAfterSave() { + $Post =& new AclPost(); + $data = array( + 'Post' => array( + 'author_id' => 1, + 'title' => 'Acl Post', + 'body' => 'post body', + 'published' => 1 + ), + ); + $Post->save($data); + $result = $this->Aco->find('first', array( + 'conditions' => array('Aco.model' => 'Post', 'Aco.foreign_key' => $Post->id) + )); + $this->assertTrue(is_array($result)); + $this->assertEqual($result['Aco']['model'], 'Post'); + $this->assertEqual($result['Aco']['foreign_key'], $Post->id); + + $aroData = array( + 'Aro' => array( + 'model' => 'AclPerson', + 'foreign_key' => 2, + 'parent_id' => null + ) + ); + $this->Aro->save($aroData); + + $Person =& new AclPerson(); + $data = array( + 'AclPerson' => array( + 'name' => 'Trent', + 'mother_id' => 2, + 'father_id' => 3, + ), + ); + $Person->save($data); + $result = $this->Aro->find('first', array( + 'conditions' => array('Aro.model' => 'AclPerson', 'Aro.foreign_key' => $Person->id) + )); + $this->assertTrue(is_array($result)); + $this->assertEqual($result['Aro']['parent_id'], 5); + + $node = $Person->node(array('model' => 'AclPerson', 'foreign_key' => 8)); + $this->assertEqual(count($node), 2); + $this->assertEqual($node[0]['Aro']['parent_id'], 5); + $this->assertEqual($node[1]['Aro']['parent_id'], null); + + $aroData = array( + 'Aro' => array( + 'model' => 'AclPerson', + 'foreign_key' => 1, + 'parent_id' => null + ) + ); + $this->Aro->create(); + $this->Aro->save($aroData); + + $Person->read(null, 8); + $Person->set('mother_id', 1); + $Person->save(); + $result = $this->Aro->find('first', array( + 'conditions' => array('Aro.model' => 'AclPerson', 'Aro.foreign_key' => $Person->id) + )); + $this->assertTrue(is_array($result)); + $this->assertEqual($result['Aro']['parent_id'], 7); + + $node = $Person->node(array('model' => 'AclPerson', 'foreign_key' => 8)); + $this->assertEqual(sizeof($node), 2); + $this->assertEqual($node[0]['Aro']['parent_id'], 7); + $this->assertEqual($node[1]['Aro']['parent_id'], null); + } + +/** + * test that an afterSave on an update does not cause parent_id to become null. + * + * @return void + */ + function testAfterSaveUpdateParentIdNotNull() { + $aroData = array( + 'Aro' => array( + 'model' => 'AclPerson', + 'foreign_key' => 2, + 'parent_id' => null + ) + ); + $this->Aro->save($aroData); + + $Person =& new AclPerson(); + $data = array( + 'AclPerson' => array( + 'name' => 'Trent', + 'mother_id' => 2, + 'father_id' => 3, + ), + ); + $Person->save($data); + $result = $this->Aro->find('first', array( + 'conditions' => array('Aro.model' => 'AclPerson', 'Aro.foreign_key' => $Person->id) + )); + $this->assertTrue(is_array($result)); + $this->assertEqual($result['Aro']['parent_id'], 5); + + $Person->save(array('id' => $Person->id, 'name' => 'Bruce')); + $result = $this->Aro->find('first', array( + 'conditions' => array('Aro.model' => 'AclPerson', 'Aro.foreign_key' => $Person->id) + )); + $this->assertEqual($result['Aro']['parent_id'], 5); + } + +/** + * Test After Delete + * + * @return void + * @access public + */ + function testAfterDelete() { + $aroData = array( + 'Aro' => array( + 'model' => 'AclPerson', + 'foreign_key' => 2, + 'parent_id' => null + ) + ); + $this->Aro->save($aroData); + $Person =& new AclPerson(); + $data = array( + 'AclPerson' => array( + 'name' => 'Trent', + 'mother_id' => 2, + 'father_id' => 3, + ), + ); + $Person->save($data); + $id = $Person->id; + $node = $Person->node(); + $this->assertEqual(count($node), 2); + $this->assertEqual($node[0]['Aro']['parent_id'], 5); + $this->assertEqual($node[1]['Aro']['parent_id'], null); + + $Person->delete($id); + $result = $this->Aro->find('first', array( + 'conditions' => array('Aro.model' => 'AclPerson', 'Aro.foreign_key' => $id) + )); + $this->assertTrue(empty($result)); + $result = $this->Aro->find('first', array( + 'conditions' => array('Aro.model' => 'AclPerson', 'Aro.foreign_key' => 2) + )); + $this->assertFalse(empty($result)); + + $data = array( + 'AclPerson' => array( + 'name' => 'Trent', + 'mother_id' => 2, + 'father_id' => 3, + ), + ); + $Person->save($data); + $id = $Person->id; + $Person->delete(2); + $result = $this->Aro->find('first', array( + 'conditions' => array('Aro.model' => 'AclPerson', 'Aro.foreign_key' => $id) + )); + $this->assertTrue(empty($result)); + + $result = $this->Aro->find('first', array( + 'conditions' => array('Aro.model' => 'AclPerson', 'Aro.foreign_key' => 2) + )); + $this->assertTrue(empty($result)); + } + +/** + * Test Node() + * + * @return void + * @access public + */ + function testNode() { + $Person =& new AclPerson(); + $aroData = array( + 'Aro' => array( + 'model' => 'AclPerson', + 'foreign_key' => 2, + 'parent_id' => null + ) + ); + $this->Aro->save($aroData); + + $Person->id = 2; + $result = $Person->node(); + $this->assertTrue(is_array($result)); + $this->assertEqual(count($result), 1); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/containable.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/containable.test.php new file mode 100644 index 000000000..ad73e33ed --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/containable.test.php @@ -0,0 +1,3731 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + * @since CakePHP(tm) v 1.2.0.5669 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', array('AppModel', 'Model')); +require_once(dirname(dirname(__FILE__)) . DS . 'models.php'); + +/** + * ContainableTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class ContainableBehaviorTest extends CakeTestCase { + +/** + * Fixtures associated with this test case + * + * @var array + * @access public + */ + var $fixtures = array( + 'core.article', 'core.article_featured', 'core.article_featureds_tags', 'core.articles_tag', 'core.attachment', 'core.category', + 'core.comment', 'core.featured', 'core.tag', 'core.user' + ); + +/** + * Method executed before each test + * + * @access public + */ + function startTest() { + $this->User =& ClassRegistry::init('User'); + $this->Article =& ClassRegistry::init('Article'); + $this->Tag =& ClassRegistry::init('Tag'); + + $this->User->bindModel(array( + 'hasMany' => array('Article', 'ArticleFeatured', 'Comment') + ), false); + $this->User->ArticleFeatured->unbindModel(array('belongsTo' => array('Category')), false); + $this->User->ArticleFeatured->hasMany['Comment']['foreignKey'] = 'article_id'; + + $this->Tag->bindModel(array( + 'hasAndBelongsToMany' => array('Article') + ), false); + + $this->User->Behaviors->attach('Containable'); + $this->Article->Behaviors->attach('Containable'); + $this->Tag->Behaviors->attach('Containable'); + } + +/** + * Method executed after each test + * + * @access public + */ + function endTest() { + unset($this->Article); + unset($this->User); + unset($this->Tag); + + ClassRegistry::flush(); + } + +/** + * testContainments method + * + * @access public + * @return void + */ + function testContainments() { + $r = $this->__containments($this->Article, array('Comment' => array('conditions' => array('Comment.user_id' => 2)))); + $this->assertTrue(Set::matches('/Article/keep/Comment/conditions[Comment.user_id=2]', $r)); + + $r = $this->__containments($this->User, array( + 'ArticleFeatured' => array( + 'Featured' => array( + 'id', + 'Category' => 'name' + ) + ))); + $this->assertEqual(Set::extract('/ArticleFeatured/keep/Featured/fields', $r), array('id')); + + $r = $this->__containments($this->Article, array( + 'Comment' => array( + 'User', + 'conditions' => array('Comment' => array('user_id' => 2)), + ), + )); + $this->assertTrue(Set::matches('/User', $r)); + $this->assertTrue(Set::matches('/Comment', $r)); + $this->assertTrue(Set::matches('/Article/keep/Comment/conditions/Comment[user_id=2]', $r)); + + $r = $this->__containments($this->Article, array('Comment(comment, published)' => 'Attachment(attachment)', 'User(user)')); + $this->assertTrue(Set::matches('/Comment', $r)); + $this->assertTrue(Set::matches('/User', $r)); + $this->assertTrue(Set::matches('/Article/keep/Comment', $r)); + $this->assertTrue(Set::matches('/Article/keep/User', $r)); + $this->assertEqual(Set::extract('/Article/keep/Comment/fields', $r), array('comment', 'published')); + $this->assertEqual(Set::extract('/Article/keep/User/fields', $r), array('user')); + $this->assertTrue(Set::matches('/Comment/keep/Attachment', $r)); + $this->assertEqual(Set::extract('/Comment/keep/Attachment/fields', $r), array('attachment')); + + $r = $this->__containments($this->Article, array('Comment' => array('limit' => 1))); + $this->assertEqual(array_keys($r), array('Comment', 'Article')); + $this->assertEqual(array_shift(Set::extract('/Comment/keep', $r)), array('keep' => array())); + $this->assertTrue(Set::matches('/Article/keep/Comment', $r)); + $this->assertEqual(array_shift(Set::extract('/Article/keep/Comment/.', $r)), array('limit' => 1)); + + $r = $this->__containments($this->Article, array('Comment.User')); + $this->assertEqual(array_keys($r), array('User', 'Comment', 'Article')); + $this->assertEqual(array_shift(Set::extract('/User/keep', $r)), array('keep' => array())); + $this->assertEqual(array_shift(Set::extract('/Comment/keep', $r)), array('keep' => array('User' => array()))); + $this->assertEqual(array_shift(Set::extract('/Article/keep', $r)), array('keep' => array('Comment' => array()))); + + $r = $this->__containments($this->Tag, array('Article' => array('User' => array('Comment' => array( + 'Attachment' => array('conditions' => array('Attachment.id >' => 1)) + ))))); + $this->assertTrue(Set::matches('/Attachment', $r)); + $this->assertTrue(Set::matches('/Comment/keep/Attachment/conditions', $r)); + $this->assertEqual($r['Comment']['keep']['Attachment']['conditions'], array('Attachment.id >' => 1)); + $this->assertTrue(Set::matches('/User/keep/Comment', $r)); + $this->assertTrue(Set::matches('/Article/keep/User', $r)); + $this->assertTrue(Set::matches('/Tag/keep/Article', $r)); + } + +/** + * testInvalidContainments method + * + * @access public + * @return void + */ + function testInvalidContainments() { + $this->expectError(); + $r = $this->__containments($this->Article, array('Comment', 'InvalidBinding')); + + $this->Article->Behaviors->attach('Containable', array('notices' => false)); + $r = $this->__containments($this->Article, array('Comment', 'InvalidBinding')); + } + +/** + * testBeforeFind method + * + * @access public + * @return void + */ + function testBeforeFind() { + $r = $this->Article->find('all', array('contain' => array('Comment'))); + $this->assertFalse(Set::matches('/User', $r)); + $this->assertTrue(Set::matches('/Comment', $r)); + $this->assertFalse(Set::matches('/Comment/User', $r)); + + $r = $this->Article->find('all', array('contain' => 'Comment.User')); + $this->assertTrue(Set::matches('/Comment/User', $r)); + $this->assertFalse(Set::matches('/Comment/Article', $r)); + + $r = $this->Article->find('all', array('contain' => array('Comment' => array('User', 'Article')))); + $this->assertTrue(Set::matches('/Comment/User', $r)); + $this->assertTrue(Set::matches('/Comment/Article', $r)); + + $r = $this->Article->find('all', array('contain' => array('Comment' => array('conditions' => array('Comment.user_id' => 2))))); + $this->assertFalse(Set::matches('/Comment[user_id!=2]', $r)); + $this->assertTrue(Set::matches('/Comment[user_id=2]', $r)); + + $r = $this->Article->find('all', array('contain' => array('Comment.user_id = 2'))); + $this->assertFalse(Set::matches('/Comment[user_id!=2]', $r)); + + $r = $this->Article->find('all', array('contain' => 'Comment.id DESC')); + $ids = $descIds = Set::extract('/Comment[1]/id', $r); + rsort($descIds); + $this->assertEqual($ids, $descIds); + + $r = $this->Article->find('all', array('contain' => 'Comment')); + $this->assertTrue(Set::matches('/Comment[user_id!=2]', $r)); + + $r = $this->Article->find('all', array('contain' => array('Comment' => array('fields' => 'comment')))); + $this->assertFalse(Set::matches('/Comment/created', $r)); + $this->assertTrue(Set::matches('/Comment/comment', $r)); + $this->assertFalse(Set::matches('/Comment/updated', $r)); + + $r = $this->Article->find('all', array('contain' => array('Comment' => array('fields' => array('comment', 'updated'))))); + $this->assertFalse(Set::matches('/Comment/created', $r)); + $this->assertTrue(Set::matches('/Comment/comment', $r)); + $this->assertTrue(Set::matches('/Comment/updated', $r)); + + $r = $this->Article->find('all', array('contain' => array('Comment' => array('comment', 'updated')))); + $this->assertFalse(Set::matches('/Comment/created', $r)); + $this->assertTrue(Set::matches('/Comment/comment', $r)); + $this->assertTrue(Set::matches('/Comment/updated', $r)); + + $r = $this->Article->find('all', array('contain' => array('Comment(comment,updated)'))); + $this->assertFalse(Set::matches('/Comment/created', $r)); + $this->assertTrue(Set::matches('/Comment/comment', $r)); + $this->assertTrue(Set::matches('/Comment/updated', $r)); + + $r = $this->Article->find('all', array('contain' => 'Comment.created')); + $this->assertTrue(Set::matches('/Comment/created', $r)); + $this->assertFalse(Set::matches('/Comment/comment', $r)); + + $r = $this->Article->find('all', array('contain' => array('User.Article(title)', 'Comment(comment)'))); + $this->assertFalse(Set::matches('/Comment/Article', $r)); + $this->assertFalse(Set::matches('/Comment/User', $r)); + $this->assertTrue(Set::matches('/Comment/comment', $r)); + $this->assertFalse(Set::matches('/Comment/created', $r)); + $this->assertTrue(Set::matches('/User/Article/title', $r)); + $this->assertFalse(Set::matches('/User/Article/created', $r)); + + $r = $this->Article->find('all', array('contain' => array())); + $this->assertFalse(Set::matches('/User', $r)); + $this->assertFalse(Set::matches('/Comment', $r)); + + $this->expectError(); + $r = $this->Article->find('all', array('contain' => array('Comment' => 'NonExistingBinding'))); + } + +/** + * testContain method + * + * @access public + * @return void + */ + function testContain() { + $this->Article->contain('Comment.User'); + $r = $this->Article->find('all'); + $this->assertTrue(Set::matches('/Comment/User', $r)); + $this->assertFalse(Set::matches('/Comment/Article', $r)); + + $r = $this->Article->find('all'); + $this->assertFalse(Set::matches('/Comment/User', $r)); + } + +/** + * testFindEmbeddedNoBindings method + * + * @access public + * @return void + */ + function testFindEmbeddedNoBindings() { + $result = $this->Article->find('all', array('contain' => false)); + $expected = array( + array('Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + )), + array('Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + )), + array('Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + )) + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindFirstLevel method + * + * @access public + * @return void + */ + function testFindFirstLevel() { + $this->Article->contain('User'); + $result = $this->Article->find('all', array('recursive' => 1)); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ); + $this->assertEqual($result, $expected); + + $this->Article->contain('User', 'Comment'); + $result = $this->Article->find('all', array('recursive' => 1)); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31' + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31' + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31' + ) + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array() + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindEmbeddedFirstLevel method + * + * @access public + * @return void + */ + function testFindEmbeddedFirstLevel() { + $result = $this->Article->find('all', array('contain' => array('User'))); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array('contain' => array('User', 'Comment'))); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31' + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31' + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31' + ) + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array() + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindSecondLevel method + * + * @access public + * @return void + */ + function testFindSecondLevel() { + $this->Article->contain(array('Comment' => 'User')); + $result = $this->Article->find('all', array('recursive' => 2)); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ) + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ) + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'Comment' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->Article->contain(array('User' => 'ArticleFeatured')); + $result = $this->Article->find('all', array('recursive' => 2)); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31', + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $this->Article->contain(array('User' => array('ArticleFeatured', 'Comment'))); + $result = $this->Article->find('all', array('recursive' => 2)); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ), + 'Comment' => array( + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ), + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31', + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ), + 'Comment' => array() + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ), + 'Comment' => array( + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ), + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31' + ) + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $this->Article->contain(array('User' => array('ArticleFeatured')), 'Tag', array('Comment' => 'Attachment')); + $result = $this->Article->find('all', array('recursive' => 2)); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Attachment' => array() + ) + ), + 'Tag' => array( + array('id' => 1, 'tag' => 'tag1', 'created' => '2007-03-18 12:22:23', 'updated' => '2007-03-18 12:24:31'), + array('id' => 2, 'tag' => 'tag2', 'created' => '2007-03-18 12:24:23', 'updated' => '2007-03-18 12:26:31') + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31', + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Attachment' => array() + ) + ), + 'Tag' => array( + array('id' => 1, 'tag' => 'tag1', 'created' => '2007-03-18 12:22:23', 'updated' => '2007-03-18 12:24:31'), + array('id' => 3, 'tag' => 'tag3', 'created' => '2007-03-18 12:26:23', 'updated' => '2007-03-18 12:28:31') + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ) + ), + 'Comment' => array(), + 'Tag' => array() + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindEmbeddedSecondLevel method + * + * @access public + * @return void + */ + function testFindEmbeddedSecondLevel() { + $result = $this->Article->find('all', array('contain' => array('Comment' => 'User'))); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ) + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ) + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'Comment' => array() + ) + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array('contain' => array('User' => 'ArticleFeatured'))); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31', + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array('contain' => array('User' => array('ArticleFeatured', 'Comment')))); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ), + 'Comment' => array( + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ), + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31' + ) + ) + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31', + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ), + 'Comment' => array() + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ), + 'Comment' => array( + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ), + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31' + ) + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array('contain' => array('User' => 'ArticleFeatured', 'Tag', 'Comment' => 'Attachment'))); + $expected = array( + array( + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Attachment' => array() + ) + ), + 'Tag' => array( + array('id' => 1, 'tag' => 'tag1', 'created' => '2007-03-18 12:22:23', 'updated' => '2007-03-18 12:24:31'), + array('id' => 2, 'tag' => 'tag2', 'created' => '2007-03-18 12:24:23', 'updated' => '2007-03-18 12:26:31') + ) + ), + array( + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31', + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Attachment' => array() + ) + ), + 'Tag' => array( + array('id' => 1, 'tag' => 'tag1', 'created' => '2007-03-18 12:22:23', 'updated' => '2007-03-18 12:24:31'), + array('id' => 3, 'tag' => 'tag3', 'created' => '2007-03-18 12:26:23', 'updated' => '2007-03-18 12:28:31') + ) + ), + array( + 'Article' => array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ) + ), + 'Comment' => array(), + 'Tag' => array() + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindThirdLevel method + * + * @access public + * @return void + */ + function testFindThirdLevel() { + $this->User->contain(array('ArticleFeatured' => array('Featured' => 'Category'))); + $result = $this->User->find('all', array('recursive' => 3)); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->User->contain(array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => array('Article', 'Attachment')))); + $result = $this->User->find('all', array('recursive' => 3)); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->User->contain(array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => 'Attachment'), 'Article')); + $result = $this->User->find('all', array('recursive' => 3)); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Article' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'Article' => array(), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'Article' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'Article' => array(), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindEmbeddedThirdLevel method + * + * @access public + * @return void + */ + function testFindEmbeddedThirdLevel() { + $result = $this->User->find('all', array('contain' => array('ArticleFeatured' => array('Featured' => 'Category')))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $result = $this->User->find('all', array('contain' => array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => array('Article', 'Attachment'))))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $result = $this->User->find('all', array('contain' => array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => 'Attachment'), 'Article'))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Article' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'Article' => array(), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'Article' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'Article' => array(), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testSettingsThirdLevel method + * + * @access public + * @return void + */ + function testSettingsThirdLevel() { + $result = $this->User->find('all', array('contain' => array('ArticleFeatured' => array('Featured' => array('Category' => array('id', 'name')))))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'name' => 'Category 1' + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'name' => 'Category 1' + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $r = $this->User->find('all', array('contain' => array( + 'ArticleFeatured' => array( + 'id', 'title', + 'Featured' => array( + 'id', 'category_id', + 'Category' => array('id', 'name') + ) + ) + ))); + + $this->assertTrue(Set::matches('/User[id=1]', $r)); + $this->assertFalse(Set::matches('/Article', $r) || Set::matches('/Comment', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured', $r)); + $this->assertFalse(Set::matches('/ArticleFeatured/User', $r) || Set::matches('/ArticleFeatured/Comment', $r) || Set::matches('/ArticleFeatured/Tag', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured', $r)); + $this->assertFalse(Set::matches('/ArticleFeatured/Featured/ArticleFeatured', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured/Category', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured[id=1]', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured[id=1]/Category[id=1]', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured[id=1]/Category[name=Category 1]', $r)); + + $r = $this->User->find('all', array('contain' => array( + 'ArticleFeatured' => array( + 'title', + 'Featured' => array( + 'id', + 'Category' => 'name' + ) + ) + ))); + + $this->assertTrue(Set::matches('/User[id=1]', $r)); + $this->assertFalse(Set::matches('/Article', $r) || Set::matches('/Comment', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured', $r)); + $this->assertFalse(Set::matches('/ArticleFeatured/User', $r) || Set::matches('/ArticleFeatured/Comment', $r) || Set::matches('/ArticleFeatured/Tag', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured', $r)); + $this->assertFalse(Set::matches('/ArticleFeatured/Featured/ArticleFeatured', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured/Category', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured[id=1]', $r)); + $this->assertTrue(Set::matches('/ArticleFeatured/Featured[id=1]/Category[name=Category 1]', $r)); + + $result = $this->User->find('all', array('contain' => array( + 'ArticleFeatured' => array( + 'title', + 'Featured' => array( + 'category_id', + 'Category' => 'name' + ) + ) + ))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'title' => 'First Article', 'id' => 1, 'user_id' => 1, + 'Featured' => array( + 'category_id' => 1, 'id' => 1, + 'Category' => array( + 'name' => 'Category 1' + ) + ) + ), + array( + 'title' => 'Third Article', 'id' => 3, 'user_id' => 1, + 'Featured' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'title' => 'Second Article', 'id' => 2, 'user_id' => 3, + 'Featured' => array( + 'category_id' => 1, 'id' => 2, + 'Category' => array( + 'name' => 'Category 1' + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $orders = array( + 'title DESC', 'title DESC, published DESC', + array('title' => 'DESC'), array('title' => 'DESC', 'published' => 'DESC'), + ); + foreach ($orders as $order) { + $result = $this->User->find('all', array('contain' => array( + 'ArticleFeatured' => array( + 'title', 'order' => $order, + 'Featured' => array( + 'category_id', + 'Category' => 'name' + ) + ) + ))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'title' => 'Third Article', 'id' => 3, 'user_id' => 1, + 'Featured' => array() + ), + array( + 'title' => 'First Article', 'id' => 1, 'user_id' => 1, + 'Featured' => array( + 'category_id' => 1, 'id' => 1, + 'Category' => array( + 'name' => 'Category 1' + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'title' => 'Second Article', 'id' => 2, 'user_id' => 3, + 'Featured' => array( + 'category_id' => 1, 'id' => 2, + 'Category' => array( + 'name' => 'Category 1' + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + } + } + +/** + * testFindThirdLevelNonReset method + * + * @access public + * @return void + */ + function testFindThirdLevelNonReset() { + $this->User->contain(false, array('ArticleFeatured' => array('Featured' => 'Category'))); + $result = $this->User->find('all', array('recursive' => 3)); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->User->resetBindings(); + + $this->User->contain(false, array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => array('Article', 'Attachment')))); + $result = $this->User->find('all', array('recursive' => 3)); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->User->resetBindings(); + + $this->User->contain(false, array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => 'Attachment'), 'Article')); + $result = $this->User->find('all', array('recursive' => 3)); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Article' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'Article' => array(), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'Article' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'Article' => array(), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindEmbeddedThirdLevelNonReset method + * + * @access public + * @return void + */ + function testFindEmbeddedThirdLevelNonReset() { + $result = $this->User->find('all', array('reset' => false, 'contain' => array('ArticleFeatured' => array('Featured' => 'Category')))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->__assertBindings($this->User, array('hasMany' => array('ArticleFeatured'))); + $this->__assertBindings($this->User->ArticleFeatured, array('hasOne' => array('Featured'))); + $this->__assertBindings($this->User->ArticleFeatured->Featured, array('belongsTo' => array('Category'))); + + $this->User->resetBindings(); + + $this->__assertBindings($this->User, array('hasMany' => array('Article', 'ArticleFeatured', 'Comment'))); + $this->__assertBindings($this->User->ArticleFeatured, array('belongsTo' => array('User'), 'hasOne' => array('Featured'), 'hasMany' => array('Comment'), 'hasAndBelongsToMany' => array('Tag'))); + $this->__assertBindings($this->User->ArticleFeatured->Featured, array('belongsTo' => array('ArticleFeatured', 'Category'))); + $this->__assertBindings($this->User->ArticleFeatured->Comment, array('belongsTo' => array('Article', 'User'), 'hasOne' => array('Attachment'))); + + $result = $this->User->find('all', array('reset' => false, 'contain' => array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => array('Article', 'Attachment'))))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->__assertBindings($this->User, array('hasMany' => array('ArticleFeatured'))); + $this->__assertBindings($this->User->ArticleFeatured, array('hasOne' => array('Featured'), 'hasMany' => array('Comment'))); + $this->__assertBindings($this->User->ArticleFeatured->Featured, array('belongsTo' => array('Category'))); + $this->__assertBindings($this->User->ArticleFeatured->Comment, array('belongsTo' => array('Article'), 'hasOne' => array('Attachment'))); + + $this->User->resetBindings(); + $this->__assertBindings($this->User, array('hasMany' => array('Article', 'ArticleFeatured', 'Comment'))); + $this->__assertBindings($this->User->ArticleFeatured, array('belongsTo' => array('User'), 'hasOne' => array('Featured'), 'hasMany' => array('Comment'), 'hasAndBelongsToMany' => array('Tag'))); + $this->__assertBindings($this->User->ArticleFeatured->Featured, array('belongsTo' => array('ArticleFeatured', 'Category'))); + $this->__assertBindings($this->User->ArticleFeatured->Comment, array('belongsTo' => array('Article', 'User'), 'hasOne' => array('Attachment'))); + + $result = $this->User->find('all', array('contain' => array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => array('Article', 'Attachment')), false))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Article' => array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Article' => array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ), + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->__assertBindings($this->User, array('hasMany' => array('ArticleFeatured'))); + $this->__assertBindings($this->User->ArticleFeatured, array('hasOne' => array('Featured'), 'hasMany' => array('Comment'))); + $this->__assertBindings($this->User->ArticleFeatured->Featured, array('belongsTo' => array('Category'))); + $this->__assertBindings($this->User->ArticleFeatured->Comment, array('belongsTo' => array('Article'), 'hasOne' => array('Attachment'))); + + $this->User->resetBindings(); + $this->__assertBindings($this->User, array('hasMany' => array('Article', 'ArticleFeatured', 'Comment'))); + $this->__assertBindings($this->User->ArticleFeatured, array('belongsTo' => array('User'), 'hasOne' => array('Featured'), 'hasMany' => array('Comment'), 'hasAndBelongsToMany' => array('Tag'))); + $this->__assertBindings($this->User->ArticleFeatured->Featured, array('belongsTo' => array('ArticleFeatured', 'Category'))); + $this->__assertBindings($this->User->ArticleFeatured->Comment, array('belongsTo' => array('Article', 'User'), 'hasOne' => array('Attachment'))); + + $result = $this->User->find('all', array('reset' => false, 'contain' => array('ArticleFeatured' => array('Featured' => 'Category', 'Comment' => 'Attachment'), 'Article'))); + $expected = array( + array( + 'User' => array( + 'id' => 1, 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Article' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31' + ) + ), + 'ArticleFeatured' => array( + array( + 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Featured' => array( + 'id' => 1, 'article_featured_id' => 1, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 1, 'article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31', + 'Attachment' => array() + ), + array( + 'id' => 2, 'article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31', + 'Attachment' => array() + ), + array( + 'id' => 3, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', + 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31', + 'Attachment' => array() + ), + array( + 'id' => 4, 'article_id' => 1, 'user_id' => 1, 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31', + 'Attachment' => array() + ) + ) + ), + array( + 'id' => 3, 'user_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31', + 'Featured' => array(), + 'Comment' => array() + ) + ) + ), + array( + 'User' => array( + 'id' => 2, 'user' => 'nate', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', 'updated' => '2007-03-17 01:20:31' + ), + 'Article' => array(), + 'ArticleFeatured' => array() + ), + array( + 'User' => array( + 'id' => 3, 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31' + ), + 'Article' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31' + ) + ), + 'ArticleFeatured' => array( + array( + 'id' => 2, 'user_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31', + 'Featured' => array( + 'id' => 2, 'article_featured_id' => 2, 'category_id' => 1, 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31', + 'Category' => array( + 'id' => 1, 'parent_id' => 0, 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31' + ) + ), + 'Comment' => array( + array( + 'id' => 5, 'article_id' => 2, 'user_id' => 1, 'comment' => 'First Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:53:23', 'updated' => '2007-03-18 10:55:31', + 'Attachment' => array( + 'id' => 1, 'comment_id' => 5, 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => 6, 'article_id' => 2, 'user_id' => 2, 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', 'created' => '2007-03-18 10:55:23', 'updated' => '2007-03-18 10:57:31', + 'Attachment' => array() + ) + ) + ) + ) + ), + array( + 'User' => array( + 'id' => 4, 'user' => 'garrett', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', 'updated' => '2007-03-17 01:24:31' + ), + 'Article' => array(), + 'ArticleFeatured' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->__assertBindings($this->User, array('hasMany' => array('Article', 'ArticleFeatured'))); + $this->__assertBindings($this->User->Article); + $this->__assertBindings($this->User->ArticleFeatured, array('hasOne' => array('Featured'), 'hasMany' => array('Comment'))); + $this->__assertBindings($this->User->ArticleFeatured->Featured, array('belongsTo' => array('Category'))); + $this->__assertBindings($this->User->ArticleFeatured->Comment, array('hasOne' => array('Attachment'))); + + $this->User->resetBindings(); + $this->__assertBindings($this->User, array('hasMany' => array('Article', 'ArticleFeatured', 'Comment'))); + $this->__assertBindings($this->User->Article, array('belongsTo' => array('User'), 'hasMany' => array('Comment'), 'hasAndBelongsToMany' => array('Tag'))); + $this->__assertBindings($this->User->ArticleFeatured, array('belongsTo' => array('User'), 'hasOne' => array('Featured'), 'hasMany' => array('Comment'), 'hasAndBelongsToMany' => array('Tag'))); + $this->__assertBindings($this->User->ArticleFeatured->Featured, array('belongsTo' => array('ArticleFeatured', 'Category'))); + $this->__assertBindings($this->User->ArticleFeatured->Comment, array('belongsTo' => array('Article', 'User'), 'hasOne' => array('Attachment'))); + } + +/** + * testEmbeddedFindFields method + * + * @access public + * @return void + */ + function testEmbeddedFindFields() { + $result = $this->Article->find('all', array( + 'contain' => array('User(user)'), + 'fields' => array('title') + )); + $expected = array( + array('Article' => array('title' => 'First Article'), 'User' => array('user' => 'mariano', 'id' => 1)), + array('Article' => array('title' => 'Second Article'), 'User' => array('user' => 'larry', 'id' => 3)), + array('Article' => array('title' => 'Third Article'), 'User' => array('user' => 'mariano', 'id' => 1)), + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array( + 'contain' => array('User(id, user)'), + 'fields' => array('title') + )); + $expected = array( + array('Article' => array('title' => 'First Article'), 'User' => array('user' => 'mariano', 'id' => 1)), + array('Article' => array('title' => 'Second Article'), 'User' => array('user' => 'larry', 'id' => 3)), + array('Article' => array('title' => 'Third Article'), 'User' => array('user' => 'mariano', 'id' => 1)), + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array( + 'contain' => array( + 'Comment(comment, published)' => 'Attachment(attachment)', 'User(user)' + ), + 'fields' => array('title') + )); + if (!empty($result)) { + foreach($result as $i=>$article) { + foreach($article['Comment'] as $j=>$comment) { + $result[$i]['Comment'][$j] = array_diff_key($comment, array('id'=>true)); + } + } + } + $expected = array( + array( + 'Article' => array('title' => 'First Article', 'id' => 1), + 'User' => array('user' => 'mariano', 'id' => 1), + 'Comment' => array( + array('comment' => 'First Comment for First Article', 'published' => 'Y', 'article_id' => 1, 'Attachment' => array()), + array('comment' => 'Second Comment for First Article', 'published' => 'Y', 'article_id' => 1, 'Attachment' => array()), + array('comment' => 'Third Comment for First Article', 'published' => 'Y', 'article_id' => 1, 'Attachment' => array()), + array('comment' => 'Fourth Comment for First Article', 'published' => 'N', 'article_id' => 1, 'Attachment' => array()), + ) + ), + array( + 'Article' => array('title' => 'Second Article', 'id' => 2), + 'User' => array('user' => 'larry', 'id' => 3), + 'Comment' => array( + array('comment' => 'First Comment for Second Article', 'published' => 'Y', 'article_id' => 2, 'Attachment' => array( + 'attachment' => 'attachment.zip', 'id' => 1 + )), + array('comment' => 'Second Comment for Second Article', 'published' => 'Y', 'article_id' => 2, 'Attachment' => array()) + ) + ), + array( + 'Article' => array('title' => 'Third Article', 'id' => 3), + 'User' => array('user' => 'mariano', 'id' => 1), + 'Comment' => array() + ), + ); + $this->assertEqual($result, $expected); + } + +/** + * test that hasOne and belongsTo fields act the same in a contain array. + * + * @return void + */ + function testHasOneFieldsInContain() { + $this->Article->unbindModel(array( + 'hasMany' => array('Comment') + ), true); + unset($this->Article->Comment); + $this->Article->bindModel(array( + 'hasOne' => array('Comment') + )); + + $result = $this->Article->find('all', array( + 'fields' => array('title', 'body'), + 'contain' => array( + 'Comment' => array( + 'fields' => array('comment') + ), + 'User' => array( + 'fields' => array('user') + ) + ) + )); + $this->assertTrue(isset($result[0]['Article']['title']), 'title missing %s'); + $this->assertTrue(isset($result[0]['Article']['body']), 'body missing %s'); + $this->assertTrue(isset($result[0]['Comment']['comment']), 'comment missing %s'); + $this->assertTrue(isset($result[0]['User']['user']), 'body missing %s'); + $this->assertFalse(isset($result[0]['Comment']['published']), 'published found %s'); + $this->assertFalse(isset($result[0]['User']['password']), 'password found %s'); + } +/** + * testFindConditionalBinding method + * + * @access public + * @return void + */ + function testFindConditionalBinding() { + $this->Article->contain(array( + 'User(user)', + 'Tag' => array( + 'fields' => array('tag', 'created'), + 'conditions' => array('created >=' => '2007-03-18 12:24') + ) + )); + $result = $this->Article->find('all', array('fields' => array('title'))); + $expected = array( + array( + 'Article' => array('id' => 1, 'title' => 'First Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array(array('tag' => 'tag2', 'created' => '2007-03-18 12:24:23')) + ), + array( + 'Article' => array('id' => 2, 'title' => 'Second Article'), + 'User' => array('id' => 3, 'user' => 'larry'), + 'Tag' => array(array('tag' => 'tag3', 'created' => '2007-03-18 12:26:23')) + ), + array( + 'Article' => array('id' => 3, 'title' => 'Third Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->Article->contain(array('User(id,user)', 'Tag' => array('fields' => array('tag', 'created')))); + $result = $this->Article->find('all', array('fields' => array('title'))); + $expected = array( + array( + 'Article' => array('id' => 1, 'title' => 'First Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array( + array('tag' => 'tag1', 'created' => '2007-03-18 12:22:23'), + array('tag' => 'tag2', 'created' => '2007-03-18 12:24:23') + ) + ), + array( + 'Article' => array('id' => 2, 'title' => 'Second Article'), + 'User' => array('id' => 3, 'user' => 'larry'), + 'Tag' => array( + array('tag' => 'tag1', 'created' => '2007-03-18 12:22:23'), + array('tag' => 'tag3', 'created' => '2007-03-18 12:26:23') + ) + ), + array( + 'Article' => array('id' => 3, 'title' => 'Third Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array() + ) + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array( + 'fields' => array('title'), + 'contain' => array('User(id,user)', 'Tag' => array('fields' => array('tag', 'created'))) + )); + $expected = array( + array( + 'Article' => array('id' => 1, 'title' => 'First Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array( + array('tag' => 'tag1', 'created' => '2007-03-18 12:22:23'), + array('tag' => 'tag2', 'created' => '2007-03-18 12:24:23') + ) + ), + array( + 'Article' => array('id' => 2, 'title' => 'Second Article'), + 'User' => array('id' => 3, 'user' => 'larry'), + 'Tag' => array( + array('tag' => 'tag1', 'created' => '2007-03-18 12:22:23'), + array('tag' => 'tag3', 'created' => '2007-03-18 12:26:23') + ) + ), + array( + 'Article' => array('id' => 3, 'title' => 'Third Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->Article->contain(array( + 'User(id,user)', + 'Tag' => array( + 'fields' => array('tag', 'created'), + 'conditions' => array('created >=' => '2007-03-18 12:24') + ) + )); + $result = $this->Article->find('all', array('fields' => array('title'))); + $expected = array( + array( + 'Article' => array('id' => 1, 'title' => 'First Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array(array('tag' => 'tag2', 'created' => '2007-03-18 12:24:23')) + ), + array( + 'Article' => array('id' => 2, 'title' => 'Second Article'), + 'User' => array('id' => 3, 'user' => 'larry'), + 'Tag' => array(array('tag' => 'tag3', 'created' => '2007-03-18 12:26:23')) + ), + array( + 'Article' => array('id' => 3, 'title' => 'Third Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array() + ) + ); + $this->assertEqual($result, $expected); + + $this->assertTrue(empty($this->User->Article->hasAndBelongsToMany['Tag']['conditions'])); + + $result = $this->User->find('all', array('contain' => array( + 'Article.Tag' => array('conditions' => array('created >=' => '2007-03-18 12:24')) + ))); + + $this->assertTrue(Set::matches('/User[id=1]', $result)); + $this->assertFalse(Set::matches('/Article[id=1]/Tag[id=1]', $result)); + $this->assertTrue(Set::matches('/Article[id=1]/Tag[id=2]', $result)); + $this->assertTrue(empty($this->User->Article->hasAndBelongsToMany['Tag']['conditions'])); + + $this->assertTrue(empty($this->User->Article->hasAndBelongsToMany['Tag']['order'])); + + $result = $this->User->find('all', array('contain' => array( + 'Article.Tag' => array('order' => 'created DESC') + ))); + + $this->assertTrue(Set::matches('/User[id=1]', $result)); + $this->assertTrue(Set::matches('/Article[id=1]/Tag[id=1]', $result)); + $this->assertTrue(Set::matches('/Article[id=1]/Tag[id=2]', $result)); + $this->assertTrue(empty($this->User->Article->hasAndBelongsToMany['Tag']['order'])); + } + +/** + * testOtherFinds method + * + * @access public + * @return void + */ + function testOtherFinds() { + $result = $this->Article->find('count'); + $expected = 3; + $this->assertEqual($result, $expected); + + $result = $this->Article->find('count', array('conditions' => array('Article.id >' => '1'))); + $expected = 2; + $this->assertEqual($result, $expected); + + $result = $this->Article->find('count', array('contain' => array())); + $expected = 3; + $this->assertEqual($result, $expected); + + $this->Article->contain(array('User(id,user)', 'Tag' => array('fields' => array('tag', 'created'), 'conditions' => array('created >=' => '2007-03-18 12:24')))); + $result = $this->Article->find('first', array('fields' => array('title'))); + $expected = array( + 'Article' => array('id' => 1, 'title' => 'First Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array(array('tag' => 'tag2', 'created' => '2007-03-18 12:24:23')) + ); + $this->assertEqual($result, $expected); + + $this->Article->contain(array('User(id,user)', 'Tag' => array('fields' => array('tag', 'created')))); + $result = $this->Article->find('first', array('fields' => array('title'))); + $expected = array( + 'Article' => array('id' => 1, 'title' => 'First Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array( + array('tag' => 'tag1', 'created' => '2007-03-18 12:22:23'), + array('tag' => 'tag2', 'created' => '2007-03-18 12:24:23') + ) + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('first', array( + 'fields' => array('title'), + 'order' => 'Article.id DESC', + 'contain' => array('User(id,user)', 'Tag' => array('fields' => array('tag', 'created'))) + )); + $expected = array( + 'Article' => array('id' => 3, 'title' => 'Third Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Tag' => array() + ); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('list', array( + 'contain' => array('User(id,user)'), + 'fields' => array('Article.id', 'Article.title') + )); + $expected = array( + 1 => 'First Article', + 2 => 'Second Article', + 3 => 'Third Article' + ); + $this->assertEqual($result, $expected); + } + +/** + * testPaginate method + * + * @access public + * @return void + */ + function testPaginate() { + $Controller =& new Controller(); + $Controller->uses = array('Article'); + $Controller->passedArgs[] = '1'; + $Controller->params['url'] = array(); + $Controller->constructClasses(); + + $Controller->paginate = array('Article' => array('fields' => array('title'), 'contain' => array('User(user)'))); + $result = $Controller->paginate('Article'); + $expected = array( + array('Article' => array('title' => 'First Article'), 'User' => array('user' => 'mariano', 'id' => 1)), + array('Article' => array('title' => 'Second Article'), 'User' => array('user' => 'larry', 'id' => 3)), + array('Article' => array('title' => 'Third Article'), 'User' => array('user' => 'mariano', 'id' => 1)), + ); + $this->assertEqual($result, $expected); + + $r = $Controller->Article->find('all'); + $this->assertTrue(Set::matches('/Article[id=1]', $r)); + $this->assertTrue(Set::matches('/User[id=1]', $r)); + $this->assertTrue(Set::matches('/Tag[id=1]', $r)); + + $Controller->paginate = array('Article' => array('contain' => array('Comment(comment)' => 'User(user)'), 'fields' => array('title'))); + $result = $Controller->paginate('Article'); + $expected = array( + array( + 'Article' => array('title' => 'First Article', 'id' => 1), + 'Comment' => array( + array( + 'comment' => 'First Comment for First Article', + 'user_id' => 2, + 'article_id' => 1, + 'User' => array('user' => 'nate') + ), + array( + 'comment' => 'Second Comment for First Article', + 'user_id' => 4, + 'article_id' => 1, + 'User' => array('user' => 'garrett') + ), + array( + 'comment' => 'Third Comment for First Article', + 'user_id' => 1, + 'article_id' => 1, + 'User' => array('user' => 'mariano') + ), + array( + 'comment' => 'Fourth Comment for First Article', + 'user_id' => 1, + 'article_id' => 1, + 'User' => array('user' => 'mariano') + ) + ) + ), + array( + 'Article' => array('title' => 'Second Article', 'id' => 2), + 'Comment' => array( + array( + 'comment' => 'First Comment for Second Article', + 'user_id' => 1, + 'article_id' => 2, + 'User' => array('user' => 'mariano') + ), + array( + 'comment' => 'Second Comment for Second Article', + 'user_id' => 2, + 'article_id' => 2, + 'User' => array('user' => 'nate') + ) + ) + ), + array( + 'Article' => array('title' => 'Third Article', 'id' => 3), + 'Comment' => array() + ), + ); + $this->assertEqual($result, $expected); + + $r = $Controller->Article->find('all'); + $this->assertTrue(Set::matches('/Article[id=1]', $r)); + $this->assertTrue(Set::matches('/User[id=1]', $r)); + $this->assertTrue(Set::matches('/Tag[id=1]', $r)); + + $Controller->Article->unbindModel(array('hasMany' => array('Comment'), 'belongsTo' => array('User'), 'hasAndBelongsToMany' => array('Tag')), false); + $Controller->Article->bindModel(array('hasMany' => array('Comment'), 'belongsTo' => array('User')), false); + + $Controller->paginate = array('Article' => array('contain' => array('Comment(comment)', 'User(user)'), 'fields' => array('title'))); + $r = $Controller->paginate('Article'); + $this->assertTrue(Set::matches('/Article[id=1]', $r)); + $this->assertTrue(Set::matches('/User[id=1]', $r)); + $this->assertTrue(Set::matches('/Comment[article_id=1]', $r)); + $this->assertFalse(Set::matches('/Comment[id=1]', $r)); + + $r = $this->Article->find('all'); + $this->assertTrue(Set::matches('/Article[id=1]', $r)); + $this->assertTrue(Set::matches('/User[id=1]', $r)); + $this->assertTrue(Set::matches('/Comment[article_id=1]', $r)); + $this->assertTrue(Set::matches('/Comment[id=1]', $r)); + } + +/** + * testOriginalAssociations method + * + * @access public + * @return void + */ + function testOriginalAssociations() { + $this->Article->Comment->Behaviors->attach('Containable'); + + $options = array( + 'conditions' => array( + 'Comment.published' => 'Y', + ), + 'contain' => 'User', + 'recursive' => 1 + ); + + $firstResult = $this->Article->Comment->find('all', $options); + + $dummyResult = $this->Article->Comment->find('all', array( + 'conditions' => array( + 'User.user' => 'mariano' + ), + 'fields' => array('User.password'), + 'contain' => array('User.password'), + )); + + $result = $this->Article->Comment->find('all', $options); + $this->assertEqual($result, $firstResult); + + $this->Article->unbindModel(array('hasMany' => array('Comment'), 'belongsTo' => array('User'), 'hasAndBelongsToMany' => array('Tag')), false); + $this->Article->bindModel(array('hasMany' => array('Comment'), 'belongsTo' => array('User')), false); + + $r = $this->Article->find('all', array('contain' => array('Comment(comment)', 'User(user)'), 'fields' => array('title'))); + $this->assertTrue(Set::matches('/Article[id=1]', $r)); + $this->assertTrue(Set::matches('/User[id=1]', $r)); + $this->assertTrue(Set::matches('/Comment[article_id=1]', $r)); + $this->assertFalse(Set::matches('/Comment[id=1]', $r)); + + $r = $this->Article->find('all'); + $this->assertTrue(Set::matches('/Article[id=1]', $r)); + $this->assertTrue(Set::matches('/User[id=1]', $r)); + $this->assertTrue(Set::matches('/Comment[article_id=1]', $r)); + $this->assertTrue(Set::matches('/Comment[id=1]', $r)); + + $this->Article->bindModel(array('hasAndBelongsToMany' => array('Tag')), false); + + $this->Article->contain(false, array('User(id,user)', 'Comment' => array('fields' => array('comment'), 'conditions' => array('created >=' => '2007-03-18 10:49')))); + $result = $this->Article->find('all', array('fields' => array('title'), 'limit' => 1, 'page' => 1, 'order' => 'Article.id ASC')); + $expected = array(array( + 'Article' => array('id' => 1, 'title' => 'First Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Comment' => array( + array('comment' => 'Third Comment for First Article', 'article_id' => 1), + array('comment' => 'Fourth Comment for First Article', 'article_id' => 1) + ) + )); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array('fields' => array('title', 'User.id', 'User.user'), 'limit' => 1, 'page' => 2, 'order' => 'Article.id ASC')); + $expected = array(array( + 'Article' => array('id' => 2, 'title' => 'Second Article'), + 'User' => array('id' => 3, 'user' => 'larry'), + 'Comment' => array( + array('comment' => 'First Comment for Second Article', 'article_id' => 2), + array('comment' => 'Second Comment for Second Article', 'article_id' => 2) + ) + )); + $this->assertEqual($result, $expected); + + $result = $this->Article->find('all', array('fields' => array('title', 'User.id', 'User.user'), 'limit' => 1, 'page' => 3, 'order' => 'Article.id ASC')); + $expected = array(array( + 'Article' => array('id' => 3, 'title' => 'Third Article'), + 'User' => array('id' => 1, 'user' => 'mariano'), + 'Comment' => array() + )); + $this->assertEqual($result, $expected); + + $this->Article->contain(false, array('User' => array('fields' => 'user'), 'Comment')); + $result = $this->Article->find('all'); + $this->assertTrue(Set::matches('/Article[id=1]', $result)); + $this->assertTrue(Set::matches('/User[user=mariano]', $result)); + $this->assertTrue(Set::matches('/Comment[article_id=1]', $result)); + $this->Article->resetBindings(); + + $this->Article->contain(false, array('User' => array('fields' => array('user')), 'Comment')); + $result = $this->Article->find('all'); + $this->assertTrue(Set::matches('/Article[id=1]', $result)); + $this->assertTrue(Set::matches('/User[user=mariano]', $result)); + $this->assertTrue(Set::matches('/Comment[article_id=1]', $result)); + $this->Article->resetBindings(); + } + +/** + * testResetAddedAssociation method + * + * @access public + */ + function testResetAddedAssociation() { + $this->assertTrue(empty($this->Article->hasMany['ArticlesTag'])); + + $this->Article->bindModel(array( + 'hasMany' => array('ArticlesTag') + )); + $this->assertTrue(!empty($this->Article->hasMany['ArticlesTag'])); + + $result = $this->Article->find('first', array( + 'conditions' => array('Article.id' => 1), + 'contain' => array('ArticlesTag') + )); + $expected = array('Article', 'ArticlesTag'); + $this->assertTrue(!empty($result)); + $this->assertEqual('First Article', $result['Article']['title']); + $this->assertTrue(!empty($result['ArticlesTag'])); + $this->assertEqual($expected, array_keys($result)); + + $this->assertTrue(empty($this->Article->hasMany['ArticlesTag'])); + } + +/** + * testResetAssociation method + * + * @access public + */ + function testResetAssociation() { + $this->Article->Behaviors->attach('Containable'); + $this->Article->Comment->Behaviors->attach('Containable'); + $this->Article->User->Behaviors->attach('Containable'); + + $initialOptions = array( + 'conditions' => array( + 'Comment.published' => 'Y', + ), + 'contain' => 'User', + 'recursive' => 1, + ); + + $initialModels = $this->Article->Comment->find('all', $initialOptions); + + $findOptions = array( + 'conditions' => array( + 'User.user' => 'mariano', + ), + 'fields' => array('User.password'), + 'contain' => array('User.password') + ); + $result = $this->Article->Comment->find('all', $findOptions); + $result = $this->Article->Comment->find('all', $initialOptions); + $this->assertEqual($result, $initialModels); + } + +/** + * testResetDeeperHasOneAssociations method + * + * @access public + */ + function testResetDeeperHasOneAssociations() { + $this->Article->User->unbindModel(array( + 'hasMany' => array('ArticleFeatured', 'Comment') + ), false); + $userHasOne = array('hasOne' => array('ArticleFeatured', 'Comment')); + + $this->Article->User->bindModel($userHasOne, false); + $expected = $this->Article->User->hasOne; + $this->Article->find('all'); + $this->assertEqual($expected, $this->Article->User->hasOne); + + $this->Article->User->bindModel($userHasOne, false); + $expected = $this->Article->User->hasOne; + $this->Article->find('all', array( + 'contain' => array( + 'User' => array('ArticleFeatured', 'Comment') + ) + )); + $this->assertEqual($expected, $this->Article->User->hasOne); + + $this->Article->User->bindModel($userHasOne, false); + $expected = $this->Article->User->hasOne; + $this->Article->find('all', array( + 'contain' => array( + 'User' => array( + 'ArticleFeatured', + 'Comment' => array('fields' => array('created')) + ) + ) + )); + $this->assertEqual($expected, $this->Article->User->hasOne); + + $this->Article->User->bindModel($userHasOne, false); + $expected = $this->Article->User->hasOne; + $this->Article->find('all', array( + 'contain' => array( + 'User' => array( + 'Comment' => array('fields' => array('created')) + ) + ) + )); + $this->assertEqual($expected, $this->Article->User->hasOne); + + $this->Article->User->bindModel($userHasOne, false); + $expected = $this->Article->User->hasOne; + $this->Article->find('all', array( + 'contain' => array( + 'User.ArticleFeatured' => array( + 'conditions' => array('ArticleFeatured.published' => 'Y') + ), + 'User.Comment' + ) + )); + $this->assertEqual($expected, $this->Article->User->hasOne); + } + +/** + * testResetMultipleHabtmAssociations method + * + * @access public + */ + function testResetMultipleHabtmAssociations() { + $articleHabtm = array( + 'hasAndBelongsToMany' => array( + 'Tag' => array( + 'className' => 'Tag', + 'joinTable' => 'articles_tags', + 'foreignKey' => 'article_id', + 'associationForeignKey' => 'tag_id' + ), + 'ShortTag' => array( + 'className' => 'Tag', + 'joinTable' => 'articles_tags', + 'foreignKey' => 'article_id', + 'associationForeignKey' => 'tag_id', + // LENGHT function mysql-only, using LIKE does almost the same + 'conditions' => 'ShortTag.tag LIKE "???"' + ) + ) + ); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all'); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => 'Tag.tag')); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => 'Tag')); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => array('Tag' => array('fields' => array(null))))); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => array('Tag' => array('fields' => array('Tag.tag'))))); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => array('Tag' => array('fields' => array('Tag.tag', 'Tag.created'))))); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => 'ShortTag.tag')); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => 'ShortTag')); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => array('ShortTag' => array('fields' => array(null))))); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => array('ShortTag' => array('fields' => array('ShortTag.tag'))))); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + + $this->Article->resetBindings(); + $this->Article->bindModel($articleHabtm, false); + $expected = $this->Article->hasAndBelongsToMany; + $this->Article->find('all', array('contain' => array('ShortTag' => array('fields' => array('ShortTag.tag', 'ShortTag.created'))))); + $this->assertEqual($expected, $this->Article->hasAndBelongsToMany); + } + +/** + * test that autoFields doesn't splice in fields from other databases. + * + * @return void + */ + function testAutoFieldsWithMultipleDatabases() { + $config = new DATABASE_CONFIG(); + + $skip = $this->skipIf( + !isset($config->test) || !isset($config->test2), + '%s Primary and secondary test databases not configured, skipping cross-database ' + .'join tests.' + .' To run these tests, you must define $test and $test2 in your database configuration.' + ); + if ($skip) { + return; + } + + $db =& ConnectionManager::getDataSource('test2'); + $this->_fixtures[$this->_fixtureClassMap['User']]->create($db); + $this->_fixtures[$this->_fixtureClassMap['User']]->insert($db); + + $this->Article->User->setDataSource('test2'); + + $result = $this->Article->find('all', array( + 'fields' => array('Article.title'), + 'contain' => array('User') + )); + $this->assertTrue(isset($result[0]['Article'])); + $this->assertTrue(isset($result[0]['User'])); + + $this->_fixtures[$this->_fixtureClassMap['User']]->drop($db); + } +/** + * test that autoFields doesn't splice in columns that aren't part of the join. + * + * @return void + */ + function testAutoFieldsWithRecursiveNegativeOne() { + $this->Article->recursive = -1; + $result = $this->Article->field('title', array('Article.title' => 'First Article')); + $this->assertNoErrors(); + $this->assertEqual($result, 'First Article', 'Field is wrong'); + } + +/** + * test that find(all) doesn't return incorrect values when mixed with containable. + * + * @return void + */ + function testFindAllReturn() { + $result = $this->Article->find('all', array( + 'conditions' => array('Article.id' => 999999999) + )); + $this->assertEqual($result, array(), 'Should be empty.'); + } + +/** + * containments method + * + * @param mixed $Model + * @param array $contain + * @access private + * @return void + */ + function __containments(&$Model, $contain = array()) { + if (!is_array($Model)) { + $result = $Model->containments($contain); + return $this->__containments($result['models']); + } else { + $result = $Model; + foreach($result as $i => $containment) { + $result[$i] = array_diff_key($containment, array('instance' => true)); + } + } + + return $result; + } + +/** + * assertBindings method + * + * @param mixed $Model + * @param array $expected + * @access private + * @return void + */ + function __assertBindings(&$Model, $expected = array()) { + $expected = array_merge(array('belongsTo' => array(), 'hasOne' => array(), 'hasMany' => array(), 'hasAndBelongsToMany' => array()), $expected); + + foreach($expected as $binding => $expect) { + $this->assertEqual(array_keys($Model->$binding), $expect); + } + } + +/** + * bindings method + * + * @param mixed $Model + * @param array $extra + * @param bool $output + * @access private + * @return void + */ + function __bindings(&$Model, $extra = array(), $output = true) { + $relationTypes = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'); + + $debug = '['; + $lines = array(); + foreach($relationTypes as $binding) { + if (!empty($Model->$binding)) { + $models = array_keys($Model->$binding); + foreach($models as $linkedModel) { + $line = $linkedModel; + if (!empty($extra) && !empty($Model->{$binding}[$linkedModel])) { + $extraData = array(); + foreach(array_intersect_key($Model->{$binding}[$linkedModel], array_flip($extra)) as $key => $value) { + $extraData[] = $key . ': ' . (is_array($value) ? '(' . implode(', ', $value) . ')' : $value); + } + $line .= ' {' . implode(' - ', $extraData) . '}'; + } + $lines[] = $line; + } + } + } + $debug .= implode(' | ' , $lines); + $debug .= ']'; + $debug = '' . $Model->alias . ': ' . $debug . '
    '; + + if ($output) { + echo $debug; + } + + return $debug; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/translate.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/translate.test.php new file mode 100644 index 000000000..c5c293bc5 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/translate.test.php @@ -0,0 +1,898 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + * @since CakePHP(tm) v 1.2.0.5669 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + define('CAKEPHP_UNIT_TEST_EXECUTION', 1); +} + +App::import('Core', array('AppModel', 'Model')); +require_once(dirname(dirname(__FILE__)) . DS . 'models.php'); + +/** + * TranslateBehaviorTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class TranslateBehaviorTest extends CakeTestCase { + +/** + * autoFixtures property + * + * @var bool false + * @access public + */ + var $autoFixtures = false; + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array( + 'core.translated_item', 'core.translate', 'core.translate_table', + 'core.translated_article', 'core.translate_article', 'core.user', 'core.comment', 'core.tag', 'core.articles_tag', + 'core.translate_with_prefix' + ); + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + ClassRegistry::flush(); + } + +/** + * testTranslateModel method + * + * @access public + * @return void + */ + function testTranslateModel() { + $TestModel =& new Tag(); + $TestModel->translateTable = 'another_i18n'; + $TestModel->Behaviors->attach('Translate', array('title')); + $translateModel =& $TestModel->Behaviors->Translate->translateModel($TestModel); + $this->assertEqual($translateModel->name, 'I18nModel'); + $this->assertEqual($translateModel->useTable, 'another_i18n'); + + $TestModel =& new User(); + $TestModel->Behaviors->attach('Translate', array('title')); + $translateModel =& $TestModel->Behaviors->Translate->translateModel($TestModel); + $this->assertEqual($translateModel->name, 'I18nModel'); + $this->assertEqual($translateModel->useTable, 'i18n'); + + $TestModel =& new TranslatedArticle(); + $translateModel =& $TestModel->Behaviors->Translate->translateModel($TestModel); + $this->assertEqual($translateModel->name, 'TranslateArticleModel'); + $this->assertEqual($translateModel->useTable, 'article_i18n'); + + $TestModel =& new TranslatedItem(); + $translateModel =& $TestModel->Behaviors->Translate->translateModel($TestModel); + $this->assertEqual($translateModel->name, 'TranslateTestModel'); + $this->assertEqual($translateModel->useTable, 'i18n'); + } + +/** + * testLocaleFalsePlain method + * + * @access public + * @return void + */ + function testLocaleFalsePlain() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = false; + + $result = $TestModel->read(null, 1); + $expected = array('TranslatedItem' => array('id' => 1, 'slug' => 'first_translated')); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('fields' => array('slug'))); + $expected = array( + array('TranslatedItem' => array('slug' => 'first_translated')), + array('TranslatedItem' => array('slug' => 'second_translated')), + array('TranslatedItem' => array('slug' => 'third_translated')) + ); + $this->assertEqual($result, $expected); + } + +/** + * testLocaleFalseAssociations method + * + * @access public + * @return void + */ + function testLocaleFalseAssociations() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = false; + $TestModel->unbindTranslation(); + $translations = array('title' => 'Title', 'content' => 'Content'); + $TestModel->bindTranslation($translations, false); + + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedItem' => array('id' => 1, 'slug' => 'first_translated'), + 'Title' => array( + array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), + array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), + array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titulek #1') + ), + 'Content' => array( + array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Content #1'), + array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Inhalt #1'), + array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Obsah #1') + ) + ); + $this->assertEqual($result, $expected); + + $TestModel->hasMany['Title']['fields'] = $TestModel->hasMany['Content']['fields'] = array('content'); + $TestModel->hasMany['Title']['conditions']['locale'] = $TestModel->hasMany['Content']['conditions']['locale'] = 'eng'; + + $result = $TestModel->find('all', array('fields' => array('TranslatedItem.slug'))); + $expected = array( + array( + 'TranslatedItem' => array('id' => 1, 'slug' => 'first_translated'), + 'Title' => array(array('foreign_key' => 1, 'content' => 'Title #1')), + 'Content' => array(array('foreign_key' => 1, 'content' => 'Content #1')) + ), + array( + 'TranslatedItem' => array('id' => 2, 'slug' => 'second_translated'), + 'Title' => array(array('foreign_key' => 2, 'content' => 'Title #2')), + 'Content' => array(array('foreign_key' => 2, 'content' => 'Content #2')) + ), + array( + 'TranslatedItem' => array('id' => 3, 'slug' => 'third_translated'), + 'Title' => array(array('foreign_key' => 3, 'content' => 'Title #3')), + 'Content' => array(array('foreign_key' => 3, 'content' => 'Content #3')) + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testLocaleSingle method + * + * @access public + * @return void + */ + function testLocaleSingle() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'eng'; + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Title #1', + 'content' => 'Content #1' + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all'); + $expected = array( + array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Title #1', + 'content' => 'Content #1' + ) + ), + array( + 'TranslatedItem' => array( + 'id' => 2, + 'slug' => 'second_translated', + 'locale' => 'eng', + 'title' => 'Title #2', + 'content' => 'Content #2' + ) + ), + array( + 'TranslatedItem' => array( + 'id' => 3, + 'slug' => 'third_translated', + 'locale' => 'eng', + 'title' => 'Title #3', + 'content' => 'Content #3' + ) + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testLocaleSingleWithConditions method + * + * @access public + * @return void + */ + function testLocaleSingleWithConditions() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'eng'; + $result = $TestModel->find('all', array('conditions' => array('slug' => 'first_translated'))); + $expected = array( + array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Title #1', + 'content' => 'Content #1' + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('conditions' => "TranslatedItem.slug = 'first_translated'")); + $expected = array( + array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Title #1', + 'content' => 'Content #1' + ) + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testLocaleSingleAssociations method + * + * @access public + * @return void + */ + function testLocaleSingleAssociations() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'eng'; + $TestModel->unbindTranslation(); + $translations = array('title' => 'Title', 'content' => 'Content'); + $TestModel->bindTranslation($translations, false); + + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Title #1', + 'content' => 'Content #1' + ), + 'Title' => array( + array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), + array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), + array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titulek #1') + ), + 'Content' => array( + array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Content #1'), + array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Inhalt #1'), + array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Obsah #1') + ) + ); + $this->assertEqual($result, $expected); + + $TestModel->hasMany['Title']['fields'] = $TestModel->hasMany['Content']['fields'] = array('content'); + $TestModel->hasMany['Title']['conditions']['locale'] = $TestModel->hasMany['Content']['conditions']['locale'] = 'eng'; + + $result = $TestModel->find('all', array('fields' => array('TranslatedItem.title'))); + $expected = array( + array( + 'TranslatedItem' => array('id' => 1, 'locale' => 'eng', 'title' => 'Title #1'), + 'Title' => array(array('foreign_key' => 1, 'content' => 'Title #1')), + 'Content' => array(array('foreign_key' => 1, 'content' => 'Content #1')) + ), + array( + 'TranslatedItem' => array('id' => 2, 'locale' => 'eng', 'title' => 'Title #2'), + 'Title' => array(array('foreign_key' => 2, 'content' => 'Title #2')), + 'Content' => array(array('foreign_key' => 2, 'content' => 'Content #2')) + ), + array( + 'TranslatedItem' => array('id' => 3, 'locale' => 'eng', 'title' => 'Title #3'), + 'Title' => array(array('foreign_key' => 3, 'content' => 'Title #3')), + 'Content' => array(array('foreign_key' => 3, 'content' => 'Content #3')) + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testLocaleMultiple method + * + * @access public + * @return void + */ + function testLocaleMultiple() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = array('deu', 'eng', 'cze'); + $delete = array( + array('locale' => 'deu'), + array('foreign_key' => 1, 'field' => 'title', 'locale' => 'eng'), + array('foreign_key' => 1, 'field' => 'content', 'locale' => 'cze'), + array('foreign_key' => 2, 'field' => 'title', 'locale' => 'cze'), + array('foreign_key' => 2, 'field' => 'content', 'locale' => 'eng'), + array('foreign_key' => 3, 'field' => 'title') + ); + $I18nModel =& ClassRegistry::getObject('TranslateTestModel'); + $I18nModel->deleteAll(array('or' => $delete)); + + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'deu', + 'title' => 'Titulek #1', + 'content' => 'Content #1' + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('fields' => array('slug', 'title', 'content'))); + $expected = array( + array( + 'TranslatedItem' => array( + 'slug' => 'first_translated', + 'locale' => 'deu', + 'title' => 'Titulek #1', + 'content' => 'Content #1' + ) + ), + array( + 'TranslatedItem' => array( + 'slug' => 'second_translated', + 'locale' => 'deu', + 'title' => 'Title #2', + 'content' => 'Obsah #2' + ) + ), + array( + 'TranslatedItem' => array( + 'slug' => 'third_translated', + 'locale' => 'deu', + 'title' => '', + 'content' => 'Content #3' + ) + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testMissingTranslation method + * + * @access public + * @return void + */ + function testMissingTranslation() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'rus'; + $result = $TestModel->read(null, 1); + $this->assertFalse($result); + + $TestModel->locale = array('rus'); + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'rus', + 'title' => '', + 'content' => '' + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testTranslatedFindList method + * + * @access public + * @return void + */ + function testTranslatedFindList() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'deu'; + $TestModel->displayField = 'title'; + $result = $TestModel->find('list', array('recursive' => 1)); + $expected = array(1 => 'Titel #1', 2 => 'Titel #2', 3 => 'Titel #3'); + $this->assertEqual($result, $expected); + + // MSSQL trigger an error and stops the page even if the debug = 0 + if ($this->db->config['driver'] != 'mssql') { + $debug = Configure::read('debug'); + Configure::write('debug', 0); + + $result = $TestModel->find('list', array('recursive' => 1, 'callbacks' => false)); + $this->assertEqual($result, array()); + + $result = $TestModel->find('list', array('recursive' => 1, 'callbacks' => 'after')); + $this->assertEqual($result, array()); + Configure::write('debug', $debug); + } + + $result = $TestModel->find('list', array('recursive' => 1, 'callbacks' => 'before')); + $expected = array(1 => null, 2 => null, 3 => null); + $this->assertEqual($result, $expected); + } + +/** + * testReadSelectedFields method + * + * @access public + * @return void + */ + function testReadSelectedFields() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'eng'; + $result = $TestModel->find('all', array('fields' => array('slug', 'TranslatedItem.content'))); + $expected = array( + array('TranslatedItem' => array('slug' => 'first_translated', 'locale' => 'eng', 'content' => 'Content #1')), + array('TranslatedItem' => array('slug' => 'second_translated', 'locale' => 'eng', 'content' => 'Content #2')), + array('TranslatedItem' => array('slug' => 'third_translated', 'locale' => 'eng', 'content' => 'Content #3')) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('fields' => array('TranslatedItem.slug', 'content'))); + $this->assertEqual($result, $expected); + + $TestModel->locale = array('eng', 'deu', 'cze'); + $delete = array(array('locale' => 'deu'), array('field' => 'content', 'locale' => 'eng')); + $I18nModel =& ClassRegistry::getObject('TranslateTestModel'); + $I18nModel->deleteAll(array('or' => $delete)); + + $result = $TestModel->find('all', array('fields' => array('title', 'content'))); + $expected = array( + array('TranslatedItem' => array('locale' => 'eng', 'title' => 'Title #1', 'content' => 'Obsah #1')), + array('TranslatedItem' => array('locale' => 'eng', 'title' => 'Title #2', 'content' => 'Obsah #2')), + array('TranslatedItem' => array('locale' => 'eng', 'title' => 'Title #3', 'content' => 'Obsah #3')) + ); + $this->assertEqual($result, $expected); + } + +/** + * testSaveCreate method + * + * @access public + * @return void + */ + function testSaveCreate() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'spa'; + $data = array('slug' => 'fourth_translated', 'title' => 'Leyenda #4', 'content' => 'Contenido #4'); + $TestModel->create($data); + $TestModel->save(); + $result = $TestModel->read(); + $expected = array('TranslatedItem' => array_merge($data, array('id' => $TestModel->id, 'locale' => 'spa'))); + $this->assertEqual($result, $expected); + } + +/** + * testSaveUpdate method + * + * @access public + * @return void + */ + function testSaveUpdate() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'spa'; + $oldData = array('slug' => 'fourth_translated', 'title' => 'Leyenda #4'); + $TestModel->create($oldData); + $TestModel->save(); + $id = $TestModel->id; + $newData = array('id' => $id, 'content' => 'Contenido #4'); + $TestModel->create($newData); + $TestModel->save(); + $result = $TestModel->read(null, $id); + $expected = array('TranslatedItem' => array_merge($oldData, $newData, array('locale' => 'spa'))); + $this->assertEqual($result, $expected); + } + +/** + * testMultipleCreate method + * + * @access public + * @return void + */ + function testMultipleCreate() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'deu'; + $data = array( + 'slug' => 'new_translated', + 'title' => array('eng' => 'New title', 'spa' => 'Nuevo leyenda'), + 'content' => array('eng' => 'New content', 'spa' => 'Nuevo contenido') + ); + $TestModel->create($data); + $TestModel->save(); + + $TestModel->unbindTranslation(); + $translations = array('title' => 'Title', 'content' => 'Content'); + $TestModel->bindTranslation($translations, false); + $TestModel->locale = array('eng', 'spa'); + + $result = $TestModel->read(); + $expected = array( + 'TranslatedItem' => array('id' => 4, 'slug' => 'new_translated', 'locale' => 'eng', 'title' => 'New title', 'content' => 'New content'), + 'Title' => array( + array('id' => 21, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'title', 'content' => 'New title'), + array('id' => 22, 'locale' => 'spa', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'title', 'content' => 'Nuevo leyenda') + ), + 'Content' => array( + array('id' => 19, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'content', 'content' => 'New content'), + array('id' => 20, 'locale' => 'spa', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'content', 'content' => 'Nuevo contenido') + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testMultipleUpdate method + * + * @access public + * @return void + */ + function testMultipleUpdate() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'eng'; + $TestModel->validate['title'] = 'notEmpty'; + $data = array('TranslatedItem' => array( + 'id' => 1, + 'title' => array('eng' => 'New Title #1', 'deu' => 'Neue Titel #1', 'cze' => 'Novy Titulek #1'), + 'content' => array('eng' => 'New Content #1', 'deu' => 'Neue Inhalt #1', 'cze' => 'Novy Obsah #1') + )); + $TestModel->create(); + $TestModel->save($data); + + $TestModel->unbindTranslation(); + $translations = array('title' => 'Title', 'content' => 'Content'); + $TestModel->bindTranslation($translations, false); + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedItem' => array('id' => '1', 'slug' => 'first_translated', 'locale' => 'eng', 'title' => 'New Title #1', 'content' => 'New Content #1'), + 'Title' => array( + array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'New Title #1'), + array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Neue Titel #1'), + array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Novy Titulek #1') + ), + 'Content' => array( + array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'New Content #1'), + array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Neue Inhalt #1'), + array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Novy Obsah #1') + ) + ); + $this->assertEqual($result, $expected); + + $TestModel->unbindTranslation($translations); + $TestModel->bindTranslation(array('title', 'content'), false); + } + +/** + * testMixedCreateUpdateWithArrayLocale method + * + * @access public + * @return void + */ + function testMixedCreateUpdateWithArrayLocale() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = array('cze', 'deu'); + $data = array('TranslatedItem' => array( + 'id' => 1, + 'title' => array('eng' => 'Updated Title #1', 'spa' => 'Nuevo leyenda #1'), + 'content' => 'Upraveny obsah #1' + )); + $TestModel->create(); + $TestModel->save($data); + + $TestModel->unbindTranslation(); + $translations = array('title' => 'Title', 'content' => 'Content'); + $TestModel->bindTranslation($translations, false); + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedItem' => array('id' => 1, 'slug' => 'first_translated', 'locale' => 'cze', 'title' => 'Titulek #1', 'content' => 'Upraveny obsah #1'), + 'Title' => array( + array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Updated Title #1'), + array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), + array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titulek #1'), + array('id' => 19, 'locale' => 'spa', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Nuevo leyenda #1') + ), + 'Content' => array( + array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Content #1'), + array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Inhalt #1'), + array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Upraveny obsah #1') + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testValidation method + * + * @access public + * @return void + */ + function testValidation() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->locale = 'eng'; + $TestModel->validate['title'] = '/Only this title/'; + $data = array('TranslatedItem' => array( + 'id' => 1, + 'title' => array('eng' => 'New Title #1', 'deu' => 'Neue Titel #1', 'cze' => 'Novy Titulek #1'), + 'content' => array('eng' => 'New Content #1', 'deu' => 'Neue Inhalt #1', 'cze' => 'Novy Obsah #1') + )); + $TestModel->create(); + $this->assertFalse($TestModel->save($data)); + $this->assertEqual($TestModel->validationErrors['title'], 'This field cannot be left blank'); + + $TestModel->locale = 'eng'; + $TestModel->validate['title'] = '/Only this title/'; + $data = array('TranslatedItem' => array( + 'id' => 1, + 'title' => array('eng' => 'Only this title', 'deu' => 'Neue Titel #1', 'cze' => 'Novy Titulek #1'), + 'content' => array('eng' => 'New Content #1', 'deu' => 'Neue Inhalt #1', 'cze' => 'Novy Obsah #1') + )); + $TestModel->create(); + $this->assertTrue($TestModel->save($data)); + } + +/** + * testAttachDetach method + * + * @access public + * @return void + */ + function testAttachDetach() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $Behavior =& $this->Model->Behaviors->Translate; + + $TestModel->unbindTranslation(); + $translations = array('title' => 'Title', 'content' => 'Content'); + $TestModel->bindTranslation($translations, false); + + $result = array_keys($TestModel->hasMany); + $expected = array('Title', 'Content'); + $this->assertEqual($result, $expected); + + $TestModel->Behaviors->detach('Translate'); + $result = array_keys($TestModel->hasMany); + $expected = array(); + $this->assertEqual($result, $expected); + + $result = isset($TestModel->Behaviors->Translate); + $this->assertFalse($result); + + $result = isset($Behavior->settings[$TestModel->alias]); + $this->assertFalse($result); + + $result = isset($Behavior->runtime[$TestModel->alias]); + $this->assertFalse($result); + + $TestModel->Behaviors->attach('Translate', array('title' => 'Title', 'content' => 'Content')); + $result = array_keys($TestModel->hasMany); + $expected = array('Title', 'Content'); + $this->assertEqual($result, $expected); + + $result = isset($TestModel->Behaviors->Translate); + $this->assertTrue($result); + + $Behavior = $TestModel->Behaviors->Translate; + + $result = isset($Behavior->settings[$TestModel->alias]); + $this->assertTrue($result); + + $result = isset($Behavior->runtime[$TestModel->alias]); + $this->assertTrue($result); + } + +/** + * testAnotherTranslateTable method + * + * @access public + * @return void + */ + function testAnotherTranslateTable() { + $this->loadFixtures('Translate', 'TranslatedItem', 'TranslateTable'); + + $TestModel =& new TranslatedItemWithTable(); + $TestModel->locale = 'eng'; + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedItemWithTable' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Another Title #1', + 'content' => 'Another Content #1' + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testTranslateWithAssociations method + * + * @access public + * @return void + */ + function testTranslateWithAssociations() { + $this->loadFixtures('TranslateArticle', 'TranslatedArticle', 'User', 'Comment', 'ArticlesTag', 'Tag'); + + $TestModel =& new TranslatedArticle(); + $TestModel->locale = 'eng'; + $recursive = $TestModel->recursive; + + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedArticle' => array( + 'id' => 1, + 'user_id' => 1, + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'locale' => 'eng', + 'title' => 'Title (eng) #1', + 'body' => 'Body (eng) #1' + ), + 'User' => array( + 'id' => 1, + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('recursive' => -1)); + $expected = array( + array( + 'TranslatedArticle' => array( + 'id' => 1, + 'user_id' => 1, + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'locale' => 'eng', + 'title' => 'Title (eng) #1', + 'body' => 'Body (eng) #1' + ) + ), + array( + 'TranslatedArticle' => array( + 'id' => 2, + 'user_id' => 3, + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31', + 'locale' => 'eng', + 'title' => 'Title (eng) #2', + 'body' => 'Body (eng) #2' + ) + ), + array( + 'TranslatedArticle' => array( + 'id' => 3, + 'user_id' => 1, + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31', + 'locale' => 'eng', + 'title' => 'Title (eng) #3', + 'body' => 'Body (eng) #3' + ) + ) + ); + $this->assertEqual($result, $expected); + $this->assertEqual($TestModel->recursive, $recursive); + + $TestModel->recursive = -1; + $result = $TestModel->read(null, 1); + $expected = array( + 'TranslatedArticle' => array( + 'id' => 1, + 'user_id' => 1, + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'locale' => 'eng', + 'title' => 'Title (eng) #1', + 'body' => 'Body (eng) #1' + ) + ); + $this->assertEqual($result, $expected); + } +/** + * testTranslateTableWithPrefix method + * Tests that is possible to have a translation model with a custom tablePrefix + * + * @access public + * @return void + */ + function testTranslateTableWithPrefix() { + $this->loadFixtures('TranslateWithPrefix', 'TranslatedItem'); + $TestModel =& new TranslatedItem2; + $TestModel->locale = 'eng'; + $result = $TestModel->read(null, 1); + $expected = array('TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'content' => 'Content #1', + 'title' => 'Title #1' + )); + $this->assertEqual($result, $expected); + } + +/** + * Test infinite loops not occuring with unbindTranslation() + * + * @return void + */ + function testUnbindTranslationInfinteLoop() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $TestModel =& new TranslatedItem(); + $TestModel->Behaviors->detach('Translate'); + $TestModel->actsAs = array(); + $TestModel->Behaviors->attach('Translate'); + $TestModel->bindTranslation(array('title', 'content'), true); + $result = $TestModel->unbindTranslation(); + + $this->assertFalse($result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/tree.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/tree.test.php new file mode 100644 index 000000000..39475cd60 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/behaviors/tree.test.php @@ -0,0 +1,1876 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + * @since CakePHP(tm) v 1.2.0.5330 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', array('AppModel', 'Model')); +require_once(dirname(dirname(__FILE__)) . DS . 'models.php'); + +/** + * NumberTreeTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class NumberTreeTest extends CakeTestCase { + +/** + * settings property + * + * @var array + * @access public + */ + var $settings = array( + 'modelClass' => 'NumberTree', + 'leftField' => 'lft', + 'rightField' => 'rght', + 'parentField' => 'parent_id' + ); + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.number_tree'); + +/** + * testInitialize method + * + * @access public + * @return void + */ + function testInitialize() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->find('count'); + $this->assertEqual($result, 7); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testDetectInvalidLeft method + * + * @access public + * @return void + */ + function testDetectInvalidLeft() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->findByName('1.1'); + + $save[$modelClass]['id'] = $result[$modelClass]['id']; + $save[$modelClass][$leftField] = 0; + + $this->Tree->save($save); + $result = $this->Tree->verify(); + $this->assertNotIdentical($result, true); + + $result = $this->Tree->recover(); + $this->assertIdentical($result, true); + + $result = $this->Tree->verify(); + $this->assertIdentical($result, true); + } + +/** + * testDetectInvalidRight method + * + * @access public + * @return void + */ + function testDetectInvalidRight() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->findByName('1.1'); + + $save[$modelClass]['id'] = $result[$modelClass]['id']; + $save[$modelClass][$rightField] = 0; + + $this->Tree->save($save); + $result = $this->Tree->verify(); + $this->assertNotIdentical($result, true); + + $result = $this->Tree->recover(); + $this->assertIdentical($result, true); + + $result = $this->Tree->verify(); + $this->assertIdentical($result, true); + } + +/** + * testDetectInvalidParent method + * + * @access public + * @return void + */ + function testDetectInvalidParent() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->findByName('1.1'); + + // Bypass behavior and any other logic + $this->Tree->updateAll(array($parentField => null), array('id' => $result[$modelClass]['id'])); + + $result = $this->Tree->verify(); + $this->assertNotIdentical($result, true); + + $result = $this->Tree->recover(); + $this->assertIdentical($result, true); + + $result = $this->Tree->verify(); + $this->assertIdentical($result, true); + } + +/** + * testDetectNoneExistantParent method + * + * @access public + * @return void + */ + function testDetectNoneExistantParent() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->findByName('1.1'); + $this->Tree->updateAll(array($parentField => 999999), array('id' => $result[$modelClass]['id'])); + + $result = $this->Tree->verify(); + $this->assertNotIdentical($result, true); + + $result = $this->Tree->recover('MPTT'); + $this->assertIdentical($result, true); + + $result = $this->Tree->verify(); + $this->assertIdentical($result, true); + } + +/** + * testRecoverFromMissingParent method + * + * @access public + * @return void + */ + function testRecoverFromMissingParent() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->findByName('1.1'); + $this->Tree->updateAll(array($parentField => 999999), array('id' => $result[$modelClass]['id'])); + + $result = $this->Tree->verify(); + $this->assertNotIdentical($result, true); + + $result = $this->Tree->recover(); + $this->assertIdentical($result, true); + + $result = $this->Tree->verify(); + $this->assertIdentical($result, true); + } + +/** + * testDetectInvalidParents method + * + * @access public + * @return void + */ + function testDetectInvalidParents() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $this->Tree->updateAll(array($parentField => null)); + + $result = $this->Tree->verify(); + $this->assertNotIdentical($result, true); + + $result = $this->Tree->recover(); + $this->assertIdentical($result, true); + + $result = $this->Tree->verify(); + $this->assertIdentical($result, true); + } + +/** + * testDetectInvalidLftsRghts method + * + * @access public + * @return void + */ + function testDetectInvalidLftsRghts() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $this->Tree->updateAll(array($leftField => 0, $rightField => 0)); + + $result = $this->Tree->verify(); + $this->assertNotIdentical($result, true); + + $this->Tree->recover(); + + $result = $this->Tree->verify(); + $this->assertIdentical($result, true); + } + +/** + * Reproduces a situation where a single node has lft= rght, and all other lft and rght fields follow sequentially + * + * @access public + * @return void + */ + function testDetectEqualLftsRghts() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(1, 3); + + $result = $this->Tree->findByName('1.1'); + $this->Tree->updateAll(array($rightField => $result[$modelClass][$leftField]), array('id' => $result[$modelClass]['id'])); + $this->Tree->updateAll(array($leftField => $this->Tree->escapeField($leftField) . ' -1'), + array($leftField . ' >' => $result[$modelClass][$leftField])); + $this->Tree->updateAll(array($rightField => $this->Tree->escapeField($rightField) . ' -1'), + array($rightField . ' >' => $result[$modelClass][$leftField])); + + $result = $this->Tree->verify(); + $this->assertNotIdentical($result, true); + + $result = $this->Tree->recover(); + $this->assertTrue($result); + + $result = $this->Tree->verify(); + $this->assertTrue($result); + } + +/** + * testAddOrphan method + * + * @access public + * @return void + */ + function testAddOrphan() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $this->Tree->save(array($modelClass => array('name' => 'testAddOrphan', $parentField => null))); + $result = $this->Tree->find(null, array('name', $parentField), $modelClass . '.' . $leftField . ' desc'); + $expected = array($modelClass => array('name' => 'testAddOrphan', $parentField => null)); + $this->assertEqual($result, $expected); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testAddMiddle method + * + * @access public + * @return void + */ + function testAddMiddle() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data= $this->Tree->find(array($modelClass . '.name' => '1.1'), array('id')); + $initialCount = $this->Tree->find('count'); + + $this->Tree->create(); + $result = $this->Tree->save(array($modelClass => array('name' => 'testAddMiddle', $parentField => $data[$modelClass]['id']))); + $expected = array_merge(array($modelClass => array('name' => 'testAddMiddle', $parentField => '2')), $result); + $this->assertIdentical($result, $expected); + + $laterCount = $this->Tree->find('count'); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount + 1, $laterCount); + + $children = $this->Tree->children($data[$modelClass]['id'], true, array('name')); + $expects = array(array($modelClass => array('name' => '1.1.1')), + array($modelClass => array('name' => '1.1.2')), + array($modelClass => array('name' => 'testAddMiddle'))); + $this->assertIdentical($children, $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testAddInvalid method + * + * @access public + * @return void + */ + function testAddInvalid() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $initialCount = $this->Tree->find('count'); + //$this->expectError('Trying to save a node under a none-existant node in TreeBehavior::beforeSave'); + + $saveSuccess = $this->Tree->save(array($modelClass => array('name' => 'testAddInvalid', $parentField => 99999))); + $this->assertIdentical($saveSuccess, false); + + $laterCount = $this->Tree->find('count'); + $this->assertIdentical($initialCount, $laterCount); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testAddNotIndexedByModel method + * + * @access public + * @return void + */ + function testAddNotIndexedByModel() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $this->Tree->save(array('name' => 'testAddNotIndexed', $parentField => null)); + $result = $this->Tree->find(null, array('name', $parentField), $modelClass . '.' . $leftField . ' desc'); + $expected = array($modelClass => array('name' => 'testAddNotIndexed', $parentField => null)); + $this->assertEqual($result, $expected); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); +} + +/** + * testMovePromote method + * + * @access public + * @return void + */ + function testMovePromote() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $parent = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $parent_id = $parent[$modelClass]['id']; + + $data = $this->Tree->find(array($modelClass . '.name' => '1.1.1'), array('id')); + $this->Tree->id= $data[$modelClass]['id']; + $this->Tree->saveField($parentField, $parent_id); + $direct = $this->Tree->children($parent_id, true, array('id', 'name', $parentField, $leftField, $rightField)); + $expects = array(array($modelClass => array('id' => 2, 'name' => '1.1', $parentField => 1, $leftField => 2, $rightField => 5)), + array($modelClass => array('id' => 5, 'name' => '1.2', $parentField => 1, $leftField => 6, $rightField => 11)), + array($modelClass => array('id' => 3, 'name' => '1.1.1', $parentField => 1, $leftField => 12, $rightField => 13))); + $this->assertEqual($direct, $expects); + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testMoveWithWhitelist method + * + * @access public + * @return void + */ + function testMoveWithWhitelist() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $parent = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $parent_id = $parent[$modelClass]['id']; + + $data = $this->Tree->find(array($modelClass . '.name' => '1.1.1'), array('id')); + $this->Tree->id = $data[$modelClass]['id']; + $this->Tree->whitelist = array($parentField, 'name', 'description'); + $this->Tree->saveField($parentField, $parent_id); + + $result = $this->Tree->children($parent_id, true, array('id', 'name', $parentField, $leftField, $rightField)); + $expected = array(array($modelClass => array('id' => 2, 'name' => '1.1', $parentField => 1, $leftField => 2, $rightField => 5)), + array($modelClass => array('id' => 5, 'name' => '1.2', $parentField => 1, $leftField => 6, $rightField => 11)), + array($modelClass => array('id' => 3, 'name' => '1.1.1', $parentField => 1, $leftField => 12, $rightField => 13))); + $this->assertEqual($result, $expected); + $this->assertTrue($this->Tree->verify()); + } + +/** + * testInsertWithWhitelist method + * + * @access public + * @return void + */ + function testInsertWithWhitelist() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $this->Tree->whitelist = array('name', $parentField); + $this->Tree->save(array($modelClass => array('name' => 'testAddOrphan', $parentField => null))); + $result = $this->Tree->findByName('testAddOrphan', array('name', $parentField, $leftField, $rightField)); + $expected = array('name' => 'testAddOrphan', $parentField => null, $leftField => '15', $rightField => 16); + $this->assertEqual($result[$modelClass], $expected); + $this->assertIdentical($this->Tree->verify(), true); + } + +/** + * testMoveBefore method + * + * @access public + * @return void + */ + function testMoveBefore() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $parent = $this->Tree->find(array($modelClass . '.name' => '1.1')); + $parent_id = $parent[$modelClass]['id']; + + $data= $this->Tree->find(array($modelClass . '.name' => '1.2'), array('id')); + $this->Tree->id = $data[$modelClass]['id']; + $this->Tree->saveField($parentField, $parent_id); + + $result = $this->Tree->children($parent_id, true, array('name')); + $expects = array(array($modelClass => array('name' => '1.1.1')), + array($modelClass => array('name' => '1.1.2')), + array($modelClass => array('name' => '1.2'))); + $this->assertEqual($result, $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testMoveAfter method + * + * @access public + * @return void + */ + function testMoveAfter() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $parent = $this->Tree->find(array($modelClass . '.name' => '1.2')); + $parent_id = $parent[$modelClass]['id']; + + $data= $this->Tree->find(array($modelClass . '.name' => '1.1'), array('id')); + $this->Tree->id = $data[$modelClass]['id']; + $this->Tree->saveField($parentField, $parent_id); + + $result = $this->Tree->children($parent_id, true, array('name')); + $expects = array(array($modelClass => array('name' => '1.2.1')), + array($modelClass => array('name' => '1.2.2')), + array($modelClass => array('name' => '1.1'))); + $this->assertEqual($result, $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testMoveDemoteInvalid method + * + * @access public + * @return void + */ + function testMoveDemoteInvalid() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $parent = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $parent_id = $parent[$modelClass]['id']; + + $data = $this->Tree->find(array($modelClass . '.name' => '1.1.1'), array('id')); + + $expects = $this->Tree->find('all'); + $before = $this->Tree->read(null, $data[$modelClass]['id']); + + $this->Tree->id = $parent_id; + //$this->expectError('Trying to save a node under itself in TreeBehavior::beforeSave'); + $this->Tree->saveField($parentField, $data[$modelClass]['id']); + + $results = $this->Tree->find('all'); + $after = $this->Tree->read(null, $data[$modelClass]['id']); + + $this->assertEqual($results, $expects); + $this->assertEqual($before, $after); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testMoveInvalid method + * + * @access public + * @return void + */ + function testMoveInvalid() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $initialCount = $this->Tree->find('count'); + $data= $this->Tree->findByName('1.1'); + + //$this->expectError('Trying to save a node under a none-existant node in TreeBehavior::beforeSave'); + $this->Tree->id = $data[$modelClass]['id']; + $this->Tree->saveField($parentField, 999999); + + //$this->assertIdentical($saveSuccess, false); + $laterCount = $this->Tree->find('count'); + $this->assertIdentical($initialCount, $laterCount); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testMoveSelfInvalid method + * + * @access public + * @return void + */ + function testMoveSelfInvalid() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $initialCount = $this->Tree->find('count'); + $data= $this->Tree->findByName('1.1'); + + //$this->expectError('Trying to set a node to be the parent of itself in TreeBehavior::beforeSave'); + $this->Tree->id = $data[$modelClass]['id']; + $saveSuccess = $this->Tree->saveField($parentField, $this->Tree->id); + + $this->assertIdentical($saveSuccess, false); + $laterCount = $this->Tree->find('count'); + $this->assertIdentical($initialCount, $laterCount); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testMoveUpSuccess method + * + * @access public + * @return void + */ + function testMoveUpSuccess() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.2'), array('id')); + $this->Tree->moveUp($data[$modelClass]['id']); + + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array(array($modelClass => array('name' => '1.2', )), + array($modelClass => array('name' => '1.1', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testMoveUpFail method + * + * @access public + * @return void + */ + function testMoveUpFail() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.1')); + + $this->Tree->moveUp($data[$modelClass]['id']); + + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array(array($modelClass => array('name' => '1.1', )), + array($modelClass => array('name' => '1.2', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testMoveUp2 method + * + * @access public + * @return void + */ + function testMoveUp2() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(1, 10); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.5'), array('id')); + $this->Tree->moveUp($data[$modelClass]['id'], 2); + + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array( + array($modelClass => array('name' => '1.1', )), + array($modelClass => array('name' => '1.2', )), + array($modelClass => array('name' => '1.5', )), + array($modelClass => array('name' => '1.3', )), + array($modelClass => array('name' => '1.4', )), + array($modelClass => array('name' => '1.6', )), + array($modelClass => array('name' => '1.7', )), + array($modelClass => array('name' => '1.8', )), + array($modelClass => array('name' => '1.9', )), + array($modelClass => array('name' => '1.10', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testMoveUpFirst method + * + * @access public + * @return void + */ + function testMoveUpFirst() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(1, 10); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.5'), array('id')); + $this->Tree->moveUp($data[$modelClass]['id'], true); + + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array( + array($modelClass => array('name' => '1.5', )), + array($modelClass => array('name' => '1.1', )), + array($modelClass => array('name' => '1.2', )), + array($modelClass => array('name' => '1.3', )), + array($modelClass => array('name' => '1.4', )), + array($modelClass => array('name' => '1.6', )), + array($modelClass => array('name' => '1.7', )), + array($modelClass => array('name' => '1.8', )), + array($modelClass => array('name' => '1.9', )), + array($modelClass => array('name' => '1.10', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testMoveDownSuccess method + * + * @access public + * @return void + */ + function testMoveDownSuccess() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.1'), array('id')); + $this->Tree->moveDown($data[$modelClass]['id']); + + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array(array($modelClass => array('name' => '1.2', )), + array($modelClass => array('name' => '1.1', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testMoveDownFail method + * + * @access public + * @return void + */ + function testMoveDownFail() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.2')); + $this->Tree->moveDown($data[$modelClass]['id']); + + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array(array($modelClass => array('name' => '1.1', )), + array($modelClass => array('name' => '1.2', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testMoveDownLast method + * + * @access public + * @return void + */ + function testMoveDownLast() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(1, 10); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.5'), array('id')); + $this->Tree->moveDown($data[$modelClass]['id'], true); + + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array( + array($modelClass => array('name' => '1.1', )), + array($modelClass => array('name' => '1.2', )), + array($modelClass => array('name' => '1.3', )), + array($modelClass => array('name' => '1.4', )), + array($modelClass => array('name' => '1.6', )), + array($modelClass => array('name' => '1.7', )), + array($modelClass => array('name' => '1.8', )), + array($modelClass => array('name' => '1.9', )), + array($modelClass => array('name' => '1.10', )), + array($modelClass => array('name' => '1.5', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testMoveDown2 method + * + * @access public + * @return void + */ + function testMoveDown2() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(1, 10); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.5'), array('id')); + $this->Tree->moveDown($data[$modelClass]['id'], 2); + + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array( + array($modelClass => array('name' => '1.1', )), + array($modelClass => array('name' => '1.2', )), + array($modelClass => array('name' => '1.3', )), + array($modelClass => array('name' => '1.4', )), + array($modelClass => array('name' => '1.6', )), + array($modelClass => array('name' => '1.7', )), + array($modelClass => array('name' => '1.5', )), + array($modelClass => array('name' => '1.8', )), + array($modelClass => array('name' => '1.9', )), + array($modelClass => array('name' => '1.10', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testSaveNoMove method + * + * @access public + * @return void + */ + function testSaveNoMove() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(1, 10); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.5'), array('id')); + $this->Tree->id = $data[$modelClass]['id']; + $this->Tree->saveField('name', 'renamed'); + $parent = $this->Tree->findByName('1. Root', array('id')); + $this->Tree->id = $parent[$modelClass]['id']; + $result = $this->Tree->children(null, true, array('name')); + $expected = array( + array($modelClass => array('name' => '1.1', )), + array($modelClass => array('name' => '1.2', )), + array($modelClass => array('name' => '1.3', )), + array($modelClass => array('name' => '1.4', )), + array($modelClass => array('name' => 'renamed', )), + array($modelClass => array('name' => '1.6', )), + array($modelClass => array('name' => '1.7', )), + array($modelClass => array('name' => '1.8', )), + array($modelClass => array('name' => '1.9', )), + array($modelClass => array('name' => '1.10', ))); + $this->assertIdentical($result, $expected); + } + +/** + * testMoveToRootAndMoveUp method + * + * @access public + * @return void + */ + function testMoveToRootAndMoveUp() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(1, 1); + $data = $this->Tree->find(array($modelClass . '.name' => '1.1'), array('id')); + $this->Tree->id = $data[$modelClass]['id']; + $this->Tree->save(array($parentField => null)); + + $result = $this->Tree->verify(); + $this->assertIdentical($result, true); + + $this->Tree->moveup(); + + $result = $this->Tree->find('all', array('fields' => 'name', 'order' => $modelClass . '.' . $leftField . ' ASC')); + $expected = array(array($modelClass => array('name' => '1.1')), + array($modelClass => array('name' => '1. Root'))); + $this->assertIdentical($result, $expected); + } + +/** + * testDelete method + * + * @access public + * @return void + */ + function testDelete() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $initialCount = $this->Tree->find('count'); + $result = $this->Tree->findByName('1.1.1'); + + $return = $this->Tree->delete($result[$modelClass]['id']); + $this->assertEqual($return, true); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount - 1, $laterCount); + + $validTree= $this->Tree->verify(); + $this->assertIdentical($validTree, true); + + $initialCount = $this->Tree->find('count'); + $result= $this->Tree->findByName('1.1'); + + $return = $this->Tree->delete($result[$modelClass]['id']); + $this->assertEqual($return, true); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount - 2, $laterCount); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testRemove method + * + * @access public + * @return void + */ + function testRemove() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $initialCount = $this->Tree->find('count'); + $result = $this->Tree->findByName('1.1'); + + $this->Tree->removeFromTree($result[$modelClass]['id']); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount, $laterCount); + + $children = $this->Tree->children($result[$modelClass][$parentField], true, array('name')); + $expects = array(array($modelClass => array('name' => '1.1.1')), + array($modelClass => array('name' => '1.1.2')), + array($modelClass => array('name' => '1.2'))); + $this->assertEqual($children, $expects); + + $topNodes = $this->Tree->children(false, true,array('name')); + $expects = array(array($modelClass => array('name' => '1. Root')), + array($modelClass => array('name' => '1.1'))); + $this->assertEqual($topNodes, $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testRemoveLastTopParent method + * + * @access public + * @return void + */ + function testRemoveLastTopParent() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $initialCount = $this->Tree->find('count'); + $initialTopNodes = $this->Tree->childCount(false); + + $result = $this->Tree->findByName('1. Root'); + $this->Tree->removeFromTree($result[$modelClass]['id']); + + $laterCount = $this->Tree->find('count'); + $laterTopNodes = $this->Tree->childCount(false); + + $this->assertEqual($initialCount, $laterCount); + $this->assertEqual($initialTopNodes, $laterTopNodes); + + $topNodes = $this->Tree->children(false, true,array('name')); + $expects = array(array($modelClass => array('name' => '1.1')), + array($modelClass => array('name' => '1.2')), + array($modelClass => array('name' => '1. Root'))); + + $this->assertEqual($topNodes, $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testRemoveNoChildren method + * + * @return void + * @access public + */ + function testRemoveNoChildren() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $initialCount = $this->Tree->find('count'); + + $result = $this->Tree->findByName('1.1.1'); + $this->Tree->removeFromTree($result[$modelClass]['id']); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount, $laterCount); + + $nodes = $this->Tree->find('list', array('order' => $leftField)); + $expects = array( + 1 => '1. Root', + 2 => '1.1', + 4 => '1.1.2', + 5 => '1.2', + 6 => '1.2.1', + 7 => '1.2.2', + 3 => '1.1.1', + ); + + $this->assertEqual($nodes, $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testRemoveAndDelete method + * + * @access public + * @return void + */ + function testRemoveAndDelete() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $initialCount = $this->Tree->find('count'); + $result = $this->Tree->findByName('1.1'); + + $this->Tree->removeFromTree($result[$modelClass]['id'], true); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount-1, $laterCount); + + $children = $this->Tree->children($result[$modelClass][$parentField], true, array('name'), $leftField . ' asc'); + $expects= array(array($modelClass => array('name' => '1.1.1')), + array($modelClass => array('name' => '1.1.2')), + array($modelClass => array('name' => '1.2'))); + $this->assertEqual($children, $expects); + + $topNodes = $this->Tree->children(false, true,array('name')); + $expects = array(array($modelClass => array('name' => '1. Root'))); + $this->assertEqual($topNodes, $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testRemoveAndDeleteNoChildren method + * + * @return void + * @access public + */ + function testRemoveAndDeleteNoChildren() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $initialCount = $this->Tree->find('count'); + + $result = $this->Tree->findByName('1.1.1'); + $this->Tree->removeFromTree($result[$modelClass]['id'], true); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount - 1, $laterCount); + + $nodes = $this->Tree->find('list', array('order' => $leftField)); + $expects = array( + 1 => '1. Root', + 2 => '1.1', + 4 => '1.1.2', + 5 => '1.2', + 6 => '1.2.1', + 7 => '1.2.2', + ); + $this->assertEqual($nodes, $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testChildren method + * + * @access public + * @return void + */ + function testChildren() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $this->Tree->id= $data[$modelClass]['id']; + + $direct = $this->Tree->children(null, true, array('id', 'name', $parentField, $leftField, $rightField)); + $expects = array(array($modelClass => array('id' => 2, 'name' => '1.1', $parentField => 1, $leftField => 2, $rightField => 7)), + array($modelClass => array('id' => 5, 'name' => '1.2', $parentField => 1, $leftField => 8, $rightField => 13))); + $this->assertEqual($direct, $expects); + + $total = $this->Tree->children(null, null, array('id', 'name', $parentField, $leftField, $rightField)); + $expects = array(array($modelClass => array('id' => 2, 'name' => '1.1', $parentField => 1, $leftField => 2, $rightField => 7)), + array($modelClass => array('id' => 3, 'name' => '1.1.1', $parentField => 2, $leftField => 3, $rightField => 4)), + array($modelClass => array('id' => 4, 'name' => '1.1.2', $parentField => 2, $leftField => 5, $rightField => 6)), + array($modelClass => array('id' => 5, 'name' => '1.2', $parentField => 1, $leftField => 8, $rightField => 13)), + array($modelClass => array( 'id' => 6, 'name' => '1.2.1', $parentField => 5, $leftField => 9, $rightField => 10)), + array($modelClass => array('id' => 7, 'name' => '1.2.2', $parentField => 5, $leftField => 11, $rightField => 12))); + $this->assertEqual($total, $expects); + + $this->assertEqual(array(), $this->Tree->children(10000)); + } + +/** + * testCountChildren method + * + * @access public + * @return void + */ + function testCountChildren() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $this->Tree->id = $data[$modelClass]['id']; + + $direct = $this->Tree->childCount(null, true); + $this->assertEqual($direct, 2); + + $total = $this->Tree->childCount(); + $this->assertEqual($total, 6); + } + +/** + * testGetParentNode method + * + * @access public + * @return void + */ + function testGetParentNode() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.2.2')); + $this->Tree->id= $data[$modelClass]['id']; + + $result = $this->Tree->getparentNode(null, array('name')); + $expects = array($modelClass => array('name' => '1.2')); + $this->assertIdentical($result, $expects); + } + +/** + * testGetPath method + * + * @access public + * @return void + */ + function testGetPath() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.2.2')); + $this->Tree->id= $data[$modelClass]['id']; + + $result = $this->Tree->getPath(null, array('name')); + $expects = array(array($modelClass => array('name' => '1. Root')), + array($modelClass => array('name' => '1.2')), + array($modelClass => array('name' => '1.2.2'))); + $this->assertIdentical($result, $expects); + } + +/** + * testNoAmbiguousColumn method + * + * @access public + * @return void + */ + function testNoAmbiguousColumn() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->bindModel(array('belongsTo' => array('Dummy' => + array('className' => $modelClass, 'foreignKey' => $parentField, 'conditions' => array('Dummy.id' => null)))), false); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $this->Tree->id= $data[$modelClass]['id']; + + $direct = $this->Tree->children(null, true, array('id', 'name', $parentField, $leftField, $rightField)); + $expects = array(array($modelClass => array('id' => 2, 'name' => '1.1', $parentField => 1, $leftField => 2, $rightField => 7)), + array($modelClass => array('id' => 5, 'name' => '1.2', $parentField => 1, $leftField => 8, $rightField => 13))); + $this->assertEqual($direct, $expects); + + $total = $this->Tree->children(null, null, array('id', 'name', $parentField, $leftField, $rightField)); + $expects = array( + array($modelClass => array('id' => 2, 'name' => '1.1', $parentField => 1, $leftField => 2, $rightField => 7)), + array($modelClass => array('id' => 3, 'name' => '1.1.1', $parentField => 2, $leftField => 3, $rightField => 4)), + array($modelClass => array('id' => 4, 'name' => '1.1.2', $parentField => 2, $leftField => 5, $rightField => 6)), + array($modelClass => array('id' => 5, 'name' => '1.2', $parentField => 1, $leftField => 8, $rightField => 13)), + array($modelClass => array( 'id' => 6, 'name' => '1.2.1', $parentField => 5, $leftField => 9, $rightField => 10)), + array($modelClass => array('id' => 7, 'name' => '1.2.2', $parentField => 5, $leftField => 11, $rightField => 12)) + ); + $this->assertEqual($total, $expects); + } + +/** + * testReorderTree method + * + * @access public + * @return void + */ + function testReorderTree() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(3, 3); + $nodes = $this->Tree->find('list', array('order' => $leftField)); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.1'), array('id')); + $this->Tree->moveDown($data[$modelClass]['id']); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.2.1'), array('id')); + $this->Tree->moveDown($data[$modelClass]['id']); + + $data = $this->Tree->find(array($modelClass . '.name' => '1.3.2.2'), array('id')); + $this->Tree->moveDown($data[$modelClass]['id']); + + $unsortedNodes = $this->Tree->find('list', array('order' => $leftField)); + $this->assertNotIdentical($nodes, $unsortedNodes); + + $this->Tree->reorder(); + $sortedNodes = $this->Tree->find('list', array('order' => $leftField)); + $this->assertIdentical($nodes, $sortedNodes); + } + +/** + * test reordering large-ish trees with cacheQueries = true. + * This caused infinite loops when moving down elements as stale data is returned + * from the memory cache + * + * @access public + * @return void + */ + function testReorderBigTreeWithQueryCaching() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 10); + + $original = $this->Tree->cacheQueries; + $this->Tree->cacheQueries = true; + $this->Tree->reorder(array('field' => 'name', 'direction' => 'DESC')); + $this->assertTrue($this->Tree->cacheQueries, 'cacheQueries was not restored after reorder(). %s'); + $this->Tree->cacheQueries = $original; + } +/** + * testGenerateTreeListWithSelfJoin method + * + * @access public + * @return void + */ + function testGenerateTreeListWithSelfJoin() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->bindModel(array('belongsTo' => array('Dummy' => + array('className' => $modelClass, 'foreignKey' => $parentField, 'conditions' => array('Dummy.id' => null)))), false); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->generateTreeList(); + $expected = array(1 => '1. Root', 2 => '_1.1', 3 => '__1.1.1', 4 => '__1.1.2', 5 => '_1.2', 6 => '__1.2.1', 7 => '__1.2.2'); + $this->assertIdentical($result, $expected); + } + +/** + * testArraySyntax method + * + * @access public + * @return void + */ + function testArraySyntax() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(3, 3); + $this->assertIdentical($this->Tree->childCount(2), $this->Tree->childCount(array('id' => 2))); + $this->assertIdentical($this->Tree->getParentNode(2), $this->Tree->getParentNode(array('id' => 2))); + $this->assertIdentical($this->Tree->getPath(4), $this->Tree->getPath(array('id' => 4))); + } +} + +/** + * ScopedTreeTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class ScopedTreeTest extends NumberTreeTest { + +/** + * settings property + * + * @var array + * @access public + */ + var $settings = array( + 'modelClass' => 'FlagTree', + 'leftField' => 'lft', + 'rightField' => 'rght', + 'parentField' => 'parent_id' + ); + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.flag_tree', 'core.ad', 'core.campaign', 'core.translate', 'core.number_tree_two'); + +/** + * testStringScope method + * + * @access public + * @return void + */ + function testStringScope() { + $this->Tree =& new FlagTree(); + $this->Tree->initialize(2, 3); + + $this->Tree->id = 1; + $this->Tree->saveField('flag', 1); + $this->Tree->id = 2; + $this->Tree->saveField('flag', 1); + + $result = $this->Tree->children(); + $expected = array( + array('FlagTree' => array('id' => '3', 'name' => '1.1.1', 'parent_id' => '2', 'lft' => '3', 'rght' => '4', 'flag' => '0')), + array('FlagTree' => array('id' => '4', 'name' => '1.1.2', 'parent_id' => '2', 'lft' => '5', 'rght' => '6', 'flag' => '0')), + array('FlagTree' => array('id' => '5', 'name' => '1.1.3', 'parent_id' => '2', 'lft' => '7', 'rght' => '8', 'flag' => '0')) + ); + $this->assertEqual($result, $expected); + + $this->Tree->Behaviors->attach('Tree', array('scope' => 'FlagTree.flag = 1')); + $this->assertEqual($this->Tree->children(), array()); + + $this->Tree->id = 1; + $this->Tree->Behaviors->attach('Tree', array('scope' => 'FlagTree.flag = 1')); + + $result = $this->Tree->children(); + $expected = array(array('FlagTree' => array('id' => '2', 'name' => '1.1', 'parent_id' => '1', 'lft' => '2', 'rght' => '9', 'flag' => '1'))); + $this->assertEqual($result, $expected); + + $this->assertTrue($this->Tree->delete()); + $this->assertEqual($this->Tree->find('count'), 11); + } + +/** + * testArrayScope method + * + * @access public + * @return void + */ + function testArrayScope() { + $this->Tree =& new FlagTree(); + $this->Tree->initialize(2, 3); + + $this->Tree->id = 1; + $this->Tree->saveField('flag', 1); + $this->Tree->id = 2; + $this->Tree->saveField('flag', 1); + + $result = $this->Tree->children(); + $expected = array( + array('FlagTree' => array('id' => '3', 'name' => '1.1.1', 'parent_id' => '2', 'lft' => '3', 'rght' => '4', 'flag' => '0')), + array('FlagTree' => array('id' => '4', 'name' => '1.1.2', 'parent_id' => '2', 'lft' => '5', 'rght' => '6', 'flag' => '0')), + array('FlagTree' => array('id' => '5', 'name' => '1.1.3', 'parent_id' => '2', 'lft' => '7', 'rght' => '8', 'flag' => '0')) + ); + $this->assertEqual($result, $expected); + + $this->Tree->Behaviors->attach('Tree', array('scope' => array('FlagTree.flag' => 1))); + $this->assertEqual($this->Tree->children(), array()); + + $this->Tree->id = 1; + $this->Tree->Behaviors->attach('Tree', array('scope' => array('FlagTree.flag' => 1))); + + $result = $this->Tree->children(); + $expected = array(array('FlagTree' => array('id' => '2', 'name' => '1.1', 'parent_id' => '1', 'lft' => '2', 'rght' => '9', 'flag' => '1'))); + $this->assertEqual($result, $expected); + + $this->assertTrue($this->Tree->delete()); + $this->assertEqual($this->Tree->find('count'), 11); + } + +/** + * testMoveUpWithScope method + * + * @access public + * @return void + */ + function testMoveUpWithScope() { + $this->Ad =& new Ad(); + $this->Ad->Behaviors->attach('Tree', array('scope'=>'Campaign')); + $this->Ad->moveUp(6); + + $this->Ad->id = 4; + $result = $this->Ad->children(); + $this->assertEqual(Set::extract('/Ad/id', $result), array(6, 5)); + $this->assertEqual(Set::extract('/Campaign/id', $result), array(2, 2)); + } + +/** + * testMoveDownWithScope method + * + * @access public + * @return void + */ + function testMoveDownWithScope() { + $this->Ad =& new Ad(); + $this->Ad->Behaviors->attach('Tree', array('scope' => 'Campaign')); + $this->Ad->moveDown(6); + + $this->Ad->id = 4; + $result = $this->Ad->children(); + $this->assertEqual(Set::extract('/Ad/id', $result), array(5, 6)); + $this->assertEqual(Set::extract('/Campaign/id', $result), array(2, 2)); + } + +/** + * Tests the interaction (non-interference) between TreeBehavior and other behaviors with respect + * to callback hooks + * + * @access public + * @return void + */ + function testTranslatingTree() { + $this->Tree =& new FlagTree(); + $this->Tree->cacheQueries = false; + $this->Tree->translateModel = 'TranslateTreeTestModel'; + $this->Tree->Behaviors->attach('Translate', array('name')); + + //Save + $this->Tree->locale = 'eng'; + $data = array('FlagTree' => array( + 'name' => 'name #1', + 'locale' => 'eng', + 'parent_id' => null, + )); + $this->Tree->save($data); + $result = $this->Tree->find('all'); + $expected = array(array('FlagTree' => array( + 'id' => 1, + 'name' => 'name #1', + 'parent_id' => null, + 'lft' => 1, + 'rght' => 2, + 'flag' => 0, + 'locale' => 'eng', + ))); + $this->assertEqual($result, $expected); + + //update existing record, same locale + $this->Tree->create(); + $data['FlagTree']['name'] = 'Named 2'; + $this->Tree->id = 1; + $this->Tree->save($data); + $result = $this->Tree->find('all'); + $expected = array(array('FlagTree' => array( + 'id' => 1, + 'name' => 'Named 2', + 'parent_id' => null, + 'lft' => 1, + 'rght' => 2, + 'flag' => 0, + 'locale' => 'eng', + ))); + $this->assertEqual($result, $expected); + + //update different locale, same record + $this->Tree->create(); + $this->Tree->locale = 'deu'; + $this->Tree->id = 1; + $data = array('FlagTree' => array( + 'id' => 1, + 'parent_id' => null, + 'name' => 'namen #1', + 'locale' => 'deu', + )); + $this->Tree->save($data); + + $this->Tree->locale = 'deu'; + $result = $this->Tree->find('all'); + $expected = array(array('FlagTree' => array( + 'id' => 1, + 'name' => 'namen #1', + 'parent_id' => null, + 'lft' => 1, + 'rght' => 2, + 'flag' => 0, + 'locale' => 'deu', + ))); + $this->assertEqual($result, $expected); + + //Save with bindTranslation + $this->Tree->locale = 'eng'; + $data = array( + 'name' => array('eng' => 'New title', 'spa' => 'Nuevo leyenda'), + 'parent_id' => null + ); + $this->Tree->create($data); + $this->Tree->save(); + + $this->Tree->unbindTranslation(); + $translations = array('name' => 'Name'); + $this->Tree->bindTranslation($translations, false); + $this->Tree->locale = array('eng', 'spa'); + + $result = $this->Tree->read(); + $expected = array( + 'FlagTree' => array('id' => 2, 'parent_id' => null, 'locale' => 'eng', 'name' => 'New title', 'flag' => 0, 'lft' => 3, 'rght' => 4), + 'Name' => array( + array('id' => 21, 'locale' => 'eng', 'model' => 'FlagTree', 'foreign_key' => 2, 'field' => 'name', 'content' => 'New title'), + array('id' => 22, 'locale' => 'spa', 'model' => 'FlagTree', 'foreign_key' => 2, 'field' => 'name', 'content' => 'Nuevo leyenda') + ), + ); + $this->assertEqual($result, $expected); + } + +/** + * testGenerateTreeListWithSelfJoin method + * + * @return void + * @access public + */ + function testAliasesWithScopeInTwoTreeAssociations() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $this->TreeTwo =& new NumberTreeTwo(); + + $record = $this->Tree->find('first'); + + $this->Tree->bindModel(array( + 'hasMany' => array( + 'SecondTree' => array( + 'className' => 'NumberTreeTwo', + 'foreignKey' => 'number_tree_id' + ) + ) + )); + $this->TreeTwo->bindModel(array( + 'belongsTo' => array( + 'FirstTree' => array( + 'className' => $modelClass, + 'foreignKey' => 'number_tree_id' + ) + ) + )); + $this->TreeTwo->Behaviors->attach('Tree', array( + 'scope' => 'FirstTree' + )); + + $data = array( + 'NumberTreeTwo' => array( + 'name' => 'First', + 'number_tree_id' => $record['FlagTree']['id'] + ) + ); + $this->TreeTwo->create(); + $this->assertTrue($this->TreeTwo->save($data)); + + $result = $this->TreeTwo->find('first'); + $expected = array('NumberTreeTwo' => array( + 'id' => 1, + 'name' => 'First', + 'number_tree_id' => $record['FlagTree']['id'], + 'parent_id' => null, + 'lft' => 1, + 'rght' => 2 + )); + $this->assertEqual($result, $expected); + } +} + +/** + * AfterTreeTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class AfterTreeTest extends NumberTreeTest { + +/** + * settings property + * + * @var array + * @access public + */ + var $settings = array( + 'modelClass' => 'AfterTree', + 'leftField' => 'lft', + 'rightField' => 'rght', + 'parentField' => 'parent_id' + ); + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.after_tree'); + +/** + * Tests the afterSave callback in the model + * + * @access public + * @return void + */ + function testAftersaveCallback() { + $this->Tree =& new AfterTree(); + + $expected = array('AfterTree' => array('name' => 'Six and One Half Changed in AfterTree::afterSave() but not in database', 'parent_id' => 6, 'lft' => 11, 'rght' => 12)); + $result = $this->Tree->save(array('AfterTree' => array('name' => 'Six and One Half', 'parent_id' => 6))); + $this->assertEqual($result, $expected); + + $expected = array('AfterTree' => array('name' => 'Six and One Half', 'parent_id' => 6, 'lft' => 11, 'rght' => 12, 'id' => 8)); + $result = $this->Tree->find('all'); + $this->assertEqual($result[7], $expected); + } +} + +/** + * UnconventionalTreeTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class UnconventionalTreeTest extends NumberTreeTest { + +/** + * settings property + * + * @var array + * @access public + */ + var $settings = array( + 'modelClass' => 'UnconventionalTree', + 'leftField' => 'left', + 'rightField' => 'right', + 'parentField' => 'join' + ); + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.unconventional_tree'); +} + +/** + * UuidTreeTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class UuidTreeTest extends NumberTreeTest { + +/** + * settings property + * + * @var array + * @access public + */ + var $settings = array( + 'modelClass' => 'UuidTree', + 'leftField' => 'lft', + 'rightField' => 'rght', + 'parentField' => 'parent_id' + ); + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.uuid_tree'); + +/** + * testMovePromote method + * + * @return void + * @access public + */ + function testMovePromote() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $parent = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $parent_id = $parent[$modelClass]['id']; + + $data = $this->Tree->find(array($modelClass . '.name' => '1.1.1'), array('id')); + $this->Tree->id= $data[$modelClass]['id']; + $this->Tree->saveField($parentField, $parent_id); + $direct = $this->Tree->children($parent_id, true, array('name', $leftField, $rightField)); + $expects = array(array($modelClass => array('name' => '1.1', $leftField => 2, $rightField => 5)), + array($modelClass => array('name' => '1.2', $leftField => 6, $rightField => 11)), + array($modelClass => array('name' => '1.1.1', $leftField => 12, $rightField => 13))); + $this->assertEqual($direct, $expects); + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testMoveWithWhitelist method + * + * @return void + * @access public + */ + function testMoveWithWhitelist() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $this->Tree->id = null; + + $parent = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $parent_id = $parent[$modelClass]['id']; + + $data = $this->Tree->find(array($modelClass . '.name' => '1.1.1'), array('id')); + $this->Tree->id = $data[$modelClass]['id']; + $this->Tree->whitelist = array($parentField, 'name', 'description'); + $this->Tree->saveField($parentField, $parent_id); + + $result = $this->Tree->children($parent_id, true, array('name', $leftField, $rightField)); + $expected = array(array($modelClass => array('name' => '1.1', $leftField => 2, $rightField => 5)), + array($modelClass => array('name' => '1.2', $leftField => 6, $rightField => 11)), + array($modelClass => array('name' => '1.1.1', $leftField => 12, $rightField => 13))); + $this->assertEqual($result, $expected); + $this->assertTrue($this->Tree->verify()); + } + +/** + * testRemoveNoChildren method + * + * @return void + * @access public + */ + function testRemoveNoChildren() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $initialCount = $this->Tree->find('count'); + + $result = $this->Tree->findByName('1.1.1'); + $this->Tree->removeFromTree($result[$modelClass]['id']); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount, $laterCount); + + $nodes = $this->Tree->find('list', array('order' => $leftField)); + $expects = array( + '1. Root', + '1.1', + '1.1.2', + '1.2', + '1.2.1', + '1.2.2', + '1.1.1', + ); + + $this->assertEqual(array_values($nodes), $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testRemoveAndDeleteNoChildren method + * + * @return void + * @access public + */ + function testRemoveAndDeleteNoChildren() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + $initialCount = $this->Tree->find('count'); + + $result = $this->Tree->findByName('1.1.1'); + $this->Tree->removeFromTree($result[$modelClass]['id'], true); + + $laterCount = $this->Tree->find('count'); + $this->assertEqual($initialCount - 1, $laterCount); + + $nodes = $this->Tree->find('list', array('order' => $leftField)); + $expects = array( + '1. Root', + '1.1', + '1.1.2', + '1.2', + '1.2.1', + '1.2.2', + ); + $this->assertEqual(array_values($nodes), $expects); + + $validTree = $this->Tree->verify(); + $this->assertIdentical($validTree, true); + } + +/** + * testChildren method + * + * @return void + * @access public + */ + function testChildren() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $data = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $this->Tree->id = $data[$modelClass]['id']; + + $direct = $this->Tree->children(null, true, array('name', $leftField, $rightField)); + $expects = array(array($modelClass => array('name' => '1.1', $leftField => 2, $rightField => 7)), + array($modelClass => array('name' => '1.2', $leftField => 8, $rightField => 13))); + $this->assertEqual($direct, $expects); + + $total = $this->Tree->children(null, null, array('name', $leftField, $rightField)); + $expects = array(array($modelClass => array('name' => '1.1', $leftField => 2, $rightField => 7)), + array($modelClass => array('name' => '1.1.1', $leftField => 3, $rightField => 4)), + array($modelClass => array('name' => '1.1.2', $leftField => 5, $rightField => 6)), + array($modelClass => array('name' => '1.2', $leftField => 8, $rightField => 13)), + array($modelClass => array('name' => '1.2.1', $leftField => 9, $rightField => 10)), + array($modelClass => array('name' => '1.2.2', $leftField => 11, $rightField => 12))); + $this->assertEqual($total, $expects); + } + +/** + * testNoAmbiguousColumn method + * + * @return void + * @access public + */ + function testNoAmbiguousColumn() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->initialize(2, 2); + + $this->Tree->bindModel(array('belongsTo' => array('Dummy' => + array('className' => $modelClass, 'foreignKey' => $parentField, 'conditions' => array('Dummy.id' => null)))), false); + + $data = $this->Tree->find(array($modelClass . '.name' => '1. Root')); + $this->Tree->id = $data[$modelClass]['id']; + + $direct = $this->Tree->children(null, true, array('name', $leftField, $rightField)); + $expects = array(array($modelClass => array('name' => '1.1', $leftField => 2, $rightField => 7)), + array($modelClass => array('name' => '1.2', $leftField => 8, $rightField => 13))); + $this->assertEqual($direct, $expects); + + $total = $this->Tree->children(null, null, array('name', $leftField, $rightField)); + $expects = array( + array($modelClass => array('name' => '1.1', $leftField => 2, $rightField => 7)), + array($modelClass => array('name' => '1.1.1', $leftField => 3, $rightField => 4)), + array($modelClass => array('name' => '1.1.2', $leftField => 5, $rightField => 6)), + array($modelClass => array('name' => '1.2', $leftField => 8, $rightField => 13)), + array($modelClass => array('name' => '1.2.1', $leftField => 9, $rightField => 10)), + array($modelClass => array('name' => '1.2.2', $leftField => 11, $rightField => 12)) + ); + $this->assertEqual($total, $expects); + } + +/** + * testGenerateTreeListWithSelfJoin method + * + * @return void + * @access public + */ + function testGenerateTreeListWithSelfJoin() { + extract($this->settings); + $this->Tree =& new $modelClass(); + $this->Tree->bindModel(array('belongsTo' => array('Dummy' => + array('className' => $modelClass, 'foreignKey' => $parentField, 'conditions' => array('Dummy.id' => null)))), false); + $this->Tree->initialize(2, 2); + + $result = $this->Tree->generateTreeList(); + $expected = array('1. Root', '_1.1', '__1.1.1', '__1.1.2', '_1.2', '__1.2.1', '__1.2.2'); + $this->assertIdentical(array_values($result), $expected); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/cake_schema.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/cake_schema.test.php new file mode 100644 index 000000000..2b3abcc8b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/cake_schema.test.php @@ -0,0 +1,980 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5550 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Model', 'CakeSchema', false); + +/** + * Test for Schema database management + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class MyAppSchema extends CakeSchema { + +/** + * name property + * + * @var string 'MyApp' + * @access public + */ + var $name = 'MyApp'; + +/** + * connection property + * + * @var string 'test_suite' + * @access public + */ + var $connection = 'test_suite'; + +/** + * comments property + * + * @var array + * @access public + */ + var $comments = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'post_id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'user_id' => array('type' => 'integer', 'null' => false), + 'title' => array('type' => 'string', 'null' => false, 'length' => 100), + 'comment' => array('type' => 'text', 'null' => false, 'default' => null), + 'published' => array('type' => 'string', 'null' => true, 'default' => 'N', 'length' => 1), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + ); + +/** + * posts property + * + * @var array + * @access public + */ + var $posts = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'author_id' => array('type' => 'integer', 'null' => true, 'default' => ''), + 'title' => array('type' => 'string', 'null' => false, 'default' => 'Title'), + 'body' => array('type' => 'text', 'null' => true, 'default' => null), + 'summary' => array('type' => 'text', 'null' => true), + 'published' => array('type' => 'string', 'null' => true, 'default' => 'Y', 'length' => 1), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + ); + +/** + * setup method + * + * @param mixed $version + * @access public + * @return void + */ + function setup($version) { + } + +/** + * teardown method + * + * @param mixed $version + * @access public + * @return void + */ + function teardown($version) { + } +} + +/** + * TestAppSchema class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TestAppSchema extends CakeSchema { + +/** + * name property + * + * @var string 'MyApp' + * @access public + */ + var $name = 'MyApp'; + +/** + * comments property + * + * @var array + * @access public + */ + var $comments = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0,'key' => 'primary'), + 'article_id' => array('type' => 'integer', 'null' => false), + 'user_id' => array('type' => 'integer', 'null' => false), + 'comment' => array('type' => 'text', 'null' => true, 'default' => null), + 'published' => array('type' => 'string', 'null' => true, 'default' => 'N', 'length' => 1), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + 'tableParameters' => array(), + ); + +/** + * posts property + * + * @var array + * @access public + */ + var $posts = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'author_id' => array('type' => 'integer', 'null' => false), + 'title' => array('type' => 'string', 'null' => false), + 'body' => array('type' => 'text', 'null' => true, 'default' => null), + 'published' => array('type' => 'string', 'null' => true, 'default' => 'N', 'length' => 1), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + 'tableParameters' => array(), + ); + +/** + * posts_tags property + * + * @var array + * @access public + */ + var $posts_tags = array( + 'post_id' => array('type' => 'integer', 'null' => false, 'key' => 'primary'), + 'tag_id' => array('type' => 'string', 'null' => false, 'key' => 'primary'), + 'indexes' => array('posts_tag' => array('column' => array('tag_id', 'post_id'), 'unique' => 1)), + 'tableParameters' => array() + ); + +/** + * tags property + * + * @var array + * @access public + */ + var $tags = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'tag' => array('type' => 'string', 'null' => false), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + 'tableParameters' => array() + ); + +/** + * datatypes property + * + * @var array + * @access public + */ + var $datatypes = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'float_field' => array('type' => 'float', 'null' => false, 'length' => '5,2', 'default' => ''), + 'bool' => array('type' => 'boolean', 'null' => false, 'default' => false), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + 'tableParameters' => array() + ); + +/** + * setup method + * + * @param mixed $version + * @access public + * @return void + */ + function setup($version) { + } + +/** + * teardown method + * + * @param mixed $version + * @access public + * @return void + */ + function teardown($version) { + } +} + +/** + * SchmeaPost class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SchemaPost extends CakeTestModel { + +/** + * name property + * + * @var string 'SchemaPost' + * @access public + */ + var $name = 'SchemaPost'; + +/** + * useTable property + * + * @var string 'posts' + * @access public + */ + var $useTable = 'posts'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('SchemaComment'); + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('SchemaTag'); +} + +/** + * SchemaComment class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SchemaComment extends CakeTestModel { + +/** + * name property + * + * @var string 'SchemaComment' + * @access public + */ + var $name = 'SchemaComment'; + +/** + * useTable property + * + * @var string 'comments' + * @access public + */ + var $useTable = 'comments'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('SchemaPost'); +} + +/** + * SchemaTag class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SchemaTag extends CakeTestModel { + +/** + * name property + * + * @var string 'SchemaTag' + * @access public + */ + var $name = 'SchemaTag'; + +/** + * useTable property + * + * @var string 'tags' + * @access public + */ + var $useTable = 'tags'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('SchemaPost'); +} + +/** + * SchemaDatatype class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SchemaDatatype extends CakeTestModel { + +/** + * name property + * + * @var string 'SchemaDatatype' + * @access public + */ + var $name = 'SchemaDatatype'; + +/** + * useTable property + * + * @var string 'datatypes' + * @access public + */ + var $useTable = 'datatypes'; +} + +/** + * Testdescribe class + * + * This class is defined purely to inherit the cacheSources variable otherwise + * testSchemaCreatTable will fail if listSources has already been called and + * its source cache populated - I.e. if the test is run within a group + * + * @uses CakeTestModel + * @package + * @subpackage cake.tests.cases.libs.model + */ +class Testdescribe extends CakeTestModel { + +/** + * name property + * + * @var string 'Testdescribe' + * @access public + */ + var $name = 'Testdescribe'; +} + +/** + * SchemaCrossDatabase class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SchemaCrossDatabase extends CakeTestModel { + +/** + * name property + * + * @var string 'SchemaCrossDatabase' + * @access public + */ + var $name = 'SchemaCrossDatabase'; + +/** + * useTable property + * + * @var string 'posts' + * @access public + */ + var $useTable = 'cross_database'; + +/** + * useDbConfig property + * + * @var string 'test2' + * @access public + */ + var $useDbConfig = 'test2'; +} + +/** + * SchemaCrossDatabaseFixture class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SchemaCrossDatabaseFixture extends CakeTestFixture { + +/** + * name property + * + * @var string 'CrossDatabase' + * @access public + */ + var $name = 'CrossDatabase'; + +/** + * table property + * + * @access public + */ + var $table = 'cross_database'; + +/** + * fields property + * + * @var array + * @access public + */ + var $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'name' => 'string' + ); + +/** + * records property + * + * @var array + * @access public + */ + var $records = array( + array('id' => 1, 'name' => 'First'), + array('id' => 2, 'name' => 'Second'), + ); +} + +/** + * SchemaPrefixAuthUser class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SchemaPrefixAuthUser extends CakeTestModel { +/** + * name property + * + * @var string + */ + var $name = 'SchemaPrefixAuthUser'; +/** + * table prefix + * + * @var string + */ + var $tablePrefix = 'auth_'; +/** + * useTable + * + * @var string + */ + var $useTable = 'users'; +} + +/** + * CakeSchemaTest + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class CakeSchemaTest extends CakeTestCase { + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array( + 'core.post', 'core.tag', 'core.posts_tag', 'core.test_plugin_comment', + 'core.datatype', 'core.auth_user', 'core.author', + 'core.test_plugin_article', 'core.user', 'core.comment' + ); + +/** + * setUp method + * + * @access public + * @return void + */ + function startTest() { + $this->Schema = new TestAppSchema(); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + @unlink(TMP . 'tests' . DS .'schema.php'); + unset($this->Schema); + ClassRegistry::flush(); + } + +/** + * testSchemaName method + * + * @access public + * @return void + */ + function testSchemaName() { + $Schema = new CakeSchema(); + $this->assertEqual(strtolower($Schema->name), strtolower(APP_DIR)); + + Configure::write('App.dir', 'Some.name.with.dots'); + $Schema = new CakeSchema(); + $this->assertEqual($Schema->name, 'SomeNameWithDots'); + + Configure::write('App.dir', 'app'); + } + +/** + * testSchemaRead method + * + * @access public + * @return void + */ + function testSchemaRead() { + $read = $this->Schema->read(array( + 'connection' => 'test_suite', + 'name' => 'TestApp', + 'models' => array('SchemaPost', 'SchemaComment', 'SchemaTag', 'SchemaDatatype') + )); + unset($read['tables']['missing']); + + $expected = array('comments', 'datatypes', 'posts', 'posts_tags', 'tags'); + $this->assertEqual(array_keys($read['tables']), $expected); + foreach ($read['tables'] as $table => $fields) { + $this->assertEqual(array_keys($fields), array_keys($this->Schema->tables[$table])); + } + + $this->assertEqual( + $read['tables']['datatypes']['float_field'], + $this->Schema->tables['datatypes']['float_field'] + ); + + $SchemaPost =& ClassRegistry::init('SchemaPost'); + $SchemaPost->table = 'sts'; + $SchemaPost->tablePrefix = 'po'; + $read = $this->Schema->read(array( + 'connection' => 'test_suite', + 'name' => 'TestApp', + 'models' => array('SchemaPost') + )); + $this->assertFalse(isset($read['tables']['missing']['posts']), 'Posts table was not read from tablePrefix %s'); + + $read = $this->Schema->read(array( + 'connection' => 'test_suite', + 'name' => 'TestApp', + 'models' => array('SchemaComment', 'SchemaTag', 'SchemaPost') + )); + $this->assertFalse(isset($read['tables']['missing']['posts_tags']), 'Join table marked as missing %s'); + } + +/** + * test read() with tablePrefix properties. + * + * @return void + */ + function testSchemaReadWithTablePrefix() { + $model =& new SchemaPrefixAuthUser(); + + $Schema =& new CakeSchema(); + $read = $Schema->read(array( + 'connection' => 'test_suite', + 'name' => 'TestApp', + 'models' => array('SchemaPrefixAuthUser') + )); + unset($read['tables']['missing']); + $this->assertTrue(isset($read['tables']['auth_users']), 'auth_users key missing %s'); + + } + +/** + * test reading schema with config prefix. + * + * @return void + */ + function testSchemaReadWithConfigPrefix() { + $db =& ConnectionManager::getDataSource('test_suite'); + $config = $db->config; + $config['prefix'] = 'schema_test_prefix_'; + ConnectionManager::create('schema_prefix', $config); + $read = $this->Schema->read(array('connection' => 'schema_prefix', 'models' => false)); + $this->assertTrue(empty($read['tables'])); + } + +/** + * test reading schema from plugins. + * + * @return void + */ + function testSchemaReadWithPlugins() { + App::objects('model', null, false); + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + + $Schema =& new CakeSchema(); + $Schema->plugin = 'TestPlugin'; + $read = $Schema->read(array( + 'connection' => 'test_suite', + 'name' => 'TestApp', + 'models' => true + )); + unset($read['tables']['missing']); + $this->assertTrue(isset($read['tables']['auth_users'])); + $this->assertTrue(isset($read['tables']['authors'])); + $this->assertTrue(isset($read['tables']['test_plugin_comments'])); + $this->assertTrue(isset($read['tables']['posts'])); + $this->assertEqual(count($read['tables']), 4); + + App::build(); + } + +/** + * test reading schema with tables from another database. + * + * @return void + */ + function testSchemaReadWithCrossDatabase() { + $config = new DATABASE_CONFIG(); + $skip = $this->skipIf( + !isset($config->test) || !isset($config->test2), + '%s Primary and secondary test databases not configured, skipping cross-database ' + .'join tests.' + .' To run these tests, you must define $test and $test2 in your database configuration.' + ); + if ($skip) { + return; + } + + $db2 =& ConnectionManager::getDataSource('test2'); + $fixture = new SchemaCrossDatabaseFixture(); + $fixture->create($db2); + $fixture->insert($db2); + + $read = $this->Schema->read(array( + 'connection' => 'test_suite', + 'name' => 'TestApp', + 'models' => array('SchemaCrossDatabase', 'SchemaPost') + )); + $this->assertTrue(isset($read['tables']['posts'])); + $this->assertFalse(isset($read['tables']['cross_database']), 'Cross database should not appear'); + $this->assertFalse(isset($read['tables']['missing']['cross_database']), 'Cross database should not appear'); + + $read = $this->Schema->read(array( + 'connection' => 'test2', + 'name' => 'TestApp', + 'models' => array('SchemaCrossDatabase', 'SchemaPost') + )); + $this->assertFalse(isset($read['tables']['posts']), 'Posts should not appear'); + $this->assertFalse(isset($read['tables']['posts']), 'Posts should not appear'); + $this->assertTrue(isset($read['tables']['cross_database'])); + + $fixture->drop($db2); + } + +/** + * test that tables are generated correctly + * + * @return void + */ + function testGenerateTable() { + $fields = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'author_id' => array('type' => 'integer', 'null' => false), + 'title' => array('type' => 'string', 'null' => false), + 'body' => array('type' => 'text', 'null' => true, 'default' => null), + 'published' => array('type' => 'string', 'null' => true, 'default' => 'N', 'length' => 1), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + ); + $result = $this->Schema->generateTable('posts', $fields); + $this->assertPattern('/var \$posts/', $result); + + eval(substr($result, 4)); + $this->assertEqual($posts, $fields); + } +/** + * testSchemaWrite method + * + * @access public + * @return void + */ + function testSchemaWrite() { + $write = $this->Schema->write(array('name' => 'MyOtherApp', 'tables' => $this->Schema->tables, 'path' => TMP . 'tests')); + $file = file_get_contents(TMP . 'tests' . DS .'schema.php'); + $this->assertEqual($write, $file); + + require_once( TMP . 'tests' . DS .'schema.php'); + $OtherSchema = new MyOtherAppSchema(); + $this->assertEqual($this->Schema->tables, $OtherSchema->tables); + } + +/** + * testSchemaComparison method + * + * @access public + * @return void + */ + function testSchemaComparison() { + $New = new MyAppSchema(); + $compare = $New->compare($this->Schema); + $expected = array( + 'comments' => array( + 'add' => array( + 'post_id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'title' => array('type' => 'string', 'null' => false, 'length' => 100), + ), + 'drop' => array( + 'article_id' => array('type' => 'integer', 'null' => false), + 'tableParameters' => array(), + ), + 'change' => array( + 'comment' => array('type' => 'text', 'null' => false, 'default' => null), + ) + ), + 'posts' => array( + 'add' => array( + 'summary' => array('type' => 'text', 'null' => 1), + ), + 'drop' => array( + 'tableParameters' => array(), + ), + 'change' => array( + 'author_id' => array('type' => 'integer', 'null' => true, 'default' => ''), + 'title' => array('type' => 'string', 'null' => false, 'default' => 'Title'), + 'published' => array('type' => 'string', 'null' => true, 'default' => 'Y', 'length' => '1') + ) + ), + ); + $this->assertEqual($expected, $compare); + + $tables = array( + 'missing' => array( + 'categories' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 100), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'MyISAM') + ) + ), + 'ratings' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'foreign_key' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'model' => array('type' => 'varchar', 'null' => false, 'default' => NULL), + 'value' => array('type' => 'float', 'null' => false, 'length' => '5,2', 'default' => NULL), + 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'MyISAM') + ) + ); + $compare = $New->compare($this->Schema, $tables); + $expected = array( + 'ratings' => array( + 'add' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'foreign_key' => array('type' => 'integer', 'null' => false, 'default' => NULL), + 'model' => array('type' => 'varchar', 'null' => false, 'default' => NULL), + 'value' => array('type' => 'float', 'null' => false, 'length' => '5,2', 'default' => NULL), + 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), + 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'MyISAM') + ) + ) + ); + $this->assertEqual($expected, $compare); + } + +/** + * test comparing '' and null and making sure they are different. + * + * @return void + */ + function testCompareEmptyStringAndNull() { + $One =& new CakeSchema(array( + 'posts' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'name' => array('type' => 'string', 'null' => false, 'default' => '') + ) + )); + $Two =& new CakeSchema(array( + 'posts' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), + 'name' => array('type' => 'string', 'null' => false, 'default' => null) + ) + )); + $compare = $One->compare($Two); + $expected = array( + 'posts' => array( + 'change' => array( + 'name' => array('type' => 'string', 'null' => false, 'default' => null) + ) + ) + ); + $this->assertEqual($expected, $compare); + } + +/** + * Test comparing tableParameters and indexes. + * + * @return void + */ + function testTableParametersAndIndexComparison() { + $old = array( + 'posts' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'author_id' => array('type' => 'integer', 'null' => false), + 'title' => array('type' => 'string', 'null' => false), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => true) + ), + 'tableParameters' => array( + 'charset' => 'latin1', + 'collate' => 'latin1_general_ci' + ) + ), + 'comments' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'post_id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'comment' => array('type' => 'text'), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => true), + 'post_id' => array('column' => 'post_id'), + ), + 'tableParameters' => array( + 'engine' => 'InnoDB', + 'charset' => 'latin1', + 'collate' => 'latin1_general_ci' + ) + ) + ); + $new = array( + 'posts' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'author_id' => array('type' => 'integer', 'null' => false), + 'title' => array('type' => 'string', 'null' => false), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => true), + 'author_id' => array('column' => 'author_id'), + ), + 'tableParameters' => array( + 'charset' => 'utf8', + 'collate' => 'utf8_general_ci', + 'engine' => 'MyISAM' + ) + ), + 'comments' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'post_id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'comment' => array('type' => 'text'), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => true), + ), + 'tableParameters' => array( + 'charset' => 'utf8', + 'collate' => 'utf8_general_ci' + ) + ) + ); + $compare = $this->Schema->compare($old, $new); + $expected = array( + 'posts' => array( + 'add' => array( + 'indexes' => array('author_id' => array('column' => 'author_id')), + ), + 'change' => array( + 'tableParameters' => array( + 'charset' => 'utf8', + 'collate' => 'utf8_general_ci', + 'engine' => 'MyISAM' + ) + ) + ), + 'comments' => array( + 'drop' => array( + 'indexes' => array('post_id' => array('column' => 'post_id')), + ), + 'change' => array( + 'tableParameters' => array( + 'charset' => 'utf8', + 'collate' => 'utf8_general_ci', + ) + ) + ) + ); + $this->assertEqual($compare, $expected); + } + +/** + * testSchemaLoading method + * + * @access public + * @return void + */ + function testSchemaLoading() { + $Other =& $this->Schema->load(array('name' => 'MyOtherApp', 'path' => TMP . 'tests')); + $this->assertEqual($Other->name, 'MyOtherApp'); + $this->assertEqual($Other->tables, $this->Schema->tables); + } + +/** + * test loading schema files inside of plugins. + * + * @return void + */ + function testSchemaLoadingFromPlugin() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + $Other =& $this->Schema->load(array('name' => 'TestPluginApp', 'plugin' => 'TestPlugin')); + $this->assertEqual($Other->name, 'TestPluginApp'); + $this->assertEqual(array_keys($Other->tables), array('acos')); + + App::build(); + } + +/** + * testSchemaCreateTable method + * + * @access public + * @return void + */ + function testSchemaCreateTable() { + $db =& ConnectionManager::getDataSource('test_suite'); + $db->cacheSources = false; + + $Schema =& new CakeSchema(array( + 'connection' => 'test_suite', + 'testdescribes' => array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'int_null' => array('type' => 'integer', 'null' => true), + 'int_not_null' => array('type' => 'integer', 'null' => false), + ), + )); + $sql = $db->createSchema($Schema); + + $col = $Schema->tables['testdescribes']['int_null']; + $col['name'] = 'int_null'; + $column = $this->db->buildColumn($col); + $this->assertPattern('/' . preg_quote($column, '/') . '/', $sql); + + $col = $Schema->tables['testdescribes']['int_not_null']; + $col['name'] = 'int_not_null'; + $column = $this->db->buildColumn($col); + $this->assertPattern('/' . preg_quote($column, '/') . '/', $sql); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/connection_manager.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/connection_manager.test.php new file mode 100644 index 000000000..e100a7bf3 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/connection_manager.test.php @@ -0,0 +1,340 @@ +ConnectionManager =& ConnectionManager::getInstance(); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + unset($this->ConnectionManager); + } + +/** + * testInstantiation method + * + * @access public + * @return void + */ + function testInstantiation() { + $this->assertTrue(is_a($this->ConnectionManager, 'ConnectionManager')); + } + +/** + * testEnumConnectionObjects method + * + * @access public + * @return void + */ + function testEnumConnectionObjects() { + $sources = ConnectionManager::enumConnectionObjects(); + $this->assertTrue(count($sources) >= 1); + + $connections = array('default', 'test', 'test_suite'); + $this->assertTrue(count(array_intersect(array_keys($sources), $connections)) >= 1); + } + +/** + * testGetDataSource method + * + * @access public + * @return void + */ + function testGetDataSource() { + $connections = ConnectionManager::enumConnectionObjects(); + $this->assertTrue(count(array_keys($connections) >= 1)); + + $source = ConnectionManager::getDataSource(key($connections)); + $this->assertTrue(is_object($source)); + + $this->expectError(new PatternExpectation('/Non-existent data source/i')); + + $source = ConnectionManager::getDataSource('non_existent_source'); + $this->assertEqual($source, null); + + } + +/** + * testGetPluginDataSource method + * + * @access public + * @return void + */ + function testGetPluginDataSource() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + + $name = 'test_source'; + $config = array('datasource' => 'TestPlugin.TestSource'); + $connection = ConnectionManager::create($name, $config); + + $this->assertTrue(class_exists('TestSource')); + $this->assertEqual($connection->configKeyName, $name); + $this->assertEqual($connection->config, $config); + + App::build(); + } + +/** + * testGetPluginDataSourceAndPluginDriver method + * + * @access public + * @return void + */ + function testGetPluginDataSourceAndPluginDriver() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + + $name = 'test_plugin_source_and_driver'; + $config = array('datasource' => 'TestPlugin.TestSource', 'driver' => 'TestPlugin.TestDriver'); + + $connection = ConnectionManager::create($name, $config); + + $this->assertTrue(class_exists('TestSource')); + $this->assertTrue(class_exists('TestDriver')); + $this->assertEqual($connection->configKeyName, $name); + $this->assertEqual($connection->config, $config); + + App::build(); + } + +/** + * testGetLocalDataSourceAndPluginDriver method + * + * @access public + * @return void + */ + function testGetLocalDataSourceAndPluginDriver() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + + $name = 'test_local_source_and_plugin_driver'; + $config = array('datasource' => 'dbo', 'driver' => 'TestPlugin.DboDummy'); + + $connection = ConnectionManager::create($name, $config); + + $this->assertTrue(class_exists('DboSource')); + $this->assertTrue(class_exists('DboDummy')); + $this->assertEqual($connection->configKeyName, $name); + + App::build(); + } + +/** + * testGetPluginDataSourceAndLocalDriver method + * + * @access public + * @return void + */ + function testGetPluginDataSourceAndLocalDriver() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'datasources' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS . 'datasources' . DS) + )); + + $name = 'test_plugin_source_and_local_driver'; + $config = array('datasource' => 'TestPlugin.TestSource', 'driver' => 'local_driver'); + + $connection = ConnectionManager::create($name, $config); + + $this->assertTrue(class_exists('TestSource')); + $this->assertTrue(class_exists('TestLocalDriver')); + $this->assertEqual($connection->configKeyName, $name); + $this->assertEqual($connection->config, $config); + App::build(); + } + +/** + * testSourceList method + * + * @access public + * @return void + */ + function testSourceList() { + $sources = ConnectionManager::sourceList(); + $this->assertTrue(count($sources) >= 1); + + $connections = array('default', 'test', 'test_suite'); + $this->assertTrue(count(array_intersect($sources, $connections)) >= 1); + } + +/** + * testGetSourceName method + * + * @access public + * @return void + */ + function testGetSourceName() { + $connections = ConnectionManager::enumConnectionObjects(); + $name = key($connections); + $source = ConnectionManager::getDataSource($name); + $result = ConnectionManager::getSourceName($source); + + $this->assertEqual($result, $name); + + $source =& new StdClass(); + $result = ConnectionManager::getSourceName($source); + $this->assertEqual($result, null); + } + +/** + * testLoadDataSource method + * + * @access public + * @return void + */ + function testLoadDataSource() { + $connections = array( + array('classname' => 'DboMysql', 'filename' => 'dbo' . DS . 'dbo_mysql'), + array('classname' => 'DboMysqli', 'filename' => 'dbo' . DS . 'dbo_mysqli'), + array('classname' => 'DboMssql', 'filename' => 'dbo' . DS . 'dbo_mssql'), + array('classname' => 'DboOracle', 'filename' => 'dbo' . DS . 'dbo_oracle'), + ); + + foreach ($connections as $connection) { + $exists = class_exists($connection['classname']); + $loaded = ConnectionManager::loadDataSource($connection); + $this->assertEqual($loaded, !$exists, "%s Failed loading the {$connection['classname']} datasource"); + } + + $connection = array('classname' => 'NonExistentDataSource', 'filename' => 'non_existent'); + $this->expectError(new PatternExpectation('/Unable to import DataSource class/i')); + + $loaded = ConnectionManager::loadDataSource($connection); + $this->assertEqual($loaded, null); + } + +/** + * testCreateDataSource method + * + * @access public + * @return void + */ + function testCreateDataSourceWithIntegrationTests() { + $name = 'test_created_connection'; + + $connections = ConnectionManager::enumConnectionObjects(); + $this->assertTrue(count(array_keys($connections) >= 1)); + + $source = ConnectionManager::getDataSource(key($connections)); + $this->assertTrue(is_object($source)); + + $config = $source->config; + $connection = ConnectionManager::create($name, $config); + + $this->assertTrue(is_object($connection)); + $this->assertEqual($name, $connection->configKeyName); + $this->assertEqual($name, ConnectionManager::getSourceName($connection)); + + $source = ConnectionManager::create(null, array()); + $this->assertEqual($source, null); + + $source = ConnectionManager::create('another_test', array()); + $this->assertEqual($source, null); + + $config = array('classname' => 'DboMysql', 'filename' => 'dbo' . DS . 'dbo_mysql'); + $source = ConnectionManager::create(null, $config); + $this->assertEqual($source, null); + } + +/** + * testConnectionData method + * + * @access public + * @return void + */ + function testConnectionData() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'datasources' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS . 'datasources' . DS) + )); + + $expected = array( + 'filename' => 'test2_source', + 'classname' => 'Test2Source', + 'parent' => '', + 'plugin' => '' + ); + + ConnectionManager::create('connection1', array('datasource' => 'Test2')); + $connections = ConnectionManager::enumConnectionObjects(); + $this->assertEqual($expected, $connections['connection1']); + + ConnectionManager::create('connection2', array('datasource' => 'Test2Source')); + $connections = ConnectionManager::enumConnectionObjects(); + $this->assertEqual($expected, $connections['connection2']); + + ConnectionManager::create('connection3', array('datasource' => 'TestPlugin.Test')); + $connections = ConnectionManager::enumConnectionObjects(); + $expected['filename'] = 'test_source'; + $expected['classname'] = 'TestSource'; + $expected['plugin'] = 'TestPlugin'; + $this->assertEqual($expected, $connections['connection3']); + + ConnectionManager::create('connection4', array('datasource' => 'TestPlugin.TestSource')); + $connections = ConnectionManager::enumConnectionObjects(); + $this->assertEqual($expected, $connections['connection4']); + + ConnectionManager::create('connection5', array('datasource' => 'Test2Other')); + $connections = ConnectionManager::enumConnectionObjects(); + $expected['filename'] = 'test2_other_source'; + $expected['classname'] = 'Test2OtherSource'; + $expected['plugin'] = ''; + $this->assertEqual($expected, $connections['connection5']); + + ConnectionManager::create('connection6', array('datasource' => 'Test2OtherSource')); + $connections = ConnectionManager::enumConnectionObjects(); + $this->assertEqual($expected, $connections['connection6']); + + ConnectionManager::create('connection7', array('datasource' => 'TestPlugin.TestOther')); + $connections = ConnectionManager::enumConnectionObjects(); + $expected['filename'] = 'test_other_source'; + $expected['classname'] = 'TestOtherSource'; + $expected['plugin'] = 'TestPlugin'; + $this->assertEqual($expected, $connections['connection7']); + + ConnectionManager::create('connection8', array('datasource' => 'TestPlugin.TestOtherSource')); + $connections = ConnectionManager::enumConnectionObjects(); + $this->assertEqual($expected, $connections['connection8']); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mssql.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mssql.test.php new file mode 100644 index 000000000..dffd1aa53 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mssql.test.php @@ -0,0 +1,677 @@ +simulate) { + $this->simulated[] = $sql; + return null; + } else { + return parent::_execute($sql); + } + } + +/** + * fetchAll method + * + * @param mixed $sql + * @access protected + * @return void + */ + function _matchRecords(&$model, $conditions = null) { + return $this->conditions(array('id' => array(1, 2))); + } + +/** + * fetchAll method + * + * @param mixed $sql + * @access protected + * @return void + */ + function fetchAll($sql, $cache = true, $modelName = null) { + $result = parent::fetchAll($sql, $cache, $modelName); + if (!empty($this->fetchAllResultsStack)) { + return array_pop($this->fetchAllResultsStack); + } + return $result; + } + +/** + * getLastQuery method + * + * @access public + * @return void + */ + function getLastQuery() { + return $this->simulated[count($this->simulated) - 1]; + } + +/** + * getPrimaryKey method + * + * @param mixed $model + * @access public + * @return void + */ + function getPrimaryKey($model) { + return parent::_getPrimaryKey($model); + } +/** + * clearFieldMappings method + * + * @access public + * @return void + */ + function clearFieldMappings() { + $this->__fieldMappings = array(); + } +} + +/** + * MssqlTestModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class MssqlTestModel extends Model { + +/** + * name property + * + * @var string 'MssqlTestModel' + * @access public + */ + var $name = 'MssqlTestModel'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * _schema property + * + * @var array + * @access protected + */ + var $_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8', 'key' => 'primary'), + 'client_id' => array('type' => 'integer', 'null' => '', 'default' => '0', 'length' => '11'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'login' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'passwd' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_1' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_2' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '25'), + 'zip_code' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'city' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'country' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'phone' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'fax' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'url' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'email' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'comments' => array('type' => 'text', 'null' => '1', 'default' => '', 'length' => ''), + 'last_login'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'MssqlClientTestModel' => array( + 'foreignKey' => 'client_id' + ) + ); +/** + * find method + * + * @param mixed $conditions + * @param mixed $fields + * @param mixed $order + * @param mixed $recursive + * @access public + * @return void + */ + function find($conditions = null, $fields = null, $order = null, $recursive = null) { + return $conditions; + } + +/** + * findAll method + * + * @param mixed $conditions + * @param mixed $fields + * @param mixed $order + * @param mixed $recursive + * @access public + * @return void + */ + function findAll($conditions = null, $fields = null, $order = null, $recursive = null) { + return $conditions; + } + +/** + * setSchema method + * + * @param array $schema + * @access public + * @return void + */ + function setSchema($schema) { + $this->_schema = $schema; + } +} + +/** + * MssqlClientTestModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class MssqlClientTestModel extends Model { +/** + * name property + * + * @var string 'MssqlAssociatedTestModel' + * @access public + */ + var $name = 'MssqlClientTestModel'; +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; +/** + * _schema property + * + * @var array + * @access protected + */ + var $_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8', 'key' => 'primary'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'email' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'created' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); +} +/** + * DboMssqlTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources.dbo + */ +class DboMssqlTest extends CakeTestCase { + +/** + * The Dbo instance to be tested + * + * @var DboSource + * @access public + */ + var $db = null; + +/** + * autoFixtures property + * + * @var bool false + * @access public + */ + var $autoFixtures = false; +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.category'); +/** + * Skip if cannot connect to mssql + * + * @access public + */ + function skip() { + $this->_initDb(); + $this->skipUnless($this->db->config['driver'] == 'mssql', '%s SQL Server connection not available'); + } + +/** + * Make sure all fixtures tables are being created + * + * @access public + */ + function start() { + $this->db->simulate = false; + parent::start(); + $this->db->simulate = true; + } +/** + * Make sure all fixtures tables are being dropped + * + * @access public + */ + function end() { + $this->db->simulate = false; + parent::end(); + $this->db->simulate = true; + } +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function setUp() { + $db = ConnectionManager::getDataSource('test_suite'); + $this->db = new DboMssqlTestDb($db->config); + $this->model = new MssqlTestModel(); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + unset($this->model); + } + +/** + * testQuoting method + * + * @access public + * @return void + */ + function testQuoting() { + $expected = "1.2"; + $result = $this->db->value(1.2, 'float'); + $this->assertIdentical($expected, $result); + + $expected = "'1,2'"; + $result = $this->db->value('1,2', 'float'); + $this->assertIdentical($expected, $result); + + $expected = 'NULL'; + $result = $this->db->value('', 'integer'); + $this->assertIdentical($expected, $result); + + $expected = 'NULL'; + $result = $this->db->value('', 'float'); + $this->assertIdentical($expected, $result); + + $expected = 'NULL'; + $result = $this->db->value('', 'binary'); + $this->assertIdentical($expected, $result); + } +/** + * testFields method + * + * @access public + * @return void + */ + function testFields() { + $fields = array( + '[MssqlTestModel].[id] AS [MssqlTestModel__0]', + '[MssqlTestModel].[client_id] AS [MssqlTestModel__1]', + '[MssqlTestModel].[name] AS [MssqlTestModel__2]', + '[MssqlTestModel].[login] AS [MssqlTestModel__3]', + '[MssqlTestModel].[passwd] AS [MssqlTestModel__4]', + '[MssqlTestModel].[addr_1] AS [MssqlTestModel__5]', + '[MssqlTestModel].[addr_2] AS [MssqlTestModel__6]', + '[MssqlTestModel].[zip_code] AS [MssqlTestModel__7]', + '[MssqlTestModel].[city] AS [MssqlTestModel__8]', + '[MssqlTestModel].[country] AS [MssqlTestModel__9]', + '[MssqlTestModel].[phone] AS [MssqlTestModel__10]', + '[MssqlTestModel].[fax] AS [MssqlTestModel__11]', + '[MssqlTestModel].[url] AS [MssqlTestModel__12]', + '[MssqlTestModel].[email] AS [MssqlTestModel__13]', + '[MssqlTestModel].[comments] AS [MssqlTestModel__14]', + 'CONVERT(VARCHAR(20), [MssqlTestModel].[last_login], 20) AS [MssqlTestModel__15]', + '[MssqlTestModel].[created] AS [MssqlTestModel__16]', + 'CONVERT(VARCHAR(20), [MssqlTestModel].[updated], 20) AS [MssqlTestModel__17]' + ); + + $result = $this->db->fields($this->model); + $expected = $fields; + $this->assertEqual($result, $expected); + + $this->db->clearFieldMappings(); + $result = $this->db->fields($this->model, null, 'MssqlTestModel.*'); + $expected = $fields; + $this->assertEqual($result, $expected); + + $this->db->clearFieldMappings(); + $result = $this->db->fields($this->model, null, array('*', 'AnotherModel.id', 'AnotherModel.name')); + $expected = array_merge($fields, array( + '[AnotherModel].[id] AS [AnotherModel__18]', + '[AnotherModel].[name] AS [AnotherModel__19]')); + $this->assertEqual($result, $expected); + + $this->db->clearFieldMappings(); + $result = $this->db->fields($this->model, null, array('*', 'MssqlClientTestModel.*')); + $expected = array_merge($fields, array( + '[MssqlClientTestModel].[id] AS [MssqlClientTestModel__18]', + '[MssqlClientTestModel].[name] AS [MssqlClientTestModel__19]', + '[MssqlClientTestModel].[email] AS [MssqlClientTestModel__20]', + 'CONVERT(VARCHAR(20), [MssqlClientTestModel].[created], 20) AS [MssqlClientTestModel__21]', + 'CONVERT(VARCHAR(20), [MssqlClientTestModel].[updated], 20) AS [MssqlClientTestModel__22]')); + $this->assertEqual($result, $expected); + } + +/** + * testDistinctFields method + * + * @access public + * @return void + */ + function testDistinctFields() { + $result = $this->db->fields($this->model, null, array('DISTINCT Car.country_code')); + $expected = array('DISTINCT [Car].[country_code] AS [Car__0]'); + $this->assertEqual($result, $expected); + + $result = $this->db->fields($this->model, null, 'DISTINCT Car.country_code'); + $expected = array('DISTINCT [Car].[country_code] AS [Car__1]'); + $this->assertEqual($result, $expected); + } + +/** + * testDistinctWithLimit method + * + * @access public + * @return void + */ + function testDistinctWithLimit() { + $this->db->read($this->model, array( + 'fields' => array('DISTINCT MssqlTestModel.city', 'MssqlTestModel.country'), + 'limit' => 5 + )); + $result = $this->db->getLastQuery(); + $this->assertPattern('/^SELECT DISTINCT TOP 5/', $result); + } + +/** + * testDescribe method + * + * @access public + * @return void + */ + function testDescribe() { + $MssqlTableDescription = array( + 0 => array( + 0 => array( + 'Default' => '((0))', + 'Field' => 'count', + 'Key' => 0, + 'Length' => '4', + 'Null' => 'NO', + 'Type' => 'integer', + ) + ) + ); + $this->db->fetchAllResultsStack = array($MssqlTableDescription); + $dummyModel = $this->model; + $result = $this->db->describe($dummyModel); + $expected = array( + 'count' => array( + 'type' => 'integer', + 'null' => false, + 'default' => '0', + 'length' => 4 + ) + ); + $this->assertEqual($result, $expected); + } +/** + * testBuildColumn + * + * @return unknown_type + * @access public + */ + function testBuildColumn() { + $column = array('name' => 'id', 'type' => 'integer', 'null' => '', 'default' => '', 'length' => '8', 'key' => 'primary'); + $result = $this->db->buildColumn($column); + $expected = '[id] int IDENTITY (1, 1) NOT NULL'; + $this->assertEqual($result, $expected); + + $column = array('name' => 'client_id', 'type' => 'integer', 'null' => '', 'default' => '0', 'length' => '11'); + $result = $this->db->buildColumn($column); + $expected = '[client_id] int DEFAULT 0 NOT NULL'; + $this->assertEqual($result, $expected); + + $column = array('name' => 'client_id', 'type' => 'integer', 'null' => true); + $result = $this->db->buildColumn($column); + $expected = '[client_id] int NULL'; + $this->assertEqual($result, $expected); + + // 'name' => 'type' format for columns + $column = array('type' => 'integer', 'name' => 'client_id'); + $result = $this->db->buildColumn($column); + $expected = '[client_id] int NULL'; + $this->assertEqual($result, $expected); + + $column = array('type' => 'string', 'name' => 'name'); + $result = $this->db->buildColumn($column); + $expected = '[name] varchar(255) NULL'; + $this->assertEqual($result, $expected); + + $column = array('name' => 'name', 'type' => 'string', 'null' => '', 'default' => '', 'length' => '255'); + $result = $this->db->buildColumn($column); + $expected = '[name] varchar(255) DEFAULT \'\' NOT NULL'; + $this->assertEqual($result, $expected); + + $column = array('name' => 'name', 'type' => 'string', 'null' => false, 'length' => '255'); + $result = $this->db->buildColumn($column); + $expected = '[name] varchar(255) NOT NULL'; + $this->assertEqual($result, $expected); + + $column = array('name' => 'name', 'type' => 'string', 'null' => false, 'default' => null, 'length' => '255'); + $result = $this->db->buildColumn($column); + $expected = '[name] varchar(255) NOT NULL'; + $this->assertEqual($result, $expected); + + $column = array('name' => 'name', 'type' => 'string', 'null' => true, 'default' => null, 'length' => '255'); + $result = $this->db->buildColumn($column); + $expected = '[name] varchar(255) NULL'; + $this->assertEqual($result, $expected); + + $column = array('name' => 'name', 'type' => 'string', 'null' => true, 'default' => '', 'length' => '255'); + $result = $this->db->buildColumn($column); + $expected = '[name] varchar(255) DEFAULT \'\''; + $this->assertEqual($result, $expected); + } +/** + * testBuildIndex method + * + * @return void + * @access public + */ + function testBuildIndex() { + $indexes = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'client_id' => array('column' => 'client_id', 'unique' => 1) + ); + $result = $this->db->buildIndex($indexes, 'items'); + $expected = array( + 'PRIMARY KEY ([id])', + 'ALTER TABLE items ADD CONSTRAINT client_id UNIQUE([client_id]);' + ); + $this->assertEqual($result, $expected); + + $indexes = array('client_id' => array('column' => 'client_id')); + $result = $this->db->buildIndex($indexes, 'items'); + $this->assertEqual($result, array()); + + $indexes = array('client_id' => array('column' => array('client_id', 'period_id'), 'unique' => 1)); + $result = $this->db->buildIndex($indexes, 'items'); + $expected = array('ALTER TABLE items ADD CONSTRAINT client_id UNIQUE([client_id], [period_id]);'); + $this->assertEqual($result, $expected); + } +/** + * testUpdateAllSyntax method + * + * @return void + * @access public + */ + function testUpdateAllSyntax() { + $fields = array('MssqlTestModel.client_id' => '[MssqlTestModel].[client_id] + 1'); + $conditions = array('MssqlTestModel.updated <' => date('2009-01-01 00:00:00')); + $this->db->update($this->model, $fields, null, $conditions); + + $result = $this->db->getLastQuery(); + $this->assertNoPattern('/MssqlTestModel/', $result); + $this->assertPattern('/^UPDATE \[mssql_test_models\]/', $result); + $this->assertPattern('/SET \[client_id\] = \[client_id\] \+ 1/', $result); + } + +/** + * testGetPrimaryKey method + * + * @return void + * @access public + */ + function testGetPrimaryKey() { + // When param is a model + $result = $this->db->getPrimaryKey($this->model); + $this->assertEqual($result, 'id'); + + $schema = $this->model->schema(); + unset($schema['id']['key']); + $this->model->setSchema($schema); + $result = $this->db->getPrimaryKey($this->model); + $this->assertNull($result); + + // When param is a table name + $this->db->simulate = false; + $this->loadFixtures('Category'); + $result = $this->db->getPrimaryKey('categories'); + $this->assertEqual($result, 'id'); + } + +/** + * testInsertMulti + * + * @return void + * @access public + */ + function testInsertMulti() { + $fields = array('id', 'name', 'login'); + $values = array('(1, \'Larry\', \'PhpNut\')', '(2, \'Renan\', \'renan.saddam\')'); + $this->db->simulated = array(); + $this->db->insertMulti($this->model, $fields, $values); + $result = $this->db->simulated; + $expected = array( + 'SET IDENTITY_INSERT [mssql_test_models] ON', + 'INSERT INTO [mssql_test_models] ([id], [name], [login]) VALUES (1, \'Larry\', \'PhpNut\')', + 'INSERT INTO [mssql_test_models] ([id], [name], [login]) VALUES (2, \'Renan\', \'renan.saddam\')', + 'SET IDENTITY_INSERT [mssql_test_models] OFF' + ); + $this->assertEqual($result, $expected); + + $fields = array('name', 'login'); + $values = array('(\'Larry\', \'PhpNut\')', '(\'Renan\', \'renan.saddam\')'); + $this->db->simulated = array(); + $this->db->insertMulti($this->model, $fields, $values); + $result = $this->db->simulated; + $expected = array( + 'INSERT INTO [mssql_test_models] ([name], [login]) VALUES (\'Larry\', \'PhpNut\')', + 'INSERT INTO [mssql_test_models] ([name], [login]) VALUES (\'Renan\', \'renan.saddam\')' + ); + $this->assertEqual($result, $expected); + } +/** + * testLastError + * + * @return void + * @access public + */ + function testLastError() { + $debug = Configure::read('debug'); + Configure::write('debug', 0); + + $this->db->simulate = false; + $query = 'SELECT [name] FROM [categories]'; + $this->assertTrue($this->db->execute($query) !== false); + $this->assertNull($this->db->lastError()); + + $query = 'SELECT [inexistent_field] FROM [categories]'; + $this->assertFalse($this->db->execute($query)); + $this->assertNotNull($this->db->lastError()); + + $query = 'SELECT [name] FROM [categories]'; + $this->assertTrue($this->db->execute($query) !== false); + $this->assertNull($this->db->lastError()); + + Configure::write('debug', $debug); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php new file mode 100644 index 000000000..c90d585ca --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mysql.test.php @@ -0,0 +1,861 @@ + array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'client_id' => array('type' => 'integer', 'null' => '', 'default' => '0', 'length' => '11'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'login' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'passwd' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_1' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_2' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '25'), + 'zip_code' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'city' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'country' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'phone' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'fax' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'url' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'email' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'comments' => array('type' => 'text', 'null' => '1', 'default' => '', 'length' => ''), + 'last_login'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } +} + +/** + * DboMysqlTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources.dbo + */ +class DboMysqlTest extends CakeTestCase { + var $fixtures = array('core.binary_test', 'core.post', 'core.author'); +/** + * The Dbo instance to be tested + * + * @var DboSource + * @access public + */ + var $Db = null; + +/** + * Skip if cannot connect to mysql + * + * @access public + */ + function skip() { + $this->_initDb(); + $this->skipUnless($this->db->config['driver'] == 'mysql', '%s MySQL connection not available'); + } + +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function startTest() { + $db = ConnectionManager::getDataSource('test_suite'); + $this->model = new MysqlTestModel(); + } + +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function tearDown() { + unset($this->model); + ClassRegistry::flush(); + } + +/** + * startCase + * + * @return void + */ + function startCase() { + $this->_debug = Configure::read('debug'); + Configure::write('debug', 1); + } + +/** + * endCase + * + * @return void + */ + function endCase() { + Configure::write('debug', $this->_debug); + } + +/** + * Test Dbo value method + * + * @access public + */ + function testQuoting() { + $result = $this->db->fields($this->model); + $expected = array( + '`MysqlTestModel`.`id`', + '`MysqlTestModel`.`client_id`', + '`MysqlTestModel`.`name`', + '`MysqlTestModel`.`login`', + '`MysqlTestModel`.`passwd`', + '`MysqlTestModel`.`addr_1`', + '`MysqlTestModel`.`addr_2`', + '`MysqlTestModel`.`zip_code`', + '`MysqlTestModel`.`city`', + '`MysqlTestModel`.`country`', + '`MysqlTestModel`.`phone`', + '`MysqlTestModel`.`fax`', + '`MysqlTestModel`.`url`', + '`MysqlTestModel`.`email`', + '`MysqlTestModel`.`comments`', + '`MysqlTestModel`.`last_login`', + '`MysqlTestModel`.`created`', + '`MysqlTestModel`.`updated`' + ); + $this->assertEqual($result, $expected); + + $expected = 1.2; + $result = $this->db->value(1.2, 'float'); + $this->assertEqual($expected, $result); + + $expected = "'1,2'"; + $result = $this->db->value('1,2', 'float'); + $this->assertEqual($expected, $result); + + $expected = "'4713e29446'"; + $result = $this->db->value('4713e29446'); + + $this->assertEqual($expected, $result); + + $expected = 'NULL'; + $result = $this->db->value('', 'integer'); + $this->assertEqual($expected, $result); + + $expected = 'NULL'; + $result = $this->db->value('', 'boolean'); + $this->assertEqual($expected, $result); + + $expected = 10010001; + $result = $this->db->value(10010001); + $this->assertEqual($expected, $result); + + $expected = "'00010010001'"; + $result = $this->db->value('00010010001'); + $this->assertEqual($expected, $result); + } + +/** + * test that localized floats don't cause trouble. + * + * @return void + */ + function testLocalizedFloats() { + $restore = setlocale(LC_ALL, null); + setlocale(LC_ALL, 'de_DE'); + + $result = $this->db->value(3.141593, 'float'); + $this->assertEqual((string)$result, '3.141593'); + + $result = $this->db->value(3.141593); + $this->assertEqual((string)$result, '3.141593'); + + setlocale(LC_ALL, $restore); + } + +/** + * testTinyintCasting method + * + * @access public + * @return void + */ + function testTinyintCasting() { + $this->db->cacheSources = false; + $this->db->query('CREATE TABLE ' . $this->db->fullTableName('tinyint') . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id));'); + + $this->model = new CakeTestModel(array( + 'name' => 'Tinyint', 'table' => 'tinyint', 'ds' => 'test_suite' + )); + + $result = $this->model->schema(); + $this->assertEqual($result['bool']['type'], 'boolean'); + $this->assertEqual($result['small_int']['type'], 'integer'); + + $this->assertTrue($this->model->save(array('bool' => 5, 'small_int' => 5))); + $result = $this->model->find('first'); + $this->assertIdentical($result['Tinyint']['bool'], '1'); + $this->assertIdentical($result['Tinyint']['small_int'], '5'); + $this->model->deleteAll(true); + + $this->assertTrue($this->model->save(array('bool' => 0, 'small_int' => 100))); + $result = $this->model->find('first'); + $this->assertIdentical($result['Tinyint']['bool'], '0'); + $this->assertIdentical($result['Tinyint']['small_int'], '100'); + $this->model->deleteAll(true); + + $this->assertTrue($this->model->save(array('bool' => true, 'small_int' => 0))); + $result = $this->model->find('first'); + $this->assertIdentical($result['Tinyint']['bool'], '1'); + $this->assertIdentical($result['Tinyint']['small_int'], '0'); + $this->model->deleteAll(true); + + $this->db->query('DROP TABLE ' . $this->db->fullTableName('tinyint')); + } + +/** + * testIndexDetection method + * + * @return void + * @access public + */ + function testIndexDetection() { + $this->db->cacheSources = false; + + $name = $this->db->fullTableName('simple'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id));'); + $expected = array('PRIMARY' => array('column' => 'id', 'unique' => 1)); + $result = $this->db->index('simple', false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $name = $this->db->fullTableName('with_a_key'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id), KEY `pointless_bool` ( `bool` ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + ); + $result = $this->db->index('with_a_key', false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $name = $this->db->fullTableName('with_two_keys'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id), KEY `pointless_bool` ( `bool` ), KEY `pointless_small_int` ( `small_int` ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'pointless_small_int' => array('column' => 'small_int', 'unique' => 0), + ); + $result = $this->db->index('with_two_keys', false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $name = $this->db->fullTableName('with_compound_keys'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id), KEY `pointless_bool` ( `bool` ), KEY `pointless_small_int` ( `small_int` ), KEY `one_way` ( `bool`, `small_int` ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'pointless_small_int' => array('column' => 'small_int', 'unique' => 0), + 'one_way' => array('column' => array('bool', 'small_int'), 'unique' => 0), + ); + $result = $this->db->index('with_compound_keys', false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $name = $this->db->fullTableName('with_multiple_compound_keys'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id), KEY `pointless_bool` ( `bool` ), KEY `pointless_small_int` ( `small_int` ), KEY `one_way` ( `bool`, `small_int` ), KEY `other_way` ( `small_int`, `bool` ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'pointless_small_int' => array('column' => 'small_int', 'unique' => 0), + 'one_way' => array('column' => array('bool', 'small_int'), 'unique' => 0), + 'other_way' => array('column' => array('small_int', 'bool'), 'unique' => 0), + ); + $result = $this->db->index('with_multiple_compound_keys', false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + } + +/** + * testBuildColumn method + * + * @access public + * @return void + */ + function testBuildColumn() { + $restore = $this->db->columns; + $this->db->columns = array('varchar(255)' => 1); + $data = array( + 'name' => 'testName', + 'type' => 'varchar(255)', + 'default', + 'null' => true, + 'key', + 'comment' => 'test' + ); + $result = $this->db->buildColumn($data); + $expected = '`testName` DEFAULT NULL COMMENT \'test\''; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'testName', + 'type' => 'varchar(255)', + 'default', + 'null' => true, + 'key', + 'charset' => 'utf8', + 'collate' => 'utf8_unicode_ci' + ); + $result = $this->db->buildColumn($data); + $expected = '`testName` CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL'; + $this->assertEqual($result, $expected); + $this->db->columns = $restore; + } + +/** + * MySQL 4.x returns index data in a different format, + * Using a mock ensure that MySQL 4.x output is properly parsed. + * + * @return void + */ + function testIndexOnMySQL4Output() { + $name = $this->db->fullTableName('simple'); + + $mockDbo =& new QueryMockDboMysql($this); + $columnData = array( + array('0' => array( + 'Table' => 'with_compound_keys', + 'Non_unique' => '0', + 'Key_name' => 'PRIMARY', + 'Seq_in_index' => '1', + 'Column_name' => 'id', + 'Collation' => 'A', + 'Cardinality' => '0', + 'Sub_part' => NULL, + 'Packed' => NULL, + 'Null' => '', + 'Index_type' => 'BTREE', + 'Comment' => '' + )), + array('0' => array( + 'Table' => 'with_compound_keys', + 'Non_unique' => '1', + 'Key_name' => 'pointless_bool', + 'Seq_in_index' => '1', + 'Column_name' => 'bool', + 'Collation' => 'A', + 'Cardinality' => NULL, + 'Sub_part' => NULL, + 'Packed' => NULL, + 'Null' => 'YES', + 'Index_type' => 'BTREE', + 'Comment' => '' + )), + array('0' => array( + 'Table' => 'with_compound_keys', + 'Non_unique' => '1', + 'Key_name' => 'pointless_small_int', + 'Seq_in_index' => '1', + 'Column_name' => 'small_int', + 'Collation' => 'A', + 'Cardinality' => NULL, + 'Sub_part' => NULL, + 'Packed' => NULL, + 'Null' => 'YES', + 'Index_type' => 'BTREE', + 'Comment' => '' + )), + array('0' => array( + 'Table' => 'with_compound_keys', + 'Non_unique' => '1', + 'Key_name' => 'one_way', + 'Seq_in_index' => '1', + 'Column_name' => 'bool', + 'Collation' => 'A', + 'Cardinality' => NULL, + 'Sub_part' => NULL, + 'Packed' => NULL, + 'Null' => 'YES', + 'Index_type' => 'BTREE', + 'Comment' => '' + )), + array('0' => array( + 'Table' => 'with_compound_keys', + 'Non_unique' => '1', + 'Key_name' => 'one_way', + 'Seq_in_index' => '2', + 'Column_name' => 'small_int', + 'Collation' => 'A', + 'Cardinality' => NULL, + 'Sub_part' => NULL, + 'Packed' => NULL, + 'Null' => 'YES', + 'Index_type' => 'BTREE', + 'Comment' => '' + )) + ); + $mockDbo->setReturnValue('query', $columnData, array('SHOW INDEX FROM ' . $name)); + + $result = $mockDbo->index($name, false); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'pointless_small_int' => array('column' => 'small_int', 'unique' => 0), + 'one_way' => array('column' => array('bool', 'small_int'), 'unique' => 0), + ); + $this->assertEqual($result, $expected); + } + +/** + * testColumn method + * + * @return void + * @access public + */ + function testColumn() { + $result = $this->db->column('varchar(50)'); + $expected = 'string'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('text'); + $expected = 'text'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('int(11)'); + $expected = 'integer'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('int(11) unsigned'); + $expected = 'integer'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('tinyint(1)'); + $expected = 'boolean'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('boolean'); + $expected = 'boolean'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('float'); + $expected = 'float'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('float unsigned'); + $expected = 'float'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('double unsigned'); + $expected = 'float'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('decimal(14,7) unsigned'); + $expected = 'float'; + $this->assertEqual($result, $expected); + } + +/** + * testAlterSchemaIndexes method + * + * @access public + * @return void + */ + function testAlterSchemaIndexes() { + App::import('Model', 'CakeSchema'); + $this->db->cacheSources = false; + + $schema1 =& new CakeSchema(array( + 'name' => 'AlterTest1', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + 'group1' => array('type' => 'integer', 'null' => true), + 'group2' => array('type' => 'integer', 'null' => true) + ))); + $this->db->query($this->db->createSchema($schema1)); + + $schema2 =& new CakeSchema(array( + 'name' => 'AlterTest2', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + 'group1' => array('type' => 'integer', 'null' => true), + 'group2' => array('type' => 'integer', 'null' => true), + 'indexes' => array( + 'name_idx' => array('column' => 'name', 'unique' => 0), + 'group_idx' => array('column' => 'group1', 'unique' => 0), + 'compound_idx' => array('column' => array('group1', 'group2'), 'unique' => 0), + 'PRIMARY' => array('column' => 'id', 'unique' => 1)) + ))); + $this->db->query($this->db->alterSchema($schema2->compare($schema1))); + + $indexes = $this->db->index('altertest'); + $this->assertEqual($schema2->tables['altertest']['indexes'], $indexes); + + // Change three indexes, delete one and add another one + $schema3 =& new CakeSchema(array( + 'name' => 'AlterTest3', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + 'group1' => array('type' => 'integer', 'null' => true), + 'group2' => array('type' => 'integer', 'null' => true), + 'indexes' => array( + 'name_idx' => array('column' => 'name', 'unique' => 1), + 'group_idx' => array('column' => 'group2', 'unique' => 0), + 'compound_idx' => array('column' => array('group2', 'group1'), 'unique' => 0), + 'id_name_idx' => array('column' => array('id', 'name'), 'unique' => 0)) + ))); + + $this->db->query($this->db->alterSchema($schema3->compare($schema2))); + + $indexes = $this->db->index('altertest'); + $this->assertEqual($schema3->tables['altertest']['indexes'], $indexes); + + // Compare us to ourself. + $this->assertEqual($schema3->compare($schema3), array()); + + // Drop the indexes + $this->db->query($this->db->alterSchema($schema1->compare($schema3))); + + $indexes = $this->db->index('altertest'); + $this->assertEqual(array(), $indexes); + + $this->db->query($this->db->dropSchema($schema1)); + } +/** + * test saving and retrieval of blobs + * + * @return void + */ + function testBlobSaving() { + $this->db->cacheSources = false; + $data = "GIF87ab + Ã’4A¿¿¿ˇˇˇ,b + ¢îè©ÀÌ#Â¥â„ã≥ï¬:¯Ü‚Héá¶jV∂ÓúÎL≥çÀóËıÎ…>ï≈ vFE%ÒâLFI<†µw˱≈£7˘ç^H“≤« >Éâ*∑ÇnÖA•Ù|flêèj£:=ÿ6óUàµ5'∂®àA¬ñ∆ˆGE(gt’≈àÚyÃó«7 ‚VìöÇ√˙Ç™ + kâ€:;kÀAõ{*¡€Î˚˚[;;"; + + $model =& new AppModel(array('name' => 'BinaryTest', 'ds' => 'test_suite')); + $model->save(compact('data')); + + $result = $model->find('first'); + $this->assertEqual($result['BinaryTest']['data'], $data); + } + +/** + * test altering the table settings with schema. + * + * @return void + */ + function testAlteringTableParameters() { + App::import('Model', 'CakeSchema'); + $this->db->cacheSources = false; + + $schema1 =& new CakeSchema(array( + 'name' => 'AlterTest1', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + 'tableParameters' => array( + 'charset' => 'latin1', + 'collate' => 'latin1_general_ci', + 'engine' => 'MyISAM' + ) + ) + )); + $this->db->query($this->db->createSchema($schema1)); + $schema2 =& new CakeSchema(array( + 'name' => 'AlterTest1', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + 'tableParameters' => array( + 'charset' => 'utf8', + 'collate' => 'utf8_general_ci', + 'engine' => 'InnoDB' + ) + ) + )); + $result = $this->db->alterSchema($schema2->compare($schema1)); + $this->assertPattern('/DEFAULT CHARSET=utf8/', $result); + $this->assertPattern('/ENGINE=InnoDB/', $result); + $this->assertPattern('/COLLATE=utf8_general_ci/', $result); + + $this->db->query($result); + $result = $this->db->listDetailedSources('altertest'); + $this->assertEqual($result['Collation'], 'utf8_general_ci'); + $this->assertEqual($result['Engine'], 'InnoDB'); + $this->assertEqual($result['charset'], 'utf8'); + + $this->db->query($this->db->dropSchema($schema1)); + } + +/** + * test alterSchema on two tables. + * + * @return void + */ + function testAlteringTwoTables() { + $schema1 =& new CakeSchema(array( + 'name' => 'AlterTest1', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + ), + 'other_table' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + ) + )); + $schema2 =& new CakeSchema(array( + 'name' => 'AlterTest1', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'field_two' => array('type' => 'string', 'null' => false, 'length' => 50), + ), + 'other_table' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'field_two' => array('type' => 'string', 'null' => false, 'length' => 50), + ) + )); + $result = $this->db->alterSchema($schema2->compare($schema1)); + $this->assertEqual(2, substr_count($result, 'field_two'), 'Too many fields'); + } + +/** + * testReadTableParameters method + * + * @access public + * @return void + */ + function testReadTableParameters() { + $this->db->cacheSources = false; + $this->db->query('CREATE TABLE ' . $this->db->fullTableName('tinyint') . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'); + $result = $this->db->readTableParameters('tinyint'); + $expected = array( + 'charset' => 'utf8', + 'collate' => 'utf8_unicode_ci', + 'engine' => 'InnoDB'); + $this->assertEqual($result, $expected); + + $this->db->query('DROP TABLE ' . $this->db->fullTableName('tinyint')); + $this->db->query('CREATE TABLE ' . $this->db->fullTableName('tinyint') . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id)) ENGINE=MyISAM DEFAULT CHARSET=cp1250 COLLATE=cp1250_general_ci;'); + $result = $this->db->readTableParameters('tinyint'); + $expected = array( + 'charset' => 'cp1250', + 'collate' => 'cp1250_general_ci', + 'engine' => 'MyISAM'); + $this->assertEqual($result, $expected); + $this->db->query('DROP TABLE ' . $this->db->fullTableName('tinyint')); + } + +/** + * testBuildTableParameters method + * + * @access public + * @return void + */ + function testBuildTableParameters() { + $this->db->cacheSources = false; + $data = array( + 'charset' => 'utf8', + 'collate' => 'utf8_unicode_ci', + 'engine' => 'InnoDB'); + $result = $this->db->buildTableParameters($data); + $expected = array( + 'DEFAULT CHARSET=utf8', + 'COLLATE=utf8_unicode_ci', + 'ENGINE=InnoDB'); + $this->assertEqual($result, $expected); + } + +/** + * testBuildTableParameters method + * + * @access public + * @return void + */ + function testGetCharsetName() { + $this->db->cacheSources = false; + $result = $this->db->getCharsetName('utf8_unicode_ci'); + $this->assertEqual($result, 'utf8'); + $result = $this->db->getCharsetName('cp1250_general_ci'); + $this->assertEqual($result, 'cp1250'); + } + +/** + * test that changing the virtualFieldSeparator allows for __ fields. + * + * @return void + */ + function testVirtualFieldSeparators() { + $model =& new CakeTestModel(array('table' => 'binary_tests', 'ds' => 'test_suite', 'name' => 'BinaryTest')); + $model->virtualFields = array( + 'other__field' => 'SUM(id)' + ); + + $this->db->virtualFieldSeparator = '_$_'; + $result = $this->db->fields($model, null, array('data', 'other__field')); + $expected = array('`BinaryTest`.`data`', '(SUM(id)) AS `BinaryTest_$_other__field`'); + $this->assertEqual($result, $expected); + } + +/** + * test that a describe() gets additional fieldParameters + * + * @return void + */ + function testDescribeGettingFieldParameters() { + $schema =& new CakeSchema(array( + 'connection' => 'test_suite', + 'testdescribes' => array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'stringy' => array( + 'type' => 'string', + 'null' => true, + 'charset' => 'cp1250', + 'collate' => 'cp1250_general_ci', + ), + 'other_col' => array( + 'type' => 'string', + 'null' => false, + 'charset' => 'latin1', + 'comment' => 'Test Comment' + ) + ) + )); + $this->db->execute($this->db->createSchema($schema)); + + $model =& new CakeTestModel(array('table' => 'testdescribes', 'name' => 'Testdescribes')); + $result = $this->db->describe($model); + $this->assertEqual($result['stringy']['collate'], 'cp1250_general_ci'); + $this->assertEqual($result['stringy']['charset'], 'cp1250'); + $this->assertEqual($result['other_col']['comment'], 'Test Comment'); + + $this->db->execute($this->db->dropSchema($schema)); + } + +/** + * test that simple delete conditions don't create joins using a mock. + * + * @return void + */ + function testSimpleDeleteConditionsNoJoins() { + $model =& new Post(); + $mockDbo =& new QueryMockDboMysql($this); + $mockDbo->expectAt(0, 'execute', array(new PatternExpectation('/AS\s+`Post`\s+WHERE\s+`Post/'))); + $mockDbo->setReturnValue('execute', true); + + $mockDbo->delete($model, array('Post.id' => 1)); + } + +/** + * test deleting with joins, a MySQL specific feature. + * + * @return void + */ + function testDeleteWithJoins() { + $model =& new Post(); + $mockDbo =& new QueryMockDboMysql($this); + $mockDbo->expectAt(0, 'execute', array(new PatternExpectation('/LEFT JOIN `authors`/'))); + $mockDbo->setReturnValue('execute', true); + + $mockDbo->delete($model, array('Author.id' => 1)); + } + +/** + * test joins on delete with multiple conditions. + * + * @return void + */ + function testDeleteWithJoinsAndMultipleConditions() { + $model =& new Post(); + $mockDbo =& new QueryMockDboMysql($this); + $mockDbo->expectAt(0, 'execute', array(new PatternExpectation('/LEFT JOIN `authors`/'))); + $mockDbo->expectAt(1, 'execute', array(new PatternExpectation('/LEFT JOIN `authors`/'))); + $mockDbo->setReturnValue('execute', true); + + $mockDbo->delete($model, array('Author.id' => 1, 'Post.id' => 2)); + $mockDbo->delete($model, array('Post.id' => 2, 'Author.id' => 1)); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mysqli.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mysqli.test.php new file mode 100644 index 000000000..e3a94512b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_mysqli.test.php @@ -0,0 +1,339 @@ +testing) { + $this->simulated[] = $sql; + return null; + } + return parent::_execute($sql); + } + +/** + * getLastQuery method + * + * @access public + * @return void + */ + function getLastQuery() { + return $this->simulated[count($this->simulated) - 1]; + } +} + +/** + * MysqliTestModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class MysqliTestModel extends Model { + +/** + * name property + * + * @var string 'MysqlTestModel' + * @access public + */ + var $name = 'MysqliTestModel'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + return array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'client_id' => array('type' => 'integer', 'null' => '', 'default' => '0', 'length' => '11'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'login' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'passwd' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_1' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_2' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '25'), + 'zip_code' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'city' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'country' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'phone' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'fax' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'url' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'email' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'comments' => array('type' => 'text', 'null' => '1', 'default' => '', 'length' => ''), + 'last_login'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } +} + +/** + * DboMysqliTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources.dbo + */ +class DboMysqliTest extends CakeTestCase { + var $fixtures = array('core.datatype'); +/** + * The Dbo instance to be tested + * + * @var DboSource + * @access public + */ + var $Db = null; + +/** + * Skip if cannot connect to mysqli + * + * @access public + */ + function skip() { + $this->_initDb(); + $this->skipUnless($this->db->config['driver'] == 'mysqli', '%s MySQLi connection not available'); + } + +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function setUp() { + $this->model = new MysqliTestModel(); + } + +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function tearDown() { + unset($this->model); + ClassRegistry::flush(); + } + +/** + * testIndexDetection method + * + * @return void + * @access public + */ + function testIndexDetection() { + $this->db->cacheSources = false; + + $name = $this->db->fullTableName('simple'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id));'); + $expected = array('PRIMARY' => array('column' => 'id', 'unique' => 1)); + $result = $this->db->index($name, false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $name = $this->db->fullTableName('with_a_key'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id), KEY `pointless_bool` ( `bool` ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + ); + $result = $this->db->index($name, false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $name = $this->db->fullTableName('with_two_keys'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id), KEY `pointless_bool` ( `bool` ), KEY `pointless_small_int` ( `small_int` ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'pointless_small_int' => array('column' => 'small_int', 'unique' => 0), + ); + $result = $this->db->index($name, false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $name = $this->db->fullTableName('with_compound_keys'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id), KEY `pointless_bool` ( `bool` ), KEY `pointless_small_int` ( `small_int` ), KEY `one_way` ( `bool`, `small_int` ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'pointless_small_int' => array('column' => 'small_int', 'unique' => 0), + 'one_way' => array('column' => array('bool', 'small_int'), 'unique' => 0), + ); + $result = $this->db->index($name, false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $name = $this->db->fullTableName('with_multiple_compound_keys'); + $this->db->query('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id), KEY `pointless_bool` ( `bool` ), KEY `pointless_small_int` ( `small_int` ), KEY `one_way` ( `bool`, `small_int` ), KEY `other_way` ( `small_int`, `bool` ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'pointless_small_int' => array('column' => 'small_int', 'unique' => 0), + 'one_way' => array('column' => array('bool', 'small_int'), 'unique' => 0), + 'other_way' => array('column' => array('small_int', 'bool'), 'unique' => 0), + ); + $result = $this->db->index($name, false); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + } + +/** + * testColumn method + * + * @return void + * @access public + */ + function testColumn() { + $result = $this->db->column('varchar(50)'); + $expected = 'string'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('text'); + $expected = 'text'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('int(11)'); + $expected = 'integer'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('int(11) unsigned'); + $expected = 'integer'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('tinyint(1)'); + $expected = 'boolean'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('boolean'); + $expected = 'boolean'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('float'); + $expected = 'float'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('float unsigned'); + $expected = 'float'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('double unsigned'); + $expected = 'float'; + $this->assertEqual($result, $expected); + + $result = $this->db->column('decimal(14,7) unsigned'); + $expected = 'float'; + $this->assertEqual($result, $expected); + } + +/** + * test transaction commands. + * + * @return void + * @access public + */ + function testTransactions() { + $this->db->testing = false; + $result = $this->db->begin($this->model); + $this->assertTrue($result); + + $beginSqlCalls = Set::extract('/.[query=START TRANSACTION]', $this->db->_queriesLog); + $this->assertEqual(1, count($beginSqlCalls)); + + $result = $this->db->commit($this->model); + $this->assertTrue($result); + } +/** + * test that float values are correctly identified + * + * @return void + */ + function testFloatParsing() { + $model =& new Model(array('ds' => 'test_suite', 'table' => 'datatypes', 'name' => 'Datatype')); + $result = $this->db->describe($model); + $this->assertEqual((string)$result['float_field']['length'], '5,2'); + } + +/** + * test that tableParameters like collation, charset and engine are functioning. + * + * @access public + * @return void + */ + function testReadTableParameters() { + $this->db->cacheSources = $this->db->testing = false; + $this->db->query('CREATE TABLE ' . $this->db->fullTableName('tinyint') . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;'); + $result = $this->db->readTableParameters('tinyint'); + $expected = array( + 'charset' => 'utf8', + 'collate' => 'utf8_unicode_ci', + 'engine' => 'InnoDB'); + $this->assertEqual($result, $expected); + + $this->db->query('DROP TABLE ' . $this->db->fullTableName('tinyint')); + $this->db->query('CREATE TABLE ' . $this->db->fullTableName('tinyint') . ' (id int(11) AUTO_INCREMENT, bool tinyint(1), small_int tinyint(2), primary key(id)) ENGINE=MyISAM DEFAULT CHARSET=cp1250 COLLATE=cp1250_general_ci;'); + $result = $this->db->readTableParameters('tinyint'); + $expected = array( + 'charset' => 'cp1250', + 'collate' => 'cp1250_general_ci', + 'engine' => 'MyISAM'); + $this->assertEqual($result, $expected); + $this->db->query('DROP TABLE ' . $this->db->fullTableName('tinyint')); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_oracle.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_oracle.test.php new file mode 100644 index 000000000..00f014451 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_oracle.test.php @@ -0,0 +1,131 @@ +_initDb(); + } + +/** + * skip method + * + * @access public + * @return void + */ + function skip() { + $this->_initDb(); + $this->skipUnless($this->db->config['driver'] == 'oracle', '%s Oracle connection not available'); + } + +/** + * testLastErrorStatement method + * + * @access public + * @return void + */ + function testLastErrorStatement() { + if ($this->skip('testLastErrorStatement')) { + return; + } + + $this->expectError(); + $this->db->execute("SELECT ' FROM dual"); + $e = $this->db->lastError(); + $r = 'ORA-01756: quoted string not properly terminated'; + $this->assertEqual($e, $r); + } + +/** + * testLastErrorConnect method + * + * @access public + * @return void + */ + function testLastErrorConnect() { + if ($this->skip('testLastErrorConnect')) { + return; + } + + $config = $this->db->config; + $old_pw = $this->db->config['password']; + $this->db->config['password'] = 'keepmeout'; + $this->db->connect(); + $e = $this->db->lastError(); + $r = 'ORA-01017: invalid username/password; logon denied'; + $this->assertEqual($e, $r); + $this->db->config['password'] = $old_pw; + $this->db->connect(); + } + +/** + * testName method + * + * @access public + * @return void + */ + function testName() { + $Db = $this->db; + #$Db =& new DboOracle($config = null, $autoConnect = false); + + $r = $Db->name($Db->name($Db->name('foo.last_update_date'))); + $e = 'foo.last_update_date'; + $this->assertEqual($e, $r); + + $r = $Db->name($Db->name($Db->name('foo._update'))); + $e = 'foo."_update"'; + $this->assertEqual($e, $r); + + $r = $Db->name($Db->name($Db->name('foo.last_update_date'))); + $e = 'foo.last_update_date'; + $this->assertEqual($e, $r); + + $r = $Db->name($Db->name($Db->name('last_update_date'))); + $e = 'last_update_date'; + $this->assertEqual($e, $r); + + $r = $Db->name($Db->name($Db->name('_update'))); + $e = '"_update"'; + $this->assertEqual($e, $r); + + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_postgres.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_postgres.test.php new file mode 100644 index 000000000..d9c6d023c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_postgres.test.php @@ -0,0 +1,883 @@ +simulated[] = $sql; + return null; + } + +/** + * getLastQuery method + * + * @access public + * @return void + */ + function getLastQuery() { + return $this->simulated[count($this->simulated) - 1]; + } +} + +/** + * PostgresTestModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class PostgresTestModel extends Model { + +/** + * name property + * + * @var string 'PostgresTestModel' + * @access public + */ + var $name = 'PostgresTestModel'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'PostgresClientTestModel' => array( + 'foreignKey' => 'client_id' + ) + ); + +/** + * find method + * + * @param mixed $conditions + * @param mixed $fields + * @param mixed $order + * @param mixed $recursive + * @access public + * @return void + */ + function find($conditions = null, $fields = null, $order = null, $recursive = null) { + return $conditions; + } + +/** + * findAll method + * + * @param mixed $conditions + * @param mixed $fields + * @param mixed $order + * @param mixed $recursive + * @access public + * @return void + */ + function findAll($conditions = null, $fields = null, $order = null, $recursive = null) { + return $conditions; + } + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + return array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'client_id' => array('type' => 'integer', 'null' => '', 'default' => '0', 'length' => '11'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'login' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'passwd' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_1' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_2' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '25'), + 'zip_code' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'city' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'country' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'phone' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'fax' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'url' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'email' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'comments' => array('type' => 'text', 'null' => '1', 'default' => '', 'length' => ''), + 'last_login'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } +} + +/** + * PostgresClientTestModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class PostgresClientTestModel extends Model { + +/** + * name property + * + * @var string 'PostgresClientTestModel' + * @access public + */ + var $name = 'PostgresClientTestModel'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + return array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8', 'key' => 'primary'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'email' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'created' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } +} + +/** + * DboPostgresTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources.dbo + */ +class DboPostgresTest extends CakeTestCase { + +/** + * Do not automatically load fixtures for each test, they will be loaded manually + * using CakeTestCase::loadFixtures + * + * @var boolean + * @access public + */ + var $autoFixtures = false; + +/** + * Fixtures + * + * @var object + * @access public + */ + var $fixtures = array('core.user', 'core.binary_test', 'core.comment', 'core.article', + 'core.tag', 'core.articles_tag', 'core.attachment', 'core.person', 'core.post', 'core.author', + 'core.datatype', + ); +/** + * Actual DB connection used in testing + * + * @var DboSource + * @access public + */ + var $db = null; + +/** + * Simulated DB connection used in testing + * + * @var DboSource + * @access public + */ + var $db2 = null; + +/** + * Skip if cannot connect to postgres + * + * @access public + */ + function skip() { + $this->_initDb(); + $this->skipUnless($this->db->config['driver'] == 'postgres', '%s PostgreSQL connection not available'); + } + +/** + * Set up test suite database connection + * + * @access public + */ + function startTest() { + $this->_initDb(); + } + +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function setUp() { + Configure::write('Cache.disable', true); + $this->startTest(); + $this->db =& ConnectionManager::getDataSource('test_suite'); + $this->db2 = new DboPostgresTestDb($this->db->config, false); + $this->model = new PostgresTestModel(); + } + +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function tearDown() { + Configure::write('Cache.disable', false); + unset($this->db2); + } + +/** + * Test field quoting method + * + * @access public + */ + function testFieldQuoting() { + $fields = array( + '"PostgresTestModel"."id" AS "PostgresTestModel__id"', + '"PostgresTestModel"."client_id" AS "PostgresTestModel__client_id"', + '"PostgresTestModel"."name" AS "PostgresTestModel__name"', + '"PostgresTestModel"."login" AS "PostgresTestModel__login"', + '"PostgresTestModel"."passwd" AS "PostgresTestModel__passwd"', + '"PostgresTestModel"."addr_1" AS "PostgresTestModel__addr_1"', + '"PostgresTestModel"."addr_2" AS "PostgresTestModel__addr_2"', + '"PostgresTestModel"."zip_code" AS "PostgresTestModel__zip_code"', + '"PostgresTestModel"."city" AS "PostgresTestModel__city"', + '"PostgresTestModel"."country" AS "PostgresTestModel__country"', + '"PostgresTestModel"."phone" AS "PostgresTestModel__phone"', + '"PostgresTestModel"."fax" AS "PostgresTestModel__fax"', + '"PostgresTestModel"."url" AS "PostgresTestModel__url"', + '"PostgresTestModel"."email" AS "PostgresTestModel__email"', + '"PostgresTestModel"."comments" AS "PostgresTestModel__comments"', + '"PostgresTestModel"."last_login" AS "PostgresTestModel__last_login"', + '"PostgresTestModel"."created" AS "PostgresTestModel__created"', + '"PostgresTestModel"."updated" AS "PostgresTestModel__updated"' + ); + + $result = $this->db->fields($this->model); + $expected = $fields; + $this->assertEqual($result, $expected); + + $result = $this->db->fields($this->model, null, 'PostgresTestModel.*'); + $expected = $fields; + $this->assertEqual($result, $expected); + + $result = $this->db->fields($this->model, null, array('*', 'AnotherModel.id', 'AnotherModel.name')); + $expected = array_merge($fields, array( + '"AnotherModel"."id" AS "AnotherModel__id"', + '"AnotherModel"."name" AS "AnotherModel__name"')); + $this->assertEqual($result, $expected); + + $result = $this->db->fields($this->model, null, array('*', 'PostgresClientTestModel.*')); + $expected = array_merge($fields, array( + '"PostgresClientTestModel"."id" AS "PostgresClientTestModel__id"', + '"PostgresClientTestModel"."name" AS "PostgresClientTestModel__name"', + '"PostgresClientTestModel"."email" AS "PostgresClientTestModel__email"', + '"PostgresClientTestModel"."created" AS "PostgresClientTestModel__created"', + '"PostgresClientTestModel"."updated" AS "PostgresClientTestModel__updated"')); + $this->assertEqual($result, $expected); + } + +/** + * testColumnParsing method + * + * @access public + * @return void + */ + function testColumnParsing() { + $this->assertEqual($this->db2->column('text'), 'text'); + $this->assertEqual($this->db2->column('date'), 'date'); + $this->assertEqual($this->db2->column('boolean'), 'boolean'); + $this->assertEqual($this->db2->column('character varying'), 'string'); + $this->assertEqual($this->db2->column('time without time zone'), 'time'); + $this->assertEqual($this->db2->column('timestamp without time zone'), 'datetime'); + } + +/** + * testValueQuoting method + * + * @access public + * @return void + */ + function testValueQuoting() { + $this->assertIdentical($this->db2->value(1.2, 'float'), "'1.200000'"); + $this->assertEqual($this->db2->value('1,2', 'float'), "'1,2'"); + + $this->assertEqual($this->db2->value('0', 'integer'), "'0'"); + $this->assertEqual($this->db2->value('', 'integer'), 'NULL'); + $this->assertEqual($this->db2->value('', 'float'), 'NULL'); + $this->assertEqual($this->db2->value('', 'integer', false), "DEFAULT"); + $this->assertEqual($this->db2->value('', 'float', false), "DEFAULT"); + $this->assertEqual($this->db2->value('0.0', 'float'), "'0.0'"); + + $this->assertEqual($this->db2->value('t', 'boolean'), "TRUE"); + $this->assertEqual($this->db2->value('f', 'boolean'), "FALSE"); + $this->assertEqual($this->db2->value(true), "TRUE"); + $this->assertEqual($this->db2->value(false), "FALSE"); + $this->assertEqual($this->db2->value('t'), "'t'"); + $this->assertEqual($this->db2->value('f'), "'f'"); + $this->assertEqual($this->db2->value('true', 'boolean'), 'TRUE'); + $this->assertEqual($this->db2->value('false', 'boolean'), 'FALSE'); + $this->assertEqual($this->db2->value('', 'boolean'), 'FALSE'); + $this->assertEqual($this->db2->value(0, 'boolean'), 'FALSE'); + $this->assertEqual($this->db2->value(1, 'boolean'), 'TRUE'); + $this->assertEqual($this->db2->value('1', 'boolean'), 'TRUE'); + $this->assertEqual($this->db2->value(null, 'boolean'), "NULL"); + $this->assertEqual($this->db2->value(array()), "NULL"); + } + +/** + * test that localized floats don't cause trouble. + * + * @return void + */ + function testLocalizedFloats() { + $restore = setlocale(LC_ALL, null); + setlocale(LC_ALL, 'de_DE'); + + $result = $this->db->value(3.141593, 'float'); + $this->assertEqual((string)$result, "'3.141593'"); + + $result = $this->db->value(3.14); + $this->assertEqual((string)$result, "'3.140000'"); + + setlocale(LC_ALL, $restore); + } + +/** + * test that date and time columns do not generate errors with null and nullish values. + * + * @return void + */ + function testDateAndTimeAsNull() { + $this->assertEqual($this->db2->value(null, 'date'), 'NULL'); + $this->assertEqual($this->db2->value('', 'date'), 'NULL'); + + $this->assertEqual($this->db2->value('', 'datetime'), 'NULL'); + $this->assertEqual($this->db2->value(null, 'datetime'), 'NULL'); + + $this->assertEqual($this->db2->value('', 'timestamp'), 'NULL'); + $this->assertEqual($this->db2->value(null, 'timestamp'), 'NULL'); + + $this->assertEqual($this->db2->value('', 'time'), 'NULL'); + $this->assertEqual($this->db2->value(null, 'time'), 'NULL'); + } + +/** + * Tests that different Postgres boolean 'flavors' are properly returned as native PHP booleans + * + * @access public + * @return void + */ + function testBooleanNormalization() { + $this->assertTrue($this->db2->boolean('t')); + $this->assertTrue($this->db2->boolean('true')); + $this->assertTrue($this->db2->boolean('TRUE')); + $this->assertTrue($this->db2->boolean(true)); + $this->assertTrue($this->db2->boolean(1)); + $this->assertTrue($this->db2->boolean(" ")); + + $this->assertFalse($this->db2->boolean('f')); + $this->assertFalse($this->db2->boolean('false')); + $this->assertFalse($this->db2->boolean('FALSE')); + $this->assertFalse($this->db2->boolean(false)); + $this->assertFalse($this->db2->boolean(0)); + $this->assertFalse($this->db2->boolean('')); + } + +/** + * test that default -> false in schemas works correctly. + * + * @return void + */ + function testBooleanDefaultFalseInSchema() { + $model = new Model(array('name' => 'Datatype', 'table' => 'datatypes', 'ds' => 'test_suite')); + $model->create(); + $this->assertIdentical(false, $model->data['Datatype']['bool']); + } + +/** + * testLastInsertIdMultipleInsert method + * + * @access public + * @return void + */ + function testLastInsertIdMultipleInsert() { + $db1 = ConnectionManager::getDataSource('test_suite'); + + $table = $db1->fullTableName('users', false); + $password = '5f4dcc3b5aa765d61d8327deb882cf99'; + $db1->execute( + "INSERT INTO {$table} (\"user\", password) VALUES ('mariano', '{$password}')" + ); + $this->assertEqual($db1->lastInsertId($table), 1); + + $db1->execute("INSERT INTO {$table} (\"user\", password) VALUES ('hoge', '{$password}')"); + $this->assertEqual($db1->lastInsertId($table), 2); + } + +/** + * Tests that table lists and descriptions are scoped to the proper Postgres schema + * + * @access public + * @return void + */ + function testSchemaScoping() { + $db1 =& ConnectionManager::getDataSource('test_suite'); + $db1->cacheSources = false; + $db1->reconnect(array('persistent' => false)); + $db1->query('CREATE SCHEMA _scope_test'); + + $db2 =& ConnectionManager::create( + 'test_suite_2', + array_merge($db1->config, array('driver' => 'postgres', 'schema' => '_scope_test')) + ); + $db2->cacheSources = false; + + $db2->query('DROP SCHEMA _scope_test'); + } + +/** + * Tests that column types without default lengths in $columns do not have length values + * applied when generating schemas. + * + * @access public + * @return void + */ + function testColumnUseLength() { + $result = array('name' => 'foo', 'type' => 'string', 'length' => 100, 'default' => 'FOO'); + $expected = '"foo" varchar(100) DEFAULT \'FOO\''; + $this->assertEqual($this->db->buildColumn($result), $expected); + + $result = array('name' => 'foo', 'type' => 'text', 'length' => 100, 'default' => 'FOO'); + $expected = '"foo" text DEFAULT \'FOO\''; + $this->assertEqual($this->db->buildColumn($result), $expected); + } + +/** + * Tests that binary data is escaped/unescaped properly on reads and writes + * + * @access public + * @return void + */ + function testBinaryDataIntegrity() { + $data = '%PDF-1.3 + %ƒÂÚÂÎßÛ†–ƒ∆ + 4 0 obj + << /Length 5 0 R /Filter /FlateDecode >> + stream + xµYMì€∆Ω„WÃ%)nï0¯îâ-«é]Q"Ï€Xµáÿ•Ip - P V,]Ú#c˚ˇ‰ut¥†âˆTi9 Ãœ=â€â€ºÃ˜_Ëœ4>à∑‚Épcé¢Pxæ®2q\' + 1UªbU ᡒ+ö«√[ıµâ„ão"R∑"HiGæä€(å≠≈^Ãøsm?YlƒÃõªï¬â€¹Ã¢EÚB&‚Î◊7bÃ’^¸m°÷˛?2±Øs“ï¬u#®U√ˇú÷g¥C;ä")n})JºIÔ3ËSnÑÎ¥≤ıD∆¢∂Msx1üèG˚±Œ™â„>¶ySïufؠ˸?UπÃã√6flÌÚC=øK?Ë…s + ˛§¯ˇ:-˜ò7€ÓFæ∂∑Õ˛∆“V’>ılflëÅd«ÜQdI ›ÎB%W¿ΩıÉn~h vêCS>«éË›(ØôK!€¡zB!√ + [Å“Ãœ"ûß ·iH¸[Àºæ∑¯¡L,ÀÚAlS∫ˆ=∫Œ≤cÄr&ˆÈ:√ÿ£˚È«4fl•À]vc›bÅôÿî=siXe4/¡p]ã]ôÆIϪʽflà_ƒ‚G?«7 ùÿ ı¯K4ïIpV◊÷·\'éµóªÚæ>î + ;›sú!2fl¬F•/f∑j£ + dw"IÊÜπ<ôÿˆ%IG1ytÛDflXg|Éòa§˜}C˛¿ÿe°G´Ú±jÃm~¿/∂hã<#-¥•ıùe87€tËœõ6w}´{æ + m‹ê– ∆¡ 6â„\ + rAÀBùZ3aË‚r$G·$ó0Ñ üâUY4È™¡%C∑Ÿ2rcÃCäı + =Õec=ëRËâ€eñ=ÊuNê°“√Ü ‹Ê9iÙ0Ë™AAEà ˙`∂£\'ûce•åƒX›ŸÃ´1SK{qdá"tÃ[wQ#SµBe∞∑µó…ÃŒV`B"Ñ≥„!è_ÓÆ-º*ºú¿Ë0ˆeê∂´ë+HFj…‡zvHÓN|ÔL÷ûñ3õÃœ$z%sá…pÎóV38âs Çoµ•ß3†<9B·¨û~¢3)ÂxóÿÃCÕòÆ ∫Ã=»ÿSÏ€S;∆~±êÆTEp∑óÈ÷ÀuìDHÈ $ÉõæÜjû§"≤ÃONM®RËíRr{õS âˆÃŠâ„¢op±W;ÂUÔ P∫kÔˇflTæ∑óflˆÆC©Ô[≥◊HÃ˚¨hê"ÆbF?ú%hË™ˇ4xèÕ(ó2ÙáíM])Ñd|=fë-cI0ñL¢kÖêk‰Rƒ«ıÄWñ8mO3âˆ&√æËX¯Hó—ì]yF2»–˜ádàà‡‹Çο„≥7mªHAS∑¶.;Å’x(1} _kd©.ï¬dç48M\'àáªCp^Krí<ɉXÓıïl!ÃŒ$N<ı∞B»G]…∂Ó¯>˛ÔbõÒπÀ•:ôO@È$pÖu‹Ê´-QqV ?V≥JÆÃqÛX8(lπï@zgÖ}Fe<ˇ‡Sñ“ÿ˜ê?6‡L∫Oß~µ –?ËeäÚ®YîÕ =Ãœ=¢DÃu*GvBk;)L¬N«î:flö∂≠ÇΩq„Ñm하Ë∂‚"û≥§:±≤i^ΩÑ!)Wıyŧô á„RÄ÷Òôc’≠—sâ„¢rı‚Pdêãh˘ßHVç5ï¬ï¬ÃˆF€çÌÛuçÖ/M=gëµ±ÿGû1coÔuñæ‘z®. õ∑7ÉÃÜÆ,°’H†ÃÉÌ∂7e º® íˆâ„◊øNWKâ€Ã‚Yµ‚ñé;µ¶gV-fl>µtË¥áßN2 ¯¶BaP-)eW.àôt^âˆ1›C∑Ö?L„&â€5’4jvã–ªZ ÷+4% ´0l…»ú^°´© ûiÏ€∑é®óܱÒÿ‰ïˆÌ–dˆ◊Æ19rQ=Ã|ı•rMæ¬;ò‰Y‰é9.†‹ËV«ã¯âˆ,+ë®j*¡·/'; + + $model =& new AppModel(array('name' => 'BinaryTest', 'ds' => 'test_suite')); + $model->save(compact('data')); + + $result = $model->find('first'); + $this->assertEqual($result['BinaryTest']['data'], $data); + } + +/** + * Tests the syntax of generated schema indexes + * + * @access public + * @return void + */ + function testSchemaIndexSyntax() { + $schema = new CakeSchema(); + $schema->tables = array('i18n' => array( + 'id' => array( + 'type' => 'integer', 'null' => false, 'default' => null, + 'length' => 10, 'key' => 'primary' + ), + 'locale' => array('type'=>'string', 'null' => false, 'length' => 6, 'key' => 'index'), + 'model' => array('type'=>'string', 'null' => false, 'key' => 'index'), + 'foreign_key' => array( + 'type'=>'integer', 'null' => false, 'length' => 10, 'key' => 'index' + ), + 'field' => array('type'=>'string', 'null' => false, 'key' => 'index'), + 'content' => array('type'=>'text', 'null' => true, 'default' => null), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'locale' => array('column' => 'locale', 'unique' => 0), + 'model' => array('column' => 'model', 'unique' => 0), + 'row_id' => array('column' => 'foreign_key', 'unique' => 0), + 'field' => array('column' => 'field', 'unique' => 0) + ) + )); + + $result = $this->db->createSchema($schema); + $this->assertNoPattern('/^CREATE INDEX(.+);,$/', $result); + } + +/** + * testCakeSchema method + * + * Test that schema generated postgresql queries are valid. ref #5696 + * Check that the create statement for a schema generated table is the same as the original sql + * + * @return void + * @access public + */ + function testCakeSchema() { + $db1 =& ConnectionManager::getDataSource('test_suite'); + $db1->cacheSources = false; + $db1->reconnect(array('persistent' => false)); + $db1->query('CREATE TABLE ' . $db1->fullTableName('datatype_tests') . ' ( + id serial NOT NULL, + "varchar" character varying(40) NOT NULL, + "full_length" character varying NOT NULL, + "timestamp" timestamp without time zone, + date date, + CONSTRAINT test_suite_data_types_pkey PRIMARY KEY (id) + )'); + $model = new Model(array('name' => 'DatatypeTest', 'ds' => 'test_suite')); + $schema = new CakeSchema(array('connection' => 'test_suite')); + $result = $schema->read(array( + 'connection' => 'test_suite', + )); + $schema->tables = array('datatype_tests' => $result['tables']['missing']['datatype_tests']); + $result = $db1->createSchema($schema, 'datatype_tests'); + + $this->assertNoPattern('/timestamp DEFAULT/', $result); + $this->assertPattern('/\"full_length\"\s*text\s.*,/', $result); + $this->assertPattern('/timestamp\s*,/', $result); + + $db1->query('DROP TABLE ' . $db1->fullTableName('datatype_tests')); + + $db1->query($result); + $result2 = $schema->read(array( + 'connection' => 'test_suite', + 'models' => array('DatatypeTest') + )); + $schema->tables = array('datatype_tests' => $result2['tables']['missing']['datatype_tests']); + $result2 = $db1->createSchema($schema, 'datatype_tests'); + $this->assertEqual($result, $result2); + + $db1->query('DROP TABLE ' . $db1->fullTableName('datatype_tests')); + } + +/** + * Test index generation from table info. + * + * @return void + */ + function testIndexGeneration() { + $name = $this->db->fullTableName('index_test', false); + $this->db->query('CREATE TABLE ' . $name . ' ("id" serial NOT NULL PRIMARY KEY, "bool" integer, "small_char" varchar(50), "description" varchar(40) )'); + $this->db->query('CREATE INDEX pointless_bool ON ' . $name . '("bool")'); + $this->db->query('CREATE UNIQUE INDEX char_index ON ' . $name . '("small_char")'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'char_index' => array('column' => 'small_char', 'unique' => 1), + + ); + $result = $this->db->index($name); + $this->assertEqual($expected, $result); + + $this->db->query('DROP TABLE ' . $name); + $name = $this->db->fullTableName('index_test_2', false); + $this->db->query('CREATE TABLE ' . $name . ' ("id" serial NOT NULL PRIMARY KEY, "bool" integer, "small_char" varchar(50), "description" varchar(40) )'); + $this->db->query('CREATE UNIQUE INDEX multi_col ON ' . $name . '("small_char", "bool")'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'multi_col' => array('column' => array('small_char', 'bool'), 'unique' => 1), + ); + $result = $this->db->index($name); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + } + +/** + * Test the alterSchema capabilities of postgres + * + * @access public + * @return void + */ + function testAlterSchema() { + $Old =& new CakeSchema(array( + 'connection' => 'test_suite', + 'name' => 'AlterPosts', + 'alter_posts' => array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'author_id' => array('type' => 'integer', 'null' => false), + 'title' => array('type' => 'string', 'null' => true), + 'body' => array('type' => 'text'), + 'published' => array('type' => 'string', 'length' => 1, 'default' => 'N'), + 'created' => array('type' => 'datetime'), + 'updated' => array('type' => 'datetime'), + ) + )); + $this->db->query($this->db->createSchema($Old)); + + $New =& new CakeSchema(array( + 'connection' => 'test_suite', + 'name' => 'AlterPosts', + 'alter_posts' => array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'author_id' => array('type' => 'integer', 'null' => true), + 'title' => array('type' => 'string', 'null' => false, 'default' => 'my title'), + 'body' => array('type' => 'string', 'length' => 500), + 'status' => array('type' => 'integer', 'length' => 3, 'default' => 1), + 'created' => array('type' => 'datetime'), + 'updated' => array('type' => 'datetime'), + ) + )); + $this->db->query($this->db->alterSchema($New->compare($Old), 'alter_posts')); + + $model = new CakeTestModel(array('table' => 'alter_posts', 'ds' => 'test_suite')); + $result = $model->schema(); + $this->assertTrue(isset($result['status'])); + $this->assertFalse(isset($result['published'])); + $this->assertEqual($result['body']['type'], 'string'); + $this->assertEqual($result['status']['default'], 1); + $this->assertEqual($result['author_id']['null'], true); + $this->assertEqual($result['title']['null'], false); + + $this->db->query($this->db->dropSchema($New)); + } + +/** + * Test the alter index capabilities of postgres + * + * @access public + * @return void + */ + function testAlterIndexes() { + $this->db->cacheSources = false; + + $schema1 =& new CakeSchema(array( + 'name' => 'AlterTest1', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + 'group1' => array('type' => 'integer', 'null' => true), + 'group2' => array('type' => 'integer', 'null' => true) + ) + )); + $this->db->query($this->db->createSchema($schema1)); + + $schema2 =& new CakeSchema(array( + 'name' => 'AlterTest2', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + 'group1' => array('type' => 'integer', 'null' => true), + 'group2' => array('type' => 'integer', 'null' => true), + 'indexes' => array( + 'name_idx' => array('column' => 'name', 'unique' => 0), + 'group_idx' => array('column' => 'group1', 'unique' => 0), + 'compound_idx' => array('column' => array('group1', 'group2'), 'unique' => 0), + 'PRIMARY' => array('column' => 'id', 'unique' => 1) + ) + ) + )); + $this->db->query($this->db->alterSchema($schema2->compare($schema1))); + + $indexes = $this->db->index('altertest'); + $this->assertEqual($schema2->tables['altertest']['indexes'], $indexes); + + // Change three indexes, delete one and add another one + $schema3 =& new CakeSchema(array( + 'name' => 'AlterTest3', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + 'group1' => array('type' => 'integer', 'null' => true), + 'group2' => array('type' => 'integer', 'null' => true), + 'indexes' => array( + 'name_idx' => array('column' => 'name', 'unique' => 1), + 'group_idx' => array('column' => 'group2', 'unique' => 0), + 'compound_idx' => array('column' => array('group2', 'group1'), 'unique' => 0), + 'another_idx' => array('column' => array('group1', 'name'), 'unique' => 0)) + ))); + + $this->db->query($this->db->alterSchema($schema3->compare($schema2))); + + $indexes = $this->db->index('altertest'); + $this->assertEqual($schema3->tables['altertest']['indexes'], $indexes); + + // Compare us to ourself. + $this->assertEqual($schema3->compare($schema3), array()); + + // Drop the indexes + $this->db->query($this->db->alterSchema($schema1->compare($schema3))); + + $indexes = $this->db->index('altertest'); + $this->assertEqual(array(), $indexes); + + $this->db->query($this->db->dropSchema($schema1)); + } + +/* + * Test it is possible to use virtual field with postgresql + * + * @access public + * @return void + */ + function testVirtualFields() { + $this->loadFixtures('Article', 'Comment'); + $Article = new Article; + $Article->virtualFields = array( + 'next_id' => 'Article.id + 1', + 'complex' => 'Article.title || Article.body', + 'functional' => 'COALESCE(User.user, Article.title)', + 'subquery' => 'SELECT count(*) FROM ' . $Article->Comment->table + ); + $result = $Article->find('first'); + $this->assertEqual($result['Article']['next_id'], 2); + $this->assertEqual($result['Article']['complex'], $result['Article']['title'] . $result['Article']['body']); + $this->assertEqual($result['Article']['functional'], $result['Article']['title']); + $this->assertEqual($result['Article']['subquery'], 6); + } + +/** + * Tests additional order options for postgres + * + * @access public + * @return void + */ + function testOrderAdditionalParams() { + $result = $this->db->order(array('title' => 'DESC NULLS FIRST', 'body' => 'DESC')); + $expected = ' ORDER BY "title" DESC NULLS FIRST, "body" DESC'; + $this->assertEqual($result, $expected); + } + +/** +* Test it is possible to do a SELECT COUNT(DISTINCT Model.field) query in postgres and it gets correctly quoted +*/ + function testQuoteDistinctInFunction() { + $this->loadFixtures('Article'); + $Article = new Article; + $result = $this->db->fields($Article, null, array('COUNT(DISTINCT Article.id)')); + $expected = array('COUNT(DISTINCT "Article"."id")'); + $this->assertEqual($result, $expected); + + $result = $this->db->fields($Article, null, array('COUNT(DISTINCT id)')); + $expected = array('COUNT(DISTINCT "id")'); + $this->assertEqual($result, $expected); + + $result = $this->db->fields($Article, null, array('COUNT(DISTINCT FUNC(id))')); + $expected = array('COUNT(DISTINCT FUNC("id"))'); + $this->assertEqual($result, $expected); + } + +/** + * test that saveAll works even with conditions that lack a model name. + * + * @return void + */ + function testUpdateAllWithNonQualifiedConditions() { + $this->loadFixtures('Article'); + $Article =& new Article(); + $result = $Article->updateAll(array('title' => "'Awesome'"), array('title' => 'Third Article')); + $this->assertTrue($result); + + $result = $Article->find('count', array( + 'conditions' => array('Article.title' => 'Awesome') + )); + $this->assertEqual($result, 1, 'Article count is wrong or fixture has changed.'); + } + +/** + * test alterSchema on two tables. + * + * @return void + */ + function testAlteringTwoTables() { + $schema1 =& new CakeSchema(array( + 'name' => 'AlterTest1', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + ), + 'other_table' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'name' => array('type' => 'string', 'null' => false, 'length' => 50), + ) + )); + $schema2 =& new CakeSchema(array( + 'name' => 'AlterTest1', + 'connection' => 'test_suite', + 'altertest' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'field_two' => array('type' => 'string', 'null' => false, 'length' => 50), + ), + 'other_table' => array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0), + 'field_two' => array('type' => 'string', 'null' => false, 'length' => 50), + ) + )); + $result = $this->db->alterSchema($schema2->compare($schema1)); + $this->assertEqual(2, substr_count($result, 'field_two'), 'Too many fields'); + $this->assertFalse(strpos(';ALTER', $result), 'Too many semi colons'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_sqlite.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_sqlite.test.php new file mode 100644 index 000000000..cd5886e0e --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo/dbo_sqlite.test.php @@ -0,0 +1,355 @@ +simulated[] = $sql; + return null; + } + +/** + * getLastQuery method + * + * @access public + * @return void + */ + function getLastQuery() { + return $this->simulated[count($this->simulated) - 1]; + } +} + +/** + * DboSqliteTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources.dbo + */ +class DboSqliteTest extends CakeTestCase { + +/** + * Do not automatically load fixtures for each test, they will be loaded manually using CakeTestCase::loadFixtures + * + * @var boolean + * @access public + */ + var $autoFixtures = false; + +/** + * Fixtures + * + * @var object + * @access public + */ + var $fixtures = array('core.user'); + +/** + * Actual DB connection used in testing + * + * @var DboSource + * @access public + */ + var $db = null; + +/** + * Simulated DB connection used in testing + * + * @var DboSource + * @access public + */ + var $db2 = null; + +/** + * Skip if cannot connect to SQLite + * + * @access public + */ + function skip() { + $this->_initDb(); + $this->skipUnless($this->db->config['driver'] == 'sqlite', '%s SQLite connection not available'); + } + +/** + * Set up test suite database connection + * + * @access public + */ + function startTest() { + $this->_initDb(); + } + +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function setUp() { + Configure::write('Cache.disable', true); + $this->startTest(); + $this->db =& ConnectionManager::getDataSource('test_suite'); + $this->db2 = new DboSqliteTestDb($this->db->config, false); + } + +/** + * Sets up a Dbo class instance for testing + * + * @access public + */ + function tearDown() { + Configure::write('Cache.disable', false); + unset($this->db2); + } + +/** + * Tests that SELECT queries from DboSqlite::listSources() are not cached + * + * @access public + */ + function testTableListCacheDisabling() { + $this->assertFalse(in_array('foo_test', $this->db->listSources())); + + $this->db->query('CREATE TABLE foo_test (test VARCHAR(255));'); + $this->assertTrue(in_array('foo_test', $this->db->listSources())); + + $this->db->query('DROP TABLE foo_test;'); + $this->assertFalse(in_array('foo_test', $this->db->listSources())); + } + +/** + * test Index introspection. + * + * @access public + * @return void + */ + function testIndex() { + $name = $this->db->fullTableName('with_a_key'); + $this->db->query('CREATE TABLE ' . $name . ' ("id" int(11) PRIMARY KEY, "bool" int(1), "small_char" varchar(50), "description" varchar(40) );'); + $this->db->query('CREATE INDEX pointless_bool ON ' . $name . '("bool")'); + $this->db->query('CREATE UNIQUE INDEX char_index ON ' . $name . '("small_char")'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'pointless_bool' => array('column' => 'bool', 'unique' => 0), + 'char_index' => array('column' => 'small_char', 'unique' => 1), + + ); + $result = $this->db->index($name); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + + $this->db->query('CREATE TABLE ' . $name . ' ("id" int(11) PRIMARY KEY, "bool" int(1), "small_char" varchar(50), "description" varchar(40) );'); + $this->db->query('CREATE UNIQUE INDEX multi_col ON ' . $name . '("small_char", "bool")'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'multi_col' => array('column' => array('small_char', 'bool'), 'unique' => 1), + ); + $result = $this->db->index($name); + $this->assertEqual($expected, $result); + $this->db->query('DROP TABLE ' . $name); + } + +/** + * Tests that cached table descriptions are saved under the sanitized key name + * + * @access public + */ + function testCacheKeyName() { + Configure::write('Cache.disable', false); + + $dbName = 'db' . rand() . '$(*%&).db'; + $this->assertFalse(file_exists(TMP . $dbName)); + + $config = $this->db->config; + $db = new DboSqlite(array_merge($this->db->config, array('database' => TMP . $dbName))); + $this->assertTrue(file_exists(TMP . $dbName)); + + $db->execute("CREATE TABLE test_list (id VARCHAR(255));"); + + $db->cacheSources = true; + $this->assertEqual($db->listSources(), array('test_list')); + $db->cacheSources = false; + + $fileName = '_' . preg_replace('/[^A-Za-z0-9_\-+]/', '_', TMP . $dbName) . '_list'; + + $result = Cache::read($fileName, '_cake_model_'); + $this->assertEqual($result, array('test_list')); + + Cache::delete($fileName, '_cake_model_'); + Configure::write('Cache.disable', true); + } + +/** + * test building columns with SQLite + * + * @return void + */ + function testBuildColumn() { + $data = array( + 'name' => 'int_field', + 'type' => 'integer', + 'null' => false, + ); + $result = $this->db->buildColumn($data); + $expected = '"int_field" integer(11) NOT NULL'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'name', + 'type' => 'string', + 'length' => 20, + 'null' => false, + ); + $result = $this->db->buildColumn($data); + $expected = '"name" varchar(20) NOT NULL'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'testName', + 'type' => 'string', + 'length' => 20, + 'default' => null, + 'null' => true, + 'collate' => 'NOCASE' + ); + $result = $this->db->buildColumn($data); + $expected = '"testName" varchar(20) DEFAULT NULL COLLATE NOCASE'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'testName', + 'type' => 'string', + 'length' => 20, + 'default' => 'test-value', + 'null' => false, + ); + $result = $this->db->buildColumn($data); + $expected = '"testName" varchar(20) DEFAULT \'test-value\' NOT NULL'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'testName', + 'type' => 'integer', + 'length' => 10, + 'default' => 10, + 'null' => false, + ); + $result = $this->db->buildColumn($data); + $expected = '"testName" integer(10) DEFAULT \'10\' NOT NULL'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'testName', + 'type' => 'integer', + 'length' => 10, + 'default' => 10, + 'null' => false, + 'collate' => 'BADVALUE' + ); + $result = $this->db->buildColumn($data); + $expected = '"testName" integer(10) DEFAULT \'10\' NOT NULL'; + $this->assertEqual($result, $expected); + } + +/** + * test describe() and normal results. + * + * @return void + */ + function testDescribe() { + $Model =& new Model(array('name' => 'User', 'ds' => 'test_suite', 'table' => 'users')); + $result = $this->db->describe($Model); + $expected = array( + 'id' => array( + 'type' => 'integer', + 'key' => 'primary', + 'null' => false, + 'default' => null, + 'length' => 11 + ), + 'user' => array( + 'type' => 'string', + 'length' => 255, + 'null' => false, + 'default' => null + ), + 'password' => array( + 'type' => 'string', + 'length' => 255, + 'null' => false, + 'default' => null + ), + 'created' => array( + 'type' => 'datetime', + 'null' => true, + 'default' => null, + 'length' => null, + ), + 'updated' => array( + 'type' => 'datetime', + 'null' => true, + 'default' => null, + 'length' => null, + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * test that describe does not corrupt UUID primary keys + * + * @return void + */ + function testDescribeWithUuidPrimaryKey() { + $tableName = 'uuid_tests'; + $this->db->query("CREATE TABLE {$tableName} (id VARCHAR(36) PRIMARY KEY, name VARCHAR, created DATETIME, modified DATETIME)"); + $Model =& new Model(array('name' => 'UuidTest', 'ds' => 'test_suite', 'table' => 'uuid_tests')); + $result = $this->db->describe($Model); + $expected = array( + 'type' => 'string', + 'length' => 36, + 'null' => false, + 'default' => null, + 'key' => 'primary', + ); + $this->assertEqual($result['id'], $expected); + $this->db->query('DROP TABLE ' . $tableName); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo_source.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo_source.test.php new file mode 100644 index 000000000..1e913ccef --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/datasources/dbo_source.test.php @@ -0,0 +1,4616 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + define('CAKEPHP_UNIT_TEST_EXECUTION', 1); +} +App::import('Model', array('Model', 'DataSource', 'DboSource', 'DboMysql', 'App')); +require_once dirname(dirname(__FILE__)) . DS . 'models.php'; + +/** + * TestModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel' + * @access public + */ + var $name = 'TestModel'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema property + * + * @var array + * @access protected + */ + var $_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'client_id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '11'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'login' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'passwd' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_1' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'addr_2' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '25'), + 'zip_code' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'city' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'country' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'phone' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'fax' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'url' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'), + 'email' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'comments' => array('type' => 'text', 'null' => '1', 'default' => '', 'length' => '155'), + 'last_login' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + +/** + * find method + * + * @param mixed $conditions + * @param mixed $fields + * @param mixed $order + * @param mixed $recursive + * @access public + * @return void + */ + function find($conditions = null, $fields = null, $order = null, $recursive = null) { + return array($conditions, $fields); + } + +/** + * findAll method + * + * @param mixed $conditions + * @param mixed $fields + * @param mixed $order + * @param mixed $recursive + * @access public + * @return void + */ + function findAll($conditions = null, $fields = null, $order = null, $recursive = null) { + return $conditions; + } +} + +/** + * TestModel2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel2 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel2' + * @access public + */ + var $name = 'TestModel2'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; +} + +/** + * TestModel4 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel3 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel3' + * @access public + */ + var $name = 'TestModel3'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; +} + +/** + * TestModel4 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel4 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel4' + * @access public + */ + var $name = 'TestModel4'; + +/** + * table property + * + * @var string 'test_model4' + * @access public + */ + var $table = 'test_model4'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'TestModel4Parent' => array( + 'className' => 'TestModel4', + 'foreignKey' => 'parent_id' + ) + ); + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array( + 'TestModel5' => array( + 'className' => 'TestModel5', + 'foreignKey' => 'test_model4_id' + ) + ); + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('TestModel7' => array( + 'className' => 'TestModel7', + 'joinTable' => 'test_model4_test_model7', + 'foreignKey' => 'test_model4_id', + 'associationForeignKey' => 'test_model7_id', + 'with' => 'TestModel4TestModel7' + )); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * TestModel4TestModel7 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel4TestModel7 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel4TestModel7' + * @access public + */ + var $name = 'TestModel4TestModel7'; + +/** + * table property + * + * @var string 'test_model4_test_model7' + * @access public + */ + var $table = 'test_model4_test_model7'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'test_model4_id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'test_model7_id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8') + ); + } + return $this->_schema; + } +} + +/** + * TestModel5 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel5 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel5' + * @access public + */ + var $name = 'TestModel5'; + +/** + * table property + * + * @var string 'test_model5' + * @access public + */ + var $table = 'test_model5'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('TestModel4' => array( + 'className' => 'TestModel4', + 'foreignKey' => 'test_model4_id' + )); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('TestModel6' => array( + 'className' => 'TestModel6', + 'foreignKey' => 'test_model5_id' + )); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'test_model4_id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * TestModel6 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel6 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel6' + * @access public + */ + var $name = 'TestModel6'; + +/** + * table property + * + * @var string 'test_model6' + * @access public + */ + var $table = 'test_model6'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('TestModel5' => array( + 'className' => 'TestModel5', + 'foreignKey' => 'test_model5_id' + )); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'test_model5_id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * TestModel7 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel7 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel7' + * @access public + */ + var $name = 'TestModel7'; + +/** + * table property + * + * @var string 'test_model7' + * @access public + */ + var $table = 'test_model7'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * TestModel8 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel8 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel8' + * @access public + */ + var $name = 'TestModel8'; + +/** + * table property + * + * @var string 'test_model8' + * @access public + */ + var $table = 'test_model8'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array( + 'TestModel9' => array( + 'className' => 'TestModel9', + 'foreignKey' => 'test_model8_id', + 'conditions' => 'TestModel9.name != \'mariano\'' + ) + ); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'test_model9_id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * TestModel9 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class TestModel9 extends CakeTestModel { + +/** + * name property + * + * @var string 'TestModel9' + * @access public + */ + var $name = 'TestModel9'; + +/** + * table property + * + * @var string 'test_model9' + * @access public + */ + var $table = 'test_model9'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('TestModel8' => array( + 'className' => 'TestModel8', + 'foreignKey' => 'test_model8_id', + 'conditions' => 'TestModel8.name != \'larry\'' + )); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'test_model8_id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '11'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * Level class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class Level extends CakeTestModel { + +/** + * name property + * + * @var string 'Level' + * @access public + */ + var $name = 'Level'; + +/** + * table property + * + * @var string 'level' + * @access public + */ + var $table = 'level'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array( + 'Group'=> array( + 'className' => 'Group' + ), + 'User2' => array( + 'className' => 'User2' + ) + ); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'name' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => '20'), + ); + } + return $this->_schema; + } +} + +/** + * Group class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class Group extends CakeTestModel { + +/** + * name property + * + * @var string 'Group' + * @access public + */ + var $name = 'Group'; + +/** + * table property + * + * @var string 'group' + * @access public + */ + var $table = 'group'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Level'); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Category2', 'User2'); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'level_id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'name' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => '20'), + ); + } + return $this->_schema; + } + +} + +/** + * User2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class User2 extends CakeTestModel { + +/** + * name property + * + * @var string 'User2' + * @access public + */ + var $name = 'User2'; + +/** + * table property + * + * @var string 'user' + * @access public + */ + var $table = 'user'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'Group' => array( + 'className' => 'Group' + ), + 'Level' => array( + 'className' => 'Level' + ) + ); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array( + 'Article2' => array( + 'className' => 'Article2' + ), + ); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'group_id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'level_id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'name' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => '20'), + ); + } + return $this->_schema; + } +} + +/** + * Category2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class Category2 extends CakeTestModel { + +/** + * name property + * + * @var string 'Category2' + * @access public + */ + var $name = 'Category2'; + +/** + * table property + * + * @var string 'category' + * @access public + */ + var $table = 'category'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'Group' => array( + 'className' => 'Group', + 'foreignKey' => 'group_id' + ), + 'ParentCat' => array( + 'className' => 'Category2', + 'foreignKey' => 'parent_id' + ) + ); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array( + 'ChildCat' => array( + 'className' => 'Category2', + 'foreignKey' => 'parent_id' + ), + 'Article2' => array( + 'className' => 'Article2', + 'order'=>'Article2.published_date DESC', + 'foreignKey' => 'category_id', + 'limit'=>'3') + ); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '10'), + 'group_id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '10'), + 'parent_id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '10'), + 'name' => array('type' => 'string', 'null' => false, 'default' => '', 'length' => '255'), + 'icon' => array('type' => 'string', 'null' => false, 'default' => '', 'length' => '255'), + 'description' => array('type' => 'text', 'null' => false, 'default' => '', 'length' => null), + + ); + } + return $this->_schema; + } +} + +/** + * Article2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class Article2 extends CakeTestModel { + +/** + * name property + * + * @var string 'Article2' + * @access public + */ + var $name = 'Article2'; + +/** + * table property + * + * @var string 'article' + * @access public + */ + var $table = 'article'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'Category2' => array('className' => 'Category2'), + 'User2' => array('className' => 'User2') + ); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '10'), + 'category_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'rate_count' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'rate_sum' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'viewed' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'version' => array('type' => 'string', 'null' => true, 'default' => '', 'length' => '45'), + 'title' => array('type' => 'string', 'null' => false, 'default' => '', 'length' => '200'), + 'intro' => array('text' => 'string', 'null' => true, 'default' => '', 'length' => null), + 'comments' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '4'), + 'body' => array('text' => 'string', 'null' => true, 'default' => '', 'length' => null), + 'isdraft' => array('type' => 'boolean', 'null' => false, 'default' => '0', 'length' => '1'), + 'allow_comments' => array('type' => 'boolean', 'null' => false, 'default' => '1', 'length' => '1'), + 'moderate_comments' => array('type' => 'boolean', 'null' => false, 'default' => '1', 'length' => '1'), + 'published' => array('type' => 'boolean', 'null' => false, 'default' => '0', 'length' => '1'), + 'multipage' => array('type' => 'boolean', 'null' => false, 'default' => '0', 'length' => '1'), + 'published_date' => array('type' => 'datetime', 'null' => true, 'default' => '', 'length' => null), + 'created' => array('type' => 'datetime', 'null' => false, 'default' => '0000-00-00 00:00:00', 'length' => null), + 'modified' => array('type' => 'datetime', 'null' => false, 'default' => '0000-00-00 00:00:00', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * CategoryFeatured2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class CategoryFeatured2 extends CakeTestModel { + +/** + * name property + * + * @var string 'CategoryFeatured2' + * @access public + */ + var $name = 'CategoryFeatured2'; + +/** + * table property + * + * @var string 'category_featured' + * @access public + */ + var $table = 'category_featured'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '10'), + 'parent_id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '10'), + 'name' => array('type' => 'string', 'null' => false, 'default' => '', 'length' => '255'), + 'icon' => array('type' => 'string', 'null' => false, 'default' => '', 'length' => '255'), + 'description' => array('text' => 'string', 'null' => false, 'default' => '', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * Featured2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class Featured2 extends CakeTestModel { + +/** + * name property + * + * @var string 'Featured2' + * @access public + */ + var $name = 'Featured2'; + +/** + * table property + * + * @var string 'featured2' + * @access public + */ + var $table = 'featured2'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'CategoryFeatured2' => array( + 'className' => 'CategoryFeatured2' + ) + ); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'article_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'category_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'name' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => '20') + ); + } + return $this->_schema; + } +} + +/** + * Comment2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class Comment2 extends CakeTestModel { + +/** + * name property + * + * @var string 'Comment2' + * @access public + */ + var $name = 'Comment2'; + +/** + * table property + * + * @var string 'comment' + * @access public + */ + var $table = 'comment'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('ArticleFeatured2', 'User2'); + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'article_featured_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'name' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => '20') + ); + } + return $this->_schema; + } +} + +/** + * ArticleFeatured2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class ArticleFeatured2 extends CakeTestModel { + +/** + * name property + * + * @var string 'ArticleFeatured2' + * @access public + */ + var $name = 'ArticleFeatured2'; + +/** + * table property + * + * @var string 'article_featured' + * @access public + */ + var $table = 'article_featured'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'CategoryFeatured2' => array('className' => 'CategoryFeatured2'), + 'User2' => array('className' => 'User2') + ); + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array( + 'Featured2' => array('className' => 'Featured2') + ); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array( + 'Comment2' => array('className'=>'Comment2', 'dependent' => true) + ); + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + if (!isset($this->_schema)) { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => '10'), + 'category_featured_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'user_id' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => '10'), + 'title' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => '20'), + 'body' => array('text' => 'string', 'null' => true, 'default' => '', 'length' => null), + 'published' => array('type' => 'boolean', 'null' => false, 'default' => '0', 'length' => '1'), + 'published_date' => array('type' => 'datetime', 'null' => true, 'default' => '', 'length' => null), + 'created' => array('type' => 'datetime', 'null' => false, 'default' => '0000-00-00 00:00:00', 'length' => null), + 'modified' => array('type' => 'datetime', 'null' => false, 'default' => '0000-00-00 00:00:00', 'length' => null) + ); + } + return $this->_schema; + } +} + +/** + * DboSourceTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model.datasources + */ +class DboSourceTest extends CakeTestCase { + +/** + * debug property + * + * @var mixed null + * @access public + */ + var $debug = null; + +/** + * autoFixtures property + * + * @var bool false + * @access public + */ + var $autoFixtures = false; + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array( + 'core.apple', 'core.article', 'core.articles_tag', 'core.attachment', 'core.comment', + 'core.sample', 'core.tag', 'core.user', 'core.post', 'core.author', 'core.data_test' + ); + +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->__config = $this->db->config; + + if (!class_exists('DboTest')) { + $db = ConnectionManager::getDataSource('test_suite'); + $class = get_class($db); + eval("class DboTest extends $class { + var \$simulated = array(); + +/** + * execute method + * + * @param \$sql + * @access protected + * @return void + */ + function _execute(\$sql) { + \$this->simulated[] = \$sql; + return null; + } + +/** + * getLastQuery method + * + * @access public + * @return void + */ + function getLastQuery() { + return \$this->simulated[count(\$this->simulated) - 1]; + } + }"); + } + + $this->testDb =& new DboTest($this->__config); + $this->testDb->cacheSources = false; + $this->testDb->startQuote = '`'; + $this->testDb->endQuote = '`'; + Configure::write('debug', 1); + $this->debug = Configure::read('debug'); + $this->Model =& new TestModel(); + } + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + unset($this->Model); + Configure::write('debug', $this->debug); + ClassRegistry::flush(); + unset($this->debug); + } + +/** + * testFieldDoubleEscaping method + * + * @access public + * @return void + */ + function testFieldDoubleEscaping() { + $config = array_merge($this->__config, array('driver' => 'test')); + $test =& ConnectionManager::create('quoteTest', $config); + $test->simulated = array(); + + $this->Model =& new Article2(array('alias' => 'Article', 'ds' => 'quoteTest')); + $this->Model->setDataSource('quoteTest'); + + $this->assertEqual($this->Model->escapeField(), '`Article`.`id`'); + $result = $test->fields($this->Model, null, $this->Model->escapeField()); + $this->assertEqual($result, array('`Article`.`id`')); + + $result = $test->read($this->Model, array( + 'fields' => $this->Model->escapeField(), + 'conditions' => null, + 'recursive' => -1 + )); + $this->assertEqual(trim($test->simulated[0]), 'SELECT `Article`.`id` FROM `' . $this->testDb->fullTableName('article', false) . '` AS `Article` WHERE 1 = 1'); + + $test->startQuote = '['; + $test->endQuote = ']'; + $this->assertEqual($this->Model->escapeField(), '[Article].[id]'); + + $result = $test->fields($this->Model, null, $this->Model->escapeField()); + $this->assertEqual($result, array('[Article].[id]')); + + $result = $test->read($this->Model, array( + 'fields' => $this->Model->escapeField(), + 'conditions' => null, + 'recursive' => -1 + )); + $this->assertEqual(trim($test->simulated[1]), 'SELECT [Article].[id] FROM [' . $this->testDb->fullTableName('article', false) . '] AS [Article] WHERE 1 = 1'); + + ClassRegistry::removeObject('Article'); + } + +/** + * testGenerateAssociationQuerySelfJoin method + * + * @access public + * @return void + */ + function testGenerateAssociationQuerySelfJoin() { + $this->startTime = microtime(true); + $this->Model =& new Article2(); + $this->_buildRelatedModels($this->Model); + $this->_buildRelatedModels($this->Model->Category2); + $this->Model->Category2->ChildCat =& new Category2(); + $this->Model->Category2->ParentCat =& new Category2(); + + $queryData = array(); + + foreach ($this->Model->Category2->__associations as $type) { + foreach ($this->Model->Category2->{$type} as $assoc => $assocData) { + $linkModel =& $this->Model->Category2->{$assoc}; + $external = isset($assocData['external']); + + if ($this->Model->Category2->alias == $linkModel->alias && $type != 'hasAndBelongsToMany' && $type != 'hasMany') { + $result = $this->testDb->generateAssociationQuery($this->Model->Category2, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null); + $this->assertTrue($result); + } else { + if ($this->Model->Category2->useDbConfig == $linkModel->useDbConfig) { + $result = $this->testDb->generateAssociationQuery($this->Model->Category2, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null); + $this->assertTrue($result); + } + } + } + } + + $query = $this->testDb->generateAssociationQuery($this->Model->Category2, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+(.+)FROM(.+)`Category2`\.`group_id`\s+=\s+`Group`\.`id`\)\s+LEFT JOIN(.+)WHERE\s+1 = 1\s*$/', $query); + + $this->Model =& new TestModel4(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'belongsTo', 'model' => 'TestModel4Parent'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $_queryData = $queryData; + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + + $expected = array( + 'fields' => array( + '`TestModel4`.`id`', + '`TestModel4`.`name`', + '`TestModel4`.`created`', + '`TestModel4`.`updated`', + '`TestModel4Parent`.`id`', + '`TestModel4Parent`.`name`', + '`TestModel4Parent`.`created`', + '`TestModel4Parent`.`updated`' + ), + 'joins' => array( + array( + 'table' => '`test_model4`', + 'alias' => 'TestModel4Parent', + 'type' => 'LEFT', + 'conditions' => '`TestModel4`.`parent_id` = `TestModel4Parent`.`id`' + ) + ), + 'limit' => array(), + 'offset' => array(), + 'conditions' => array(), + 'order' => array(), + 'group' => null + ); + $this->assertEqual($queryData, $expected); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`, `TestModel4Parent`\.`id`, `TestModel4Parent`\.`name`, `TestModel4Parent`\.`created`, `TestModel4Parent`\.`updated`\s+/', $result); + $this->assertPattern('/FROM\s+`test_model4` AS `TestModel4`\s+LEFT JOIN\s+`test_model4` AS `TestModel4Parent`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel4`.`parent_id` = `TestModel4Parent`.`id`\)\s+WHERE/', $result); + $this->assertPattern('/\s+WHERE\s+1 = 1\s+$/', $result); + + $params['assocData']['type'] = 'INNER'; + $this->Model->belongsTo['TestModel4Parent']['type'] = 'INNER'; + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $_queryData, $params['external'], $resultSet); + $this->assertTrue($result); + $this->assertEqual($_queryData['joins'][0]['type'], 'INNER'); + } + +/** + * testGenerateInnerJoinAssociationQuery method + * + * @access public + * @return void + */ + function testGenerateInnerJoinAssociationQuery() { + $this->Model =& new TestModel9(); + $test =& ConnectionManager::create('test2', $this->__config); + $this->Model->setDataSource('test2'); + $this->Model->TestModel8 =& new TestModel8(); + $this->Model->TestModel8->setDataSource('test2'); + + $this->testDb->read($this->Model, array('recursive' => 1)); + $result = $this->testDb->getLastQuery(); + $this->assertPattern('/`TestModel9` LEFT JOIN `' . $this->testDb->fullTableName('test_model8', false) . '`/', $result); + + $this->Model->belongsTo['TestModel8']['type'] = 'INNER'; + $this->testDb->read($this->Model, array('recursive' => 1)); + $result = $this->testDb->getLastQuery(); + $this->assertPattern('/`TestModel9` INNER JOIN `' . $this->testDb->fullTableName('test_model8', false) . '`/', $result); + + } + +/** + * testGenerateAssociationQuerySelfJoinWithConditionsInHasOneBinding method + * + * @access public + * @return void + */ + function testGenerateAssociationQuerySelfJoinWithConditionsInHasOneBinding() { + $this->Model =& new TestModel8(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'hasOne', 'model' => 'TestModel9'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + $_queryData = $queryData; + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel8`\.`id`, `TestModel8`\.`test_model9_id`, `TestModel8`\.`name`, `TestModel8`\.`created`, `TestModel8`\.`updated`, `TestModel9`\.`id`, `TestModel9`\.`test_model8_id`, `TestModel9`\.`name`, `TestModel9`\.`created`, `TestModel9`\.`updated`\s+/', $result); + $this->assertPattern('/FROM\s+`test_model8` AS `TestModel8`\s+LEFT JOIN\s+`test_model9` AS `TestModel9`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel9`\.`name` != \'mariano\'\s+AND\s+`TestModel9`.`test_model8_id` = `TestModel8`.`id`\)\s+WHERE/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQuerySelfJoinWithConditionsInBelongsToBinding method + * + * @access public + * @return void + */ + function testGenerateAssociationQuerySelfJoinWithConditionsInBelongsToBinding() { + $this->Model =& new TestModel9(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'belongsTo', 'model' => 'TestModel8'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel9`\.`id`, `TestModel9`\.`test_model8_id`, `TestModel9`\.`name`, `TestModel9`\.`created`, `TestModel9`\.`updated`, `TestModel8`\.`id`, `TestModel8`\.`test_model9_id`, `TestModel8`\.`name`, `TestModel8`\.`created`, `TestModel8`\.`updated`\s+/', $result); + $this->assertPattern('/FROM\s+`test_model9` AS `TestModel9`\s+LEFT JOIN\s+`test_model8` AS `TestModel8`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel8`\.`name` != \'larry\'\s+AND\s+`TestModel9`.`test_model8_id` = `TestModel8`.`id`\)\s+WHERE/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQuerySelfJoinWithConditions method + * + * @access public + * @return void + */ + function testGenerateAssociationQuerySelfJoinWithConditions() { + $this->Model =& new TestModel4(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'belongsTo', 'model' => 'TestModel4Parent'); + $queryData = array('conditions' => array('TestModel4Parent.name !=' => 'mariano')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`, `TestModel4Parent`\.`id`, `TestModel4Parent`\.`name`, `TestModel4Parent`\.`created`, `TestModel4Parent`\.`updated`\s+/', $result); + $this->assertPattern('/FROM\s+`test_model4` AS `TestModel4`\s+LEFT JOIN\s+`test_model4` AS `TestModel4Parent`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel4`.`parent_id` = `TestModel4Parent`.`id`\)\s+WHERE/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?`TestModel4Parent`.`name`\s+!=\s+\'mariano\'(?:\))?\s*$/', $result); + + $this->Featured2 =& new Featured2(); + $this->Featured2->schema(); + + $this->Featured2->bindModel(array( + 'belongsTo' => array( + 'ArticleFeatured2' => array( + 'conditions' => 'ArticleFeatured2.published = \'Y\'', + 'fields' => 'id, title, user_id, published' + ) + ) + )); + + $this->_buildRelatedModels($this->Featured2); + + $binding = array('type' => 'belongsTo', 'model' => 'ArticleFeatured2'); + $queryData = array('conditions' => array()); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Featured2, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Featured2, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + $result = $this->testDb->generateAssociationQuery($this->Featured2, $null, null, null, null, $queryData, false, $null); + + $this->assertPattern( + '/^SELECT\s+`Featured2`\.`id`, `Featured2`\.`article_id`, `Featured2`\.`category_id`, `Featured2`\.`name`,\s+'. + '`ArticleFeatured2`\.`id`, `ArticleFeatured2`\.`title`, `ArticleFeatured2`\.`user_id`, `ArticleFeatured2`\.`published`\s+' . + 'FROM\s+`featured2` AS `Featured2`\s+LEFT JOIN\s+`article_featured` AS `ArticleFeatured2`' . + '\s+ON\s+\(`ArticleFeatured2`.`published` = \'Y\'\s+AND\s+`Featured2`\.`article_featured2_id` = `ArticleFeatured2`\.`id`\)' . + '\s+WHERE\s+1\s+=\s+1\s*$/', + $result + ); + } + +/** + * testGenerateAssociationQueryHasOne method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasOne() { + $this->Model =& new TestModel4(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'hasOne', 'model' => 'TestModel5'); + + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + + $result = $this->testDb->buildJoinStatement($queryData['joins'][0]); + $expected = ' LEFT JOIN `test_model5` AS `TestModel5` ON (`TestModel5`.`test_model4_id` = `TestModel4`.`id`)'; + $this->assertEqual(trim($result), trim($expected)); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`, `TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model4` AS `TestModel4`\s+LEFT JOIN\s+/', $result); + $this->assertPattern('/`test_model5` AS `TestModel5`\s+ON\s+\(`TestModel5`.`test_model4_id` = `TestModel4`.`id`\)\s+WHERE/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?\s*1 = 1\s*(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQueryHasOneWithConditions method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasOneWithConditions() { + $this->Model =& new TestModel4(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'hasOne', 'model' => 'TestModel5'); + + $queryData = array('conditions' => array('TestModel5.name !=' => 'mariano')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + + $this->assertPattern('/^SELECT\s+`TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`, `TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model4` AS `TestModel4`\s+LEFT JOIN\s+`test_model5` AS `TestModel5`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel5`.`test_model4_id`\s+=\s+`TestModel4`.`id`\)\s+WHERE/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?\s*`TestModel5`.`name`\s+!=\s+\'mariano\'\s*(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQueryBelongsTo method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryBelongsTo() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type'=>'belongsTo', 'model'=>'TestModel4'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + + $result = $this->testDb->buildJoinStatement($queryData['joins'][0]); + $expected = ' LEFT JOIN `test_model4` AS `TestModel4` ON (`TestModel5`.`test_model4_id` = `TestModel4`.`id`)'; + $this->assertEqual(trim($result), trim($expected)); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`, `TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+LEFT JOIN\s+`test_model4` AS `TestModel4`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel5`.`test_model4_id` = `TestModel4`.`id`\)\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?\s*1 = 1\s*(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQueryBelongsToWithConditions method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryBelongsToWithConditions() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'belongsTo', 'model' => 'TestModel4'); + $queryData = array('conditions' => array('TestModel5.name !=' => 'mariano')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertTrue($result); + + $result = $this->testDb->buildJoinStatement($queryData['joins'][0]); + $expected = ' LEFT JOIN `test_model4` AS `TestModel4` ON (`TestModel5`.`test_model4_id` = `TestModel4`.`id`)'; + $this->assertEqual(trim($result), trim($expected)); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`, `TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+LEFT JOIN\s+`test_model4` AS `TestModel4`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel5`.`test_model4_id` = `TestModel4`.`id`\)\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+`TestModel5`.`name` != \'mariano\'\s*$/', $result); + } + +/** + * testGenerateAssociationQueryHasMany method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasMany() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + + $this->assertPattern('/^SELECT\s+`TestModel6`\.`id`, `TestModel6`\.`test_model5_id`, `TestModel6`\.`name`, `TestModel6`\.`created`, `TestModel6`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE/', $result); + $this->assertPattern('/\s+WHERE\s+`TestModel6`.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?\s*1 = 1\s*(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQueryHasManyWithLimit method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasManyWithLimit() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $this->Model->hasMany['TestModel6']['limit'] = 2; + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern( + '/^SELECT\s+' . + '`TestModel6`\.`id`, `TestModel6`\.`test_model5_id`, `TestModel6`\.`name`, `TestModel6`\.`created`, `TestModel6`\.`updated`\s+'. + 'FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+' . + '`TestModel6`.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)\s*'. + 'LIMIT \d*'. + '\s*$/', $result + ); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern( + '/^SELECT\s+'. + '`TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`\s+'. + 'FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+'. + '(?:\()?\s*1 = 1\s*(?:\))?'. + '\s*$/', $result + ); + } + +/** + * testGenerateAssociationQueryHasManyWithConditions method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasManyWithConditions() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array('conditions' => array('TestModel5.name !=' => 'mariano')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel6`\.`id`, `TestModel6`\.`test_model5_id`, `TestModel6`\.`name`, `TestModel6`\.`created`, `TestModel6`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?`TestModel5`.`name`\s+!=\s+\'mariano\'(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQueryHasManyWithOffsetAndLimit method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasManyWithOffsetAndLimit() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $__backup = $this->Model->hasMany['TestModel6']; + + $this->Model->hasMany['TestModel6']['offset'] = 2; + $this->Model->hasMany['TestModel6']['limit'] = 5; + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + + $this->assertPattern('/^SELECT\s+`TestModel6`\.`id`, `TestModel6`\.`test_model5_id`, `TestModel6`\.`name`, `TestModel6`\.`created`, `TestModel6`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + $this->assertPattern('/\s+LIMIT 2,\s*5\s*$/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + $this->Model->hasMany['TestModel6'] = $__backup; + } + +/** + * testGenerateAssociationQueryHasManyWithPageAndLimit method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasManyWithPageAndLimit() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $__backup = $this->Model->hasMany['TestModel6']; + + $this->Model->hasMany['TestModel6']['page'] = 2; + $this->Model->hasMany['TestModel6']['limit'] = 5; + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel6`\.`id`, `TestModel6`\.`test_model5_id`, `TestModel6`\.`name`, `TestModel6`\.`created`, `TestModel6`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + $this->assertPattern('/\s+LIMIT 5,\s*5\s*$/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`test_model4_id`, `TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + $this->Model->hasMany['TestModel6'] = $__backup; + } + +/** + * testGenerateAssociationQueryHasManyWithFields method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasManyWithFields() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array('fields' => array('`TestModel5`.`name`')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel6`\.`id`, `TestModel6`\.`test_model5_id`, `TestModel6`\.`name`, `TestModel6`\.`created`, `TestModel6`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`name`, `TestModel5`\.`id`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array('fields' => array('`TestModel5`.`id`, `TestModel5`.`name`')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel6`\.`id`, `TestModel6`\.`test_model5_id`, `TestModel6`\.`name`, `TestModel6`\.`created`, `TestModel6`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`name`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array('fields' => array('`TestModel5`.`name`', '`TestModel5`.`created`')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel6`\.`id`, `TestModel6`\.`test_model5_id`, `TestModel6`\.`name`, `TestModel6`\.`created`, `TestModel6`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`name`, `TestModel5`\.`created`, `TestModel5`\.`id`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + $this->Model->hasMany['TestModel6']['fields'] = array('name'); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array('fields' => array('`TestModel5`.`id`', '`TestModel5`.`name`')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel6`\.`name`, `TestModel6`\.`test_model5_id`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`name`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + unset($this->Model->hasMany['TestModel6']['fields']); + + $this->Model->hasMany['TestModel6']['fields'] = array('id', 'name'); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array('fields' => array('`TestModel5`.`id`', '`TestModel5`.`name`')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel6`\.`id`, `TestModel6`\.`name`, `TestModel6`\.`test_model5_id`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`name`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + unset($this->Model->hasMany['TestModel6']['fields']); + + $this->Model->hasMany['TestModel6']['fields'] = array('test_model5_id', 'name'); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array('fields' => array('`TestModel5`.`id`', '`TestModel5`.`name`')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel6`\.`test_model5_id`, `TestModel6`\.`name`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model6` AS `TestModel6`\s+WHERE\s+/', $result); + $this->assertPattern('/WHERE\s+(?:\()?`TestModel6`\.`test_model5_id`\s+=\s+\({\$__cakeID__\$}\)(?:\))?/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel5`\.`id`, `TestModel5`\.`name`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model5` AS `TestModel5`\s+WHERE\s+/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + unset($this->Model->hasMany['TestModel6']['fields']); + } + +/** + * test generateAssociationQuery with a hasMany and an aggregate function. + * + * @return void + */ + function testGenerateAssociationQueryHasManyAndAggregateFunction() { + $this->Model =& new TestModel5(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'hasMany', 'model' => 'TestModel6'); + $queryData = array('fields' => array('MIN(TestModel5.test_model4_id)')); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + $this->Model->recursive = 0; + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, $params['type'], $params['assoc'], $params['assocData'], $queryData, false, $resultSet); + $this->assertPattern('/^SELECT\s+MIN\(`TestModel5`\.`test_model4_id`\)\s+FROM/', $result); + } + +/** + * testGenerateAssociationQueryHasAndBelongsToMany method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasAndBelongsToMany() { + $this->Model =& new TestModel4(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type' => 'hasAndBelongsToMany', 'model' => 'TestModel7'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params =& $this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel7`\.`id`, `TestModel7`\.`name`, `TestModel7`\.`created`, `TestModel7`\.`updated`, `TestModel4TestModel7`\.`test_model4_id`, `TestModel4TestModel7`\.`test_model7_id`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model7` AS `TestModel7`\s+JOIN\s+`' . $this->testDb->fullTableName('test_model4_test_model7', false) . '`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel4TestModel7`\.`test_model4_id`\s+=\s+{\$__cakeID__\$}\s+AND/', $result); + $this->assertPattern('/\s+AND\s+`TestModel4TestModel7`\.`test_model7_id`\s+=\s+`TestModel7`\.`id`\)/', $result); + $this->assertPattern('/WHERE\s+(?:\()?1 = 1(?:\))?\s*$/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model4` AS `TestModel4`\s+WHERE/', $result); + $this->assertPattern('/\s+WHERE\s+(?:\()?1 = 1(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQueryHasAndBelongsToManyWithConditions method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasAndBelongsToManyWithConditions() { + $this->Model =& new TestModel4(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $binding = array('type'=>'hasAndBelongsToMany', 'model'=>'TestModel7'); + $queryData = array('conditions' => array('TestModel4.name !=' => 'mariano')); + $resultSet = null; + $null = null; + + $params =& $this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel7`\.`id`, `TestModel7`\.`name`, `TestModel7`\.`created`, `TestModel7`\.`updated`, `TestModel4TestModel7`\.`test_model4_id`, `TestModel4TestModel7`\.`test_model7_id`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model7`\s+AS\s+`TestModel7`\s+JOIN\s+`test_model4_test_model7`\s+AS\s+`TestModel4TestModel7`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel4TestModel7`\.`test_model4_id`\s+=\s+{\$__cakeID__\$}/', $result); + $this->assertPattern('/\s+AND\s+`TestModel4TestModel7`\.`test_model7_id`\s+=\s+`TestModel7`\.`id`\)\s+WHERE\s+/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model4` AS `TestModel4`\s+WHERE\s+(?:\()?`TestModel4`.`name`\s+!=\s+\'mariano\'(?:\))?\s*$/', $result); + } + +/** + * testGenerateAssociationQueryHasAndBelongsToManyWithOffsetAndLimit method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasAndBelongsToManyWithOffsetAndLimit() { + $this->Model =& new TestModel4(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $__backup = $this->Model->hasAndBelongsToMany['TestModel7']; + + $this->Model->hasAndBelongsToMany['TestModel7']['offset'] = 2; + $this->Model->hasAndBelongsToMany['TestModel7']['limit'] = 5; + + $binding = array('type'=>'hasAndBelongsToMany', 'model'=>'TestModel7'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel7`\.`id`, `TestModel7`\.`name`, `TestModel7`\.`created`, `TestModel7`\.`updated`, `TestModel4TestModel7`\.`test_model4_id`, `TestModel4TestModel7`\.`test_model7_id`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model7`\s+AS\s+`TestModel7`\s+JOIN\s+`test_model4_test_model7`\s+AS\s+`TestModel4TestModel7`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel4TestModel7`\.`test_model4_id`\s+=\s+{\$__cakeID__\$}\s+/', $result); + $this->assertPattern('/\s+AND\s+`TestModel4TestModel7`\.`test_model7_id`\s+=\s+`TestModel7`\.`id`\)\s+WHERE\s+/', $result); + $this->assertPattern('/\s+(?:\()?1\s+=\s+1(?:\))?\s*\s+LIMIT 2,\s*5\s*$/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model4` AS `TestModel4`\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + $this->Model->hasAndBelongsToMany['TestModel7'] = $__backup; + } + +/** + * testGenerateAssociationQueryHasAndBelongsToManyWithPageAndLimit method + * + * @access public + * @return void + */ + function testGenerateAssociationQueryHasAndBelongsToManyWithPageAndLimit() { + $this->Model =& new TestModel4(); + $this->Model->schema(); + $this->_buildRelatedModels($this->Model); + + $__backup = $this->Model->hasAndBelongsToMany['TestModel7']; + + $this->Model->hasAndBelongsToMany['TestModel7']['page'] = 2; + $this->Model->hasAndBelongsToMany['TestModel7']['limit'] = 5; + + $binding = array('type'=>'hasAndBelongsToMany', 'model'=>'TestModel7'); + $queryData = array(); + $resultSet = null; + $null = null; + + $params = &$this->_prepareAssociationQuery($this->Model, $queryData, $binding); + + $result = $this->testDb->generateAssociationQuery($this->Model, $params['linkModel'], $params['type'], $params['assoc'], $params['assocData'], $queryData, $params['external'], $resultSet); + $this->assertPattern('/^SELECT\s+`TestModel7`\.`id`, `TestModel7`\.`name`, `TestModel7`\.`created`, `TestModel7`\.`updated`, `TestModel4TestModel7`\.`test_model4_id`, `TestModel4TestModel7`\.`test_model7_id`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model7`\s+AS\s+`TestModel7`\s+JOIN\s+`test_model4_test_model7`\s+AS\s+`TestModel4TestModel7`/', $result); + $this->assertPattern('/\s+ON\s+\(`TestModel4TestModel7`\.`test_model4_id`\s+=\s+{\$__cakeID__\$}/', $result); + $this->assertPattern('/\s+AND\s+`TestModel4TestModel7`\.`test_model7_id`\s+=\s+`TestModel7`\.`id`\)\s+WHERE\s+/', $result); + $this->assertPattern('/\s+(?:\()?1\s+=\s+1(?:\))?\s*\s+LIMIT 5,\s*5\s*$/', $result); + + $result = $this->testDb->generateAssociationQuery($this->Model, $null, null, null, null, $queryData, false, $null); + $this->assertPattern('/^SELECT\s+`TestModel4`\.`id`, `TestModel4`\.`name`, `TestModel4`\.`created`, `TestModel4`\.`updated`\s+/', $result); + $this->assertPattern('/\s+FROM\s+`test_model4` AS `TestModel4`\s+WHERE\s+(?:\()?1\s+=\s+1(?:\))?\s*$/', $result); + + $this->Model->hasAndBelongsToMany['TestModel7'] = $__backup; + } + +/** + * buildRelatedModels method + * + * @param mixed $model + * @access protected + * @return void + */ + function _buildRelatedModels(&$model) { + foreach ($model->__associations as $type) { + foreach ($model->{$type} as $assoc => $assocData) { + if (is_string($assocData)) { + $className = $assocData; + } elseif (isset($assocData['className'])) { + $className = $assocData['className']; + } + $model->$className =& new $className(); + $model->$className->schema(); + } + } + } + +/** + * &_prepareAssociationQuery method + * + * @param mixed $model + * @param mixed $queryData + * @param mixed $binding + * @access public + * @return void + */ + function &_prepareAssociationQuery(&$model, &$queryData, $binding) { + $type = $binding['type']; + $assoc = $binding['model']; + $assocData = $model->{$type}[$assoc]; + $className = $assocData['className']; + + $linkModel =& $model->{$className}; + $external = isset($assocData['external']); + $queryData = $this->testDb->__scrubQueryData($queryData); + + $result = array_merge(array('linkModel' => &$linkModel), compact('type', 'assoc', 'assocData', 'external')); + return $result; + } + +/** + * testSelectDistict method + * + * @access public + * @return void + */ + function testSelectDistict() { + $result = $this->testDb->fields($this->Model, 'Vendor', "DISTINCT Vendor.id, Vendor.name"); + $expected = array('DISTINCT `Vendor`.`id`', '`Vendor`.`name`'); + $this->assertEqual($result, $expected); + } + +/** + * test that booleans and null make logical condition strings. + * + * @return void + */ + function testBooleanNullConditionsParsing() { + $result = $this->testDb->conditions(true); + $this->assertEqual($result, ' WHERE 1 = 1', 'true conditions failed %s'); + + $result = $this->testDb->conditions(false); + $this->assertEqual($result, ' WHERE 0 = 1', 'false conditions failed %s'); + + $result = $this->testDb->conditions(null); + $this->assertEqual($result, ' WHERE 1 = 1', 'null conditions failed %s'); + + $result = $this->testDb->conditions(array()); + $this->assertEqual($result, ' WHERE 1 = 1', 'array() conditions failed %s'); + + $result = $this->testDb->conditions(''); + $this->assertEqual($result, ' WHERE 1 = 1', '"" conditions failed %s'); + + $result = $this->testDb->conditions(' ', '" " conditions failed %s'); + $this->assertEqual($result, ' WHERE 1 = 1'); + } +/** + * testStringConditionsParsing method + * + * @access public + * @return void + */ + function testStringConditionsParsing() { + $result = $this->testDb->conditions("ProjectBid.project_id = Project.id"); + $expected = " WHERE `ProjectBid`.`project_id` = `Project`.`id`"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("Candy.name LIKE 'a' AND HardCandy.name LIKE 'c'"); + $expected = " WHERE `Candy`.`name` LIKE 'a' AND `HardCandy`.`name` LIKE 'c'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("HardCandy.name LIKE 'a' AND Candy.name LIKE 'c'"); + $expected = " WHERE `HardCandy`.`name` LIKE 'a' AND `Candy`.`name` LIKE 'c'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("Post.title = '1.1'"); + $expected = " WHERE `Post`.`title` = '1.1'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("User.id != 0 AND User.user LIKE '%arr%'"); + $expected = " WHERE `User`.`id` != 0 AND `User`.`user` LIKE '%arr%'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("SUM(Post.comments_count) > 500"); + $expected = " WHERE SUM(`Post`.`comments_count`) > 500"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("(Post.created < '" . date('Y-m-d H:i:s') . "') GROUP BY YEAR(Post.created), MONTH(Post.created)"); + $expected = " WHERE (`Post`.`created` < '" . date('Y-m-d H:i:s') . "') GROUP BY YEAR(`Post`.`created`), MONTH(`Post`.`created`)"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("score BETWEEN 90.1 AND 95.7"); + $expected = " WHERE score BETWEEN 90.1 AND 95.7"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('score' => array(2=>1, 2, 10))); + $expected = " WHERE score IN (1, 2, 10)"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("Aro.rght = Aro.lft + 1.1"); + $expected = " WHERE `Aro`.`rght` = `Aro`.`lft` + 1.1"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("(Post.created < '" . date('Y-m-d H:i:s') . "') GROUP BY YEAR(Post.created), MONTH(Post.created)"); + $expected = " WHERE (`Post`.`created` < '" . date('Y-m-d H:i:s') . "') GROUP BY YEAR(`Post`.`created`), MONTH(`Post`.`created`)"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('Sportstaette.sportstaette LIKE "%ru%" AND Sportstaette.sportstaettenart_id = 2'); + $expected = ' WHERE `Sportstaette`.`sportstaette` LIKE "%ru%" AND `Sportstaette`.`sportstaettenart_id` = 2'; + $this->assertPattern('/\s*WHERE\s+`Sportstaette`\.`sportstaette`\s+LIKE\s+"%ru%"\s+AND\s+`Sports/', $result); + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('Sportstaette.sportstaettenart_id = 2 AND Sportstaette.sportstaette LIKE "%ru%"'); + $expected = ' WHERE `Sportstaette`.`sportstaettenart_id` = 2 AND `Sportstaette`.`sportstaette` LIKE "%ru%"'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('SUM(Post.comments_count) > 500 AND NOT Post.title IS NULL AND NOT Post.extended_title IS NULL'); + $expected = ' WHERE SUM(`Post`.`comments_count`) > 500 AND NOT `Post`.`title` IS NULL AND NOT `Post`.`extended_title` IS NULL'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('NOT Post.title IS NULL AND NOT Post.extended_title IS NULL AND SUM(Post.comments_count) > 500'); + $expected = ' WHERE NOT `Post`.`title` IS NULL AND NOT `Post`.`extended_title` IS NULL AND SUM(`Post`.`comments_count`) > 500'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('NOT Post.extended_title IS NULL AND NOT Post.title IS NULL AND Post.title != "" AND SPOON(SUM(Post.comments_count) + 1.1) > 500'); + $expected = ' WHERE NOT `Post`.`extended_title` IS NULL AND NOT `Post`.`title` IS NULL AND `Post`.`title` != "" AND SPOON(SUM(`Post`.`comments_count`) + 1.1) > 500'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('NOT Post.title_extended IS NULL AND NOT Post.title IS NULL AND Post.title_extended != Post.title'); + $expected = ' WHERE NOT `Post`.`title_extended` IS NULL AND NOT `Post`.`title` IS NULL AND `Post`.`title_extended` != `Post`.`title`'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("Comment.id = 'a'"); + $expected = " WHERE `Comment`.`id` = 'a'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("lower(Article.title) LIKE 'a%'"); + $expected = " WHERE lower(`Article`.`title`) LIKE 'a%'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('((MATCH(Video.title) AGAINST(\'My Search*\' IN BOOLEAN MODE) * 2) + (MATCH(Video.description) AGAINST(\'My Search*\' IN BOOLEAN MODE) * 0.4) + (MATCH(Video.tags) AGAINST(\'My Search*\' IN BOOLEAN MODE) * 1.5))'); + $expected = ' WHERE ((MATCH(`Video`.`title`) AGAINST(\'My Search*\' IN BOOLEAN MODE) * 2) + (MATCH(`Video`.`description`) AGAINST(\'My Search*\' IN BOOLEAN MODE) * 0.4) + (MATCH(`Video`.`tags`) AGAINST(\'My Search*\' IN BOOLEAN MODE) * 1.5))'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('DATEDIFF(NOW(),Article.published) < 1 && Article.live=1'); + $expected = " WHERE DATEDIFF(NOW(),`Article`.`published`) < 1 && `Article`.`live`=1"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('file = "index.html"'); + $expected = ' WHERE file = "index.html"'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions("file = 'index.html'"); + $expected = " WHERE file = 'index.html'"; + $this->assertEqual($result, $expected); + + $letter = $letter = 'd.a'; + $conditions = array('Company.name like ' => $letter . '%'); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE `Company`.`name` like 'd.a%'"; + $this->assertEqual($result, $expected); + + $conditions = array('Artist.name' => 'JUDY and MARY'); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE `Artist`.`name` = 'JUDY and MARY'"; + $this->assertEqual($result, $expected); + + $conditions = array('Artist.name' => 'JUDY AND MARY'); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE `Artist`.`name` = 'JUDY AND MARY'"; + $this->assertEqual($result, $expected); + } + +/** + * testQuotesInStringConditions method + * + * @access public + * @return void + */ + function testQuotesInStringConditions() { + $result = $this->testDb->conditions('Member.email = \'mariano@cricava.com\''); + $expected = ' WHERE `Member`.`email` = \'mariano@cricava.com\''; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('Member.email = "mariano@cricava.com"'); + $expected = ' WHERE `Member`.`email` = "mariano@cricava.com"'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions('Member.email = \'mariano@cricava.com\' AND Member.user LIKE \'mariano.iglesias%\''); + $expected = ' WHERE `Member`.`email` = \'mariano@cricava.com\' AND `Member`.`user` LIKE \'mariano.iglesias%\''; + $this->assertEqual($result, $expected); + + + $result = $this->testDb->conditions('Member.email = "mariano@cricava.com" AND Member.user LIKE "mariano.iglesias%"'); + $expected = ' WHERE `Member`.`email` = "mariano@cricava.com" AND `Member`.`user` LIKE "mariano.iglesias%"'; + $this->assertEqual($result, $expected); + } + +/** + * testParenthesisInStringConditions method + * + * @access public + * @return void + */ + function testParenthesisInStringConditions() { + $result = $this->testDb->conditions('Member.name = \'(lu\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(lu\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \')lu\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\)lu\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'va(lu\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'va\(lu\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'va)lu\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'va\)lu\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'va(lu)\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'va\(lu\)\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'va(lu)e\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'va\(lu\)e\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'(mariano)\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano\)\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'(mariano)iglesias\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano\)iglesias\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'(mariano) iglesias\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano\) iglesias\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'(mariano word) iglesias\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano word\) iglesias\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'(mariano.iglesias)\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano.iglesias\)\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'Mariano Iglesias (mariano.iglesias)\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'Mariano Iglesias \(mariano.iglesias\)\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'Mariano Iglesias (mariano.iglesias) CakePHP\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'Mariano Iglesias \(mariano.iglesias\) CakePHP\'$/', $result); + + $result = $this->testDb->conditions('Member.name = \'(mariano.iglesias) CakePHP\''); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano.iglesias\) CakePHP\'$/', $result); + } + +/** + * testParenthesisInArrayConditions method + * + * @access public + * @return void + */ + function testParenthesisInArrayConditions() { + $result = $this->testDb->conditions(array('Member.name' => '(lu')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(lu\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => ')lu')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\)lu\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => 'va(lu')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'va\(lu\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => 'va)lu')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'va\)lu\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => 'va(lu)')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'va\(lu\)\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => 'va(lu)e')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'va\(lu\)e\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => '(mariano)')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano\)\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => '(mariano)iglesias')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano\)iglesias\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => '(mariano) iglesias')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano\) iglesias\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => '(mariano word) iglesias')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano word\) iglesias\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => '(mariano.iglesias)')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano.iglesias\)\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => 'Mariano Iglesias (mariano.iglesias)')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'Mariano Iglesias \(mariano.iglesias\)\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => 'Mariano Iglesias (mariano.iglesias) CakePHP')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'Mariano Iglesias \(mariano.iglesias\) CakePHP\'$/', $result); + + $result = $this->testDb->conditions(array('Member.name' => '(mariano.iglesias) CakePHP')); + $this->assertPattern('/^\s+WHERE\s+`Member`.`name`\s+=\s+\'\(mariano.iglesias\) CakePHP\'$/', $result); + } + +/** + * testArrayConditionsParsing method + * + * @access public + * @return void + */ + function testArrayConditionsParsing() { + $result = $this->testDb->conditions(array('Stereo.type' => 'in dash speakers')); + $this->assertPattern("/^\s+WHERE\s+`Stereo`.`type`\s+=\s+'in dash speakers'/", $result); + + $result = $this->testDb->conditions(array('Candy.name LIKE' => 'a', 'HardCandy.name LIKE' => 'c')); + $this->assertPattern("/^\s+WHERE\s+`Candy`.`name` LIKE\s+'a'\s+AND\s+`HardCandy`.`name`\s+LIKE\s+'c'/", $result); + + $result = $this->testDb->conditions(array('HardCandy.name LIKE' => 'a', 'Candy.name LIKE' => 'c')); + $expected = " WHERE `HardCandy`.`name` LIKE 'a' AND `Candy`.`name` LIKE 'c'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('HardCandy.name LIKE' => 'a%', 'Candy.name LIKE' => '%c%')); + $expected = " WHERE `HardCandy`.`name` LIKE 'a%' AND `Candy`.`name` LIKE '%c%'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('HardCandy.name LIKE' => 'to be or%', 'Candy.name LIKE' => '%not to be%')); + $expected = " WHERE `HardCandy`.`name` LIKE 'to be or%' AND `Candy`.`name` LIKE '%not to be%'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('score BETWEEN ? AND ?' => array(90.1, 95.7))); + $expected = " WHERE `score` BETWEEN 90.100000 AND 95.700000"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Post.title' => 1.1)); + $expected = " WHERE `Post`.`title` = 1.100000"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Post.title' => 1.1), true, true, new Post()); + $expected = " WHERE `Post`.`title` = '1.1'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('SUM(Post.comments_count) >' => '500')); + $expected = " WHERE SUM(`Post`.`comments_count`) > '500'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('MAX(Post.rating) >' => '50')); + $expected = " WHERE MAX(`Post`.`rating`) > '50'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('title LIKE' => '%hello')); + $expected = " WHERE `title` LIKE '%hello'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Post.name' => 'mad(g)ik')); + $expected = " WHERE `Post`.`name` = 'mad(g)ik'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('score' => array(1, 2, 10))); + $expected = " WHERE score IN (1, 2, 10)"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('score' => array())); + $expected = " WHERE `score` IS NULL"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('score !=' => array())); + $expected = " WHERE `score` IS NOT NULL"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('score !=' => '20')); + $expected = " WHERE `score` != '20'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('score >' => '20')); + $expected = " WHERE `score` > '20'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('client_id >' => '20'), true, true, new TestModel()); + $expected = " WHERE `client_id` > 20"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('OR' => array( + array('User.user' => 'mariano'), + array('User.user' => 'nate') + ))); + + $expected = " WHERE ((`User`.`user` = 'mariano') OR (`User`.`user` = 'nate'))"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('or' => array( + 'score BETWEEN ? AND ?' => array('4', '5'), 'rating >' => '20' + ))); + $expected = " WHERE ((`score` BETWEEN '4' AND '5') OR (`rating` > '20'))"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('or' => array( + 'score BETWEEN ? AND ?' => array('4', '5'), array('score >' => '20') + ))); + $expected = " WHERE ((`score` BETWEEN '4' AND '5') OR (`score` > '20'))"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('and' => array( + 'score BETWEEN ? AND ?' => array('4', '5'), array('score >' => '20') + ))); + $expected = " WHERE ((`score` BETWEEN '4' AND '5') AND (`score` > '20'))"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array( + 'published' => 1, 'or' => array('score >' => '2', array('score >' => '20')) + )); + $expected = " WHERE `published` = 1 AND ((`score` > '2') OR (`score` > '20'))"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array(array('Project.removed' => false))); + $expected = " WHERE `Project`.`removed` = 0"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array(array('Project.removed' => true))); + $expected = " WHERE `Project`.`removed` = 1"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array(array('Project.removed' => null))); + $expected = " WHERE `Project`.`removed` IS NULL"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array(array('Project.removed !=' => null))); + $expected = " WHERE `Project`.`removed` IS NOT NULL"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('(Usergroup.permissions) & 4' => 4)); + $expected = " WHERE (`Usergroup`.`permissions`) & 4 = 4"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('((Usergroup.permissions) & 4)' => 4)); + $expected = " WHERE ((`Usergroup`.`permissions`) & 4) = 4"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Post.modified >=' => 'DATE_SUB(NOW(), INTERVAL 7 DAY)')); + $expected = " WHERE `Post`.`modified` >= 'DATE_SUB(NOW(), INTERVAL 7 DAY)'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Post.modified >= DATE_SUB(NOW(), INTERVAL 7 DAY)')); + $expected = " WHERE `Post`.`modified` >= DATE_SUB(NOW(), INTERVAL 7 DAY)"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array( + 'NOT' => array('Course.id' => null, 'Course.vet' => 'N', 'level_of_education_id' => array(912,999)), + 'Enrollment.yearcompleted >' => '0') + ); + $this->assertPattern('/^\s*WHERE\s+\(NOT\s+\(`Course`\.`id` IS NULL\)\s+AND NOT\s+\(`Course`\.`vet`\s+=\s+\'N\'\)\s+AND NOT\s+\(level_of_education_id IN \(912, 999\)\)\)\s+AND\s+`Enrollment`\.`yearcompleted`\s+>\s+\'0\'\s*$/', $result); + + $result = $this->testDb->conditions(array('id <>' => '8')); + $this->assertPattern('/^\s*WHERE\s+`id`\s+<>\s+\'8\'\s*$/', $result); + + $result = $this->testDb->conditions(array('TestModel.field =' => 'gribe$@()lu')); + $expected = " WHERE `TestModel`.`field` = 'gribe$@()lu'"; + $this->assertEqual($result, $expected); + + $conditions['NOT'] = array('Listing.expiration BETWEEN ? AND ?' => array("1", "100")); + $conditions[0]['OR'] = array( + "Listing.title LIKE" => "%term%", + "Listing.description LIKE" => "%term%" + ); + $conditions[1]['OR'] = array( + "Listing.title LIKE" => "%term_2%", + "Listing.description LIKE" => "%term_2%" + ); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE NOT (`Listing`.`expiration` BETWEEN '1' AND '100') AND" . + " ((`Listing`.`title` LIKE '%term%') OR (`Listing`.`description` LIKE '%term%')) AND" . + " ((`Listing`.`title` LIKE '%term_2%') OR (`Listing`.`description` LIKE '%term_2%'))"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('MD5(CONCAT(Reg.email,Reg.id))' => 'blah')); + $expected = " WHERE MD5(CONCAT(`Reg`.`email`,`Reg`.`id`)) = 'blah'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array( + 'MD5(CONCAT(Reg.email,Reg.id))' => array('blah', 'blahblah') + )); + $expected = " WHERE MD5(CONCAT(`Reg`.`email`,`Reg`.`id`)) IN ('blah', 'blahblah')"; + $this->assertEqual($result, $expected); + + $conditions = array('id' => array(2, 5, 6, 9, 12, 45, 78, 43, 76)); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE id IN (2, 5, 6, 9, 12, 45, 78, 43, 76)"; + $this->assertEqual($result, $expected); + + $conditions = array('title' => 'user(s)'); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE `title` = 'user(s)'"; + $this->assertEqual($result, $expected); + + $conditions = array('title' => 'user(s) data'); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE `title` = 'user(s) data'"; + $this->assertEqual($result, $expected); + + $conditions = array('title' => 'user(s,arg) data'); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE `title` = 'user(s,arg) data'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array("Book.book_name" => 'Java(TM)')); + $expected = " WHERE `Book`.`book_name` = 'Java(TM)'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array("Book.book_name" => 'Java(TM) ')); + $expected = " WHERE `Book`.`book_name` = 'Java(TM) '"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array("Book.id" => 0)); + $expected = " WHERE `Book`.`id` = 0"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array("Book.id" => NULL)); + $expected = " WHERE `Book`.`id` IS NULL"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Listing.beds >=' => 0)); + $expected = " WHERE `Listing`.`beds` >= 0"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array( + 'ASCII(SUBSTRING(keyword, 1, 1)) BETWEEN ? AND ?' => array(65, 90) + )); + $expected = ' WHERE ASCII(SUBSTRING(keyword, 1, 1)) BETWEEN 65 AND 90'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('or' => array( + '? BETWEEN Model.field1 AND Model.field2' => '2009-03-04' + ))); + $expected = " WHERE '2009-03-04' BETWEEN Model.field1 AND Model.field2"; + $this->assertEqual($result, $expected); + } + +/** + * testArrayConditionsParsingComplexKeys method + * + * @access public + * @return void + */ + function testArrayConditionsParsingComplexKeys() { + $result = $this->testDb->conditions(array( + 'CAST(Book.created AS DATE)' => '2008-08-02' + )); + $expected = " WHERE CAST(`Book`.`created` AS DATE) = '2008-08-02'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array( + 'CAST(Book.created AS DATE) <=' => '2008-08-02' + )); + $expected = " WHERE CAST(`Book`.`created` AS DATE) <= '2008-08-02'"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array( + '(Stats.clicks * 100) / Stats.views >' => 50 + )); + $expected = " WHERE (`Stats`.`clicks` * 100) / `Stats`.`views` > 50"; + $this->assertEqual($result, $expected); + } + +/** + * testMixedConditionsParsing method + * + * @access public + * @return void + */ + function testMixedConditionsParsing() { + $conditions[] = 'User.first_name = \'Firstname\''; + $conditions[] = array('User.last_name' => 'Lastname'); + $result = $this->testDb->conditions($conditions); + $expected = " WHERE `User`.`first_name` = 'Firstname' AND `User`.`last_name` = 'Lastname'"; + $this->assertEqual($result, $expected); + + $conditions = array( + 'Thread.project_id' => 5, + 'Thread.buyer_id' => 14, + '1=1 GROUP BY Thread.project_id' + ); + $result = $this->testDb->conditions($conditions); + $this->assertPattern('/^\s*WHERE\s+`Thread`.`project_id`\s*=\s*5\s+AND\s+`Thread`.`buyer_id`\s*=\s*14\s+AND\s+1\s*=\s*1\s+GROUP BY `Thread`.`project_id`$/', $result); + } + +/** + * testConditionsOptionalArguments method + * + * @access public + * @return void + */ + function testConditionsOptionalArguments() { + $result = $this->testDb->conditions( array('Member.name' => 'Mariano'), true, false); + $this->assertPattern('/^\s*`Member`.`name`\s*=\s*\'Mariano\'\s*$/', $result); + + $result = $this->testDb->conditions( array(), true, false); + $this->assertPattern('/^\s*1\s*=\s*1\s*$/', $result); + } + +/** + * testConditionsWithModel + * + * @access public + * @return void + */ + function testConditionsWithModel() { + $this->Model =& new Article2(); + + $result = $this->testDb->conditions(array('Article2.viewed >=' => 0), true, true, $this->Model); + $expected = " WHERE `Article2`.`viewed` >= 0"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Article2.viewed >=' => '0'), true, true, $this->Model); + $expected = " WHERE `Article2`.`viewed` >= 0"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Article2.viewed >=' => '1'), true, true, $this->Model); + $expected = " WHERE `Article2`.`viewed` >= 1"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Article2.rate_sum BETWEEN ? AND ?' => array(0, 10)), true, true, $this->Model); + $expected = " WHERE `Article2`.`rate_sum` BETWEEN 0 AND 10"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Article2.rate_sum BETWEEN ? AND ?' => array('0', '10')), true, true, $this->Model); + $expected = " WHERE `Article2`.`rate_sum` BETWEEN 0 AND 10"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->conditions(array('Article2.rate_sum BETWEEN ? AND ?' => array('1', '10')), true, true, $this->Model); + $expected = " WHERE `Article2`.`rate_sum` BETWEEN 1 AND 10"; + $this->assertEqual($result, $expected); + } + +/** + * testFieldParsing method + * + * @access public + * @return void + */ + function testFieldParsing() { + $result = $this->testDb->fields($this->Model, 'Vendor', "Vendor.id, COUNT(Model.vendor_id) AS `Vendor`.`count`"); + $expected = array('`Vendor`.`id`', 'COUNT(`Model`.`vendor_id`) AS `Vendor`.`count`'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, 'Vendor', "`Vendor`.`id`, COUNT(`Model`.`vendor_id`) AS `Vendor`.`count`"); + $expected = array('`Vendor`.`id`', 'COUNT(`Model`.`vendor_id`) AS `Vendor`.`count`'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, 'Post', "CONCAT(REPEAT(' ', COUNT(Parent.name) - 1), Node.name) AS name, Node.created"); + $expected = array("CONCAT(REPEAT(' ', COUNT(`Parent`.`name`) - 1), Node.name) AS name", "`Node`.`created`"); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, 'round( (3.55441 * fooField), 3 ) AS test'); + $this->assertEqual($result, array('round( (3.55441 * fooField), 3 ) AS test')); + + $result = $this->testDb->fields($this->Model, null, 'ROUND(`Rating`.`rate_total` / `Rating`.`rate_count`,2) AS rating'); + $this->assertEqual($result, array('ROUND(`Rating`.`rate_total` / `Rating`.`rate_count`,2) AS rating')); + + $result = $this->testDb->fields($this->Model, null, 'ROUND(Rating.rate_total / Rating.rate_count,2) AS rating'); + $this->assertEqual($result, array('ROUND(Rating.rate_total / Rating.rate_count,2) AS rating')); + + $result = $this->testDb->fields($this->Model, 'Post', "Node.created, CONCAT(REPEAT(' ', COUNT(Parent.name) - 1), Node.name) AS name"); + $expected = array("`Node`.`created`", "CONCAT(REPEAT(' ', COUNT(`Parent`.`name`) - 1), Node.name) AS name"); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, 'Post', "2.2,COUNT(*), SUM(Something.else) as sum, Node.created, CONCAT(REPEAT(' ', COUNT(Parent.name) - 1), Node.name) AS name,Post.title,Post.1,1.1"); + $expected = array( + '2.2', 'COUNT(*)', 'SUM(`Something`.`else`) as sum', '`Node`.`created`', + "CONCAT(REPEAT(' ', COUNT(`Parent`.`name`) - 1), Node.name) AS name", '`Post`.`title`', '`Post`.`1`', '1.1' + ); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, "(`Provider`.`star_total` / `Provider`.`total_ratings`) as `rating`"); + $expected = array("(`Provider`.`star_total` / `Provider`.`total_ratings`) as `rating`"); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, 'Post'); + $expected = array( + '`Post`.`id`', '`Post`.`client_id`', '`Post`.`name`', '`Post`.`login`', + '`Post`.`passwd`', '`Post`.`addr_1`', '`Post`.`addr_2`', '`Post`.`zip_code`', + '`Post`.`city`', '`Post`.`country`', '`Post`.`phone`', '`Post`.`fax`', + '`Post`.`url`', '`Post`.`email`', '`Post`.`comments`', '`Post`.`last_login`', + '`Post`.`created`', '`Post`.`updated`' + ); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, 'Other'); + $expected = array( + '`Other`.`id`', '`Other`.`client_id`', '`Other`.`name`', '`Other`.`login`', + '`Other`.`passwd`', '`Other`.`addr_1`', '`Other`.`addr_2`', '`Other`.`zip_code`', + '`Other`.`city`', '`Other`.`country`', '`Other`.`phone`', '`Other`.`fax`', + '`Other`.`url`', '`Other`.`email`', '`Other`.`comments`', '`Other`.`last_login`', + '`Other`.`created`', '`Other`.`updated`' + ); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, array(), false); + $expected = array('id', 'client_id', 'name', 'login', 'passwd', 'addr_1', 'addr_2', 'zip_code', 'city', 'country', 'phone', 'fax', 'url', 'email', 'comments', 'last_login', 'created', 'updated'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, 'COUNT(*)'); + $expected = array('COUNT(*)'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, 'SUM(Thread.unread_buyer) AS ' . $this->testDb->name('sum_unread_buyer')); + $expected = array('SUM(`Thread`.`unread_buyer`) AS `sum_unread_buyer`'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, 'name, count(*)'); + $expected = array('`TestModel`.`name`', 'count(*)'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, 'count(*), name'); + $expected = array('count(*)', '`TestModel`.`name`'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields( + $this->Model, null, 'field1, field2, field3, count(*), name' + ); + $expected = array( + '`TestModel`.`field1`', '`TestModel`.`field2`', + '`TestModel`.`field3`', 'count(*)', '`TestModel`.`name`' + ); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, array('dayofyear(now())')); + $expected = array('dayofyear(now())'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, array('MAX(Model.field) As Max')); + $expected = array('MAX(`Model`.`field`) As Max'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, array('Model.field AS AnotherName')); + $expected = array('`Model`.`field` AS `AnotherName`'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, array('field AS AnotherName')); + $expected = array('`field` AS `AnotherName`'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, null, array( + 'TestModel.field AS AnotherName' + )); + $expected = array('`TestModel`.`field` AS `AnotherName`'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->fields($this->Model, 'Foo', array( + 'id', 'title', '(user_count + discussion_count + post_count) AS score' + )); + $expected = array( + '`Foo`.`id`', + '`Foo`.`title`', + '(user_count + discussion_count + post_count) AS score' + ); + $this->assertEqual($result, $expected); + } + +/** + * test that fields() will accept objects made from DboSource::expression + * + * @return void + */ + function testFieldsWithExpression() { + $expression = $this->testDb->expression("CASE Sample.id WHEN 1 THEN 'Id One' ELSE 'Other Id' END AS case_col"); + $result = $this->testDb->fields($this->Model, null, array("id", $expression)); + $expected = array( + '`TestModel`.`id`', + "CASE Sample.id WHEN 1 THEN 'Id One' ELSE 'Other Id' END AS case_col" + ); + $this->assertEqual($result, $expected); + } + +/** + * test that order() will accept objects made from DboSource::expression + * + * @return void + */ + function testOrderWithExpression() { + $expression = $this->testDb->expression("CASE Sample.id WHEN 1 THEN 'Id One' ELSE 'Other Id' END AS case_col"); + $result = $this->testDb->order($expression); + $expected = " ORDER BY CASE Sample.id WHEN 1 THEN 'Id One' ELSE 'Other Id' END AS case_col"; + $this->assertEqual($result, $expected); + } + +/** + * testMergeAssociations method + * + * @access public + * @return void + */ + function testMergeAssociations() { + $data = array('Article2' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', + 'body' => 'First Article Body', 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + )); + $merge = array('Topic' => array(array( + 'id' => '1', 'topic' => 'Topic', 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ))); + $expected = array( + 'Article2' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', + 'body' => 'First Article Body', 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Topic' => array( + 'id' => '1', 'topic' => 'Topic', 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ) + ); + $this->testDb->__mergeAssociation($data, $merge, 'Topic', 'hasOne'); + $this->assertEqual($data, $expected); + + $data = array('Article2' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', + 'body' => 'First Article Body', 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + )); + $merge = array('User2' => array(array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ))); + + $expected = array( + 'Article2' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', + 'body' => 'First Article Body', 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ); + $this->testDb->__mergeAssociation($data, $merge, 'User2', 'belongsTo'); + $this->assertEqual($data, $expected); + + $data = array( + 'Article2' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ) + ); + $merge = array(array('Comment' => false)); + $expected = array( + 'Article2' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Comment' => array() + ); + $this->testDb->__mergeAssociation($data, $merge, 'Comment', 'hasMany'); + $this->assertEqual($data, $expected); + + $data = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ) + ); + $merge = array( + array( + 'Comment' => array( + 'id' => '1', 'comment' => 'Comment 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'Comment' => array( + 'id' => '2', 'comment' => 'Comment 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ); + $expected = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Comment' => array( + array( + 'id' => '1', 'comment' => 'Comment 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + array( + 'id' => '2', 'comment' => 'Comment 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ); + $this->testDb->__mergeAssociation($data, $merge, 'Comment', 'hasMany'); + $this->assertEqual($data, $expected); + + $data = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ) + ); + $merge = array( + array( + 'Comment' => array( + 'id' => '1', 'comment' => 'Comment 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'Comment' => array( + 'id' => '2', 'comment' => 'Comment 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ); + $expected = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Comment' => array( + array( + 'id' => '1', 'comment' => 'Comment 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'id' => '2', 'comment' => 'Comment 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ) + ); + $this->testDb->__mergeAssociation($data, $merge, 'Comment', 'hasMany'); + $this->assertEqual($data, $expected); + + $data = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ) + ); + $merge = array( + array( + 'Comment' => array( + 'id' => '1', 'comment' => 'Comment 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Tag' => array( + array('id' => 1, 'tag' => 'Tag 1'), + array('id' => 2, 'tag' => 'Tag 2') + ) + ), + array( + 'Comment' => array( + 'id' => '2', 'comment' => 'Comment 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Tag' => array() + ) + ); + $expected = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Comment' => array( + array( + 'id' => '1', 'comment' => 'Comment 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Tag' => array( + array('id' => 1, 'tag' => 'Tag 1'), + array('id' => 2, 'tag' => 'Tag 2') + ) + ), + array( + 'id' => '2', 'comment' => 'Comment 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', + 'User2' => array( + 'id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'Tag' => array() + ) + ) + ); + $this->testDb->__mergeAssociation($data, $merge, 'Comment', 'hasMany'); + $this->assertEqual($data, $expected); + + $data = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ) + ); + $merge = array( + array( + 'Tag' => array( + 'id' => '1', 'tag' => 'Tag 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'Tag' => array( + 'id' => '2', 'tag' => 'Tag 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'Tag' => array( + 'id' => '3', 'tag' => 'Tag 3', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ); + $expected = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Tag' => array( + array( + 'id' => '1', 'tag' => 'Tag 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + array( + 'id' => '2', 'tag' => 'Tag 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + array( + 'id' => '3', 'tag' => 'Tag 3', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ); + $this->testDb->__mergeAssociation($data, $merge, 'Tag', 'hasAndBelongsToMany'); + $this->assertEqual($data, $expected); + + $data = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ) + ); + $merge = array( + array( + 'Tag' => array( + 'id' => '1', 'tag' => 'Tag 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'Tag' => array( + 'id' => '2', 'tag' => 'Tag 2', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ), + array( + 'Tag' => array( + 'id' => '3', 'tag' => 'Tag 3', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ) + ) + ); + $expected = array( + 'Article' => array( + 'id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31' + ), + 'Tag' => array('id' => '1', 'tag' => 'Tag 1', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31') + ); + $this->testDb->__mergeAssociation($data, $merge, 'Tag', 'hasOne'); + $this->assertEqual($data, $expected); + } + +/** + * testRenderStatement method + * + * @access public + * @return void + */ + function testRenderStatement() { + $result = $this->testDb->renderStatement('select', array( + 'fields' => 'id', 'table' => 'table', 'conditions' => 'WHERE 1=1', + 'alias' => '', 'joins' => '', 'order' => '', 'limit' => '', 'group' => '' + )); + $this->assertPattern('/^\s*SELECT\s+id\s+FROM\s+table\s+WHERE\s+1=1\s*$/', $result); + + $result = $this->testDb->renderStatement('update', array('fields' => 'value=2', 'table' => 'table', 'conditions' => 'WHERE 1=1', 'alias' => '')); + $this->assertPattern('/^\s*UPDATE\s+table\s+SET\s+value=2\s+WHERE\s+1=1\s*$/', $result); + + $result = $this->testDb->renderStatement('update', array('fields' => 'value=2', 'table' => 'table', 'conditions' => 'WHERE 1=1', 'alias' => 'alias', 'joins' => '')); + $this->assertPattern('/^\s*UPDATE\s+table\s+AS\s+alias\s+SET\s+value=2\s+WHERE\s+1=1\s*$/', $result); + + $result = $this->testDb->renderStatement('delete', array('fields' => 'value=2', 'table' => 'table', 'conditions' => 'WHERE 1=1', 'alias' => '')); + $this->assertPattern('/^\s*DELETE\s+FROM\s+table\s+WHERE\s+1=1\s*$/', $result); + + $result = $this->testDb->renderStatement('delete', array('fields' => 'value=2', 'table' => 'table', 'conditions' => 'WHERE 1=1', 'alias' => 'alias', 'joins' => '')); + $this->assertPattern('/^\s*DELETE\s+alias\s+FROM\s+table\s+AS\s+alias\s+WHERE\s+1=1\s*$/', $result); + } + +/** + * testStatements method + * + * @access public + * @return void + */ + function testStatements() { + $Article =& ClassRegistry::init('Article'); + + $result = $this->testDb->update($Article, array('field1'), array('value1')); + $this->assertFalse($result); + $result = $this->testDb->getLastQuery(); + $this->assertPattern('/^\s*UPDATE\s+' . $this->testDb->fullTableName('articles') . '\s+SET\s+`field1`\s*=\s*\'value1\'\s+WHERE\s+1 = 1\s*$/', $result); + + $result = $this->testDb->update($Article, array('field1'), array('2'), '2=2'); + $this->assertFalse($result); + $result = $this->testDb->getLastQuery(); + $this->assertPattern('/^\s*UPDATE\s+' . $this->testDb->fullTableName('articles') . ' AS `Article`\s+LEFT JOIN\s+' . $this->testDb->fullTableName('users') . ' AS `User` ON \(`Article`.`user_id` = `User`.`id`\)\s+SET\s+`Article`\.`field1`\s*=\s*2\s+WHERE\s+2\s*=\s*2\s*$/', $result); + + $result = $this->testDb->delete($Article); + $this->assertTrue($result); + $result = $this->testDb->getLastQuery(); + $this->assertPattern('/^\s*DELETE\s+FROM\s+' . $this->testDb->fullTableName('articles') . '\s+WHERE\s+1 = 1\s*$/', $result); + + $result = $this->testDb->delete($Article, true); + $this->assertTrue($result); + $result = $this->testDb->getLastQuery(); + $this->assertPattern('/^\s*DELETE\s+`Article`\s+FROM\s+' . $this->testDb->fullTableName('articles') . '\s+AS `Article`\s+LEFT JOIN\s+' . $this->testDb->fullTableName('users') . ' AS `User` ON \(`Article`.`user_id` = `User`.`id`\)\s+WHERE\s+1\s*=\s*1\s*$/', $result); + + $result = $this->testDb->delete($Article, '2=2'); + $this->assertTrue($result); + $result = $this->testDb->getLastQuery(); + $this->assertPattern('/^\s*DELETE\s+`Article`\s+FROM\s+' . $this->testDb->fullTableName('articles') . '\s+AS `Article`\s+LEFT JOIN\s+' . $this->testDb->fullTableName('users') . ' AS `User` ON \(`Article`.`user_id` = `User`.`id`\)\s+WHERE\s+2\s*=\s*2\s*$/', $result); + + $result = $this->testDb->hasAny($Article, '1=2'); + $this->assertFalse($result); + + $result = $this->testDb->insertMulti('articles', array('field'), array('(1)', '(2)')); + $this->assertFalse($result); + $result = $this->testDb->getLastQuery(); + $this->assertPattern('/^\s*INSERT INTO\s+' . $this->testDb->fullTableName('articles') . '\s+\(`field`\)\s+VALUES\s+\(1\),\s*\(2\)\s*$/', $result); + } + +/** + * testSchema method + * + * @access public + * @return void + */ + function testSchema() { + $Schema =& new CakeSchema(); + $Schema->tables = array('table' => array(), 'anotherTable' => array()); + + $this->expectError(); + $result = $this->testDb->dropSchema(null); + $this->assertTrue($result === null); + + $result = $this->testDb->dropSchema($Schema, 'non_existing'); + $this->assertTrue(empty($result)); + + $result = $this->testDb->dropSchema($Schema, 'table'); + $this->assertPattern('/^\s*DROP TABLE IF EXISTS\s+' . $this->testDb->fullTableName('table') . ';\s*$/s', $result); + } + +/** + * testMagicMethodQuerying method + * + * @access public + * @return void + */ + function testMagicMethodQuerying() { + $result = $this->testDb->query('findByFieldName', array('value'), $this->Model); + $expected = array('first', array( + 'conditions' => array('TestModel.field_name' => 'value'), + 'fields' => null, 'order' => null, 'recursive' => null + )); + $this->assertEqual($result, $expected); + + $result = $this->testDb->query('findByFindBy', array('value'), $this->Model); + $expected = array('first', array( + 'conditions' => array('TestModel.find_by' => 'value'), + 'fields' => null, 'order' => null, 'recursive' => null + )); + $this->assertEqual($result, $expected); + + $result = $this->testDb->query('findAllByFieldName', array('value'), $this->Model); + $expected = array('all', array( + 'conditions' => array('TestModel.field_name' => 'value'), + 'fields' => null, 'order' => null, 'limit' => null, + 'page' => null, 'recursive' => null + )); + $this->assertEqual($result, $expected); + + $result = $this->testDb->query('findAllById', array('a'), $this->Model); + $expected = array('all', array( + 'conditions' => array('TestModel.id' => 'a'), + 'fields' => null, 'order' => null, 'limit' => null, + 'page' => null, 'recursive' => null + )); + $this->assertEqual($result, $expected); + + $result = $this->testDb->query('findByFieldName', array(array('value1', 'value2', 'value3')), $this->Model); + $expected = array('first', array( + 'conditions' => array('TestModel.field_name' => array('value1', 'value2', 'value3')), + 'fields' => null, 'order' => null, 'recursive' => null + )); + $this->assertEqual($result, $expected); + + $result = $this->testDb->query('findByFieldName', array(null), $this->Model); + $expected = array('first', array( + 'conditions' => array('TestModel.field_name' => null), + 'fields' => null, 'order' => null, 'recursive' => null + )); + $this->assertEqual($result, $expected); + + $result = $this->testDb->query('findByFieldName', array('= a'), $this->Model); + $expected = array('first', array( + 'conditions' => array('TestModel.field_name' => '= a'), + 'fields' => null, 'order' => null, 'recursive' => null + )); + $this->assertEqual($result, $expected); + + $result = $this->testDb->query('findByFieldName', array(), $this->Model); + $expected = false; + $this->assertEqual($result, $expected); + + $result = $this->testDb->query('directCall', array(), $this->Model); + $this->assertFalse($result); + + $result = $this->testDb->query('directCall', true, $this->Model); + $this->assertFalse($result); + + $result = $this->testDb->query('directCall', false, $this->Model); + $this->assertFalse($result); + } + +/** + * testOrderParsing method + * + * @access public + * @return void + */ + function testOrderParsing() { + $result = $this->testDb->order("ADDTIME(Event.time_begin, '-06:00:00') ASC"); + $expected = " ORDER BY ADDTIME(`Event`.`time_begin`, '-06:00:00') ASC"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->order("title, id"); + $this->assertPattern('/^\s*ORDER BY\s+`title`\s+ASC,\s+`id`\s+ASC\s*$/', $result); + + $result = $this->testDb->order("title desc, id desc"); + $this->assertPattern('/^\s*ORDER BY\s+`title`\s+desc,\s+`id`\s+desc\s*$/', $result); + + $result = $this->testDb->order(array("title desc, id desc")); + $this->assertPattern('/^\s*ORDER BY\s+`title`\s+desc,\s+`id`\s+desc\s*$/', $result); + + $result = $this->testDb->order(array("title", "id")); + $this->assertPattern('/^\s*ORDER BY\s+`title`\s+ASC,\s+`id`\s+ASC\s*$/', $result); + + $result = $this->testDb->order(array(array('title'), array('id'))); + $this->assertPattern('/^\s*ORDER BY\s+`title`\s+ASC,\s+`id`\s+ASC\s*$/', $result); + + $result = $this->testDb->order(array("Post.title" => 'asc', "Post.id" => 'desc')); + $this->assertPattern('/^\s*ORDER BY\s+`Post`.`title`\s+asc,\s+`Post`.`id`\s+desc\s*$/', $result); + + $result = $this->testDb->order(array(array("Post.title" => 'asc', "Post.id" => 'desc'))); + $this->assertPattern('/^\s*ORDER BY\s+`Post`.`title`\s+asc,\s+`Post`.`id`\s+desc\s*$/', $result); + + $result = $this->testDb->order(array("title")); + $this->assertPattern('/^\s*ORDER BY\s+`title`\s+ASC\s*$/', $result); + + $result = $this->testDb->order(array(array("title"))); + $this->assertPattern('/^\s*ORDER BY\s+`title`\s+ASC\s*$/', $result); + + $result = $this->testDb->order("Dealer.id = 7 desc, Dealer.id = 3 desc, Dealer.title asc"); + $expected = " ORDER BY `Dealer`.`id` = 7 desc, `Dealer`.`id` = 3 desc, `Dealer`.`title` asc"; + $this->assertEqual($result, $expected); + + $result = $this->testDb->order(array("Page.name" => "='test' DESC")); + $this->assertPattern("/^\s*ORDER BY\s+`Page`\.`name`\s*='test'\s+DESC\s*$/", $result); + + $result = $this->testDb->order("Page.name = 'view' DESC"); + $this->assertPattern("/^\s*ORDER BY\s+`Page`\.`name`\s*=\s*'view'\s+DESC\s*$/", $result); + + $result = $this->testDb->order("(Post.views)"); + $this->assertPattern("/^\s*ORDER BY\s+\(`Post`\.`views`\)\s+ASC\s*$/", $result); + + $result = $this->testDb->order("(Post.views)*Post.views"); + $this->assertPattern("/^\s*ORDER BY\s+\(`Post`\.`views`\)\*`Post`\.`views`\s+ASC\s*$/", $result); + + $result = $this->testDb->order("(Post.views) * Post.views"); + $this->assertPattern("/^\s*ORDER BY\s+\(`Post`\.`views`\) \* `Post`\.`views`\s+ASC\s*$/", $result); + + $result = $this->testDb->order("(Model.field1 + Model.field2) * Model.field3"); + $this->assertPattern("/^\s*ORDER BY\s+\(`Model`\.`field1` \+ `Model`\.`field2`\) \* `Model`\.`field3`\s+ASC\s*$/", $result); + + $result = $this->testDb->order("Model.name+0 ASC"); + $this->assertPattern("/^\s*ORDER BY\s+`Model`\.`name`\+0\s+ASC\s*$/", $result); + + $result = $this->testDb->order("Anuncio.destaque & 2 DESC"); + $expected = ' ORDER BY `Anuncio`.`destaque` & 2 DESC'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->order("3963.191 * id"); + $expected = ' ORDER BY 3963.191 * id ASC'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->order(array('Property.sale_price IS NULL')); + $expected = ' ORDER BY `Property`.`sale_price` IS NULL ASC'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->order(array('Export.column-name' => 'ASC')); + $expected = ' ORDER BY `Export`.`column-name` ASC'; + $this->assertEqual($result, $expected, 'Columns with -s are not working with order()'); + } + +/** + * testComplexSortExpression method + * + * @return void + * @access public + */ + function testComplexSortExpression() { + $result = $this->testDb->order(array('(Model.field > 100) DESC', 'Model.field ASC')); + $this->assertPattern("/^\s*ORDER BY\s+\(`Model`\.`field`\s+>\s+100\)\s+DESC,\s+`Model`\.`field`\s+ASC\s*$/", $result); + } + +/** + * testCalculations method + * + * @access public + * @return void + */ + function testCalculations() { + $result = $this->testDb->calculate($this->Model, 'count'); + $this->assertEqual($result, 'COUNT(*) AS `count`'); + + $result = $this->testDb->calculate($this->Model, 'count', array('id')); + $this->assertEqual($result, 'COUNT(`id`) AS `count`'); + + $result = $this->testDb->calculate( + $this->Model, + 'count', + array($this->testDb->expression('DISTINCT id')) + ); + $this->assertEqual($result, 'COUNT(DISTINCT id) AS `count`'); + + $result = $this->testDb->calculate($this->Model, 'count', array('id', 'id_count')); + $this->assertEqual($result, 'COUNT(`id`) AS `id_count`'); + + $result = $this->testDb->calculate($this->Model, 'count', array('Model.id', 'id_count')); + $this->assertEqual($result, 'COUNT(`Model`.`id`) AS `id_count`'); + + $result = $this->testDb->calculate($this->Model, 'max', array('id')); + $this->assertEqual($result, 'MAX(`id`) AS `id`'); + + $result = $this->testDb->calculate($this->Model, 'max', array('Model.id', 'id')); + $this->assertEqual($result, 'MAX(`Model`.`id`) AS `id`'); + + $result = $this->testDb->calculate($this->Model, 'max', array('`Model`.`id`', 'id')); + $this->assertEqual($result, 'MAX(`Model`.`id`) AS `id`'); + + $result = $this->testDb->calculate($this->Model, 'min', array('`Model`.`id`', 'id')); + $this->assertEqual($result, 'MIN(`Model`.`id`) AS `id`'); + + $result = $this->testDb->calculate($this->Model, 'min', 'left'); + $this->assertEqual($result, 'MIN(`left`) AS `left`'); + } + +/** + * testLength method + * + * @access public + * @return void + */ + function testLength() { + $result = $this->testDb->length('varchar(255)'); + $expected = 255; + $this->assertIdentical($result, $expected); + + $result = $this->testDb->length('int(11)'); + $expected = 11; + $this->assertIdentical($result, $expected); + + $result = $this->testDb->length('float(5,3)'); + $expected = '5,3'; + $this->assertIdentical($result, $expected); + + $result = $this->testDb->length('decimal(5,2)'); + $expected = '5,2'; + $this->assertIdentical($result, $expected); + + $result = $this->testDb->length("enum('test','me','now')"); + $expected = 4; + $this->assertIdentical($result, $expected); + + $result = $this->testDb->length("set('a','b','cd')"); + $expected = 2; + $this->assertIdentical($result, $expected); + + $this->expectError(); + $result = $this->testDb->length(false); + $this->assertTrue($result === null); + + $result = $this->testDb->length('datetime'); + $expected = null; + $this->assertIdentical($result, $expected); + + $result = $this->testDb->length('text'); + $expected = null; + $this->assertIdentical($result, $expected); + } + +/** + * testBuildIndex method + * + * @access public + * @return void + */ + function testBuildIndex() { + $data = array( + 'PRIMARY' => array('column' => 'id') + ); + $result = $this->testDb->buildIndex($data); + $expected = array('PRIMARY KEY (`id`)'); + $this->assertIdentical($result, $expected); + + $data = array( + 'MyIndex' => array('column' => 'id', 'unique' => true) + ); + $result = $this->testDb->buildIndex($data); + $expected = array('UNIQUE KEY `MyIndex` (`id`)'); + $this->assertEqual($result, $expected); + + $data = array( + 'MyIndex' => array('column' => array('id', 'name'), 'unique' => true) + ); + $result = $this->testDb->buildIndex($data); + $expected = array('UNIQUE KEY `MyIndex` (`id`, `name`)'); + $this->assertEqual($result, $expected); + } + +/** + * testBuildColumn method + * + * @access public + * @return void + */ + function testBuildColumn() { + $this->expectError(); + $data = array( + 'name' => 'testName', + 'type' => 'varchar(255)', + 'default', + 'null' => true, + 'key' + ); + $this->testDb->buildColumn($data); + + $data = array( + 'name' => 'testName', + 'type' => 'string', + 'length' => 255, + 'default', + 'null' => true, + 'key' + ); + $result = $this->testDb->buildColumn($data); + $expected = '`testName` varchar(255) DEFAULT NULL'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'int_field', + 'type' => 'integer', + 'default' => '', + 'null' => false, + ); + $restore = $this->testDb->columns; + + $this->testDb->columns = array('integer' => array('name' => 'int', 'limit' => '11', 'formatter' => 'intval'), ); + $result = $this->testDb->buildColumn($data); + $expected = '`int_field` int(11) NOT NULL'; + $this->assertEqual($result, $expected); + + $this->testDb->fieldParameters['param'] = array( + 'value' => 'COLLATE', + 'quote' => false, + 'join' => ' ', + 'column' => 'Collate', + 'position' => 'beforeDefault', + 'options' => array('GOOD', 'OK') + ); + $data = array( + 'name' => 'int_field', + 'type' => 'integer', + 'default' => '', + 'null' => false, + 'param' => 'BAD' + ); + $result = $this->testDb->buildColumn($data); + $expected = '`int_field` int(11) NOT NULL'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'int_field', + 'type' => 'integer', + 'default' => '', + 'null' => false, + 'param' => 'GOOD' + ); + $result = $this->testDb->buildColumn($data); + $expected = '`int_field` int(11) COLLATE GOOD NOT NULL'; + $this->assertEqual($result, $expected); + + $this->testDb->columns = $restore; + + $data = array( + 'name' => 'created', + 'type' => 'timestamp', + 'default' => 'current_timestamp', + 'null' => false, + ); + $result = $this->db->buildColumn($data); + $expected = '`created` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'created', + 'type' => 'timestamp', + 'default' => 'CURRENT_TIMESTAMP', + 'null' => true, + ); + $result = $this->db->buildColumn($data); + $expected = '`created` timestamp DEFAULT CURRENT_TIMESTAMP'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'modified', + 'type' => 'timestamp', + 'null' => true, + ); + $result = $this->db->buildColumn($data); + $expected = '`modified` timestamp NULL'; + $this->assertEqual($result, $expected); + + $data = array( + 'name' => 'modified', + 'type' => 'timestamp', + 'default' => null, + 'null' => true, + ); + $result = $this->db->buildColumn($data); + $expected = '`modified` timestamp NULL'; + $this->assertEqual($result, $expected); + } + +/** + * test hasAny() + * + * @return void + */ + function testHasAny() { + $this->testDb->hasAny($this->Model, array()); + $expected = 'SELECT COUNT(`TestModel`.`id`) AS count FROM `test_models` AS `TestModel` WHERE 1 = 1'; + $this->assertEqual(end($this->testDb->simulated), $expected); + + $this->testDb->hasAny($this->Model, array('TestModel.name' => 'harry')); + $expected = "SELECT COUNT(`TestModel`.`id`) AS count FROM `test_models` AS `TestModel` WHERE `TestModel`.`name` = 'harry'"; + $this->assertEqual(end($this->testDb->simulated), $expected); + } + +/** + * testIntrospectType method + * + * @access public + * @return void + */ + function testIntrospectType() { + $this->assertEqual($this->testDb->introspectType(0), 'integer'); + $this->assertEqual($this->testDb->introspectType(2), 'integer'); + $this->assertEqual($this->testDb->introspectType('2'), 'string'); + $this->assertEqual($this->testDb->introspectType('2.2'), 'string'); + $this->assertEqual($this->testDb->introspectType(2.2), 'float'); + $this->assertEqual($this->testDb->introspectType('stringme'), 'string'); + $this->assertEqual($this->testDb->introspectType('0stringme'), 'string'); + + $data = array(2.2); + $this->assertEqual($this->testDb->introspectType($data), 'float'); + + $data = array('2.2'); + $this->assertEqual($this->testDb->introspectType($data), 'float'); + + $data = array(2); + $this->assertEqual($this->testDb->introspectType($data), 'integer'); + + $data = array('2'); + $this->assertEqual($this->testDb->introspectType($data), 'integer'); + + $data = array('string'); + $this->assertEqual($this->testDb->introspectType($data), 'string'); + + $data = array(2.2, '2.2'); + $this->assertEqual($this->testDb->introspectType($data), 'float'); + + $data = array(2, '2'); + $this->assertEqual($this->testDb->introspectType($data), 'integer'); + + $data = array('string one', 'string two'); + $this->assertEqual($this->testDb->introspectType($data), 'string'); + + $data = array('2.2', 3); + $this->assertEqual($this->testDb->introspectType($data), 'integer'); + + $data = array('2.2', '0stringme'); + $this->assertEqual($this->testDb->introspectType($data), 'string'); + + $data = array(2.2, 3); + $this->assertEqual($this->testDb->introspectType($data), 'integer'); + + $data = array(2.2, '0stringme'); + $this->assertEqual($this->testDb->introspectType($data), 'string'); + + $data = array(2, 'stringme'); + $this->assertEqual($this->testDb->introspectType($data), 'string'); + + $data = array(2, '2.2', 'stringgme'); + $this->assertEqual($this->testDb->introspectType($data), 'string'); + + $data = array(2, '2.2'); + $this->assertEqual($this->testDb->introspectType($data), 'integer'); + + $data = array(2, 2.2); + $this->assertEqual($this->testDb->introspectType($data), 'integer'); + + + // NULL + $result = $this->testDb->value(null, 'boolean'); + $this->assertEqual($result, 'NULL'); + + // EMPTY STRING + $result = $this->testDb->value('', 'boolean'); + $this->assertEqual($result, "NULL"); + + + // BOOLEAN + $result = $this->testDb->value('true', 'boolean'); + $this->assertEqual($result, 1); + + $result = $this->testDb->value('false', 'boolean'); + $this->assertEqual($result, 1); + + $result = $this->testDb->value(true, 'boolean'); + $this->assertEqual($result, 1); + + $result = $this->testDb->value(false, 'boolean'); + $this->assertEqual($result, 0); + + $result = $this->testDb->value(1, 'boolean'); + $this->assertEqual($result, 1); + + $result = $this->testDb->value(0, 'boolean'); + $this->assertEqual($result, 0); + + $result = $this->testDb->value('abc', 'boolean'); + $this->assertEqual($result, 1); + + $result = $this->testDb->value(1.234, 'boolean'); + $this->assertEqual($result, 1); + + $result = $this->testDb->value('1.234e05', 'boolean'); + $this->assertEqual($result, 1); + + // NUMBERS + $result = $this->testDb->value(123, 'integer'); + $this->assertEqual($result, 123); + + $result = $this->testDb->value('123', 'integer'); + $this->assertEqual($result, '123'); + + $result = $this->testDb->value('0123', 'integer'); + $this->assertEqual($result, "'0123'"); + + $result = $this->testDb->value('0x123ABC', 'integer'); + $this->assertEqual($result, "'0x123ABC'"); + + $result = $this->testDb->value('0x123', 'integer'); + $this->assertEqual($result, "'0x123'"); + + $result = $this->testDb->value(1.234, 'float'); + $this->assertEqual($result, 1.234); + + $result = $this->testDb->value('1.234', 'float'); + $this->assertEqual($result, '1.234'); + + $result = $this->testDb->value(' 1.234 ', 'float'); + $this->assertEqual($result, "' 1.234 '"); + + $result = $this->testDb->value('1.234e05', 'float'); + $this->assertEqual($result, "'1.234e05'"); + + $result = $this->testDb->value('1.234e+5', 'float'); + $this->assertEqual($result, "'1.234e+5'"); + + $result = $this->testDb->value('1,234', 'float'); + $this->assertEqual($result, "'1,234'"); + + $result = $this->testDb->value('FFF', 'integer'); + $this->assertEqual($result, "'FFF'"); + + $result = $this->testDb->value('abc', 'integer'); + $this->assertEqual($result, "'abc'"); + + // STRINGS + $result = $this->testDb->value('123', 'string'); + $this->assertEqual($result, "'123'"); + + $result = $this->testDb->value(123, 'string'); + $this->assertEqual($result, "'123'"); + + $result = $this->testDb->value(1.234, 'string'); + $this->assertEqual($result, "'1.234'"); + + $result = $this->testDb->value('abc', 'string'); + $this->assertEqual($result, "'abc'"); + + $result = $this->testDb->value(' abc ', 'string'); + $this->assertEqual($result, "' abc '"); + + $result = $this->testDb->value('a bc', 'string'); + $this->assertEqual($result, "'a bc'"); + } + +/** + * testValue method + * + * @access public + * @return void + */ + function testValue() { + $result = $this->testDb->value('{$__cakeForeignKey__$}'); + $this->assertEqual($result, '{$__cakeForeignKey__$}'); + + $result = $this->testDb->value(array('first', 2, 'third')); + $expected = array('\'first\'', 2, '\'third\''); + $this->assertEqual($result, $expected); + } + +/** + * testReconnect method + * + * @access public + * @return void + */ + function testReconnect() { + $this->testDb->reconnect(array('prefix' => 'foo')); + $this->assertTrue($this->testDb->connected); + $this->assertEqual($this->testDb->config['prefix'], 'foo'); + } + +/** + * testRealQueries method + * + * @access public + * @return void + */ + function testRealQueries() { + $this->loadFixtures('Apple', 'Article', 'User', 'Comment', 'Tag'); + + $Apple =& ClassRegistry::init('Apple'); + $Article =& ClassRegistry::init('Article'); + + $result = $this->db->rawQuery('SELECT color, name FROM ' . $this->db->fullTableName('apples')); + $this->assertTrue(!empty($result)); + + $result = $this->db->fetchRow($result); + $expected = array($this->db->fullTableName('apples', false) => array( + 'color' => 'Red 1', + 'name' => 'Red Apple 1' + )); + $this->assertEqual($result, $expected); + + $result = $this->db->fetchAll('SELECT name FROM ' . $this->testDb->fullTableName('apples') . ' ORDER BY id'); + $expected = array( + array($this->db->fullTableName('apples', false) => array('name' => 'Red Apple 1')), + array($this->db->fullTableName('apples', false) => array('name' => 'Bright Red Apple')), + array($this->db->fullTableName('apples', false) => array('name' => 'green blue')), + array($this->db->fullTableName('apples', false) => array('name' => 'Test Name')), + array($this->db->fullTableName('apples', false) => array('name' => 'Blue Green')), + array($this->db->fullTableName('apples', false) => array('name' => 'My new apple')), + array($this->db->fullTableName('apples', false) => array('name' => 'Some odd color')) + ); + $this->assertEqual($result, $expected); + + $result = $this->db->field($this->testDb->fullTableName('apples', false), 'SELECT color, name FROM ' . $this->testDb->fullTableName('apples') . ' ORDER BY id'); + $expected = array( + 'color' => 'Red 1', + 'name' => 'Red Apple 1' + ); + $this->assertEqual($result, $expected); + + $Apple->unbindModel(array(), false); + $result = $this->db->read($Apple, array( + 'fields' => array($Apple->escapeField('name')), + 'conditions' => null, + 'recursive' => -1 + )); + $expected = array( + array('Apple' => array('name' => 'Red Apple 1')), + array('Apple' => array('name' => 'Bright Red Apple')), + array('Apple' => array('name' => 'green blue')), + array('Apple' => array('name' => 'Test Name')), + array('Apple' => array('name' => 'Blue Green')), + array('Apple' => array('name' => 'My new apple')), + array('Apple' => array('name' => 'Some odd color')) + ); + $this->assertEqual($result, $expected); + + $result = $this->db->read($Article, array( + 'fields' => array('id', 'user_id', 'title'), + 'conditions' => null, + 'recursive' => 1 + )); + + $this->assertTrue(Set::matches('/Article[id=1]', $result)); + $this->assertTrue(Set::matches('/Comment[id=1]', $result)); + $this->assertTrue(Set::matches('/Comment[id=2]', $result)); + $this->assertFalse(Set::matches('/Comment[id=10]', $result)); + } + +/** + * testName method + * + * @access public + * @return void + */ + function testName() { + $result = $this->testDb->name('name'); + $expected = '`name`'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name(array('name', 'Model.*')); + $expected = array('`name`', '`Model`.*'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('MTD()'); + $expected = 'MTD()'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('(sm)'); + $expected = '(sm)'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('name AS x'); + $expected = '`name` AS `x`'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('Model.name AS x'); + $expected = '`Model`.`name` AS `x`'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('Function(Something.foo)'); + $expected = 'Function(`Something`.`foo`)'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('Function(SubFunction(Something.foo))'); + $expected = 'Function(SubFunction(`Something`.`foo`))'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('Function(Something.foo) AS x'); + $expected = 'Function(`Something`.`foo`) AS `x`'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('name-with-minus'); + $expected = '`name-with-minus`'; + $this->assertEqual($result, $expected); + + $result = $this->testDb->name(array('my-name', 'Foo-Model.*')); + $expected = array('`my-name`', '`Foo-Model`.*'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->name(array('Team.P%', 'Team.G/G')); + $expected = array('`Team`.`P%`', '`Team`.`G/G`'); + $this->assertEqual($result, $expected); + + $result = $this->testDb->name('Model.name as y'); + $expected = '`Model`.`name` AS `y`'; + $this->assertEqual($result, $expected); + } + +/** + * test that cacheMethod works as exepected + * + * @return void + */ + function testCacheMethod() { + $this->testDb->cacheMethods = true; + $result = $this->testDb->cacheMethod('name', 'some-key', 'stuff'); + $this->assertEqual($result, 'stuff'); + + $result = $this->testDb->cacheMethod('name', 'some-key'); + $this->assertEqual($result, 'stuff'); + + $result = $this->testDb->cacheMethod('conditions', 'some-key'); + $this->assertNull($result); + + $result = $this->testDb->cacheMethod('name', 'other-key'); + $this->assertNull($result); + + $this->testDb->cacheMethods = false; + $result = $this->testDb->cacheMethod('name', 'some-key', 'stuff'); + $this->assertEqual($result, 'stuff'); + + $result = $this->testDb->cacheMethod('name', 'some-key'); + $this->assertNull($result); + } + +/** + * testLog method + * + * @access public + * @return void + */ + function testLog() { + $this->testDb->logQuery('Query 1'); + $this->testDb->logQuery('Query 2'); + + $log = $this->testDb->getLog(false, false); + $result = Set::extract($log['log'], '/query'); + $expected = array('Query 1', 'Query 2'); + $this->assertEqual($result, $expected); + + $oldError = $this->testDb->error; + $this->testDb->error = true; + $result = $this->testDb->logQuery('Error 1'); + $this->assertFalse($result); + $this->testDb->error = $oldError; + + $log = $this->testDb->getLog(false, false); + $result = Set::combine($log['log'], '/query', '/error'); + $expected = array('Query 1' => false, 'Query 2' => false, 'Error 1' => true); + $this->assertEqual($result, $expected); + + Configure::write('debug', 2); + ob_start(); + $this->testDb->showLog(); + $contents = ob_get_clean(); + + $this->assertPattern('/Query 1/s', $contents); + $this->assertPattern('/Query 2/s', $contents); + $this->assertPattern('/Error 1/s', $contents); + + ob_start(); + $this->testDb->showLog(true); + $contents = ob_get_clean(); + + $this->assertPattern('/Query 1/s', $contents); + $this->assertPattern('/Query 2/s', $contents); + $this->assertPattern('/Error 1/s', $contents); + + $oldError = $this->testDb->error; + $oldDebug = Configure::read('debug'); + Configure::write('debug', 2); + + $this->testDb->error = true; + $this->expectError(); + ob_start(); + $this->testDb->showQuery('Error 2'); + $contents = ob_get_clean(); + + $this->assertPattern('/Error 2/s', $contents); + + $this->testDb->error = $oldError; + Configure::write('debug', $oldDebug); + } + +/** + * test getting the query log as an array. + * + * @return void + */ + function testGetLog() { + $this->testDb->logQuery('Query 1'); + $this->testDb->logQuery('Query 2'); + + $oldError = $this->testDb->error; + $this->testDb->error = true; + $result = $this->testDb->logQuery('Error 1'); + $this->assertFalse($result); + $this->testDb->error = $oldError; + + $log = $this->testDb->getLog(); + $expected = array('query' => 'Query 1', 'error' => '', 'affected' => '', 'numRows' => '', 'took' => ''); + $this->assertEqual($log['log'][0], $expected); + $expected = array('query' => 'Query 2', 'error' => '', 'affected' => '', 'numRows' => '', 'took' => ''); + $this->assertEqual($log['log'][1], $expected); + $expected = array('query' => 'Error 1', 'error' => true, 'affected' => '', 'numRows' => '', 'took' => ''); + $this->assertEqual($log['log'][2], $expected); + } + +/** + * test that execute runs queries. + * + * @return void + */ + function testExecute() { + $query = 'SELECT * FROM ' . $this->testDb->fullTableName('articles') . ' WHERE 1 = 1'; + + $this->db->_result = null; + $this->db->took = null; + $this->db->affected = null; + $result = $this->db->execute($query, array('stats' => false)); + $this->assertNotNull($result, 'No query performed! %s'); + $this->assertNull($this->db->took, 'Stats were set %s'); + $this->assertNull($this->db->affected, 'Stats were set %s'); + + $result = $this->db->execute($query); + $this->assertNotNull($result, 'No query performed! %s'); + $this->assertNotNull($this->db->took, 'Stats were not set %s'); + $this->assertNotNull($this->db->affected, 'Stats were not set %s'); + } + +/** + * test that query() returns boolean values from operations like CREATE TABLE + * + * @return void + */ + function testFetchAllBooleanReturns() { + $name = $this->db->fullTableName('test_query'); + $query = "CREATE TABLE {$name} (name varchar(10));"; + $result = $this->db->query($query); + $this->assertTrue($result, 'Query did not return a boolean. %s'); + + $query = "DROP TABLE {$name};"; + $result = $this->db->fetchAll($query); + $this->assertTrue($result, 'Query did not return a boolean. %s'); + } + +/** + * test ShowQuery generation of regular and error messages + * + * @return void + */ + function testShowQuery() { + $this->testDb->error = false; + ob_start(); + $this->testDb->showQuery('Some Query'); + $contents = ob_get_clean(); + $this->assertPattern('/Some Query/s', $contents); + $this->assertPattern('/Aff:/s', $contents); + $this->assertPattern('/Num:/s', $contents); + $this->assertPattern('/Took:/s', $contents); + + $this->expectError(); + $this->testDb->error = true; + ob_start(); + $this->testDb->showQuery('Another Query'); + $contents = ob_get_clean(); + $this->assertPattern('/Another Query/s', $contents); + $this->assertNoPattern('/Aff:/s', $contents); + $this->assertNoPattern('/Num:/s', $contents); + $this->assertNoPattern('/Took:/s', $contents); + } + +/** + * test fields generating usable virtual fields to use in query + * + * @return void + */ + function testVirtualFields() { + $this->loadFixtures('Article'); + + $Article =& ClassRegistry::init('Article'); + $Article->virtualFields = array( + 'this_moment' => 'NOW()', + 'two' => '1 + 1', + 'comment_count' => 'SELECT COUNT(*) FROM ' . $this->db->fullTableName('comments') . + ' WHERE Article.id = ' . $this->db->fullTableName('comments') . '.article_id' + ); + $result = $this->db->fields($Article); + $expected = array( + '`Article`.`id`', + '`Article`.`user_id`', + '`Article`.`title`', + '`Article`.`body`', + '`Article`.`published`', + '`Article`.`created`', + '`Article`.`updated`', + '(NOW()) AS `Article__this_moment`', + '(1 + 1) AS `Article__two`', + '(SELECT COUNT(*) FROM comments WHERE `Article`.`id` = `comments`.`article_id`) AS `Article__comment_count`' + ); + $this->assertEqual($expected, $result); + + $result = $this->db->fields($Article, null, array('this_moment', 'title')); + $expected = array( + '`Article`.`title`', + '(NOW()) AS `Article__this_moment`', + ); + $this->assertEqual($expected, $result); + + $result = $this->db->fields($Article, null, array('Article.title', 'Article.this_moment')); + $expected = array( + '`Article`.`title`', + '(NOW()) AS `Article__this_moment`', + ); + $this->assertEqual($expected, $result); + + $result = $this->db->fields($Article, null, array('Article.this_moment', 'Article.title')); + $expected = array( + '`Article`.`title`', + '(NOW()) AS `Article__this_moment`', + ); + $this->assertEqual($expected, $result); + + $result = $this->db->fields($Article, null, array('Article.*')); + $expected = array( + '`Article`.*', + '(NOW()) AS `Article__this_moment`', + '(1 + 1) AS `Article__two`', + '(SELECT COUNT(*) FROM comments WHERE `Article`.`id` = `comments`.`article_id`) AS `Article__comment_count`' + ); + $this->assertEqual($expected, $result); + + $result = $this->db->fields($Article, null, array('*')); + $expected = array( + '*', + '(NOW()) AS `Article__this_moment`', + '(1 + 1) AS `Article__two`', + '(SELECT COUNT(*) FROM comments WHERE `Article`.`id` = `comments`.`article_id`) AS `Article__comment_count`' + ); + $this->assertEqual($expected, $result); + } + +/** + * test conditions to generate query conditions for virtual fields + * + * @return void + */ + function testVirtualFieldsInConditions() { + $Article =& ClassRegistry::init('Article'); + $Article->virtualFields = array( + 'this_moment' => 'NOW()', + 'two' => '1 + 1', + 'comment_count' => 'SELECT COUNT(*) FROM ' . $this->db->fullTableName('comments') . + ' WHERE Article.id = ' . $this->db->fullTableName('comments') . '.article_id' + ); + $conditions = array('two' => 2); + $result = $this->db->conditions($conditions, true, false, $Article); + $expected = '(1 + 1) = 2'; + $this->assertEqual($expected, $result); + + $conditions = array('this_moment BETWEEN ? AND ?' => array(1,2)); + $expected = 'NOW() BETWEEN 1 AND 2'; + $result = $this->db->conditions($conditions, true, false, $Article); + $this->assertEqual($expected, $result); + + $conditions = array('comment_count >' => 5); + $expected = '(SELECT COUNT(*) FROM comments WHERE `Article`.`id` = `comments`.`article_id`) > 5'; + $result = $this->db->conditions($conditions, true, false, $Article); + $this->assertEqual($expected, $result); + + $conditions = array('NOT' => array('two' => 2)); + $result = $this->db->conditions($conditions, true, false, $Article); + $expected = 'NOT ((1 + 1) = 2)'; + $this->assertEqual($expected, $result); + } + +/** + * test that virtualFields with complex functions and aliases work. + * + * @return void + */ + function testConditionsWithComplexVirtualFields() { + $Article =& ClassRegistry::init('Article'); + $Article->virtualFields = array( + 'distance' => 'ACOS(SIN(20 * PI() / 180) + * SIN(Article.latitude * PI() / 180) + + COS(20 * PI() / 180) + * COS(Article.latitude * PI() / 180) + * COS((50 - Article.longitude) * PI() / 180) + ) * 180 / PI() * 60 * 1.1515 * 1.609344' + ); + $conditions = array('distance >=' => 20); + $result = $this->db->conditions($conditions, true, true, $Article); + + $this->assertPattern('/\) >= 20/', $result); + $this->assertPattern('/[`\'"]Article[`\'"].[`\'"]latitude[`\'"]/', $result); + $this->assertPattern('/[`\'"]Article[`\'"].[`\'"]longitude[`\'"]/', $result); + } + +/** + * test order to generate query order clause for virtual fields + * + * @return void + */ + function testVirtualFieldsInOrder() { + $Article =& ClassRegistry::init('Article'); + $Article->virtualFields = array( + 'this_moment' => 'NOW()', + 'two' => '1 + 1', + ); + $order = array('two', 'this_moment'); + $result = $this->db->order($order, 'ASC', $Article); + $expected = ' ORDER BY (1 + 1) ASC, (NOW()) ASC'; + $this->assertEqual($expected, $result); + + $order = array('Article.two', 'Article.this_moment'); + $result = $this->db->order($order, 'ASC', $Article); + $expected = ' ORDER BY (1 + 1) ASC, (NOW()) ASC'; + $this->assertEqual($expected, $result); + } + +/** + * test calculate to generate claculate statements on virtual fields + * + * @return void + */ + function testVirtualFieldsInCalculate() { + $Article =& ClassRegistry::init('Article'); + $Article->virtualFields = array( + 'this_moment' => 'NOW()', + 'two' => '1 + 1', + 'comment_count' => 'SELECT COUNT(*) FROM ' . $this->db->fullTableName('comments') . + ' WHERE Article.id = ' . $this->db->fullTableName('comments'). '.article_id' + ); + + $result = $this->db->calculate($Article, 'count', array('this_moment')); + $expected = 'COUNT(NOW()) AS `count`'; + $this->assertEqual($expected, $result); + + $result = $this->db->calculate($Article, 'max', array('comment_count')); + $expected = 'MAX(SELECT COUNT(*) FROM comments WHERE `Article`.`id` = `comments`.`article_id`) AS `comment_count`'; + $this->assertEqual($expected, $result); + } + +/** + * test a full example of using virtual fields + * + * @return void + */ + function testVirtualFieldsFetch() { + $this->loadFixtures('Article', 'Comment'); + + $Article =& ClassRegistry::init('Article'); + $Article->virtualFields = array( + 'comment_count' => 'SELECT COUNT(*) FROM ' . $this->db->fullTableName('comments') . + ' WHERE Article.id = ' . $this->db->fullTableName('comments') . '.article_id' + ); + + $conditions = array('comment_count >' => 2); + $query = 'SELECT ' . join(',',$this->db->fields($Article, null, array('id', 'comment_count'))) . + ' FROM ' . $this->db->fullTableName($Article) . ' Article ' . $this->db->conditions($conditions, true, true, $Article); + $result = $this->db->fetchAll($query); + $expected = array(array( + 'Article' => array('id' => 1, 'comment_count' => 4) + )); + $this->assertEqual($expected, $result); + } + +/** + * test reading complex virtualFields with subqueries. + * + * @return void + */ + function testVirtualFieldsComplexRead() { + $this->loadFixtures('DataTest', 'Article', 'Comment'); + + $Article =& ClassRegistry::init('Article'); + $commentTable = $this->db->fullTableName('comments'); + $Article =& ClassRegistry::init('Article'); + $Article->virtualFields = array( + 'comment_count' => 'SELECT COUNT(*) FROM ' . $commentTable . + ' AS Comment WHERE Article.id = Comment.article_id' + ); + $result = $Article->find('all'); + $this->assertTrue(count($result) > 0); + $this->assertTrue($result[0]['Article']['comment_count'] > 0); + + $DataTest =& ClassRegistry::init('DataTest'); + $DataTest->virtualFields = array( + 'complicated' => 'ACOS(SIN(20 * PI() / 180) + * SIN(DataTest.float * PI() / 180) + + COS(20 * PI() / 180) + * COS(DataTest.count * PI() / 180) + * COS((50 - DataTest.float) * PI() / 180) + ) * 180 / PI() * 60 * 1.1515 * 1.609344' + ); + $result = $DataTest->find('all'); + $this->assertTrue(count($result) > 0); + $this->assertTrue($result[0]['DataTest']['complicated'] > 0); + } + +/** + * test that virtualFields with complex functions and aliases work. + * + * @return void + */ + function testFieldsWithComplexVirtualFields() { + $Article =& new Article(); + $Article->virtualFields = array( + 'distance' => 'ACOS(SIN(20 * PI() / 180) + * SIN(Article.latitude * PI() / 180) + + COS(20 * PI() / 180) + * COS(Article.latitude * PI() / 180) + * COS((50 - Article.longitude) * PI() / 180) + ) * 180 / PI() * 60 * 1.1515 * 1.609344' + ); + + $fields = array('id', 'distance'); + $result = $this->db->fields($Article, null, $fields); + $qs = $this->db->startQuote; + $qe = $this->db->endQuote; + + $this->assertEqual($result[0], "{$qs}Article{$qe}.{$qs}id{$qe}"); + $this->assertPattern('/Article__distance/', $result[1]); + $this->assertPattern('/[`\'"]Article[`\'"].[`\'"]latitude[`\'"]/', $result[1]); + $this->assertPattern('/[`\'"]Article[`\'"].[`\'"]longitude[`\'"]/', $result[1]); + } + +/** + * test reading virtual fields containing newlines when recursive > 0 + * + * @return void + */ + function testReadVirtualFieldsWithNewLines() { + $Article =& new Article(); + $Article->recursive = 1; + $Article->virtualFields = array( + 'test' => ' + User.id + User.id + ' + ); + $result = $this->db->fields($Article, null, array()); + $result = $this->db->fields($Article, $Article->alias, $result); + $this->assertPattern('/[`\"]User[`\"]\.[`\"]id[`\"] \+ [`\"]User[`\"]\.[`\"]id[`\"]/', $result[7]); + } + +/** + * test group to generate GROUP BY statements on virtual fields + * + * @return void + */ + function testVirtualFieldsInGroup() { + $Article =& ClassRegistry::init('Article'); + $Article->virtualFields = array( + 'this_year' => 'YEAR(Article.created)' + ); + + $result = $this->db->group('this_year',$Article); + $expected = " GROUP BY (YEAR(`Article`.`created`))"; + $this->assertEqual($expected, $result); + } + +/** + * Test that group works without a model + * + * @return void + */ + function testGroupNoModel() { + $result = $this->db->group('created'); + $this->assertEqual(' GROUP BY created', $result); + } + +/** + * test the permutations of fullTableName() + * + * @return void + */ + function testFullTablePermutations() { + $Article =& ClassRegistry::init('Article'); + $result = $this->testDb->fullTableName($Article, false); + $this->assertEqual($result, 'articles'); + + $Article->tablePrefix = 'tbl_'; + $result = $this->testDb->fullTableName($Article, false); + $this->assertEqual($result, 'tbl_articles'); + + $Article->useTable = $Article->table = 'with spaces'; + $Article->tablePrefix = ''; + $result = $this->testDb->fullTableName($Article); + $this->assertEqual($result, '`with spaces`'); + } + +/** + * test that read() only calls queryAssociation on db objects when the method is defined. + * + * @return void + */ + function testReadOnlyCallingQueryAssociationWhenDefined() { + ConnectionManager::create('test_no_queryAssociation', array( + 'datasource' => 'data' + )); + $Article =& ClassRegistry::init('Article'); + $Article->Comment->useDbConfig = 'test_no_queryAssociation'; + $result = $Article->find('all'); + $this->assertTrue(is_array($result)); + } + +/** + * test that fields() is using methodCache() + * + * @return void + */ + function testFieldsUsingMethodCache() { + $this->testDb->cacheMethods = false; + $this->assertTrue(empty($this->testDb->methodCache['fields']), 'Cache not empty'); + + $Article =& ClassRegistry::init('Article'); + $this->testDb->fields($Article, null, array('title', 'body', 'published')); + $this->assertTrue(empty($this->testDb->methodCache['fields']), 'Cache not empty'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/db_acl.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/db_acl.test.php new file mode 100644 index 000000000..b970d2332 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/db_acl.test.php @@ -0,0 +1,397 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.controller.components.dbacl.models + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + define('CAKEPHP_UNIT_TEST_EXECUTION', 1); +} +App::import('Component', 'Acl'); +App::import('Core', 'db_acl'); + +/** + * DB ACL wrapper test class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class DbAclNodeTestBase extends AclNode { + +/** + * useDbConfig property + * + * @var string 'test_suite' + * @access public + */ + var $useDbConfig = 'test_suite'; + +/** + * cacheSources property + * + * @var bool false + * @access public + */ + var $cacheSources = false; +} + +/** + * Aro Test Wrapper + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class DbAroTest extends DbAclNodeTestBase { + +/** + * name property + * + * @var string 'DbAroTest' + * @access public + */ + var $name = 'DbAroTest'; + +/** + * useTable property + * + * @var string 'aros' + * @access public + */ + var $useTable = 'aros'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('DbAcoTest' => array('with' => 'DbPermissionTest')); +} + +/** + * Aco Test Wrapper + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class DbAcoTest extends DbAclNodeTestBase { + +/** + * name property + * + * @var string 'DbAcoTest' + * @access public + */ + var $name = 'DbAcoTest'; + +/** + * useTable property + * + * @var string 'acos' + * @access public + */ + var $useTable = 'acos'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('DbAroTest' => array('with' => 'DbPermissionTest')); +} + +/** + * Permission Test Wrapper + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class DbPermissionTest extends CakeTestModel { + +/** + * name property + * + * @var string 'DbPermissionTest' + * @access public + */ + var $name = 'DbPermissionTest'; + +/** + * useTable property + * + * @var string 'aros_acos' + * @access public + */ + var $useTable = 'aros_acos'; + +/** + * cacheQueries property + * + * @var bool false + * @access public + */ + var $cacheQueries = false; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('DbAroTest' => array('foreignKey' => 'aro_id'), 'DbAcoTest' => array('foreignKey' => 'aco_id')); +} + +/** + * DboActionTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class DbAcoActionTest extends CakeTestModel { + +/** + * name property + * + * @var string 'DbAcoActionTest' + * @access public + */ + var $name = 'DbAcoActionTest'; + +/** + * useTable property + * + * @var string 'aco_actions' + * @access public + */ + var $useTable = 'aco_actions'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('DbAcoTest' => array('foreignKey' => 'aco_id')); +} + +/** + * DbAroUserTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class DbAroUserTest extends CakeTestModel { + +/** + * name property + * + * @var string 'AuthUser' + * @access public + */ + var $name = 'AuthUser'; + +/** + * useTable property + * + * @var string 'auth_users' + * @access public + */ + var $useTable = 'auth_users'; + +/** + * bindNode method + * + * @param mixed $ref + * @access public + * @return void + */ + function bindNode($ref = null) { + if (Configure::read('DbAclbindMode') == 'string') { + return 'ROOT/admins/Gandalf'; + } elseif (Configure::read('DbAclbindMode') == 'array') { + return array('DbAroTest' => array('DbAroTest.model' => 'AuthUser', 'DbAroTest.foreign_key' => 2)); + } + } +} + +/** + * DbAclTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components + */ +class DbAclTest extends DbAcl { + +/** + * construct method + * + * @access private + * @return void + */ + function __construct() { + $this->Aro =& new DbAroTest(); + $this->Aro->Permission =& new DbPermissionTest(); + $this->Aco =& new DbAcoTest(); + $this->Aro->Permission =& new DbPermissionTest(); + } +} + +/** + * AclNodeTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.controller.components.dbacl.models + */ +class AclNodeTest extends CakeTestCase { + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.aro', 'core.aco', 'core.aros_aco', 'core.aco_action', 'core.auth_user'); + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + Configure::write('Acl.classname', 'DbAclTest'); + Configure::write('Acl.database', 'test_suite'); + } + +/** + * testNode method + * + * @access public + * @return void + */ + function testNode() { + $Aco =& new DbAcoTest(); + $result = Set::extract($Aco->node('Controller1'), '{n}.DbAcoTest.id'); + $expected = array(2, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($Aco->node('Controller1/action1'), '{n}.DbAcoTest.id'); + $expected = array(3, 2, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($Aco->node('Controller2/action1'), '{n}.DbAcoTest.id'); + $expected = array(7, 6, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($Aco->node('Controller1/action2'), '{n}.DbAcoTest.id'); + $expected = array(5, 2, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($Aco->node('Controller1/action1/record1'), '{n}.DbAcoTest.id'); + $expected = array(4, 3, 2, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($Aco->node('Controller2/action1/record1'), '{n}.DbAcoTest.id'); + $expected = array(8, 7, 6, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($Aco->node('Controller2/action3'), '{n}.DbAcoTest.id'); + $this->assertFalse($result); + + $result = Set::extract($Aco->node('Controller2/action3/record5'), '{n}.DbAcoTest.id'); + $this->assertFalse($result); + + $result = $Aco->node(''); + $this->assertEqual($result, null); + } + +/** + * test that node() doesn't dig deeper than it should. + * + * @return void + */ + function testNodeWithDuplicatePathSegments() { + $Aco =& new DbAcoTest(); + $nodes = $Aco->node('ROOT/Users'); + $this->assertEqual($nodes[0]['DbAcoTest']['parent_id'], 1, 'Parent id does not point at ROOT. %s'); + } + +/** + * testNodeArrayFind method + * + * @access public + * @return void + */ + function testNodeArrayFind() { + $Aro = new DbAroTest(); + Configure::write('DbAclbindMode', 'string'); + $result = Set::extract($Aro->node(array('DbAroUserTest' => array('id' => '1', 'foreign_key' => '1'))), '{n}.DbAroTest.id'); + $expected = array(3, 2, 1); + $this->assertEqual($result, $expected); + + Configure::write('DbAclbindMode', 'array'); + $result = Set::extract($Aro->node(array('DbAroUserTest' => array('id' => 4, 'foreign_key' => 2))), '{n}.DbAroTest.id'); + $expected = array(4); + $this->assertEqual($result, $expected); + } + /** + * testNodeObjectFind method + * + * @access public + * @return void + */ + function testNodeObjectFind() { + $Aro = new DbAroTest(); + $Model = new DbAroUserTest(); + $Model->id = 1; + $result = Set::extract($Aro->node($Model), '{n}.DbAroTest.id'); + $expected = array(3, 2, 1); + $this->assertEqual($result, $expected); + + $Model->id = 2; + $result = Set::extract($Aro->node($Model), '{n}.DbAroTest.id'); + $expected = array(4, 2, 1); + $this->assertEqual($result, $expected); + + } + +/** + * testNodeAliasParenting method + * + * @access public + * @return void + */ + function testNodeAliasParenting() { + $Aco = new DbAcoTest(); + $db =& ConnectionManager::getDataSource('test_suite'); + $db->truncate($Aco); + $db->_queriesLog = array(); + + $Aco->create(array('model' => null, 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Application')); + $Aco->save(); + + $Aco->create(array('model' => null, 'foreign_key' => null, 'parent_id' => $Aco->id, 'alias' => 'Pages')); + $Aco->save(); + + $result = $Aco->find('all'); + $expected = array( + array('DbAcoTest' => array('id' => '1', 'parent_id' => null, 'model' => null, 'foreign_key' => null, 'alias' => 'Application', 'lft' => '1', 'rght' => '4'), 'DbAroTest' => array()), + array('DbAcoTest' => array('id' => '2', 'parent_id' => '1', 'model' => null, 'foreign_key' => null, 'alias' => 'Pages', 'lft' => '2', 'rght' => '3', ), 'DbAroTest' => array()) + ); + $this->assertEqual($result, $expected); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model.test.php new file mode 100644 index 000000000..14d57f9ab --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model.test.php @@ -0,0 +1,103 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', array('AppModel', 'Model')); +require_once dirname(__FILE__) . DS . 'models.php'; + +SimpleTest::ignore('BaseModelTest'); + +/** + * ModelBaseTest + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class BaseModelTest extends CakeTestCase { + +/** + * autoFixtures property + * + * @var bool false + * @access public + */ + var $autoFixtures = false; + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array( + 'core.category', 'core.category_thread', 'core.user', 'core.my_category', 'core.my_product', + 'core.my_user', 'core.my_categories_my_users', 'core.my_categories_my_products', + 'core.article', 'core.featured', 'core.article_featureds_tags', 'core.article_featured', + 'core.articles', 'core.numeric_article', 'core.tag', 'core.articles_tag', 'core.comment', + 'core.attachment', 'core.apple', 'core.sample', 'core.another_article', 'core.item', + 'core.advertisement', 'core.home', 'core.post', 'core.author', 'core.bid', 'core.portfolio', + 'core.product', 'core.project', 'core.thread', 'core.message', 'core.items_portfolio', + 'core.syfile', 'core.image', 'core.device_type', 'core.device_type_category', + 'core.feature_set', 'core.exterior_type_category', 'core.document', 'core.device', + 'core.document_directory', 'core.primary_model', 'core.secondary_model', 'core.something', + 'core.something_else', 'core.join_thing', 'core.join_a', 'core.join_b', 'core.join_c', + 'core.join_a_b', 'core.join_a_c', 'core.uuid', 'core.data_test', 'core.posts_tag', + 'core.the_paper_monkies', 'core.person', 'core.underscore_field', 'core.node', + 'core.dependency', 'core.story', 'core.stories_tag', 'core.cd', 'core.book', 'core.basket', + 'core.overall_favorite', 'core.account', 'core.content', 'core.content_account', + 'core.film_file', 'core.test_plugin_article', 'core.test_plugin_comment', 'core.uuiditem', + 'core.counter_cache_user', 'core.counter_cache_post', + 'core.counter_cache_user_nonstandard_primary_key', + 'core.counter_cache_post_nonstandard_primary_key', 'core.uuidportfolio', + 'core.uuiditems_uuidportfolio', 'core.uuiditems_uuidportfolio_numericid', 'core.fruit', + 'core.fruits_uuid_tag', 'core.uuid_tag', 'core.product_update_all', 'core.group_update_all' + ); + +/** + * start method + * + * @access public + * @return void + */ + function start() { + parent::start(); + $this->debug = Configure::read('debug'); + Configure::write('debug', 2); + } + +/** + * end method + * + * @access public + * @return void + */ + function end() { + parent::end(); + Configure::write('debug', $this->debug); + } + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + ClassRegistry::flush(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_behavior.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_behavior.test.php new file mode 100644 index 000000000..ee139877b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_behavior.test.php @@ -0,0 +1,1138 @@ + 'testMethod', '/look for\s+(.+)/' => 'speakEnglish'); + +/** + * setup method + * + * @param mixed $model + * @param array $config + * @access public + * @return void + */ + function setup(&$model, $config = array()) { + parent::setup($model, $config); + if (isset($config['mangle'])) { + $config['mangle'] .= ' mangled'; + } + $this->settings[$model->alias] = array_merge(array('beforeFind' => 'on', 'afterFind' => 'off'), $config); + } + +/** + * beforeFind method + * + * @param mixed $model + * @param mixed $query + * @access public + * @return void + */ + function beforeFind(&$model, $query) { + $settings = $this->settings[$model->alias]; + if (!isset($settings['beforeFind']) || $settings['beforeFind'] == 'off') { + return parent::beforeFind($model, $query); + } + switch ($settings['beforeFind']) { + case 'on': + return false; + break; + case 'test': + return null; + break; + case 'modify': + $query['fields'] = array($model->alias . '.id', $model->alias . '.name', $model->alias . '.mytime'); + $query['recursive'] = -1; + return $query; + break; + } + } + +/** + * afterFind method + * + * @param mixed $model + * @param mixed $results + * @param mixed $primary + * @access public + * @return void + */ + function afterFind(&$model, $results, $primary) { + $settings = $this->settings[$model->alias]; + if (!isset($settings['afterFind']) || $settings['afterFind'] == 'off') { + return parent::afterFind($model, $results, $primary); + } + switch ($settings['afterFind']) { + case 'on': + return array(); + break; + case 'test': + return true; + break; + case 'test2': + return null; + break; + case 'modify': + return Set::extract($results, "{n}.{$model->alias}"); + break; + } + } + +/** + * beforeSave method + * + * @param mixed $model + * @access public + * @return void + */ + function beforeSave(&$model) { + $settings = $this->settings[$model->alias]; + if (!isset($settings['beforeSave']) || $settings['beforeSave'] == 'off') { + return parent::beforeSave($model); + } + switch ($settings['beforeSave']) { + case 'on': + return false; + break; + case 'test': + return null; + break; + case 'modify': + $model->data[$model->alias]['name'] .= ' modified before'; + return true; + break; + } + } + +/** + * afterSave method + * + * @param mixed $model + * @param mixed $created + * @access public + * @return void + */ + function afterSave(&$model, $created) { + $settings = $this->settings[$model->alias]; + if (!isset($settings['afterSave']) || $settings['afterSave'] == 'off') { + return parent::afterSave($model, $created); + } + $string = 'modified after'; + if ($created) { + $string .= ' on create'; + } + switch ($settings['afterSave']) { + case 'on': + $model->data[$model->alias]['aftersave'] = $string; + break; + case 'test': + unset($model->data[$model->alias]['name']); + break; + case 'test2': + return false; + break; + case 'modify': + $model->data[$model->alias]['name'] .= ' ' . $string; + break; + } + } + +/** + * beforeValidate method + * + * @param mixed $model + * @access public + * @return void + */ + function beforeValidate(&$model) { + $settings = $this->settings[$model->alias]; + if (!isset($settings['validate']) || $settings['validate'] == 'off') { + return parent::beforeValidate($model); + } + switch ($settings['validate']) { + case 'on': + $model->invalidate('name'); + return true; + break; + case 'test': + return null; + break; + case 'whitelist': + $this->_addToWhitelist($model, array('name')); + return true; + break; + case 'stop': + $model->invalidate('name'); + return false; + break; + } + } + +/** + * beforeDelete method + * + * @param mixed $model + * @param bool $cascade + * @access public + * @return void + */ + function beforeDelete(&$model, $cascade = true) { + $settings =& $this->settings[$model->alias]; + if (!isset($settings['beforeDelete']) || $settings['beforeDelete'] == 'off') { + return parent::beforeDelete($model, $cascade); + } + switch ($settings['beforeDelete']) { + case 'on': + return false; + break; + case 'test': + return null; + break; + case 'test2': + echo 'beforeDelete success'; + if ($cascade) { + echo ' (cascading) '; + } + break; + } + } + +/** + * afterDelete method + * + * @param mixed $model + * @access public + * @return void + */ + function afterDelete(&$model) { + $settings =& $this->settings[$model->alias]; + if (!isset($settings['afterDelete']) || $settings['afterDelete'] == 'off') { + return parent::afterDelete($model); + } + switch ($settings['afterDelete']) { + case 'on': + echo 'afterDelete success'; + break; + } + } + +/** + * onError method + * + * @param mixed $model + * @access public + * @return void + */ + function onError(&$model) { + $settings = $this->settings[$model->alias]; + if (!isset($settings['onError']) || $settings['onError'] == 'off') { + return parent::onError($model, $cascade); + } + echo "onError trigger success"; + } +/** + * beforeTest method + * + * @param mixed $model + * @access public + * @return void + */ + function beforeTest(&$model) { + $model->beforeTestResult[] = strtolower(get_class($this)); + return strtolower(get_class($this)); + } + +/** + * testMethod method + * + * @param mixed $model + * @param bool $param + * @access public + * @return void + */ + function testMethod(&$model, $param = true) { + if ($param === true) { + return 'working'; + } + } + +/** + * testData method + * + * @param mixed $model + * @access public + * @return void + */ + function testData(&$model) { + if (!isset($model->data['Apple']['field'])) { + return false; + } + $model->data['Apple']['field_2'] = true; + return true; + } + +/** + * validateField method + * + * @param mixed $model + * @param mixed $field + * @access public + * @return void + */ + function validateField(&$model, $field) { + return current($field) === 'Orange'; + } + +/** + * speakEnglish method + * + * @param mixed $model + * @param mixed $method + * @param mixed $query + * @access public + * @return void + */ + function speakEnglish(&$model, $method, $query) { + $method = preg_replace('/look for\s+/', 'Item.name = \'', $method); + $query = preg_replace('/^in\s+/', 'Location.name = \'', $query); + return $method . '\' AND ' . $query . '\''; + } +} + +/** + * Test2Behavior class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Test2Behavior extends TestBehavior{ +} + +/** + * Test3Behavior class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Test3Behavior extends TestBehavior{ +} + +/** + * Test4Behavior class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Test4Behavior extends ModelBehavior{ + function setup(&$model, $config = null) { + $model->bindModel( + array('hasMany' => array('Comment')) + ); + } +} + +/** + * Test5Behavior class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Test5Behavior extends ModelBehavior{ + function setup(&$model, $config = null) { + $model->bindModel( + array('belongsTo' => array('User')) + ); + } +} + +/** + * Test6Behavior class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Test6Behavior extends ModelBehavior{ + function setup(&$model, $config = null) { + $model->bindModel( + array('hasAndBelongsToMany' => array('Tag')) + ); + } +} + +/** + * Test7Behavior class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Test7Behavior extends ModelBehavior{ + function setup(&$model, $config = null) { + $model->bindModel( + array('hasOne' => array('Attachment')) + ); + } +} + +/** + * BehaviorTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class BehaviorTest extends CakeTestCase { + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array( + 'core.apple', 'core.sample', 'core.article', 'core.user', 'core.comment', + 'core.attachment', 'core.tag', 'core.articles_tag' + ); + +/** + * tearDown method + * + * @access public + * @return void + */ + function endTest() { + ClassRegistry::flush(); + } + +/** + * testBehaviorBinding method + * + * @access public + * @return void + */ + function testBehaviorBinding() { + $Apple = new Apple(); + $this->assertIdentical($Apple->Behaviors->attached(), array()); + + $Apple->Behaviors->attach('Test', array('key' => 'value')); + $this->assertIdentical($Apple->Behaviors->attached(), array('Test')); + $this->assertEqual(strtolower(get_class($Apple->Behaviors->Test)), 'testbehavior'); + $expected = array('beforeFind' => 'on', 'afterFind' => 'off', 'key' => 'value'); + $this->assertEqual($Apple->Behaviors->Test->settings['Apple'], $expected); + $this->assertEqual(array_keys($Apple->Behaviors->Test->settings), array('Apple')); + + $this->assertIdentical($Apple->Sample->Behaviors->attached(), array()); + $Apple->Sample->Behaviors->attach('Test', array('key2' => 'value2')); + $this->assertIdentical($Apple->Sample->Behaviors->attached(), array('Test')); + $this->assertEqual($Apple->Sample->Behaviors->Test->settings['Sample'], array('beforeFind' => 'on', 'afterFind' => 'off', 'key2' => 'value2')); + + $this->assertEqual(array_keys($Apple->Behaviors->Test->settings), array('Apple', 'Sample')); + $this->assertIdentical( + $Apple->Sample->Behaviors->Test->settings, + $Apple->Behaviors->Test->settings + ); + $this->assertNotIdentical($Apple->Behaviors->Test->settings['Apple'], $Apple->Sample->Behaviors->Test->settings['Sample']); + + $Apple->Behaviors->attach('Test', array('key2' => 'value2', 'key3' => 'value3', 'beforeFind' => 'off')); + $Apple->Sample->Behaviors->attach('Test', array('key' => 'value', 'key3' => 'value3', 'beforeFind' => 'off')); + $this->assertEqual($Apple->Behaviors->Test->settings['Apple'], array('beforeFind' => 'off', 'afterFind' => 'off', 'key' => 'value', 'key2' => 'value2', 'key3' => 'value3')); + $this->assertEqual($Apple->Behaviors->Test->settings['Apple'], $Apple->Sample->Behaviors->Test->settings['Sample']); + + $this->assertFalse(isset($Apple->Child->Behaviors->Test)); + $Apple->Child->Behaviors->attach('Test', array('key' => 'value', 'key2' => 'value2', 'key3' => 'value3', 'beforeFind' => 'off')); + $this->assertEqual($Apple->Child->Behaviors->Test->settings['Child'], $Apple->Sample->Behaviors->Test->settings['Sample']); + + $this->assertFalse(isset($Apple->Parent->Behaviors->Test)); + $Apple->Parent->Behaviors->attach('Test', array('key' => 'value', 'key2' => 'value2', 'key3' => 'value3', 'beforeFind' => 'off')); + $this->assertEqual($Apple->Parent->Behaviors->Test->settings['Parent'], $Apple->Sample->Behaviors->Test->settings['Sample']); + + $Apple->Parent->Behaviors->attach('Test', array('key' => 'value', 'key2' => 'value', 'key3' => 'value', 'beforeFind' => 'off')); + $this->assertNotEqual($Apple->Parent->Behaviors->Test->settings['Parent'], $Apple->Sample->Behaviors->Test->settings['Sample']); + + $Apple->Behaviors->attach('Plugin.Test', array('key' => 'new value')); + $expected = array( + 'beforeFind' => 'off', 'afterFind' => 'off', 'key' => 'new value', + 'key2' => 'value2', 'key3' => 'value3' + ); + $this->assertEqual($Apple->Behaviors->Test->settings['Apple'], $expected); + + $current = $Apple->Behaviors->Test->settings['Apple']; + $expected = array_merge($current, array('mangle' => 'trigger mangled')); + $Apple->Behaviors->attach('Test', array('mangle' => 'trigger')); + $this->assertEqual($Apple->Behaviors->Test->settings['Apple'], $expected); + + $Apple->Behaviors->attach('Test'); + $expected = array_merge($current, array('mangle' => 'trigger mangled mangled')); + + $this->assertEqual($Apple->Behaviors->Test->settings['Apple'], $expected); + $Apple->Behaviors->attach('Test', array('mangle' => 'trigger')); + $expected = array_merge($current, array('mangle' => 'trigger mangled')); + $this->assertEqual($Apple->Behaviors->Test->settings['Apple'], $expected); + } + +/** + * test that attach()/detach() works with plugin.banana + * + * @return void + */ + function testDetachWithPluginNames() { + $Apple = new Apple(); + $Apple->Behaviors->attach('Plugin.Test'); + $this->assertTrue(isset($Apple->Behaviors->Test), 'Missing behavior'); + $this->assertEqual($Apple->Behaviors->attached(), array('Test')); + + $Apple->Behaviors->detach('Plugin.Test'); + $this->assertEqual($Apple->Behaviors->attached(), array()); + + $Apple->Behaviors->attach('Plugin.Test'); + $this->assertTrue(isset($Apple->Behaviors->Test), 'Missing behavior'); + $this->assertEqual($Apple->Behaviors->attached(), array('Test')); + + $Apple->Behaviors->detach('Test'); + $this->assertEqual($Apple->Behaviors->attached(), array()); + } + +/** + * test that attaching a non existant Behavior triggers a cake error. + * + * @return void + */ + function testInvalidBehaviorCausingCakeError() { + $Apple =& new Apple(); + $Apple->Behaviors =& new MockModelBehaviorCollection(); + $Apple->Behaviors->expectOnce('cakeError'); + $Apple->Behaviors->expectAt(0, 'cakeError', array('missingBehaviorFile', '*')); + $this->assertFalse($Apple->Behaviors->attach('NoSuchBehavior')); + } + +/** + * testBehaviorToggling method + * + * @access public + * @return void + */ + function testBehaviorToggling() { + $Apple = new Apple(); + $this->assertIdentical($Apple->Behaviors->enabled(), array()); + + $Apple->Behaviors->init('Apple', array('Test' => array('key' => 'value'))); + $this->assertIdentical($Apple->Behaviors->enabled(), array('Test')); + + $Apple->Behaviors->disable('Test'); + $this->assertIdentical($Apple->Behaviors->attached(), array('Test')); + $this->assertIdentical($Apple->Behaviors->enabled(), array()); + + $Apple->Sample->Behaviors->attach('Test'); + $this->assertIdentical($Apple->Sample->Behaviors->enabled('Test'), true); + $this->assertIdentical($Apple->Behaviors->enabled(), array()); + + $Apple->Behaviors->enable('Test'); + $this->assertIdentical($Apple->Behaviors->attached('Test'), true); + $this->assertIdentical($Apple->Behaviors->enabled(), array('Test')); + + $Apple->Behaviors->disable('Test'); + $this->assertIdentical($Apple->Behaviors->enabled(), array()); + $Apple->Behaviors->attach('Test', array('enabled' => true)); + $this->assertIdentical($Apple->Behaviors->enabled(), array('Test')); + $Apple->Behaviors->attach('Test', array('enabled' => false)); + $this->assertIdentical($Apple->Behaviors->enabled(), array()); + $Apple->Behaviors->detach('Test'); + $this->assertIdentical($Apple->Behaviors->enabled(), array()); + } + +/** + * testBehaviorFindCallbacks method + * + * @access public + * @return void + */ + function testBehaviorFindCallbacks() { + $Apple = new Apple(); + $expected = $Apple->find('all'); + + $Apple->Behaviors->attach('Test'); + $this->assertIdentical($Apple->find('all'), null); + + $Apple->Behaviors->attach('Test', array('beforeFind' => 'off')); + $this->assertIdentical($Apple->find('all'), $expected); + + $Apple->Behaviors->attach('Test', array('beforeFind' => 'test')); + $this->assertIdentical($Apple->find('all'), $expected); + + $Apple->Behaviors->attach('Test', array('beforeFind' => 'modify')); + $expected2 = array( + array('Apple' => array('id' => '1', 'name' => 'Red Apple 1', 'mytime' => '22:57:17')), + array('Apple' => array('id' => '2', 'name' => 'Bright Red Apple', 'mytime' => '22:57:17')), + array('Apple' => array('id' => '3', 'name' => 'green blue', 'mytime' => '22:57:17')) + ); + $result = $Apple->find('all', array('conditions' => array('Apple.id <' => '4'))); + $this->assertEqual($result, $expected2); + + $Apple->Behaviors->disable('Test'); + $result = $Apple->find('all'); + $this->assertEqual($result, $expected); + + $Apple->Behaviors->attach('Test', array('beforeFind' => 'off', 'afterFind' => 'on')); + $this->assertIdentical($Apple->find('all'), array()); + + $Apple->Behaviors->attach('Test', array('afterFind' => 'off')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Behaviors->attach('Test', array('afterFind' => 'test')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Behaviors->attach('Test', array('afterFind' => 'test2')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Behaviors->attach('Test', array('afterFind' => 'modify')); + $expected = array( + array('id' => '1', 'apple_id' => '2', 'color' => 'Red 1', 'name' => 'Red Apple 1', 'created' => '2006-11-22 10:38:58', 'date' => '1951-01-04', 'modified' => '2006-12-01 13:31:26', 'mytime' => '22:57:17'), + array('id' => '2', 'apple_id' => '1', 'color' => 'Bright Red 1', 'name' => 'Bright Red Apple', 'created' => '2006-11-22 10:43:13', 'date' => '2014-01-01', 'modified' => '2006-11-30 18:38:10', 'mytime' => '22:57:17'), + array('id' => '3', 'apple_id' => '2', 'color' => 'blue green', 'name' => 'green blue', 'created' => '2006-12-25 05:13:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:24', 'mytime' => '22:57:17'), + array('id' => '4', 'apple_id' => '2', 'color' => 'Blue Green', 'name' => 'Test Name', 'created' => '2006-12-25 05:23:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:36', 'mytime' => '22:57:17'), + array('id' => '5', 'apple_id' => '5', 'color' => 'Green', 'name' => 'Blue Green', 'created' => '2006-12-25 05:24:06', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:16', 'mytime' => '22:57:17'), + array('id' => '6', 'apple_id' => '4', 'color' => 'My new appleOrange', 'name' => 'My new apple', 'created' => '2006-12-25 05:29:39', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:39', 'mytime' => '22:57:17'), + array('id' => '7', 'apple_id' => '6', 'color' => 'Some wierd color', 'name' => 'Some odd color', 'created' => '2006-12-25 05:34:21', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:34:21', 'mytime' => '22:57:17') + ); + $this->assertEqual($Apple->find('all'), $expected); + } + +/** + * testBehaviorHasManyFindCallbacks method + * + * @access public + * @return void + */ + function testBehaviorHasManyFindCallbacks() { + $Apple = new Apple(); + $Apple->unbindModel(array('hasOne' => array('Sample'), 'belongsTo' => array('Parent')), false); + $expected = $Apple->find('all'); + + $Apple->unbindModel(array('hasMany' => array('Child'))); + $wellBehaved = $Apple->find('all'); + $Apple->Child->Behaviors->attach('Test', array('afterFind' => 'modify')); + $Apple->unbindModel(array('hasMany' => array('Child'))); + $this->assertIdentical($Apple->find('all'), $wellBehaved); + + $Apple->Child->Behaviors->attach('Test', array('before' => 'off')); + $this->assertIdentical($Apple->find('all'), $expected); + + $Apple->Child->Behaviors->attach('Test', array('before' => 'test')); + $this->assertIdentical($Apple->find('all'), $expected); + + $expected2 = array( + array( + 'Apple' => array('id' => 1), + 'Child' => array( + array('id' => 2,'name' => 'Bright Red Apple', 'mytime' => '22:57:17'))), + array( + 'Apple' => array('id' => 2), + 'Child' => array( + array('id' => 1, 'name' => 'Red Apple 1', 'mytime' => '22:57:17'), + array('id' => 3, 'name' => 'green blue', 'mytime' => '22:57:17'), + array('id' => 4, 'name' => 'Test Name', 'mytime' => '22:57:17'))), + array( + 'Apple' => array('id' => 3), + 'Child' => array()) + ); + + $Apple->Child->Behaviors->attach('Test', array('before' => 'modify')); + $result = $Apple->find('all', array('fields' => array('Apple.id'), 'conditions' => array('Apple.id <' => '4'))); + //$this->assertEqual($result, $expected2); + + $Apple->Child->Behaviors->disable('Test'); + $result = $Apple->find('all'); + $this->assertEqual($result, $expected); + + $Apple->Child->Behaviors->attach('Test', array('before' => 'off', 'after' => 'on')); + //$this->assertIdentical($Apple->find('all'), array()); + + $Apple->Child->Behaviors->attach('Test', array('after' => 'off')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Child->Behaviors->attach('Test', array('after' => 'test')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Child->Behaviors->attach('Test', array('after' => 'test2')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Child->Behaviors->attach('Test', array('after' => 'modify')); + $expected = array( + array('id' => '1', 'apple_id' => '2', 'color' => 'Red 1', 'name' => 'Red Apple 1', 'created' => '2006-11-22 10:38:58', 'date' => '1951-01-04', 'modified' => '2006-12-01 13:31:26', 'mytime' => '22:57:17'), + array('id' => '2', 'apple_id' => '1', 'color' => 'Bright Red 1', 'name' => 'Bright Red Apple', 'created' => '2006-11-22 10:43:13', 'date' => '2014-01-01', 'modified' => '2006-11-30 18:38:10', 'mytime' => '22:57:17'), + array('id' => '3', 'apple_id' => '2', 'color' => 'blue green', 'name' => 'green blue', 'created' => '2006-12-25 05:13:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:24', 'mytime' => '22:57:17'), + array('id' => '4', 'apple_id' => '2', 'color' => 'Blue Green', 'name' => 'Test Name', 'created' => '2006-12-25 05:23:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:36', 'mytime' => '22:57:17'), + array('id' => '5', 'apple_id' => '5', 'color' => 'Green', 'name' => 'Blue Green', 'created' => '2006-12-25 05:24:06', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:16', 'mytime' => '22:57:17'), + array('id' => '6', 'apple_id' => '4', 'color' => 'My new appleOrange', 'name' => 'My new apple', 'created' => '2006-12-25 05:29:39', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:39', 'mytime' => '22:57:17'), + array('id' => '7', 'apple_id' => '6', 'color' => 'Some wierd color', 'name' => 'Some odd color', 'created' => '2006-12-25 05:34:21', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:34:21', 'mytime' => '22:57:17') + ); + //$this->assertEqual($Apple->find('all'), $expected); + + } + /** + * testBehaviorHasOneFindCallbacks method + * + * @access public + * @return void + */ + function testBehaviorHasOneFindCallbacks() { + $Apple = new Apple(); + $Apple->unbindModel(array('hasMany' => array('Child'), 'belongsTo' => array('Parent')), false); + $expected = $Apple->find('all'); + + $Apple->unbindModel(array('hasOne' => array('Sample'))); + $wellBehaved = $Apple->find('all'); + $Apple->Sample->Behaviors->attach('Test'); + $Apple->unbindModel(array('hasOne' => array('Sample'))); + $this->assertIdentical($Apple->find('all'), $wellBehaved); + + $Apple->Sample->Behaviors->attach('Test', array('before' => 'off')); + $this->assertIdentical($Apple->find('all'), $expected); + + $Apple->Sample->Behaviors->attach('Test', array('before' => 'test')); + $this->assertIdentical($Apple->find('all'), $expected); + + $Apple->Sample->Behaviors->attach('Test', array('before' => 'modify')); + $expected2 = array( + array( + 'Apple' => array('id' => 1), + 'Child' => array( + array('id' => 2,'name' => 'Bright Red Apple', 'mytime' => '22:57:17'))), + array( + 'Apple' => array('id' => 2), + 'Child' => array( + array('id' => 1, 'name' => 'Red Apple 1', 'mytime' => '22:57:17'), + array('id' => 3, 'name' => 'green blue', 'mytime' => '22:57:17'), + array('id' => 4, 'name' => 'Test Name', 'mytime' => '22:57:17'))), + array( + 'Apple' => array('id' => 3), + 'Child' => array()) + ); + $result = $Apple->find('all', array('fields' => array('Apple.id'), 'conditions' => array('Apple.id <' => '4'))); + //$this->assertEqual($result, $expected2); + + $Apple->Sample->Behaviors->disable('Test'); + $result = $Apple->find('all'); + $this->assertEqual($result, $expected); + + $Apple->Sample->Behaviors->attach('Test', array('before' => 'off', 'after' => 'on')); + //$this->assertIdentical($Apple->find('all'), array()); + + $Apple->Sample->Behaviors->attach('Test', array('after' => 'off')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Sample->Behaviors->attach('Test', array('after' => 'test')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Sample->Behaviors->attach('Test', array('after' => 'test2')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Sample->Behaviors->attach('Test', array('after' => 'modify')); + $expected = array( + array('id' => '1', 'apple_id' => '2', 'color' => 'Red 1', 'name' => 'Red Apple 1', 'created' => '2006-11-22 10:38:58', 'date' => '1951-01-04', 'modified' => '2006-12-01 13:31:26', 'mytime' => '22:57:17'), + array('id' => '2', 'apple_id' => '1', 'color' => 'Bright Red 1', 'name' => 'Bright Red Apple', 'created' => '2006-11-22 10:43:13', 'date' => '2014-01-01', 'modified' => '2006-11-30 18:38:10', 'mytime' => '22:57:17'), + array('id' => '3', 'apple_id' => '2', 'color' => 'blue green', 'name' => 'green blue', 'created' => '2006-12-25 05:13:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:24', 'mytime' => '22:57:17'), + array('id' => '4', 'apple_id' => '2', 'color' => 'Blue Green', 'name' => 'Test Name', 'created' => '2006-12-25 05:23:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:36', 'mytime' => '22:57:17'), + array('id' => '5', 'apple_id' => '5', 'color' => 'Green', 'name' => 'Blue Green', 'created' => '2006-12-25 05:24:06', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:16', 'mytime' => '22:57:17'), + array('id' => '6', 'apple_id' => '4', 'color' => 'My new appleOrange', 'name' => 'My new apple', 'created' => '2006-12-25 05:29:39', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:39', 'mytime' => '22:57:17'), + array('id' => '7', 'apple_id' => '6', 'color' => 'Some wierd color', 'name' => 'Some odd color', 'created' => '2006-12-25 05:34:21', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:34:21', 'mytime' => '22:57:17') + ); + //$this->assertEqual($Apple->find('all'), $expected); + } + /** + * testBehaviorBelongsToFindCallbacks method + * + * @access public + * @return void + */ + function testBehaviorBelongsToFindCallbacks() { + $Apple = new Apple(); + $Apple->unbindModel(array('hasMany' => array('Child'), 'hasOne' => array('Sample')), false); + $expected = $Apple->find('all'); + + $Apple->unbindModel(array('belongsTo' => array('Parent'))); + $wellBehaved = $Apple->find('all'); + $Apple->Parent->Behaviors->attach('Test'); + $Apple->unbindModel(array('belongsTo' => array('Parent'))); + $this->assertIdentical($Apple->find('all'), $wellBehaved); + + $Apple->Parent->Behaviors->attach('Test', array('before' => 'off')); + $this->assertIdentical($Apple->find('all'), $expected); + + $Apple->Parent->Behaviors->attach('Test', array('before' => 'test')); + $this->assertIdentical($Apple->find('all'), $expected); + + $Apple->Parent->Behaviors->attach('Test', array('before' => 'modify')); + $expected2 = array( + array( + 'Apple' => array('id' => 1), + 'Parent' => array('id' => 2,'name' => 'Bright Red Apple', 'mytime' => '22:57:17')), + array( + 'Apple' => array('id' => 2), + 'Parent' => array('id' => 1, 'name' => 'Red Apple 1', 'mytime' => '22:57:17')), + array( + 'Apple' => array('id' => 3), + 'Parent' => array('id' => 2,'name' => 'Bright Red Apple', 'mytime' => '22:57:17')) + ); + $result = $Apple->find('all', array( + 'fields' => array('Apple.id', 'Parent.id', 'Parent.name', 'Parent.mytime'), + 'conditions' => array('Apple.id <' => '4') + )); + $this->assertEqual($result, $expected2); + + $Apple->Parent->Behaviors->disable('Test'); + $result = $Apple->find('all'); + $this->assertEqual($result, $expected); + + $Apple->Parent->Behaviors->attach('Test', array('after' => 'off')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Parent->Behaviors->attach('Test', array('after' => 'test')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Parent->Behaviors->attach('Test', array('after' => 'test2')); + $this->assertEqual($Apple->find('all'), $expected); + + $Apple->Parent->Behaviors->attach('Test', array('after' => 'modify')); + $expected = array( + array('id' => '1', 'apple_id' => '2', 'color' => 'Red 1', 'name' => 'Red Apple 1', 'created' => '2006-11-22 10:38:58', 'date' => '1951-01-04', 'modified' => '2006-12-01 13:31:26', 'mytime' => '22:57:17'), + array('id' => '2', 'apple_id' => '1', 'color' => 'Bright Red 1', 'name' => 'Bright Red Apple', 'created' => '2006-11-22 10:43:13', 'date' => '2014-01-01', 'modified' => '2006-11-30 18:38:10', 'mytime' => '22:57:17'), + array('id' => '3', 'apple_id' => '2', 'color' => 'blue green', 'name' => 'green blue', 'created' => '2006-12-25 05:13:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:24', 'mytime' => '22:57:17'), + array('id' => '4', 'apple_id' => '2', 'color' => 'Blue Green', 'name' => 'Test Name', 'created' => '2006-12-25 05:23:36', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:23:36', 'mytime' => '22:57:17'), + array('id' => '5', 'apple_id' => '5', 'color' => 'Green', 'name' => 'Blue Green', 'created' => '2006-12-25 05:24:06', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:16', 'mytime' => '22:57:17'), + array('id' => '6', 'apple_id' => '4', 'color' => 'My new appleOrange', 'name' => 'My new apple', 'created' => '2006-12-25 05:29:39', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:29:39', 'mytime' => '22:57:17'), + array('id' => '7', 'apple_id' => '6', 'color' => 'Some wierd color', 'name' => 'Some odd color', 'created' => '2006-12-25 05:34:21', 'date' => '2006-12-25', 'modified' => '2006-12-25 05:34:21', 'mytime' => '22:57:17') + ); + //$this->assertEqual($Apple->find('all'), $expected); + } + +/** + * testBehaviorSaveCallbacks method + * + * @access public + * @return void + */ + function testBehaviorSaveCallbacks() { + $Sample = new Sample(); + $record = array('Sample' => array('apple_id' => 6, 'name' => 'sample99')); + + $Sample->Behaviors->attach('Test', array('beforeSave' => 'on')); + $Sample->create(); + $this->assertIdentical($Sample->save($record), false); + + $Sample->Behaviors->attach('Test', array('beforeSave' => 'off')); + $Sample->create(); + $this->assertIdentical($Sample->save($record), $record); + + $Sample->Behaviors->attach('Test', array('beforeSave' => 'test')); + $Sample->create(); + $this->assertIdentical($Sample->save($record), $record); + + $Sample->Behaviors->attach('Test', array('beforeSave' => 'modify')); + $expected = Set::insert($record, 'Sample.name', 'sample99 modified before'); + $Sample->create(); + $this->assertIdentical($Sample->save($record), $expected); + + $Sample->Behaviors->disable('Test'); + $this->assertIdentical($Sample->save($record), $record); + + $Sample->Behaviors->attach('Test', array('beforeSave' => 'off', 'afterSave' => 'on')); + $expected = Set::merge($record, array('Sample' => array('aftersave' => 'modified after on create'))); + $Sample->create(); + $this->assertIdentical($Sample->save($record), $expected); + + $Sample->Behaviors->attach('Test', array('beforeSave' => 'modify', 'afterSave' => 'modify')); + $expected = Set::merge($record, array('Sample' => array('name' => 'sample99 modified before modified after on create'))); + $Sample->create(); + $this->assertIdentical($Sample->save($record), $expected); + + $Sample->Behaviors->attach('Test', array('beforeSave' => 'off', 'afterSave' => 'test')); + $Sample->create(); + $this->assertIdentical($Sample->save($record), $record); + + $Sample->Behaviors->attach('Test', array('afterSave' => 'test2')); + $Sample->create(); + $this->assertIdentical($Sample->save($record), $record); + + $Sample->Behaviors->attach('Test', array('beforeFind' => 'off', 'afterFind' => 'off')); + $Sample->recursive = -1; + $record2 = $Sample->read(null, 1); + + $Sample->Behaviors->attach('Test', array('afterSave' => 'on')); + $expected = Set::merge($record2, array('Sample' => array('aftersave' => 'modified after'))); + $Sample->create(); + $this->assertIdentical($Sample->save($record2), $expected); + + $Sample->Behaviors->attach('Test', array('afterSave' => 'modify')); + $expected = Set::merge($record2, array('Sample' => array('name' => 'sample1 modified after'))); + $Sample->create(); + $this->assertIdentical($Sample->save($record2), $expected); + } + +/** + * testBehaviorDeleteCallbacks method + * + * @access public + * @return void + */ + function testBehaviorDeleteCallbacks() { + $Apple = new Apple(); + + $Apple->Behaviors->attach('Test', array('beforeFind' => 'off', 'beforeDelete' => 'off')); + $this->assertIdentical($Apple->delete(6), true); + + $Apple->Behaviors->attach('Test', array('beforeDelete' => 'on')); + $this->assertIdentical($Apple->delete(4), false); + + $Apple->Behaviors->attach('Test', array('beforeDelete' => 'test2')); + if (ob_start()) { + $results = $Apple->delete(4); + $this->assertIdentical(trim(ob_get_clean()), 'beforeDelete success (cascading)'); + $this->assertIdentical($results, true); + } + if (ob_start()) { + $results = $Apple->delete(3, false); + $this->assertIdentical(trim(ob_get_clean()), 'beforeDelete success'); + $this->assertIdentical($results, true); + } + + $Apple->Behaviors->attach('Test', array('beforeDelete' => 'off', 'afterDelete' => 'on')); + if (ob_start()) { + $results = $Apple->delete(2, false); + $this->assertIdentical(trim(ob_get_clean()), 'afterDelete success'); + $this->assertIdentical($results, true); + } + } + /** + * testBehaviorOnErrorCallback method + * + * @access public + * @return void + */ + function testBehaviorOnErrorCallback() { + $Apple = new Apple(); + + $Apple->Behaviors->attach('Test', array('beforeFind' => 'off', 'onError' => 'on')); + if (ob_start()) { + $Apple->Behaviors->Test->onError($Apple); + $this->assertIdentical(trim(ob_get_clean()), 'onError trigger success'); + } + + if (ob_start()) { + $Apple->delete(99); + //$this->assertIdentical(trim(ob_get_clean()), 'onError trigger success'); + } + } + /** + * testBehaviorValidateCallback method + * + * @access public + * @return void + */ + function testBehaviorValidateCallback() { + $Apple = new Apple(); + + $Apple->Behaviors->attach('Test'); + $this->assertIdentical($Apple->validates(), true); + + $Apple->Behaviors->attach('Test', array('validate' => 'on')); + $this->assertIdentical($Apple->validates(), false); + $this->assertIdentical($Apple->validationErrors, array('name' => true)); + + $Apple->Behaviors->attach('Test', array('validate' => 'stop')); + $this->assertIdentical($Apple->validates(), false); + $this->assertIdentical($Apple->validationErrors, array('name' => true)); + + $Apple->Behaviors->attach('Test', array('validate' => 'whitelist')); + $Apple->validates(); + $this->assertIdentical($Apple->whitelist, array()); + + $Apple->whitelist = array('unknown'); + $Apple->validates(); + $this->assertIdentical($Apple->whitelist, array('unknown', 'name')); + } + +/** + * testBehaviorValidateMethods method + * + * @access public + * @return void + */ + function testBehaviorValidateMethods() { + $Apple = new Apple(); + $Apple->Behaviors->attach('Test'); + $Apple->validate['color'] = 'validateField'; + + $result = $Apple->save(array('name' => 'Genetically Modified Apple', 'color' => 'Orange')); + $this->assertEqual(array_keys($result['Apple']), array('name', 'color', 'modified', 'created')); + + $Apple->create(); + $result = $Apple->save(array('name' => 'Regular Apple', 'color' => 'Red')); + $this->assertFalse($result); + } + +/** + * testBehaviorMethodDispatching method + * + * @access public + * @return void + */ + function testBehaviorMethodDispatching() { + $Apple = new Apple(); + $Apple->Behaviors->attach('Test'); + + $expected = 'working'; + $this->assertEqual($Apple->testMethod(), $expected); + $this->assertEqual($Apple->Behaviors->dispatchMethod($Apple, 'testMethod'), $expected); + + $result = $Apple->Behaviors->dispatchMethod($Apple, 'wtf'); + $this->assertEqual($result, array('unhandled')); + + $result = $Apple->{'look for the remote'}('in the couch'); + $expected = "Item.name = 'the remote' AND Location.name = 'the couch'"; + $this->assertEqual($result, $expected); + } + +/** + * testBehaviorMethodDispatchingWithData method + * + * @access public + * @return void + */ + function testBehaviorMethodDispatchingWithData() { + $Apple = new Apple(); + $Apple->Behaviors->attach('Test'); + + $Apple->set('field', 'value'); + $this->assertTrue($Apple->testData()); + $this->assertTrue($Apple->data['Apple']['field_2']); + + $this->assertTrue($Apple->testData('one', 'two', 'three', 'four', 'five', 'six')); + } + +/** + * testBehaviorTrigger method + * + * @access public + * @return void + */ + function testBehaviorTrigger() { + $Apple =& new Apple(); + $Apple->Behaviors->attach('Test'); + $Apple->Behaviors->attach('Test2'); + $Apple->Behaviors->attach('Test3'); + + $Apple->beforeTestResult = array(); + $Apple->Behaviors->trigger($Apple, 'beforeTest'); + $expected = array('testbehavior', 'test2behavior', 'test3behavior'); + $this->assertIdentical($Apple->beforeTestResult, $expected); + + $Apple->beforeTestResult = array(); + $Apple->Behaviors->trigger($Apple, 'beforeTest', array(), array('break' => true, 'breakOn' => 'test2behavior')); + $expected = array('testbehavior', 'test2behavior'); + $this->assertIdentical($Apple->beforeTestResult, $expected); + + $Apple->beforeTestResult = array(); + $Apple->Behaviors->trigger($Apple, 'beforeTest', array(), array('break' => true, 'breakOn' => array('test2behavior', 'test3behavior'))); + $expected = array('testbehavior', 'test2behavior'); + $this->assertIdentical($Apple->beforeTestResult, $expected); + } + +/** + * undocumented function + * + * @return void + * @access public + */ + function testBindModelCallsInBehaviors() { + $this->loadFixtures('Article', 'Comment'); + + // hasMany + $Article = new Article(); + $Article->unbindModel(array('hasMany' => array('Comment'))); + $result = $Article->find('first'); + $this->assertFalse(array_key_exists('Comment', $result)); + + $Article->Behaviors->attach('Test4'); + $result = $Article->find('first'); + $this->assertTrue(array_key_exists('Comment', $result)); + + // belongsTo + $Article->unbindModel(array('belongsTo' => array('User'))); + $result = $Article->find('first'); + $this->assertFalse(array_key_exists('User', $result)); + + $Article->Behaviors->attach('Test5'); + $result = $Article->find('first'); + $this->assertTrue(array_key_exists('User', $result)); + + // hasAndBelongsToMany + $Article->unbindModel(array('hasAndBelongsToMany' => array('Tag'))); + $result = $Article->find('first'); + $this->assertFalse(array_key_exists('Tag', $result)); + + $Article->Behaviors->attach('Test6'); + $result = $Article->find('first'); + $this->assertTrue(array_key_exists('Comment', $result)); + + // hasOne + $Comment = new Comment(); + $Comment->unbindModel(array('hasOne' => array('Attachment'))); + $result = $Comment->find('first'); + $this->assertFalse(array_key_exists('Attachment', $result)); + + $Comment->Behaviors->attach('Test7'); + $result = $Comment->find('first'); + $this->assertTrue(array_key_exists('Attachment', $result)); + } + +/** + * Test attach and detaching + * + * @access public + * @return void + */ + function testBehaviorAttachAndDetach() { + $Sample =& new Sample(); + $Sample->actsAs = array('Test3' => array('bar'), 'Test2' => array('foo', 'bar')); + $Sample->Behaviors->init($Sample->alias, $Sample->actsAs); + $Sample->Behaviors->attach('Test2'); + $Sample->Behaviors->detach('Test3'); + + $Sample->Behaviors->trigger($Sample, 'beforeTest'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_delete.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_delete.test.php new file mode 100644 index 000000000..668a61317 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_delete.test.php @@ -0,0 +1,754 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once dirname(__FILE__) . DS . 'model.test.php'; + +/** + * ModelDeleteTest + * + * @package cake + * @subpackage cake.tests.cases.libs.model.operations + */ +class ModelDeleteTest extends BaseModelTest { + +/** + * testDeleteHabtmReferenceWithConditions method + * + * @access public + * @return void + */ + function testDeleteHabtmReferenceWithConditions() { + $this->loadFixtures('Portfolio', 'Item', 'ItemsPortfolio'); + + $Portfolio =& new Portfolio(); + $Portfolio->hasAndBelongsToMany['Item']['conditions'] = array('ItemsPortfolio.item_id >' => 1); + + $result = $Portfolio->find('first', array( + 'conditions' => array('Portfolio.id' => 1) + )); + $expected = array( + array( + 'id' => 3, + 'syfile_id' => 3, + 'published' => 0, + 'name' => 'Item 3', + 'ItemsPortfolio' => array( + 'id' => 3, + 'item_id' => 3, + 'portfolio_id' => 1 + )), + array( + 'id' => 4, + 'syfile_id' => 4, + 'published' => 0, + 'name' => 'Item 4', + 'ItemsPortfolio' => array( + 'id' => 4, + 'item_id' => 4, + 'portfolio_id' => 1 + )), + array( + 'id' => 5, + 'syfile_id' => 5, + 'published' => 0, + 'name' => 'Item 5', + 'ItemsPortfolio' => array( + 'id' => 5, + 'item_id' => 5, + 'portfolio_id' => 1 + ))); + $this->assertEqual($result['Item'], $expected); + + $result = $Portfolio->ItemsPortfolio->find('all', array( + 'conditions' => array('ItemsPortfolio.portfolio_id' => 1) + )); + $expected = array( + array( + 'ItemsPortfolio' => array( + 'id' => 1, + 'item_id' => 1, + 'portfolio_id' => 1 + )), + array( + 'ItemsPortfolio' => array( + 'id' => 3, + 'item_id' => 3, + 'portfolio_id' => 1 + )), + array( + 'ItemsPortfolio' => array( + 'id' => 4, + 'item_id' => 4, + 'portfolio_id' => 1 + )), + array( + 'ItemsPortfolio' => array( + 'id' => 5, + 'item_id' => 5, + 'portfolio_id' => 1 + ))); + $this->assertEqual($result, $expected); + + $Portfolio->delete(1); + + $result = $Portfolio->find('first', array( + 'conditions' => array('Portfolio.id' => 1) + )); + $this->assertFalse($result); + + $result = $Portfolio->ItemsPortfolio->find('all', array( + 'conditions' => array('ItemsPortfolio.portfolio_id' => 1) + )); + $this->assertFalse($result); + } + +/** + * testDeleteArticleBLinks method + * + * @access public + * @return void + */ + function testDeleteArticleBLinks() { + $this->loadFixtures('Article', 'ArticlesTag', 'Tag'); + $TestModel =& new ArticleB(); + + $result = $TestModel->ArticlesTag->find('all'); + $expected = array( + array('ArticlesTag' => array('article_id' => '1', 'tag_id' => '1')), + array('ArticlesTag' => array('article_id' => '1', 'tag_id' => '2')), + array('ArticlesTag' => array('article_id' => '2', 'tag_id' => '1')), + array('ArticlesTag' => array('article_id' => '2', 'tag_id' => '3')) + ); + $this->assertEqual($result, $expected); + + $TestModel->delete(1); + $result = $TestModel->ArticlesTag->find('all'); + + $expected = array( + array('ArticlesTag' => array('article_id' => '2', 'tag_id' => '1')), + array('ArticlesTag' => array('article_id' => '2', 'tag_id' => '3')) + ); + $this->assertEqual($result, $expected); + } + +/** + * testDeleteDependentWithConditions method + * + * @access public + * @return void + */ + function testDeleteDependentWithConditions() { + $this->loadFixtures('Cd','Book','OverallFavorite'); + + $Cd =& new Cd(); + $Book =& new Book(); + $OverallFavorite =& new OverallFavorite(); + + $Cd->delete(1); + + $result = $OverallFavorite->find('all', array( + 'fields' => array('model_type', 'model_id', 'priority') + )); + $expected = array( + array( + 'OverallFavorite' => array( + 'model_type' => 'Book', + 'model_id' => 1, + 'priority' => 2 + ))); + + $this->assertTrue(is_array($result)); + $this->assertEqual($result, $expected); + + $Book->delete(1); + + $result = $OverallFavorite->find('all', array( + 'fields' => array('model_type', 'model_id', 'priority') + )); + $expected = array(); + + $this->assertTrue(is_array($result)); + $this->assertEqual($result, $expected); + } + +/** + * testDel method + * + * @access public + * @return void + */ + function testDelete() { + $this->loadFixtures('Article'); + $TestModel =& new Article(); + + $result = $TestModel->delete(2); + $this->assertTrue($result); + + $result = $TestModel->read(null, 2); + $this->assertFalse($result); + + $TestModel->recursive = -1; + $result = $TestModel->find('all', array( + 'fields' => array('id', 'title') + )); + $expected = array( + array('Article' => array( + 'id' => 1, + 'title' => 'First Article' + )), + array('Article' => array( + 'id' => 3, + 'title' => 'Third Article' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->delete(3); + $this->assertTrue($result); + + $result = $TestModel->read(null, 3); + $this->assertFalse($result); + + $TestModel->recursive = -1; + $result = $TestModel->find('all', array( + 'fields' => array('id', 'title') + )); + $expected = array( + array('Article' => array( + 'id' => 1, + 'title' => 'First Article' + ))); + + $this->assertEqual($result, $expected); + + // make sure deleting a non-existent record doesn't break save() + // ticket #6293 + $this->loadFixtures('Uuid'); + $Uuid =& new Uuid(); + $data = array( + 'B607DAB9-88A2-46CF-B57C-842CA9E3B3B3', + '52C8865C-10EE-4302-AE6C-6E7D8E12E2C8', + '8208C7FE-E89C-47C5-B378-DED6C271F9B8'); + foreach ($data as $id) { + $Uuid->save(array('id' => $id)); + } + $Uuid->delete('52C8865C-10EE-4302-AE6C-6E7D8E12E2C8'); + $Uuid->delete('52C8865C-10EE-4302-AE6C-6E7D8E12E2C8'); + foreach ($data as $id) { + $Uuid->save(array('id' => $id)); + } + $result = $Uuid->find('all', array( + 'conditions' => array('id' => $data), + 'fields' => array('id'), + 'order' => 'id')); + $expected = array( + array('Uuid' => array( + 'id' => '52C8865C-10EE-4302-AE6C-6E7D8E12E2C8')), + array('Uuid' => array( + 'id' => '8208C7FE-E89C-47C5-B378-DED6C271F9B8')), + array('Uuid' => array( + 'id' => 'B607DAB9-88A2-46CF-B57C-842CA9E3B3B3'))); + $this->assertEqual($result, $expected); + } + +/** + * test that delete() updates the correct records counterCache() records. + * + * @return void + */ + function testDeleteUpdatingCounterCacheCorrectly() { + $this->loadFixtures('CounterCacheUser', 'CounterCachePost'); + $User =& new CounterCacheUser(); + + $User->Post->delete(3); + $result = $User->read(null, 301); + $this->assertEqual($result['User']['post_count'], 0); + + $result = $User->read(null, 66); + $this->assertEqual($result['User']['post_count'], 2); + } + +/** + * testDeleteAll method + * + * @access public + * @return void + */ + function testDeleteAll() { + $this->loadFixtures('Article'); + $TestModel =& new Article(); + + $data = array('Article' => array( + 'user_id' => 2, + 'id' => 4, + 'title' => 'Fourth Article', + 'published' => 'N' + )); + $result = $TestModel->set($data) && $TestModel->save(); + $this->assertTrue($result); + + $data = array('Article' => array( + 'user_id' => 2, + 'id' => 5, + 'title' => 'Fifth Article', + 'published' => 'Y' + )); + $result = $TestModel->set($data) && $TestModel->save(); + $this->assertTrue($result); + + $data = array('Article' => array( + 'user_id' => 1, + 'id' => 6, + 'title' => 'Sixth Article', + 'published' => 'N' + )); + $result = $TestModel->set($data) && $TestModel->save(); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->find('all', array( + 'fields' => array('id', 'user_id', 'title', 'published') + )); + + $expected = array( + array('Article' => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'published' => 'Y' + )), + array('Article' => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'published' => 'Y' + )), + array('Article' => array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Article', + 'published' => 'Y')), + array('Article' => array( + 'id' => 4, + 'user_id' => 2, + 'title' => 'Fourth Article', + 'published' => 'N' + )), + array('Article' => array( + 'id' => 5, + 'user_id' => 2, + 'title' => 'Fifth Article', + 'published' => 'Y' + )), + array('Article' => array( + 'id' => 6, + 'user_id' => 1, + 'title' => 'Sixth Article', + 'published' => 'N' + ))); + + $this->assertEqual($result, $expected); + + $result = $TestModel->deleteAll(array('Article.published' => 'N')); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->find('all', array( + 'fields' => array('id', 'user_id', 'title', 'published') + )); + $expected = array( + array('Article' => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'published' => 'Y' + )), + array('Article' => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'published' => 'Y' + )), + array('Article' => array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Article', + 'published' => 'Y' + )), + array('Article' => array( + 'id' => 5, + 'user_id' => 2, + 'title' => 'Fifth Article', + 'published' => 'Y' + ))); + $this->assertEqual($result, $expected); + + $data = array('Article.user_id' => array(2, 3)); + $result = $TestModel->deleteAll($data, true, true); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->find('all', array( + 'fields' => array('id', 'user_id', 'title', 'published') + )); + $expected = array( + array('Article' => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'published' => 'Y' + )), + array('Article' => array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Article', + 'published' => 'Y' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->deleteAll(array('Article.user_id' => 999)); + $this->assertTrue($result, 'deleteAll returned false when all no records matched conditions. %s'); + + $this->expectError(); + ob_start(); + $result = $TestModel->deleteAll(array('Article.non_existent_field' => 999)); + ob_get_clean(); + $this->assertFalse($result, 'deleteAll returned true when find query generated sql error. %s'); + } + +/** + * testRecursiveDel method + * + * @access public + * @return void + */ + function testRecursiveDel() { + $this->loadFixtures('Article', 'Comment', 'Attachment'); + $TestModel =& new Article(); + + $result = $TestModel->delete(2); + $this->assertTrue($result); + + $TestModel->recursive = 2; + $result = $TestModel->read(null, 2); + $this->assertFalse($result); + + $result = $TestModel->Comment->read(null, 5); + $this->assertFalse($result); + + $result = $TestModel->Comment->read(null, 6); + $this->assertFalse($result); + + $result = $TestModel->Comment->Attachment->read(null, 1); + $this->assertFalse($result); + + $result = $TestModel->find('count'); + $this->assertEqual($result, 2); + + $result = $TestModel->Comment->find('count'); + $this->assertEqual($result, 4); + + $result = $TestModel->Comment->Attachment->find('count'); + $this->assertEqual($result, 0); + } + +/** + * testDependentExclusiveDelete method + * + * @access public + * @return void + */ + function testDependentExclusiveDelete() { + $this->loadFixtures('Article', 'Comment'); + $TestModel =& new Article10(); + + $result = $TestModel->find('all'); + $this->assertEqual(count($result[0]['Comment']), 4); + $this->assertEqual(count($result[1]['Comment']), 2); + $this->assertEqual($TestModel->Comment->find('count'), 6); + + $TestModel->delete(1); + $this->assertEqual($TestModel->Comment->find('count'), 2); + } + +/** + * testDeleteLinks method + * + * @access public + * @return void + */ + function testDeleteLinks() { + $this->loadFixtures('Article', 'ArticlesTag', 'Tag'); + $TestModel =& new Article(); + + $result = $TestModel->ArticlesTag->find('all'); + $expected = array( + array('ArticlesTag' => array( + 'article_id' => '1', + 'tag_id' => '1' + )), + array('ArticlesTag' => array( + 'article_id' => '1', + 'tag_id' => '2' + )), + array('ArticlesTag' => array( + 'article_id' => '2', + 'tag_id' => '1' + )), + array('ArticlesTag' => array( + 'article_id' => '2', + 'tag_id' => '3' + ))); + $this->assertEqual($result, $expected); + + $TestModel->delete(1); + $result = $TestModel->ArticlesTag->find('all'); + + $expected = array( + array('ArticlesTag' => array( + 'article_id' => '2', + 'tag_id' => '1' + )), + array('ArticlesTag' => array( + 'article_id' => '2', + 'tag_id' => '3' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->deleteAll(array('Article.user_id' => 999)); + $this->assertTrue($result, 'deleteAll returned false when all no records matched conditions. %s'); + } + +/** + * test deleteLinks with Multiple habtm associations + * + * @return void + */ + function testDeleteLinksWithMultipleHabtmAssociations() { + $this->loadFixtures('JoinA', 'JoinB', 'JoinC', 'JoinAB', 'JoinAC'); + $JoinA =& new JoinA(); + + //create two new join records to expose the issue. + $JoinA->JoinAsJoinC->create(array( + 'join_a_id' => 1, + 'join_c_id' => 2, + )); + $JoinA->JoinAsJoinC->save(); + $JoinA->JoinAsJoinB->create(array( + 'join_a_id' => 1, + 'join_b_id' => 2, + )); + $JoinA->JoinAsJoinB->save(); + + $result = $JoinA->delete(1); + $this->assertTrue($result, 'Delete failed %s'); + + $joinedBs = $JoinA->JoinAsJoinB->find('count', array( + 'conditions' => array('JoinAsJoinB.join_a_id' => 1) + )); + $this->assertEqual($joinedBs, 0, 'JoinA/JoinB link records left over. %s'); + + $joinedBs = $JoinA->JoinAsJoinC->find('count', array( + 'conditions' => array('JoinAsJoinC.join_a_id' => 1) + )); + $this->assertEqual($joinedBs, 0, 'JoinA/JoinC link records left over. %s'); + } + +/** + * testHabtmDeleteLinksWhenNoPrimaryKeyInJoinTable method + * + * @access public + * @return void + */ + function testHabtmDeleteLinksWhenNoPrimaryKeyInJoinTable() { + + $this->loadFixtures('Apple', 'Device', 'ThePaperMonkies'); + $ThePaper =& new ThePaper(); + $ThePaper->id = 1; + $ThePaper->save(array('Monkey' => array(2, 3))); + + $result = $ThePaper->findById(1); + $expected = array( + array( + 'id' => '2', + 'device_type_id' => '1', + 'name' => 'Device 2', + 'typ' => '1' + ), + array( + 'id' => '3', + 'device_type_id' => '1', + 'name' => 'Device 3', + 'typ' => '2' + )); + $this->assertEqual($result['Monkey'], $expected); + + $ThePaper =& new ThePaper(); + $ThePaper->id = 2; + $ThePaper->save(array('Monkey' => array(2, 3))); + + $result = $ThePaper->findById(2); + $expected = array( + array( + 'id' => '2', + 'device_type_id' => '1', + 'name' => 'Device 2', + 'typ' => '1' + ), + array( + 'id' => '3', + 'device_type_id' => '1', + 'name' => 'Device 3', + 'typ' => '2' + )); + $this->assertEqual($result['Monkey'], $expected); + + $ThePaper->delete(1); + $result = $ThePaper->findById(2); + $expected = array( + array( + 'id' => '2', + 'device_type_id' => '1', + 'name' => 'Device 2', + 'typ' => '1' + ), + array( + 'id' => '3', + 'device_type_id' => '1', + 'name' => 'Device 3', + 'typ' => '2' + )); + $this->assertEqual($result['Monkey'], $expected); + } + +/** + * test that beforeDelete returning false can abort deletion. + * + * @return void + */ + function testBeforeDeleteDeleteAbortion() { + $this->loadFixtures('Post'); + $Model =& new CallbackPostTestModel(); + $Model->beforeDeleteReturn = false; + + $result = $Model->delete(1); + $this->assertFalse($result); + + $exists = $Model->findById(1); + $this->assertTrue(is_array($exists)); + } + +/** + * test for a habtm deletion error that occurs in postgres but should not. + * And should not occur in any dbo. + * + * @return void + */ + function testDeleteHabtmPostgresFailure() { + $this->loadFixtures('Article', 'Tag', 'ArticlesTag'); + + $Article =& ClassRegistry::init('Article'); + $Article->hasAndBelongsToMany['Tag']['unique'] = true; + + $Tag =& ClassRegistry::init('Tag'); + $Tag->bindModel(array('hasAndBelongsToMany' => array( + 'Article' => array( + 'className' => 'Article', + 'unique' => true + ) + )), true); + + // Article 1 should have Tag.1 and Tag.2 + $before = $Article->find("all", array( + "conditions" => array("Article.id" => 1), + )); + $this->assertEqual(count($before[0]['Tag']), 2, 'Tag count for Article.id = 1 is incorrect, should be 2 %s'); + + // From now on, Tag #1 is only associated with Post #1 + $submitted_data = array( + "Tag" => array("id" => 1, 'tag' => 'tag1'), + "Article" => array( + "Article" => array(1) + ) + ); + $Tag->save($submitted_data); + + // One more submission (The other way around) to make sure the reverse save looks good. + $submitted_data = array( + "Article" => array("id" => 2, 'title' => 'second article'), + "Tag" => array( + "Tag" => array(2, 3) + ) + ); + // ERROR: + // Postgresql: DELETE FROM "articles_tags" WHERE tag_id IN ('1', '3') + // MySQL: DELETE `ArticlesTag` FROM `articles_tags` AS `ArticlesTag` WHERE `ArticlesTag`.`article_id` = 2 AND `ArticlesTag`.`tag_id` IN (1, 3) + $Article->save($submitted_data); + + // Want to make sure Article #1 has Tag #1 and Tag #2 still. + $after = $Article->find("all", array( + "conditions" => array("Article.id" => 1), + )); + + // Removing Article #2 from Tag #1 is all that should have happened. + $this->assertEqual(count($before[0]["Tag"]), count($after[0]["Tag"])); + } + +/** + * test that deleting records inside the beforeDelete doesn't truncate the table. + * + * @return void + */ + function testBeforeDeleteWipingTable() { + $this->loadFixtures('Comment'); + + $Comment =& new BeforeDeleteComment(); + // Delete 3 records. + $Comment->delete(4); + $result = $Comment->find('count'); + + $this->assertTrue($result > 1, 'Comments are all gone.'); + $Comment->create(array( + 'article_id' => 1, + 'user_id' => 2, + 'comment' => 'new record', + 'published' => 'Y' + )); + $Comment->save(); + + $Comment->delete(5); + $result = $Comment->find('count'); + + $this->assertTrue($result > 1, 'Comments are all gone.'); + } + +/** + * test that deleting the same record from the beforeDelete and the delete doesn't truncate the table. + * + * @return void + */ + function testBeforeDeleteWipingTableWithDuplicateDelete() { + $this->loadFixtures('Comment'); + + $Comment =& new BeforeDeleteComment(); + $Comment->delete(1); + + $result = $Comment->find('count'); + $this->assertTrue($result > 1, 'Comments are all gone.'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_integration.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_integration.test.php new file mode 100644 index 000000000..3d951e434 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_integration.test.php @@ -0,0 +1,1930 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once dirname(__FILE__) . DS . 'model.test.php'; +App::import('Core', 'DboSource'); + +/** + * DboMock class + * A Dbo Source driver to mock a connection and a identity name() method + */ +class DboMock extends DboSource { + +/** +* Returns the $field without modifications +*/ + function name($field) { + return $field; + } +/** +* Returns true to fake a database connection +*/ + function connect() { + return true; + } +} + +/** + * ModelIntegrationTest + * + * @package cake + * @subpackage cake.tests.cases.libs.model.operations + */ +class ModelIntegrationTest extends BaseModelTest { + +/** + * testPkInHAbtmLinkModelArticleB + * + * @access public + * @return void + */ + function testPkInHabtmLinkModelArticleB() { + $this->loadFixtures('Article', 'Tag'); + $TestModel2 =& new ArticleB(); + $this->assertEqual($TestModel2->ArticlesTag->primaryKey, 'article_id'); + } + +/** + * Tests that $cacheSources can only be disabled in the db using model settings, not enabled + * + * @access public + * @return void + */ + function testCacheSourcesDisabling() { + $this->db->cacheSources = true; + $TestModel = new JoinA(); + $TestModel->cacheSources = false; + $TestModel->setSource('join_as'); + $this->assertFalse($this->db->cacheSources); + + $this->db->cacheSources = false; + $TestModel = new JoinA(); + $TestModel->cacheSources = true; + $TestModel->setSource('join_as'); + $this->assertFalse($this->db->cacheSources); + } + +/** + * testPkInHabtmLinkModel method + * + * @access public + * @return void + */ + function testPkInHabtmLinkModel() { + //Test Nonconformant Models + $this->loadFixtures('Content', 'ContentAccount', 'Account'); + $TestModel =& new Content(); + $this->assertEqual($TestModel->ContentAccount->primaryKey, 'iContentAccountsId'); + + //test conformant models with no PK in the join table + $this->loadFixtures('Article', 'Tag'); + $TestModel2 =& new Article(); + $this->assertEqual($TestModel2->ArticlesTag->primaryKey, 'article_id'); + + //test conformant models with PK in join table + $this->loadFixtures('Item', 'Portfolio', 'ItemsPortfolio'); + $TestModel3 =& new Portfolio(); + $this->assertEqual($TestModel3->ItemsPortfolio->primaryKey, 'id'); + + //test conformant models with PK in join table - join table contains extra field + $this->loadFixtures('JoinA', 'JoinB', 'JoinAB'); + $TestModel4 =& new JoinA(); + $this->assertEqual($TestModel4->JoinAsJoinB->primaryKey, 'id'); + + } + +/** + * testDynamicBehaviorAttachment method + * + * @access public + * @return void + */ + function testDynamicBehaviorAttachment() { + $this->loadFixtures('Apple'); + $TestModel =& new Apple(); + $this->assertEqual($TestModel->Behaviors->attached(), array()); + + $TestModel->Behaviors->attach('Tree', array('left' => 'left_field', 'right' => 'right_field')); + $this->assertTrue(is_object($TestModel->Behaviors->Tree)); + $this->assertEqual($TestModel->Behaviors->attached(), array('Tree')); + + $expected = array( + 'parent' => 'parent_id', + 'left' => 'left_field', + 'right' => 'right_field', + 'scope' => '1 = 1', + 'type' => 'nested', + '__parentChange' => false, + 'recursive' => -1 + ); + + $this->assertEqual($TestModel->Behaviors->Tree->settings['Apple'], $expected); + + $expected['enabled'] = false; + $TestModel->Behaviors->attach('Tree', array('enabled' => false)); + $this->assertEqual($TestModel->Behaviors->Tree->settings['Apple'], $expected); + $this->assertEqual($TestModel->Behaviors->attached(), array('Tree')); + + $TestModel->Behaviors->detach('Tree'); + $this->assertEqual($TestModel->Behaviors->attached(), array()); + $this->assertFalse(isset($TestModel->Behaviors->Tree)); + } + +/** + * Tests cross database joins. Requires $test and $test2 to both be set in DATABASE_CONFIG + * NOTE: When testing on MySQL, you must set 'persistent' => false on *both* database connections, + * or one connection will step on the other. + */ + function testCrossDatabaseJoins() { + $config = new DATABASE_CONFIG(); + + $skip = $this->skipIf( + !isset($config->test) || !isset($config->test2), + '%s Primary and secondary test databases not configured, skipping cross-database ' + .'join tests.' + .' To run these tests, you must define $test and $test2 in your database configuration.' + ); + + if ($skip) { + return; + } + + $this->loadFixtures('Article', 'Tag', 'ArticlesTag', 'User', 'Comment'); + $TestModel =& new Article(); + + $expected = array( + array( + 'Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + ), + array( + 'id' => '3', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Third Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:49:23', + 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => '4', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + )), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ))), + array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ), + 'Comment' => array( + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + )), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))), + array( + 'Article' => array( + 'id' => '3', + 'user_id' => '1', + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array(), + 'Tag' => array() + )); + $this->assertEqual($TestModel->find('all'), $expected); + + $db2 =& ConnectionManager::getDataSource('test2'); + + foreach (array('User', 'Comment') as $class) { + $this->_fixtures[$this->_fixtureClassMap[$class]]->create($db2); + $this->_fixtures[$this->_fixtureClassMap[$class]]->insert($db2); + $this->db->truncate(Inflector::pluralize(Inflector::underscore($class))); + } + + $this->assertEqual($TestModel->User->find('all'), array()); + $this->assertEqual($TestModel->Comment->find('all'), array()); + $this->assertEqual($TestModel->find('count'), 3); + + $TestModel->User->setDataSource('test2'); + $TestModel->Comment->setDataSource('test2'); + + foreach ($expected as $key => $value) { + unset($value['Comment'], $value['Tag']); + $expected[$key] = $value; + } + + $TestModel->recursive = 0; + $result = $TestModel->find('all'); + $this->assertEqual($result, $expected); + + foreach ($expected as $key => $value) { + unset($value['Comment'], $value['Tag']); + $expected[$key] = $value; + } + + $TestModel->recursive = 0; + $result = $TestModel->find('all'); + $this->assertEqual($result, $expected); + + $result = Set::extract($TestModel->User->find('all'), '{n}.User.id'); + $this->assertEqual($result, array('1', '2', '3', '4')); + $this->assertEqual($TestModel->find('all'), $expected); + + $TestModel->Comment->unbindModel(array('hasOne' => array('Attachment'))); + $expected = array( + array( + 'Comment' => array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + ), + 'Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + array( + 'Comment' => array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + ), + 'User' => array( + 'id' => '4', + 'user' => 'garrett', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', + 'updated' => '2007-03-17 01:24:31' + ), + 'Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + array( + 'Comment' => array( + 'id' => '3', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Third Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:49:23', + 'updated' => '2007-03-18 10:51:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + array( + 'Comment' => array( + 'id' => '4', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + array( + 'Comment' => array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + )), + array( + 'Comment' => array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + ), + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + ), + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ))); + $this->assertEqual($TestModel->Comment->find('all'), $expected); + + foreach (array('User', 'Comment') as $class) { + $this->_fixtures[$this->_fixtureClassMap[$class]]->drop($db2); + } + } + +/** + * testDisplayField method + * + * @access public + * @return void + */ + function testDisplayField() { + $this->loadFixtures('Post', 'Comment', 'Person'); + $Post = new Post(); + $Comment = new Comment(); + $Person = new Person(); + + $this->assertEqual($Post->displayField, 'title'); + $this->assertEqual($Person->displayField, 'name'); + $this->assertEqual($Comment->displayField, 'id'); + } + +/** + * testSchema method + * + * @access public + * @return void + */ + function testSchema() { + $Post = new Post(); + + $result = $Post->schema(); + $columns = array('id', 'author_id', 'title', 'body', 'published', 'created', 'updated'); + $this->assertEqual(array_keys($result), $columns); + + $types = array('integer', 'integer', 'string', 'text', 'string', 'datetime', 'datetime'); + $this->assertEqual(Set::extract(array_values($result), '{n}.type'), $types); + + $result = $Post->schema('body'); + $this->assertEqual($result['type'], 'text'); + $this->assertNull($Post->schema('foo')); + + $this->assertEqual($Post->getColumnTypes(), array_combine($columns, $types)); + } + +/** + * test deconstruct() with time fields. + * + * @return void + */ + function testDeconstructFieldsTime() { + $this->loadFixtures('Apple'); + $TestModel =& new Apple(); + + $data = array(); + $data['Apple']['mytime']['hour'] = ''; + $data['Apple']['mytime']['min'] = ''; + $data['Apple']['mytime']['sec'] = ''; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('mytime'=> '')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['mytime']['hour'] = ''; + $data['Apple']['mytime']['min'] = ''; + $data['Apple']['mytime']['meridan'] = ''; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('mytime'=> '')); + $this->assertEqual($TestModel->data, $expected, 'Empty values are not returning properly. %s'); + + $data = array(); + $data['Apple']['mytime']['hour'] = '12'; + $data['Apple']['mytime']['min'] = '0'; + $data['Apple']['mytime']['meridian'] = 'am'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('mytime'=> '00:00:00')); + $this->assertEqual($TestModel->data, $expected, 'Midnight is not returning proper values. %s'); + + $data = array(); + $data['Apple']['mytime']['hour'] = '00'; + $data['Apple']['mytime']['min'] = '00'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('mytime'=> '00:00:00')); + $this->assertEqual($TestModel->data, $expected, 'Midnight is not returning proper values. %s'); + + $data = array(); + $data['Apple']['mytime']['hour'] = '03'; + $data['Apple']['mytime']['min'] = '04'; + $data['Apple']['mytime']['sec'] = '04'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('mytime'=> '03:04:04')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['mytime']['hour'] = '3'; + $data['Apple']['mytime']['min'] = '4'; + $data['Apple']['mytime']['sec'] = '4'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple' => array('mytime'=> '03:04:04')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['mytime']['hour'] = '03'; + $data['Apple']['mytime']['min'] = '4'; + $data['Apple']['mytime']['sec'] = '4'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('mytime'=> '03:04:04')); + $this->assertEqual($TestModel->data, $expected); + + $db = ConnectionManager::getDataSource('test_suite'); + $data = array(); + $data['Apple']['mytime'] = $db->expression('NOW()'); + $TestModel->data = null; + $TestModel->set($data); + $this->assertEqual($TestModel->data, $data); + } + +/** + * testDeconstructFields with datetime, timestamp, and date fields + * + * @access public + * @return void + */ + function testDeconstructFieldsDateTime() { + $this->loadFixtures('Apple'); + $TestModel =& new Apple(); + + //test null/empty values first + $data['Apple']['created']['year'] = ''; + $data['Apple']['created']['month'] = ''; + $data['Apple']['created']['day'] = ''; + $data['Apple']['created']['hour'] = ''; + $data['Apple']['created']['min'] = ''; + $data['Apple']['created']['sec'] = ''; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('created'=> '')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['date']['year'] = ''; + $data['Apple']['date']['month'] = ''; + $data['Apple']['date']['day'] = ''; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('date'=> '')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['year'] = '2007'; + $data['Apple']['created']['month'] = '08'; + $data['Apple']['created']['day'] = '20'; + $data['Apple']['created']['hour'] = ''; + $data['Apple']['created']['min'] = ''; + $data['Apple']['created']['sec'] = ''; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('created'=> '2007-08-20 00:00:00')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['year'] = '2007'; + $data['Apple']['created']['month'] = '08'; + $data['Apple']['created']['day'] = '20'; + $data['Apple']['created']['hour'] = '10'; + $data['Apple']['created']['min'] = '12'; + $data['Apple']['created']['sec'] = ''; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('created'=> '2007-08-20 10:12:00')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['year'] = '2007'; + $data['Apple']['created']['month'] = ''; + $data['Apple']['created']['day'] = '12'; + $data['Apple']['created']['hour'] = '20'; + $data['Apple']['created']['min'] = ''; + $data['Apple']['created']['sec'] = ''; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('created'=> '')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['hour'] = '20'; + $data['Apple']['created']['min'] = '33'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('created'=> '')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['hour'] = '20'; + $data['Apple']['created']['min'] = '33'; + $data['Apple']['created']['sec'] = '33'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('created'=> '')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['hour'] = '13'; + $data['Apple']['created']['min'] = '00'; + $data['Apple']['date']['year'] = '2006'; + $data['Apple']['date']['month'] = '12'; + $data['Apple']['date']['day'] = '25'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array( + 'Apple'=> array( + 'created'=> '', + 'date'=> '2006-12-25' + )); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['year'] = '2007'; + $data['Apple']['created']['month'] = '08'; + $data['Apple']['created']['day'] = '20'; + $data['Apple']['created']['hour'] = '10'; + $data['Apple']['created']['min'] = '12'; + $data['Apple']['created']['sec'] = '09'; + $data['Apple']['date']['year'] = '2006'; + $data['Apple']['date']['month'] = '12'; + $data['Apple']['date']['day'] = '25'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array( + 'Apple'=> array( + 'created'=> '2007-08-20 10:12:09', + 'date'=> '2006-12-25' + )); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['year'] = '--'; + $data['Apple']['created']['month'] = '--'; + $data['Apple']['created']['day'] = '--'; + $data['Apple']['created']['hour'] = '--'; + $data['Apple']['created']['min'] = '--'; + $data['Apple']['created']['sec'] = '--'; + $data['Apple']['date']['year'] = '--'; + $data['Apple']['date']['month'] = '--'; + $data['Apple']['date']['day'] = '--'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('created'=> '', 'date'=> '')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['created']['year'] = '2007'; + $data['Apple']['created']['month'] = '--'; + $data['Apple']['created']['day'] = '20'; + $data['Apple']['created']['hour'] = '10'; + $data['Apple']['created']['min'] = '12'; + $data['Apple']['created']['sec'] = '09'; + $data['Apple']['date']['year'] = '2006'; + $data['Apple']['date']['month'] = '12'; + $data['Apple']['date']['day'] = '25'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('created'=> '', 'date'=> '2006-12-25')); + $this->assertEqual($TestModel->data, $expected); + + $data = array(); + $data['Apple']['date']['year'] = '2006'; + $data['Apple']['date']['month'] = '12'; + $data['Apple']['date']['day'] = '25'; + + $TestModel->data = null; + $TestModel->set($data); + $expected = array('Apple'=> array('date'=> '2006-12-25')); + $this->assertEqual($TestModel->data, $expected); + + $db = ConnectionManager::getDataSource('test_suite'); + $data = array(); + $data['Apple']['modified'] = $db->expression('NOW()'); + $TestModel->data = null; + $TestModel->set($data); + $this->assertEqual($TestModel->data, $data); + } + +/** + * testTablePrefixSwitching method + * + * @access public + * @return void + */ + function testTablePrefixSwitching() { + ConnectionManager::create('database1', + array_merge($this->db->config, array('prefix' => 'aaa_') + )); + ConnectionManager::create('database2', + array_merge($this->db->config, array('prefix' => 'bbb_') + )); + + $db1 = ConnectionManager::getDataSource('database1'); + $db2 = ConnectionManager::getDataSource('database2'); + + $TestModel = new Apple(); + $TestModel->setDataSource('database1'); + $this->assertEqual($this->db->fullTableName($TestModel, false), 'aaa_apples'); + $this->assertEqual($db1->fullTableName($TestModel, false), 'aaa_apples'); + $this->assertEqual($db2->fullTableName($TestModel, false), 'aaa_apples'); + + $TestModel->setDataSource('database2'); + $this->assertEqual($this->db->fullTableName($TestModel, false), 'bbb_apples'); + $this->assertEqual($db1->fullTableName($TestModel, false), 'bbb_apples'); + $this->assertEqual($db2->fullTableName($TestModel, false), 'bbb_apples'); + + $TestModel = new Apple(); + $TestModel->tablePrefix = 'custom_'; + $this->assertEqual($this->db->fullTableName($TestModel, false), 'custom_apples'); + $TestModel->setDataSource('database1'); + $this->assertEqual($this->db->fullTableName($TestModel, false), 'custom_apples'); + $this->assertEqual($db1->fullTableName($TestModel, false), 'custom_apples'); + + $TestModel = new Apple(); + $TestModel->setDataSource('database1'); + $this->assertEqual($this->db->fullTableName($TestModel, false), 'aaa_apples'); + $TestModel->tablePrefix = ''; + $TestModel->setDataSource('database2'); + $this->assertEqual($db2->fullTableName($TestModel, false), 'apples'); + $this->assertEqual($db1->fullTableName($TestModel, false), 'apples'); + + $TestModel->tablePrefix = null; + $TestModel->setDataSource('database1'); + $this->assertEqual($db2->fullTableName($TestModel, false), 'aaa_apples'); + $this->assertEqual($db1->fullTableName($TestModel, false), 'aaa_apples'); + + $TestModel->tablePrefix = false; + $TestModel->setDataSource('database2'); + $this->assertEqual($db2->fullTableName($TestModel, false), 'apples'); + $this->assertEqual($db1->fullTableName($TestModel, false), 'apples'); + } + +/** + * Tests validation parameter order in custom validation methods + * + * @access public + * @return void + */ + function testInvalidAssociation() { + $TestModel =& new ValidationTest1(); + $this->assertNull($TestModel->getAssociated('Foo')); + } + +/** + * testLoadModelSecondIteration method + * + * @access public + * @return void + */ + function testLoadModelSecondIteration() { + $model = new ModelA(); + $this->assertIsA($model,'ModelA'); + + $this->assertIsA($model->ModelB, 'ModelB'); + $this->assertIsA($model->ModelB->ModelD, 'ModelD'); + + $this->assertIsA($model->ModelC, 'ModelC'); + $this->assertIsA($model->ModelC->ModelD, 'ModelD'); + } + +/** + * ensure that exists() does not persist between method calls reset on create + * + * @return void + */ + function testResetOfExistsOnCreate() { + $this->loadFixtures('Article'); + $Article =& new Article(); + $Article->id = 1; + $Article->saveField('title', 'Reset me'); + $Article->delete(); + $Article->id = 1; + $this->assertFalse($Article->exists()); + + $Article->create(); + $this->assertFalse($Article->exists()); + $Article->id = 2; + $Article->saveField('title', 'Staying alive'); + $result = $Article->read(null, 2); + $this->assertEqual($result['Article']['title'], 'Staying alive'); + } + +/** + * testUseTableFalseExistsCheck method + * + * @return void + */ + function testUseTableFalseExistsCheck() { + $this->loadFixtures('Article'); + $Article =& new Article(); + $Article->id = 1337; + $result = $Article->exists(); + $this->assertFalse($result); + + $Article->useTable = false; + $Article->id = null; + $result = $Article->exists(); + $this->assertFalse($result); + + // An article with primary key of '1' has been loaded by the fixtures. + $Article->useTable = false; + $Article->id = 1; + $result = $Article->exists(); + $this->assertTrue($result); + } + +/** + * testPluginAssociations method + * + * @access public + * @return void + */ + function testPluginAssociations() { + $this->loadFixtures('TestPluginArticle', 'User', 'TestPluginComment'); + $TestModel =& new TestPluginArticle(); + + $result = $TestModel->find('all'); + $expected = array( + array( + 'TestPluginArticle' => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Plugin Article', + 'body' => 'First Plugin Article Body', + 'published' => 'Y', + 'created' => '2008-09-24 10:39:23', + 'updated' => '2008-09-24 10:41:31' + ), + 'User' => array( + 'id' => 1, + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'TestPluginComment' => array( + array( + 'id' => 1, + 'article_id' => 1, + 'user_id' => 2, + 'comment' => 'First Comment for First Plugin Article', + 'published' => 'Y', + 'created' => '2008-09-24 10:45:23', + 'updated' => '2008-09-24 10:47:31' + ), + array( + 'id' => 2, + 'article_id' => 1, + 'user_id' => 4, + 'comment' => 'Second Comment for First Plugin Article', + 'published' => 'Y', + 'created' => '2008-09-24 10:47:23', + 'updated' => '2008-09-24 10:49:31' + ), + array( + 'id' => 3, + 'article_id' => 1, + 'user_id' => 1, + 'comment' => 'Third Comment for First Plugin Article', + 'published' => 'Y', + 'created' => '2008-09-24 10:49:23', + 'updated' => '2008-09-24 10:51:31' + ), + array( + 'id' => 4, + 'article_id' => 1, + 'user_id' => 1, + 'comment' => 'Fourth Comment for First Plugin Article', + 'published' => 'N', + 'created' => '2008-09-24 10:51:23', + 'updated' => '2008-09-24 10:53:31' + ))), + array( + 'TestPluginArticle' => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Plugin Article', + 'body' => 'Second Plugin Article Body', + 'published' => 'Y', + 'created' => '2008-09-24 10:41:23', + 'updated' => '2008-09-24 10:43:31' + ), + 'User' => array( + 'id' => 3, + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ), + 'TestPluginComment' => array( + array( + 'id' => 5, + 'article_id' => 2, + 'user_id' => 1, + 'comment' => 'First Comment for Second Plugin Article', + 'published' => 'Y', + 'created' => '2008-09-24 10:53:23', + 'updated' => '2008-09-24 10:55:31' + ), + array( + 'id' => 6, + 'article_id' => 2, + 'user_id' => 2, + 'comment' => 'Second Comment for Second Plugin Article', + 'published' => 'Y', + 'created' => '2008-09-24 10:55:23', + 'updated' => '2008-09-24 10:57:31' + ))), + array( + 'TestPluginArticle' => array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Plugin Article', + 'body' => 'Third Plugin Article Body', + 'published' => 'Y', + 'created' => '2008-09-24 10:43:23', + 'updated' => '2008-09-24 10:45:31' + ), + 'User' => array( + 'id' => 1, + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'TestPluginComment' => array() + )); + + $this->assertEqual($result, $expected); + } + +/** + * Tests getAssociated method + * + * @access public + * @return void + */ + function testGetAssociated() { + $this->loadFixtures('Article'); + $Article = ClassRegistry::init('Article'); + + $assocTypes = array('hasMany', 'hasOne', 'belongsTo', 'hasAndBelongsToMany'); + foreach ($assocTypes as $type) { + $this->assertEqual($Article->getAssociated($type), array_keys($Article->{$type})); + } + + $Article->bindModel(array('hasMany' => array('Category'))); + $this->assertEqual($Article->getAssociated('hasMany'), array('Comment', 'Category')); + + $results = $Article->getAssociated(); + $this->assertEqual(sort(array_keys($results)), array('Category', 'Comment', 'Tag')); + + $Article->unbindModel(array('hasAndBelongsToMany' => array('Tag'))); + $this->assertEqual($Article->getAssociated('hasAndBelongsToMany'), array()); + + $result = $Article->getAssociated('Category'); + $expected = array( + 'className' => 'Category', + 'foreignKey' => 'article_id', + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'dependent' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '', + 'association' => 'hasMany', + ); + $this->assertEqual($result, $expected); + } + +/** + * testAutoConstructAssociations method + * + * @access public + * @return void + */ + function testAutoConstructAssociations() { + $this->loadFixtures('User', 'ArticleFeatured'); + $TestModel =& new AssociationTest1(); + + $result = $TestModel->hasAndBelongsToMany; + $expected = array('AssociationTest2' => array( + 'unique' => false, + 'joinTable' => 'join_as_join_bs', + 'foreignKey' => false, + 'className' => 'AssociationTest2', + 'with' => 'JoinAsJoinB', + 'associationForeignKey' => 'join_b_id', + 'conditions' => '', 'fields' => '', 'order' => '', 'limit' => '', 'offset' => '', + 'finderQuery' => '', 'deleteQuery' => '', 'insertQuery' => '' + )); + $this->assertEqual($result, $expected); + + // Tests related to ticket https://trac.cakephp.org/ticket/5594 + $TestModel =& new ArticleFeatured(); + $TestFakeModel =& new ArticleFeatured(array('table' => false)); + + $expected = array( + 'User' => array( + 'className' => 'User', 'foreignKey' => 'user_id', + 'conditions' => '', 'fields' => '', 'order' => '', 'counterCache' => '' + ), + 'Category' => array( + 'className' => 'Category', 'foreignKey' => 'category_id', + 'conditions' => '', 'fields' => '', 'order' => '', 'counterCache' => '' + ) + ); + $this->assertIdentical($TestModel->belongsTo, $expected); + $this->assertIdentical($TestFakeModel->belongsTo, $expected); + + $this->assertEqual($TestModel->User->name, 'User'); + $this->assertEqual($TestFakeModel->User->name, 'User'); + $this->assertEqual($TestModel->Category->name, 'Category'); + $this->assertEqual($TestFakeModel->Category->name, 'Category'); + + $expected = array( + 'Featured' => array( + 'className' => 'Featured', + 'foreignKey' => 'article_featured_id', + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'dependent' => '' + )); + + $this->assertIdentical($TestModel->hasOne, $expected); + $this->assertIdentical($TestFakeModel->hasOne, $expected); + + $this->assertEqual($TestModel->Featured->name, 'Featured'); + $this->assertEqual($TestFakeModel->Featured->name, 'Featured'); + + $expected = array( + 'Comment' => array( + 'className' => 'Comment', + 'dependent' => true, + 'foreignKey' => 'article_featured_id', + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + )); + + $this->assertIdentical($TestModel->hasMany, $expected); + $this->assertIdentical($TestFakeModel->hasMany, $expected); + + $this->assertEqual($TestModel->Comment->name, 'Comment'); + $this->assertEqual($TestFakeModel->Comment->name, 'Comment'); + + $expected = array( + 'Tag' => array( + 'className' => 'Tag', + 'joinTable' => 'article_featureds_tags', + 'with' => 'ArticleFeaturedsTag', + 'foreignKey' => 'article_featured_id', + 'associationForeignKey' => 'tag_id', + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'unique' => true, + 'finderQuery' => '', + 'deleteQuery' => '', + 'insertQuery' => '' + )); + + $this->assertIdentical($TestModel->hasAndBelongsToMany, $expected); + $this->assertIdentical($TestFakeModel->hasAndBelongsToMany, $expected); + + $this->assertEqual($TestModel->Tag->name, 'Tag'); + $this->assertEqual($TestFakeModel->Tag->name, 'Tag'); + } + +/** + * test Model::__construct + * + * ensure that $actsAS and $_findMethods are merged. + * + * @return void + */ + function testConstruct() { + $this->loadFixtures('Post', 'Comment'); + + $TestModel =& ClassRegistry::init('MergeVarPluginPost'); + $this->assertEqual($TestModel->actsAs, array('Containable', 'Tree')); + $this->assertTrue(isset($TestModel->Behaviors->Containable)); + $this->assertTrue(isset($TestModel->Behaviors->Tree)); + + $TestModel =& ClassRegistry::init('MergeVarPluginComment'); + $expected = array('Containable', 'Containable' => array('some_settings')); + $this->assertEqual($TestModel->actsAs, $expected); + $this->assertTrue(isset($TestModel->Behaviors->Containable)); + } + +/** + * test Model::__construct + * + * ensure that $actsAS and $_findMethods are merged. + * + * @return void + */ + function testConstructWithAlternateDataSource() { + $TestModel =& ClassRegistry::init(array( + 'class' => 'DoesntMatter', 'ds' => 'test_suite', 'table' => false + )); + $this->assertEqual('test_suite', $TestModel->useDbConfig); + + //deprecated but test it anyway + $NewVoid =& new TheVoid(null, false, 'other'); + $this->assertEqual('other', $NewVoid->useDbConfig); + } + +/** + * testColumnTypeFetching method + * + * @access public + * @return void + */ + function testColumnTypeFetching() { + $model =& new Test(); + $this->assertEqual($model->getColumnType('id'), 'integer'); + $this->assertEqual($model->getColumnType('notes'), 'text'); + $this->assertEqual($model->getColumnType('updated'), 'datetime'); + $this->assertEqual($model->getColumnType('unknown'), null); + + $model =& new Article(); + $this->assertEqual($model->getColumnType('User.created'), 'datetime'); + $this->assertEqual($model->getColumnType('Tag.id'), 'integer'); + $this->assertEqual($model->getColumnType('Article.id'), 'integer'); + } + +/** + * testHabtmUniqueKey method + * + * @access public + * @return void + */ + function testHabtmUniqueKey() { + $model =& new Item(); + $this->assertFalse($model->hasAndBelongsToMany['Portfolio']['unique']); + } + +/** + * testIdentity method + * + * @access public + * @return void + */ + function testIdentity() { + $TestModel =& new Test(); + $result = $TestModel->alias; + $expected = 'Test'; + $this->assertEqual($result, $expected); + + $TestModel =& new TestAlias(); + $result = $TestModel->alias; + $expected = 'TestAlias'; + $this->assertEqual($result, $expected); + + $TestModel =& new Test(array('alias' => 'AnotherTest')); + $result = $TestModel->alias; + $expected = 'AnotherTest'; + $this->assertEqual($result, $expected); + } + +/** + * testWithAssociation method + * + * @access public + * @return void + */ + function testWithAssociation() { + $this->loadFixtures('Something', 'SomethingElse', 'JoinThing'); + $TestModel =& new Something(); + $result = $TestModel->SomethingElse->find('all'); + + $expected = array( + array( + 'SomethingElse' => array( + 'id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'Something' => array( + array( + 'id' => '3', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31', + 'JoinThing' => array( + 'id' => '3', + 'something_id' => '3', + 'something_else_id' => '1', + 'doomed' => '1', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + )))), + array( + 'SomethingElse' => array( + 'id' => '2', + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'Something' => array( + array( + 'id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'JoinThing' => array( + 'id' => '1', + 'something_id' => '1', + 'something_else_id' => '2', + 'doomed' => '1', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )))), + array( + 'SomethingElse' => array( + 'id' => '3', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ), + 'Something' => array( + array( + 'id' => '2', + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31', + 'JoinThing' => array( + 'id' => '2', + 'something_id' => '2', + 'something_else_id' => '3', + 'doomed' => '0', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ))))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all'); + $expected = array( + array( + 'Something' => array( + 'id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'SomethingElse' => array( + array( + 'id' => '2', + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31', + 'JoinThing' => array( + 'doomed' => '1', + 'something_id' => '1', + 'something_else_id' => '2' + )))), + array( + 'Something' => array( + 'id' => '2', + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'SomethingElse' => array( + array( + 'id' => '3', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31', + 'JoinThing' => array( + 'doomed' => '0', + 'something_id' => '2', + 'something_else_id' => '3' + )))), + array( + 'Something' => array( + 'id' => '3', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ), + 'SomethingElse' => array( + array( + 'id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'JoinThing' => array( + 'doomed' => '1', + 'something_id' => '3', + 'something_else_id' => '1' + ))))); + $this->assertEqual($result, $expected); + + $result = $TestModel->findById(1); + $expected = array( + 'Something' => array( + 'id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'SomethingElse' => array( + array( + 'id' => '2', + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31', + 'JoinThing' => array( + 'doomed' => '1', + 'something_id' => '1', + 'something_else_id' => '2' + )))); + $this->assertEqual($result, $expected); + + $expected = $TestModel->findById(1); + $TestModel->set($expected); + $TestModel->save(); + $result = $TestModel->findById(1); + $this->assertEqual($result, $expected); + + $TestModel->hasAndBelongsToMany['SomethingElse']['unique'] = false; + $TestModel->create(array( + 'Something' => array('id' => 1), + 'SomethingElse' => array(3, array( + 'something_else_id' => 1, + 'doomed' => '1' + )))); + + $ts = date('Y-m-d H:i:s'); + $TestModel->save(); + + $TestModel->hasAndBelongsToMany['SomethingElse']['order'] = 'SomethingElse.id ASC'; + $result = $TestModel->findById(1); + $expected = array( + 'Something' => array( + 'id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => $ts), + 'SomethingElse' => array( + array( + 'id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'JoinThing' => array( + 'doomed' => '1', + 'something_id' => '1', + 'something_else_id' => '1' + )), + array( + 'id' => '2', + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31', + 'JoinThing' => array( + 'doomed' => '1', + 'something_id' => '1', + 'something_else_id' => '2' + )), + array( + 'id' => '3', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31', + 'JoinThing' => array( + 'doomed' => '0', + 'something_id' => '1', + 'something_else_id' => '3' + )))); + + $this->assertEqual($result, $expected); + } + +/** + * testFindSelfAssociations method + * + * @access public + * @return void + */ + function testFindSelfAssociations() { + $this->loadFixtures('Person'); + + $TestModel =& new Person(); + $TestModel->recursive = 2; + $result = $TestModel->read(null, 1); + $expected = array( + 'Person' => array( + 'id' => 1, + 'name' => 'person', + 'mother_id' => 2, + 'father_id' => 3 + ), + 'Mother' => array( + 'id' => 2, + 'name' => 'mother', + 'mother_id' => 4, + 'father_id' => 5, + 'Mother' => array( + 'id' => 4, + 'name' => 'mother - grand mother', + 'mother_id' => 0, + 'father_id' => 0 + ), + 'Father' => array( + 'id' => 5, + 'name' => 'mother - grand father', + 'mother_id' => 0, + 'father_id' => 0 + )), + 'Father' => array( + 'id' => 3, + 'name' => 'father', + 'mother_id' => 6, + 'father_id' => 7, + 'Father' => array( + 'id' => 7, + 'name' => 'father - grand father', + 'mother_id' => 0, + 'father_id' => 0 + ), + 'Mother' => array( + 'id' => 6, + 'name' => 'father - grand mother', + 'mother_id' => 0, + 'father_id' => 0 + ))); + + $this->assertEqual($result, $expected); + + $TestModel->recursive = 3; + $result = $TestModel->read(null, 1); + $expected = array( + 'Person' => array( + 'id' => 1, + 'name' => 'person', + 'mother_id' => 2, + 'father_id' => 3 + ), + 'Mother' => array( + 'id' => 2, + 'name' => 'mother', + 'mother_id' => 4, + 'father_id' => 5, + 'Mother' => array( + 'id' => 4, + 'name' => 'mother - grand mother', + 'mother_id' => 0, + 'father_id' => 0, + 'Mother' => array(), + 'Father' => array()), + 'Father' => array( + 'id' => 5, + 'name' => 'mother - grand father', + 'mother_id' => 0, + 'father_id' => 0, + 'Father' => array(), + 'Mother' => array() + )), + 'Father' => array( + 'id' => 3, + 'name' => 'father', + 'mother_id' => 6, + 'father_id' => 7, + 'Father' => array( + 'id' => 7, + 'name' => 'father - grand father', + 'mother_id' => 0, + 'father_id' => 0, + 'Father' => array(), + 'Mother' => array() + ), + 'Mother' => array( + 'id' => 6, + 'name' => 'father - grand mother', + 'mother_id' => 0, + 'father_id' => 0, + 'Mother' => array(), + 'Father' => array() + ))); + + $this->assertEqual($result, $expected); + } + +/** + * testDynamicAssociations method + * + * @access public + * @return void + */ + function testDynamicAssociations() { + $this->loadFixtures('Article', 'Comment'); + $TestModel =& new Article(); + + $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = $TestModel->hasOne = array(); + $TestModel->hasMany['Comment'] = array_merge($TestModel->hasMany['Comment'], array( + 'foreignKey' => false, + 'conditions' => array('Comment.user_id =' => '2') + )); + $result = $TestModel->find('all'); + $expected = array( + array( + 'Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + ))), + array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + ))), + array( + 'Article' => array( + 'id' => '3', + 'user_id' => '1', + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + )))); + + $this->assertEqual($result, $expected); + } + +/** + * testCreation method + * + * @access public + * @return void + */ + function testCreation() { + $this->loadFixtures('Article'); + $TestModel =& new Test(); + $result = $TestModel->create(); + $expected = array('Test' => array('notes' => 'write some notes here')); + $this->assertEqual($result, $expected); + $TestModel =& new User(); + $result = $TestModel->schema(); + + if (isset($this->db->columns['primary_key']['length'])) { + $intLength = $this->db->columns['primary_key']['length']; + } elseif (isset($this->db->columns['integer']['length'])) { + $intLength = $this->db->columns['integer']['length']; + } else { + $intLength = 11; + } + foreach (array('collate', 'charset') as $type) { + unset($result['user'][$type]); + unset($result['password'][$type]); + } + + $expected = array( + 'id' => array( + 'type' => 'integer', + 'null' => false, + 'default' => null, + 'length' => $intLength, + 'key' => 'primary' + ), + 'user' => array( + 'type' => 'string', + 'null' => false, + 'default' => '', + 'length' => 255 + ), + 'password' => array( + 'type' => 'string', + 'null' => false, + 'default' => '', + 'length' => 255 + ), + 'created' => array( + 'type' => 'datetime', + 'null' => true, + 'default' => null, + 'length' => null + ), + 'updated'=> array( + 'type' => 'datetime', + 'null' => true, + 'default' => null, + 'length' => null + )); + + $this->assertEqual($result, $expected); + + $TestModel =& new Article(); + $result = $TestModel->create(); + $expected = array('Article' => array('published' => 'N')); + $this->assertEqual($result, $expected); + + $FeaturedModel =& new Featured(); + $data = array( + 'article_featured_id' => 1, + 'category_id' => 1, + 'published_date' => array( + 'year' => 2008, + 'month' => 06, + 'day' => 11 + ), + 'end_date' => array( + 'year' => 2008, + 'month' => 06, + 'day' => 20 + )); + + $expected = array( + 'Featured' => array( + 'article_featured_id' => 1, + 'category_id' => 1, + 'published_date' => '2008-6-11 00:00:00', + 'end_date' => '2008-6-20 00:00:00' + )); + + $this->assertEqual($FeaturedModel->create($data), $expected); + + $data = array( + 'published_date' => array( + 'year' => 2008, + 'month' => 06, + 'day' => 11 + ), + 'end_date' => array( + 'year' => 2008, + 'month' => 06, + 'day' => 20 + ), + 'article_featured_id' => 1, + 'category_id' => 1 + ); + + $expected = array( + 'Featured' => array( + 'published_date' => '2008-6-11 00:00:00', + 'end_date' => '2008-6-20 00:00:00', + 'article_featured_id' => 1, + 'category_id' => 1 + )); + + $this->assertEqual($FeaturedModel->create($data), $expected); + } + +/** + * testEscapeField to prove it escapes the field well even when it has part of the alias on it + * @see ttp://cakephp.lighthouseapp.com/projects/42648-cakephp-1x/tickets/473-escapefield-doesnt-consistently-prepend-modelname + * + * @access public + * @return void + */ + function testEscapeField() { + $TestModel =& new Test(); + $db =& $TestModel->getDataSource(); + + $result = $TestModel->escapeField('test_field'); + $expected = $db->name('Test.test_field'); + $this->assertEqual($result, $expected); + + $result = $TestModel->escapeField('TestField'); + $expected = $db->name('Test.TestField'); + $this->assertEqual($result, $expected); + + $result = $TestModel->escapeField('DomainHandle', 'Domain'); + $expected = $db->name('Domain.DomainHandle'); + $this->assertEqual($result, $expected); + + ConnectionManager::create('mock', array('driver' => 'mock')); + $TestModel->setDataSource('mock'); + $db =& $TestModel->getDataSource(); + + $result = $TestModel->escapeField('DomainHandle', 'Domain'); + $expected = $db->name('Domain.DomainHandle'); + $this->assertEqual($result, $expected); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_read.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_read.test.php new file mode 100755 index 000000000..d59af8988 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_read.test.php @@ -0,0 +1,7456 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once dirname(__FILE__) . DS . 'model.test.php'; +/** + * ModelReadTest + * + * @package cake + * @subpackage cake.tests.cases.libs.model.operations + */ +class ModelReadTest extends BaseModelTest { + +/** + * testFetchingNonUniqueFKJoinTableRecords() + * + * Tests if the results are properly returned in the case there are non-unique FK's + * in the join table but another fields value is different. For example: + * something_id | something_else_id | doomed = 1 + * something_id | something_else_id | doomed = 0 + * Should return both records and not just one. + * + * @access public + * @return void + */ + function testFetchingNonUniqueFKJoinTableRecords() { + $this->loadFixtures('Something', 'SomethingElse', 'JoinThing'); + $Something = new Something(); + + $joinThingData = array( + 'JoinThing' => array( + 'something_id' => 1, + 'something_else_id' => 2, + 'doomed' => '0', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ) + ); + + $Something->JoinThing->create($joinThingData); + $Something->JoinThing->save(); + + $result = $Something->JoinThing->find('all', array('conditions' => array('something_else_id' => 2))); + $this->assertEqual($result[0]['JoinThing']['doomed'], true); + $this->assertEqual($result[1]['JoinThing']['doomed'], false); + + $result = $Something->find('first'); + $this->assertEqual(count($result['SomethingElse']), 2); + + $doomed = Set::extract('/JoinThing/doomed', $result['SomethingElse']); + $this->assertTrue(in_array(true, $doomed)); + $this->assertTrue(in_array(false, $doomed)); + } + +/** + * testGroupBy method + * + * These tests will never pass with Postgres or Oracle as all fields in a select must be + * part of an aggregate function or in the GROUP BY statement. + * + * @access public + * @return void + */ + function testGroupBy() { + $db = ConnectionManager::getDataSource('test_suite'); + $isStrictGroupBy = in_array($db->config['driver'], array('postgres', 'oracle')); + $message = '%s Postgres and Oracle have strict GROUP BY and are incompatible with this test.'; + + if ($this->skipIf($isStrictGroupBy, $message )) { + return; + } + + $this->loadFixtures('Project', 'Product', 'Thread', 'Message', 'Bid'); + $Thread =& new Thread(); + $Product =& new Product(); + + $result = $Thread->find('all', array( + 'group' => 'Thread.project_id', + 'order' => 'Thread.id ASC' + )); + + $expected = array( + array( + 'Thread' => array( + 'id' => 1, + 'project_id' => 1, + 'name' => 'Project 1, Thread 1' + ), + 'Project' => array( + 'id' => 1, + 'name' => 'Project 1' + ), + 'Message' => array( + array( + 'id' => 1, + 'thread_id' => 1, + 'name' => 'Thread 1, Message 1' + ))), + array( + 'Thread' => array( + 'id' => 3, + 'project_id' => 2, + 'name' => 'Project 2, Thread 1' + ), + 'Project' => array( + 'id' => 2, + 'name' => 'Project 2' + ), + 'Message' => array( + array( + 'id' => 3, + 'thread_id' => 3, + 'name' => 'Thread 3, Message 1' + )))); + $this->assertEqual($result, $expected); + + $rows = $Thread->find('all', array( + 'group' => 'Thread.project_id', + 'fields' => array('Thread.project_id', 'COUNT(*) AS total') + )); + $result = array(); + foreach($rows as $row) { + $result[$row['Thread']['project_id']] = $row[0]['total']; + } + $expected = array( + 1 => 2, + 2 => 1 + ); + $this->assertEqual($result, $expected); + + $rows = $Thread->find('all', array( + 'group' => 'Thread.project_id', + 'fields' => array('Thread.project_id', 'COUNT(*) AS total'), + 'order'=> 'Thread.project_id' + )); + $result = array(); + foreach($rows as $row) { + $result[$row['Thread']['project_id']] = $row[0]['total']; + } + $expected = array( + 1 => 2, + 2 => 1 + ); + $this->assertEqual($result, $expected); + + $result = $Thread->find('all', array( + 'conditions' => array('Thread.project_id' => 1), + 'group' => 'Thread.project_id' + )); + $expected = array( + array( + 'Thread' => array( + 'id' => 1, + 'project_id' => 1, + 'name' => 'Project 1, Thread 1' + ), + 'Project' => array( + 'id' => 1, + 'name' => 'Project 1' + ), + 'Message' => array( + array( + 'id' => 1, + 'thread_id' => 1, + 'name' => 'Thread 1, Message 1' + )))); + $this->assertEqual($result, $expected); + + $result = $Thread->find('all', array( + 'conditions' => array('Thread.project_id' => 1), + 'group' => 'Thread.project_id, Project.id' + )); + $this->assertEqual($result, $expected); + + $result = $Thread->find('all', array( + 'conditions' => array('Thread.project_id' => 1), + 'group' => 'project_id' + )); + $this->assertEqual($result, $expected); + + $result = $Thread->find('all', array( + 'conditions' => array('Thread.project_id' => 1), + 'group' => array('project_id') + )); + $this->assertEqual($result, $expected); + + $result = $Thread->find('all', array( + 'conditions' => array('Thread.project_id' => 1), + 'group' => array('project_id', 'Project.id') + )); + $this->assertEqual($result, $expected); + + $result = $Thread->find('all', array( + 'conditions' => array('Thread.project_id' => 1), + 'group' => array('Thread.project_id', 'Project.id') + )); + $this->assertEqual($result, $expected); + + $expected = array( + array('Product' => array('type' => 'Clothing'), array('price' => 32)), + array('Product' => array('type' => 'Food'), array('price' => 9)), + array('Product' => array('type' => 'Music'), array( 'price' => 4)), + array('Product' => array('type' => 'Toy'), array('price' => 3)) + ); + $result = $Product->find('all',array( + 'fields'=>array('Product.type', 'MIN(Product.price) as price'), + 'group'=> 'Product.type', + 'order' => 'Product.type ASC' + )); + $this->assertEqual($result, $expected); + + $result = $Product->find('all', array( + 'fields'=>array('Product.type', 'MIN(Product.price) as price'), + 'group'=> array('Product.type'), + 'order' => 'Product.type ASC')); + $this->assertEqual($result, $expected); + } + +/** + * testOldQuery method + * + * @access public + * @return void + */ + function testOldQuery() { + $this->loadFixtures('Article'); + $Article =& new Article(); + + $query = 'SELECT title FROM '; + $query .= $this->db->fullTableName('articles'); + $query .= ' WHERE ' . $this->db->fullTableName('articles') . '.id IN (1,2)'; + + $results = $Article->query($query); + $this->assertTrue(is_array($results)); + $this->assertEqual(count($results), 2); + + $query = 'SELECT title, body FROM '; + $query .= $this->db->fullTableName('articles'); + $query .= ' WHERE ' . $this->db->fullTableName('articles') . '.id = 1'; + + $results = $Article->query($query, false); + $this->assertTrue(!isset($this->db->_queryCache[$query])); + $this->assertTrue(is_array($results)); + + $query = 'SELECT title, id FROM '; + $query .= $this->db->fullTableName('articles'); + $query .= ' WHERE ' . $this->db->fullTableName('articles'); + $query .= '.published = ' . $this->db->value('Y'); + + $results = $Article->query($query, true); + $this->assertTrue(isset($this->db->_queryCache[$query])); + $this->assertTrue(is_array($results)); + } + +/** + * testPreparedQuery method + * + * @access public + * @return void + */ + function testPreparedQuery() { + $this->loadFixtures('Article'); + $Article =& new Article(); + $this->db->_queryCache = array(); + + $finalQuery = 'SELECT title, published FROM '; + $finalQuery .= $this->db->fullTableName('articles'); + $finalQuery .= ' WHERE ' . $this->db->fullTableName('articles'); + $finalQuery .= '.id = ' . $this->db->value(1); + $finalQuery .= ' AND ' . $this->db->fullTableName('articles'); + $finalQuery .= '.published = ' . $this->db->value('Y'); + + $query = 'SELECT title, published FROM '; + $query .= $this->db->fullTableName('articles'); + $query .= ' WHERE ' . $this->db->fullTableName('articles'); + $query .= '.id = ? AND ' . $this->db->fullTableName('articles') . '.published = ?'; + + $params = array(1, 'Y'); + $result = $Article->query($query, $params); + $expected = array( + '0' => array( + $this->db->fullTableName('articles', false) => array( + 'title' => 'First Article', 'published' => 'Y') + )); + + if (isset($result[0][0])) { + $expected[0][0] = $expected[0][$this->db->fullTableName('articles', false)]; + unset($expected[0][$this->db->fullTableName('articles', false)]); + } + + $this->assertEqual($result, $expected); + $this->assertTrue(isset($this->db->_queryCache[$finalQuery])); + + $finalQuery = 'SELECT id, created FROM '; + $finalQuery .= $this->db->fullTableName('articles'); + $finalQuery .= ' WHERE ' . $this->db->fullTableName('articles'); + $finalQuery .= '.title = ' . $this->db->value('First Article'); + + $query = 'SELECT id, created FROM '; + $query .= $this->db->fullTableName('articles'); + $query .= ' WHERE ' . $this->db->fullTableName('articles') . '.title = ?'; + + $params = array('First Article'); + $result = $Article->query($query, $params, false); + $this->assertTrue(is_array($result)); + $this->assertTrue( + isset($result[0][$this->db->fullTableName('articles', false)]) + || isset($result[0][0]) + ); + $this->assertFalse(isset($this->db->_queryCache[$finalQuery])); + + $query = 'SELECT title FROM '; + $query .= $this->db->fullTableName('articles'); + $query .= ' WHERE ' . $this->db->fullTableName('articles') . '.title LIKE ?'; + + $params = array('%First%'); + $result = $Article->query($query, $params); + $this->assertTrue(is_array($result)); + $this->assertTrue( + isset($result[0][$this->db->fullTableName('articles', false)]['title']) + || isset($result[0][0]['title']) + ); + + //related to ticket #5035 + $query = 'SELECT title FROM '; + $query .= $this->db->fullTableName('articles') . ' WHERE title = ? AND published = ?'; + $params = array('First? Article', 'Y'); + $Article->query($query, $params); + + $expected = 'SELECT title FROM '; + $expected .= $this->db->fullTableName('articles'); + $expected .= " WHERE title = 'First? Article' AND published = 'Y'"; + $this->assertTrue(isset($this->db->_queryCache[$expected])); + + } + +/** + * testParameterMismatch method + * + * @access public + * @return void + */ + function testParameterMismatch() { + $this->loadFixtures('Article'); + $Article =& new Article(); + + $query = 'SELECT * FROM ' . $this->db->fullTableName('articles'); + $query .= ' WHERE ' . $this->db->fullTableName('articles'); + $query .= '.published = ? AND ' . $this->db->fullTableName('articles') . '.user_id = ?'; + $params = array('Y'); + $this->expectError(); + + ob_start(); + $result = $Article->query($query, $params); + ob_end_clean(); + $this->assertEqual($result, null); + } + +/** + * testVeryStrangeUseCase method + * + * @access public + * @return void + */ + function testVeryStrangeUseCase() { + $message = "%s skipping SELECT * FROM ? WHERE ? = ? AND ? = ?; prepared query."; + $message .= " MSSQL is incompatible with this test."; + + if ($this->skipIf($this->db->config['driver'] == 'mssql', $message)) { + return; + } + + $this->loadFixtures('Article'); + $Article =& new Article(); + + $query = 'SELECT * FROM ? WHERE ? = ? AND ? = ?'; + $param = array( + $this->db->fullTableName('articles'), + $this->db->fullTableName('articles') . '.user_id', '3', + $this->db->fullTableName('articles') . '.published', 'Y' + ); + $this->expectError(); + + ob_start(); + $result = $Article->query($query, $param); + ob_end_clean(); + } + +/** + * testRecursiveUnbind method + * + * @access public + * @return void + */ + function testRecursiveUnbind() { + $this->loadFixtures('Apple', 'Sample'); + $TestModel =& new Apple(); + $TestModel->recursive = 2; + + $result = $TestModel->find('all'); + $expected = array( + array( + 'Apple' => array ( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' =>'', + 'apple_id' => '', + 'name' => '' + ), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))))), + array( + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2', + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + )), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 1, + 'apple_id' => 3, + 'name' => 'sample1' + )), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3' + ), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ))))), + array( + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 1, + 'apple_id' => 3, + 'name' => 'sample1', + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + )), + 'Child' => array() + ), + array( + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', 'mytime' => '22:57:17'), + 'Sample' => array('id' => 2, 'apple_id' => 2, 'name' => 'sample2'), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3', + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + )), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ))))), + array( + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4' + ), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4', + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + )), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4' + ), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))))), + array( + 'Apple' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3' + ), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + ), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ), + 'Sample' => array() + ))), + array( + 'Apple' => array( + 'id' => 7, + 'apple_id' => 6, + 'color' => + 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + ), + 'Child' => array())); + $this->assertEqual($result, $expected); + + $result = $TestModel->Parent->unbindModel(array('hasOne' => array('Sample'))); + $this->assertTrue($result); + + $result = $TestModel->find('all'); + $expected = array( + array( + 'Apple' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17'), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' =>'', + 'apple_id' => '', + 'name' => '' + ), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))))), + array( + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2', + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + )), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', 'modified' => + '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 1, + 'apple_id' => 3, + 'name' => 'sample1' + )), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3' + ), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ))))), + array( + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 1, + 'apple_id' => 3, + 'name' => 'sample1', + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + )), + 'Child' => array() + ), + array( + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3', + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + )), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ))))), + array( + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4', + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + )), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4' + ), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))))), + array( + 'Apple' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + ), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ), + 'Sample' => array() + ))), + array( + 'Apple' => array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + ), + 'Child' => array() + )); + + $this->assertEqual($result, $expected); + + $result = $TestModel->Parent->unbindModel(array('hasOne' => array('Sample'))); + $this->assertTrue($result); + + $result = $TestModel->unbindModel(array('hasMany' => array('Child'))); + $this->assertTrue($result); + + $result = $TestModel->find('all'); + $expected = array( + array( + 'Apple' => array ( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' =>'', + 'apple_id' => '', + 'name' => '' + )), + array( + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2', + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 1, + 'apple_id' => 3, + 'name' => 'sample1', + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3', + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4', + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + )), + array( + 'Apple' => array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + ))); + + $this->assertEqual($result, $expected); + + $result = $TestModel->unbindModel(array('hasMany' => array('Child'))); + $this->assertTrue($result); + + $result = $TestModel->Sample->unbindModel(array('belongsTo' => array('Apple'))); + $this->assertTrue($result); + + $result = $TestModel->find('all'); + $expected = array( + array( + 'Apple' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' =>'', + 'apple_id' => '', + 'name' => '' + )), + array( + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + )), + array( + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 1, + 'apple_id' => 3, + 'name' => 'sample1' + )), + array( + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3' + )), + array( + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4' + ), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4' + )), + array( + 'Apple' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3' + ), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + )), + array( + 'Apple' => array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17', + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->Parent->unbindModel(array('belongsTo' => array('Parent'))); + $this->assertTrue($result); + + $result = $TestModel->unbindModel(array('hasMany' => array('Child'))); + $this->assertTrue($result); + + $result = $TestModel->find('all'); + $expected = array( + array( + 'Apple' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' =>'', + 'apple_id' => '', + 'name' => '' + )), + array( + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17', + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2', + 'Apple' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 1, + 'apple_id' => 3, + 'name' => 'sample1', + 'Apple' => array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 2, + 'apple_id' => 1, + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17', + 'Sample' => array( + 'id' => 2, + 'apple_id' => 2, + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => 1, + 'apple_id' => 2, + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => 3, + 'apple_id' => 2, + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3', + 'Apple' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => + '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17', + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4' + ), + 'Child' => array( + array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => 4, + 'apple_id' => 5, + 'name' => 'sample4', + 'Apple' => array( + 'id' => 5, + 'apple_id' => 5, + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17'), + 'Parent' => array( + 'id' => 4, + 'apple_id' => 2, + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17', + 'Sample' => array( + 'id' => 3, + 'apple_id' => 4, + 'name' => 'sample3' + ), + 'Child' => array( + array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + )), + array( + 'Apple' => array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => 6, + 'apple_id' => 4, + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17', + 'Sample' => array(), + 'Child' => array( + array( + 'id' => 7, + 'apple_id' => 6, + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', 'modified' => + '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ))), + 'Sample' => array( + 'id' => '', + 'apple_id' => '', + 'name' => '' + ))); + $this->assertEqual($result, $expected); + } + +/** + * testSelfAssociationAfterFind method + * + * @access public + * @return void + */ + function testSelfAssociationAfterFind() { + $this->loadFixtures('Apple'); + $afterFindModel = new NodeAfterFind(); + $afterFindModel->recursive = 3; + $afterFindData = $afterFindModel->find('all'); + + $duplicateModel = new NodeAfterFind(); + $duplicateModel->recursive = 3; + $duplicateModelData = $duplicateModel->find('all'); + + $noAfterFindModel = new NodeNoAfterFind(); + $noAfterFindModel->recursive = 3; + $noAfterFindData = $noAfterFindModel->find('all'); + + $this->assertFalse($afterFindModel == $noAfterFindModel); + // Limitation of PHP 4 and PHP 5 > 5.1.6 when comparing recursive objects + if (PHP_VERSION === '5.1.6') { + $this->assertFalse($afterFindModel != $duplicateModel); + } + $this->assertEqual($afterFindData, $noAfterFindData); + } + +/** + * testFindAllThreaded method + * + * @access public + * @return void + */ + function testFindAllThreaded() { + $this->loadFixtures('Category'); + $TestModel =& new Category(); + + $result = $TestModel->find('threaded'); + $expected = array( + array( + 'Category' => array( + 'id' => '1', + 'parent_id' => '0', + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => '2', + 'parent_id' => '1', + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array('Category' => array( + 'id' => '7', + 'parent_id' => '2', + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array()), + array('Category' => array( + 'id' => '8', + 'parent_id' => '2', + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array())) + ), + array( + 'Category' => array( + 'id' => '3', + 'parent_id' => '1', + 'name' => 'Category 1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array() + ) + ) + ), + array( + 'Category' => array( + 'id' => '4', + 'parent_id' => '0', + 'name' => 'Category 2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array() + ), + array( + 'Category' => array( + 'id' => '5', + 'parent_id' => '0', + 'name' => 'Category 3', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => '6', + 'parent_id' => '5', + 'name' => 'Category 3.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array() + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('threaded', array( + 'conditions' => array('Category.name LIKE' => 'Category 1%') + )); + + $expected = array( + array( + 'Category' => array( + 'id' => '1', + 'parent_id' => '0', + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => '2', + 'parent_id' => '1', + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array('Category' => array( + 'id' => '7', + 'parent_id' => '2', + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array()), + array('Category' => array( + 'id' => '8', + 'parent_id' => '2', + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array())) + ), + array( + 'Category' => array( + 'id' => '3', + 'parent_id' => '1', + 'name' => 'Category 1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array() + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('threaded', array( + 'fields' => 'id, parent_id, name' + )); + + $expected = array( + array( + 'Category' => array( + 'id' => '1', + 'parent_id' => '0', + 'name' => 'Category 1' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => '2', + 'parent_id' => '1', + 'name' => 'Category 1.1' + ), + 'children' => array( + array('Category' => array( + 'id' => '7', + 'parent_id' => '2', + 'name' => 'Category 1.1.1'), + 'children' => array()), + array('Category' => array( + 'id' => '8', + 'parent_id' => '2', + 'name' => 'Category 1.1.2'), + 'children' => array())) + ), + array( + 'Category' => array( + 'id' => '3', + 'parent_id' => '1', + 'name' => 'Category 1.2' + ), + 'children' => array() + ) + ) + ), + array( + 'Category' => array( + 'id' => '4', + 'parent_id' => '0', + 'name' => 'Category 2' + ), + 'children' => array() + ), + array( + 'Category' => array( + 'id' => '5', + 'parent_id' => '0', + 'name' => 'Category 3' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => '6', + 'parent_id' => '5', + 'name' => 'Category 3.1' + ), + 'children' => array() + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('threaded', array('order' => 'id DESC')); + + $expected = array( + array( + 'Category' => array( + 'id' => 5, + 'parent_id' => 0, + 'name' => 'Category 3', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => 6, + 'parent_id' => 5, + 'name' => 'Category 3.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array() + ) + ) + ), + array( + 'Category' => array( + 'id' => 4, + 'parent_id' => 0, + 'name' => 'Category 2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array() + ), + array( + 'Category' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => 3, + 'parent_id' => 1, + 'name' => 'Category 1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array() + ), + array( + 'Category' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array('Category' => array( + 'id' => '8', + 'parent_id' => '2', + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array()), + array('Category' => array( + 'id' => '7', + 'parent_id' => '2', + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array())) + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('threaded', array( + 'conditions' => array('Category.name LIKE' => 'Category 3%') + )); + $expected = array( + array( + 'Category' => array( + 'id' => '5', + 'parent_id' => '0', + 'name' => 'Category 3', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => '6', + 'parent_id' => '5', + 'name' => 'Category 3.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'children' => array() + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('threaded', array( + 'conditions' => array('Category.name LIKE' => 'Category 1.1%') + )); + $expected = array( + array('Category' => + array( + 'id' => '2', + 'parent_id' => '1', + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array( + array('Category' => array( + 'id' => '7', + 'parent_id' => '2', + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array()), + array('Category' => array( + 'id' => '8', + 'parent_id' => '2', + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31'), + 'children' => array())))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('threaded', array( + 'fields' => 'id, parent_id, name', + 'conditions' => array('Category.id !=' => 2) + )); + $expected = array( + array( + 'Category' => array( + 'id' => '1', + 'parent_id' => '0', + 'name' => 'Category 1' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => '3', + 'parent_id' => '1', + 'name' => 'Category 1.2' + ), + 'children' => array() + ) + ) + ), + array( + 'Category' => array( + 'id' => '4', + 'parent_id' => '0', + 'name' => 'Category 2' + ), + 'children' => array() + ), + array( + 'Category' => array( + 'id' => '5', + 'parent_id' => '0', + 'name' => 'Category 3' + ), + 'children' => array( + array( + 'Category' => array( + 'id' => '6', + 'parent_id' => '5', + 'name' => 'Category 3.1' + ), + 'children' => array() + ) + ) + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array( + 'fields' => 'id, name, parent_id', + 'conditions' => array('Category.id !=' => 1) + )); + $expected = array ( + array ('Category' => array( + 'id' => '2', + 'name' => 'Category 1.1', + 'parent_id' => '1' + )), + array ('Category' => array( + 'id' => '3', + 'name' => 'Category 1.2', + 'parent_id' => '1' + )), + array ('Category' => array( + 'id' => '4', + 'name' => 'Category 2', + 'parent_id' => '0' + )), + array ('Category' => array( + 'id' => '5', + 'name' => 'Category 3', + 'parent_id' => '0' + )), + array ('Category' => array( + 'id' => '6', + 'name' => 'Category 3.1', + 'parent_id' => '5' + )), + array ('Category' => array( + 'id' => '7', + 'name' => 'Category 1.1.1', + 'parent_id' => '2' + )), + array ('Category' => array( + 'id' => '8', + 'name' => 'Category 1.1.2', + 'parent_id' => '2' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('threaded', array( + 'fields' => 'id, parent_id, name', + 'conditions' => array('Category.id !=' => 1) + )); + $expected = array( + array( + 'Category' => array( + 'id' => '2', + 'parent_id' => '1', + 'name' => 'Category 1.1' + ), + 'children' => array( + array('Category' => array( + 'id' => '7', + 'parent_id' => '2', + 'name' => 'Category 1.1.1'), + 'children' => array()), + array('Category' => array( + 'id' => '8', + 'parent_id' => '2', + 'name' => 'Category 1.1.2'), + 'children' => array())) + ), + array( + 'Category' => array( + 'id' => '3', + 'parent_id' => '1', + 'name' => 'Category 1.2' + ), + 'children' => array() + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * test find('neighbors') + * + * @return void + * @access public + */ + function testFindNeighbors() { + $this->loadFixtures('User', 'Article'); + $TestModel =& new Article(); + + $TestModel->id = 1; + $result = $TestModel->find('neighbors', array('fields' => array('id'))); + $expected = array( + 'prev' => null, + 'next' => array( + 'Article' => array('id' => 2) + )); + $this->assertEqual($result, $expected); + + $TestModel->id = 2; + $result = $TestModel->find('neighbors', array( + 'fields' => array('id') + )); + + $expected = array( + 'prev' => array( + 'Article' => array( + 'id' => 1 + )), + 'next' => array( + 'Article' => array( + 'id' => 3 + ))); + $this->assertEqual($result, $expected); + + $TestModel->id = 3; + $result = $TestModel->find('neighbors', array('fields' => array('id'))); + $expected = array( + 'prev' => array( + 'Article' => array( + 'id' => 2 + )), + 'next' => null + ); + $this->assertEqual($result, $expected); + + $TestModel->id = 1; + $result = $TestModel->find('neighbors', array('recursive' => -1)); + $expected = array( + 'prev' => null, + 'next' => array( + 'Article' => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ) + ) + ); + $this->assertEqual($result, $expected); + + $TestModel->id = 2; + $result = $TestModel->find('neighbors', array('recursive' => -1)); + $expected = array( + 'prev' => array( + 'Article' => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ) + ), + 'next' => array( + 'Article' => array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ) + ) + ); + $this->assertEqual($result, $expected); + + $TestModel->id = 3; + $result = $TestModel->find('neighbors', array('recursive' => -1)); + $expected = array( + 'prev' => array( + 'Article' => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ) + ), + 'next' => null + ); + $this->assertEqual($result, $expected); + + $TestModel->recursive = 0; + $TestModel->id = 1; + $one = $TestModel->read(); + $TestModel->id = 2; + $two = $TestModel->read(); + $TestModel->id = 3; + $three = $TestModel->read(); + + $TestModel->id = 1; + $result = $TestModel->find('neighbors'); + $expected = array('prev' => null, 'next' => $two); + $this->assertEqual($result, $expected); + + $TestModel->id = 2; + $result = $TestModel->find('neighbors'); + $expected = array('prev' => $one, 'next' => $three); + $this->assertEqual($result, $expected); + + $TestModel->id = 3; + $result = $TestModel->find('neighbors'); + $expected = array('prev' => $two, 'next' => null); + $this->assertEqual($result, $expected); + + $TestModel->recursive = 2; + $TestModel->id = 1; + $one = $TestModel->read(); + $TestModel->id = 2; + $two = $TestModel->read(); + $TestModel->id = 3; + $three = $TestModel->read(); + + $TestModel->id = 1; + $result = $TestModel->find('neighbors', array('recursive' => 2)); + $expected = array('prev' => null, 'next' => $two); + $this->assertEqual($result, $expected); + + $TestModel->id = 2; + $result = $TestModel->find('neighbors', array('recursive' => 2)); + $expected = array('prev' => $one, 'next' => $three); + $this->assertEqual($result, $expected); + + $TestModel->id = 3; + $result = $TestModel->find('neighbors', array('recursive' => 2)); + $expected = array('prev' => $two, 'next' => null); + $this->assertEqual($result, $expected); + } + +/** + * testFindCombinedRelations method + * + * @access public + * @return void + */ + function testFindCombinedRelations() { + $this->loadFixtures('Apple', 'Sample'); + $TestModel =& new Apple(); + + $result = $TestModel->find('all'); + + $expected = array( + array( + 'Apple' => array( + 'id' => '1', + 'apple_id' => '2', + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => '2', + 'apple_id' => '1', + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => null, + 'apple_id' => null, + 'name' => null + ), + 'Child' => array( + array( + 'id' => '2', + 'apple_id' => '1', + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => '2', + 'apple_id' => '1', + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => '1', + 'apple_id' => '2', + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => '2', + 'apple_id' => '2', + 'name' => 'sample2' + ), + 'Child' => array( + array( + 'id' => '1', + 'apple_id' => '2', + 'color' => 'Red 1', + 'name' => 'Red Apple 1', + 'created' => '2006-11-22 10:38:58', + 'date' => '1951-01-04', + 'modified' => '2006-12-01 13:31:26', + 'mytime' => '22:57:17' + ), + array( + 'id' => '3', + 'apple_id' => '2', + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + array( + 'id' => '4', + 'apple_id' => '2', + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => '3', + 'apple_id' => '2', + 'color' => 'blue green', + 'name' => 'green blue', + 'created' => '2006-12-25 05:13:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:24', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => '2', + 'apple_id' => '1', + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => '1', + 'apple_id' => '3', + 'name' => 'sample1' + ), + 'Child' => array() + ), + array( + 'Apple' => array( + 'id' => '4', + 'apple_id' => '2', + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => '2', + 'apple_id' => '1', + 'color' => 'Bright Red 1', + 'name' => 'Bright Red Apple', + 'created' => '2006-11-22 10:43:13', + 'date' => '2014-01-01', + 'modified' => '2006-11-30 18:38:10', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => '3', + 'apple_id' => '4', + 'name' => 'sample3' + ), + 'Child' => array( + array( + 'id' => '6', + 'apple_id' => '4', + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => '5', + 'apple_id' => '5', + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => '5', + 'apple_id' => '5', + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => '4', + 'apple_id' => '5', + 'name' => 'sample4' + ), + 'Child' => array( + array( + 'id' => '5', + 'apple_id' => '5', + 'color' => 'Green', + 'name' => 'Blue Green', + 'created' => '2006-12-25 05:24:06', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:16', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => '6', + 'apple_id' => '4', + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => '4', + 'apple_id' => '2', + 'color' => 'Blue Green', + 'name' => 'Test Name', + 'created' => '2006-12-25 05:23:36', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:23:36', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => null, + 'apple_id' => null, + 'name' => null + ), + 'Child' => array( + array( + 'id' => '7', + 'apple_id' => '6', + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ))), + array( + 'Apple' => array( + 'id' => '7', + 'apple_id' => '6', + 'color' => 'Some wierd color', + 'name' => 'Some odd color', + 'created' => '2006-12-25 05:34:21', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:34:21', + 'mytime' => '22:57:17' + ), + 'Parent' => array( + 'id' => '6', + 'apple_id' => '4', + 'color' => 'My new appleOrange', + 'name' => 'My new apple', + 'created' => '2006-12-25 05:29:39', + 'date' => '2006-12-25', + 'modified' => '2006-12-25 05:29:39', + 'mytime' => '22:57:17' + ), + 'Sample' => array( + 'id' => null, + 'apple_id' => null, + 'name' => null + ), + 'Child' => array() + )); + $this->assertEqual($result, $expected); + } + +/** + * testSaveEmpty method + * + * @access public + * @return void + */ + function testSaveEmpty() { + $this->loadFixtures('Thread'); + $TestModel =& new Thread(); + $data = array(); + $expected = $TestModel->save($data); + $this->assertFalse($expected); + } + +/** + * testFindAllWithConditionInChildQuery + * + * @todo external conditions like this are going to need to be revisited at some point + * @access public + * @return void + */ + function testFindAllWithConditionInChildQuery() { + $this->loadFixtures('Basket', 'FilmFile'); + + $TestModel =& new Basket(); + $recursive = 3; + $result = $TestModel->find('all', compact('conditions', 'recursive')); + + $expected = array( + array( + 'Basket' => array( + 'id' => 1, + 'type' => 'nonfile', + 'name' => 'basket1', + 'object_id' => 1, + 'user_id' => 1, + ), + 'FilmFile' => array( + 'id' => '', + 'name' => '', + ) + ), + array( + 'Basket' => array( + 'id' => 2, + 'type' => 'file', + 'name' => 'basket2', + 'object_id' => 2, + 'user_id' => 1, + ), + 'FilmFile' => array( + 'id' => 2, + 'name' => 'two', + ) + ), + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindAllWithConditionsHavingMixedDataTypes method + * + * @access public + * @return void + */ + function testFindAllWithConditionsHavingMixedDataTypes() { + $this->loadFixtures('Article'); + $TestModel =& new Article(); + $expected = array( + array( + 'Article' => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ) + ), + array( + 'Article' => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ) + ) + ); + $conditions = array('id' => array('1', 2)); + $recursive = -1; + $order = 'Article.id ASC'; + $result = $TestModel->find('all', compact('conditions', 'recursive', 'order')); + $this->assertEqual($result, $expected); + + if ($this->skipIf($this->db->config['driver'] == 'postgres', 'The rest of testFindAllWithConditionsHavingMixedDataTypes test is not compatible with Postgres')) { + return; + } + $conditions = array('id' => array('1', 2, '3.0')); + $order = 'Article.id ASC'; + $result = $TestModel->find('all', compact('recursive', 'conditions', 'order')); + $expected = array( + array( + 'Article' => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ) + ), + array( + 'Article' => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ) + ), + array( + 'Article' => array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ) + ) + ); + $this->assertEqual($result, $expected); + } + +/** + * testBindUnbind method + * + * @access public + * @return void + */ + function testBindUnbind() { + $this->loadFixtures('User', 'Comment', 'FeatureSet'); + $TestModel =& new User(); + + $result = $TestModel->hasMany; + $expected = array(); + $this->assertEqual($result, $expected); + + $result = $TestModel->bindModel(array('hasMany' => array('Comment'))); + $this->assertTrue($result); + + $result = $TestModel->find('all', array( + 'fields' => 'User.id, User.user' + )); + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano' + ), + 'Comment' => array( + array( + 'id' => '3', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Third Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:49:23', + 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => '4', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + ), + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ))), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + ))), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry' + ), + 'Comment' => array() + ), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett' + ), + 'Comment' => array( + array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + )))); + + $this->assertEqual($result, $expected); + + $TestModel->resetAssociations(); + $result = $TestModel->hasMany; + $this->assertEqual($result, array()); + + $result = $TestModel->bindModel(array('hasMany' => array('Comment')), false); + $this->assertTrue($result); + + $result = $TestModel->find('all', array( + 'fields' => 'User.id, User.user' + )); + + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano' + ), + 'Comment' => array( + array( + 'id' => '3', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Third Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:49:23', + 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => '4', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + ), + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ))), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + ))), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry' + ), + 'Comment' => array() + ), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett' + ), + 'Comment' => array( + array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + )))); + + $this->assertEqual($result, $expected); + + $result = $TestModel->hasMany; + $expected = array( + 'Comment' => array( + 'className' => 'Comment', + 'foreignKey' => 'user_id', + 'conditions' => null, + 'fields' => null, + 'order' => null, + 'limit' => null, + 'offset' => null, + 'dependent' => null, + 'exclusive' => null, + 'finderQuery' => null, + 'counterQuery' => null + )); + $this->assertEqual($result, $expected); + + $result = $TestModel->unbindModel(array('hasMany' => array('Comment'))); + $this->assertTrue($result); + + $result = $TestModel->hasMany; + $expected = array(); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array( + 'fields' => 'User.id, User.user' + )); + $expected = array( + array('User' => array('id' => '1', 'user' => 'mariano')), + array('User' => array('id' => '2', 'user' => 'nate')), + array('User' => array('id' => '3', 'user' => 'larry')), + array('User' => array('id' => '4', 'user' => 'garrett'))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array( + 'fields' => 'User.id, User.user' + )); + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano' + ), + 'Comment' => array( + array( + 'id' => '3', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Third Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:49:23', + 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => '4', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + ), + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ))), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + ))), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry' + ), + 'Comment' => array() + ), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett' + ), + 'Comment' => array( + array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => + 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + )))); + $this->assertEqual($result, $expected); + + $result = $TestModel->unbindModel(array('hasMany' => array('Comment')), false); + $this->assertTrue($result); + + $result = $TestModel->find('all', array('fields' => 'User.id, User.user')); + $expected = array( + array('User' => array('id' => '1', 'user' => 'mariano')), + array('User' => array('id' => '2', 'user' => 'nate')), + array('User' => array('id' => '3', 'user' => 'larry')), + array('User' => array('id' => '4', 'user' => 'garrett'))); + $this->assertEqual($result, $expected); + + $result = $TestModel->hasMany; + $expected = array(); + $this->assertEqual($result, $expected); + + $result = $TestModel->bindModel(array('hasMany' => array( + 'Comment' => array('className' => 'Comment', 'conditions' => 'Comment.published = \'Y\'') + ))); + $this->assertTrue($result); + + $result = $TestModel->find('all', array('fields' => 'User.id, User.user')); + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano' + ), + 'Comment' => array( + array( + 'id' => '3', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Third Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:49:23', + 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ))), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + ))), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry' + ), + 'Comment' => array() + ), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett' + ), + 'Comment' => array( + array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + )))); + + $this->assertEqual($result, $expected); + + $TestModel2 =& new DeviceType(); + + $expected = array( + 'className' => 'FeatureSet', + 'foreignKey' => 'feature_set_id', + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'counterCache' => '' + ); + $this->assertEqual($TestModel2->belongsTo['FeatureSet'], $expected); + + $TestModel2->bindModel(array( + 'belongsTo' => array( + 'FeatureSet' => array( + 'className' => 'FeatureSet', + 'conditions' => array('active' => true) + ) + ) + )); + $expected['conditions'] = array('active' => true); + $this->assertEqual($TestModel2->belongsTo['FeatureSet'], $expected); + + $TestModel2->bindModel(array( + 'belongsTo' => array( + 'FeatureSet' => array( + 'className' => 'FeatureSet', + 'foreignKey' => false, + 'conditions' => array('Feature.name' => 'DeviceType.name') + ) + ) + )); + $expected['conditions'] = array('Feature.name' => 'DeviceType.name'); + $expected['foreignKey'] = false; + $this->assertEqual($TestModel2->belongsTo['FeatureSet'], $expected); + + $TestModel2->bindModel(array( + 'hasMany' => array( + 'NewFeatureSet' => array( + 'className' => 'FeatureSet', + 'conditions' => array('active' => true) + ) + ) + )); + + $expected = array( + 'className' => 'FeatureSet', + 'conditions' => array('active' => true), + 'foreignKey' => 'device_type_id', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'dependent' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ); + $this->assertEqual($TestModel2->hasMany['NewFeatureSet'], $expected); + $this->assertTrue(is_object($TestModel2->NewFeatureSet)); + } + +/** + * testBindMultipleTimes method + * + * @access public + * @return void + */ + function testBindMultipleTimes() { + $this->loadFixtures('User', 'Comment', 'Article'); + $TestModel =& new User(); + + $result = $TestModel->hasMany; + $expected = array(); + $this->assertEqual($result, $expected); + + $result = $TestModel->bindModel(array( + 'hasMany' => array( + 'Items' => array('className' => 'Comment') + ))); + $this->assertTrue($result); + + $result = $TestModel->find('all', array( + 'fields' => 'User.id, User.user' + )); + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano' + ), + 'Items' => array( + array( + 'id' => '3', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Third Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:49:23', + 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => '4', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + ), + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ))), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate' + ), + 'Items' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + ))), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry' + ), + 'Items' => array() + ), + array( + 'User' => array( + 'id' => '4', 'user' => 'garrett'), + 'Items' => array( + array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + )))); + $this->assertEqual($result, $expected); + + $result = $TestModel->bindModel(array( + 'hasMany' => array( + 'Items' => array('className' => 'Article') + ))); + $this->assertTrue($result); + + $result = $TestModel->find('all', array( + 'fields' => 'User.id, User.user' + )); + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano' + ), + 'Items' => array( + array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ))), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate' + ), + 'Items' => array() + ), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry' + ), + 'Items' => array( + array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ))), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett' + ), + 'Items' => array() + )); + $this->assertEqual($result, $expected); + } + +/** + * test that multiple reset = true calls to bindModel() result in the original associations. + * + * @return void + */ + function testBindModelMultipleTimesResetCorrectly() { + $this->loadFixtures('User', 'Comment', 'Article'); + $TestModel =& new User(); + + $TestModel->bindModel(array('hasMany' => array('Comment'))); + $TestModel->bindModel(array('hasMany' => array('Comment'))); + $TestModel->resetAssociations(); + + $this->assertFalse(isset($TestModel->hasMany['Comment']), 'Association left behind'); + } + +/** + * testBindMultipleTimes method with different reset settings + * + * @access public + * @return void + */ + function testBindMultipleTimesWithDifferentResetSettings() { + $this->loadFixtures('User', 'Comment', 'Article'); + $TestModel =& new User(); + + $result = $TestModel->hasMany; + $expected = array(); + $this->assertEqual($result, $expected); + + $result = $TestModel->bindModel(array( + 'hasMany' => array('Comment') + )); + $this->assertTrue($result); + $result = $TestModel->bindModel( + array('hasMany' => array('Article')), + false + ); + $this->assertTrue($result); + + $result = array_keys($TestModel->hasMany); + $expected = array('Comment', 'Article'); + $this->assertEqual($result, $expected); + + $TestModel->resetAssociations(); + + $result = array_keys($TestModel->hasMany); + $expected = array('Article'); + $this->assertEqual($result, $expected); + } + +/** + * test that bindModel behaves with Custom primary Key associations + * + * @return void + */ + function bindWithCustomPrimaryKey() { + $this->loadFixtures('Story', 'StoriesTag', 'Tag'); + $Model =& ClassRegistry::init('StoriesTag'); + $Model->bindModel(array( + 'belongsTo' => array( + 'Tag' => array( + 'className' => 'Tag', + 'foreignKey' => 'story' + )))); + + $result = $Model->find('all'); + $this->assertFalse(empty($result)); + } + +/** + * test that calling unbindModel() with reset == true multiple times + * leaves associations in the correct state. + * + * @return void + */ + function testUnbindMultipleTimesResetCorrectly() { + $this->loadFixtures('User', 'Comment', 'Article'); + $TestModel =& new Article10(); + + $TestModel->unbindModel(array('hasMany' => array('Comment'))); + $TestModel->unbindModel(array('hasMany' => array('Comment'))); + $TestModel->resetAssociations(); + + $this->assertTrue(isset($TestModel->hasMany['Comment']), 'Association permanently removed'); + } + +/** + * testBindMultipleTimes method with different reset settings + * + * @access public + * @return void + */ + function testUnBindMultipleTimesWithDifferentResetSettings() { + $this->loadFixtures('User', 'Comment', 'Article'); + $TestModel =& new Comment(); + + $result = array_keys($TestModel->belongsTo); + $expected = array('Article', 'User'); + $this->assertEqual($result, $expected); + + $result = $TestModel->unbindModel(array( + 'belongsTo' => array('User') + )); + $this->assertTrue($result); + $result = $TestModel->unbindModel( + array('belongsTo' => array('Article')), + false + ); + $this->assertTrue($result); + + $result = array_keys($TestModel->belongsTo); + $expected = array(); + $this->assertEqual($result, $expected); + + $TestModel->resetAssociations(); + + $result = array_keys($TestModel->belongsTo); + $expected = array('User'); + $this->assertEqual($result, $expected); + } + +/** + * testAssociationAfterFind method + * + * @access public + * @return void + */ + function testAssociationAfterFind() { + $this->loadFixtures('Post', 'Author', 'Comment'); + $TestModel =& new Post(); + $result = $TestModel->find('all'); + $expected = array( + array( + 'Post' => array( + 'id' => '1', + 'author_id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'Author' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31', + 'test' => 'working' + )), + array( + 'Post' => array( + 'id' => '2', + 'author_id' => '3', + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'Author' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31', + 'test' => 'working' + )), + array( + 'Post' => array( + 'id' => '3', + 'author_id' => '1', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ), + 'Author' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31', + 'test' => 'working' + ))); + $this->assertEqual($result, $expected); + unset($TestModel); + + $Author =& new Author(); + $Author->Post->bindModel(array( + 'hasMany' => array( + 'Comment' => array( + 'className' => 'ModifiedComment', + 'foreignKey' => 'article_id', + ) + ))); + $result = $Author->find('all', array( + 'conditions' => array('Author.id' => 1), + 'recursive' => 2 + )); + $expected = array( + 'id' => 1, + 'article_id' => 1, + 'user_id' => 2, + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31', + 'callback' => 'Fire' + ); + $this->assertEqual($result[0]['Post'][0]['Comment'][0], $expected); + } + +/** + * Tests that callbacks can be properly disabled + * + * @access public + * @return void + */ + function testCallbackDisabling() { + $this->loadFixtures('Author'); + $TestModel = new ModifiedAuthor(); + + $result = Set::extract($TestModel->find('all'), '/Author/user'); + $expected = array('mariano (CakePHP)', 'nate (CakePHP)', 'larry (CakePHP)', 'garrett (CakePHP)'); + $this->assertEqual($result, $expected); + + $result = Set::extract($TestModel->find('all', array('callbacks' => 'after')), '/Author/user'); + $expected = array('mariano (CakePHP)', 'nate (CakePHP)', 'larry (CakePHP)', 'garrett (CakePHP)'); + $this->assertEqual($result, $expected); + + $result = Set::extract($TestModel->find('all', array('callbacks' => 'before')), '/Author/user'); + $expected = array('mariano', 'nate', 'larry', 'garrett'); + $this->assertEqual($result, $expected); + + $result = Set::extract($TestModel->find('all', array('callbacks' => false)), '/Author/user'); + $expected = array('mariano', 'nate', 'larry', 'garrett'); + $this->assertEqual($result, $expected); + } + +/** + * Tests that the database configuration assigned to the model can be changed using + * (before|after)Find callbacks + * + * @access public + * @return void + */ + function testCallbackSourceChange() { + $this->loadFixtures('Post'); + $TestModel = new Post(); + $this->assertEqual(3, count($TestModel->find('all'))); + + $this->expectError(new PatternExpectation('/Non-existent data source foo/i')); + $this->assertFalse($TestModel->find('all', array('connection' => 'foo'))); + } + +/** + * testMultipleBelongsToWithSameClass method + * + * @access public + * @return void + */ + function testMultipleBelongsToWithSameClass() { + $this->loadFixtures( + 'DeviceType', + 'DeviceTypeCategory', + 'FeatureSet', + 'ExteriorTypeCategory', + 'Document', + 'Device', + 'DocumentDirectory' + ); + + $DeviceType =& new DeviceType(); + + $DeviceType->recursive = 2; + $result = $DeviceType->read(null, 1); + + $expected = array( + 'DeviceType' => array( + 'id' => 1, + 'device_type_category_id' => 1, + 'feature_set_id' => 1, + 'exterior_type_category_id' => 1, + 'image_id' => 1, + 'extra1_id' => 1, + 'extra2_id' => 1, + 'name' => 'DeviceType 1', + 'order' => 0 + ), + 'Image' => array( + 'id' => 1, + 'document_directory_id' => 1, + 'name' => 'Document 1', + 'DocumentDirectory' => array( + 'id' => 1, + 'name' => 'DocumentDirectory 1' + )), + 'Extra1' => array( + 'id' => 1, + 'document_directory_id' => 1, + 'name' => 'Document 1', + 'DocumentDirectory' => array( + 'id' => 1, + 'name' => 'DocumentDirectory 1' + )), + 'Extra2' => array( + 'id' => 1, + 'document_directory_id' => 1, + 'name' => 'Document 1', + 'DocumentDirectory' => array( + 'id' => 1, + 'name' => 'DocumentDirectory 1' + )), + 'DeviceTypeCategory' => array( + 'id' => 1, + 'name' => 'DeviceTypeCategory 1' + ), + 'FeatureSet' => array( + 'id' => 1, + 'name' => 'FeatureSet 1' + ), + 'ExteriorTypeCategory' => array( + 'id' => 1, + 'image_id' => 1, + 'name' => 'ExteriorTypeCategory 1', + 'Image' => array( + 'id' => 1, + 'device_type_id' => 1, + 'name' => 'Device 1', + 'typ' => 1 + )), + 'Device' => array( + array( + 'id' => 1, + 'device_type_id' => 1, + 'name' => 'Device 1', + 'typ' => 1 + ), + array( + 'id' => 2, + 'device_type_id' => 1, + 'name' => 'Device 2', + 'typ' => 1 + ), + array( + 'id' => 3, + 'device_type_id' => 1, + 'name' => 'Device 3', + 'typ' => 2 + ))); + + $this->assertEqual($result, $expected); + } + +/** + * testHabtmRecursiveBelongsTo method + * + * @access public + * @return void + */ + function testHabtmRecursiveBelongsTo() { + $this->loadFixtures('Portfolio', 'Item', 'ItemsPortfolio', 'Syfile', 'Image'); + $Portfolio =& new Portfolio(); + + $result = $Portfolio->find(array('id' => 2), null, null, 3); + $expected = array( + 'Portfolio' => array( + 'id' => 2, + 'seller_id' => 1, + 'name' => 'Portfolio 2' + ), + 'Item' => array( + array( + 'id' => 2, + 'syfile_id' => 2, + 'published' => 0, + 'name' => 'Item 2', + 'ItemsPortfolio' => array( + 'id' => 2, + 'item_id' => 2, + 'portfolio_id' => 2 + ), + 'Syfile' => array( + 'id' => 2, + 'image_id' => 2, + 'name' => 'Syfile 2', + 'item_count' => null, + 'Image' => array( + 'id' => 2, + 'name' => 'Image 2' + ) + )), + array( + 'id' => 6, + 'syfile_id' => 6, + 'published' => 0, + 'name' => 'Item 6', + 'ItemsPortfolio' => array( + 'id' => 6, + 'item_id' => 6, + 'portfolio_id' => 2 + ), + 'Syfile' => array( + 'id' => 6, + 'image_id' => null, + 'name' => 'Syfile 6', + 'item_count' => null, + 'Image' => array() + )))); + + $this->assertEqual($result, $expected); + } + +/** + * testHabtmFinderQuery method + * + * @access public + * @return void + */ + function testHabtmFinderQuery() { + $this->loadFixtures('Article', 'Tag', 'ArticlesTag'); + $Article =& new Article(); + + $sql = $this->db->buildStatement( + array( + 'fields' => $this->db->fields($Article->Tag, null, array( + 'Tag.id', 'Tag.tag', 'ArticlesTag.article_id', 'ArticlesTag.tag_id' + )), + 'table' => $this->db->fullTableName('tags'), + 'alias' => 'Tag', + 'limit' => null, + 'offset' => null, + 'group' => null, + 'joins' => array(array( + 'alias' => 'ArticlesTag', + 'table' => $this->db->fullTableName('articles_tags'), + 'conditions' => array( + array("ArticlesTag.article_id" => '{$__cakeID__$}'), + array("ArticlesTag.tag_id" => $this->db->identifier('Tag.id')) + ) + )), + 'conditions' => array(), + 'order' => null + ), + $Article + ); + + $Article->hasAndBelongsToMany['Tag']['finderQuery'] = $sql; + $result = $Article->find('first'); + $expected = array( + array( + 'id' => '1', + 'tag' => 'tag1' + ), + array( + 'id' => '2', + 'tag' => 'tag2' + )); + + $this->assertEqual($result['Tag'], $expected); + } + +/** + * testHabtmLimitOptimization method + * + * @access public + * @return void + */ + function testHabtmLimitOptimization() { + $this->loadFixtures('Article', 'User', 'Comment', 'Tag', 'ArticlesTag'); + $TestModel =& new Article(); + + $TestModel->hasAndBelongsToMany['Tag']['limit'] = 2; + $result = $TestModel->read(null, 2); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ), + 'Comment' => array( + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + )), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))); + + $this->assertEqual($result, $expected); + + $TestModel->hasAndBelongsToMany['Tag']['limit'] = 1; + $result = $TestModel->read(null, 2); + unset($expected['Tag'][1]); + + $this->assertEqual($result, $expected); + } + +/** + * testHasManyLimitOptimization method + * + * @access public + * @return void + */ + function testHasManyLimitOptimization() { + $this->loadFixtures('Project', 'Thread', 'Message', 'Bid'); + $Project =& new Project(); + $Project->recursive = 3; + + $result = $Project->find('all'); + $expected = array( + array( + 'Project' => array( + 'id' => 1, + 'name' => 'Project 1' + ), + 'Thread' => array( + array( + 'id' => 1, + 'project_id' => 1, + 'name' => 'Project 1, Thread 1', + 'Project' => array( + 'id' => 1, + 'name' => 'Project 1', + 'Thread' => array( + array( + 'id' => 1, + 'project_id' => 1, + 'name' => 'Project 1, Thread 1' + ), + array( + 'id' => 2, + 'project_id' => 1, + 'name' => 'Project 1, Thread 2' + ))), + 'Message' => array( + array( + 'id' => 1, + 'thread_id' => 1, + 'name' => 'Thread 1, Message 1', + 'Bid' => array( + 'id' => 1, + 'message_id' => 1, + 'name' => 'Bid 1.1' + )))), + array( + 'id' => 2, + 'project_id' => 1, + 'name' => 'Project 1, Thread 2', + 'Project' => array( + 'id' => 1, + 'name' => 'Project 1', + 'Thread' => array( + array( + 'id' => 1, + 'project_id' => 1, + 'name' => 'Project 1, Thread 1' + ), + array( + 'id' => 2, + 'project_id' => 1, + 'name' => 'Project 1, Thread 2' + ))), + 'Message' => array( + array( + 'id' => 2, + 'thread_id' => 2, + 'name' => 'Thread 2, Message 1', + 'Bid' => array( + 'id' => 4, + 'message_id' => 2, + 'name' => 'Bid 2.1' + )))))), + array( + 'Project' => array( + 'id' => 2, + 'name' => 'Project 2' + ), + 'Thread' => array( + array( + 'id' => 3, + 'project_id' => 2, + 'name' => 'Project 2, Thread 1', + 'Project' => array( + 'id' => 2, + 'name' => 'Project 2', + 'Thread' => array( + array( + 'id' => 3, + 'project_id' => 2, + 'name' => 'Project 2, Thread 1' + ))), + 'Message' => array( + array( + 'id' => 3, + 'thread_id' => 3, + 'name' => 'Thread 3, Message 1', + 'Bid' => array( + 'id' => 3, + 'message_id' => 3, + 'name' => 'Bid 3.1' + )))))), + array( + 'Project' => array( + 'id' => 3, + 'name' => 'Project 3' + ), + 'Thread' => array() + )); + + $this->assertEqual($result, $expected); + } + +/** + * testFindAllRecursiveSelfJoin method + * + * @access public + * @return void + */ + function testFindAllRecursiveSelfJoin() { + $this->loadFixtures('Home', 'AnotherArticle', 'Advertisement'); + $TestModel =& new Home(); + $TestModel->recursive = 2; + + $result = $TestModel->find('all'); + $expected = array( + array( + 'Home' => array( + 'id' => '1', + 'another_article_id' => '1', + 'advertisement_id' => '1', + 'title' => 'First Home', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'AnotherArticle' => array( + 'id' => '1', + 'title' => 'First Article', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'Home' => array( + array( + 'id' => '1', + 'another_article_id' => '1', + 'advertisement_id' => '1', + 'title' => 'First Home', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ))), + 'Advertisement' => array( + 'id' => '1', + 'title' => 'First Ad', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'Home' => array( + array( + 'id' => '1', + 'another_article_id' => '1', + 'advertisement_id' => '1', + 'title' => 'First Home', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => '2', + 'another_article_id' => '3', + 'advertisement_id' => '1', + 'title' => 'Second Home', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + )))), + array( + 'Home' => array( + 'id' => '2', + 'another_article_id' => '3', + 'advertisement_id' => '1', + 'title' => 'Second Home', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'AnotherArticle' => array( + 'id' => '3', + 'title' => 'Third Article', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31', + 'Home' => array( + array( + 'id' => '2', + 'another_article_id' => '3', + 'advertisement_id' => '1', + 'title' => 'Second Home', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ))), + 'Advertisement' => array( + 'id' => '1', + 'title' => 'First Ad', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31', + 'Home' => array( + array( + 'id' => '1', + 'another_article_id' => '1', + 'advertisement_id' => '1', + 'title' => 'First Home', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => '2', + 'another_article_id' => '3', + 'advertisement_id' => '1', + 'title' => 'Second Home', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ))))); + + $this->assertEqual($result, $expected); + } + +/** + * testFindAllRecursiveWithHabtm method + * + * @return void + * @access public + */ + function testFindAllRecursiveWithHabtm() { + $this->loadFixtures( + 'MyCategoriesMyUsers', + 'MyCategoriesMyProducts', + 'MyCategory', + 'MyUser', + 'MyProduct' + ); + + $MyUser =& new MyUser(); + $MyUser->recursive = 2; + + $result = $MyUser->find('all'); + $expected = array( + array( + 'MyUser' => array('id' => '1', 'firstname' => 'userA'), + 'MyCategory' => array( + array( + 'id' => '1', + 'name' => 'A', + 'MyProduct' => array( + array( + 'id' => '1', + 'name' => 'book' + ))), + array( + 'id' => '3', + 'name' => 'C', + 'MyProduct' => array( + array( + 'id' => '2', + 'name' => 'computer' + ))))), + array( + 'MyUser' => array( + 'id' => '2', + 'firstname' => 'userB' + ), + 'MyCategory' => array( + array( + 'id' => '1', + 'name' => 'A', + 'MyProduct' => array( + array( + 'id' => '1', + 'name' => 'book' + ))), + array( + 'id' => '2', + 'name' => 'B', + 'MyProduct' => array( + array( + 'id' => '1', + 'name' => 'book' + ), + array( + 'id' => '2', + 'name' => 'computer' + )))))); + + $this->assertIdentical($result, $expected); + } + +/** + * testReadFakeThread method + * + * @access public + * @return void + */ + function testReadFakeThread() { + $this->loadFixtures('CategoryThread'); + $TestModel =& new CategoryThread(); + + $fullDebug = $this->db->fullDebug; + $this->db->fullDebug = true; + $TestModel->recursive = 6; + $TestModel->id = 7; + $result = $TestModel->read(); + $expected = array( + 'CategoryThread' => array( + 'id' => 7, + 'parent_id' => 6, + 'name' => 'Category 2.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => 6, + 'parent_id' => 5, + 'name' => 'Category 2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 5, + 'parent_id' => 4, + 'name' => 'Category 1.1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 4, + 'parent_id' => 3, + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 3, + 'parent_id' => 2, + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ))))))); + + $this->db->fullDebug = $fullDebug; + $this->assertEqual($result, $expected); + } + +/** + * testFindFakeThread method + * + * @access public + * @return void + */ + function testFindFakeThread() { + $this->loadFixtures('CategoryThread'); + $TestModel =& new CategoryThread(); + + $fullDebug = $this->db->fullDebug; + $this->db->fullDebug = true; + $TestModel->recursive = 6; + $result = $TestModel->find(array('CategoryThread.id' => 7)); + + $expected = array( + 'CategoryThread' => array( + 'id' => 7, + 'parent_id' => 6, + 'name' => 'Category 2.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => 6, + 'parent_id' => 5, + 'name' => 'Category 2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 5, + 'parent_id' => 4, + 'name' => 'Category 1.1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 4, + 'parent_id' => 3, + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 3, + 'parent_id' => 2, + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ))))))); + + $this->db->fullDebug = $fullDebug; + $this->assertEqual($result, $expected); + } + +/** + * testFindAllFakeThread method + * + * @access public + * @return void + */ + function testFindAllFakeThread() { + $this->loadFixtures('CategoryThread'); + $TestModel =& new CategoryThread(); + + $fullDebug = $this->db->fullDebug; + $this->db->fullDebug = true; + $TestModel->recursive = 6; + $result = $TestModel->find('all', null, null, 'CategoryThread.id ASC'); + $expected = array( + array( + 'CategoryThread' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => null, + 'parent_id' => null, + 'name' => null, + 'created' => null, + 'updated' => null, + 'ParentCategory' => array() + )), + array( + 'CategoryThread' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array() + )), + array( + 'CategoryThread' => array( + 'id' => 3, + 'parent_id' => 2, + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array() + ))), + array( + 'CategoryThread' => array( + 'id' => 4, + 'parent_id' => 3, + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => 3, + 'parent_id' => 2, + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array() + )))), + array( + 'CategoryThread' => array( + 'id' => 5, + 'parent_id' => 4, + 'name' => 'Category 1.1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => 4, + 'parent_id' => 3, + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 3, + 'parent_id' => 2, + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array() + ))))), + array( + 'CategoryThread' => array( + 'id' => 6, + 'parent_id' => 5, + 'name' => 'Category 2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => 5, + 'parent_id' => 4, + 'name' => 'Category 1.1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 4, + 'parent_id' => 3, + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 3, + 'parent_id' => 2, + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array() + )))))), + array( + 'CategoryThread' => array( + 'id' => 7, + 'parent_id' => 6, + 'name' => 'Category 2.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ), + 'ParentCategory' => array( + 'id' => 6, + 'parent_id' => 5, + 'name' => 'Category 2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 5, + 'parent_id' => 4, + 'name' => 'Category 1.1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 4, + 'parent_id' => 3, + 'name' => 'Category 1.1.2', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 3, + 'parent_id' => 2, + 'name' => 'Category 1.1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 2, + 'parent_id' => 1, + 'name' => 'Category 1.1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31', + 'ParentCategory' => array( + 'id' => 1, + 'parent_id' => 0, + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + )))))))); + + $this->db->fullDebug = $fullDebug; + $this->assertEqual($result, $expected); + } + +/** + * testConditionalNumerics method + * + * @access public + * @return void + */ + function testConditionalNumerics() { + $this->loadFixtures('NumericArticle'); + $NumericArticle =& new NumericArticle(); + $data = array('title' => '12345abcde'); + $result = $NumericArticle->find($data); + $this->assertTrue(!empty($result)); + + $data = array('title' => '12345'); + $result = $NumericArticle->find($data); + $this->assertTrue(empty($result)); + } + +/** + * test find('all') method + * + * @access public + * @return void + */ + function testFindAll() { + $this->loadFixtures('User'); + $TestModel =& new User(); + $TestModel->cacheQueries = false; + + $result = $TestModel->find('all'); + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + )), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + )), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + )), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', + 'updated' => '2007-03-17 01:24:31' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('conditions' => 'User.id > 2')); + $expected = array( + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + )), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', + 'updated' => '2007-03-17 01:24:31' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array( + 'conditions' => array('User.id !=' => '0', 'User.user LIKE' => '%arr%') + )); + $expected = array( + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + )), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', + 'updated' => '2007-03-17 01:24:31' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('conditions' => array('User.id' => '0'))); + $expected = array(); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array( + 'conditions' => array('or' => array('User.id' => '0', 'User.user LIKE' => '%a%') + ))); + + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + )), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + )), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + )), + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', + 'updated' => '2007-03-17 01:24:31' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('fields' => 'User.id, User.user')); + $expected = array( + array('User' => array('id' => '1', 'user' => 'mariano')), + array('User' => array('id' => '2', 'user' => 'nate')), + array('User' => array('id' => '3', 'user' => 'larry')), + array('User' => array('id' => '4', 'user' => 'garrett'))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('fields' => 'User.user', 'order' => 'User.user ASC')); + $expected = array( + array('User' => array('user' => 'garrett')), + array('User' => array('user' => 'larry')), + array('User' => array('user' => 'mariano')), + array('User' => array('user' => 'nate'))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('fields' => 'User.user', 'order' => 'User.user DESC')); + $expected = array( + array('User' => array('user' => 'nate')), + array('User' => array('user' => 'mariano')), + array('User' => array('user' => 'larry')), + array('User' => array('user' => 'garrett'))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('limit' => 3, 'page' => 1)); + + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + )), + array( + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + )), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ))); + $this->assertEqual($result, $expected); + + $ids = array(4 => 1, 5 => 3); + $result = $TestModel->find('all', array( + 'conditions' => array('User.id' => $ids), + 'order' => 'User.id' + )); + $expected = array( + array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + )), + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ))); + $this->assertEqual($result, $expected); + + // These tests are expected to fail on SQL Server since the LIMIT/OFFSET + // hack can't handle small record counts. + if ($this->db->config['driver'] != 'mssql') { + $result = $TestModel->find('all', array('limit' => 3, 'page' => 2)); + $expected = array( + array( + 'User' => array( + 'id' => '4', + 'user' => 'garrett', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:22:23', + 'updated' => '2007-03-17 01:24:31' + ))); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array('limit' => 3, 'page' => 3)); + $expected = array(); + $this->assertEqual($result, $expected); + } + } + +/** + * test find('list') method + * + * @access public + * @return void + */ + function testGenerateFindList() { + $this->loadFixtures('Article', 'Apple', 'Post', 'Author', 'User'); + + $TestModel =& new Article(); + $TestModel->displayField = 'title'; + + $result = $TestModel->find('list', array( + 'order' => 'Article.title ASC' + )); + + $expected = array( + 1 => 'First Article', + 2 => 'Second Article', + 3 => 'Third Article' + ); + $this->assertEqual($result, $expected); + + $db =& ConnectionManager::getDataSource('test_suite'); + if ($db->config['driver'] == 'mysql') { + $result = $TestModel->find('list', array( + 'order' => array('FIELD(Article.id, 3, 2) ASC', 'Article.title ASC') + )); + $expected = array( + 1 => 'First Article', + 3 => 'Third Article', + 2 => 'Second Article' + ); + $this->assertEqual($result, $expected); + } + + $result = Set::combine( + $TestModel->find('all', array( + 'order' => 'Article.title ASC', + 'fields' => array('id', 'title') + )), + '{n}.Article.id', '{n}.Article.title' + ); + $expected = array( + 1 => 'First Article', + 2 => 'Second Article', + 3 => 'Third Article' + ); + $this->assertEqual($result, $expected); + + $result = Set::combine( + $TestModel->find('all', array( + 'order' => 'Article.title ASC' + )), + '{n}.Article.id', '{n}.Article' + ); + $expected = array( + 1 => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 2 => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 3 => array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + )); + + $this->assertEqual($result, $expected); + + $result = Set::combine( + $TestModel->find('all', array( + 'order' => 'Article.title ASC' + )), + '{n}.Article.id', '{n}.Article', '{n}.Article.user_id' + ); + $expected = array( + 1 => array( + 1 => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 3 => array( + 'id' => 3, + 'user_id' => 1, + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + )), + 3 => array( + 2 => array( + 'id' => 2, + 'user_id' => 3, + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ))); + + $this->assertEqual($result, $expected); + + $result = Set::combine( + $TestModel->find('all', array( + 'order' => 'Article.title ASC', + 'fields' => array('id', 'title', 'user_id') + )), + '{n}.Article.id', '{n}.Article.title', '{n}.Article.user_id' + ); + + $expected = array( + 1 => array( + 1 => 'First Article', + 3 => 'Third Article' + ), + 3 => array( + 2 => 'Second Article' + )); + $this->assertEqual($result, $expected); + + $TestModel =& new Apple(); + $expected = array( + 1 => 'Red Apple 1', + 2 => 'Bright Red Apple', + 3 => 'green blue', + 4 => 'Test Name', + 5 => 'Blue Green', + 6 => 'My new apple', + 7 => 'Some odd color' + ); + + $this->assertEqual($TestModel->find('list'), $expected); + $this->assertEqual($TestModel->Parent->find('list'), $expected); + + $TestModel =& new Post(); + $result = $TestModel->find('list', array( + 'fields' => 'Post.title' + )); + $expected = array( + 1 => 'First Post', + 2 => 'Second Post', + 3 => 'Third Post' + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('list', array( + 'fields' => 'title' + )); + $expected = array( + 1 => 'First Post', + 2 => 'Second Post', + 3 => 'Third Post' + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('list', array( + 'fields' => array('title', 'id') + )); + $expected = array( + 'First Post' => '1', + 'Second Post' => '2', + 'Third Post' => '3' + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('list', array( + 'fields' => array('title', 'id', 'created') + )); + $expected = array( + '2007-03-18 10:39:23' => array( + 'First Post' => '1' + ), + '2007-03-18 10:41:23' => array( + 'Second Post' => '2' + ), + '2007-03-18 10:43:23' => array( + 'Third Post' => '3' + ), + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('list', array( + 'fields' => array('Post.body') + )); + $expected = array( + 1 => 'First Post Body', + 2 => 'Second Post Body', + 3 => 'Third Post Body' + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('list', array( + 'fields' => array('Post.title', 'Post.body') + )); + $expected = array( + 'First Post' => 'First Post Body', + 'Second Post' => 'Second Post Body', + 'Third Post' => 'Third Post Body' + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('list', array( + 'fields' => array('Post.id', 'Post.title', 'Author.user'), + 'recursive' => 1 + )); + $expected = array( + 'mariano' => array( + 1 => 'First Post', + 3 => 'Third Post' + ), + 'larry' => array( + 2 => 'Second Post' + )); + $this->assertEqual($result, $expected); + + $TestModel =& new User(); + $result = $TestModel->find('list', array( + 'fields' => array('User.user', 'User.password') + )); + $expected = array( + 'mariano' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'nate' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'larry' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'garrett' => '5f4dcc3b5aa765d61d8327deb882cf99' + ); + $this->assertEqual($result, $expected); + + $TestModel =& new ModifiedAuthor(); + $result = $TestModel->find('list', array( + 'fields' => array('Author.id', 'Author.user') + )); + $expected = array( + 1 => 'mariano (CakePHP)', + 2 => 'nate (CakePHP)', + 3 => 'larry (CakePHP)', + 4 => 'garrett (CakePHP)' + ); + $this->assertEqual($result, $expected); + + $TestModel =& new Article(); + $TestModel->displayField = 'title'; + $result = $TestModel->find('list', array( + 'conditions' => array('User.user' => 'mariano'), + 'recursive' => 0 + )); + $expected = array( + 1 => 'First Article', + 3 => 'Third Article' + ); + $this->assertEqual($result, $expected); + } + +/** + * testFindField method + * + * @access public + * @return void + */ + function testFindField() { + $this->loadFixtures('User'); + $TestModel =& new User(); + + $TestModel->id = 1; + $result = $TestModel->field('user'); + $this->assertEqual($result, 'mariano'); + + $result = $TestModel->field('User.user'); + $this->assertEqual($result, 'mariano'); + + $TestModel->id = false; + $result = $TestModel->field('user', array( + 'user' => 'mariano' + )); + $this->assertEqual($result, 'mariano'); + + $result = $TestModel->field('COUNT(*) AS count', true); + $this->assertEqual($result, 4); + + $result = $TestModel->field('COUNT(*)', true); + $this->assertEqual($result, 4); + } + +/** + * testFindUnique method + * + * @access public + * @return void + */ + function testFindUnique() { + $this->loadFixtures('User'); + $TestModel =& new User(); + + $this->assertFalse($TestModel->isUnique(array( + 'user' => 'nate' + ))); + $TestModel->id = 2; + $this->assertTrue($TestModel->isUnique(array( + 'user' => 'nate' + ))); + $this->assertFalse($TestModel->isUnique(array( + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99' + ))); + } + +/** + * test find('count') method + * + * @access public + * @return void + */ + function testFindCount() { + $this->loadFixtures('User', 'Project'); + + $TestModel =& new User(); + $result = $TestModel->find('count'); + $this->assertEqual($result, 4); + + $fullDebug = $this->db->fullDebug; + $this->db->fullDebug = true; + $TestModel->order = 'User.id'; + $this->db->_queriesLog = array(); + $result = $TestModel->find('count'); + $this->assertEqual($result, 4); + + $this->assertTrue(isset($this->db->_queriesLog[0]['query'])); + $this->assertNoPattern('/ORDER\s+BY/', $this->db->_queriesLog[0]['query']); + } + +/** + * Test that find('first') does not use the id set to the object. + * + * @return void + */ + function testFindFirstNoIdUsed() { + $this->loadFixtures('Project'); + + $Project =& new Project(); + $Project->id = 3; + $result = $Project->find('first'); + + $this->assertEqual($result['Project']['name'], 'Project 1', 'Wrong record retrieved'); + } + +/** + * test find with COUNT(DISTINCT field) + * + * @return void + */ + function testFindCountDistinct() { + $skip = $this->skipIf( + $this->db->config['driver'] == 'sqlite', + 'SELECT COUNT(DISTINCT field) is not compatible with SQLite' + ); + if ($skip) { + return; + } + $this->loadFixtures('Project'); + $TestModel =& new Project(); + $TestModel->create(array('name' => 'project')) && $TestModel->save(); + $TestModel->create(array('name' => 'project')) && $TestModel->save(); + $TestModel->create(array('name' => 'project')) && $TestModel->save(); + + $result = $TestModel->find('count', array('fields' => 'DISTINCT name')); + $this->assertEqual($result, 4); + } + +/** + * Test find(count) with Db::expression + * + * @access public + * @return void + */ + function testFindCountWithDbExpressions() { + if ($this->skipIf($this->db->config['driver'] == 'postgres', '%s testFindCountWithExpressions is not compatible with Postgres')) { + return; + } + $this->loadFixtures('Project'); + $db = ConnectionManager::getDataSource('test_suite'); + $TestModel =& new Project(); + + $result = $TestModel->find('count', array('conditions' => array( + $db->expression('Project.name = \'Project 3\'') + ))); + $this->assertEqual($result, 1); + + $result = $TestModel->find('count', array('conditions' => array( + 'Project.name' => $db->expression('\'Project 3\'') + ))); + $this->assertEqual($result, 1); + } + +/** + * testFindMagic method + * + * @access public + * @return void + */ + function testFindMagic() { + $this->loadFixtures('User'); + $TestModel =& new User(); + + $result = $TestModel->findByUser('mariano'); + $expected = array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + )); + $this->assertEqual($result, $expected); + + $result = $TestModel->findByPassword('5f4dcc3b5aa765d61d8327deb882cf99'); + $expected = array('User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + )); + $this->assertEqual($result, $expected); + } + +/** + * testRead method + * + * @access public + * @return void + */ + function testRead() { + $this->loadFixtures('User', 'Article'); + $TestModel =& new User(); + + $result = $TestModel->read(); + $this->assertFalse($result); + + $TestModel->id = 2; + $result = $TestModel->read(); + $expected = array( + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + )); + $this->assertEqual($result, $expected); + + $result = $TestModel->read(null, 2); + $expected = array( + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + )); + $this->assertEqual($result, $expected); + + $TestModel->id = 2; + $result = $TestModel->read(array('id', 'user')); + $expected = array('User' => array('id' => '2', 'user' => 'nate')); + $this->assertEqual($result, $expected); + + $result = $TestModel->read('id, user', 2); + $expected = array( + 'User' => array( + 'id' => '2', + 'user' => 'nate' + )); + $this->assertEqual($result, $expected); + + $result = $TestModel->bindModel(array('hasMany' => array('Article'))); + $this->assertTrue($result); + + $TestModel->id = 1; + $result = $TestModel->read('id, user'); + $expected = array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano' + ), + 'Article' => array( + array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => '3', + 'user_id' => '1', + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ))); + $this->assertEqual($result, $expected); + } + +/** + * testRecursiveRead method + * + * @access public + * @return void + */ + function testRecursiveRead() { + $this->loadFixtures( + 'User', + 'Article', + 'Comment', + 'Tag', + 'ArticlesTag', + 'Featured', + 'ArticleFeatured' + ); + $TestModel =& new User(); + + $result = $TestModel->bindModel(array('hasMany' => array('Article')), false); + $this->assertTrue($result); + + $TestModel->recursive = 0; + $result = $TestModel->read('id, user', 1); + $expected = array( + 'User' => array('id' => '1', 'user' => 'mariano'), + ); + $this->assertEqual($result, $expected); + + $TestModel->recursive = 1; + $result = $TestModel->read('id, user', 1); + $expected = array( + 'User' => array( + 'id' => '1', + 'user' => 'mariano' + ), + 'Article' => array( + array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + array( + 'id' => '3', + 'user_id' => '1', + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ))); + $this->assertEqual($result, $expected); + + $TestModel->recursive = 2; + $result = $TestModel->read('id, user', 3); + $expected = array( + 'User' => array( + 'id' => '3', + 'user' => 'larry' + ), + 'Article' => array( + array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31', + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ), + 'Comment' => array( + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + )), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))))); + $this->assertEqual($result, $expected); + } + + function testRecursiveFindAll() { + $this->db->truncate(new Featured()); + + $this->loadFixtures( + 'User', + 'Article', + 'Comment', + 'Tag', + 'ArticlesTag', + 'Attachment', + 'ArticleFeatured', + 'Featured', + 'Category' + ); + $TestModel =& new Article(); + + $result = $TestModel->find('all', array('conditions' => array('Article.user_id' => 1))); + $expected = array( + array( + 'Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + ), + array( + 'id' => '3', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Third Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:49:23', + 'updated' => '2007-03-18 10:51:31' + ), + array( + 'id' => '4', + 'article_id' => '1', + 'user_id' => '1', + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + ) + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ))), + array( + 'Article' => array( + 'id' => '3', + 'user_id' => '1', + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array(), + 'Tag' => array() + ) + ); + $this->assertEqual($result, $expected); + + $result = $TestModel->find('all', array( + 'conditions' => array('Article.user_id' => 3), + 'limit' => 1, + 'recursive' => 2 + )); + + $expected = array( + array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ), + 'Comment' => array( + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31', + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Attachment' => array( + 'id' => '1', + 'comment_id' => 5, + 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + ) + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31', + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + ), + 'Attachment' => false + ) + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + )))); + + $this->assertEqual($result, $expected); + + $Featured = new Featured(); + + $Featured->recursive = 2; + $Featured->bindModel(array( + 'belongsTo' => array( + 'ArticleFeatured' => array( + 'conditions' => "ArticleFeatured.published = 'Y'", + 'fields' => 'id, title, user_id, published' + ) + ) + )); + + $Featured->ArticleFeatured->unbindModel(array( + 'hasMany' => array('Attachment', 'Comment'), + 'hasAndBelongsToMany' => array('Tag')) + ); + + $orderBy = 'ArticleFeatured.id ASC'; + $result = $Featured->find('all', array( + 'order' => $orderBy, 'limit' => 3 + )); + + $expected = array( + array( + 'Featured' => array( + 'id' => '1', + 'article_featured_id' => '1', + 'category_id' => '1', + 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'ArticleFeatured' => array( + 'id' => '1', + 'title' => 'First Article', + 'user_id' => '1', + 'published' => 'Y', + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Category' => array(), + 'Featured' => array( + 'id' => '1', + 'article_featured_id' => '1', + 'category_id' => '1', + 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + 'Category' => array( + 'id' => '1', + 'parent_id' => '0', + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + )), + array( + 'Featured' => array( + 'id' => '2', + 'article_featured_id' => '2', + 'category_id' => '1', + 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'ArticleFeatured' => array( + 'id' => '2', + 'title' => 'Second Article', + 'user_id' => '3', + 'published' => 'Y', + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ), + 'Category' => array(), + 'Featured' => array( + 'id' => '2', + 'article_featured_id' => '2', + 'category_id' => '1', + 'published_date' => '2007-03-31 10:39:23', + 'end_date' => '2007-05-15 10:39:23', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + 'Category' => array( + 'id' => '1', + 'parent_id' => '0', + 'name' => 'Category 1', + 'created' => '2007-03-18 15:30:23', + 'updated' => '2007-03-18 15:32:31' + ))); + $this->assertEqual($result, $expected); + } + +/** + * testRecursiveFindAllWithLimit method + * + * @access public + * @return void + */ + function testRecursiveFindAllWithLimit() { + $this->loadFixtures('Article', 'User', 'Tag', 'ArticlesTag', 'Comment', 'Attachment'); + $TestModel =& new Article(); + + $TestModel->hasMany['Comment']['limit'] = 2; + + $result = $TestModel->find('all', array( + 'conditions' => array('Article.user_id' => 1) + )); + $expected = array( + array( + 'Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array( + array( + 'id' => '1', + 'article_id' => '1', + 'user_id' => '2', + 'comment' => 'First Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:45:23', + 'updated' => '2007-03-18 10:47:31' + ), + array( + 'id' => '2', + 'article_id' => '1', + 'user_id' => '4', + 'comment' => 'Second Comment for First Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:47:23', + 'updated' => '2007-03-18 10:49:31' + ), + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ))), + array( + 'Article' => array( + 'id' => '3', + 'user_id' => '1', + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Comment' => array(), + 'Tag' => array() + ) + ); + $this->assertEqual($result, $expected); + + $TestModel->hasMany['Comment']['limit'] = 1; + + $result = $TestModel->find('all', array( + 'conditions' => array('Article.user_id' => 3), + 'limit' => 1, + 'recursive' => 2 + )); + $expected = array( + array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ), + 'Comment' => array( + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31', + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Attachment' => array( + 'id' => '1', + 'comment_id' => 5, + 'attachment' => 'attachment.zip', + 'created' => '2007-03-18 10:51:23', + 'updated' => '2007-03-18 10:53:31' + ) + ) + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ) + ) + ) + ); + $this->assertEqual($result, $expected); + } +/** + * Testing availability of $this->findQueryType in Model callbacks + * + * @return void + */ + function testFindQueryTypeInCallbacks() { + $this->loadFixtures('Comment'); + $Comment =& new AgainModifiedComment(); + $comments = $Comment->find('all'); + $this->assertEqual($comments[0]['Comment']['querytype'], 'all'); + $comments = $Comment->find('first'); + $this->assertEqual($comments['Comment']['querytype'], 'first'); + } + +/** + * testVirtualFields() + * + * Test correct fetching of virtual fields + * currently is not possible to do Relation.virtualField + * + * @access public + * @return void + */ + function testVirtualFields() { + $this->loadFixtures('Post', 'Author'); + $Post =& ClassRegistry::init('Post'); + $Post->virtualFields = array('two' => "1 + 1"); + $result = $Post->find('first'); + $this->assertEqual($result['Post']['two'], 2); + + $Post->Author->virtualFields = array('false' => '1 = 2'); + $result = $Post->find('first'); + $this->assertEqual($result['Post']['two'], 2); + $this->assertEqual($result['Author']['false'], false); + + $result = $Post->find('first',array('fields' => array('author_id'))); + $this->assertFalse(isset($result['Post']['two'])); + $this->assertFalse(isset($result['Author']['false'])); + + $result = $Post->find('first',array('fields' => array('author_id', 'two'))); + $this->assertEqual($result['Post']['two'], 2); + $this->assertFalse(isset($result['Author']['false'])); + + $result = $Post->find('first',array('fields' => array('two'))); + $this->assertEqual($result['Post']['two'], 2); + + $Post->id = 1; + $result = $Post->field('two'); + $this->assertEqual($result, 2); + + $result = $Post->find('first',array( + 'conditions' => array('two' => 2), + 'limit' => 1 + )); + $this->assertEqual($result['Post']['two'], 2); + + $result = $Post->find('first',array( + 'conditions' => array('two <' => 3), + 'limit' => 1 + )); + $this->assertEqual($result['Post']['two'], 2); + + $result = $Post->find('first',array( + 'conditions' => array('NOT' => array('two >' => 3)), + 'limit' => 1 + )); + $this->assertEqual($result['Post']['two'], 2); + + $dbo =& $Post->getDataSource(); + $Post->virtualFields = array('other_field' => 'Post.id + 1'); + $result = $Post->find('first', array( + 'conditions' => array('other_field' => 3), + 'limit' => 1 + )); + $this->assertEqual($result['Post']['id'], 2); + + $Post->virtualFields = array('other_field' => 'Post.id + 1'); + $result = $Post->find('all', array( + 'fields' => array($dbo->calculate($Post, 'max', array('other_field'))) + )); + $this->assertEqual($result[0][0]['other_field'], 4); + + ClassRegistry::flush(); + $Writing =& ClassRegistry::init(array('class' => 'Post', 'alias' => 'Writing'), 'Model'); + $Writing->virtualFields = array('two' => "1 + 1"); + $result = $Writing->find('first'); + $this->assertEqual($result['Writing']['two'], 2); + + $Post->create(); + $Post->virtualFields = array('other_field' => 'COUNT(Post.id) + 1'); + $result = $Post->field('other_field'); + $this->assertEqual($result, 4); + + if ($this->skipIf($this->db->config['driver'] == 'postgres', 'The rest of virtualFieds test is not compatible with Postgres')) { + return; + } + ClassRegistry::flush(); + $Post =& ClassRegistry::init('Post'); + + $Post->create(); + $Post->virtualFields = array( + 'year' => 'YEAR(Post.created)', + 'unique_test_field' => 'COUNT(Post.id)' + ); + + $expectation = array( + 'Post' => array( + 'year' => 2007, + 'unique_test_field' => 3 + ) + ); + + $result = $Post->find('first', array( + 'fields' => array_keys($Post->virtualFields), + 'group' => array('year') + )); + + $this->assertEqual($result, $expectation); + + + $Author =& ClassRegistry::init('Author'); + $Author->virtualFields = array( + 'full_name' => 'CONCAT(Author.user, " ", Author.id)' + ); + + $result = $Author->find('first', array( + 'conditions' => array('Author.user' => 'mariano'), + 'fields' => array('Author.password', 'Author.full_name'), + 'recursive' => -1 + )); + $this->assertTrue(isset($result['Author']['full_name'])); + + $result = $Author->find('first', array( + 'conditions' => array('Author.user' => 'mariano'), + 'fields' => array('Author.full_name', 'Author.password'), + 'recursive' => -1 + )); + $this->assertTrue(isset($result['Author']['full_name'])); + } + +/** + * test that virtual fields work when they don't contain functions. + * + * @return void + */ + function testVirtualFieldAsAString() { + $this->loadFixtures('Post', 'Author'); + $Post =& new Post(); + $Post->virtualFields = array( + 'writer' => 'Author.user' + ); + $result = $Post->find('first'); + $this->assertTrue(isset($result['Post']['writer']), 'virtual field not fetched %s'); + } + +/** + * test that isVirtualField will accept both aliased and non aliased fieldnames + * + * @return void + */ + function testIsVirtualField() { + $this->loadFixtures('Post'); + $Post =& ClassRegistry::init('Post'); + $Post->virtualFields = array('other_field' => 'COUNT(Post.id) + 1'); + + $this->assertTrue($Post->isVirtualField('other_field')); + $this->assertTrue($Post->isVirtualField('Post.other_field')); + $this->assertFalse($Post->isVirtualField('id')); + $this->assertFalse($Post->isVirtualField('Post.id')); + $this->assertFalse($Post->isVirtualField(array())); + } + +/** + * test that getting virtual fields works with and without model alias attached + * + * @return void + */ + function testGetVirtualField() { + $this->loadFixtures('Post'); + $Post =& ClassRegistry::init('Post'); + $Post->virtualFields = array('other_field' => 'COUNT(Post.id) + 1'); + + $this->assertEqual($Post->getVirtualField('other_field'), $Post->virtualFields['other_field']); + $this->assertEqual($Post->getVirtualField('Post.other_field'), $Post->virtualFields['other_field']); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_validation.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_validation.test.php new file mode 100644 index 000000000..d0b3fb648 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_validation.test.php @@ -0,0 +1,673 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once dirname(__FILE__) . DS . 'model.test.php'; + +/** + * ModelValidationTest + * + * @package cake + * @subpackage cake.tests.cases.libs.model.operations + */ +class ModelValidationTest extends BaseModelTest { + +/** + * Tests validation parameter order in custom validation methods + * + * @access public + * @return void + */ + function testValidationParams() { + $TestModel =& new ValidationTest1(); + $TestModel->validate['title'] = array( + 'rule' => 'customValidatorWithParams', + 'required' => true + ); + $TestModel->create(array('title' => 'foo')); + $TestModel->invalidFields(); + + $expected = array( + 'data' => array( + 'title' => 'foo' + ), + 'validator' => array( + 'rule' => 'customValidatorWithParams', + 'on' => null, + 'last' => false, + 'allowEmpty' => false, + 'required' => true + ), + 'or' => true, + 'ignore_on_same' => 'id' + ); + $this->assertEqual($TestModel->validatorParams, $expected); + + $TestModel->validate['title'] = array( + 'rule' => 'customValidatorWithMessage', + 'required' => true + ); + $expected = array( + 'title' => 'This field will *never* validate! Muhahaha!' + ); + + $this->assertEqual($TestModel->invalidFields(), $expected); + + $TestModel->validate['title'] = array( + 'rule' => array('customValidatorWithSixParams', 'one', 'two', null, 'four'), + 'required' => true + ); + $TestModel->create(array('title' => 'foo')); + $TestModel->invalidFields(); + $expected = array( + 'data' => array( + 'title' => 'foo' + ), + 'one' => 'one', + 'two' => 'two', + 'three' => null, + 'four' => 'four', + 'five' => array( + 'rule' => array(1 => 'one', 2 => 'two', 3 => null, 4 => 'four'), + 'on' => null, + 'last' => false, + 'allowEmpty' => false, + 'required' => true + ), + 'six' => 6 + ); + $this->assertEqual($TestModel->validatorParams, $expected); + + $TestModel->validate['title'] = array( + 'rule' => array('customValidatorWithSixParams', 'one', array('two'), null, 'four', array('five' => 5)), + 'required' => true + ); + $TestModel->create(array('title' => 'foo')); + $TestModel->invalidFields(); + $expected = array( + 'data' => array( + 'title' => 'foo' + ), + 'one' => 'one', + 'two' => array('two'), + 'three' => null, + 'four' => 'four', + 'five' => array('five' => 5), + 'six' => array( + 'rule' => array(1 => 'one', 2 => array('two'), 3 => null, 4 => 'four', 5 => array('five' => 5)), + 'on' => null, + 'last' => false, + 'allowEmpty' => false, + 'required' => true + ) + ); + $this->assertEqual($TestModel->validatorParams, $expected); + } + +/** + * Tests validation parameter fieldList in invalidFields + * + * @access public + * @return void + */ + function testInvalidFieldsWithFieldListParams() { + $TestModel =& new ValidationTest1(); + $TestModel->validate = $validate = array( + 'title' => array( + 'rule' => 'alphaNumeric', + 'required' => true + ), + 'name' => array( + 'rule' => 'alphaNumeric', + 'required' => true + )); + $TestModel->set(array('title' => '$$', 'name' => '##')); + $TestModel->invalidFields(array('fieldList' => array('title'))); + $expected = array( + 'title' => 'This field cannot be left blank' + ); + $this->assertEqual($TestModel->validationErrors, $expected); + $TestModel->validationErrors = array(); + + $TestModel->invalidFields(array('fieldList' => array('name'))); + $expected = array( + 'name' => 'This field cannot be left blank' + ); + $this->assertEqual($TestModel->validationErrors, $expected); + $TestModel->validationErrors = array(); + + $TestModel->invalidFields(array('fieldList' => array('name', 'title'))); + $expected = array( + 'name' => 'This field cannot be left blank', + 'title' => 'This field cannot be left blank' + ); + $this->assertEqual($TestModel->validationErrors, $expected); + $TestModel->validationErrors = array(); + + $TestModel->whitelist = array('name'); + $TestModel->invalidFields(); + $expected = array('name' => 'This field cannot be left blank'); + $this->assertEqual($TestModel->validationErrors, $expected); + + $this->assertEqual($TestModel->validate, $validate); + } + +/** + * Test that invalidFields() integrates well with save(). And that fieldList can be an empty type. + * + * @return void + */ + function testInvalidFieldsWhitelist() { + $TestModel =& new ValidationTest1(); + $TestModel->validate = array( + 'title' => array( + 'rule' => 'alphaNumeric', + 'required' => true + ), + 'name' => array( + 'rule' => 'alphaNumeric', + 'required' => true + )); + + $TestModel->whitelist = array('name'); + $TestModel->save(array('name' => '#$$#', 'title' => '$$$$')); + + $expected = array('name' => 'This field cannot be left blank'); + $this->assertEqual($TestModel->validationErrors, $expected); + } + +/** + * testValidates method + * + * @access public + * @return void + */ + function testValidates() { + $TestModel =& new TestValidate(); + + $TestModel->validate = array( + 'user_id' => 'numeric', + 'title' => array('allowEmpty' => false, 'rule' => 'notEmpty'), + 'body' => 'notEmpty' + ); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => '', + 'body' => 'body' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 'title', + 'body' => 'body' + )); + $result = $TestModel->create($data) && $TestModel->validates(); + $this->assertTrue($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => '0', + 'body' => 'body' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $TestModel->validate['modified'] = array('allowEmpty' => true, 'rule' => 'date'); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body', + 'modified' => '' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body', + 'modified' => '2007-05-01' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body', + 'modified' => 'invalid-date-here' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body', + 'modified' => 0 + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body', + 'modified' => '0' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $TestModel->validate['modified'] = array('allowEmpty' => false, 'rule' => 'date'); + + $data = array('TestValidate' => array('modified' => null)); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array('modified' => false)); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array('modified' => '')); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'modified' => '2007-05-01' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $TestModel->validate['slug'] = array('allowEmpty' => false, 'rule' => array('maxLength', 45)); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body', + 'slug' => '' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body', + 'slug' => 'slug-right-here' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $data = array('TestValidate' => array( + 'user_id' => '1', + 'title' => 0, + 'body' => 'body', + 'slug' => 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $TestModel->validate = array( + 'number' => array( + 'rule' => 'validateNumber', + 'min' => 3, + 'max' => 5 + ), + 'title' => array( + 'allowEmpty' => false, + 'rule' => 'notEmpty' + )); + + $data = array('TestValidate' => array( + 'title' => 'title', + 'number' => '0' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'title' => 'title', + 'number' => 0 + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'title' => 'title', + 'number' => '3' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $data = array('TestValidate' => array( + 'title' => 'title', + 'number' => 3 + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $TestModel->validate = array( + 'number' => array( + 'rule' => 'validateNumber', + 'min' => 5, + 'max' => 10 + ), + 'title' => array( + 'allowEmpty' => false, + 'rule' => 'notEmpty' + )); + + $data = array('TestValidate' => array( + 'title' => 'title', + 'number' => '3' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'title' => 'title', + 'number' => 3 + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $TestModel->validate = array( + 'title' => array( + 'allowEmpty' => false, + 'rule' => 'validateTitle' + )); + + $data = array('TestValidate' => array('title' => '')); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array('title' => 'new title')); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array('title' => 'title-new')); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $TestModel->validate = array('title' => array( + 'allowEmpty' => true, + 'rule' => 'validateTitle' + )); + $data = array('TestValidate' => array('title' => '')); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $TestModel->validate = array( + 'title' => array( + 'length' => array( + 'allowEmpty' => true, + 'rule' => array('maxLength', 10) + ))); + $data = array('TestValidate' => array('title' => '')); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $TestModel->validate = array( + 'title' => array( + 'rule' => array('userDefined', 'Article', 'titleDuplicate') + )); + $data = array('TestValidate' => array('title' => 'My Article Title')); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertFalse($result); + + $data = array('TestValidate' => array( + 'title' => 'My Article With a Different Title' + )); + $result = $TestModel->create($data); + $this->assertTrue($result); + $result = $TestModel->validates(); + $this->assertTrue($result); + + $TestModel->validate = array( + 'title' => array( + 'tooShort' => array('rule' => array('minLength', 50)), + 'onlyLetters' => array('rule' => '/^[a-z]+$/i') + ), + ); + $data = array('TestValidate' => array( + 'title' => 'I am a short string' + )); + $TestModel->create($data); + $result = $TestModel->validates(); + $this->assertFalse($result); + $result = $TestModel->validationErrors; + $expected = array( + 'title' => 'onlyLetters' + ); + $this->assertEqual($result, $expected); + + $TestModel->validate = array( + 'title' => array( + 'tooShort' => array( + 'rule' => array('minLength', 50), + 'last' => true + ), + 'onlyLetters' => array('rule' => '/^[a-z]+$/i') + ), + ); + $data = array('TestValidate' => array( + 'title' => 'I am a short string' + )); + $TestModel->create($data); + $result = $TestModel->validates(); + $this->assertFalse($result); + $result = $TestModel->validationErrors; + $expected = array( + 'title' => 'tooShort' + ); + $this->assertEqual($result, $expected); + } + +/** + * test that validates() checks all the 'with' associations as well for validation + * as this can cause partial/wrong data insertion. + * + * @return void + */ + function testValidatesWithAssociations() { + $data = array( + 'Something' => array( + 'id' => 5, + 'title' => 'Extra Fields', + 'body' => 'Extra Fields Body', + 'published' => '1' + ), + 'SomethingElse' => array( + array('something_else_id' => 1, 'doomed' => '') + ) + ); + + $Something =& new Something(); + $JoinThing =& $Something->JoinThing; + + $JoinThing->validate = array('doomed' => array('rule' => 'notEmpty')); + + $expectedError = array('doomed' => 'This field cannot be left blank'); + + $Something->create(); + $result = $Something->save($data); + $this->assertFalse($result, 'Save occured even when with models failed. %s'); + $this->assertEqual($JoinThing->validationErrors, $expectedError); + $count = $Something->find('count', array('conditions' => array('Something.id' => $data['Something']['id']))); + $this->assertIdentical($count, 0); + + $data = array( + 'Something' => array( + 'id' => 5, + 'title' => 'Extra Fields', + 'body' => 'Extra Fields Body', + 'published' => '1' + ), + 'SomethingElse' => array( + array('something_else_id' => 1, 'doomed' => 1), + array('something_else_id' => 1, 'doomed' => '') + ) + ); + $Something->create(); + $result = $Something->save($data); + $this->assertFalse($result, 'Save occured even when with models failed. %s'); + + $joinRecords = $JoinThing->find('count', array( + 'conditions' => array('JoinThing.something_id' => $data['Something']['id']) + )); + $this->assertEqual($joinRecords, 0, 'Records were saved on the join table. %s'); + } + +/** + * test that saveAll and with models with validation interact well + * + * @return void + */ + function testValidatesWithModelsAndSaveAll() { + $data = array( + 'Something' => array( + 'id' => 5, + 'title' => 'Extra Fields', + 'body' => 'Extra Fields Body', + 'published' => '1' + ), + 'SomethingElse' => array( + array('something_else_id' => 1, 'doomed' => '') + ) + ); + $Something =& new Something(); + $JoinThing =& $Something->JoinThing; + + $JoinThing->validate = array('doomed' => array('rule' => 'notEmpty')); + $expectedError = array('doomed' => 'This field cannot be left blank'); + + $Something->create(); + $result = $Something->saveAll($data, array('validate' => 'only')); + $this->assertFalse($result); + $this->assertEqual($JoinThing->validationErrors, $expectedError); + + $Something->create(); + $result = $Something->saveAll($data, array('validate' => 'first')); + $this->assertFalse($result); + $this->assertEqual($JoinThing->validationErrors, $expectedError); + + $count = $Something->find('count', array('conditions' => array('Something.id' => $data['Something']['id']))); + $this->assertIdentical($count, 0); + + $joinRecords = $JoinThing->find('count', array( + 'conditions' => array('JoinThing.something_id' => $data['Something']['id']) + )); + $this->assertEqual($joinRecords, 0, 'Records were saved on the join table. %s'); + } + +/** + * Test that missing validation methods trigger errors in development mode. + * Helps to make developement easier. + * + * @return void + */ + function testMissingValidationErrorTriggering() { + $restore = Configure::read('debug'); + Configure::write('debug', 2); + + $TestModel =& new ValidationTest1(); + $TestModel->create(array('title' => 'foo')); + $TestModel->validate = array( + 'title' => array( + 'rule' => array('thisOneBringsThePain'), + 'required' => true + ) + ); + $this->expectError(new PatternExpectation('/thisOneBringsThePain for title/i')); + $TestModel->invalidFields(array('fieldList' => array('title'))); + + Configure::write('debug', 0); + $this->assertNoErrors(); + $TestModel->invalidFields(array('fieldList' => array('title'))); + Configure::write('debug', $restore); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_write.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_write.test.php new file mode 100644 index 000000000..d88bffeaa --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/model_write.test.php @@ -0,0 +1,4048 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +require_once dirname(__FILE__) . DS . 'model.test.php'; +/** + * ModelWriteTest + * + * @package cake + * @subpackage cake.tests.cases.libs.model.operations + */ +class ModelWriteTest extends BaseModelTest { + +/** + * testInsertAnotherHabtmRecordWithSameForeignKey method + * + * @access public + * @return void + */ + function testInsertAnotherHabtmRecordWithSameForeignKey() { + $this->loadFixtures('JoinA', 'JoinB', 'JoinAB'); + $TestModel = new JoinA(); + + $result = $TestModel->JoinAsJoinB->findById(1); + $expected = array( + 'JoinAsJoinB' => array( + 'id' => 1, + 'join_a_id' => 1, + 'join_b_id' => 2, + 'other' => 'Data for Join A 1 Join B 2', + 'created' => '2008-01-03 10:56:33', + 'updated' => '2008-01-03 10:56:33' + )); + $this->assertEqual($result, $expected); + + $TestModel->JoinAsJoinB->create(); + $result = $TestModel->JoinAsJoinB->save(array( + 'join_a_id' => 1, + 'join_b_id' => 1, + 'other' => 'Data for Join A 1 Join B 1', + 'created' => '2008-01-03 10:56:44', + 'updated' => '2008-01-03 10:56:44' + )); + $this->assertTrue($result); + $lastInsertId = $TestModel->JoinAsJoinB->getLastInsertID(); + $this->assertTrue($lastInsertId != null); + + $result = $TestModel->JoinAsJoinB->findById(1); + $expected = array( + 'JoinAsJoinB' => array( + 'id' => 1, + 'join_a_id' => 1, + 'join_b_id' => 2, + 'other' => 'Data for Join A 1 Join B 2', + 'created' => '2008-01-03 10:56:33', + 'updated' => '2008-01-03 10:56:33' + )); + $this->assertEqual($result, $expected); + + $updatedValue = 'UPDATED Data for Join A 1 Join B 2'; + $TestModel->JoinAsJoinB->id = 1; + $result = $TestModel->JoinAsJoinB->saveField('other', $updatedValue, false); + $this->assertTrue($result); + + $result = $TestModel->JoinAsJoinB->findById(1); + $this->assertEqual($result['JoinAsJoinB']['other'], $updatedValue); + } + +/** + * testSaveDateAsFirstEntry method + * + * @access public + * @return void + */ + function testSaveDateAsFirstEntry() { + $this->loadFixtures('Article'); + + $Article =& new Article(); + + $data = array( + 'Article' => array( + 'created' => array( + 'day' => '1', + 'month' => '1', + 'year' => '2008' + ), + 'title' => 'Test Title', + 'user_id' => 1 + )); + $Article->create(); + $this->assertTrue($Article->save($data)); + + $testResult = $Article->find(array('Article.title' => 'Test Title')); + + $this->assertEqual($testResult['Article']['title'], $data['Article']['title']); + $this->assertEqual($testResult['Article']['created'], '2008-01-01 00:00:00'); + + } + +/** + * testUnderscoreFieldSave method + * + * @access public + * @return void + */ + function testUnderscoreFieldSave() { + $this->loadFixtures('UnderscoreField'); + $UnderscoreField =& new UnderscoreField(); + + $currentCount = $UnderscoreField->find('count'); + $this->assertEqual($currentCount, 3); + $data = array('UnderscoreField' => array( + 'user_id' => '1', + 'my_model_has_a_field' => 'Content here', + 'body' => 'Body', + 'published' => 'Y', + 'another_field' => 4 + )); + $ret = $UnderscoreField->save($data); + $this->assertTrue($ret); + + $currentCount = $UnderscoreField->find('count'); + $this->assertEqual($currentCount, 4); + } + +/** + * testAutoSaveUuid method + * + * @access public + * @return void + */ + function testAutoSaveUuid() { + // SQLite does not support non-integer primary keys + $this->skipIf($this->db->config['driver'] == 'sqlite'); + + $this->loadFixtures('Uuid'); + $TestModel =& new Uuid(); + + $TestModel->save(array('title' => 'Test record')); + $result = $TestModel->findByTitle('Test record'); + $this->assertEqual( + array_keys($result['Uuid']), + array('id', 'title', 'count', 'created', 'updated') + ); + $this->assertEqual(strlen($result['Uuid']['id']), 36); + } + +/** + * Ensure that if the id key is null but present the save doesn't fail (with an + * x sql error: "Column id specified twice") + * + * @return void + * @access public + */ + function testSaveUuidNull() { + // SQLite does not support non-integer primary keys + $this->skipIf($this->db->config['driver'] == 'sqlite'); + + $this->loadFixtures('Uuid'); + $TestModel =& new Uuid(); + + $TestModel->save(array('title' => 'Test record', 'id' => null)); + $result = $TestModel->findByTitle('Test record'); + $this->assertEqual( + array_keys($result['Uuid']), + array('id', 'title', 'count', 'created', 'updated') + ); + $this->assertEqual(strlen($result['Uuid']['id']), 36); + } + +/** + * testZeroDefaultFieldValue method + * + * @access public + * @return void + */ + function testZeroDefaultFieldValue() { + $this->skipIf( + $this->db->config['driver'] == 'sqlite', + '%s SQLite uses loose typing, this operation is unsupported' + ); + $this->loadFixtures('DataTest'); + $TestModel =& new DataTest(); + + $TestModel->create(array()); + $TestModel->save(); + $result = $TestModel->findById($TestModel->id); + $this->assertIdentical($result['DataTest']['count'], '0'); + $this->assertIdentical($result['DataTest']['float'], '0'); + } + +/** + * testNonNumericHabtmJoinKey method + * + * @access public + * @return void + */ + function testNonNumericHabtmJoinKey() { + $this->loadFixtures('Post', 'Tag', 'PostsTag'); + $Post =& new Post(); + $Post->bindModel(array( + 'hasAndBelongsToMany' => array('Tag') + )); + $Post->Tag->primaryKey = 'tag'; + + $result = $Post->find('all'); + $expected = array( + array( + 'Post' => array( + 'id' => '1', + 'author_id' => '1', + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + ), + 'Author' => array( + 'id' => null, + 'user' => null, + 'password' => null, + 'created' => null, + 'updated' => null, + 'test' => 'working' + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ))), + array( + 'Post' => array( + 'id' => '2', + 'author_id' => '3', + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'Author' => array( + 'id' => null, + 'user' => null, + 'password' => null, + 'created' => null, + 'updated' => null, + 'test' => 'working' + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))), + array( + 'Post' => array( + 'id' => '3', + 'author_id' => '1', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ), + 'Author' => array( + 'id' => null, + 'user' => null, + 'password' => null, + 'created' => null, + 'updated' => null, + 'test' => 'working' + ), + 'Tag' => array() + )); + $this->assertEqual($result, $expected); + } + +/** + * Tests validation parameter order in custom validation methods + * + * @access public + * @return void + */ + function testAllowSimulatedFields() { + $TestModel =& new ValidationTest1(); + + $TestModel->create(array( + 'title' => 'foo', + 'bar' => 'baz' + )); + $expected = array( + 'ValidationTest1' => array( + 'title' => 'foo', + 'bar' => 'baz' + )); + $this->assertEqual($TestModel->data, $expected); + } + +/** + * test that Caches are getting cleared on save(). + * ensure that both inflections of controller names are getting cleared + * as url for controller could be either overallFavorites/index or overall_favorites/index + * + * @return void + */ + function testCacheClearOnSave() { + $_back = array( + 'check' => Configure::read('Cache.check'), + 'disable' => Configure::read('Cache.disable'), + ); + Configure::write('Cache.check', true); + Configure::write('Cache.disable', false); + + $this->loadFixtures('OverallFavorite'); + $OverallFavorite =& new OverallFavorite(); + + touch(CACHE . 'views' . DS . 'some_dir_overallfavorites_index.php'); + touch(CACHE . 'views' . DS . 'some_dir_overall_favorites_index.php'); + + $data = array( + 'OverallFavorite' => array( + 'id' => 22, + 'model_type' => '8-track', + 'model_id' => '3', + 'priority' => '1' + ) + ); + $OverallFavorite->create($data); + $OverallFavorite->save(); + + $this->assertFalse(file_exists(CACHE . 'views' . DS . 'some_dir_overallfavorites_index.php')); + $this->assertFalse(file_exists(CACHE . 'views' . DS . 'some_dir_overall_favorites_index.php')); + + Configure::write('Cache.check', $_back['check']); + Configure::write('Cache.disable', $_back['disable']); + } + +/** + * testSaveWithCounterCache method + * + * @access public + * @return void + */ + function testSaveWithCounterCache() { + $this->loadFixtures('Syfile', 'Item'); + $TestModel =& new Syfile(); + $TestModel2 =& new Item(); + + $result = $TestModel->findById(1); + $this->assertIdentical($result['Syfile']['item_count'], null); + + $TestModel2->save(array( + 'name' => 'Item 7', + 'syfile_id' => 1, + 'published' => false + )); + + $result = $TestModel->findById(1); + $this->assertIdentical($result['Syfile']['item_count'], '2'); + + $TestModel2->delete(1); + $result = $TestModel->findById(1); + $this->assertIdentical($result['Syfile']['item_count'], '1'); + + $TestModel2->id = 2; + $TestModel2->saveField('syfile_id', 1); + + $result = $TestModel->findById(1); + $this->assertIdentical($result['Syfile']['item_count'], '2'); + + $result = $TestModel->findById(2); + $this->assertIdentical($result['Syfile']['item_count'], '0'); + } + +/** + * Tests that counter caches are updated when records are added + * + * @access public + * @return void + */ + function testCounterCacheIncrease() { + $this->loadFixtures('CounterCacheUser', 'CounterCachePost'); + $User = new CounterCacheUser(); + $Post = new CounterCachePost(); + $data = array('Post' => array( + 'id' => 22, + 'title' => 'New Post', + 'user_id' => 66 + )); + + $Post->save($data); + $user = $User->find('first', array( + 'conditions' => array('id' => 66), + 'recursive' => -1 + )); + + $result = $user[$User->alias]['post_count']; + $expected = 3; + $this->assertEqual($result, $expected); + } + +/** + * Tests that counter caches are updated when records are deleted + * + * @access public + * @return void + */ + function testCounterCacheDecrease() { + $this->loadFixtures('CounterCacheUser', 'CounterCachePost'); + $User = new CounterCacheUser(); + $Post = new CounterCachePost(); + + $Post->delete(2); + $user = $User->find('first', array( + 'conditions' => array('id' => 66), + 'recursive' => -1 + )); + + $result = $user[$User->alias]['post_count']; + $expected = 1; + $this->assertEqual($result, $expected); + } + +/** + * Tests that counter caches are updated when foreign keys of counted records change + * + * @access public + * @return void + */ + function testCounterCacheUpdated() { + $this->loadFixtures('CounterCacheUser', 'CounterCachePost'); + $User = new CounterCacheUser(); + $Post = new CounterCachePost(); + + $data = $Post->find('first', array( + 'conditions' => array('id' => 1), + 'recursive' => -1 + )); + $data[$Post->alias]['user_id'] = 301; + $Post->save($data); + + $users = $User->find('all',array('order' => 'User.id')); + $this->assertEqual($users[0]['User']['post_count'], 1); + $this->assertEqual($users[1]['User']['post_count'], 2); + } + +/** + * Test counter cache with models that use a non-standard (i.e. not using 'id') + * as their primary key. + * + * @access public + * @return void + */ + function testCounterCacheWithNonstandardPrimaryKey() { + $this->loadFixtures( + 'CounterCacheUserNonstandardPrimaryKey', + 'CounterCachePostNonstandardPrimaryKey' + ); + + $User = new CounterCacheUserNonstandardPrimaryKey(); + $Post = new CounterCachePostNonstandardPrimaryKey(); + + $data = $Post->find('first', array( + 'conditions' => array('pid' => 1), + 'recursive' => -1 + )); + $data[$Post->alias]['uid'] = 301; + $Post->save($data); + + $users = $User->find('all',array('order' => 'User.uid')); + $this->assertEqual($users[0]['User']['post_count'], 1); + $this->assertEqual($users[1]['User']['post_count'], 2); + } + +/** + * test Counter Cache With Self Joining table + * + * @return void + * @access public + */ + function testCounterCacheWithSelfJoin() { + $skip = $this->skipIf( + ($this->db->config['driver'] == 'sqlite'), + 'SQLite 2.x does not support ALTER TABLE ADD COLUMN' + ); + if ($skip) { + return; + } + + $this->loadFixtures('CategoryThread'); + $this->db->query('ALTER TABLE '. $this->db->fullTableName('category_threads') . " ADD COLUMN child_count INTEGER"); + $Category =& new CategoryThread(); + $result = $Category->updateAll(array('CategoryThread.name' => "'updated'"), array('CategoryThread.parent_id' => 5)); + $this->assertTrue($result); + + $Category =& new CategoryThread(); + $Category->belongsTo['ParentCategory']['counterCache'] = 'child_count'; + $Category->updateCounterCache(array('parent_id' => 5)); + $result = Set::extract($Category->find('all', array('conditions' => array('CategoryThread.id' => 5))), '{n}.CategoryThread.child_count'); + $expected = array_fill(0, 1, 1); + $this->assertEqual($result, $expected); + } + +/** + * testSaveWithCounterCacheScope method + * + * @access public + * @return void + */ + function testSaveWithCounterCacheScope() { + $this->loadFixtures('Syfile', 'Item'); + $TestModel =& new Syfile(); + $TestModel2 =& new Item(); + $TestModel2->belongsTo['Syfile']['counterCache'] = true; + $TestModel2->belongsTo['Syfile']['counterScope'] = array('published' => true); + + $result = $TestModel->findById(1); + $this->assertIdentical($result['Syfile']['item_count'], null); + + $TestModel2->save(array( + 'name' => 'Item 7', + 'syfile_id' => 1, + 'published'=> true + )); + + $result = $TestModel->findById(1); + $this->assertIdentical($result['Syfile']['item_count'], '1'); + + $TestModel2->id = 1; + $TestModel2->saveField('published', true); + $result = $TestModel->findById(1); + $this->assertIdentical($result['Syfile']['item_count'], '2'); + + $TestModel2->save(array( + 'id' => 1, + 'syfile_id' => 1, + 'published'=> false + )); + + $result = $TestModel->findById(1); + $this->assertIdentical($result['Syfile']['item_count'], '1'); + } + +/** + * test that beforeValidate returning false can abort saves. + * + * @return void + */ + function testBeforeValidateSaveAbortion() { + $Model =& new CallbackPostTestModel(); + $Model->beforeValidateReturn = false; + + $data = array( + 'title' => 'new article', + 'body' => 'this is some text.' + ); + $Model->create(); + $result = $Model->save($data); + $this->assertFalse($result); + } +/** + * test that beforeSave returning false can abort saves. + * + * @return void + */ + function testBeforeSaveSaveAbortion() { + $Model =& new CallbackPostTestModel(); + $Model->beforeSaveReturn = false; + + $data = array( + 'title' => 'new article', + 'body' => 'this is some text.' + ); + $Model->create(); + $result = $Model->save($data); + $this->assertFalse($result); + } + +/** + * testSaveField method + * + * @access public + * @return void + */ + function testSaveField() { + $this->loadFixtures('Article'); + $TestModel =& new Article(); + + $TestModel->id = 1; + $result = $TestModel->saveField('title', 'New First Article'); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body'), 1); + $expected = array('Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'New First Article', + 'body' => 'First Article Body' + )); + $this->assertEqual($result, $expected); + + $TestModel->id = 1; + $result = $TestModel->saveField('title', ''); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body'), 1); + $expected = array('Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => '', + 'body' => 'First Article Body' + )); + $result['Article']['title'] = trim($result['Article']['title']); + $this->assertEqual($result, $expected); + + $TestModel->id = 1; + $TestModel->set('body', 'Messed up data'); + $this->assertTrue($TestModel->saveField('title', 'First Article')); + $result = $TestModel->read(array('id', 'user_id', 'title', 'body'), 1); + $expected = array('Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body' + )); + $this->assertEqual($result, $expected); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body'), 1); + + $TestModel->id = 1; + $result = $TestModel->saveField('title', '', true); + $this->assertFalse($result); + + $this->loadFixtures('Node', 'Dependency'); + $Node =& new Node(); + $Node->set('id', 1); + $result = $Node->read(); + $this->assertEqual(Set::extract('/ParentNode/name', $result), array('Second')); + + $Node->saveField('state', 10); + $result = $Node->read(); + $this->assertEqual(Set::extract('/ParentNode/name', $result), array('Second')); + } + +/** + * testSaveWithCreate method + * + * @access public + * @return void + */ + function testSaveWithCreate() { + $this->loadFixtures( + 'User', + 'Article', + 'User', + 'Comment', + 'Tag', + 'ArticlesTag', + 'Attachment' + ); + $TestModel =& new User(); + + $data = array('User' => array( + 'user' => 'user', + 'password' => '' + )); + $result = $TestModel->save($data); + $this->assertFalse($result); + $this->assertTrue(!empty($TestModel->validationErrors)); + + $TestModel =& new Article(); + + $data = array('Article' => array( + 'user_id' => '', + 'title' => '', + 'body' => '' + )); + $result = $TestModel->create($data) && $TestModel->save(); + $this->assertFalse($result); + $this->assertTrue(!empty($TestModel->validationErrors)); + + $data = array('Article' => array( + 'id' => 1, + 'user_id' => '1', + 'title' => 'New First Article', + 'body' => '' + )); + $result = $TestModel->create($data) && $TestModel->save(); + $this->assertFalse($result); + + $data = array('Article' => array( + 'id' => 1, + 'title' => 'New First Article' + )); + $result = $TestModel->create() && $TestModel->save($data, false); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 1); + $expected = array('Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'New First Article', + 'body' => 'First Article Body', + 'published' => 'N' + )); + $this->assertEqual($result, $expected); + + $data = array('Article' => array( + 'id' => 1, + 'user_id' => '2', + 'title' => 'First Article', + 'body' => 'New First Article Body', + 'published' => 'Y' + )); + $result = $TestModel->create() && $TestModel->save($data, true, array('id', 'title', 'published')); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 1); + $expected = array('Article' => array( + 'id' => '1', + 'user_id' => '1', + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y' + )); + $this->assertEqual($result, $expected); + + $data = array( + 'Article' => array( + 'user_id' => '2', + 'title' => 'New Article', + 'body' => 'New Article Body', + 'created' => '2007-03-18 14:55:23', + 'updated' => '2007-03-18 14:57:31' + ), + 'Tag' => array('Tag' => array(1, 3)) + ); + $TestModel->create(); + $result = $TestModel->create() && $TestModel->save($data); + $this->assertTrue($result); + + $TestModel->recursive = 2; + $result = $TestModel->read(null, 4); + $expected = array( + 'Article' => array( + 'id' => '4', + 'user_id' => '2', + 'title' => 'New Article', + 'body' => 'New Article Body', + 'published' => 'N', + 'created' => '2007-03-18 14:55:23', + 'updated' => '2007-03-18 14:57:31' + ), + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + ), + 'Comment' => array(), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))); + $this->assertEqual($result, $expected); + + $data = array('Comment' => array( + 'article_id' => '4', + 'user_id' => '1', + 'comment' => 'Comment New Article', + 'published' => 'Y', + 'created' => '2007-03-18 14:57:23', + 'updated' => '2007-03-18 14:59:31' + )); + $result = $TestModel->Comment->create() && $TestModel->Comment->save($data); + $this->assertTrue($result); + + $data = array('Attachment' => array( + 'comment_id' => '7', + 'attachment' => 'newattachment.zip', + 'created' => '2007-03-18 15:02:23', + 'updated' => '2007-03-18 15:04:31' + )); + $result = $TestModel->Comment->Attachment->save($data); + $this->assertTrue($result); + + $TestModel->recursive = 2; + $result = $TestModel->read(null, 4); + $expected = array( + 'Article' => array( + 'id' => '4', + 'user_id' => '2', + 'title' => 'New Article', + 'body' => 'New Article Body', + 'published' => 'N', + 'created' => '2007-03-18 14:55:23', + 'updated' => '2007-03-18 14:57:31' + ), + 'User' => array( + 'id' => '2', + 'user' => 'nate', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:18:23', + 'updated' => '2007-03-17 01:20:31' + ), + 'Comment' => array( + array( + 'id' => '7', + 'article_id' => '4', + 'user_id' => '1', + 'comment' => 'Comment New Article', + 'published' => 'Y', + 'created' => '2007-03-18 14:57:23', + 'updated' => '2007-03-18 14:59:31', + 'Article' => array( + 'id' => '4', + 'user_id' => '2', + 'title' => 'New Article', + 'body' => 'New Article Body', + 'published' => 'N', + 'created' => '2007-03-18 14:55:23', + 'updated' => '2007-03-18 14:57:31' + ), + 'User' => array( + 'id' => '1', + 'user' => 'mariano', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ), + 'Attachment' => array( + 'id' => '2', + 'comment_id' => '7', + 'attachment' => 'newattachment.zip', + 'created' => '2007-03-18 15:02:23', + 'updated' => '2007-03-18 15:04:31' + ))), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))); + + $this->assertEqual($result, $expected); + } + +/** + * test that a null Id doesn't cause errors + * + * @return void + */ + function testSaveWithNullId() { + $this->loadFixtures('User'); + $User =& new User(); + $User->read(null, 1); + $User->data['User']['id'] = null; + $this->assertTrue($User->save(array('password' => 'test'))); + $this->assertTrue($User->id > 0); + + $result = $User->read(null, 2); + $User->data['User']['id'] = null; + $this->assertTrue($User->save(array('password' => 'test'))); + $this->assertTrue($User->id > 0); + + $User->data['User'] = array('password' => 'something'); + $this->assertTrue($User->save()); + $result = $User->read(); + $this->assertEqual($User->data['User']['password'], 'something'); + } + +/** + * testSaveWithSet method + * + * @access public + * @return void + */ + function testSaveWithSet() { + $this->loadFixtures('Article'); + $TestModel =& new Article(); + + // Create record we will be updating later + + $data = array('Article' => array( + 'user_id' => '1', + 'title' => 'Fourth Article', + 'body' => 'Fourth Article Body', + 'published' => 'Y' + )); + $result = $TestModel->create() && $TestModel->save($data); + $this->assertTrue($result); + + // Check record we created + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 4); + $expected = array('Article' => array( + 'id' => '4', + 'user_id' => '1', + 'title' => 'Fourth Article', + 'body' => 'Fourth Article Body', + 'published' => 'Y' + )); + $this->assertEqual($result, $expected); + + // Create new record just to overlap Model->id on previously created record + + $data = array('Article' => array( + 'user_id' => '4', + 'title' => 'Fifth Article', + 'body' => 'Fifth Article Body', + 'published' => 'Y' + )); + $result = $TestModel->create() && $TestModel->save($data); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 5); + $expected = array('Article' => array( + 'id' => '5', + 'user_id' => '4', + 'title' => 'Fifth Article', + 'body' => 'Fifth Article Body', + 'published' => 'Y' + )); + $this->assertEqual($result, $expected); + + // Go back and edit the first article we created, starting by checking it's still there + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 4); + $expected = array('Article' => array( + 'id' => '4', + 'user_id' => '1', + 'title' => 'Fourth Article', + 'body' => 'Fourth Article Body', + 'published' => 'Y' + )); + $this->assertEqual($result, $expected); + + // And now do the update with set() + + $data = array('Article' => array( + 'id' => '4', + 'title' => 'Fourth Article - New Title', + 'published' => 'N' + )); + $result = $TestModel->set($data) && $TestModel->save(); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 4); + $expected = array('Article' => array( + 'id' => '4', + 'user_id' => '1', + 'title' => 'Fourth Article - New Title', + 'body' => 'Fourth Article Body', + 'published' => 'N' + )); + $this->assertEqual($result, $expected); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 5); + $expected = array('Article' => array( + 'id' => '5', + 'user_id' => '4', + 'title' => 'Fifth Article', + 'body' => 'Fifth Article Body', + 'published' => 'Y' + )); + $this->assertEqual($result, $expected); + + $data = array('Article' => array('id' => '5', 'title' => 'Fifth Article - New Title 5')); + $result = ($TestModel->set($data) && $TestModel->save()); + $this->assertTrue($result); + + $TestModel->recursive = -1; + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 5); + $expected = array('Article' => array( + 'id' => '5', + 'user_id' => '4', + 'title' => 'Fifth Article - New Title 5', + 'body' => 'Fifth Article Body', + 'published' => 'Y' + )); + $this->assertEqual($result, $expected); + + $TestModel->recursive = -1; + $result = $TestModel->find('all', array('fields' => array('id', 'title'))); + $expected = array( + array('Article' => array('id' => 1, 'title' => 'First Article' )), + array('Article' => array('id' => 2, 'title' => 'Second Article' )), + array('Article' => array('id' => 3, 'title' => 'Third Article' )), + array('Article' => array('id' => 4, 'title' => 'Fourth Article - New Title' )), + array('Article' => array('id' => 5, 'title' => 'Fifth Article - New Title 5' )) + ); + $this->assertEqual($result, $expected); + } + +/** + * testSaveWithNonExistentFields method + * + * @access public + * @return void + */ + function testSaveWithNonExistentFields() { + $this->loadFixtures('Article'); + $TestModel =& new Article(); + $TestModel->recursive = -1; + + $data = array( + 'non_existent' => 'This field does not exist', + 'user_id' => '1', + 'title' => 'Fourth Article - New Title', + 'body' => 'Fourth Article Body', + 'published' => 'N' + ); + $result = $TestModel->create() && $TestModel->save($data); + $this->assertTrue($result); + + $expected = array('Article' => array( + 'id' => '4', + 'user_id' => '1', + 'title' => 'Fourth Article - New Title', + 'body' => 'Fourth Article Body', + 'published' => 'N' + )); + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 4); + $this->assertEqual($result, $expected); + + $data = array( + 'user_id' => '1', + 'non_existent' => 'This field does not exist', + 'title' => 'Fiveth Article - New Title', + 'body' => 'Fiveth Article Body', + 'published' => 'N' + ); + $result = $TestModel->create() && $TestModel->save($data); + $this->assertTrue($result); + + $expected = array('Article' => array( + 'id' => '5', + 'user_id' => '1', + 'title' => 'Fiveth Article - New Title', + 'body' => 'Fiveth Article Body', + 'published' => 'N' + )); + $result = $TestModel->read(array('id', 'user_id', 'title', 'body', 'published'), 5); + $this->assertEqual($result, $expected); + } + +/** + * testSaveFromXml method + * + * @access public + * @return void + */ + function testSaveFromXml() { + $this->loadFixtures('Article'); + App::import('Core', 'Xml'); + + $Article = new Article(); + $Article->save(new Xml('
    ')); + $this->assertTrue($Article->save(new Xml('
    '))); + + $results = $Article->find(array('Article.title' => 'test xml')); + $this->assertTrue($results); + } + +/** + * testSaveHabtm method + * + * @access public + * @return void + */ + function testSaveHabtm() { + $this->loadFixtures('Article', 'User', 'Comment', 'Tag', 'ArticlesTag'); + $TestModel =& new Article(); + + $result = $TestModel->findById(2); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + ), + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + ), + 'Comment' => array( + array( + 'id' => '5', + 'article_id' => '2', + 'user_id' => '1', + 'comment' => 'First Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:53:23', + 'updated' => '2007-03-18 10:55:31' + ), + array( + 'id' => '6', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y', + 'created' => '2007-03-18 10:55:23', + 'updated' => '2007-03-18 10:57:31' + )), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ) + ) + ); + $this->assertEqual($result, $expected); + + $data = array( + 'Article' => array( + 'id' => '2', + 'title' => 'New Second Article' + ), + 'Tag' => array('Tag' => array(1, 2)) + ); + + $this->assertTrue($TestModel->set($data)); + $this->assertTrue($TestModel->save()); + + $TestModel->unbindModel(array('belongsTo' => array('User'), 'hasMany' => array('Comment'))); + $result = $TestModel->find(array('Article.id' => 2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'New Second Article', + 'body' => 'Second Article Body' + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ))); + $this->assertEqual($result, $expected); + + $data = array('Article' => array('id' => '2'), 'Tag' => array('Tag' => array(2, 3))); + $result = $TestModel->set($data); + $this->assertTrue($result); + + $result = $TestModel->save(); + $this->assertTrue($result); + + $TestModel->unbindModel(array( + 'belongsTo' => array('User'), + 'hasMany' => array('Comment') + )); + $result = $TestModel->find(array('Article.id'=>2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'New Second Article', + 'body' => 'Second Article Body' + ), + 'Tag' => array( + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))); + $this->assertEqual($result, $expected); + + $data = array('Tag' => array('Tag' => array(1, 2, 3))); + + $result = $TestModel->set($data); + $this->assertTrue($result); + + $result = $TestModel->save(); + $this->assertTrue($result); + + $TestModel->unbindModel(array( + 'belongsTo' => array('User'), + 'hasMany' => array('Comment') + )); + $result = $TestModel->find(array('Article.id' => 2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'New Second Article', + 'body' => 'Second Article Body' + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))); + $this->assertEqual($result, $expected); + + $data = array('Tag' => array('Tag' => array())); + $result = $TestModel->set($data); + $this->assertTrue($result); + + $result = $TestModel->save(); + $this->assertTrue($result); + + $data = array('Tag' => array('Tag' => '')); + $result = $TestModel->set($data); + $this->assertTrue($result); + + $result = $TestModel->save(); + $this->assertTrue($result); + + $TestModel->unbindModel(array( + 'belongsTo' => array('User'), + 'hasMany' => array('Comment') + )); + $result = $TestModel->find(array('Article.id'=>2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'New Second Article', + 'body' => 'Second Article Body' + ), + 'Tag' => array() + ); + $this->assertEqual($result, $expected); + + $data = array('Tag' => array('Tag' => array(2, 3))); + $result = $TestModel->set($data); + $this->assertTrue($result); + + $result = $TestModel->save(); + $this->assertTrue($result); + + $TestModel->unbindModel(array( + 'belongsTo' => array('User'), + 'hasMany' => array('Comment') + )); + $result = $TestModel->find(array('Article.id'=>2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'New Second Article', + 'body' => 'Second Article Body' + ), + 'Tag' => array( + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))); + $this->assertEqual($result, $expected); + + $data = array( + 'Tag' => array( + 'Tag' => array(1, 2) + ), + 'Article' => array( + 'id' => '2', + 'title' => 'New Second Article' + )); + $this->assertTrue($TestModel->set($data)); + $this->assertTrue($TestModel->save()); + + $TestModel->unbindModel(array( + 'belongsTo' => array('User'), + 'hasMany' => array('Comment') + )); + $result = $TestModel->find(array('Article.id'=>2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'New Second Article', + 'body' => 'Second Article Body' + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ))); + $this->assertEqual($result, $expected); + + $data = array( + 'Tag' => array( + 'Tag' => array(1, 2) + ), + 'Article' => array( + 'id' => '2', + 'title' => 'New Second Article Title' + )); + $result = $TestModel->set($data); + $this->assertTrue($result); + $this->assertTrue($TestModel->save()); + + $TestModel->unbindModel(array( + 'belongsTo' => array('User'), + 'hasMany' => array('Comment') + )); + $result = $TestModel->find(array('Article.id'=>2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'New Second Article Title', + 'body' => 'Second Article Body' + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ) + ) + ); + $this->assertEqual($result, $expected); + + $data = array( + 'Tag' => array( + 'Tag' => array(2, 3) + ), + 'Article' => array( + 'id' => '2', + 'title' => 'Changed Second Article' + )); + $this->assertTrue($TestModel->set($data)); + $this->assertTrue($TestModel->save()); + + $TestModel->unbindModel(array( + 'belongsTo' => array('User'), + 'hasMany' => array('Comment') + )); + $result = $TestModel->find(array('Article.id'=>2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Changed Second Article', + 'body' => 'Second Article Body' + ), + 'Tag' => array( + array( + 'id' => '2', + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ) + ) + ); + $this->assertEqual($result, $expected); + + $data = array( + 'Tag' => array( + 'Tag' => array(1, 3) + ), + 'Article' => array('id' => '2'), + ); + + $result = $TestModel->set($data); + $this->assertTrue($result); + + $result = $TestModel->save(); + $this->assertTrue($result); + + $TestModel->unbindModel(array( + 'belongsTo' => array('User'), + 'hasMany' => array('Comment') + )); + $result = $TestModel->find(array('Article.id'=>2), array('id', 'user_id', 'title', 'body')); + $expected = array( + 'Article' => array( + 'id' => '2', + 'user_id' => '3', + 'title' => 'Changed Second Article', + 'body' => 'Second Article Body' + ), + 'Tag' => array( + array( + 'id' => '1', + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + array( + 'id' => '3', + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))); + $this->assertEqual($result, $expected); + + $data = array( + 'Article' => array( + 'id' => 10, + 'user_id' => '2', + 'title' => 'New Article With Tags and fieldList', + 'body' => 'New Article Body with Tags and fieldList', + 'created' => '2007-03-18 14:55:23', + 'updated' => '2007-03-18 14:57:31' + ), + 'Tag' => array( + 'Tag' => array(1, 2, 3) + )); + $result = $TestModel->create() + && $TestModel->save($data, true, array('user_id', 'title', 'published')); + $this->assertTrue($result); + + $TestModel->unbindModel(array('belongsTo' => array('User'), 'hasMany' => array('Comment'))); + $result = $TestModel->read(); + $expected = array( + 'Article' => array( + 'id' => 4, + 'user_id' => 2, + 'title' => 'New Article With Tags and fieldList', + 'body' => '', + 'published' => 'N', + 'created' => '', + 'updated' => '' + ), + 'Tag' => array( + 0 => array( + 'id' => 1, + 'tag' => 'tag1', + 'created' => '2007-03-18 12:22:23', + 'updated' => '2007-03-18 12:24:31' + ), + 1 => array( + 'id' => 2, + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ), + 2 => array( + 'id' => 3, + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))); + $this->assertEqual($result, $expected); + + + $this->loadFixtures('JoinA', 'JoinC', 'JoinAC', 'JoinB', 'JoinAB'); + $TestModel = new JoinA(); + $TestModel->hasBelongsToMany['JoinC']['unique'] = true; + $data = array( + 'JoinA' => array( + 'id' => 1, + 'name' => 'Join A 1', + 'body' => 'Join A 1 Body', + ), + 'JoinC' => array( + 'JoinC' => array( + array('join_c_id' => 2, 'other' => 'new record'), + array('join_c_id' => 3, 'other' => 'new record') + ) + ) + ); + $TestModel->save($data); + $result = $TestModel->read(null, 1); + $expected = array(4, 5); + $this->assertEqual(Set::extract('/JoinC/JoinAsJoinC/id', $result), $expected); + $expected = array('new record', 'new record'); + $this->assertEqual(Set::extract('/JoinC/JoinAsJoinC/other', $result), $expected); + } + +/** + * testSaveHabtmCustomKeys method + * + * @access public + * @return void + */ + function testSaveHabtmCustomKeys() { + $this->loadFixtures('Story', 'StoriesTag', 'Tag'); + $Story =& new Story(); + + $data = array( + 'Story' => array('story' => '1'), + 'Tag' => array( + 'Tag' => array(2, 3) + )); + $result = $Story->set($data); + $this->assertTrue($result); + + $result = $Story->save(); + $this->assertTrue($result); + + $result = $Story->find('all', array('order' => array('Story.story'))); + $expected = array( + array( + 'Story' => array( + 'story' => 1, + 'title' => 'First Story' + ), + 'Tag' => array( + array( + 'id' => 2, + 'tag' => 'tag2', + 'created' => '2007-03-18 12:24:23', + 'updated' => '2007-03-18 12:26:31' + ), + array( + 'id' => 3, + 'tag' => 'tag3', + 'created' => '2007-03-18 12:26:23', + 'updated' => '2007-03-18 12:28:31' + ))), + array( + 'Story' => array( + 'story' => 2, + 'title' => 'Second Story' + ), + 'Tag' => array() + )); + $this->assertEqual($result, $expected); + } + +/** + * test that saving habtm records respects conditions set in the 'conditions' key + * for the association. + * + * @return void + */ + function testHabtmSaveWithConditionsInAssociation() { + $this->loadFixtures('JoinThing', 'Something', 'SomethingElse'); + $Something =& new Something(); + $Something->unbindModel(array('hasAndBelongsToMany' => array('SomethingElse')), false); + + $Something->bindModel(array( + 'hasAndBelongsToMany' => array( + 'DoomedSomethingElse' => array( + 'className' => 'SomethingElse', + 'joinTable' => 'join_things', + 'conditions' => 'JoinThing.doomed = true', + 'unique' => true + ), + 'NotDoomedSomethingElse' => array( + 'className' => 'SomethingElse', + 'joinTable' => 'join_things', + 'conditions' => array('JoinThing.doomed' => 0), + 'unique' => true + ) + ) + ), false); + $result = $Something->read(null, 1); + $this->assertTrue(empty($result['NotDoomedSomethingElse'])); + $this->assertEqual(count($result['DoomedSomethingElse']), 1); + + $data = array( + 'Something' => array('id' => 1), + 'NotDoomedSomethingElse' => array( + 'NotDoomedSomethingElse' => array( + array('something_else_id' => 2, 'doomed' => 0), + array('something_else_id' => 3, 'doomed' => 0) + ) + ) + ); + $Something->create($data); + $result = $Something->save(); + $this->assertTrue($result); + + $result = $Something->read(null, 1); + $this->assertEqual(count($result['NotDoomedSomethingElse']), 2); + $this->assertEqual(count($result['DoomedSomethingElse']), 1); + } +/** + * testHabtmSaveKeyResolution method + * + * @access public + * @return void + */ + function testHabtmSaveKeyResolution() { + $this->loadFixtures('Apple', 'Device', 'ThePaperMonkies'); + $ThePaper =& new ThePaper(); + + $ThePaper->id = 1; + $ThePaper->save(array('Monkey' => array(2, 3))); + + $result = $ThePaper->findById(1); + $expected = array( + array( + 'id' => '2', + 'device_type_id' => '1', + 'name' => 'Device 2', + 'typ' => '1' + ), + array( + 'id' => '3', + 'device_type_id' => '1', + 'name' => 'Device 3', + 'typ' => '2' + )); + $this->assertEqual($result['Monkey'], $expected); + + $ThePaper->id = 2; + $ThePaper->save(array('Monkey' => array(1, 2, 3))); + + $result = $ThePaper->findById(2); + $expected = array( + array( + 'id' => '1', + 'device_type_id' => '1', + 'name' => 'Device 1', + 'typ' => '1' + ), + array( + 'id' => '2', + 'device_type_id' => '1', + 'name' => 'Device 2', + 'typ' => '1' + ), + array( + 'id' => '3', + 'device_type_id' => '1', + 'name' => 'Device 3', + 'typ' => '2' + )); + $this->assertEqual($result['Monkey'], $expected); + + $ThePaper->id = 2; + $ThePaper->save(array('Monkey' => array(1, 3))); + + $result = $ThePaper->findById(2); + $expected = array( + array( + 'id' => '1', + 'device_type_id' => '1', + 'name' => 'Device 1', + 'typ' => '1' + ), + array( + 'id' => '3', + 'device_type_id' => '1', + 'name' => 'Device 3', + 'typ' => '2' + )); + $this->assertEqual($result['Monkey'], $expected); + + $result = $ThePaper->findById(1); + $expected = array( + array( + 'id' => '2', + 'device_type_id' => '1', + 'name' => 'Device 2', + 'typ' => '1' + ), + array( + 'id' => '3', + 'device_type_id' => '1', + 'name' => 'Device 3', + 'typ' => '2' + )); + $this->assertEqual($result['Monkey'], $expected); + } + +/** + * testCreationOfEmptyRecord method + * + * @access public + * @return void + */ + function testCreationOfEmptyRecord() { + $this->loadFixtures('Author'); + $TestModel =& new Author(); + $this->assertEqual($TestModel->find('count'), 4); + + $TestModel->deleteAll(true, false, false); + $this->assertEqual($TestModel->find('count'), 0); + + $result = $TestModel->save(); + $this->assertTrue(isset($result['Author']['created'])); + $this->assertTrue(isset($result['Author']['updated'])); + $this->assertEqual($TestModel->find('count'), 1); + } + +/** + * testCreateWithPKFiltering method + * + * @access public + * @return void + */ + function testCreateWithPKFiltering() { + $TestModel =& new Article(); + $data = array( + 'id' => 5, + 'user_id' => 2, + 'title' => 'My article', + 'body' => 'Some text' + ); + + $result = $TestModel->create($data); + $expected = array( + 'Article' => array( + 'published' => 'N', + 'id' => 5, + 'user_id' => 2, + 'title' => 'My article', + 'body' => 'Some text' + )); + + $this->assertEqual($result, $expected); + $this->assertEqual($TestModel->id, 5); + + $result = $TestModel->create($data, true); + $expected = array( + 'Article' => array( + 'published' => 'N', + 'id' => false, + 'user_id' => 2, + 'title' => 'My article', + 'body' => 'Some text' + )); + + $this->assertEqual($result, $expected); + $this->assertFalse($TestModel->id); + + $result = $TestModel->create(array('Article' => $data), true); + $expected = array( + 'Article' => array( + 'published' => 'N', + 'id' => false, + 'user_id' => 2, + 'title' => 'My article', + 'body' => 'Some text' + )); + + $this->assertEqual($result, $expected); + $this->assertFalse($TestModel->id); + + $data = array( + 'id' => 6, + 'user_id' => 2, + 'title' => 'My article', + 'body' => 'Some text', + 'created' => '1970-01-01 00:00:00', + 'updated' => '1970-01-01 12:00:00', + 'modified' => '1970-01-01 12:00:00' + ); + + $result = $TestModel->create($data); + $expected = array( + 'Article' => array( + 'published' => 'N', + 'id' => 6, + 'user_id' => 2, + 'title' => 'My article', + 'body' => 'Some text', + 'created' => '1970-01-01 00:00:00', + 'updated' => '1970-01-01 12:00:00', + 'modified' => '1970-01-01 12:00:00' + )); + $this->assertEqual($result, $expected); + $this->assertEqual($TestModel->id, 6); + + $result = $TestModel->create(array( + 'Article' => array_diff_key($data, array( + 'created' => true, + 'updated' => true, + 'modified' => true + ))), true); + $expected = array( + 'Article' => array( + 'published' => 'N', + 'id' => false, + 'user_id' => 2, + 'title' => 'My article', + 'body' => 'Some text' + )); + $this->assertEqual($result, $expected); + $this->assertFalse($TestModel->id); + } + +/** + * testCreationWithMultipleData method + * + * @access public + * @return void + */ + function testCreationWithMultipleData() { + $this->loadFixtures('Article', 'Comment'); + $Article =& new Article(); + $Comment =& new Comment(); + + $articles = $Article->find('all', array( + 'fields' => array('id','title'), + 'recursive' => -1 + )); + + $comments = $Comment->find('all', array( + 'fields' => array('id','article_id','user_id','comment','published'), 'recursive' => -1)); + + $this->assertEqual($articles, array( + array('Article' => array( + 'id' => 1, + 'title' => 'First Article' + )), + array('Article' => array( + 'id' => 2, + 'title' => 'Second Article' + )), + array('Article' => array( + 'id' => 3, + 'title' => 'Third Article' + )))); + + $this->assertEqual($comments, array( + array('Comment' => array( + 'id' => 1, + 'article_id' => 1, + 'user_id' => 2, + 'comment' => 'First Comment for First Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 2, + 'article_id' => 1, + 'user_id' => 4, + 'comment' => 'Second Comment for First Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 3, + 'article_id' => 1, + 'user_id' => 1, + 'comment' => 'Third Comment for First Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 4, + 'article_id' => 1, + 'user_id' => 1, + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N' + )), + array('Comment' => array( + 'id' => 5, + 'article_id' => 2, + 'user_id' => 1, + 'comment' => 'First Comment for Second Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 6, + 'article_id' => 2, + 'user_id' => 2, + 'comment' => 'Second Comment for Second Article', + 'published' => 'Y' + )))); + + $data = array( + 'Comment' => array( + 'article_id' => 2, + 'user_id' => 4, + 'comment' => 'Brand New Comment', + 'published' => 'N' + ), + 'Article' => array( + 'id' => 2, + 'title' => 'Second Article Modified' + )); + + $result = $Comment->create($data); + + $this->assertTrue($result); + $result = $Comment->save(); + $this->assertTrue($result); + + $articles = $Article->find('all', array( + 'fields' => array('id','title'), + 'recursive' => -1 + )); + + $comments = $Comment->find('all', array( + 'fields' => array('id','article_id','user_id','comment','published'), + 'recursive' => -1 + )); + + $this->assertEqual($articles, array( + array('Article' => array( + 'id' => 1, + 'title' => 'First Article' + )), + array('Article' => array( + 'id' => 2, + 'title' => 'Second Article' + )), + array('Article' => array( + 'id' => 3, + 'title' => 'Third Article' + )))); + + $this->assertEqual($comments, array( + array('Comment' => array( + 'id' => 1, + 'article_id' => 1, + 'user_id' => 2, + 'comment' => 'First Comment for First Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 2, + 'article_id' => 1, + 'user_id' => 4, + 'comment' => 'Second Comment for First Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 3, + 'article_id' => 1, + 'user_id' => 1, + 'comment' => 'Third Comment for First Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 4, + 'article_id' => 1, + 'user_id' => 1, + 'comment' => 'Fourth Comment for First Article', + 'published' => 'N' + )), + array('Comment' => array( + 'id' => 5, + 'article_id' => 2, + 'user_id' => 1, + 'comment' => 'First Comment for Second Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 6, + 'article_id' => 2, + 'user_id' => 2, 'comment' => + 'Second Comment for Second Article', + 'published' => 'Y' + )), + array('Comment' => array( + 'id' => 7, + 'article_id' => 2, + 'user_id' => 4, + 'comment' => 'Brand New Comment', + 'published' => 'N' + )))); + + } + +/** + * testCreationWithMultipleDataSameModel method + * + * @access public + * @return void + */ + function testCreationWithMultipleDataSameModel() { + $this->loadFixtures('Article'); + $Article =& new Article(); + $SecondaryArticle =& new Article(); + + $result = $Article->field('title', array('id' => 1)); + $this->assertEqual($result, 'First Article'); + + $data = array( + 'Article' => array( + 'user_id' => 2, + 'title' => 'Brand New Article', + 'body' => 'Brand New Article Body', + 'published' => 'Y' + ), + 'SecondaryArticle' => array( + 'id' => 1 + )); + + $Article->create(); + $result = $Article->save($data); + $this->assertTrue($result); + + $result = $Article->getInsertID(); + $this->assertTrue(!empty($result)); + + $result = $Article->field('title', array('id' => 1)); + $this->assertEqual($result, 'First Article'); + + $articles = $Article->find('all', array( + 'fields' => array('id','title'), + 'recursive' => -1 + )); + + $this->assertEqual($articles, array( + array('Article' => array( + 'id' => 1, + 'title' => 'First Article' + )), + array('Article' => array( + 'id' => 2, + 'title' => 'Second Article' + )), + array('Article' => array( + 'id' => 3, + 'title' => 'Third Article' + )), + array('Article' => array( + 'id' => 4, + 'title' => 'Brand New Article' + )))); + } + +/** + * testCreationWithMultipleDataSameModelManualInstances method + * + * @access public + * @return void + */ + function testCreationWithMultipleDataSameModelManualInstances() { + $this->loadFixtures('PrimaryModel'); + $Primary =& new PrimaryModel(); + $Secondary =& new PrimaryModel(); + + $result = $Primary->field('primary_name', array('id' => 1)); + $this->assertEqual($result, 'Primary Name Existing'); + + $data = array( + 'PrimaryModel' => array( + 'primary_name' => 'Primary Name New' + ), + 'SecondaryModel' => array( + 'id' => array(1) + )); + + $Primary->create(); + $result = $Primary->save($data); + $this->assertTrue($result); + + $result = $Primary->field('primary_name', array('id' => 1)); + $this->assertEqual($result, 'Primary Name Existing'); + + $result = $Primary->getInsertID(); + $this->assertTrue(!empty($result)); + + $result = $Primary->field('primary_name', array('id' => $result)); + $this->assertEqual($result, 'Primary Name New'); + + $result = $Primary->find('count'); + $this->assertEqual($result, 2); + } + +/** + * testRecordExists method + * + * @access public + * @return void + */ + function testRecordExists() { + $this->loadFixtures('User'); + $TestModel =& new User(); + + $this->assertFalse($TestModel->exists()); + $TestModel->read(null, 1); + $this->assertTrue($TestModel->exists()); + $TestModel->create(); + $this->assertFalse($TestModel->exists()); + $TestModel->id = 4; + $this->assertTrue($TestModel->exists()); + + $TestModel =& new TheVoid(); + $this->assertFalse($TestModel->exists()); + + $TestModel->id = 5; + $this->expectError(); + ob_start(); + $this->assertFalse($TestModel->exists()); + $output = ob_get_clean(); + } + +/** + * testUpdateExisting method + * + * @access public + * @return void + */ + function testUpdateExisting() { + $this->loadFixtures('User', 'Article', 'Comment'); + $TestModel =& new User(); + $TestModel->create(); + + $TestModel->save(array( + 'User' => array( + 'user' => 'some user', + 'password' => 'some password' + ))); + $this->assertTrue(is_int($TestModel->id) || (intval($TestModel->id) === 5)); + $id = $TestModel->id; + + $TestModel->save(array( + 'User' => array( + 'user' => 'updated user' + ))); + $this->assertEqual($TestModel->id, $id); + + $result = $TestModel->findById($id); + $this->assertEqual($result['User']['user'], 'updated user'); + $this->assertEqual($result['User']['password'], 'some password'); + + $Article =& new Article(); + $Comment =& new Comment(); + $data = array( + 'Comment' => array( + 'id' => 1, + 'comment' => 'First Comment for First Article' + ), + 'Article' => array( + 'id' => 2, + 'title' => 'Second Article' + )); + + $result = $Article->save($data); + $this->assertTrue($result); + + $result = $Comment->save($data); + $this->assertTrue($result); + } + +/** + * test updating records and saving blank values. + * + * @return void + */ + function testUpdateSavingBlankValues() { + $this->loadFixtures('Article'); + $Article =& new Article(); + $Article->validate = array(); + $Article->create(); + $result = $Article->save(array( + 'id' => 1, + 'title' => '', + 'body' => '' + )); + $this->assertTrue($result); + $result = $Article->find('first', array('conditions' => array('Article.id' => 1))); + $this->assertEqual('', $result['Article']['title'], 'Title is not blank'); + $this->assertEqual('', $result['Article']['body'], 'Body is not blank'); + } + +/** + * testUpdateMultiple method + * + * @access public + * @return void + */ + function testUpdateMultiple() { + $this->loadFixtures('Comment', 'Article', 'User', 'CategoryThread'); + $TestModel =& new Comment(); + $result = Set::extract($TestModel->find('all'), '{n}.Comment.user_id'); + $expected = array('2', '4', '1', '1', '1', '2'); + $this->assertEqual($result, $expected); + + $TestModel->updateAll(array('Comment.user_id' => 5), array('Comment.user_id' => 2)); + $result = Set::combine($TestModel->find('all'), '{n}.Comment.id', '{n}.Comment.user_id'); + $expected = array(1 => 5, 2 => 4, 3 => 1, 4 => 1, 5 => 1, 6 => 5); + $this->assertEqual($result, $expected); + + $result = $TestModel->updateAll( + array('Comment.comment' => "'Updated today'"), + array('Comment.user_id' => 5) + ); + $this->assertTrue($result); + $result = Set::extract( + $TestModel->find('all', array( + 'conditions' => array( + 'Comment.user_id' => 5 + ))), + '{n}.Comment.comment' + ); + $expected = array_fill(0, 2, 'Updated today'); + $this->assertEqual($result, $expected); + } + +/** + * testHabtmUuidWithUuidId method + * + * @access public + * @return void + */ + function testHabtmUuidWithUuidId() { + $this->loadFixtures('Uuidportfolio', 'Uuiditem', 'UuiditemsUuidportfolio'); + $TestModel =& new Uuidportfolio(); + + $data = array('Uuidportfolio' => array('name' => 'Portfolio 3')); + $data['Uuiditem']['Uuiditem'] = array('483798c8-c7cc-430e-8cf9-4fcc40cf8569'); + $TestModel->create($data); + $TestModel->save(); + $id = $TestModel->id; + $result = $TestModel->read(null, $id); + $this->assertEqual(1, count($result['Uuiditem'])); + $this->assertEqual(strlen($result['Uuiditem'][0]['UuiditemsUuidportfolio']['id']), 36); + } + +/** + * test HABTM saving when join table has no primary key and only 2 columns. + * + * @return void + */ + function testHabtmSavingWithNoPrimaryKeyUuidJoinTable() { + $this->loadFixtures('UuidTag', 'Fruit', 'FruitsUuidTag'); + $Fruit =& new Fruit(); + $data = array( + 'Fruit' => array( + 'color' => 'Red', + 'shape' => 'Heart-shaped', + 'taste' => 'sweet', + 'name' => 'Strawberry', + ), + 'UuidTag' => array( + 'UuidTag' => array( + '481fc6d0-b920-43e0-e50f-6d1740cf8569' + ) + ) + ); + $this->assertTrue($Fruit->save($data)); + } + +/** + * test HABTM saving when join table has no primary key and only 2 columns, no with model is used. + * + * @return void + */ + function testHabtmSavingWithNoPrimaryKeyUuidJoinTableNoWith() { + $this->loadFixtures('UuidTag', 'Fruit', 'FruitsUuidTag'); + $Fruit =& new FruitNoWith(); + $data = array( + 'Fruit' => array( + 'color' => 'Red', + 'shape' => 'Heart-shaped', + 'taste' => 'sweet', + 'name' => 'Strawberry', + ), + 'UuidTag' => array( + 'UuidTag' => array( + '481fc6d0-b920-43e0-e50f-6d1740cf8569' + ) + ) + ); + $this->assertTrue($Fruit->save($data)); + } + +/** + * testHabtmUuidWithNumericId method + * + * @access public + * @return void + */ + function testHabtmUuidWithNumericId() { + $this->loadFixtures('Uuidportfolio', 'Uuiditem', 'UuiditemsUuidportfolioNumericid'); + $TestModel =& new Uuiditem(); + + $data = array('Uuiditem' => array('name' => 'Item 7', 'published' => 0)); + $data['Uuidportfolio']['Uuidportfolio'] = array('480af662-eb8c-47d3-886b-230540cf8569'); + $TestModel->create($data); + $TestModel->save(); + $id = $TestModel->id; + $result = $TestModel->read(null, $id); + $this->assertEqual(1, count($result['Uuidportfolio'])); + } + +/** + * testSaveMultipleHabtm method + * + * @access public + * @return void + */ + function testSaveMultipleHabtm() { + $this->loadFixtures('JoinA', 'JoinB', 'JoinC', 'JoinAB', 'JoinAC'); + $TestModel = new JoinA(); + $result = $TestModel->findById(1); + + $expected = array( + 'JoinA' => array( + 'id' => 1, + 'name' => 'Join A 1', + 'body' => 'Join A 1 Body', + 'created' => '2008-01-03 10:54:23', + 'updated' => '2008-01-03 10:54:23' + ), + 'JoinB' => array( + 0 => array( + 'id' => 2, + 'name' => 'Join B 2', + 'created' => '2008-01-03 10:55:02', + 'updated' => '2008-01-03 10:55:02', + 'JoinAsJoinB' => array( + 'id' => 1, + 'join_a_id' => 1, + 'join_b_id' => 2, + 'other' => 'Data for Join A 1 Join B 2', + 'created' => '2008-01-03 10:56:33', + 'updated' => '2008-01-03 10:56:33' + ))), + 'JoinC' => array( + 0 => array( + 'id' => 2, + 'name' => 'Join C 2', + 'created' => '2008-01-03 10:56:12', + 'updated' => '2008-01-03 10:56:12', + 'JoinAsJoinC' => array( + 'id' => 1, + 'join_a_id' => 1, + 'join_c_id' => 2, + 'other' => 'Data for Join A 1 Join C 2', + 'created' => '2008-01-03 10:57:22', + 'updated' => '2008-01-03 10:57:22' + )))); + + $this->assertEqual($result, $expected); + + $ts = date('Y-m-d H:i:s'); + $TestModel->id = 1; + $data = array( + 'JoinA' => array( + 'id' => '1', + 'name' => 'New name for Join A 1', + 'updated' => $ts + ), + 'JoinB' => array( + array( + 'id' => 1, + 'join_b_id' => 2, + 'other' => 'New data for Join A 1 Join B 2', + 'created' => $ts, + 'updated' => $ts + )), + 'JoinC' => array( + array( + 'id' => 1, + 'join_c_id' => 2, + 'other' => 'New data for Join A 1 Join C 2', + 'created' => $ts, + 'updated' => $ts + ))); + + $TestModel->set($data); + $TestModel->save(); + + $result = $TestModel->findById(1); + $expected = array( + 'JoinA' => array( + 'id' => 1, + 'name' => 'New name for Join A 1', + 'body' => 'Join A 1 Body', + 'created' => '2008-01-03 10:54:23', + 'updated' => $ts + ), + 'JoinB' => array( + 0 => array( + 'id' => 2, + 'name' => 'Join B 2', + 'created' => '2008-01-03 10:55:02', + 'updated' => '2008-01-03 10:55:02', + 'JoinAsJoinB' => array( + 'id' => 1, + 'join_a_id' => 1, + 'join_b_id' => 2, + 'other' => 'New data for Join A 1 Join B 2', + 'created' => $ts, + 'updated' => $ts + ))), + 'JoinC' => array( + 0 => array( + 'id' => 2, + 'name' => 'Join C 2', + 'created' => '2008-01-03 10:56:12', + 'updated' => '2008-01-03 10:56:12', + 'JoinAsJoinC' => array( + 'id' => 1, + 'join_a_id' => 1, + 'join_c_id' => 2, + 'other' => 'New data for Join A 1 Join C 2', + 'created' => $ts, + 'updated' => $ts + )))); + + $this->assertEqual($result, $expected); + } + +/** + * testSaveAll method + * + * @access public + * @return void + */ + function testSaveAll() { + $this->loadFixtures('Post', 'Author', 'Comment', 'Attachment'); + $TestModel =& new Post(); + + $result = $TestModel->find('all'); + $this->assertEqual(count($result), 3); + $this->assertFalse(isset($result[3])); + $ts = date('Y-m-d H:i:s'); + + $TestModel->saveAll(array( + 'Post' => array( + 'title' => 'Post with Author', + 'body' => 'This post will be saved with an author' + ), + 'Author' => array( + 'user' => 'bob', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf90' + ))); + + $result = $TestModel->find('all'); + $expected = array( + 'Post' => array( + 'id' => '4', + 'author_id' => '5', + 'title' => 'Post with Author', + 'body' => 'This post will be saved with an author', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + ), + 'Author' => array( + 'id' => '5', + 'user' => 'bob', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf90', + 'created' => $ts, + 'updated' => $ts, + 'test' => 'working' + )); + $this->assertEqual($result[3], $expected); + $this->assertEqual(count($result), 4); + + $TestModel->deleteAll(true); + $this->assertEqual($TestModel->find('all'), array()); + + // SQLite seems to reset the PK counter when that happens, so we need this to make the tests pass + $this->db->truncate($TestModel); + + $ts = date('Y-m-d H:i:s'); + $TestModel->saveAll(array( + array( + 'title' => 'Multi-record post 1', + 'body' => 'First multi-record post', + 'author_id' => 2 + ), + array( + 'title' => 'Multi-record post 2', + 'body' => 'Second multi-record post', + 'author_id' => 2 + ))); + + $result = $TestModel->find('all', array( + 'recursive' => -1, + 'order' => 'Post.id ASC' + )); + $expected = array( + array( + 'Post' => array( + 'id' => '1', + 'author_id' => '2', + 'title' => 'Multi-record post 1', + 'body' => 'First multi-record post', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )), + array( + 'Post' => array( + 'id' => '2', + 'author_id' => '2', + 'title' => 'Multi-record post 2', + 'body' => 'Second multi-record post', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + ))); + $this->assertEqual($result, $expected); + + $TestModel =& new Comment(); + $ts = date('Y-m-d H:i:s'); + $result = $TestModel->saveAll(array( + 'Comment' => array( + 'article_id' => 2, + 'user_id' => 2, + 'comment' => 'New comment with attachment', + 'published' => 'Y' + ), + 'Attachment' => array( + 'attachment' => 'some_file.tgz' + ))); + $this->assertTrue($result); + + $result = $TestModel->find('all'); + $expected = array( + 'id' => '7', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'New comment with attachment', + 'published' => 'Y', + 'created' => $ts, + 'updated' => $ts + ); + $this->assertEqual($result[6]['Comment'], $expected); + + $expected = array( + 'id' => '7', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'New comment with attachment', + 'published' => 'Y', + 'created' => $ts, + 'updated' => $ts + ); + $this->assertEqual($result[6]['Comment'], $expected); + + $expected = array( + 'id' => '2', + 'comment_id' => '7', + 'attachment' => 'some_file.tgz', + 'created' => $ts, + 'updated' => $ts + ); + $this->assertEqual($result[6]['Attachment'], $expected); + } + +/** + * Test SaveAll with Habtm relations + * + * @access public + * @return void + */ + function testSaveAllHabtm() { + $this->loadFixtures('Article', 'Tag', 'Comment', 'User'); + $data = array( + 'Article' => array( + 'user_id' => 1, + 'title' => 'Article Has and belongs to Many Tags' + ), + 'Tag' => array( + 'Tag' => array(1, 2) + ), + 'Comment' => array( + array( + 'comment' => 'Article comment', + 'user_id' => 1 + ))); + $Article =& new Article(); + $result = $Article->saveAll($data); + $this->assertTrue($result); + + $result = $Article->read(); + $this->assertEqual(count($result['Tag']), 2); + $this->assertEqual($result['Tag'][0]['tag'], 'tag1'); + $this->assertEqual(count($result['Comment']), 1); + $this->assertEqual(count($result['Comment'][0]['comment']['Article comment']), 1); + } + +/** + * Test SaveAll with Habtm relations and extra join table fields + * + * @access public + * @return void + */ + function testSaveAllHabtmWithExtraJoinTableFields() { + $this->loadFixtures('Something', 'SomethingElse', 'JoinThing'); + + $data = array( + 'Something' => array( + 'id' => 4, + 'title' => 'Extra Fields', + 'body' => 'Extra Fields Body', + 'published' => '1' + ), + 'SomethingElse' => array( + array('something_else_id' => 1, 'doomed' => '1'), + array('something_else_id' => 2, 'doomed' => '0'), + array('something_else_id' => 3, 'doomed' => '1') + ) + ); + + $Something =& new Something(); + $result = $Something->saveAll($data); + $this->assertTrue($result); + $result = $Something->read(); + + $this->assertEqual(count($result['SomethingElse']), 3); + $this->assertTrue(Set::matches('/Something[id=4]', $result)); + + $this->assertTrue(Set::matches('/SomethingElse[id=1]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=1]/JoinThing[something_else_id=1]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=1]/JoinThing[doomed=1]', $result)); + + $this->assertTrue(Set::matches('/SomethingElse[id=2]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=2]/JoinThing[something_else_id=2]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=2]/JoinThing[doomed=0]', $result)); + + $this->assertTrue(Set::matches('/SomethingElse[id=3]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=3]/JoinThing[something_else_id=3]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=3]/JoinThing[doomed=1]', $result)); + } + +/** + * testSaveAllHasOne method + * + * @access public + * @return void + */ + function testSaveAllHasOne() { + $model = new Comment(); + $model->deleteAll(true); + $this->assertEqual($model->find('all'), array()); + + $model->Attachment->deleteAll(true); + $this->assertEqual($model->Attachment->find('all'), array()); + + $this->assertTrue($model->saveAll(array( + 'Comment' => array( + 'comment' => 'Comment with attachment', + 'article_id' => 1, + 'user_id' => 1 + ), + 'Attachment' => array( + 'attachment' => 'some_file.zip' + )))); + $result = $model->find('all', array('fields' => array( + 'Comment.id', 'Comment.comment', 'Attachment.id', + 'Attachment.comment_id', 'Attachment.attachment' + ))); + $expected = array(array( + 'Comment' => array( + 'id' => '1', + 'comment' => 'Comment with attachment' + ), + 'Attachment' => array( + 'id' => '1', + 'comment_id' => '1', + 'attachment' => 'some_file.zip' + ))); + $this->assertEqual($result, $expected); + + + $model->Attachment->bindModel(array('belongsTo' => array('Comment')), false); + $data = array( + 'Comment' => array( + 'comment' => 'Comment with attachment', + 'article_id' => 1, + 'user_id' => 1 + ), + 'Attachment' => array( + 'attachment' => 'some_file.zip' + )); + $this->assertTrue($model->saveAll($data, array('validate' => 'first'))); + } + +/** + * testSaveAllBelongsTo method + * + * @access public + * @return void + */ + function testSaveAllBelongsTo() { + $model = new Comment(); + $model->deleteAll(true); + $this->assertEqual($model->find('all'), array()); + + $model->Article->deleteAll(true); + $this->assertEqual($model->Article->find('all'), array()); + + $this->assertTrue($model->saveAll(array( + 'Comment' => array( + 'comment' => 'Article comment', + 'article_id' => 1, + 'user_id' => 1 + ), + 'Article' => array( + 'title' => 'Model Associations 101', + 'user_id' => 1 + )))); + $result = $model->find('all', array('fields' => array( + 'Comment.id', 'Comment.comment', 'Comment.article_id', 'Article.id', 'Article.title' + ))); + $expected = array(array( + 'Comment' => array( + 'id' => '1', + 'article_id' => '1', + 'comment' => 'Article comment' + ), + 'Article' => array( + 'id' => '1', + 'title' => 'Model Associations 101' + ))); + $this->assertEqual($result, $expected); + } + +/** + * testSaveAllHasOneValidation method + * + * @access public + * @return void + */ + function testSaveAllHasOneValidation() { + $model = new Comment(); + $model->deleteAll(true); + $this->assertEqual($model->find('all'), array()); + + $model->Attachment->deleteAll(true); + $this->assertEqual($model->Attachment->find('all'), array()); + + $model->validate = array('comment' => 'notEmpty'); + $model->Attachment->validate = array('attachment' => 'notEmpty'); + $model->Attachment->bindModel(array('belongsTo' => array('Comment'))); + + $this->assertFalse($model->saveAll( + array( + 'Comment' => array( + 'comment' => '', + 'article_id' => 1, + 'user_id' => 1 + ), + 'Attachment' => array('attachment' => '') + ), + array('validate' => 'first') + )); + $expected = array( + 'Comment' => array('comment' => 'This field cannot be left blank'), + 'Attachment' => array('attachment' => 'This field cannot be left blank') + ); + $this->assertEqual($model->validationErrors, $expected['Comment']); + $this->assertEqual($model->Attachment->validationErrors, $expected['Attachment']); + + $this->assertFalse($model->saveAll( + array( + 'Comment' => array('comment' => '', 'article_id' => 1, 'user_id' => 1), + 'Attachment' => array('attachment' => '') + ), + array('validate' => 'only') + )); + $this->assertEqual($model->validationErrors, $expected['Comment']); + $this->assertEqual($model->Attachment->validationErrors, $expected['Attachment']); + } + +/** + * testSaveAllAtomic method + * + * @access public + * @return void + */ + function testSaveAllAtomic() { + $this->loadFixtures('Article', 'User'); + $TestModel =& new Article(); + + $result = $TestModel->saveAll(array( + 'Article' => array( + 'title' => 'Post with Author', + 'body' => 'This post will be saved with an author', + 'user_id' => 2 + ), + 'Comment' => array( + array('comment' => 'First new comment', 'user_id' => 2)) + ), array('atomic' => false)); + + $this->assertIdentical($result, array('Article' => true, 'Comment' => array(true))); + + $result = $TestModel->saveAll(array( + array( + 'id' => '1', + 'title' => 'Baleeted First Post', + 'body' => 'Baleeted!', + 'published' => 'N' + ), + array( + 'id' => '2', + 'title' => 'Just update the title' + ), + array( + 'title' => 'Creating a fourth post', + 'body' => 'Fourth post body', + 'user_id' => 2 + ) + ), array('atomic' => false)); + $this->assertIdentical($result, array(true, true, true)); + + $TestModel->validate = array('title' => 'notEmpty', 'author_id' => 'numeric'); + $result = $TestModel->saveAll(array( + array( + 'id' => '1', + 'title' => 'Un-Baleeted First Post', + 'body' => 'Not Baleeted!', + 'published' => 'Y' + ), + array( + 'id' => '2', + 'title' => '', + 'body' => 'Trying to get away with an empty title' + ) + ), array('validate' => true, 'atomic' => false)); + + $this->assertIdentical($result, array(true, false)); + + $result = $TestModel->saveAll(array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'comment' => 'First new comment', + 'published' => 'Y', + 'user_id' => 1 + ), + array( + 'comment' => 'Second new comment', + 'published' => 'Y', + 'user_id' => 2 + )) + ), array('validate' => true, 'atomic' => false)); + $this->assertIdentical($result, array('Article' => true, 'Comment' => array(true, true))); + } + +/** + * testSaveAllHasMany method + * + * @access public + * @return void + */ + function testSaveAllHasMany() { + $this->loadFixtures('Article', 'Comment'); + $TestModel =& new Article(); + $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array(); + + $result = $TestModel->saveAll(array( + 'Article' => array('id' => 2), + 'Comment' => array( + array('comment' => 'First new comment', 'published' => 'Y', 'user_id' => 1), + array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2) + ) + )); + $this->assertTrue($result); + + $result = $TestModel->findById(2); + $expected = array( + 'First Comment for Second Article', + 'Second Comment for Second Article', + 'First new comment', + 'Second new comment' + ); + $this->assertEqual(Set::extract($result['Comment'], '{n}.comment'), $expected); + + $result = $TestModel->saveAll( + array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'comment' => 'Third new comment', + 'published' => 'Y', + 'user_id' => 1 + ))), + array('atomic' => false) + ); + $this->assertTrue($result); + + $result = $TestModel->findById(2); + $expected = array( + 'First Comment for Second Article', + 'Second Comment for Second Article', + 'First new comment', + 'Second new comment', + 'Third new comment' + ); + $this->assertEqual(Set::extract($result['Comment'], '{n}.comment'), $expected); + + $TestModel->beforeSaveReturn = false; + $result = $TestModel->saveAll( + array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'comment' => 'Fourth new comment', + 'published' => 'Y', + 'user_id' => 1 + ))), + array('atomic' => false) + ); + $this->assertEqual($result, array('Article' => false)); + + $result = $TestModel->findById(2); + $expected = array( + 'First Comment for Second Article', + 'Second Comment for Second Article', + 'First new comment', + 'Second new comment', + 'Third new comment' + ); + $this->assertEqual(Set::extract($result['Comment'], '{n}.comment'), $expected); + } + +/** + * testSaveAllHasManyValidation method + * + * @access public + * @return void + */ + function testSaveAllHasManyValidation() { + $this->loadFixtures('Article', 'Comment'); + $TestModel =& new Article(); + $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array(); + $TestModel->Comment->validate = array('comment' => 'notEmpty'); + + $result = $TestModel->saveAll(array( + 'Article' => array('id' => 2), + 'Comment' => array( + array('comment' => '', 'published' => 'Y', 'user_id' => 1), + ) + ), array('validate' => true)); + $expected = array('Comment' => array(false)); + $this->assertEqual($result, $expected); + + $expected = array('Comment' => array( + array('comment' => 'This field cannot be left blank') + )); + $this->assertEqual($TestModel->validationErrors, $expected); + $expected = array( + array('comment' => 'This field cannot be left blank') + ); + $this->assertEqual($TestModel->Comment->validationErrors, $expected); + + $result = $TestModel->saveAll(array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1 + )) + ), array('validate' => 'first')); + $this->assertFalse($result); + } + +/** + * test saveAll with transactions and ensure there is no missing rollback. + * + * @return void + */ + function testSaveAllManyRowsTransactionNoRollback() { + $this->loadFixtures('Post'); + + Mock::generate('DboSource', 'MockTransactionDboSource'); + $db = ConnectionManager::create('mock_transaction', array( + 'datasource' => 'MockTransactionDbo', + )); + $db->expectOnce('rollback'); + + $Post =& new Post(); + $Post->useDbConfig = 'mock_transaction'; + + $Post->validate = array( + 'title' => array('rule' => array('notEmpty')) + ); + + $data = array( + array('author_id' => 1, 'title' => 'New Fourth Post'), + array('author_id' => 1, 'title' => '') + ); + $Post->saveAll($data, array('atomic' => true)); + } + +/** + * test saveAll with transactions and ensure there is no missing rollback. + * + * @return void + */ + function testSaveAllAssociatedTransactionNoRollback() { + $testDb = ConnectionManager::getDataSource('test_suite'); + + Mock::generate('DboSource', 'MockTransactionAssociatedDboSource'); + $db = ConnectionManager::create('mock_transaction_assoc', array( + 'datasource' => 'MockTransactionAssociatedDbo', + )); + $db->columns = $testDb->columns; + + $db->expectOnce('rollback'); + + $Post =& new Post(); + $Post->useDbConfig = 'mock_transaction_assoc'; + $Post->Author->useDbConfig = 'mock_transaction_assoc'; + + $Post->Author->validate = array( + 'user' => array('rule' => array('notEmpty')) + ); + + $data = array( + 'Post' => array( + 'title' => 'New post', + 'body' => 'Content', + 'published' => 'Y' + ), + 'Author' => array( + 'user' => '', + 'password' => "sekret" + ) + ); + $Post->saveAll($data); + } + +/** + * test saveAll with nested saveAll call. + * + * @return void + */ + function testSaveAllNestedSaveAll() { + $this->loadFixtures('Sample'); + $TransactionTestModel =& new TransactionTestModel(); + + $data = array( + array('apple_id' => 1, 'name' => 'sample5'), + ); + + $this->assertTrue($TransactionTestModel->saveAll($data, array('atomic' => true))); + } + +/** + * testSaveAllTransaction method + * + * @access public + * @return void + */ + function testSaveAllTransaction() { + $this->loadFixtures('Post', 'Author', 'Comment', 'Attachment'); + $TestModel =& new Post(); + + $TestModel->validate = array('title' => 'notEmpty'); + $data = array( + array('author_id' => 1, 'title' => 'New Fourth Post'), + array('author_id' => 1, 'title' => 'New Fifth Post'), + array('author_id' => 1, 'title' => '') + ); + $ts = date('Y-m-d H:i:s'); + $this->assertFalse($TestModel->saveAll($data)); + + $result = $TestModel->find('all', array('recursive' => -1)); + $expected = array( + array('Post' => array( + 'id' => '1', + 'author_id' => 1, + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + array('Post' => array( + 'id' => '2', + 'author_id' => 3, + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + )), + array('Post' => array( + 'id' => '3', + 'author_id' => 1, + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ))); + + if (count($result) != 3) { + // Database doesn't support transactions + $expected[] = array( + 'Post' => array( + 'id' => '4', + 'author_id' => 1, + 'title' => 'New Fourth Post', + 'body' => null, + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )); + + $expected[] = array( + 'Post' => array( + 'id' => '5', + 'author_id' => 1, + 'title' => 'New Fifth Post', + 'body' => null, + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )); + + $this->assertEqual($result, $expected); + // Skip the rest of the transactional tests + return; + } + + $this->assertEqual($result, $expected); + + $data = array( + array('author_id' => 1, 'title' => 'New Fourth Post'), + array('author_id' => 1, 'title' => ''), + array('author_id' => 1, 'title' => 'New Sixth Post') + ); + $ts = date('Y-m-d H:i:s'); + $this->assertFalse($TestModel->saveAll($data)); + + $result = $TestModel->find('all', array('recursive' => -1)); + $expected = array( + array('Post' => array( + 'id' => '1', + 'author_id' => 1, + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + array('Post' => array( + 'id' => '2', + 'author_id' => 3, + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + )), + array('Post' => array( + 'id' => '3', + 'author_id' => 1, + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ))); + + if (count($result) != 3) { + // Database doesn't support transactions + $expected[] = array( + 'Post' => array( + 'id' => '4', + 'author_id' => 1, + 'title' => 'New Fourth Post', + 'body' => 'Third Post Body', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )); + + $expected[] = array( + 'Post' => array( + 'id' => '5', + 'author_id' => 1, + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )); + } + $this->assertEqual($result, $expected); + + $TestModel->validate = array('title' => 'notEmpty'); + $data = array( + array('author_id' => 1, 'title' => 'New Fourth Post'), + array('author_id' => 1, 'title' => 'New Fifth Post'), + array('author_id' => 1, 'title' => 'New Sixth Post') + ); + $this->assertTrue($TestModel->saveAll($data)); + + $result = $TestModel->find('all', array( + 'recursive' => -1, + 'fields' => array('author_id', 'title','body','published') + )); + + $expected = array( + array('Post' => array( + 'author_id' => 1, + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y' + )), + array('Post' => array( + 'author_id' => 3, + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y' + )), + array('Post' => array( + 'author_id' => 1, + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y' + )), + array('Post' => array( + 'author_id' => 1, + 'title' => 'New Fourth Post', + 'body' => '', + 'published' => 'N' + )), + array('Post' => array( + 'author_id' => 1, + 'title' => 'New Fifth Post', + 'body' => '', + 'published' => 'N' + )), + array('Post' => array( + 'author_id' => 1, + 'title' => 'New Sixth Post', + 'body' => '', + 'published' => 'N' + ))); + $this->assertEqual($result, $expected); + } + +/** + * testSaveAllValidation method + * + * @access public + * @return void + */ + function testSaveAllValidation() { + $this->loadFixtures('Post', 'Author', 'Comment', 'Attachment'); + $TestModel =& new Post(); + + $data = array( + array( + 'id' => '1', + 'title' => 'Baleeted First Post', + 'body' => 'Baleeted!', + 'published' => 'N' + ), + array( + 'id' => '2', + 'title' => 'Just update the title' + ), + array( + 'title' => 'Creating a fourth post', + 'body' => 'Fourth post body', + 'author_id' => 2 + )); + + $this->assertTrue($TestModel->saveAll($data)); + + $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); + $ts = date('Y-m-d H:i:s'); + $expected = array( + array( + 'Post' => array( + 'id' => '1', + 'author_id' => '1', + 'title' => 'Baleeted First Post', + 'body' => 'Baleeted!', + 'published' => 'N', + 'created' => '2007-03-18 10:39:23', + 'updated' => $ts + )), + array( + 'Post' => array( + 'id' => '2', + 'author_id' => '3', + 'title' => 'Just update the title', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', 'updated' => $ts + )), + array( + 'Post' => array( + 'id' => '3', + 'author_id' => '1', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + )), + array( + 'Post' => array( + 'id' => '4', + 'author_id' => '2', + 'title' => 'Creating a fourth post', + 'body' => 'Fourth post body', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + ))); + $this->assertEqual($result, $expected); + + $TestModel->validate = array('title' => 'notEmpty', 'author_id' => 'numeric'); + $data = array( + array( + 'id' => '1', + 'title' => 'Un-Baleeted First Post', + 'body' => 'Not Baleeted!', + 'published' => 'Y' + ), + array( + 'id' => '2', + 'title' => '', + 'body' => 'Trying to get away with an empty title' + )); + $result = $TestModel->saveAll($data); + $this->assertEqual($result, false); + + $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); + $errors = array(1 => array('title' => 'This field cannot be left blank')); + $transactionWorked = Set::matches('/Post[1][title=Baleeted First Post]', $result); + if (!$transactionWorked) { + $this->assertTrue(Set::matches('/Post[1][title=Un-Baleeted First Post]', $result)); + $this->assertTrue(Set::matches('/Post[2][title=Just update the title]', $result)); + } + + $this->assertEqual($TestModel->validationErrors, $errors); + + $TestModel->validate = array('title' => 'notEmpty', 'author_id' => 'numeric'); + $data = array( + array( + 'id' => '1', + 'title' => 'Un-Baleeted First Post', + 'body' => 'Not Baleeted!', + 'published' => 'Y' + ), + array( + 'id' => '2', + 'title' => '', + 'body' => 'Trying to get away with an empty title' + )); + $result = $TestModel->saveAll($data, array('validate' => true, 'atomic' => false)); + $this->assertEqual($result, array(true, false)); + $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); + $errors = array(1 => array('title' => 'This field cannot be left blank')); + $newTs = date('Y-m-d H:i:s'); + $expected = array( + array( + 'Post' => array( + 'id' => '1', + 'author_id' => '1', + 'title' => 'Un-Baleeted First Post', + 'body' => 'Not Baleeted!', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => $newTs + )), + array( + 'Post' => array( + 'id' => '2', + 'author_id' => '3', + 'title' => 'Just update the title', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => $ts + )), + array( + 'Post' => array( + 'id' => '3', + 'author_id' => '1', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + )), + array( + 'Post' => array( + 'id' => '4', + 'author_id' => '2', + 'title' => 'Creating a fourth post', + 'body' => 'Fourth post body', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + ))); + $this->assertEqual($result, $expected); + $this->assertEqual($TestModel->validationErrors, $errors); + + $data = array( + array( + 'id' => '1', + 'title' => 'Re-Baleeted First Post', + 'body' => 'Baleeted!', + 'published' => 'N' + ), + array( + 'id' => '2', + 'title' => '', + 'body' => 'Trying to get away with an empty title' + )); + $this->assertFalse($TestModel->saveAll($data, array('validate' => 'first'))); + + $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); + $this->assertEqual($result, $expected); + $this->assertEqual($TestModel->validationErrors, $errors); + + $data = array( + array( + 'title' => 'First new post', + 'body' => 'Woohoo!', + 'published' => 'Y' + ), + array( + 'title' => 'Empty body', + 'body' => '' + )); + + $TestModel->validate['body'] = 'notEmpty'; + } + +/** + * testSaveAllValidationOnly method + * + * @access public + * @return void + */ + function testSaveAllValidationOnly() { + $TestModel =& new Comment(); + $TestModel->Attachment->validate = array('attachment' => 'notEmpty'); + + $data = array( + 'Comment' => array( + 'comment' => 'This is the comment' + ), + 'Attachment' => array( + 'attachment' => '' + ) + ); + + $result = $TestModel->saveAll($data, array('validate' => 'only')); + $this->assertFalse($result); + + $TestModel =& new Article(); + $TestModel->validate = array('title' => 'notEmpty'); + $result = $TestModel->saveAll( + array( + 0 => array('title' => ''), + 1 => array('title' => 'title 1'), + 2 => array('title' => 'title 2'), + ), + array('validate'=>'only') + ); + $this->assertFalse($result); + $expected = array( + 0 => array('title' => 'This field cannot be left blank'), + ); + $this->assertEqual($TestModel->validationErrors, $expected); + + $result = $TestModel->saveAll( + array( + 0 => array('title' => 'title 0'), + 1 => array('title' => ''), + 2 => array('title' => 'title 2'), + ), + array('validate'=>'only') + ); + $this->assertFalse($result); + $expected = array( + 1 => array('title' => 'This field cannot be left blank'), + ); + $this->assertEqual($TestModel->validationErrors, $expected); + } + +/** + * testSaveAllValidateFirst method + * + * @access public + * @return void + */ + function testSaveAllValidateFirst() { + $model =& new Article(); + $model->deleteAll(true); + + $model->Comment->validate = array('comment' => 'notEmpty'); + $result = $model->saveAll(array( + 'Article' => array( + 'title' => 'Post with Author', + 'body' => 'This post will be saved author' + ), + 'Comment' => array( + array('comment' => 'First new comment'), + array('comment' => '') + ) + ), array('validate' => 'first')); + + $this->assertFalse($result); + + $result = $model->find('all'); + $this->assertEqual($result, array()); + $expected = array('Comment' => array( + 1 => array('comment' => 'This field cannot be left blank') + )); + + $this->assertEqual($model->Comment->validationErrors, $expected['Comment']); + + $this->assertIdentical($model->Comment->find('count'), 0); + + $result = $model->saveAll( + array( + 'Article' => array( + 'title' => 'Post with Author', + 'body' => 'This post will be saved with an author', + 'user_id' => 2 + ), + 'Comment' => array( + array( + 'comment' => 'Only new comment', + 'user_id' => 2 + ))), + array('validate' => 'first') + ); + + $this->assertIdentical($result, true); + + $result = $model->Comment->find('all'); + $this->assertIdentical(count($result), 1); + $result = Set::extract('/Comment/article_id', $result); + $this->assertTrue($result[0] === 1 || $result[0] === '1'); + + + $model->deleteAll(true); + $data = array( + 'Article' => array( + 'title' => 'Post with Author saveAlled from comment', + 'body' => 'This post will be saved with an author', + 'user_id' => 2 + ), + 'Comment' => array( + 'comment' => 'Only new comment', 'user_id' => 2 + )); + + $result = $model->Comment->saveAll($data, array('validate' => 'first')); + $this->assertTrue($result); + + $result = $model->find('all'); + $this->assertEqual( + $result[0]['Article']['title'], + 'Post with Author saveAlled from comment' + ); + $this->assertEqual($result[0]['Comment'][0]['comment'], 'Only new comment'); + } + +/** + * test saveAll()'s return is correct when using atomic = false and validate = first. + * + * @return void + */ + function testSaveAllValidateFirstAtomicFalse() { + $Something =& new Something(); + $invalidData = array( + array( + 'title' => 'foo', + 'body' => 'bar', + 'published' => 'baz', + ), + array( + 'body' => 3, + 'published' =>'sd', + ), + ); + $Something->create(); + $Something->validate = array( + 'title' => array( + 'rule' => 'alphaNumeric', + 'required' => true, + ), + 'body' => array( + 'rule' => 'alphaNumeric', + 'required' => true, + 'allowEmpty' => true, + ), + ); + $result = $Something->saveAll($invalidData, array( + 'atomic' => false, + 'validate' => 'first', + )); + $expected = array(true, false); + $this->assertEqual($result, $expected); + + $Something =& new Something(); + $validData = array( + array( + 'title' => 'title value', + 'body' => 'body value', + 'published' => 'baz', + ), + array( + 'title' => 'valid', + 'body' => 'this body', + 'published' =>'sd', + ), + ); + $Something->create(); + $result = $Something->saveAll($validData, array( + 'atomic' => false, + 'validate' => 'first', + )); + $expected = array(true, true); + $this->assertEqual($result, $expected); + } + +/** + * testUpdateWithCalculation method + * + * @access public + * @return void + */ + function testUpdateWithCalculation() { + $this->loadFixtures('DataTest'); + $model =& new DataTest(); + $model->deleteAll(true); + $result = $model->saveAll(array( + array('count' => 5, 'float' => 1.1), + array('count' => 3, 'float' => 1.2), + array('count' => 4, 'float' => 1.3), + array('count' => 1, 'float' => 2.0), + )); + $this->assertTrue($result); + + $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); + $this->assertEqual($result, array(5, 3, 4, 1)); + + $this->assertTrue($model->updateAll(array('count' => 'count + 2'))); + $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); + $this->assertEqual($result, array(7, 5, 6, 3)); + + $this->assertTrue($model->updateAll(array('DataTest.count' => 'DataTest.count - 1'))); + $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); + $this->assertEqual($result, array(6, 4, 5, 2)); + } + +/** + * testSaveAllHasManyValidationOnly method + * + * @access public + * @return void + */ + function testSaveAllHasManyValidationOnly() { + $this->loadFixtures('Article', 'Comment'); + $TestModel =& new Article(); + $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array(); + $TestModel->Comment->validate = array('comment' => 'notEmpty'); + + $result = $TestModel->saveAll( + array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'id' => 1, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1), + array( + 'id' => 2, + 'comment' => + 'comment', + 'published' => 'Y', + 'user_id' => 1 + ))), + array('validate' => 'only') + ); + $this->assertFalse($result); + + $result = $TestModel->saveAll( + array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'id' => 1, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1 + ), + array( + 'id' => 2, + 'comment' => 'comment', + 'published' => 'Y', + 'user_id' => 1 + ), + array( + 'id' => 3, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1 + ))), + array( + 'validate' => 'only', + 'atomic' => false + )); + $expected = array( + 'Article' => true, + 'Comment' => array(false, true, false) + ); + $this->assertIdentical($result, $expected); + + $expected = array('Comment' => array( + 0 => array('comment' => 'This field cannot be left blank'), + 2 => array('comment' => 'This field cannot be left blank') + )); + $this->assertEqual($TestModel->validationErrors, $expected); + + $expected = array( + 0 => array('comment' => 'This field cannot be left blank'), + 2 => array('comment' => 'This field cannot be left blank') + ); + $this->assertEqual($TestModel->Comment->validationErrors, $expected); + } + +/** + * TestFindAllWithoutForeignKey + * + * @link http://code.cakephp.org/tickets/view/69 + * @access public + * @return void + */ + function testFindAllForeignKey() { + $this->loadFixtures('ProductUpdateAll', 'GroupUpdateAll'); + $ProductUpdateAll =& new ProductUpdateAll(); + + $conditions = array('Group.name' => 'group one'); + + $ProductUpdateAll->bindModel(array( + 'belongsTo' => array( + 'Group' => array('className' => 'GroupUpdateAll') + ) + )); + + $ProductUpdateAll->belongsTo = array( + 'Group' => array('className' => 'GroupUpdateAll', 'foreignKey' => 'group_id') + ); + + $results = $ProductUpdateAll->find('all', compact('conditions')); + $this->assertTrue(!empty($results)); + + $ProductUpdateAll->bindModel(array('belongsTo'=>array('Group'))); + $ProductUpdateAll->belongsTo = array( + 'Group' => array( + 'className' => 'GroupUpdateAll', + 'foreignKey' => false, + 'conditions' => 'ProductUpdateAll.groupcode = Group.code' + )); + + $resultsFkFalse = $ProductUpdateAll->find('all', compact('conditions')); + $this->assertTrue(!empty($resultsFkFalse)); + $expected = array( + '0' => array( + 'ProductUpdateAll' => array( + 'id' => 1, + 'name' => 'product one', + 'groupcode' => 120, + 'group_id' => 1), + 'Group' => array( + 'id' => 1, + 'name' => 'group one', + 'code' => 120) + ), + '1' => array( + 'ProductUpdateAll' => array( + 'id' => 2, + 'name' => 'product two', + 'groupcode' => 120, + 'group_id' => 1), + 'Group' => array( + 'id' => 1, + 'name' => 'group one', + 'code' => 120) + ) + + ); + $this->assertEqual($results, $expected); + $this->assertEqual($resultsFkFalse, $expected); + } + +/** + * test updateAll with empty values. + * + * @return void + */ + function testUpdateAllEmptyValues() { + $this->loadFixtures('Author', 'Post'); + $model = new Author(); + $result = $model->updateAll(array('user' => '""')); + $this->assertTrue($result); + } + +/** + * testUpdateAllWithJoins + * + * @link http://code.cakephp.org/tickets/view/69 + * @access public + * @return void + */ + function testUpdateAllWithJoins() { + $this->skipIf( + $this->db->config['driver'] == 'postgres', + '%s Currently, there is no way of doing joins in an update statement in postgresql' + ); + $this->loadFixtures('ProductUpdateAll', 'GroupUpdateAll'); + $ProductUpdateAll =& new ProductUpdateAll(); + + $conditions = array('Group.name' => 'group one'); + + $ProductUpdateAll->bindModel(array('belongsTo' => array( + 'Group' => array('className' => 'GroupUpdateAll'))) + ); + + $ProductUpdateAll->updateAll(array('name' => "'new product'"), $conditions); + $results = $ProductUpdateAll->find('all', array( + 'conditions' => array('ProductUpdateAll.name' => 'new product') + )); + $expected = array( + '0' => array( + 'ProductUpdateAll' => array( + 'id' => 1, + 'name' => 'new product', + 'groupcode' => 120, + 'group_id' => 1), + 'Group' => array( + 'id' => 1, + 'name' => 'group one', + 'code' => 120) + ), + '1' => array( + 'ProductUpdateAll' => array( + 'id' => 2, + 'name' => 'new product', + 'groupcode' => 120, + 'group_id' => 1), + 'Group' => array( + 'id' => 1, + 'name' => 'group one', + 'code' => 120))); + + $this->assertEqual($results, $expected); + } + +/** + * testUpdateAllWithoutForeignKey + * + * @link http://code.cakephp.org/tickets/view/69 + * @access public + * @return void + */ + function testUpdateAllWithoutForeignKey() { + $this->skipIf( + $this->db->config['driver'] == 'postgres', + '%s Currently, there is no way of doing joins in an update statement in postgresql' + ); + $this->loadFixtures('ProductUpdateAll', 'GroupUpdateAll'); + $ProductUpdateAll =& new ProductUpdateAll(); + + $conditions = array('Group.name' => 'group one'); + + $ProductUpdateAll->bindModel(array('belongsTo' => array( + 'Group' => array('className' => 'GroupUpdateAll') + ))); + + $ProductUpdateAll->belongsTo = array( + 'Group' => array( + 'className' => 'GroupUpdateAll', + 'foreignKey' => false, + 'conditions' => 'ProductUpdateAll.groupcode = Group.code' + ) + ); + + $ProductUpdateAll->updateAll(array('name' => "'new product'"), $conditions); + $resultsFkFalse = $ProductUpdateAll->find('all', array('conditions' => array('ProductUpdateAll.name'=>'new product'))); + $expected = array( + '0' => array( + 'ProductUpdateAll' => array( + 'id' => 1, + 'name' => 'new product', + 'groupcode' => 120, + 'group_id' => 1), + 'Group' => array( + 'id' => 1, + 'name' => 'group one', + 'code' => 120) + ), + '1' => array( + 'ProductUpdateAll' => array( + 'id' => 2, + 'name' => 'new product', + 'groupcode' => 120, + 'group_id' => 1), + 'Group' => array( + 'id' => 1, + 'name' => 'group one', + 'code' => 120))); + $this->assertEqual($resultsFkFalse, $expected); + } + +/** + * test that saveAll behaves like plain save() when suplied empty data + * + * @link http://cakephp.lighthouseapp.com/projects/42648/tickets/277-test-saveall-with-validation-returns-incorrect-boolean-when-saving-empty-data + * @access public + * @return void + */ + function testSaveAllEmptyData() { + $this->loadFixtures('Article', 'ProductUpdateAll'); + $model =& new Article(); + $result = $model->saveAll(array(), array('validate' => 'first')); + $this->assertTrue($result); + + $model =& new ProductUpdateAll(); + $result = $model->saveAll(array()); + $this->assertFalse($result); + } + +/** + * test writing floats in german locale. + * + * @return void + */ + function testWriteFloatAsGerman() { + $restore = setlocale(LC_ALL, null); + setlocale(LC_ALL, 'de_DE'); + + $model = new DataTest(); + $result = $model->save(array( + 'count' => 1, + 'float' => 3.14593 + )); + $this->assertTrue($result); + setlocale(LC_ALL, $restore); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/models.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/models.php new file mode 100644 index 000000000..febb073cc --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/model/models.php @@ -0,0 +1,3594 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.model + * @since CakePHP(tm) v 1.2.0.6464 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + define('CAKEPHP_UNIT_TEST_EXECUTION', 1); +} + +/** + * Test class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Test extends CakeTestModel { + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * name property + * + * @var string 'Test' + * @access public + */ + var $name = 'Test'; + +/** + * schema property + * + * @var array + * @access protected + */ + var $_schema = array( + 'id'=> array('type' => 'integer', 'null' => '', 'default' => '1', 'length' => '8', 'key'=>'primary'), + 'name'=> array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'email'=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'notes'=> array('type' => 'text', 'null' => '1', 'default' => 'write some notes here', 'length' => ''), + 'created'=> array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); +} + +/** + * TestAlias class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TestAlias extends CakeTestModel { + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * name property + * + * @var string 'TestAlias' + * @access public + */ + var $name = 'TestAlias'; + +/** + * alias property + * + * @var string 'TestAlias' + * @access public + */ + var $alias = 'TestAlias'; + +/** + * schema property + * + * @var array + * @access protected + */ + var $_schema = array( + 'id'=> array('type' => 'integer', 'null' => '', 'default' => '1', 'length' => '8', 'key'=>'primary'), + 'name'=> array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'email'=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'notes'=> array('type' => 'text', 'null' => '1', 'default' => 'write some notes here', 'length' => ''), + 'created'=> array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); +} + +/** + * TestValidate class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TestValidate extends CakeTestModel { + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * name property + * + * @var string 'TestValidate' + * @access public + */ + var $name = 'TestValidate'; + +/** + * schema property + * + * @var array + * @access protected + */ + var $_schema = array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'title' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'body' => array('type' => 'string', 'null' => '1', 'default' => '', 'length' => ''), + 'number' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'modified' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + +/** + * validateNumber method + * + * @param mixed $value + * @param mixed $options + * @access public + * @return void + */ + function validateNumber($value, $options) { + $options = array_merge(array('min' => 0, 'max' => 100), $options); + $valid = ($value['number'] >= $options['min'] && $value['number'] <= $options['max']); + return $valid; + } + +/** + * validateTitle method + * + * @param mixed $value + * @access public + * @return void + */ + function validateTitle($value) { + return (!empty($value) && strpos(low($value['title']), 'title-') === 0); + } +} + +/** + * User class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class User extends CakeTestModel { + +/** + * name property + * + * @var string 'User' + * @access public + */ + var $name = 'User'; + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array('user' => 'notEmpty', 'password' => 'notEmpty'); +} + +/** + * Article class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Article extends CakeTestModel { + +/** + * name property + * + * @var string 'Article' + * @access public + */ + var $name = 'Article'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('User'); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Comment' => array('dependent' => true)); + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Tag'); + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array('user_id' => 'numeric', 'title' => array('allowEmpty' => false, 'rule' => 'notEmpty'), 'body' => 'notEmpty'); + +/** + * beforeSaveReturn property + * + * @var bool true + * @access public + */ + var $beforeSaveReturn = true; + +/** + * beforeSave method + * + * @access public + * @return void + */ + function beforeSave() { + return $this->beforeSaveReturn; + } + +/** + * titleDuplicate method + * + * @param mixed $title + * @access public + * @return void + */ + function titleDuplicate ($title) { + if ($title === 'My Article Title') { + return false; + } + return true; + } +} + +/** + * Model stub for beforeDelete testing + * + * @see #250 + * @package cake.tests + */ +class BeforeDeleteComment extends CakeTestModel { + var $name = 'BeforeDeleteComment'; + + var $useTable = 'comments'; + + function beforeDelete($cascade = true) { + $db =& $this->getDataSource(); + $db->delete($this, array($this->alias . '.' . $this->primaryKey => array(1, 3))); + return true; + } +} + +/** + * NumericArticle class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class NumericArticle extends CakeTestModel { + +/** + * name property + * + * @var string 'NumericArticle' + * @access public + */ + var $name = 'NumericArticle'; + +/** + * useTable property + * + * @var string 'numeric_articles' + * @access public + */ + var $useTable = 'numeric_articles'; +} + +/** + * Article10 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Article10 extends CakeTestModel { + +/** + * name property + * + * @var string 'Article10' + * @access public + */ + var $name = 'Article10'; + +/** + * useTable property + * + * @var string 'articles' + * @access public + */ + var $useTable = 'articles'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Comment' => array('dependent' => true, 'exclusive' => true)); +} + +/** + * ArticleFeatured class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ArticleFeatured extends CakeTestModel { + +/** + * name property + * + * @var string 'ArticleFeatured' + * @access public + */ + var $name = 'ArticleFeatured'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('User', 'Category'); + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('Featured'); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Comment' => array('className' => 'Comment', 'dependent' => true)); + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Tag'); + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array('user_id' => 'numeric', 'title' => 'notEmpty', 'body' => 'notEmpty'); +} + +/** + * Featured class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Featured extends CakeTestModel { + +/** + * name property + * + * @var string 'Featured' + * @access public + */ + var $name = 'Featured'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('ArticleFeatured', 'Category'); +} + +/** + * Tag class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Tag extends CakeTestModel { + +/** + * name property + * + * @var string 'Tag' + * @access public + */ + var $name = 'Tag'; +} + +/** + * ArticlesTag class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ArticlesTag extends CakeTestModel { + +/** + * name property + * + * @var string 'ArticlesTag' + * @access public + */ + var $name = 'ArticlesTag'; +} + +/** + * ArticleFeaturedsTag class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ArticleFeaturedsTag extends CakeTestModel { + +/** + * name property + * + * @var string 'ArticleFeaturedsTag' + * @access public + */ + var $name = 'ArticleFeaturedsTag'; +} + +/** + * Comment class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Comment extends CakeTestModel { + +/** + * name property + * + * @var string 'Comment' + * @access public + */ + var $name = 'Comment'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Article', 'User'); + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('Attachment' => array('dependent' => true)); +} + +/** + * Modified Comment Class has afterFind Callback + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ModifiedComment extends CakeTestModel { + +/** + * name property + * + * @var string 'Comment' + * @access public + */ + var $name = 'Comment'; + +/** + * useTable property + * + * @var string 'comments' + * @access public + */ + var $useTable = 'comments'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Article'); + +/** + * afterFind callback + * + * @return void + */ + function afterFind($results) { + if (isset($results[0])) { + $results[0]['Comment']['callback'] = 'Fire'; + } + return $results; + } +} + +/** + * Modified Comment Class has afterFind Callback + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class AgainModifiedComment extends CakeTestModel { + +/** + * name property + * + * @var string 'Comment' + * @access public + */ + var $name = 'Comment'; + +/** + * useTable property + * + * @var string 'comments' + * @access public + */ + var $useTable = 'comments'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Article'); + +/** + * afterFind callback + * + * @return void + */ + function afterFind($results) { + if (isset($results[0])) { + $results[0]['Comment']['querytype'] = $this->findQueryType; + } + return $results; + } +} + +/** + * MergeVarPluginAppModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class MergeVarPluginAppModel extends AppModel { + +/** + * actsAs parameter + * + * @var array + */ + var $actsAs = array( + 'Containable' + ); +} + +/** + * MergeVarPluginPost class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class MergeVarPluginPost extends MergeVarPluginAppModel { + +/** + * actsAs parameter + * + * @var array + */ + var $actsAs = array( + 'Tree' + ); + +/** + * useTable parameter + * + * @var string + */ + var $useTable = 'posts'; +} + +/** + * MergeVarPluginComment class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class MergeVarPluginComment extends MergeVarPluginAppModel { + +/** + * actsAs parameter + * + * @var array + */ + var $actsAs = array( + 'Containable' => array('some_settings') + ); + +/** + * useTable parameter + * + * @var string + */ + var $useTable = 'comments'; +} + + +/** + * Attachment class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Attachment extends CakeTestModel { + +/** + * name property + * + * @var string 'Attachment' + * @access public + */ + var $name = 'Attachment'; +} + +/** + * Category class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Category extends CakeTestModel { + +/** + * name property + * + * @var string 'Category' + * @access public + */ + var $name = 'Category'; +} + +/** + * CategoryThread class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class CategoryThread extends CakeTestModel { + +/** + * name property + * + * @var string 'CategoryThread' + * @access public + */ + var $name = 'CategoryThread'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('ParentCategory' => array('className' => 'CategoryThread', 'foreignKey' => 'parent_id')); +} + +/** + * Apple class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Apple extends CakeTestModel { + +/** + * name property + * + * @var string 'Apple' + * @access public + */ + var $name = 'Apple'; + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array('name' => 'notEmpty'); + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('Sample'); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Child' => array('className' => 'Apple', 'dependent' => true)); + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Parent' => array('className' => 'Apple', 'foreignKey' => 'apple_id')); +} + +/** + * Sample class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Sample extends CakeTestModel { + +/** + * name property + * + * @var string 'Sample' + * @access public + */ + var $name = 'Sample'; + +/** + * belongsTo property + * + * @var string 'Apple' + * @access public + */ + var $belongsTo = 'Apple'; +} + +/** + * AnotherArticle class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class AnotherArticle extends CakeTestModel { + +/** + * name property + * + * @var string 'AnotherArticle' + * @access public + */ + var $name = 'AnotherArticle'; + +/** + * hasMany property + * + * @var string 'Home' + * @access public + */ + var $hasMany = 'Home'; +} + +/** + * Advertisement class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Advertisement extends CakeTestModel { + +/** + * name property + * + * @var string 'Advertisement' + * @access public + */ + var $name = 'Advertisement'; + +/** + * hasMany property + * + * @var string 'Home' + * @access public + */ + var $hasMany = 'Home'; +} + +/** + * Home class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Home extends CakeTestModel { + +/** + * name property + * + * @var string 'Home' + * @access public + */ + var $name = 'Home'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('AnotherArticle', 'Advertisement'); +} + +/** + * Post class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Post extends CakeTestModel { + +/** + * name property + * + * @var string 'Post' + * @access public + */ + var $name = 'Post'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Author'); + + function beforeFind($queryData) { + if (isset($queryData['connection'])) { + $this->useDbConfig = $queryData['connection']; + } + return true; + } + + function afterFind($results) { + $this->useDbConfig = 'test_suite'; + return $results; + } +} + +/** + * Author class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Author extends CakeTestModel { + +/** + * name property + * + * @var string 'Author' + * @access public + */ + var $name = 'Author'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Post'); + +/** + * afterFind method + * + * @param mixed $results + * @access public + * @return void + */ + function afterFind($results) { + $results[0]['Author']['test'] = 'working'; + return $results; + } +} + +/** + * ModifiedAuthor class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ModifiedAuthor extends Author { + +/** + * name property + * + * @var string 'Author' + * @access public + */ + var $name = 'Author'; + +/** + * afterFind method + * + * @param mixed $results + * @access public + * @return void + */ + function afterFind($results) { + foreach($results as $index => $result) { + $results[$index]['Author']['user'] .= ' (CakePHP)'; + } + return $results; + } +} + +/** + * Project class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Project extends CakeTestModel { + +/** + * name property + * + * @var string 'Project' + * @access public + */ + var $name = 'Project'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Thread'); +} + +/** + * Thread class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Thread extends CakeTestModel { + +/** + * name property + * + * @var string 'Thread' + * @access public + */ + var $name = 'Thread'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $belongsTo = array('Project'); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Message'); +} + +/** + * Message class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Message extends CakeTestModel { + +/** + * name property + * + * @var string 'Message' + * @access public + */ + var $name = 'Message'; + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('Bid'); +} + +/** + * Bid class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Bid extends CakeTestModel { + +/** + * name property + * + * @var string 'Bid' + * @access public + */ + var $name = 'Bid'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Message'); +} + +/** + * NodeAfterFind class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class NodeAfterFind extends CakeTestModel { + +/** + * name property + * + * @var string 'NodeAfterFind' + * @access public + */ + var $name = 'NodeAfterFind'; + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array('name' => 'notEmpty'); + +/** + * useTable property + * + * @var string 'apples' + * @access public + */ + var $useTable = 'apples'; + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('Sample' => array('className' => 'NodeAfterFindSample')); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Child' => array('className' => 'NodeAfterFind', 'dependent' => true)); + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Parent' => array('className' => 'NodeAfterFind', 'foreignKey' => 'apple_id')); + +/** + * afterFind method + * + * @param mixed $results + * @access public + * @return void + */ + function afterFind($results) { + return $results; + } +} + +/** + * NodeAfterFindSample class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class NodeAfterFindSample extends CakeTestModel { + +/** + * name property + * + * @var string 'NodeAfterFindSample' + * @access public + */ + var $name = 'NodeAfterFindSample'; + +/** + * useTable property + * + * @var string 'samples' + * @access public + */ + var $useTable = 'samples'; + +/** + * belongsTo property + * + * @var string 'NodeAfterFind' + * @access public + */ + var $belongsTo = 'NodeAfterFind'; +} + +/** + * NodeNoAfterFind class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class NodeNoAfterFind extends CakeTestModel { + +/** + * name property + * + * @var string 'NodeAfterFind' + * @access public + */ + var $name = 'NodeAfterFind'; + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array('name' => 'notEmpty'); + +/** + * useTable property + * + * @var string 'apples' + * @access public + */ + var $useTable = 'apples'; + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('Sample' => array('className' => 'NodeAfterFindSample')); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Child' => array('className' => 'NodeAfterFind', 'dependent' => true)); + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Parent' => array('className' => 'NodeAfterFind', 'foreignKey' => 'apple_id')); +} + +/** + * Node class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Node extends CakeTestModel{ + +/** + * name property + * + * @var string 'Node' + * @access public + */ + var $name = 'Node'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array( + 'ParentNode' => array( + 'className' => 'Node', + 'joinTable' => 'dependency', + 'with' => 'Dependency', + 'foreignKey' => 'child_id', + 'associationForeignKey' => 'parent_id', + ) + ); +} + +/** + * Dependency class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Dependency extends CakeTestModel { + +/** + * name property + * + * @var string 'Dependency' + * @access public + */ + var $name = 'Dependency'; +} + +/** + * ModelA class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ModelA extends CakeTestModel { + +/** + * name property + * + * @var string 'ModelA' + * @access public + */ + var $name = 'ModelA'; + +/** + * useTable property + * + * @var string 'apples' + * @access public + */ + var $useTable = 'apples'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('ModelB', 'ModelC'); +} + +/** + * ModelB class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ModelB extends CakeTestModel { + +/** + * name property + * + * @var string 'ModelB' + * @access public + */ + var $name = 'ModelB'; + +/** + * useTable property + * + * @var string 'messages' + * @access public + */ + var $useTable = 'messages'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('ModelD'); +} + +/** + * ModelC class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ModelC extends CakeTestModel { + +/** + * name property + * + * @var string 'ModelC' + * @access public + */ + var $name = 'ModelC'; + +/** + * useTable property + * + * @var string 'bids' + * @access public + */ + var $useTable = 'bids'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('ModelD'); +} + +/** + * ModelD class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ModelD extends CakeTestModel { + +/** + * name property + * + * @var string 'ModelD' + * @access public + */ + var $name = 'ModelD'; + +/** + * useTable property + * + * @var string 'threads' + * @access public + */ + var $useTable = 'threads'; +} + +/** + * Something class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Something extends CakeTestModel { + +/** + * name property + * + * @var string 'Something' + * @access public + */ + var $name = 'Something'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('SomethingElse' => array('with' => array('JoinThing' => array('doomed')))); +} + +/** + * SomethingElse class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SomethingElse extends CakeTestModel { + +/** + * name property + * + * @var string 'SomethingElse' + * @access public + */ + var $name = 'SomethingElse'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Something' => array('with' => 'JoinThing')); +} + +/** + * JoinThing class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class JoinThing extends CakeTestModel { + +/** + * name property + * + * @var string 'JoinThing' + * @access public + */ + var $name = 'JoinThing'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Something', 'SomethingElse'); +} + +/** + * Portfolio class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Portfolio extends CakeTestModel { + +/** + * name property + * + * @var string 'Portfolio' + * @access public + */ + var $name = 'Portfolio'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Item'); +} + +/** + * Item class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Item extends CakeTestModel { + +/** + * name property + * + * @var string 'Item' + * @access public + */ + var $name = 'Item'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Syfile' => array('counterCache' => true)); + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Portfolio' => array('unique' => false)); +} + +/** + * ItemsPortfolio class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ItemsPortfolio extends CakeTestModel { + +/** + * name property + * + * @var string 'ItemsPortfolio' + * @access public + */ + var $name = 'ItemsPortfolio'; +} + +/** + * Syfile class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Syfile extends CakeTestModel { + +/** + * name property + * + * @var string 'Syfile' + * @access public + */ + var $name = 'Syfile'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Image'); +} + +/** + * Image class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Image extends CakeTestModel { + +/** + * name property + * + * @var string 'Image' + * @access public + */ + var $name = 'Image'; +} + +/** + * DeviceType class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class DeviceType extends CakeTestModel { + +/** + * name property + * + * @var string 'DeviceType' + * @access public + */ + var $name = 'DeviceType'; + +/** + * order property + * + * @var array + * @access public + */ + var $order = array('DeviceType.order' => 'ASC'); + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'DeviceTypeCategory', 'FeatureSet', 'ExteriorTypeCategory', + 'Image' => array('className' => 'Document'), + 'Extra1' => array('className' => 'Document'), + 'Extra2' => array('className' => 'Document')); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Device' => array('order' => array('Device.id' => 'ASC'))); +} + +/** + * DeviceTypeCategory class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class DeviceTypeCategory extends CakeTestModel { + +/** + * name property + * + * @var string 'DeviceTypeCategory' + * @access public + */ + var $name = 'DeviceTypeCategory'; +} + +/** + * FeatureSet class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class FeatureSet extends CakeTestModel { + +/** + * name property + * + * @var string 'FeatureSet' + * @access public + */ + var $name = 'FeatureSet'; +} + +/** + * ExteriorTypeCategory class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ExteriorTypeCategory extends CakeTestModel { + +/** + * name property + * + * @var string 'ExteriorTypeCategory' + * @access public + */ + var $name = 'ExteriorTypeCategory'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Image' => array('className' => 'Device')); +} + +/** + * Document class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Document extends CakeTestModel { + +/** + * name property + * + * @var string 'Document' + * @access public + */ + var $name = 'Document'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('DocumentDirectory'); +} + +/** + * Device class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Device extends CakeTestModel { + +/** + * name property + * + * @var string 'Device' + * @access public + */ + var $name = 'Device'; +} + +/** + * DocumentDirectory class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class DocumentDirectory extends CakeTestModel { + +/** + * name property + * + * @var string 'DocumentDirectory' + * @access public + */ + var $name = 'DocumentDirectory'; +} + +/** + * PrimaryModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class PrimaryModel extends CakeTestModel { + +/** + * name property + * + * @var string 'PrimaryModel' + * @access public + */ + var $name = 'PrimaryModel'; +} + +/** + * SecondaryModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class SecondaryModel extends CakeTestModel { + +/** + * name property + * + * @var string 'SecondaryModel' + * @access public + */ + var $name = 'SecondaryModel'; +} + +/** + * JoinA class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class JoinA extends CakeTestModel { + +/** + * name property + * + * @var string 'JoinA' + * @access public + */ + var $name = 'JoinA'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('JoinB', 'JoinC'); +} + +/** + * JoinB class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class JoinB extends CakeTestModel { + +/** + * name property + * + * @var string 'JoinB' + * @access public + */ + var $name = 'JoinB'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('JoinA'); +} + +/** + * JoinC class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class JoinC extends CakeTestModel { + +/** + * name property + * + * @var string 'JoinC' + * @access public + */ + var $name = 'JoinC'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('JoinA'); +} + +/** + * ThePaper class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ThePaper extends CakeTestModel { + +/** + * name property + * + * @var string 'ThePaper' + * @access public + */ + var $name = 'ThePaper'; + +/** + * useTable property + * + * @var string 'apples' + * @access public + */ + var $useTable = 'apples'; + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('Itself' => array('className' => 'ThePaper', 'foreignKey' => 'apple_id')); + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Monkey' => array('joinTable' => 'the_paper_monkies', 'order' => 'id')); +} + +/** + * Monkey class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Monkey extends CakeTestModel { + +/** + * name property + * + * @var string 'Monkey' + * @access public + */ + var $name = 'Monkey'; + +/** + * useTable property + * + * @var string 'devices' + * @access public + */ + var $useTable = 'devices'; +} + +/** + * AssociationTest1 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class AssociationTest1 extends CakeTestModel { + +/** + * useTable property + * + * @var string 'join_as' + * @access public + */ + var $useTable = 'join_as'; + +/** + * name property + * + * @var string 'AssociationTest1' + * @access public + */ + var $name = 'AssociationTest1'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('AssociationTest2' => array( + 'unique' => false, 'joinTable' => 'join_as_join_bs', 'foreignKey' => false + )); +} + +/** + * AssociationTest2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class AssociationTest2 extends CakeTestModel { + +/** + * useTable property + * + * @var string 'join_bs' + * @access public + */ + var $useTable = 'join_bs'; + +/** + * name property + * + * @var string 'AssociationTest2' + * @access public + */ + var $name = 'AssociationTest2'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('AssociationTest1' => array( + 'unique' => false, 'joinTable' => 'join_as_join_bs' + )); +} + +/** + * Callback class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Callback extends CakeTestModel { + +} +/** + * CallbackPostTestModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class CallbackPostTestModel extends CakeTestModel { + var $useTable = 'posts'; +/** + * variable to control return of beforeValidate + * + * @var string + */ + var $beforeValidateReturn = true; +/** + * variable to control return of beforeSave + * + * @var string + */ + var $beforeSaveReturn = true; +/** + * variable to control return of beforeDelete + * + * @var string + */ + var $beforeDeleteReturn = true; +/** + * beforeSave callback + * + * @return void + */ + function beforeSave($options) { + return $this->beforeSaveReturn; + } +/** + * beforeValidate callback + * + * @return void + */ + function beforeValidate($options) { + return $this->beforeValidateReturn; + } +/** + * beforeDelete callback + * + * @return void + */ + function beforeDelete($cascade = true) { + return $this->beforeDeleteReturn; + } +} + +/** + * Uuid class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Uuid extends CakeTestModel { + +/** + * name property + * + * @var string 'Uuid' + * @access public + */ + var $name = 'Uuid'; +} + +/** + * DataTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class DataTest extends CakeTestModel { + +/** + * name property + * + * @var string 'DataTest' + * @access public + */ + var $name = 'DataTest'; +} + +/** + * TheVoid class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TheVoid extends CakeTestModel { + +/** + * name property + * + * @var string 'TheVoid' + * @access public + */ + var $name = 'TheVoid'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; +} + +/** + * ValidationTest1 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ValidationTest1 extends CakeTestModel { + +/** + * name property + * + * @var string 'ValidationTest' + * @access public + */ + var $name = 'ValidationTest1'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema property + * + * @var array + * @access protected + */ + var $_schema = array(); + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array( + 'title' => 'notEmpty', + 'published' => 'customValidationMethod', + 'body' => array( + 'notEmpty', + '/^.{5,}$/s' => 'no matchy', + '/^[0-9A-Za-z \\.]{1,}$/s' + ) + ); + +/** + * customValidationMethod method + * + * @param mixed $data + * @access public + * @return void + */ + function customValidationMethod($data) { + return $data === 1; + } + +/** + * Custom validator with parameters + default values + * + * @access public + * @return array + */ + function customValidatorWithParams($data, $validator, $or = true, $ignore_on_same = 'id') { + $this->validatorParams = get_defined_vars(); + unset($this->validatorParams['this']); + return true; + } + +/** + * Custom validator with messaage + * + * @access public + * @return array + */ + function customValidatorWithMessage($data) { + return 'This field will *never* validate! Muhahaha!'; + } +/** + * Test validation with many parameters + * + * @return void + */ + function customValidatorWithSixParams($data, $one = 1, $two = 2, $three = 3, $four = 4, $five = 5, $six = 6) { + $this->validatorParams = get_defined_vars(); + unset($this->validatorParams['this']); + return true; + } +} + +/** + * ValidationTest2 class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ValidationTest2 extends CakeTestModel { + +/** + * name property + * + * @var string 'ValidationTest2' + * @access public + */ + var $name = 'ValidationTest2'; + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array( + 'title' => 'notEmpty', + 'published' => 'customValidationMethod', + 'body' => array( + 'notEmpty', + '/^.{5,}$/s' => 'no matchy', + '/^[0-9A-Za-z \\.]{1,}$/s' + ) + ); + +/** + * customValidationMethod method + * + * @param mixed $data + * @access public + * @return void + */ + function customValidationMethod($data) { + return $data === 1; + } + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + return array(); + } +} + +/** + * Person class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Person extends CakeTestModel { + +/** + * name property + * + * @var string 'Person' + * @access public + */ + var $name = 'Person'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'Mother' => array( + 'className' => 'Person', + 'foreignKey' => 'mother_id'), + 'Father' => array( + 'className' => 'Person', + 'foreignKey' => 'father_id')); +} + +/** + * UnderscoreField class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class UnderscoreField extends CakeTestModel { + +/** + * name property + * + * @var string 'UnderscoreField' + * @access public + */ + var $name = 'UnderscoreField'; +} + +/** + * Product class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Product extends CakeTestModel { + +/** + * name property + * + * @var string 'Product' + * @access public + */ + var $name = 'Product'; +} + +/** + * Story class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Story extends CakeTestModel { + +/** + * name property + * + * @var string 'Story' + * @access public + */ + var $name = 'Story'; + +/** + * primaryKey property + * + * @var string 'story' + * @access public + */ + var $primaryKey = 'story'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Tag' => array('foreignKey' => 'story')); + +/** + * validate property + * + * @var array + * @access public + */ + var $validate = array('title' => 'notEmpty'); +} + +/** + * Cd class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Cd extends CakeTestModel { + +/** + * name property + * + * @var string 'Cd' + * @access public + */ + var $name = 'Cd'; + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('OverallFavorite' => array('foreignKey' => 'model_id', 'dependent' => true, 'conditions' => array('model_type' => 'Cd'))); +} + +/** + * Book class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Book extends CakeTestModel { + +/** + * name property + * + * @var string 'Book' + * @access public + */ + var $name = 'Book'; + +/** + * hasOne property + * + * @var array + * @access public + */ + var $hasOne = array('OverallFavorite' => array('foreignKey' => 'model_id', 'dependent' => true, 'conditions' => 'OverallFavorite.model_type = \'Book\'')); +} + +/** + * OverallFavorite class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class OverallFavorite extends CakeTestModel { + +/** + * name property + * + * @var string 'OverallFavorite' + * @access public + */ + var $name = 'OverallFavorite'; +} + +/** + * MyUser class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class MyUser extends CakeTestModel { + +/** + * name property + * + * @var string 'MyUser' + * @access public + */ + var $name = 'MyUser'; + +/** + * undocumented variable + * + * @var string + * @access public + */ + var $hasAndBelongsToMany = array('MyCategory'); +} + +/** + * MyCategory class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class MyCategory extends CakeTestModel { + +/** + * name property + * + * @var string 'MyCategory' + * @access public + */ + var $name = 'MyCategory'; + +/** + * undocumented variable + * + * @var string + * @access public + */ + var $hasAndBelongsToMany = array('MyProduct', 'MyUser'); +} + +/** + * MyProduct class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class MyProduct extends CakeTestModel { + +/** + * name property + * + * @var string 'MyProduct' + * @access public + */ + var $name = 'MyProduct'; + +/** + * undocumented variable + * + * @var string + * @access public + */ + var $hasAndBelongsToMany = array('MyCategory'); +} + +/** + * MyCategoriesMyUser class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class MyCategoriesMyUser extends CakeTestModel { + +/** + * name property + * + * @var string 'MyCategoriesMyUser' + * @access public + */ + var $name = 'MyCategoriesMyUser'; +} + +/** + * MyCategoriesMyProduct class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class MyCategoriesMyProduct extends CakeTestModel { + +/** + * name property + * + * @var string 'MyCategoriesMyProduct' + * @access public + */ + var $name = 'MyCategoriesMyProduct'; +} + +/** + * I18nModel class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class I18nModel extends CakeTestModel { + +/** + * name property + * + * @var string 'I18nModel' + * @access public + */ + var $name = 'I18nModel'; + +/** + * useTable property + * + * @var string 'i18n' + * @access public + */ + var $useTable = 'i18n'; + +/** + * displayField property + * + * @var string 'field' + * @access public + */ + var $displayField = 'field'; +} + +/** + * NumberTree class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class NumberTree extends CakeTestModel { + +/** + * name property + * + * @var string 'NumberTree' + * @access public + */ + var $name = 'NumberTree'; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Tree'); + +/** + * initialize method + * + * @param int $levelLimit + * @param int $childLimit + * @param mixed $currentLevel + * @param mixed $parent_id + * @param string $prefix + * @param bool $hierachial + * @access public + * @return void + */ + function initialize($levelLimit = 3, $childLimit = 3, $currentLevel = null, $parent_id = null, $prefix = '1', $hierachial = true) { + if (!$parent_id) { + $db =& ConnectionManager::getDataSource($this->useDbConfig); + $db->truncate($this->table); + $this->save(array($this->name => array('name' => '1. Root'))); + $this->initialize($levelLimit, $childLimit, 1, $this->id, '1', $hierachial); + $this->create(array()); + } + + if (!$currentLevel || $currentLevel > $levelLimit) { + return; + } + + for ($i = 1; $i <= $childLimit; $i++) { + $name = $prefix . '.' . $i; + $data = array($this->name => array('name' => $name)); + $this->create($data); + + if ($hierachial) { + if ($this->name == 'UnconventionalTree') { + $data[$this->name]['join'] = $parent_id; + } else { + $data[$this->name]['parent_id'] = $parent_id; + } + } + $this->save($data); + $this->initialize($levelLimit, $childLimit, $currentLevel + 1, $this->id, $name, $hierachial); + } + } +} + +/** + * NumberTreeTwo class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class NumberTreeTwo extends NumberTree { + +/** + * name property + * + * @var string 'NumberTree' + * @access public + */ + var $name = 'NumberTreeTwo'; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array(); +} + +/** + * FlagTree class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class FlagTree extends NumberTree { + +/** + * name property + * + * @var string 'FlagTree' + * @access public + */ + var $name = 'FlagTree'; +} + +/** + * UnconventionalTree class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class UnconventionalTree extends NumberTree { + +/** + * name property + * + * @var string 'FlagTree' + * @access public + */ + var $name = 'UnconventionalTree'; + var $actsAs = array( + 'Tree' => array( + 'parent' => 'join', + 'left' => 'left', + 'right' => 'right' + ) + ); +} + +/** + * UuidTree class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class UuidTree extends NumberTree { + +/** + * name property + * + * @var string 'FlagTree' + * @access public + */ + var $name = 'UuidTree'; +} + +/** + * Campaign class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Campaign extends CakeTestModel { + +/** + * name property + * + * @var string 'Campaign' + * @access public + */ + var $name = 'Campaign'; + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array('Ad' => array('fields' => array('id','campaign_id','name'))); +} + +/** + * Ad class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Ad extends CakeTestModel { + +/** + * name property + * + * @var string 'Ad' + * @access public + */ + var $name = 'Ad'; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Tree'); + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('Campaign'); +} + +/** + * AfterTree class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class AfterTree extends NumberTree { + +/** + * name property + * + * @var string 'AfterTree' + * @access public + */ + var $name = 'AfterTree'; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Tree'); + + function afterSave($created) { + if ($created && isset($this->data['AfterTree'])) { + $this->data['AfterTree']['name'] = 'Six and One Half Changed in AfterTree::afterSave() but not in database'; + } + } +} + +/** + * Nonconformant Content class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Content extends CakeTestModel { + +/** + * name property + * + * @var string 'Content' + * @access public + */ + var $name = 'Content'; + +/** + * useTable property + * + * @var string 'Content' + * @access public + */ + var $useTable = 'Content'; + +/** + * primaryKey property + * + * @var string 'iContentId' + * @access public + */ + var $primaryKey = 'iContentId'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Account' => array('className' => 'Account', 'with' => 'ContentAccount', 'joinTable' => 'ContentAccounts', 'foreignKey' => 'iContentId', 'associationForeignKey', 'iAccountId')); +} + +/** + * Nonconformant Account class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Account extends CakeTestModel { + +/** + * name property + * + * @var string 'Account' + * @access public + */ + var $name = 'Account'; + +/** + * useTable property + * + * @var string 'Account' + * @access public + */ + var $useTable = 'Accounts'; + +/** + * primaryKey property + * + * @var string 'iAccountId' + * @access public + */ + var $primaryKey = 'iAccountId'; +} + +/** + * Nonconformant ContentAccount class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class ContentAccount extends CakeTestModel { + +/** + * name property + * + * @var string 'Account' + * @access public + */ + var $name = 'ContentAccount'; + +/** + * useTable property + * + * @var string 'Account' + * @access public + */ + var $useTable = 'ContentAccounts'; + +/** + * primaryKey property + * + * @var string 'iAccountId' + * @access public + */ + var $primaryKey = 'iContentAccountsId'; +} + +/** + * FilmFile class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class FilmFile extends CakeTestModel { + var $name = 'FilmFile'; +} + +/** + * Basket test model + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Basket extends CakeTestModel { + var $name = 'Basket'; + + var $belongsTo = array( + 'FilmFile' => array( + 'className' => 'FilmFile', + 'foreignKey' => 'object_id', + 'conditions' => "Basket.type = 'file'", + 'fields' => '', + 'order' => '' + ) + ); +} + +/** + * TestPluginArticle class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TestPluginArticle extends CakeTestModel { + +/** + * name property + * + * @var string 'TestPluginArticle' + * @access public + */ + var $name = 'TestPluginArticle'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('User'); + +/** + * hasMany property + * + * @var array + * @access public + */ + var $hasMany = array( + 'TestPluginComment' => array( + 'className' => 'TestPlugin.TestPluginComment', + 'foreignKey' => 'article_id', + 'dependent' => true + ) + ); +} + +/** + * TestPluginComment class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TestPluginComment extends CakeTestModel { + +/** + * name property + * + * @var string 'TestPluginComment' + * @access public + */ + var $name = 'TestPluginComment'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array( + 'TestPluginArticle' => array( + 'className' => 'TestPlugin.TestPluginArticle', + 'foreignKey' => 'article_id', + ), + 'User' + ); +} + +/** + * Uuidportfolio class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Uuidportfolio extends CakeTestModel { + +/** + * name property + * + * @var string 'Uuidportfolio' + * @access public + */ + var $name = 'Uuidportfolio'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Uuiditem'); +} + +/** + * Uuiditem class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class Uuiditem extends CakeTestModel { + +/** + * name property + * + * @var string 'Item' + * @access public + */ + var $name = 'Uuiditem'; + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('Uuidportfolio' => array('with' => 'UuiditemsUuidportfolioNumericid')); + +} + +/** + * UuiditemsPortfolio class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class UuiditemsUuidportfolio extends CakeTestModel { + +/** + * name property + * + * @var string 'ItemsPortfolio' + * @access public + */ + var $name = 'UuiditemsUuidportfolio'; +} + +/** + * UuiditemsPortfolioNumericid class + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class UuiditemsUuidportfolioNumericid extends CakeTestModel { + +/** + * name property + * + * @var string + * @access public + */ + var $name = 'UuiditemsUuidportfolioNumericid'; +} + +/** + * TranslateTestModel class. + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TranslateTestModel extends CakeTestModel { + +/** + * name property + * + * @var string 'TranslateTestModel' + * @access public + */ + var $name = 'TranslateTestModel'; + +/** + * useTable property + * + * @var string 'i18n' + * @access public + */ + var $useTable = 'i18n'; + +/** + * displayField property + * + * @var string 'field' + * @access public + */ + var $displayField = 'field'; +} + +/** + * TranslateTestModel class. + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TranslateWithPrefix extends CakeTestModel { +/** + * name property + * + * @var string 'TranslateTestModel' + * @access public + */ + var $name = 'TranslateWithPrefix'; +/** + * tablePrefix property + * + * @var string 'i18n' + * @access public + */ + var $tablePrefix = 'i18n_'; +/** + * displayField property + * + * @var string 'field' + * @access public + */ + var $displayField = 'field'; +} +/** + * TranslatedItem class. + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TranslatedItem extends CakeTestModel { + +/** + * name property + * + * @var string 'TranslatedItem' + * @access public + */ + var $name = 'TranslatedItem'; + +/** + * cacheQueries property + * + * @var bool false + * @access public + */ + var $cacheQueries = false; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Translate' => array('content', 'title')); + +/** + * translateModel property + * + * @var string 'TranslateTestModel' + * @access public + */ + var $translateModel = 'TranslateTestModel'; +} + +/** + * TranslatedItem class. + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TranslatedItem2 extends CakeTestModel { +/** + * name property + * + * @var string 'TranslatedItem' + * @access public + */ + var $name = 'TranslatedItem'; +/** + * cacheQueries property + * + * @var bool false + * @access public + */ + var $cacheQueries = false; +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Translate' => array('content', 'title')); +/** + * translateModel property + * + * @var string 'TranslateTestModel' + * @access public + */ + var $translateModel = 'TranslateWithPrefix'; +} +/** + * TranslatedItemWithTable class. + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TranslatedItemWithTable extends CakeTestModel { + +/** + * name property + * + * @var string 'TranslatedItemWithTable' + * @access public + */ + var $name = 'TranslatedItemWithTable'; + +/** + * useTable property + * + * @var string 'translated_items' + * @access public + */ + var $useTable = 'translated_items'; + +/** + * cacheQueries property + * + * @var bool false + * @access public + */ + var $cacheQueries = false; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Translate' => array('content', 'title')); + +/** + * translateModel property + * + * @var string 'TranslateTestModel' + * @access public + */ + var $translateModel = 'TranslateTestModel'; + +/** + * translateTable property + * + * @var string 'another_i18n' + * @access public + */ + var $translateTable = 'another_i18n'; +} + +/** + * TranslateArticleModel class. + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TranslateArticleModel extends CakeTestModel { + +/** + * name property + * + * @var string 'TranslateArticleModel' + * @access public + */ + var $name = 'TranslateArticleModel'; + +/** + * useTable property + * + * @var string 'article_i18n' + * @access public + */ + var $useTable = 'article_i18n'; + +/** + * displayField property + * + * @var string 'field' + * @access public + */ + var $displayField = 'field'; +} + +/** + * TranslatedArticle class. + * + * @package cake + * @subpackage cake.tests.cases.libs.model + */ +class TranslatedArticle extends CakeTestModel { + +/** + * name property + * + * @var string 'TranslatedArticle' + * @access public + */ + var $name = 'TranslatedArticle'; + +/** + * cacheQueries property + * + * @var bool false + * @access public + */ + var $cacheQueries = false; + +/** + * actsAs property + * + * @var array + * @access public + */ + var $actsAs = array('Translate' => array('title', 'body')); + +/** + * translateModel property + * + * @var string 'TranslateArticleModel' + * @access public + */ + var $translateModel = 'TranslateArticleModel'; + +/** + * belongsTo property + * + * @var array + * @access public + */ + var $belongsTo = array('User'); +} + +class CounterCacheUser extends CakeTestModel { + var $name = 'CounterCacheUser'; + var $alias = 'User'; + + var $hasMany = array('Post' => array( + 'className' => 'CounterCachePost', + 'foreignKey' => 'user_id' + )); +} + +class CounterCachePost extends CakeTestModel { + var $name = 'CounterCachePost'; + var $alias = 'Post'; + + var $belongsTo = array('User' => array( + 'className' => 'CounterCacheUser', + 'foreignKey' => 'user_id', + 'counterCache' => true + )); +} + +class CounterCacheUserNonstandardPrimaryKey extends CakeTestModel { + var $name = 'CounterCacheUserNonstandardPrimaryKey'; + var $alias = 'User'; + var $primaryKey = 'uid'; + + var $hasMany = array('Post' => array( + 'className' => 'CounterCachePostNonstandardPrimaryKey', + 'foreignKey' => 'uid' + )); +} + +class CounterCachePostNonstandardPrimaryKey extends CakeTestModel { + var $name = 'CounterCachePostNonstandardPrimaryKey'; + var $alias = 'Post'; + var $primaryKey = 'pid'; + + var $belongsTo = array('User' => array( + 'className' => 'CounterCacheUserNonstandardPrimaryKey', + 'foreignKey' => 'uid', + 'counterCache' => true + )); +} + +class ArticleB extends CakeTestModel { + var $name = 'ArticleB'; + var $useTable = 'articles'; + var $hasAndBelongsToMany = array( + 'TagB' => array( + 'className' => 'TagB', + 'joinTable' => 'articles_tags', + 'foreignKey' => 'article_id', + 'associationForeignKey' => 'tag_id' + ) + ); +} + +class TagB extends CakeTestModel { + var $name = 'TagB'; + var $useTable = 'tags'; + var $hasAndBelongsToMany = array( + 'ArticleB' => array( + 'className' => 'ArticleB', + 'joinTable' => 'articles_tags', + 'foreignKey' => 'tag_id', + 'associationForeignKey' => 'article_id' + ) + ); +} + +class Fruit extends CakeTestModel { + var $name = 'Fruit'; + var $hasAndBelongsToMany = array( + 'UuidTag' => array( + 'className' => 'UuidTag', + 'joinTable' => 'fruits_uuid_tags', + 'foreignKey' => 'fruit_id', + 'associationForeignKey' => 'uuid_tag_id', + 'with' => 'FruitsUuidTag' + ) + ); +} + +class FruitsUuidTag extends CakeTestModel { + var $name = 'FruitsUuidTag'; + var $primaryKey = false; + var $belongsTo = array( + 'UuidTag' => array( + 'className' => 'UuidTag', + 'foreignKey' => 'uuid_tag_id', + ), + 'Fruit' => array( + 'className' => 'Fruit', + 'foreignKey' => 'fruit_id', + ) + ); +} + +class UuidTag extends CakeTestModel { + var $name = 'UuidTag'; + var $hasAndBelongsToMany = array( + 'Fruit' => array( + 'className' => 'Fruit', + 'joinTable' => 'fruits_uuid_tags', + 'foreign_key' => 'uuid_tag_id', + 'associationForeignKey' => 'fruit_id', + 'with' => 'FruitsUuidTag' + ) + ); +} + +class FruitNoWith extends CakeTestModel { + var $name = 'Fruit'; + var $useTable = 'fruits'; + var $hasAndBelongsToMany = array( + 'UuidTag' => array( + 'className' => 'UuidTagNoWith', + 'joinTable' => 'fruits_uuid_tags', + 'foreignKey' => 'fruit_id', + 'associationForeignKey' => 'uuid_tag_id', + ) + ); +} + +class UuidTagNoWith extends CakeTestModel { + var $name = 'UuidTag'; + var $useTable = 'uuid_tags'; + var $hasAndBelongsToMany = array( + 'Fruit' => array( + 'className' => 'FruitNoWith', + 'joinTable' => 'fruits_uuid_tags', + 'foreign_key' => 'uuid_tag_id', + 'associationForeignKey' => 'fruit_id', + ) + ); +} + +class ProductUpdateAll extends CakeTestModel { + var $name = 'ProductUpdateAll'; + var $useTable = 'product_update_all'; +} + +class GroupUpdateAll extends CakeTestModel { + var $name = 'GroupUpdateAll'; + var $useTable = 'group_update_all'; +} + +class TransactionTestModel extends CakeTestModel { + var $name = 'TransactionTestModel'; + var $useTable = 'samples'; + + function afterSave($created) { + $data = array( + array('apple_id' => 1, 'name' => 'sample6'), + ); + $this->saveAll($data, array('atomic' => true, 'callbacks' => false)); + } +} \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/multibyte.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/multibyte.test.php new file mode 100644 index 000000000..47149c708 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/multibyte.test.php @@ -0,0 +1,9343 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.6833 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Multibyte'); + +/** + * MultibyteTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class MultibyteTest extends CakeTestCase { + +/** + * testUtf8 method + * + * @access public + * @return void + */ + function testUtf8() { + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $result = Multibyte::utf8($string); + $expected = array(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126); + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $result = Multibyte::utf8($string); + $expected = array(161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, + 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200); + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $result = Multibyte::utf8($string); + $expected = array(201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, + 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, + 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, + 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300); + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $result = Multibyte::utf8($string); + $expected = array(301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, + 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, + 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, + 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400); + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $result = Multibyte::utf8($string); + $expected = array(401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, + 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, + 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, + 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, + 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500); + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $result = Multibyte::utf8($string); + $expected = array(601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, + 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, + 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, + 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, + 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700); + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $result = Multibyte::utf8($string); + $expected = array(1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, + 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051); + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $result = Multibyte::utf8($string); + $expected = array(1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, + 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, + 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100); + $this->assertEqual($result, $expected); + + $string = 'Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿'; + $result = Multibyte::utf8($string); + $expected = array(1401, 1402, 1403, 1404, 1405, 1406, 1407); + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $result = Multibyte::utf8($string); + $expected = array(1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615); + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $result = Multibyte::utf8($string); + $expected = array(10032, 10033, 10034, 10035, 10036, 10037, 10038, 10039, 10040, 10041, 10042, 10043, 10044, + 10045, 10046, 10047, 10048, 10049, 10050, 10051, 10052, 10053, 10054, 10055, 10056, 10057, + 10058, 10059, 10060, 10061, 10062, 10063, 10064, 10065, 10066, 10067, 10068, 10069, 10070, + 10071, 10072, 10073, 10074, 10075, 10076, 10077, 10078); + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $result = Multibyte::utf8($string); + $expected = array(11904, 11905, 11906, 11907, 11908, 11909, 11910, 11911, 11912, 11913, 11914, 11915, 11916, 11917, 11918, 11919, + 11920, 11921, 11922, 11923, 11924, 11925, 11926, 11927, 11928, 11929, 11931, 11932, 11933, 11934, 11935, 11936, + 11937, 11938, 11939, 11940, 11941, 11942, 11943, 11944, 11945, 11946, 11947, 11948, 11949, 11950, 11951, 11952, + 11953, 11954, 11955, 11956, 11957, 11958, 11959, 11960, 11961, 11962, 11963, 11964, 11965, 11966, 11967, 11968, + 11969, 11970, 11971, 11972, 11973, 11974, 11975, 11976, 11977, 11978, 11979, 11980, 11981, 11982, 11983, 11984, + 11985, 11986, 11987, 11988, 11989, 11990, 11991, 11992, 11993, 11994, 11995, 11996, 11997, 11998, 11999, 12000); + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $result = Multibyte::utf8($string); + $expected = array(12101, 12102, 12103, 12104, 12105, 12106, 12107, 12108, 12109, 12110, 12111, 12112, 12113, 12114, 12115, 12116, + 12117, 12118, 12119, 12120, 12121, 12122, 12123, 12124, 12125, 12126, 12127, 12128, 12129, 12130, 12131, 12132, + 12133, 12134, 12135, 12136, 12137, 12138, 12139, 12140, 12141, 12142, 12143, 12144, 12145, 12146, 12147, 12148, + 12149, 12150, 12151, 12152, 12153, 12154, 12155, 12156, 12157, 12158, 12159); + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $result = Multibyte::utf8($string); + $expected = array(45601, 45602, 45603, 45604, 45605, 45606, 45607, 45608, 45609, 45610, 45611, 45612, 45613, 45614, 45615, 45616, + 45617, 45618, 45619, 45620, 45621, 45622, 45623, 45624, 45625, 45626, 45627, 45628, 45629, 45630, 45631, 45632, + 45633, 45634, 45635, 45636, 45637, 45638, 45639, 45640, 45641, 45642, 45643, 45644, 45645, 45646, 45647, 45648, + 45649, 45650, 45651, 45652, 45653, 45654, 45655, 45656, 45657, 45658, 45659, 45660, 45661, 45662, 45663, 45664, + 45665, 45666, 45667, 45668, 45669, 45670, 45671, 45672, 45673, 45674, 45675, 45676, 45677, 45678, 45679, 45680, + 45681, 45682, 45683, 45684, 45685, 45686, 45687, 45688, 45689, 45690, 45691, 45692, 45693, 45694, 45695, 45696, + 45697, 45698, 45699, 45700); + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $result = Multibyte::utf8($string); + $expected = array(65136, 65137, 65138, 65139, 65140, 65141, 65142, 65143, 65144, 65145, 65146, 65147, 65148, 65149, 65150, 65151, + 65152, 65153, 65154, 65155, 65156, 65157, 65158, 65159, 65160, 65161, 65162, 65163, 65164, 65165, 65166, 65167, + 65168, 65169, 65170, 65171, 65172, 65173, 65174, 65175, 65176, 65177, 65178, 65179, 65180, 65181, 65182, 65183, + 65184, 65185, 65186, 65187, 65188, 65189, 65190, 65191, 65192, 65193, 65194, 65195, 65196, 65197, 65198, 65199, + 65200); + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $result = Multibyte::utf8($string); + $expected = array(65201, 65202, 65203, 65204, 65205, 65206, 65207, 65208, 65209, 65210, 65211, 65212, 65213, 65214, 65215, 65216, + 65217, 65218, 65219, 65220, 65221, 65222, 65223, 65224, 65225, 65226, 65227, 65228, 65229, 65230, 65231, 65232, + 65233, 65234, 65235, 65236, 65237, 65238, 65239, 65240, 65241, 65242, 65243, 65244, 65245, 65246, 65247, 65248, + 65249, 65250, 65251, 65252, 65253, 65254, 65255, 65256, 65257, 65258, 65259, 65260, 65261, 65262, 65263, 65264, + 65265, 65266, 65267, 65268, 65269, 65270, 65271, 65272, 65273, 65274, 65275, 65276); + $this->assertEqual($result, $expected); + + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $result = Multibyte::utf8($string); + $expected = array(65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, + 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370); + $this->assertEqual($result, $expected); + + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $result = Multibyte::utf8($string); + $expected = array(65377, 65378, 65379, 65380, 65381, 65382, 65383, 65384, 65385, 65386, 65387, 65388, 65389, 65390, 65391, 65392, + 65393, 65394, 65395, 65396, 65397, 65398, 65399, 65400); + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $result = Multibyte::utf8($string); + $expected = array(65401, 65402, 65403, 65404, 65405, 65406, 65407, 65408, 65409, 65410, 65411, 65412, 65413, 65414, 65415, 65416, + 65417, 65418, 65419, 65420, 65421, 65422, 65423, 65424, 65425, 65426, 65427, 65428, 65429, 65430, 65431, 65432, + 65433, 65434, 65435, 65436, 65437, 65438); + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = Multibyte::utf8($string); + $expected = array(292, 275, 314, 316, 335, 44, 32, 372, 337, 345, 316, 271, 33); + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $result = Multibyte::utf8($string); + $expected = array(72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33); + $this->assertEqual($result, $expected); + + $string = '¨'; + $result = Multibyte::utf8($string); + $expected = array(168); + $this->assertEqual($result, $expected); + + $string = '¿'; + $result = Multibyte::utf8($string); + $expected = array(191); + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $result = Multibyte::utf8($string); + $expected = array(269, 105, 110, 105); + $this->assertEqual($result, $expected); + + $string = 'moći'; + $result = Multibyte::utf8($string); + $expected = array(109, 111, 263, 105); + $this->assertEqual($result, $expected); + + $string = 'državni'; + $result = Multibyte::utf8($string); + $expected = array(100, 114, 382, 97, 118, 110, 105); + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $result = Multibyte::utf8($string); + $expected = array(25226, 30334, 24230, 35774, 20026, 39318, 39029); + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = Multibyte::utf8($string); + $expected = array(19968, 20108, 19977, 21608, 27704, 40845); + $this->assertEqual($result, $expected); + + $string = 'ԀԂԄԆԈԊԌԎÔÔ’'; + $result = Multibyte::utf8($string); + $expected = array(1280, 1282, 1284, 1286, 1288, 1290, 1292, 1294, 1296, 1298); + $this->assertEqual($result, $expected); + + $string = 'ÔÔƒÔ…Ô‡Ô‰Ô‹ÔÔÔÔ’'; + $result = Multibyte::utf8($string); + $expected = array(1281, 1283, 1285, 1287, 1289, 1291, 1293, 1295, 1296, 1298); + $this->assertEqual($result, $expected); + + $string = 'Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€ÕÕ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕÕŽÕÕÕ‘Õ’Õ“Õ”Õ•Õ–Ö‡'; + $result = Multibyte::utf8($string); + $expected = array(1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, + 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, + 1365, 1366, 1415); + $this->assertEqual($result, $expected); + + $string = 'Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€ÖÖ‚ÖƒÖ„Ö…Ö†Ö‡'; + $result = Multibyte::utf8($string); + $expected = array(1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, + 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, + 1413, 1414, 1415); + $this->assertEqual($result, $expected); + + $string = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $result = Multibyte::utf8($string); + $expected = array(4256, 4257, 4258, 4259, 4260, 4261, 4262, 4263, 4264, 4265, 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, + 4274, 4275, 4276, 4277, 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, 4287, 4288, 4289, 4290, 4291, + 4292, 4293); + $this->assertEqual($result, $expected); + + $string = 'ḀḂḄḆḈḊḌḎá¸á¸’ḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎá¹á¹’ṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎáºáº’ẔẖẗẘẙẚẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎá»á»’ỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸ'; + $result = Multibyte::utf8($string); + $expected = array(7680, 7682, 7684, 7686, 7688, 7690, 7692, 7694, 7696, 7698, 7700, 7702, 7704, 7706, 7708, 7710, 7712, 7714, + 7716, 7718, 7720, 7722, 7724, 7726, 7728, 7730, 7732, 7734, 7736, 7738, 7740, 7742, 7744, 7746, 7748, 7750, + 7752, 7754, 7756, 7758, 7760, 7762, 7764, 7766, 7768, 7770, 7772, 7774, 7776, 7778, 7780, 7782, 7784, 7786, + 7788, 7790, 7792, 7794, 7796, 7798, 7800, 7802, 7804, 7806, 7808, 7810, 7812, 7814, 7816, 7818, 7820, 7822, + 7824, 7826, 7828, 7830, 7831, 7832, 7833, 7834, 7840, 7842, 7844, 7846, 7848, 7850, 7852, 7854, 7856, + 7858, 7860, 7862, 7864, 7866, 7868, 7870, 7872, 7874, 7876, 7878, 7880, 7882, 7884, 7886, 7888, 7890, 7892, + 7894, 7896, 7898, 7900, 7902, 7904, 7906, 7908, 7910, 7912, 7914, 7916, 7918, 7920, 7922, 7924, 7926, 7928); + $this->assertEqual($result, $expected); + + $string = 'á¸á¸ƒá¸…ḇḉḋá¸á¸á¸‘ḓḕḗḙḛá¸á¸Ÿá¸¡á¸£á¸¥á¸§á¸©á¸«á¸­á¸¯á¸±á¸³á¸µá¸·á¸¹á¸»á¸½á¸¿á¹á¹ƒá¹…ṇṉṋá¹á¹á¹‘ṓṕṗṙṛá¹á¹Ÿá¹¡á¹£á¹¥á¹§á¹©á¹«á¹­á¹¯á¹±á¹³á¹µá¹·á¹¹á¹»á¹½á¹¿áºáºƒáº…ẇẉẋáºáºáº‘ẓẕẖẗẘẙẚạảấầẩẫậắằẳẵặẹẻẽếá»á»ƒá»…ệỉịá»á»á»‘ồổỗộớá»á»Ÿá»¡á»£á»¥á»§á»©á»«á»­á»¯á»±á»³á»µá»·á»¹'; + $result = Multibyte::utf8($string); + $expected = array(7681, 7683, 7685, 7687, 7689, 7691, 7693, 7695, 7697, 7699, 7701, 7703, 7705, 7707, 7709, 7711, 7713, 7715, + 7717, 7719, 7721, 7723, 7725, 7727, 7729, 7731, 7733, 7735, 7737, 7739, 7741, 7743, 7745, 7747, 7749, 7751, + 7753, 7755, 7757, 7759, 7761, 7763, 7765, 7767, 7769, 7771, 7773, 7775, 7777, 7779, 7781, 7783, 7785, 7787, + 7789, 7791, 7793, 7795, 7797, 7799, 7801, 7803, 7805, 7807, 7809, 7811, 7813, 7815, 7817, 7819, 7821, 7823, + 7825, 7827, 7829, 7830, 7831, 7832, 7833, 7834, 7841, 7843, 7845, 7847, 7849, 7851, 7853, 7855, 7857, 7859, + 7861, 7863, 7865, 7867, 7869, 7871, 7873, 7875, 7877, 7879, 7881, 7883, 7885, 7887, 7889, 7891, 7893, 7895, + 7897, 7899, 7901, 7903, 7905, 7907, 7909, 7911, 7913, 7915, 7917, 7919, 7921, 7923, 7925, 7927, 7929); + $this->assertEqual($result, $expected); + + $string = 'ΩKÅℲ'; + $result = Multibyte::utf8($string); + $expected = array(8486, 8490, 8491, 8498); + $this->assertEqual($result, $expected); + + $string = 'ωkåⅎ'; + $result = Multibyte::utf8($string); + $expected = array(969, 107, 229, 8526); + $this->assertEqual($result, $expected); + + $string = 'ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯↃ'; + $result = Multibyte::utf8($string); + $expected = array(8544, 8545, 8546, 8547, 8548, 8549, 8550, 8551, 8552, 8553, 8554, 8555, 8556, 8557, 8558, 8559, 8579); + $this->assertEqual($result, $expected); + + $string = 'ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↄ'; + $result = Multibyte::utf8($string); + $expected = array(8560, 8561, 8562, 8563, 8564, 8565, 8566, 8567, 8568, 8569, 8570, 8571, 8572, 8573, 8574, 8575, 8580); + $this->assertEqual($result, $expected); + + $string = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀâ“ⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌâ“â“Žâ“'; + $result = Multibyte::utf8($string); + $expected = array(9398, 9399, 9400, 9401, 9402, 9403, 9404, 9405, 9406, 9407, 9408, 9409, 9410, 9411, 9412, 9413, 9414, + 9415, 9416, 9417, 9418, 9419, 9420, 9421, 9422, 9423); + $this->assertEqual($result, $expected); + + $string = 'â“ⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜâ“ⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ'; + $result = Multibyte::utf8($string); + $expected = array(9424, 9425, 9426, 9427, 9428, 9429, 9430, 9431, 9432, 9433, 9434, 9435, 9436, 9437, 9438, 9439, 9440, 9441, + 9442, 9443, 9444, 9445, 9446, 9447, 9448, 9449); + $this->assertEqual($result, $expected); + + $string = 'â°€â°â°‚ⰃⰄⰅⰆⰇⰈⰉⰊⰋⰌâ°â°Žâ°â°â°‘ⰒⰓⰔⰕⰖⰗⰘⰙⰚⰛⰜâ°â°žâ°Ÿâ° â°¡â°¢â°£â°¤â°¥â°¦â°§â°¨â°©â°ªâ°«â°¬â°­â°®'; + $result = Multibyte::utf8($string); + $expected = array(11264, 11265, 11266, 11267, 11268, 11269, 11270, 11271, 11272, 11273, 11274, 11275, 11276, 11277, 11278, + 11279, 11280, 11281, 11282, 11283, 11284, 11285, 11286, 11287, 11288, 11289, 11290, 11291, 11292, 11293, + 11294, 11295, 11296, 11297, 11298, 11299, 11300, 11301, 11302, 11303, 11304, 11305, 11306, 11307, 11308, + 11309, 11310); + $this->assertEqual($result, $expected); + + $string = 'ⰰⰱⰲⰳⰴⰵⰶⰷⰸⰹⰺⰻⰼⰽⰾⰿⱀâ±â±‚ⱃⱄⱅⱆⱇⱈⱉⱊⱋⱌâ±â±Žâ±â±â±‘ⱒⱓⱔⱕⱖⱗⱘⱙⱚⱛⱜâ±â±ž'; + $result = Multibyte::utf8($string); + $expected = array(11312, 11313, 11314, 11315, 11316, 11317, 11318, 11319, 11320, 11321, 11322, 11323, 11324, 11325, 11326, 11327, + 11328, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338, 11339, 11340, 11341, 11342, 11343, + 11344, 11345, 11346, 11347, 11348, 11349, 11350, 11351, 11352, 11353, 11354, 11355, 11356, 11357, 11358); + $this->assertEqual($result, $expected); + + $string = 'ⲀⲂⲄⲆⲈⲊⲌⲎâ²â²’ⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⲲⲴⲶⲸⲺⲼⲾⳀⳂⳄⳆⳈⳊⳌⳎâ³â³’ⳔⳖⳘⳚⳜⳞⳠⳢ'; + $result = Multibyte::utf8($string); + $expected = array(11392, 11394, 11396, 11398, 11400, 11402, 11404, 11406, 11408, 11410, 11412, 11414, 11416, 11418, 11420, + 11422, 11424, 11426, 11428, 11430, 11432, 11434, 11436, 11438, 11440, 11442, 11444, 11446, 11448, 11450, + 11452, 11454, 11456, 11458, 11460, 11462, 11464, 11466, 11468, 11470, 11472, 11474, 11476, 11478, 11480, + 11482, 11484, 11486, 11488, 11490); + $this->assertEqual($result, $expected); + + $string = 'â²â²ƒâ²…ⲇⲉⲋâ²â²â²‘ⲓⲕⲗⲙⲛâ²â²Ÿâ²¡â²£â²¥â²§â²©â²«â²­â²¯â²±â²³â²µâ²·â²¹â²»â²½â²¿â³â³ƒâ³…ⳇⳉⳋâ³â³â³‘ⳓⳕⳗⳙⳛâ³â³Ÿâ³¡â³£'; + $result = Multibyte::utf8($string); + $expected = array(11393, 11395, 11397, 11399, 11401, 11403, 11405, 11407, 11409, 11411, 11413, 11415, 11417, 11419, 11421, 11423, + 11425, 11427, 11429, 11431, 11433, 11435, 11437, 11439, 11441, 11443, 11445, 11447, 11449, 11451, 11453, 11455, + 11457, 11459, 11461, 11463, 11465, 11467, 11469, 11471, 11473, 11475, 11477, 11479, 11481, 11483, 11485, 11487, + 11489, 11491); + $this->assertEqual($result, $expected); + + + $string = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $result = Multibyte::utf8($string); + $expected = array(64256, 64257, 64258, 64259, 64260, 64261, 64262, 64275, 64276, 64277, 64278, 64279); + $this->assertEqual($result, $expected); + } + +/** + * testAscii method + * + * @access public + * @return void + */ + function testAscii() { + $utf8 = array(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126); + $result = Multibyte::ascii($utf8); + + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $utf8 = array(161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, + 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200); + $result = Multibyte::ascii($utf8); + + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $utf8 = array(201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, + 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, + 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, + 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300); + $result = Multibyte::ascii($utf8); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $utf8 = array(301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, + 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, + 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, + 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, + 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, + 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, + 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, + 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, + 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, + 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, + 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, + 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, + 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051); + $expected = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, + 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, + 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100); + $expected = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(1401, 1402, 1403, 1404, 1405, 1406, 1407); + $expected = 'Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615); + $expected = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(10032, 10033, 10034, 10035, 10036, 10037, 10038, 10039, 10040, 10041, 10042, 10043, 10044, + 10045, 10046, 10047, 10048, 10049, 10050, 10051, 10052, 10053, 10054, 10055, 10056, 10057, + 10058, 10059, 10060, 10061, 10062, 10063, 10064, 10065, 10066, 10067, 10068, 10069, 10070, + 10071, 10072, 10073, 10074, 10075, 10076, 10077, 10078); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(11904, 11905, 11906, 11907, 11908, 11909, 11910, 11911, 11912, 11913, 11914, 11915, 11916, 11917, 11918, 11919, + 11920, 11921, 11922, 11923, 11924, 11925, 11926, 11927, 11928, 11929, 11931, 11932, 11933, 11934, 11935, 11936, + 11937, 11938, 11939, 11940, 11941, 11942, 11943, 11944, 11945, 11946, 11947, 11948, 11949, 11950, 11951, 11952, + 11953, 11954, 11955, 11956, 11957, 11958, 11959, 11960, 11961, 11962, 11963, 11964, 11965, 11966, 11967, 11968, + 11969, 11970, 11971, 11972, 11973, 11974, 11975, 11976, 11977, 11978, 11979, 11980, 11981, 11982, 11983, 11984, + 11985, 11986, 11987, 11988, 11989, 11990, 11991, 11992, 11993, 11994, 11995, 11996, 11997, 11998, 11999, 12000); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(12101, 12102, 12103, 12104, 12105, 12106, 12107, 12108, 12109, 12110, 12111, 12112, 12113, 12114, 12115, 12116, + 12117, 12118, 12119, 12120, 12121, 12122, 12123, 12124, 12125, 12126, 12127, 12128, 12129, 12130, 12131, 12132, + 12133, 12134, 12135, 12136, 12137, 12138, 12139, 12140, 12141, 12142, 12143, 12144, 12145, 12146, 12147, 12148, + 12149, 12150, 12151, 12152, 12153, 12154, 12155, 12156, 12157, 12158, 12159); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(45601, 45602, 45603, 45604, 45605, 45606, 45607, 45608, 45609, 45610, 45611, 45612, 45613, 45614, 45615, 45616, + 45617, 45618, 45619, 45620, 45621, 45622, 45623, 45624, 45625, 45626, 45627, 45628, 45629, 45630, 45631, 45632, + 45633, 45634, 45635, 45636, 45637, 45638, 45639, 45640, 45641, 45642, 45643, 45644, 45645, 45646, 45647, 45648, + 45649, 45650, 45651, 45652, 45653, 45654, 45655, 45656, 45657, 45658, 45659, 45660, 45661, 45662, 45663, 45664, + 45665, 45666, 45667, 45668, 45669, 45670, 45671, 45672, 45673, 45674, 45675, 45676, 45677, 45678, 45679, 45680, + 45681, 45682, 45683, 45684, 45685, 45686, 45687, 45688, 45689, 45690, 45691, 45692, 45693, 45694, 45695, 45696, + 45697, 45698, 45699, 45700); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(65136, 65137, 65138, 65139, 65140, 65141, 65142, 65143, 65144, 65145, 65146, 65147, 65148, 65149, 65150, 65151, + 65152, 65153, 65154, 65155, 65156, 65157, 65158, 65159, 65160, 65161, 65162, 65163, 65164, 65165, 65166, 65167, + 65168, 65169, 65170, 65171, 65172, 65173, 65174, 65175, 65176, 65177, 65178, 65179, 65180, 65181, 65182, 65183, + 65184, 65185, 65186, 65187, 65188, 65189, 65190, 65191, 65192, 65193, 65194, 65195, 65196, 65197, 65198, 65199, + 65200); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(65201, 65202, 65203, 65204, 65205, 65206, 65207, 65208, 65209, 65210, 65211, 65212, 65213, 65214, 65215, 65216, + 65217, 65218, 65219, 65220, 65221, 65222, 65223, 65224, 65225, 65226, 65227, 65228, 65229, 65230, 65231, 65232, + 65233, 65234, 65235, 65236, 65237, 65238, 65239, 65240, 65241, 65242, 65243, 65244, 65245, 65246, 65247, 65248, + 65249, 65250, 65251, 65252, 65253, 65254, 65255, 65256, 65257, 65258, 65259, 65260, 65261, 65262, 65263, 65264, + 65265, 65266, 65267, 65268, 65269, 65270, 65271, 65272, 65273, 65274, 65275, 65276); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, + 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370); + $expected = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(65377, 65378, 65379, 65380, 65381, 65382, 65383, 65384, 65385, 65386, 65387, 65388, 65389, 65390, 65391, 65392, + 65393, 65394, 65395, 65396, 65397, 65398, 65399, 65400); + $expected = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(65401, 65402, 65403, 65404, 65405, 65406, 65407, 65408, 65409, 65410, 65411, 65412, 65413, 65414, 65415, 65416, + 65417, 65418, 65419, 65420, 65421, 65422, 65423, 65424, 65425, 65426, 65427, 65428, 65429, 65430, 65431, 65432, + 65433, 65434, 65435, 65436, 65437, 65438); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(292, 275, 314, 316, 335, 44, 32, 372, 337, 345, 316, 271, 33); + $expected = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33); + $expected = 'Hello, World!'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(168); + $expected = '¨'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(191); + $expected = '¿'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(269, 105, 110, 105); + $expected = 'Äini'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(109, 111, 263, 105); + $expected = 'moći'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(100, 114, 382, 97, 118, 110, 105); + $expected = 'državni'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(25226, 30334, 24230, 35774, 20026, 39318, 39029); + $expected = '把百度设为首页'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(19968, 20108, 19977, 21608, 27704, 40845); + $expected = '一二三周永é¾'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(1280, 1282, 1284, 1286, 1288, 1290, 1292, 1294, 1296, 1298); + $expected = 'ԀԂԄԆԈԊԌԎÔÔ’'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(1281, 1283, 1285, 1287, 1289, 1291, 1293, 1295, 1296, 1298); + $expected = 'ÔÔƒÔ…Ô‡Ô‰Ô‹ÔÔÔÔ’'; + $result = Multibyte::ascii($utf8); + $this->assertEqual($result, $expected); + + $utf8 = array(1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, + 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, + 1366, 1415); + $result = Multibyte::ascii($utf8); + $expected = 'Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€ÕÕ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕÕŽÕÕÕ‘Õ’Õ“Õ”Õ•Õ–Ö‡'; + $this->assertEqual($result, $expected); + + $utf8 = array(1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, + 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, + 1413, 1414, 1415); + $result = Multibyte::ascii($utf8); + $expected = 'Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€ÖÖ‚ÖƒÖ„Ö…Ö†Ö‡'; + $this->assertEqual($result, $expected); + + $utf8 = array(4256, 4257, 4258, 4259, 4260, 4261, 4262, 4263, 4264, 4265, 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, 4274, + 4275, 4276, 4277, 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, 4287, 4288, 4289, 4290, 4291, 4292, 4293); + $result = Multibyte::ascii($utf8); + $expected = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $this->assertEqual($result, $expected); + + $utf8 = array(7680, 7682, 7684, 7686, 7688, 7690, 7692, 7694, 7696, 7698, 7700, 7702, 7704, 7706, 7708, 7710, 7712, 7714, + 7716, 7718, 7720, 7722, 7724, 7726, 7728, 7730, 7732, 7734, 7736, 7738, 7740, 7742, 7744, 7746, 7748, 7750, + 7752, 7754, 7756, 7758, 7760, 7762, 7764, 7766, 7768, 7770, 7772, 7774, 7776, 7778, 7780, 7782, 7784, 7786, + 7788, 7790, 7792, 7794, 7796, 7798, 7800, 7802, 7804, 7806, 7808, 7810, 7812, 7814, 7816, 7818, 7820, 7822, + 7824, 7826, 7828, 7830, 7831, 7832, 7833, 7834, 7840, 7842, 7844, 7846, 7848, 7850, 7852, 7854, 7856, + 7858, 7860, 7862, 7864, 7866, 7868, 7870, 7872, 7874, 7876, 7878, 7880, 7882, 7884, 7886, 7888, 7890, 7892, + 7894, 7896, 7898, 7900, 7902, 7904, 7906, 7908, 7910, 7912, 7914, 7916, 7918, 7920, 7922, 7924, 7926, 7928); + $result = Multibyte::ascii($utf8); + $expected = 'ḀḂḄḆḈḊḌḎá¸á¸’ḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎá¹á¹’ṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎáºáº’ẔẖẗẘẙẚẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎá»á»’ỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸ'; + $this->assertEqual($result, $expected); + + $utf8 = array(7681, 7683, 7685, 7687, 7689, 7691, 7693, 7695, 7697, 7699, 7701, 7703, 7705, 7707, 7709, 7711, 7713, 7715, + 7717, 7719, 7721, 7723, 7725, 7727, 7729, 7731, 7733, 7735, 7737, 7739, 7741, 7743, 7745, 7747, 7749, 7751, + 7753, 7755, 7757, 7759, 7761, 7763, 7765, 7767, 7769, 7771, 7773, 7775, 7777, 7779, 7781, 7783, 7785, 7787, + 7789, 7791, 7793, 7795, 7797, 7799, 7801, 7803, 7805, 7807, 7809, 7811, 7813, 7815, 7817, 7819, 7821, 7823, + 7825, 7827, 7829, 7830, 7831, 7832, 7833, 7834, 7841, 7843, 7845, 7847, 7849, 7851, 7853, 7855, 7857, 7859, + 7861, 7863, 7865, 7867, 7869, 7871, 7873, 7875, 7877, 7879, 7881, 7883, 7885, 7887, 7889, 7891, 7893, 7895, + 7897, 7899, 7901, 7903, 7905, 7907, 7909, 7911, 7913, 7915, 7917, 7919, 7921, 7923, 7925, 7927, 7929); + $result = Multibyte::ascii($utf8); + $expected = 'á¸á¸ƒá¸…ḇḉḋá¸á¸á¸‘ḓḕḗḙḛá¸á¸Ÿá¸¡á¸£á¸¥á¸§á¸©á¸«á¸­á¸¯á¸±á¸³á¸µá¸·á¸¹á¸»á¸½á¸¿á¹á¹ƒá¹…ṇṉṋá¹á¹á¹‘ṓṕṗṙṛá¹á¹Ÿá¹¡á¹£á¹¥á¹§á¹©á¹«á¹­á¹¯á¹±á¹³á¹µá¹·á¹¹á¹»á¹½á¹¿áºáºƒáº…ẇẉẋáºáºáº‘ẓẕẖẗẘẙẚạảấầẩẫậắằẳẵặẹẻẽếá»á»ƒá»…ệỉịá»á»á»‘ồổỗộớá»á»Ÿá»¡á»£á»¥á»§á»©á»«á»­á»¯á»±á»³á»µá»·á»¹'; + $this->assertEqual($result, $expected); + + $utf8 = array(8486, 8490, 8491, 8498); + $result = Multibyte::ascii($utf8); + $expected = 'ΩKÅℲ'; + $this->assertEqual($result, $expected); + + $utf8 = array(969, 107, 229, 8526); + $result = Multibyte::ascii($utf8); + $expected = 'ωkåⅎ'; + $this->assertEqual($result, $expected); + + $utf8 = array(8544, 8545, 8546, 8547, 8548, 8549, 8550, 8551, 8552, 8553, 8554, 8555, 8556, 8557, 8558, 8559, 8579); + $result = Multibyte::ascii($utf8); + $expected = 'ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯↃ'; + $this->assertEqual($result, $expected); + + $utf8 = array(8560, 8561, 8562, 8563, 8564, 8565, 8566, 8567, 8568, 8569, 8570, 8571, 8572, 8573, 8574, 8575, 8580); + $result = Multibyte::ascii($utf8); + $expected = 'ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↄ'; + $this->assertEqual($result, $expected); + + $utf8 = array(9398, 9399, 9400, 9401, 9402, 9403, 9404, 9405, 9406, 9407, 9408, 9409, 9410, 9411, 9412, 9413, 9414, + 9415, 9416, 9417, 9418, 9419, 9420, 9421, 9422, 9423); + $result = Multibyte::ascii($utf8); + $expected = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀâ“ⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌâ“â“Žâ“'; + $this->assertEqual($result, $expected); + + $utf8 = array(9424, 9425, 9426, 9427, 9428, 9429, 9430, 9431, 9432, 9433, 9434, 9435, 9436, 9437, 9438, 9439, 9440, 9441, + 9442, 9443, 9444, 9445, 9446, 9447, 9448, 9449); + $result = Multibyte::ascii($utf8); + $expected = 'â“ⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜâ“ⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ'; + $this->assertEqual($result, $expected); + + $utf8 = array(11264, 11265, 11266, 11267, 11268, 11269, 11270, 11271, 11272, 11273, 11274, 11275, 11276, 11277, 11278, 11279, + 11280, 11281, 11282, 11283, 11284, 11285, 11286, 11287, 11288, 11289, 11290, 11291, 11292, 11293, 11294, 11295, + 11296, 11297, 11298, 11299, 11300, 11301, 11302, 11303, 11304, 11305, 11306, 11307, 11308, 11309, 11310); + $result = Multibyte::ascii($utf8); + $expected = 'â°€â°â°‚ⰃⰄⰅⰆⰇⰈⰉⰊⰋⰌâ°â°Žâ°â°â°‘ⰒⰓⰔⰕⰖⰗⰘⰙⰚⰛⰜâ°â°žâ°Ÿâ° â°¡â°¢â°£â°¤â°¥â°¦â°§â°¨â°©â°ªâ°«â°¬â°­â°®'; + $this->assertEqual($result, $expected); + + $utf8 = array(11312, 11313, 11314, 11315, 11316, 11317, 11318, 11319, 11320, 11321, 11322, 11323, 11324, 11325, 11326, 11327, + 11328, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338, 11339, 11340, 11341, 11342, 11343, + 11344, 11345, 11346, 11347, 11348, 11349, 11350, 11351, 11352, 11353, 11354, 11355, 11356, 11357, 11358); + $result = Multibyte::ascii($utf8); + $expected = 'ⰰⰱⰲⰳⰴⰵⰶⰷⰸⰹⰺⰻⰼⰽⰾⰿⱀâ±â±‚ⱃⱄⱅⱆⱇⱈⱉⱊⱋⱌâ±â±Žâ±â±â±‘ⱒⱓⱔⱕⱖⱗⱘⱙⱚⱛⱜâ±â±ž'; + $this->assertEqual($result, $expected); + + $utf8 = array(11392, 11394, 11396, 11398, 11400, 11402, 11404, 11406, 11408, 11410, 11412, 11414, 11416, 11418, 11420, + 11422, 11424, 11426, 11428, 11430, 11432, 11434, 11436, 11438, 11440, 11442, 11444, 11446, 11448, 11450, + 11452, 11454, 11456, 11458, 11460, 11462, 11464, 11466, 11468, 11470, 11472, 11474, 11476, 11478, 11480, + 11482, 11484, 11486, 11488, 11490); + $result = Multibyte::ascii($utf8); + $expected = 'ⲀⲂⲄⲆⲈⲊⲌⲎâ²â²’ⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⲲⲴⲶⲸⲺⲼⲾⳀⳂⳄⳆⳈⳊⳌⳎâ³â³’ⳔⳖⳘⳚⳜⳞⳠⳢ'; + $this->assertEqual($result, $expected); + + $utf8 = array(11393, 11395, 11397, 11399, 11401, 11403, 11405, 11407, 11409, 11411, 11413, 11415, 11417, 11419, 11421, 11423, + 11425, 11427, 11429, 11431, 11433, 11435, 11437, 11439, 11441, 11443, 11445, 11447, 11449, 11451, 11453, 11455, + 11457, 11459, 11461, 11463, 11465, 11467, 11469, 11471, 11473, 11475, 11477, 11479, 11481, 11483, 11485, 11487, + 11489, 11491); + $result = Multibyte::ascii($utf8); + $expected = 'â²â²ƒâ²…ⲇⲉⲋâ²â²â²‘ⲓⲕⲗⲙⲛâ²â²Ÿâ²¡â²£â²¥â²§â²©â²«â²­â²¯â²±â²³â²µâ²·â²¹â²»â²½â²¿â³â³ƒâ³…ⳇⳉⳋâ³â³â³‘ⳓⳕⳗⳙⳛâ³â³Ÿâ³¡â³£'; + $this->assertEqual($result, $expected); + + $utf8 = array(64256, 64257, 64258, 64259, 64260, 64261, 64262, 64275, 64276, 64277, 64278, 64279); + $result = Multibyte::ascii($utf8); + $expected = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStripos method + * + * @access public + * @return void + */ + function testUsingMbStripos() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'f'; + $result = mb_stripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; + $find = 'f'; + $result = mb_stripos($string, $find, 6); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã¥'; + $result = mb_stripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'Ã¥'; + $result = mb_stripos($string, $find, 6); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'Ä‹'; + $result = mb_stripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'Ä‹'; + $result = mb_stripos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'f'; + $result = mb_stripos($string, $find); + $expected = 37; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'Îœ'; + $result = mb_stripos($string, $find); + $expected = 20; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'É'; + $result = mb_stripos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_stripos($string, $find); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_stripos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_stripos($string, $find, 40); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ʀ'; + $result = mb_stripos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ñ—'; + $result = mb_stripos($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_stripos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_stripos($string, $find, 5); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_stripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_stripos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_stripos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_stripos($string, $find); + $expected = 31; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_stripos($string, $find); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_stripos($string, $find); + $expected = 46; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_stripos($string, $find); + $expected = 45; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_stripos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'K'; + $result = mb_stripos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_stripos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_stripos($string, $find); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_stripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å'; + $result = mb_stripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'O'; + $result = mb_stripos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_stripos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'N'; + $result = mb_stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'Ć'; + $result = mb_stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'Ž'; + $result = mb_stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_stripos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_stripos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'DŽ'; + $result = mb_stripos($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStripos method + * + * @access public + * @return void + */ + function testMultibyteStripos() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'f'; + $result = Multibyte::stripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; + $find = 'f'; + $result = Multibyte::stripos($string, $find, 6); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã¥'; + $result = Multibyte::stripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'Ã¥'; + $result = Multibyte::stripos($string, $find, 6); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'Ä‹'; + $result = Multibyte::stripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'Ä‹'; + $result = Multibyte::stripos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'f'; + $result = Multibyte::stripos($string, $find); + $expected = 37; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'Îœ'; + $result = Multibyte::stripos($string, $find); + $expected = 20; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'É'; + $result = Multibyte::stripos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::stripos($string, $find); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::stripos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::stripos($string, $find, 40); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ʀ'; + $result = Multibyte::stripos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ñ—'; + $result = Multibyte::stripos($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::stripos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::stripos($string, $find, 5); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::stripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::stripos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::stripos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::stripos($string, $find); + $expected = 31; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::stripos($string, $find); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::stripos($string, $find); + $expected = 46; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::stripos($string, $find); + $expected = 45; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::stripos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'K'; + $result = Multibyte::stripos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::stripos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::stripos($string, $find); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::stripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å'; + $result = Multibyte::stripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'O'; + $result = Multibyte::stripos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::stripos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'N'; + $result = Multibyte::stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'Ć'; + $result = Multibyte::stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'Ž'; + $result = Multibyte::stripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::stripos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::stripos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'DŽ'; + $result = Multibyte::stripos($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStristr method + * + * @access public + * @return void + */ + function testUsingMbStristr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'f'; + $result = mb_stristr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'f'; + $result = mb_stristr($string, $find, true); + $expected = 'ABCDE'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã¥'; + $result = mb_stristr($string, $find); + $expected = 'ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã¥'; + $result = mb_stristr($string, $find, true); + $expected = 'ÀÃÂÃÄ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'Ä‹'; + $result = mb_stristr($string, $find); + $expected = 'ĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'Ä‹'; + $result = mb_stristr($string, $find, true); + $expected = 'ĀĂĄĆĈ'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'f'; + $result = mb_stristr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'f'; + $result = mb_stristr($string, $find, true); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'Îœ'; + $result = mb_stristr($string, $find); + $expected = 'µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'Îœ'; + $result = mb_stristr($string, $find, true); + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'þ'; + $result = mb_stristr($string, $find); + $expected = 'Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'þ'; + $result = mb_stristr($string, $find, true); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_stristr($string, $find); + $expected = 'ŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_stristr($string, $find, true); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃń'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_stristr($string, $find); + $expected = 'ƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $find = 'Ƹ'; + $result = mb_stristr($string, $find, true); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ʀ'; + $result = mb_stristr($string, $find); + $expected = 'Ê€ÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ʀ'; + $result = mb_stristr($string, $find, true); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ñ—'; + $result = mb_stristr($string, $find); + $expected = 'ЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ñ—'; + $result = mb_stristr($string, $find, true); + $expected = 'ЀÐЂЃЄЅІ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_stristr($string, $find); + $expected = 'РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_stristr($string, $find, true); + $expected = 'ÐœÐОП'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_stristr($string, $find); + $expected = 'نهوىيًٌÙÙŽÙ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_stristr($string, $find, true); + $expected = 'Ùقكلم'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_stristr($string, $find); + $expected = '✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_stristr($string, $find, true); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_stristr($string, $find); + $expected = 'âºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_stristr($string, $find, true); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâº'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_stristr($string, $find); + $expected = '⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_stristr($string, $find, true); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_stristr($string, $find); + $expected = '눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_stristr($string, $find, true); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_stristr($string, $find); + $expected = 'ﺞﺟﺠﺡﺢﺣﺤﺥﺦﺧﺨﺩﺪﺫﺬﺭﺮﺯﺰ'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_stristr($string, $find, true); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïº'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_stristr($string, $find); + $expected = 'ﻞﻟﻠﻡﻢﻣﻤﻥﻦﻧﻨﻩﻪﻫﻬﻭﻮﻯﻰﻱﻲﻳﻴﻵﻶﻷﻸﻹﻺﻻﻼ'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_stristr($string, $find, true); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'K'; + $result = mb_stristr($string, $find); + $expected = 'klï½ï½Žï½ï½ï½‘rstuvwxyz'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'K'; + $result = mb_stristr($string, $find, true); + $expected = 'ï½ï½‚cdefghij'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_stristr($string, $find); + $expected = 'アイウエオカキク'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_stristr($string, $find, true); + $expected = '。「」、・ヲァィゥェォャュョッー'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_stristr($string, $find); + $expected = 'ハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_stristr($string, $find, true); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å'; + $result = mb_stristr($string, $find); + $expected = 'őřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å'; + $result = mb_stristr($string, $find, true); + $expected = 'ĤēĺļÅ, Å´'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'ĺļ'; + $result = mb_stristr($string, $find, true); + $expected = 'Ĥē'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'O'; + $result = mb_stristr($string, $find); + $expected = 'o, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'O'; + $result = mb_stristr($string, $find, true); + $expected = 'Hell'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = mb_stristr($string, $find); + $expected = 'World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = mb_stristr($string, $find, true); + $expected = 'Hello, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = mb_stristr($string, $find); + $expected = 'llo, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = mb_stristr($string, $find, true); + $expected = 'He'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = mb_stristr($string, $find); + $expected = 'rld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = mb_stristr($string, $find, true); + $expected = 'Hello, Wo'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'N'; + $result = mb_stristr($string, $find); + $expected = 'ni'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'N'; + $result = mb_stristr($string, $find, true); + $expected = 'Äi'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'Ć'; + $result = mb_stristr($string, $find); + $expected = 'ći'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'Ć'; + $result = mb_stristr($string, $find, true); + $expected = 'mo'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'Ž'; + $result = mb_stristr($string, $find); + $expected = 'žavni'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'Ž'; + $result = mb_stristr($string, $find, true); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_stristr($string, $find); + $expected = '设为首页'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_stristr($string, $find, true); + $expected = '把百度'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_stristr($string, $find); + $expected = '周永é¾'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_stristr($string, $find, true); + $expected = '一二三'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '二周'; + $result = mb_stristr($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStristr method + * + * @access public + * @return void + */ + function testMultibyteStristr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'f'; + $result = Multibyte::stristr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'f'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ABCDE'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã¥'; + $result = Multibyte::stristr($string, $find); + $expected = 'ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã¥'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ÀÃÂÃÄ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'Ä‹'; + $result = Multibyte::stristr($string, $find); + $expected = 'ĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'Ä‹'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ĀĂĄĆĈ'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'f'; + $result = Multibyte::stristr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'f'; + $result = Multibyte::stristr($string, $find, true); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'Îœ'; + $result = Multibyte::stristr($string, $find); + $expected = 'µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'Îœ'; + $result = Multibyte::stristr($string, $find, true); + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'þ'; + $result = Multibyte::stristr($string, $find); + $expected = 'Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'þ'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::stristr($string, $find); + $expected = 'ŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃń'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::stristr($string, $find); + $expected = 'ƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $find = 'Ƹ'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ʀ'; + $result = Multibyte::stristr($string, $find); + $expected = 'Ê€ÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ʀ'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ñ—'; + $result = Multibyte::stristr($string, $find); + $expected = 'ЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ñ—'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ЀÐЂЃЄЅІ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::stristr($string, $find); + $expected = 'РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ÐœÐОП'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::stristr($string, $find); + $expected = 'نهوىيًٌÙÙŽÙ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'Ùقكلم'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::stristr($string, $find); + $expected = '✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::stristr($string, $find, true); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::stristr($string, $find); + $expected = 'âºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::stristr($string, $find, true); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâº'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::stristr($string, $find); + $expected = '⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::stristr($string, $find, true); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::stristr($string, $find); + $expected = '눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::stristr($string, $find, true); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::stristr($string, $find); + $expected = 'ﺞﺟﺠﺡﺢﺣﺤﺥﺦﺧﺨﺩﺪﺫﺬﺭﺮﺯﺰ'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïº'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::stristr($string, $find); + $expected = 'ﻞﻟﻠﻡﻢﻣﻤﻥﻦﻧﻨﻩﻪﻫﻬﻭﻮﻯﻰﻱﻲﻳﻴﻵﻶﻷﻸﻹﻺﻻﻼ'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'K'; + $result = Multibyte::stristr($string, $find); + $expected = 'klï½ï½Žï½ï½ï½‘rstuvwxyz'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'K'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ï½ï½‚cdefghij'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::stristr($string, $find); + $expected = 'アイウエオカキク'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::stristr($string, $find, true); + $expected = '。「」、・ヲァィゥェォャュョッー'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::stristr($string, $find); + $expected = 'ハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å'; + $result = Multibyte::stristr($string, $find); + $expected = 'őřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'ĤēĺļÅ, Å´'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'ĺļ'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'Ĥē'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'O'; + $result = Multibyte::stristr($string, $find); + $expected = 'o, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'O'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'Hell'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = Multibyte::stristr($string, $find); + $expected = 'World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'Hello, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = Multibyte::stristr($string, $find); + $expected = 'llo, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'He'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = Multibyte::stristr($string, $find); + $expected = 'rld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'Hello, Wo'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'N'; + $result = Multibyte::stristr($string, $find); + $expected = 'ni'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'N'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'Äi'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'Ć'; + $result = Multibyte::stristr($string, $find); + $expected = 'ći'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'Ć'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'mo'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'Ž'; + $result = Multibyte::stristr($string, $find); + $expected = 'žavni'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'Ž'; + $result = Multibyte::stristr($string, $find, true); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::stristr($string, $find); + $expected = '设为首页'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::stristr($string, $find, true); + $expected = '把百度'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::stristr($string, $find); + $expected = '周永é¾'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::stristr($string, $find, true); + $expected = '一二三'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '二周'; + $result = Multibyte::stristr($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrlen method + * + * @access public + * @return void + */ + function testUsingMbStrlen() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $result = mb_strlen($string); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $result = mb_strlen($string); + $expected = 30; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $result = mb_strlen($string); + $expected = 61; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $result = mb_strlen($string); + $expected = 94; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $result = mb_strlen($string); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $result = mb_strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $result = mb_strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $result = mb_strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $result = mb_strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $result = mb_strlen($string); + $expected = 28; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $result = mb_strlen($string); + $expected = 49; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $result = mb_strlen($string); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $result = mb_strlen($string); + $expected = 47; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $result = mb_strlen($string); + $expected = 96; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $result = mb_strlen($string); + $expected = 59; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $result = mb_strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $result = mb_strlen($string); + $expected = 65; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $result = mb_strlen($string); + $expected = 76; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $result = mb_strlen($string); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $result = mb_strlen($string); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $result = mb_strlen($string); + $expected = 38; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = mb_strlen($string); + $expected = 13; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $result = mb_strlen($string); + $expected = 13; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $result = mb_strlen($string); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $result = mb_strlen($string); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $result = mb_strlen($string); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $result = mb_strlen($string); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = mb_strlen($string); + $expected = 6; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrlen method + * + * @access public + * @return void + */ + function testMultibyteStrlen() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $result = Multibyte::strlen($string); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $result = Multibyte::strlen($string); + $expected = 30; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $result = Multibyte::strlen($string); + $expected = 61; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $result = Multibyte::strlen($string); + $expected = 94; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $result = Multibyte::strlen($string); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $result = Multibyte::strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $result = Multibyte::strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $result = Multibyte::strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $result = Multibyte::strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $result = Multibyte::strlen($string); + $expected = 28; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $result = Multibyte::strlen($string); + $expected = 49; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $result = Multibyte::strlen($string); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $result = Multibyte::strlen($string); + $expected = 47; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $result = Multibyte::strlen($string); + $expected = 96; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $result = Multibyte::strlen($string); + $expected = 59; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $result = Multibyte::strlen($string); + $expected = 100; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $result = Multibyte::strlen($string); + $expected = 65; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $result = Multibyte::strlen($string); + $expected = 76; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $result = Multibyte::strlen($string); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $result = Multibyte::strlen($string); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $result = Multibyte::strlen($string); + $expected = 38; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = Multibyte::strlen($string); + $expected = 13; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $result = Multibyte::strlen($string); + $expected = 13; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $result = Multibyte::strlen($string); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $result = Multibyte::strlen($string); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $result = Multibyte::strlen($string); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $result = Multibyte::strlen($string); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = Multibyte::strlen($string); + $expected = 6; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrpos method + * + * @access public + * @return void + */ + function testUsingMbStrpos() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strpos($string, $find, 6); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strpos($string, $find, 6); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strpos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strpos($string, $find); + $expected = 37; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strpos($string, $find); + $expected = 20; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'é'; + $result = mb_strpos($string, $find); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strpos($string, $find); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_strpos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'ƹ'; + $result = mb_strpos($string, $find); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strpos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strpos($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_strpos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Ñ€'; + $result = mb_strpos($string, $find, 5); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strpos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strpos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strpos($string, $find); + $expected = 31; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strpos($string, $find); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strpos($string, $find); + $expected = 46; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strpos($string, $find); + $expected = 45; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strpos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strpos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strpos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strpos($string, $find); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘Å™'; + $result = mb_strpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strpos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strpos($string, $find, 5); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strpos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strpos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '一周'; + $result = mb_strpos($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrpos method + * + * @access public + * @return void + */ + function testMultibyteStrpos() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strpos($string, $find, 6); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strpos($string, $find, 6); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strpos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strpos($string, $find); + $expected = 37; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strpos($string, $find); + $expected = 20; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'é'; + $result = Multibyte::strpos($string, $find); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strpos($string, $find); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::strpos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'ƹ'; + $result = Multibyte::strpos($string, $find); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strpos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strpos($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::strpos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Ñ€'; + $result = Multibyte::strpos($string, $find, 5); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strpos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strpos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strpos($string, $find); + $expected = 31; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strpos($string, $find); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strpos($string, $find); + $expected = 46; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strpos($string, $find); + $expected = 45; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strpos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strpos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strpos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strpos($string, $find); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘Å™'; + $result = Multibyte::strpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strpos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strpos($string, $find, 5); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strpos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strpos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '一周'; + $result = Multibyte::strpos($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrrchr method + * + * @access public + * @return void + */ + function testUsingMbStrrchr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strrchr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strrchr($string, $find, true); + $expected = 'ABCDE'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strrchr($string, $find); + $expected = 'ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strrchr($string, $find, true); + $expected = 'ÀÃÂÃÄ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strrchr($string, $find); + $expected = 'ĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strrchr($string, $find, true); + $expected = 'ĀĂĄĆĈ'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strrchr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strrchr($string, $find, true); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strrchr($string, $find); + $expected = 'µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strrchr($string, $find, true); + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = mb_strrchr($string, $find); + $expected = 'Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = mb_strrchr($string, $find, true); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strrchr($string, $find); + $expected = 'ŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strrchr($string, $find, true); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃń'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_strrchr($string, $find); + $expected = 'ƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $find = 'Ƹ'; + $result = mb_strrchr($string, $find, true); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strrchr($string, $find); + $expected = 'Ê€ÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strrchr($string, $find, true); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strrchr($string, $find); + $expected = 'ЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strrchr($string, $find, true); + $expected = 'ЀÐЂЃЄЅІ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_strrchr($string, $find); + $expected = 'РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_strrchr($string, $find, true); + $expected = 'ÐœÐОП'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strrchr($string, $find); + $expected = 'نهوىيًٌÙÙŽÙ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strrchr($string, $find, true); + $expected = 'Ùقكلم'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strrchr($string, $find); + $expected = '✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strrchr($string, $find, true); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strrchr($string, $find); + $expected = 'âºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strrchr($string, $find, true); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâº'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strrchr($string, $find); + $expected = '⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strrchr($string, $find, true); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strrchr($string, $find); + $expected = '눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strrchr($string, $find, true); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strrchr($string, $find); + $expected = 'ﺞﺟﺠﺡﺢﺣﺤﺥﺦﺧﺨﺩﺪﺫﺬﺭﺮﺯﺰ'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strrchr($string, $find, true); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïº'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strrchr($string, $find); + $expected = 'ﻞﻟﻠﻡﻢﻣﻤﻥﻦﻧﻨﻩﻪﻫﻬﻭﻮﻯﻰﻱﻲﻳﻴﻵﻶﻷﻸﻹﻺﻻﻼ'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strrchr($string, $find, true); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strrchr($string, $find); + $expected = 'klï½ï½Žï½ï½ï½‘rstuvwxyz'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strrchr($string, $find, true); + $expected = 'ï½ï½‚cdefghij'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strrchr($string, $find); + $expected = 'アイウエオカキク'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strrchr($string, $find, true); + $expected = '。「」、・ヲァィゥェォャュョッー'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strrchr($string, $find); + $expected = 'ハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strrchr($string, $find, true); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strrchr($string, $find); + $expected = 'őřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strrchr($string, $find, true); + $expected = 'ĤēĺļÅ, Å´'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strrchr($string, $find); + $expected = 'orld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strrchr($string, $find, true); + $expected = 'Hello, W'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = mb_strrchr($string, $find); + $expected = 'World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = mb_strrchr($string, $find, true); + $expected = 'Hello, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = mb_strrchr($string, $find); + $expected = 'llo, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = mb_strrchr($string, $find, true); + $expected = 'He'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = mb_strrchr($string, $find); + $expected = 'rld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = mb_strrchr($string, $find, true); + $expected = 'Hello, Wo'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strrchr($string, $find); + $expected = 'ni'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strrchr($string, $find, true); + $expected = 'Äi'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strrchr($string, $find); + $expected = 'ći'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strrchr($string, $find, true); + $expected = 'mo'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strrchr($string, $find); + $expected = 'žavni'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strrchr($string, $find, true); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strrchr($string, $find); + $expected = '设为首页'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strrchr($string, $find, true); + $expected = '把百度'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strrchr($string, $find); + $expected = '周永é¾'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strrchr($string, $find, true); + $expected = '一二三'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周é¾'; + $result = mb_strrchr($string, $find, true); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrrchr method + * + * @access public + * @return void + */ + function testMultibyteStrrchr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strrchr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ABCDE'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ÀÃÂÃÄ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ĀĂĄĆĈ'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strrchr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strrchr($string, $find); + $expected = 'µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = Multibyte::strrchr($string, $find); + $expected = 'Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃń'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $find = 'Ƹ'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strrchr($string, $find); + $expected = 'Ê€ÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ЀÐЂЃЄЅІ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::strrchr($string, $find); + $expected = 'РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ÐœÐОП'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strrchr($string, $find); + $expected = 'نهوىيًٌÙÙŽÙ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'Ùقكلم'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strrchr($string, $find); + $expected = '✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strrchr($string, $find); + $expected = 'âºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâº'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strrchr($string, $find); + $expected = '⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strrchr($string, $find); + $expected = '눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ﺞﺟﺠﺡﺢﺣﺤﺥﺦﺧﺨﺩﺪﺫﺬﺭﺮﺯﺰ'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïº'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ﻞﻟﻠﻡﻢﻣﻤﻥﻦﻧﻨﻩﻪﻫﻬﻭﻮﻯﻰﻱﻲﻳﻴﻵﻶﻷﻸﻹﻺﻻﻼ'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strrchr($string, $find); + $expected = 'klï½ï½Žï½ï½ï½‘rstuvwxyz'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ï½ï½‚cdefghij'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strrchr($string, $find); + $expected = 'アイウエオカキク'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '。「」、・ヲァィゥェォャュョッー'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strrchr($string, $find); + $expected = 'őřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'ĤēĺļÅ, Å´'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strrchr($string, $find); + $expected = 'orld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'Hello, W'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = Multibyte::strrchr($string, $find); + $expected = 'World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'Hello, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = Multibyte::strrchr($string, $find); + $expected = 'llo, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'He'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = Multibyte::strrchr($string, $find); + $expected = 'rld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'Hello, Wo'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ni'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'Äi'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strrchr($string, $find); + $expected = 'ći'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'mo'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strrchr($string, $find); + $expected = 'žavni'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strrchr($string, $find, true); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strrchr($string, $find); + $expected = '设为首页'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '把百度'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strrchr($string, $find); + $expected = '周永é¾'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strrchr($string, $find, true); + $expected = '一二三'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周é¾'; + $result = Multibyte::strrchr($string, $find, true); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrrichr method + * + * @access public + * @return void + */ + function testUsingMbStrrichr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strrichr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strrichr($string, $find, true); + $expected = 'ABCDE'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strrichr($string, $find); + $expected = 'ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strrichr($string, $find, true); + $expected = 'ÀÃÂÃÄ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strrichr($string, $find); + $expected = 'ĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strrichr($string, $find, true); + $expected = 'ĀĂĄĆĈ'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strrichr($string, $find); + $expected = 'fghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strrichr($string, $find, true); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcde'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strrichr($string, $find); + $expected = 'µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strrichr($string, $find, true); + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = mb_strrichr($string, $find); + $expected = 'þÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = mb_strrichr($string, $find, true); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüý'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strrichr($string, $find); + $expected = 'ņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strrichr($string, $find, true); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅ'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_strrichr($string, $find); + $expected = 'ƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $find = 'Ƹ'; + $result = mb_strrichr($string, $find, true); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strrichr($string, $find); + $expected = 'Ê€ÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strrichr($string, $find, true); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strrichr($string, $find); + $expected = 'ЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strrichr($string, $find, true); + $expected = 'ЀÐЂЃЄЅІ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_strrichr($string, $find); + $expected = 'Ñ€Ñтуфхцчшщъыь'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп'; + $find = 'Р'; + $result = mb_strrichr($string, $find, true); + $expected = 'ÐœÐОП'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strrichr($string, $find); + $expected = 'نهوىيًٌÙÙŽÙ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strrichr($string, $find, true); + $expected = 'Ùقكلم'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strrichr($string, $find); + $expected = '✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strrichr($string, $find, true); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strrichr($string, $find); + $expected = 'âºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strrichr($string, $find, true); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâº'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strrichr($string, $find); + $expected = '⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strrichr($string, $find, true); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strrichr($string, $find); + $expected = '눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strrichr($string, $find, true); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strrichr($string, $find); + $expected = 'ﺞﺟﺠﺡﺢﺣﺤﺥﺦﺧﺨﺩﺪﺫﺬﺭﺮﺯﺰ'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strrichr($string, $find, true); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïº'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strrichr($string, $find); + $expected = 'ﻞﻟﻠﻡﻢﻣﻤﻥﻦﻧﻨﻩﻪﻫﻬﻭﻮﻯﻰﻱﻲﻳﻴﻵﻶﻷﻸﻹﻺﻻﻼ'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strrichr($string, $find, true); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strrichr($string, $find); + $expected = 'klï½ï½Žï½ï½ï½‘rstuvwxyz'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strrichr($string, $find, true); + $expected = 'ï½ï½‚cdefghij'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strrichr($string, $find); + $expected = 'アイウエオカキク'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strrichr($string, $find, true); + $expected = '。「」、・ヲァィゥェォャュョッー'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strrichr($string, $find); + $expected = 'ハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strrichr($string, $find, true); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strrichr($string, $find); + $expected = 'őřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strrichr($string, $find, true); + $expected = 'ĤēĺļÅ, Å´'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strrichr($string, $find); + $expected = 'orld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strrichr($string, $find, true); + $expected = 'Hello, W'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = mb_strrichr($string, $find); + $expected = 'World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = mb_strrichr($string, $find, true); + $expected = 'Hello, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = mb_strrichr($string, $find); + $expected = 'llo, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = mb_strrichr($string, $find, true); + $expected = 'He'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = mb_strrichr($string, $find); + $expected = 'rld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = mb_strrichr($string, $find, true); + $expected = 'Hello, Wo'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strrichr($string, $find); + $expected = 'ni'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strrichr($string, $find, true); + $expected = 'Äi'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strrichr($string, $find); + $expected = 'ći'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strrichr($string, $find, true); + $expected = 'mo'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strrichr($string, $find); + $expected = 'žavni'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strrichr($string, $find, true); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strrichr($string, $find); + $expected = '设为首页'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strrichr($string, $find, true); + $expected = '把百度'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strrichr($string, $find); + $expected = '周永é¾'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strrichr($string, $find, true); + $expected = '一二三'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '百设'; + $result = mb_strrichr($string, $find, true); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrrichr method + * + * @access public + * @return void + */ + function testMultibyteStrrichr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strrichr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ABCDE'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ÀÃÂÃÄ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ĀĂĄĆĈ'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strrichr($string, $find); + $expected = 'fghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcde'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strrichr($string, $find); + $expected = 'µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = Multibyte::strrichr($string, $find); + $expected = 'þÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüý'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅ'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $find = 'Ƹ'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strrichr($string, $find); + $expected = 'Ê€ÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ЀÐЂЃЄЅІ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::strrichr($string, $find); + $expected = 'Ñ€Ñтуфхцчшщъыь'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп'; + $find = 'Р'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ÐœÐОП'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strrichr($string, $find); + $expected = 'نهوىيًٌÙÙŽÙ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'Ùقكلم'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strrichr($string, $find); + $expected = '✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strrichr($string, $find); + $expected = 'âºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâº'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strrichr($string, $find); + $expected = '⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strrichr($string, $find); + $expected = '눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ﺞﺟﺠﺡﺢﺣﺤﺥﺦﺧﺨﺩﺪﺫﺬﺭﺮﺯﺰ'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïº'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ﻞﻟﻠﻡﻢﻣﻤﻥﻦﻧﻨﻩﻪﻫﻬﻭﻮﻯﻰﻱﻲﻳﻴﻵﻶﻷﻸﻹﻺﻻﻼ'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strrichr($string, $find); + $expected = 'klï½ï½Žï½ï½ï½‘rstuvwxyz'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ï½ï½‚cdefghij'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strrichr($string, $find); + $expected = 'アイウエオカキク'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '。「」、・ヲァィゥェォャュョッー'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strrichr($string, $find); + $expected = 'őřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'ĤēĺļÅ, Å´'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strrichr($string, $find); + $expected = 'orld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'Hello, W'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = Multibyte::strrichr($string, $find); + $expected = 'World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'Hello, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = Multibyte::strrichr($string, $find); + $expected = 'llo, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'He'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = Multibyte::strrichr($string, $find); + $expected = 'rld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'Hello, Wo'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ni'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'Äi'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strrichr($string, $find); + $expected = 'ći'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'mo'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strrichr($string, $find); + $expected = 'žavni'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strrichr($string, $find, true); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strrichr($string, $find); + $expected = '设为首页'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '把百度'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strrichr($string, $find); + $expected = '周永é¾'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strrichr($string, $find, true); + $expected = '一二三'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '百设'; + $result = Multibyte::strrichr($string, $find, true); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrripos method + * + * @access public + * @return void + */ + function testUsingMbStrripos() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strripos($string, $find, 6); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strripos($string, $find, 6); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'ÓÔ'; + $result = mb_strripos($string, $find); + $expected = 19; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strripos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strripos($string, $find); + $expected = 69; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strripos($string, $find); + $expected = 20; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'é'; + $result = mb_strripos($string, $find); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strripos($string, $find); + $expected = 25; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_strripos($string, $find); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'ƹ'; + $result = mb_strripos($string, $find); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strripos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strripos($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_strripos($string, $find); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Ñ€'; + $result = mb_strripos($string, $find, 5); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strripos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strripos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strripos($string, $find); + $expected = 31; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strripos($string, $find); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strripos($string, $find); + $expected = 46; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strripos($string, $find); + $expected = 45; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strripos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strripos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½ï½‘rstuvwxyz'; + $find = 'ï½ï½'; + $result = mb_strripos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strripos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strripos($string, $find); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strripos($string, $find, 5); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strripos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strripos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'dž'; + $result = mb_strripos($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrripos method + * + * @access public + * @return void + */ + function testMultibyteStrripos() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strripos($string, $find, 6); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strripos($string, $find, 6); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'ÓÔ'; + $result = Multibyte::strripos($string, $find); + $expected = 19; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strripos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strripos($string, $find); + $expected = 69; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strripos($string, $find); + $expected = 20; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'é'; + $result = Multibyte::strripos($string, $find); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strripos($string, $find); + $expected = 25; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::strripos($string, $find); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'ƹ'; + $result = Multibyte::strripos($string, $find); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strripos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strripos($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::strripos($string, $find); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Ñ€'; + $result = Multibyte::strripos($string, $find, 5); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strripos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strripos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strripos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strripos($string, $find); + $expected = 31; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strripos($string, $find); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strripos($string, $find); + $expected = 46; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strripos($string, $find); + $expected = 45; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strripos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strripos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½ï½‘rstuvwxyz'; + $find = 'ï½ï½'; + $result = Multibyte::strripos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strripos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strripos($string, $find); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strripos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strripos($string, $find, 5); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strripos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strripos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strripos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'dž'; + $result = Multibyte::strripos($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrrpos method + * + * @access public + * @return void + */ + function testUsingMbStrrpos() { + $skip = extension_loaded('mbstring') && version_compare(PHP_VERSION, '5.2.0', '<'); + if ($this->skipIf($skip, '%s PHP version does not support $offset parameter in mb_strrpos().')) { + return; + } + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strrpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strrpos($string, $find, 6); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strrpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'ÙÚ'; + $result = mb_strrpos($string, $find); + $expected = 25; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strrpos($string, $find, 6); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strrpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strrpos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strrpos($string, $find); + $expected = 37; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strrpos($string, $find); + $expected = 20; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'é'; + $result = mb_strrpos($string, $find); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strrpos($string, $find); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_strrpos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'ƹ'; + $result = mb_strrpos($string, $find); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strrpos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strrpos($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_strrpos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Ñ€'; + $result = mb_strrpos($string, $find, 5); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strrpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strrpos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strrpos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strrpos($string, $find); + $expected = 31; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strrpos($string, $find); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strrpos($string, $find); + $expected = 46; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strrpos($string, $find); + $expected = 45; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strrpos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strrpos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½ï½‘rstuvwxyz'; + $find = 'ï½ï½'; + $result = mb_strrpos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strrpos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strrpos($string, $find); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strrpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strrpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strrpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strrpos($string, $find, 5); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strrpos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strrpos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'H'; + $result = mb_strrpos($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrrpos method + * + * @access public + * @return void + */ + function testMultibyteStrrpos() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strrpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strrpos($string, $find, 6); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strrpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strrpos($string, $find, 6); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞ'; + $find = 'ÙÚ'; + $result = Multibyte::strrpos($string, $find); + $expected = 25; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strrpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strrpos($string, $find, 6); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strrpos($string, $find); + $expected = 37; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strrpos($string, $find); + $expected = 20; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'é'; + $result = Multibyte::strrpos($string, $find); + $expected = 32; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strrpos($string, $find); + $expected = 24; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::strrpos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'ƹ'; + $result = Multibyte::strrpos($string, $find); + $expected = 40; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strrpos($string, $find); + $expected = 39; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strrpos($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::strrpos($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Ñ€'; + $result = Multibyte::strrpos($string, $find, 5); + $expected = 36; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strrpos($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strrpos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strrpos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strrpos($string, $find); + $expected = 31; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strrpos($string, $find); + $expected = 26; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strrpos($string, $find); + $expected = 46; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strrpos($string, $find); + $expected = 45; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strrpos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strrpos($string, $find); + $expected = 10; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½ï½‘rstuvwxyz'; + $find = 'ï½ï½'; + $result = Multibyte::strrpos($string, $find); + $expected = 15; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strrpos($string, $find); + $expected = 16; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strrpos($string, $find); + $expected = 17; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strrpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strrpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strrpos($string, $find); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strrpos($string, $find, 5); + $expected = 8; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strrpos($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strrpos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strrpos($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'H'; + $result = Multibyte::strrpos($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrstr method + * + * @access public + * @return void + */ + function testUsingMbStrstr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strstr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_strstr($string, $find, true); + $expected = 'ABCDE'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strstr($string, $find); + $expected = 'ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = mb_strstr($string, $find, true); + $expected = 'ÀÃÂÃÄ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strstr($string, $find); + $expected = 'ĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_strstr($string, $find, true); + $expected = 'ĀĂĄĆĈ'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strstr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = mb_strstr($string, $find, true); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strstr($string, $find); + $expected = 'µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = mb_strstr($string, $find, true); + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = mb_strstr($string, $find); + $expected = 'Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = mb_strstr($string, $find, true); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strstr($string, $find); + $expected = 'ŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = mb_strstr($string, $find, true); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃń'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_strstr($string, $find); + $expected = 'ƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $find = 'Ƹ'; + $result = mb_strstr($string, $find, true); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strstr($string, $find); + $expected = 'Ê€ÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = mb_strstr($string, $find, true); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strstr($string, $find); + $expected = 'ЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_strstr($string, $find, true); + $expected = 'ЀÐЂЃЄЅІ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_strstr($string, $find); + $expected = 'РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_strstr($string, $find, true); + $expected = 'ÐœÐОП'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strstr($string, $find); + $expected = 'نهوىيًٌÙÙŽÙ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_strstr($string, $find, true); + $expected = 'Ùقكلم'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strstr($string, $find); + $expected = '✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_strstr($string, $find, true); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strstr($string, $find); + $expected = 'âºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_strstr($string, $find, true); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâº'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strstr($string, $find); + $expected = '⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_strstr($string, $find, true); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strstr($string, $find); + $expected = '눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = mb_strstr($string, $find, true); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strstr($string, $find); + $expected = 'ﺞﺟﺠﺡﺢﺣﺤﺥﺦﺧﺨﺩﺪﺫﺬﺭﺮﺯﺰ'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = mb_strstr($string, $find, true); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïº'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strstr($string, $find); + $expected = 'ﻞﻟﻠﻡﻢﻣﻤﻥﻦﻧﻨﻩﻪﻫﻬﻭﻮﻯﻰﻱﻲﻳﻴﻵﻶﻷﻸﻹﻺﻻﻼ'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_strstr($string, $find, true); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strstr($string, $find); + $expected = 'klï½ï½Žï½ï½ï½‘rstuvwxyz'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = mb_strstr($string, $find, true); + $expected = 'ï½ï½‚cdefghij'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'K'; + $result = mb_strstr($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strstr($string, $find); + $expected = 'アイウエオカキク'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_strstr($string, $find, true); + $expected = '。「」、・ヲァィゥェォャュョッー'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strstr($string, $find); + $expected = 'ハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_strstr($string, $find, true); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strstr($string, $find); + $expected = 'őřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_strstr($string, $find, true); + $expected = 'ĤēĺļÅ, Å´'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'ĺļ'; + $result = mb_strstr($string, $find, true); + $expected = 'Ĥē'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strstr($string, $find); + $expected = 'o, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_strstr($string, $find, true); + $expected = 'Hell'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = mb_strstr($string, $find); + $expected = 'World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = mb_strstr($string, $find, true); + $expected = 'Hello, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = mb_strstr($string, $find); + $expected = 'llo, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = mb_strstr($string, $find, true); + $expected = 'He'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = mb_strstr($string, $find); + $expected = 'rld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = mb_strstr($string, $find, true); + $expected = 'Hello, Wo'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strstr($string, $find); + $expected = 'ni'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_strstr($string, $find, true); + $expected = 'Äi'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strstr($string, $find); + $expected = 'ći'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_strstr($string, $find, true); + $expected = 'mo'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strstr($string, $find); + $expected = 'žavni'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_strstr($string, $find, true); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strstr($string, $find); + $expected = '设为首页'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_strstr($string, $find, true); + $expected = '把百度'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strstr($string, $find); + $expected = '周永é¾'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_strstr($string, $find, true); + $expected = '一二三'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '二周'; + $result = mb_strstr($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrstr method + * + * @access public + * @return void + */ + function testMultibyteStrstr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strstr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ0123456789'; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ABCDE'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strstr($string, $find); + $expected = 'ÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ÀÃÂÃÄ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strstr($string, $find); + $expected = 'ĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ĀĂĄĆĈ'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strstr($string, $find); + $expected = 'FGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $find = 'F'; + $result = Multibyte::strstr($string, $find, true); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDE'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strstr($string, $find); + $expected = 'µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $find = 'µ'; + $result = Multibyte::strstr($string, $find, true); + $expected = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = Multibyte::strstr($string, $find); + $expected = 'Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $find = 'Þ'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strstr($string, $find); + $expected = 'ŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'Å…'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃń'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::strstr($string, $find); + $expected = 'ƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $find = 'Ƹ'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strstr($string, $find); + $expected = 'Ê€ÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $find = 'Ê€'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strstr($string, $find); + $expected = 'ЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ЀÐЂЃЄЅІ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::strstr($string, $find); + $expected = 'РСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ÐœÐОП'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strstr($string, $find); + $expected = 'نهوىيًٌÙÙŽÙ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'Ùقكلم'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strstr($string, $find); + $expected = '✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::strstr($string, $find, true); + $expected = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strstr($string, $find); + $expected = 'âºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::strstr($string, $find, true); + $expected = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâº'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strstr($string, $find); + $expected = '⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::strstr($string, $find, true); + $expected = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strstr($string, $find); + $expected = '눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눻'; + $result = Multibyte::strstr($string, $find, true); + $expected = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strstr($string, $find); + $expected = 'ﺞﺟﺠﺡﺢﺣﺤﺥﺦﺧﺨﺩﺪﺫﺬﺭﺮﺯﺰ'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞ'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïº'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strstr($string, $find); + $expected = 'ﻞﻟﻠﻡﻢﻣﻤﻥﻦﻧﻨﻩﻪﻫﻬﻭﻮﻯﻰﻱﻲﻳﻴﻵﻶﻷﻸﻹﻺﻻﻼ'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strstr($string, $find); + $expected = 'klï½ï½Žï½ï½ï½‘rstuvwxyz'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'k'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ï½ï½‚cdefghij'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $find = 'K'; + $result = Multibyte::strstr($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strstr($string, $find); + $expected = 'アイウエオカキク'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::strstr($string, $find, true); + $expected = '。「」、・ヲァィゥェォャュョッー'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strstr($string, $find); + $expected = 'ハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ケコサシスセソタï¾ï¾‚テトナニヌネノ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strstr($string, $find); + $expected = 'őřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'ĤēĺļÅ, Å´'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'ĺļ'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'Ĥē'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strstr($string, $find); + $expected = 'o, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'Hell'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = Multibyte::strstr($string, $find); + $expected = 'World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'Wo'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'Hello, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = Multibyte::strstr($string, $find); + $expected = 'llo, World!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'll'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'He'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = Multibyte::strstr($string, $find); + $expected = 'rld!'; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rld'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'Hello, Wo'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strstr($string, $find); + $expected = 'ni'; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'Äi'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strstr($string, $find); + $expected = 'ći'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'mo'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strstr($string, $find); + $expected = 'žavni'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::strstr($string, $find, true); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strstr($string, $find); + $expected = '设为首页'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::strstr($string, $find, true); + $expected = '把百度'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strstr($string, $find); + $expected = '周永é¾'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::strstr($string, $find, true); + $expected = '一二三'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '二周'; + $result = Multibyte::strstr($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrtolower method + * + * @access public + * @return void + */ + function testUsingMbStrtolower() { + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~'; + $result = mb_strtolower($string); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@'; + $result = mb_strtolower($string); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@'; + $this->assertEqual($result, $expected); + + $string = 'À'; + $result = mb_strtolower($string); + $expected = 'à'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = mb_strtolower($string); + $expected = 'á'; + $this->assertEqual($result, $expected); + + $string = 'Â'; + $result = mb_strtolower($string); + $expected = 'â'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = mb_strtolower($string); + $expected = 'ã'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = mb_strtolower($string); + $expected = 'ä'; + $this->assertEqual($result, $expected); + + $string = 'Ã…'; + $result = mb_strtolower($string); + $expected = 'Ã¥'; + $this->assertEqual($result, $expected); + + $string = 'Æ'; + $result = mb_strtolower($string); + $expected = 'æ'; + $this->assertEqual($result, $expected); + + $string = 'Ç'; + $result = mb_strtolower($string); + $expected = 'ç'; + $this->assertEqual($result, $expected); + + $string = 'È'; + $result = mb_strtolower($string); + $expected = 'è'; + $this->assertEqual($result, $expected); + + $string = 'É'; + $result = mb_strtolower($string); + $expected = 'é'; + $this->assertEqual($result, $expected); + + $string = 'Ê'; + $result = mb_strtolower($string); + $expected = 'ê'; + $this->assertEqual($result, $expected); + + $string = 'Ë'; + $result = mb_strtolower($string); + $expected = 'ë'; + $this->assertEqual($result, $expected); + + $string = 'ÃŒ'; + $result = mb_strtolower($string); + $expected = 'ì'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = mb_strtolower($string); + $expected = 'í'; + $this->assertEqual($result, $expected); + + $string = 'ÃŽ'; + $result = mb_strtolower($string); + $expected = 'î'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = mb_strtolower($string); + $expected = 'ï'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = mb_strtolower($string); + $expected = 'ð'; + $this->assertEqual($result, $expected); + + $string = 'Ñ'; + $result = mb_strtolower($string); + $expected = 'ñ'; + $this->assertEqual($result, $expected); + + $string = 'Ã’'; + $result = mb_strtolower($string); + $expected = 'ò'; + $this->assertEqual($result, $expected); + + $string = 'Ó'; + $result = mb_strtolower($string); + $expected = 'ó'; + $this->assertEqual($result, $expected); + + $string = 'Ô'; + $result = mb_strtolower($string); + $expected = 'ô'; + $this->assertEqual($result, $expected); + + $string = 'Õ'; + $result = mb_strtolower($string); + $expected = 'õ'; + $this->assertEqual($result, $expected); + + $string = 'Ö'; + $result = mb_strtolower($string); + $expected = 'ö'; + $this->assertEqual($result, $expected); + + $string = 'Ø'; + $result = mb_strtolower($string); + $expected = 'ø'; + $this->assertEqual($result, $expected); + + $string = 'Ù'; + $result = mb_strtolower($string); + $expected = 'ù'; + $this->assertEqual($result, $expected); + + $string = 'Ú'; + $result = mb_strtolower($string); + $expected = 'ú'; + $this->assertEqual($result, $expected); + + $string = 'Û'; + $result = mb_strtolower($string); + $expected = 'û'; + $this->assertEqual($result, $expected); + + $string = 'Ãœ'; + $result = mb_strtolower($string); + $expected = 'ü'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = mb_strtolower($string); + $expected = 'ý'; + $this->assertEqual($result, $expected); + + $string = 'Þ'; + $result = mb_strtolower($string); + $expected = 'þ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $result = mb_strtolower($string); + $expected = 'àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ'; + $this->assertEqual($result, $expected); + + $string = 'Ä€'; + $result = mb_strtolower($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Ä‚'; + $result = mb_strtolower($string); + $expected = 'ă'; + $this->assertEqual($result, $expected); + + $string = 'Ä„'; + $result = mb_strtolower($string); + $expected = 'Ä…'; + $this->assertEqual($result, $expected); + + $string = 'Ć'; + $result = mb_strtolower($string); + $expected = 'ć'; + $this->assertEqual($result, $expected); + + $string = 'Ĉ'; + $result = mb_strtolower($string); + $expected = 'ĉ'; + $this->assertEqual($result, $expected); + + $string = 'ÄŠ'; + $result = mb_strtolower($string); + $expected = 'Ä‹'; + $this->assertEqual($result, $expected); + + $string = 'ÄŒ'; + $result = mb_strtolower($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'ÄŽ'; + $result = mb_strtolower($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = mb_strtolower($string); + $expected = 'Ä‘'; + $this->assertEqual($result, $expected); + + $string = 'Ä’'; + $result = mb_strtolower($string); + $expected = 'Ä“'; + $this->assertEqual($result, $expected); + + $string = 'Ä”'; + $result = mb_strtolower($string); + $expected = 'Ä•'; + $this->assertEqual($result, $expected); + + $string = 'Ä–'; + $result = mb_strtolower($string); + $expected = 'Ä—'; + $this->assertEqual($result, $expected); + + $string = 'Ę'; + $result = mb_strtolower($string); + $expected = 'Ä™'; + $this->assertEqual($result, $expected); + + $string = 'Äš'; + $result = mb_strtolower($string); + $expected = 'Ä›'; + $this->assertEqual($result, $expected); + + $string = 'Äœ'; + $result = mb_strtolower($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Äž'; + $result = mb_strtolower($string); + $expected = 'ÄŸ'; + $this->assertEqual($result, $expected); + + $string = 'Ä '; + $result = mb_strtolower($string); + $expected = 'Ä¡'; + $this->assertEqual($result, $expected); + + $string = 'Ä¢'; + $result = mb_strtolower($string); + $expected = 'Ä£'; + $this->assertEqual($result, $expected); + + $string = 'Ĥ'; + $result = mb_strtolower($string); + $expected = 'Ä¥'; + $this->assertEqual($result, $expected); + + $string = 'Ħ'; + $result = mb_strtolower($string); + $expected = 'ħ'; + $this->assertEqual($result, $expected); + + $string = 'Ĩ'; + $result = mb_strtolower($string); + $expected = 'Ä©'; + $this->assertEqual($result, $expected); + + $string = 'Ī'; + $result = mb_strtolower($string); + $expected = 'Ä«'; + $this->assertEqual($result, $expected); + + $string = 'Ĭ'; + $result = mb_strtolower($string); + $expected = 'Ä­'; + $this->assertEqual($result, $expected); + + $string = 'Ä®'; + $result = mb_strtolower($string); + $expected = 'į'; + $this->assertEqual($result, $expected); + + $string = 'IJ'; + $result = mb_strtolower($string); + $expected = 'ij'; + $this->assertEqual($result, $expected); + + $string = 'Ä´'; + $result = mb_strtolower($string); + $expected = 'ĵ'; + $this->assertEqual($result, $expected); + + $string = 'Ķ'; + $result = mb_strtolower($string); + $expected = 'Ä·'; + $this->assertEqual($result, $expected); + + $string = 'Ĺ'; + $result = mb_strtolower($string); + $expected = 'ĺ'; + $this->assertEqual($result, $expected); + + $string = 'Ä»'; + $result = mb_strtolower($string); + $expected = 'ļ'; + $this->assertEqual($result, $expected); + + $string = 'Ľ'; + $result = mb_strtolower($string); + $expected = 'ľ'; + $this->assertEqual($result, $expected); + + $string = 'Ä¿'; + $result = mb_strtolower($string); + $expected = 'Å€'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = mb_strtolower($string); + $expected = 'Å‚'; + $this->assertEqual($result, $expected); + + $string = 'Ń'; + $result = mb_strtolower($string); + $expected = 'Å„'; + $this->assertEqual($result, $expected); + + $string = 'Å…'; + $result = mb_strtolower($string); + $expected = 'ņ'; + $this->assertEqual($result, $expected); + + $string = 'Ň'; + $result = mb_strtolower($string); + $expected = 'ň'; + $this->assertEqual($result, $expected); + + $string = 'ÅŠ'; + $result = mb_strtolower($string); + $expected = 'Å‹'; + $this->assertEqual($result, $expected); + + $string = 'ÅŒ'; + $result = mb_strtolower($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'ÅŽ'; + $result = mb_strtolower($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = mb_strtolower($string); + $expected = 'Å‘'; + $this->assertEqual($result, $expected); + + $string = 'Å’'; + $result = mb_strtolower($string); + $expected = 'Å“'; + $this->assertEqual($result, $expected); + + $string = 'Å”'; + $result = mb_strtolower($string); + $expected = 'Å•'; + $this->assertEqual($result, $expected); + + $string = 'Å–'; + $result = mb_strtolower($string); + $expected = 'Å—'; + $this->assertEqual($result, $expected); + + $string = 'Ř'; + $result = mb_strtolower($string); + $expected = 'Å™'; + $this->assertEqual($result, $expected); + + $string = 'Åš'; + $result = mb_strtolower($string); + $expected = 'Å›'; + $this->assertEqual($result, $expected); + + $string = 'Åœ'; + $result = mb_strtolower($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'Åž'; + $result = mb_strtolower($string); + $expected = 'ÅŸ'; + $this->assertEqual($result, $expected); + + $string = 'Å '; + $result = mb_strtolower($string); + $expected = 'Å¡'; + $this->assertEqual($result, $expected); + + $string = 'Å¢'; + $result = mb_strtolower($string); + $expected = 'Å£'; + $this->assertEqual($result, $expected); + + $string = 'Ť'; + $result = mb_strtolower($string); + $expected = 'Å¥'; + $this->assertEqual($result, $expected); + + $string = 'Ŧ'; + $result = mb_strtolower($string); + $expected = 'ŧ'; + $this->assertEqual($result, $expected); + + $string = 'Ũ'; + $result = mb_strtolower($string); + $expected = 'Å©'; + $this->assertEqual($result, $expected); + + $string = 'Ū'; + $result = mb_strtolower($string); + $expected = 'Å«'; + $this->assertEqual($result, $expected); + + $string = 'Ŭ'; + $result = mb_strtolower($string); + $expected = 'Å­'; + $this->assertEqual($result, $expected); + + $string = 'Å®'; + $result = mb_strtolower($string); + $expected = 'ů'; + $this->assertEqual($result, $expected); + + $string = 'Å°'; + $result = mb_strtolower($string); + $expected = 'ű'; + $this->assertEqual($result, $expected); + + $string = 'Ų'; + $result = mb_strtolower($string); + $expected = 'ų'; + $this->assertEqual($result, $expected); + + $string = 'Å´'; + $result = mb_strtolower($string); + $expected = 'ŵ'; + $this->assertEqual($result, $expected); + + $string = 'Ŷ'; + $result = mb_strtolower($string); + $expected = 'Å·'; + $this->assertEqual($result, $expected); + + $string = 'Ź'; + $result = mb_strtolower($string); + $expected = 'ź'; + $this->assertEqual($result, $expected); + + $string = 'Å»'; + $result = mb_strtolower($string); + $expected = 'ż'; + $this->assertEqual($result, $expected); + + $string = 'Ž'; + $result = mb_strtolower($string); + $expected = 'ž'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $result = mb_strtolower($string); + $expected = 'ÄăąćĉċÄÄđēĕėęěÄğġģĥħĩīĭįijĵķĺļľŀłńņňŋÅÅőœŕŗřśÅşšţťŧũūŭůűųŵŷźżž'; + $this->assertEqual($result, $expected); + + $string = 'ĤĒĹĻŎ, Å´ÅŘĻĎ!'; + $result = mb_strtolower($string); + $expected = 'ĥēĺļÅ, ŵőřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĥēĺļÅ, ŵőřļÄ!'; + $result = mb_strtolower($string); + $expected = 'ĥēĺļÅ, ŵőřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ἈΙ'; + $result = mb_strtolower($string); + $expected = 'ἀι'; + $this->assertEqual($result, $expected); + + $string = 'ԀԂԄԆԈԊԌԎÔÔ’'; + $result = mb_strtolower($string); + $expected = 'ÔÔƒÔ…Ô‡Ô‰Ô‹ÔÔÔÔ’'; + $this->assertEqual($result, $expected); + + $string = 'Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€ÕÕ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕÕŽÕÕÕ‘Õ’Õ“Õ”Õ•Õ–Ö‡'; + $result = mb_strtolower($string); + $expected = 'Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€ÖÖ‚ÖƒÖ„Ö…Ö†Ö‡'; + $this->assertEqual($result, $expected); + + $string = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $result = mb_strtolower($string); + $expected = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $this->assertEqual($result, $expected); + + $string = 'ḀḂḄḆḈḊḌḎá¸á¸’ḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎá¹á¹’ṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎáºáº’ẔẖẗẘẙẚẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎá»á»’ỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸ'; + $result = mb_strtolower($string); + $expected = 'á¸á¸ƒá¸…ḇḉḋá¸á¸á¸‘ḓḕḗḙḛá¸á¸Ÿá¸¡á¸£á¸¥á¸§á¸©á¸«á¸­á¸¯á¸±á¸³á¸µá¸·á¸¹á¸»á¸½á¸¿á¹á¹ƒá¹…ṇṉṋá¹á¹á¹‘ṓṕṗṙṛá¹á¹Ÿá¹¡á¹£á¹¥á¹§á¹©á¹«á¹­á¹¯á¹±á¹³á¹µá¹·á¹¹á¹»á¹½á¹¿áºáºƒáº…ẇẉẋáºáºáº‘ẓẕẖẗẘẙẚạảấầẩẫậắằẳẵặẹẻẽếá»á»ƒá»…ệỉịá»á»á»‘ồổỗộớá»á»Ÿá»¡á»£á»¥á»§á»©á»«á»­á»¯á»±á»³á»µá»·á»¹'; + $this->assertEqual($result, $expected); + + $string = 'ΩKÅ'; + $result = mb_strtolower($string); + $expected = 'ωkÃ¥'; + $this->assertEqual($result, $expected); + + $string = 'ΩKÃ…'; + $result = mb_strtolower($string); + $expected = 'ωkÃ¥'; + $this->assertEqual($result, $expected); + +/* +mb_strtolower does not work for these strings. + + $string = 'ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯↃ'; + $result = mb_strtolower($string); + $expected = 'ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↄ'; + $this->assertEqual($result, $expected); + + $string = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀâ“ⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌâ“â“Žâ“'; + $result = mb_strtolower($string); + $expected = 'â“ⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜâ“ⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ'; + $this->assertEqual($result, $expected); + + $string = 'â°€â°â°‚ⰃⰄⰅⰆⰇⰈⰉⰊⰋⰌâ°â°Žâ°â°â°‘ⰒⰓⰔⰕⰖⰗⰘⰙⰚⰛⰜâ°â°žâ°Ÿâ° â°¡â°¢â°£â°¤â°¥â°¦â°§â°¨â°©â°ªâ°«â°¬â°­â°®'; + $result = mb_strtolower($string); + $expected = 'ⰰⰱⰲⰳⰴⰵⰶⰷⰸⰹⰺⰻⰼⰽⰾⰿⱀâ±â±‚ⱃⱄⱅⱆⱇⱈⱉⱊⱋⱌâ±â±Žâ±â±â±‘ⱒⱓⱔⱕⱖⱗⱘⱙⱚⱛⱜâ±â±ž'; + $this->assertEqual($result, $expected); + + $string = 'ⲀⲂⲄⲆⲈⲊⲌⲎâ²â²’ⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⲲⲴⲶⲸⲺⲼⲾⳀⳂⳄⳆⳈⳊⳌⳎâ³â³’ⳔⳖⳘⳚⳜⳞⳠⳢ'; + $result = mb_strtolower($string); + $expected = 'â²â²ƒâ²…ⲇⲉⲋâ²â²â²‘ⲓⲕⲗⲙⲛâ²â²Ÿâ²¡â²£â²¥â²§â²©â²«â²­â²¯â²±â²³â²µâ²·â²¹â²»â²½â²¿â³â³ƒâ³…ⳇⳉⳋâ³â³â³‘ⳓⳕⳗⳙⳛâ³â³Ÿâ³¡â³£'; + $this->assertEqual($result, $expected); +*/ + $string = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $result = mb_strtolower($string); + $expected = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrtolower method + * + * @access public + * @return void + */ + function testMultibyteStrtolower() { + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~'; + $result = Multibyte::strtolower($string); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@'; + $result = Multibyte::strtolower($string); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@'; + $this->assertEqual($result, $expected); + + $string = 'À'; + $result = Multibyte::strtolower($string); + $expected = 'à'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = Multibyte::strtolower($string); + $expected = 'á'; + $this->assertEqual($result, $expected); + + $string = 'Â'; + $result = Multibyte::strtolower($string); + $expected = 'â'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = Multibyte::strtolower($string); + $expected = 'ã'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = Multibyte::strtolower($string); + $expected = 'ä'; + $this->assertEqual($result, $expected); + + $string = 'Ã…'; + $result = Multibyte::strtolower($string); + $expected = 'Ã¥'; + $this->assertEqual($result, $expected); + + $string = 'Æ'; + $result = Multibyte::strtolower($string); + $expected = 'æ'; + $this->assertEqual($result, $expected); + + $string = 'Ç'; + $result = Multibyte::strtolower($string); + $expected = 'ç'; + $this->assertEqual($result, $expected); + + $string = 'È'; + $result = Multibyte::strtolower($string); + $expected = 'è'; + $this->assertEqual($result, $expected); + + $string = 'É'; + $result = Multibyte::strtolower($string); + $expected = 'é'; + $this->assertEqual($result, $expected); + + $string = 'Ê'; + $result = Multibyte::strtolower($string); + $expected = 'ê'; + $this->assertEqual($result, $expected); + + $string = 'Ë'; + $result = Multibyte::strtolower($string); + $expected = 'ë'; + $this->assertEqual($result, $expected); + + $string = 'ÃŒ'; + $result = Multibyte::strtolower($string); + $expected = 'ì'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = Multibyte::strtolower($string); + $expected = 'í'; + $this->assertEqual($result, $expected); + + $string = 'ÃŽ'; + $result = Multibyte::strtolower($string); + $expected = 'î'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = Multibyte::strtolower($string); + $expected = 'ï'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = Multibyte::strtolower($string); + $expected = 'ð'; + $this->assertEqual($result, $expected); + + $string = 'Ñ'; + $result = Multibyte::strtolower($string); + $expected = 'ñ'; + $this->assertEqual($result, $expected); + + $string = 'Ã’'; + $result = Multibyte::strtolower($string); + $expected = 'ò'; + $this->assertEqual($result, $expected); + + $string = 'Ó'; + $result = Multibyte::strtolower($string); + $expected = 'ó'; + $this->assertEqual($result, $expected); + + $string = 'Ô'; + $result = Multibyte::strtolower($string); + $expected = 'ô'; + $this->assertEqual($result, $expected); + + $string = 'Õ'; + $result = Multibyte::strtolower($string); + $expected = 'õ'; + $this->assertEqual($result, $expected); + + $string = 'Ö'; + $result = Multibyte::strtolower($string); + $expected = 'ö'; + $this->assertEqual($result, $expected); + + $string = 'Ø'; + $result = Multibyte::strtolower($string); + $expected = 'ø'; + $this->assertEqual($result, $expected); + + $string = 'Ù'; + $result = Multibyte::strtolower($string); + $expected = 'ù'; + $this->assertEqual($result, $expected); + + $string = 'Ú'; + $result = Multibyte::strtolower($string); + $expected = 'ú'; + $this->assertEqual($result, $expected); + + $string = 'Û'; + $result = Multibyte::strtolower($string); + $expected = 'û'; + $this->assertEqual($result, $expected); + + $string = 'Ãœ'; + $result = Multibyte::strtolower($string); + $expected = 'ü'; + $this->assertEqual($result, $expected); + + $string = 'Ã'; + $result = Multibyte::strtolower($string); + $expected = 'ý'; + $this->assertEqual($result, $expected); + + $string = 'Þ'; + $result = Multibyte::strtolower($string); + $expected = 'þ'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $result = Multibyte::strtolower($string); + $expected = 'àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ'; + $this->assertEqual($result, $expected); + + $string = 'Ä€'; + $result = Multibyte::strtolower($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Ä‚'; + $result = Multibyte::strtolower($string); + $expected = 'ă'; + $this->assertEqual($result, $expected); + + $string = 'Ä„'; + $result = Multibyte::strtolower($string); + $expected = 'Ä…'; + $this->assertEqual($result, $expected); + + $string = 'Ć'; + $result = Multibyte::strtolower($string); + $expected = 'ć'; + $this->assertEqual($result, $expected); + + $string = 'Ĉ'; + $result = Multibyte::strtolower($string); + $expected = 'ĉ'; + $this->assertEqual($result, $expected); + + $string = 'ÄŠ'; + $result = Multibyte::strtolower($string); + $expected = 'Ä‹'; + $this->assertEqual($result, $expected); + + $string = 'ÄŒ'; + $result = Multibyte::strtolower($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'ÄŽ'; + $result = Multibyte::strtolower($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = Multibyte::strtolower($string); + $expected = 'Ä‘'; + $this->assertEqual($result, $expected); + + $string = 'Ä’'; + $result = Multibyte::strtolower($string); + $expected = 'Ä“'; + $this->assertEqual($result, $expected); + + $string = 'Ä”'; + $result = Multibyte::strtolower($string); + $expected = 'Ä•'; + $this->assertEqual($result, $expected); + + $string = 'Ä–'; + $result = Multibyte::strtolower($string); + $expected = 'Ä—'; + $this->assertEqual($result, $expected); + + $string = 'Ę'; + $result = Multibyte::strtolower($string); + $expected = 'Ä™'; + $this->assertEqual($result, $expected); + + $string = 'Äš'; + $result = Multibyte::strtolower($string); + $expected = 'Ä›'; + $this->assertEqual($result, $expected); + + $string = 'Äœ'; + $result = Multibyte::strtolower($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Äž'; + $result = Multibyte::strtolower($string); + $expected = 'ÄŸ'; + $this->assertEqual($result, $expected); + + $string = 'Ä '; + $result = Multibyte::strtolower($string); + $expected = 'Ä¡'; + $this->assertEqual($result, $expected); + + $string = 'Ä¢'; + $result = Multibyte::strtolower($string); + $expected = 'Ä£'; + $this->assertEqual($result, $expected); + + $string = 'Ĥ'; + $result = Multibyte::strtolower($string); + $expected = 'Ä¥'; + $this->assertEqual($result, $expected); + + $string = 'Ħ'; + $result = Multibyte::strtolower($string); + $expected = 'ħ'; + $this->assertEqual($result, $expected); + + $string = 'Ĩ'; + $result = Multibyte::strtolower($string); + $expected = 'Ä©'; + $this->assertEqual($result, $expected); + + $string = 'Ī'; + $result = Multibyte::strtolower($string); + $expected = 'Ä«'; + $this->assertEqual($result, $expected); + + $string = 'Ĭ'; + $result = Multibyte::strtolower($string); + $expected = 'Ä­'; + $this->assertEqual($result, $expected); + + $string = 'Ä®'; + $result = Multibyte::strtolower($string); + $expected = 'į'; + $this->assertEqual($result, $expected); + + $string = 'IJ'; + $result = Multibyte::strtolower($string); + $expected = 'ij'; + $this->assertEqual($result, $expected); + + $string = 'Ä´'; + $result = Multibyte::strtolower($string); + $expected = 'ĵ'; + $this->assertEqual($result, $expected); + + $string = 'Ķ'; + $result = Multibyte::strtolower($string); + $expected = 'Ä·'; + $this->assertEqual($result, $expected); + + $string = 'Ĺ'; + $result = Multibyte::strtolower($string); + $expected = 'ĺ'; + $this->assertEqual($result, $expected); + + $string = 'Ä»'; + $result = Multibyte::strtolower($string); + $expected = 'ļ'; + $this->assertEqual($result, $expected); + + $string = 'Ľ'; + $result = Multibyte::strtolower($string); + $expected = 'ľ'; + $this->assertEqual($result, $expected); + + $string = 'Ä¿'; + $result = Multibyte::strtolower($string); + $expected = 'Å€'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = Multibyte::strtolower($string); + $expected = 'Å‚'; + $this->assertEqual($result, $expected); + + $string = 'Ń'; + $result = Multibyte::strtolower($string); + $expected = 'Å„'; + $this->assertEqual($result, $expected); + + $string = 'Å…'; + $result = Multibyte::strtolower($string); + $expected = 'ņ'; + $this->assertEqual($result, $expected); + + $string = 'Ň'; + $result = Multibyte::strtolower($string); + $expected = 'ň'; + $this->assertEqual($result, $expected); + + $string = 'ÅŠ'; + $result = Multibyte::strtolower($string); + $expected = 'Å‹'; + $this->assertEqual($result, $expected); + + $string = 'ÅŒ'; + $result = Multibyte::strtolower($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'ÅŽ'; + $result = Multibyte::strtolower($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = Multibyte::strtolower($string); + $expected = 'Å‘'; + $this->assertEqual($result, $expected); + + $string = 'Å’'; + $result = Multibyte::strtolower($string); + $expected = 'Å“'; + $this->assertEqual($result, $expected); + + $string = 'Å”'; + $result = Multibyte::strtolower($string); + $expected = 'Å•'; + $this->assertEqual($result, $expected); + + $string = 'Å–'; + $result = Multibyte::strtolower($string); + $expected = 'Å—'; + $this->assertEqual($result, $expected); + + $string = 'Ř'; + $result = Multibyte::strtolower($string); + $expected = 'Å™'; + $this->assertEqual($result, $expected); + + $string = 'Åš'; + $result = Multibyte::strtolower($string); + $expected = 'Å›'; + $this->assertEqual($result, $expected); + + $string = 'Åœ'; + $result = Multibyte::strtolower($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'Åž'; + $result = Multibyte::strtolower($string); + $expected = 'ÅŸ'; + $this->assertEqual($result, $expected); + + $string = 'Å '; + $result = Multibyte::strtolower($string); + $expected = 'Å¡'; + $this->assertEqual($result, $expected); + + $string = 'Å¢'; + $result = Multibyte::strtolower($string); + $expected = 'Å£'; + $this->assertEqual($result, $expected); + + $string = 'Ť'; + $result = Multibyte::strtolower($string); + $expected = 'Å¥'; + $this->assertEqual($result, $expected); + + $string = 'Ŧ'; + $result = Multibyte::strtolower($string); + $expected = 'ŧ'; + $this->assertEqual($result, $expected); + + $string = 'Ũ'; + $result = Multibyte::strtolower($string); + $expected = 'Å©'; + $this->assertEqual($result, $expected); + + $string = 'Ū'; + $result = Multibyte::strtolower($string); + $expected = 'Å«'; + $this->assertEqual($result, $expected); + + $string = 'Ŭ'; + $result = Multibyte::strtolower($string); + $expected = 'Å­'; + $this->assertEqual($result, $expected); + + $string = 'Å®'; + $result = Multibyte::strtolower($string); + $expected = 'ů'; + $this->assertEqual($result, $expected); + + $string = 'Å°'; + $result = Multibyte::strtolower($string); + $expected = 'ű'; + $this->assertEqual($result, $expected); + + $string = 'Ų'; + $result = Multibyte::strtolower($string); + $expected = 'ų'; + $this->assertEqual($result, $expected); + + $string = 'Å´'; + $result = Multibyte::strtolower($string); + $expected = 'ŵ'; + $this->assertEqual($result, $expected); + + $string = 'Ŷ'; + $result = Multibyte::strtolower($string); + $expected = 'Å·'; + $this->assertEqual($result, $expected); + + $string = 'Ź'; + $result = Multibyte::strtolower($string); + $expected = 'ź'; + $this->assertEqual($result, $expected); + + $string = 'Å»'; + $result = Multibyte::strtolower($string); + $expected = 'ż'; + $this->assertEqual($result, $expected); + + $string = 'Ž'; + $result = Multibyte::strtolower($string); + $expected = 'ž'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $result = Multibyte::strtolower($string); + $expected = 'ÄăąćĉċÄÄđēĕėęěÄğġģĥħĩīĭįijĵķĺļľŀłńņňŋÅÅőœŕŗřśÅşšţťŧũūŭůűųŵŷźżž'; + $this->assertEqual($result, $expected); + + $string = 'ĤĒĹĻŎ, Å´ÅŘĻĎ!'; + $result = Multibyte::strtolower($string); + $expected = 'ĥēĺļÅ, ŵőřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ĥēĺļÅ, ŵőřļÄ!'; + $result = Multibyte::strtolower($string); + $expected = 'ĥēĺļÅ, ŵőřļÄ!'; + $this->assertEqual($result, $expected); + + $string = 'ἈΙ'; + $result = Multibyte::strtolower($string); + $expected = 'ἀι'; + $this->assertEqual($result, $expected); + + $string = 'ԀԂԄԆԈԊԌԎÔÔ’'; + $result = Multibyte::strtolower($string); + $expected = 'ÔÔƒÔ…Ô‡Ô‰Ô‹ÔÔÔÔ’'; + $this->assertEqual($result, $expected); + + $string = 'Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€ÕÕ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕÕŽÕÕÕ‘Õ’Õ“Õ”Õ•Õ–Ö‡'; + $result = Multibyte::strtolower($string); + $expected = 'Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€ÖÖ‚ÖƒÖ„Ö…Ö†Ö‡'; + $this->assertEqual($result, $expected); + + $string = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $result = Multibyte::strtolower($string); + $expected = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $this->assertEqual($result, $expected); + + $string = 'ḀḂḄḆḈḊḌḎá¸á¸’ḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎá¹á¹’ṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎáºáº’ẔẖẗẘẙẚẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎá»á»’ỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸ'; + $result = Multibyte::strtolower($string); + $expected = 'á¸á¸ƒá¸…ḇḉḋá¸á¸á¸‘ḓḕḗḙḛá¸á¸Ÿá¸¡á¸£á¸¥á¸§á¸©á¸«á¸­á¸¯á¸±á¸³á¸µá¸·á¸¹á¸»á¸½á¸¿á¹á¹ƒá¹…ṇṉṋá¹á¹á¹‘ṓṕṗṙṛá¹á¹Ÿá¹¡á¹£á¹¥á¹§á¹©á¹«á¹­á¹¯á¹±á¹³á¹µá¹·á¹¹á¹»á¹½á¹¿áºáºƒáº…ẇẉẋáºáºáº‘ẓẕẖẗẘẙẚạảấầẩẫậắằẳẵặẹẻẽếá»á»ƒá»…ệỉịá»á»á»‘ồổỗộớá»á»Ÿá»¡á»£á»¥á»§á»©á»«á»­á»¯á»±á»³á»µá»·á»¹'; + $this->assertEqual($result, $expected); + + $string = 'ΩKÅℲ'; + $result = Multibyte::strtolower($string); + $expected = 'ωkåⅎ'; + $this->assertEqual($result, $expected); + + $string = 'ΩKÅ'; + $result = Multibyte::strtolower($string); + $expected = 'ωkÃ¥'; + $this->assertEqual($result, $expected); + + $string = 'ΩKÃ…'; + $result = Multibyte::strtolower($string); + $expected = 'ωkÃ¥'; + $this->assertEqual($result, $expected); + + $string = 'ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯↃ'; + $result = Multibyte::strtolower($string); + $expected = 'ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↄ'; + $this->assertEqual($result, $expected); + + $string = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀâ“ⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌâ“â“Žâ“'; + $result = Multibyte::strtolower($string); + $expected = 'â“ⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜâ“ⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ'; + $this->assertEqual($result, $expected); + + $string = 'â°€â°â°‚ⰃⰄⰅⰆⰇⰈⰉⰊⰋⰌâ°â°Žâ°â°â°‘ⰒⰓⰔⰕⰖⰗⰘⰙⰚⰛⰜâ°â°žâ°Ÿâ° â°¡â°¢â°£â°¤â°¥â°¦â°§â°¨â°©â°ªâ°«â°¬â°­â°®'; + $result = Multibyte::strtolower($string); + $expected = 'ⰰⰱⰲⰳⰴⰵⰶⰷⰸⰹⰺⰻⰼⰽⰾⰿⱀâ±â±‚ⱃⱄⱅⱆⱇⱈⱉⱊⱋⱌâ±â±Žâ±â±â±‘ⱒⱓⱔⱕⱖⱗⱘⱙⱚⱛⱜâ±â±ž'; + $this->assertEqual($result, $expected); + + $string = 'ⲀⲂⲄⲆⲈⲊⲌⲎâ²â²’ⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⲲⲴⲶⲸⲺⲼⲾⳀⳂⳄⳆⳈⳊⳌⳎâ³â³’ⳔⳖⳘⳚⳜⳞⳠⳢ'; + $result = Multibyte::strtolower($string); + $expected = 'â²â²ƒâ²…ⲇⲉⲋâ²â²â²‘ⲓⲕⲗⲙⲛâ²â²Ÿâ²¡â²£â²¥â²§â²©â²«â²­â²¯â²±â²³â²µâ²·â²¹â²»â²½â²¿â³â³ƒâ³…ⳇⳉⳋâ³â³â³‘ⳓⳕⳗⳙⳛâ³â³Ÿâ³¡â³£'; + $this->assertEqual($result, $expected); + + $string = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $result = Multibyte::strtolower($string); + $expected = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbStrtoupper method + * + * @access public + * @return void + */ + function testUsingMbStrtoupper() { + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $result = mb_strtoupper($string); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@'; + $result = mb_strtoupper($string); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@'; + $this->assertEqual($result, $expected); + + $string = 'à'; + $result = mb_strtoupper($string); + $expected = 'À'; + $this->assertEqual($result, $expected); + + $string = 'á'; + $result = mb_strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'â'; + $result = mb_strtoupper($string); + $expected = 'Â'; + $this->assertEqual($result, $expected); + + $string = 'ã'; + $result = mb_strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'ä'; + $result = mb_strtoupper($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Ã¥'; + $result = mb_strtoupper($string); + $expected = 'Ã…'; + $this->assertEqual($result, $expected); + + $string = 'æ'; + $result = mb_strtoupper($string); + $expected = 'Æ'; + $this->assertEqual($result, $expected); + + $string = 'ç'; + $result = mb_strtoupper($string); + $expected = 'Ç'; + $this->assertEqual($result, $expected); + + $string = 'è'; + $result = mb_strtoupper($string); + $expected = 'È'; + $this->assertEqual($result, $expected); + + $string = 'é'; + $result = mb_strtoupper($string); + $expected = 'É'; + $this->assertEqual($result, $expected); + + $string = 'ê'; + $result = mb_strtoupper($string); + $expected = 'Ê'; + $this->assertEqual($result, $expected); + + $string = 'ë'; + $result = mb_strtoupper($string); + $expected = 'Ë'; + $this->assertEqual($result, $expected); + + $string = 'ì'; + $result = mb_strtoupper($string); + $expected = 'ÃŒ'; + $this->assertEqual($result, $expected); + + $string = 'í'; + $result = mb_strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'î'; + $result = mb_strtoupper($string); + $expected = 'ÃŽ'; + $this->assertEqual($result, $expected); + + $string = 'ï'; + $result = mb_strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'ð'; + $result = mb_strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'ñ'; + $result = mb_strtoupper($string); + $expected = 'Ñ'; + $this->assertEqual($result, $expected); + + $string = 'ò'; + $result = mb_strtoupper($string); + $expected = 'Ã’'; + $this->assertEqual($result, $expected); + + $string = 'ó'; + $result = mb_strtoupper($string); + $expected = 'Ó'; + $this->assertEqual($result, $expected); + + $string = 'ô'; + $result = mb_strtoupper($string); + $expected = 'Ô'; + $this->assertEqual($result, $expected); + + $string = 'õ'; + $result = mb_strtoupper($string); + $expected = 'Õ'; + $this->assertEqual($result, $expected); + + $string = 'ö'; + $result = mb_strtoupper($string); + $expected = 'Ö'; + $this->assertEqual($result, $expected); + + $string = 'ø'; + $result = mb_strtoupper($string); + $expected = 'Ø'; + $this->assertEqual($result, $expected); + + $string = 'ù'; + $result = mb_strtoupper($string); + $expected = 'Ù'; + $this->assertEqual($result, $expected); + + $string = 'ú'; + $result = mb_strtoupper($string); + $expected = 'Ú'; + $this->assertEqual($result, $expected); + + $string = 'û'; + $result = mb_strtoupper($string); + $expected = 'Û'; + $this->assertEqual($result, $expected); + + $string = 'ü'; + $result = mb_strtoupper($string); + $expected = 'Ãœ'; + $this->assertEqual($result, $expected); + + $string = 'ý'; + $result = mb_strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'þ'; + $result = mb_strtoupper($string); + $expected = 'Þ'; + $this->assertEqual($result, $expected); + + $string = 'àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ'; + $result = mb_strtoupper($string); + $expected = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = mb_strtoupper($string); + $expected = 'Ä€'; + $this->assertEqual($result, $expected); + + $string = 'ă'; + $result = mb_strtoupper($string); + $expected = 'Ä‚'; + $this->assertEqual($result, $expected); + + $string = 'Ä…'; + $result = mb_strtoupper($string); + $expected = 'Ä„'; + $this->assertEqual($result, $expected); + + $string = 'ć'; + $result = mb_strtoupper($string); + $expected = 'Ć'; + $this->assertEqual($result, $expected); + + $string = 'ĉ'; + $result = mb_strtoupper($string); + $expected = 'Ĉ'; + $this->assertEqual($result, $expected); + + $string = 'Ä‹'; + $result = mb_strtoupper($string); + $expected = 'ÄŠ'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = mb_strtoupper($string); + $expected = 'ÄŒ'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = mb_strtoupper($string); + $expected = 'ÄŽ'; + $this->assertEqual($result, $expected); + + $string = 'Ä‘'; + $result = mb_strtoupper($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Ä“'; + $result = mb_strtoupper($string); + $expected = 'Ä’'; + $this->assertEqual($result, $expected); + + $string = 'Ä•'; + $result = mb_strtoupper($string); + $expected = 'Ä”'; + $this->assertEqual($result, $expected); + + $string = 'Ä—'; + $result = mb_strtoupper($string); + $expected = 'Ä–'; + $this->assertEqual($result, $expected); + + $string = 'Ä™'; + $result = mb_strtoupper($string); + $expected = 'Ę'; + $this->assertEqual($result, $expected); + + $string = 'Ä›'; + $result = mb_strtoupper($string); + $expected = 'Äš'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = mb_strtoupper($string); + $expected = 'Äœ'; + $this->assertEqual($result, $expected); + + $string = 'ÄŸ'; + $result = mb_strtoupper($string); + $expected = 'Äž'; + $this->assertEqual($result, $expected); + + $string = 'Ä¡'; + $result = mb_strtoupper($string); + $expected = 'Ä '; + $this->assertEqual($result, $expected); + + $string = 'Ä£'; + $result = mb_strtoupper($string); + $expected = 'Ä¢'; + $this->assertEqual($result, $expected); + + $string = 'Ä¥'; + $result = mb_strtoupper($string); + $expected = 'Ĥ'; + $this->assertEqual($result, $expected); + + $string = 'ħ'; + $result = mb_strtoupper($string); + $expected = 'Ħ'; + $this->assertEqual($result, $expected); + + $string = 'Ä©'; + $result = mb_strtoupper($string); + $expected = 'Ĩ'; + $this->assertEqual($result, $expected); + + $string = 'Ä«'; + $result = mb_strtoupper($string); + $expected = 'Ī'; + $this->assertEqual($result, $expected); + + $string = 'Ä­'; + $result = mb_strtoupper($string); + $expected = 'Ĭ'; + $this->assertEqual($result, $expected); + + $string = 'į'; + $result = mb_strtoupper($string); + $expected = 'Ä®'; + $this->assertEqual($result, $expected); + + $string = 'ij'; + $result = mb_strtoupper($string); + $expected = 'IJ'; + $this->assertEqual($result, $expected); + + $string = 'ĵ'; + $result = mb_strtoupper($string); + $expected = 'Ä´'; + $this->assertEqual($result, $expected); + + $string = 'Ä·'; + $result = mb_strtoupper($string); + $expected = 'Ķ'; + $this->assertEqual($result, $expected); + + $string = 'ĺ'; + $result = mb_strtoupper($string); + $expected = 'Ĺ'; + $this->assertEqual($result, $expected); + + $string = 'ļ'; + $result = mb_strtoupper($string); + $expected = 'Ä»'; + $this->assertEqual($result, $expected); + + $string = 'ľ'; + $result = mb_strtoupper($string); + $expected = 'Ľ'; + $this->assertEqual($result, $expected); + + $string = 'Å€'; + $result = mb_strtoupper($string); + $expected = 'Ä¿'; + $this->assertEqual($result, $expected); + + $string = 'Å‚'; + $result = mb_strtoupper($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'Å„'; + $result = mb_strtoupper($string); + $expected = 'Ń'; + $this->assertEqual($result, $expected); + + $string = 'ņ'; + $result = mb_strtoupper($string); + $expected = 'Å…'; + $this->assertEqual($result, $expected); + + $string = 'ň'; + $result = mb_strtoupper($string); + $expected = 'Ň'; + $this->assertEqual($result, $expected); + + $string = 'Å‹'; + $result = mb_strtoupper($string); + $expected = 'ÅŠ'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = mb_strtoupper($string); + $expected = 'ÅŒ'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = mb_strtoupper($string); + $expected = 'ÅŽ'; + $this->assertEqual($result, $expected); + + $string = 'Å‘'; + $result = mb_strtoupper($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'Å“'; + $result = mb_strtoupper($string); + $expected = 'Å’'; + $this->assertEqual($result, $expected); + + $string = 'Å•'; + $result = mb_strtoupper($string); + $expected = 'Å”'; + $this->assertEqual($result, $expected); + + $string = 'Å—'; + $result = mb_strtoupper($string); + $expected = 'Å–'; + $this->assertEqual($result, $expected); + + $string = 'Å™'; + $result = mb_strtoupper($string); + $expected = 'Ř'; + $this->assertEqual($result, $expected); + + $string = 'Å›'; + $result = mb_strtoupper($string); + $expected = 'Åš'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = mb_strtoupper($string); + $expected = 'Åœ'; + $this->assertEqual($result, $expected); + + $string = 'ÅŸ'; + $result = mb_strtoupper($string); + $expected = 'Åž'; + $this->assertEqual($result, $expected); + + $string = 'Å¡'; + $result = mb_strtoupper($string); + $expected = 'Å '; + $this->assertEqual($result, $expected); + + $string = 'Å£'; + $result = mb_strtoupper($string); + $expected = 'Å¢'; + $this->assertEqual($result, $expected); + + $string = 'Å¥'; + $result = mb_strtoupper($string); + $expected = 'Ť'; + $this->assertEqual($result, $expected); + + $string = 'ŧ'; + $result = mb_strtoupper($string); + $expected = 'Ŧ'; + $this->assertEqual($result, $expected); + + $string = 'Å©'; + $result = mb_strtoupper($string); + $expected = 'Ũ'; + $this->assertEqual($result, $expected); + + $string = 'Å«'; + $result = mb_strtoupper($string); + $expected = 'Ū'; + $this->assertEqual($result, $expected); + + $string = 'Å­'; + $result = mb_strtoupper($string); + $expected = 'Ŭ'; + $this->assertEqual($result, $expected); + + $string = 'ů'; + $result = mb_strtoupper($string); + $expected = 'Å®'; + $this->assertEqual($result, $expected); + + $string = 'ű'; + $result = mb_strtoupper($string); + $expected = 'Å°'; + $this->assertEqual($result, $expected); + + $string = 'ų'; + $result = mb_strtoupper($string); + $expected = 'Ų'; + $this->assertEqual($result, $expected); + + $string = 'ŵ'; + $result = mb_strtoupper($string); + $expected = 'Å´'; + $this->assertEqual($result, $expected); + + $string = 'Å·'; + $result = mb_strtoupper($string); + $expected = 'Ŷ'; + $this->assertEqual($result, $expected); + + $string = 'ź'; + $result = mb_strtoupper($string); + $expected = 'Ź'; + $this->assertEqual($result, $expected); + + $string = 'ż'; + $result = mb_strtoupper($string); + $expected = 'Å»'; + $this->assertEqual($result, $expected); + + $string = 'ž'; + $result = mb_strtoupper($string); + $expected = 'Ž'; + $this->assertEqual($result, $expected); + + $string = 'ÄăąćĉċÄÄđēĕėęěÄğġģĥħĩīĭįijĵķĺļľŀłńņňŋÅÅőœŕŗřśÅşšţťŧũūŭůűųŵŷźżž'; + $result = mb_strtoupper($string); + $expected = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = mb_strtoupper($string); + $expected = 'ĤĒĹĻŎ, Å´ÅŘĻĎ!'; + $this->assertEqual($result, $expected); + + $string = 'ἀι'; + $result = mb_strtoupper($string); + $expected = 'ἈΙ'; + $this->assertEqual($result, $expected); + + $string = 'ÔÔƒÔ…Ô‡Ô‰Ô‹ÔÔÔÔ’'; + $result = mb_strtoupper($string); + $expected = 'ԀԂԄԆԈԊԌԎÔÔ’'; + $this->assertEqual($result, $expected); + + $string = 'Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€ÖÖ‚ÖƒÖ„Ö…Ö†Ö‡'; + $result = mb_strtoupper($string); + $expected = 'Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€ÕÕ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕÕŽÕÕÕ‘Õ’Õ“Õ”Õ•Õ–Ö‡'; + $this->assertEqual($result, $expected); + + $string = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $result = mb_strtoupper($string); + $expected = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $this->assertEqual($result, $expected); + + $string = 'á¸á¸ƒá¸…ḇḉḋá¸á¸á¸‘ḓḕḗḙḛá¸á¸Ÿá¸¡á¸£á¸¥á¸§á¸©á¸«á¸­á¸¯á¸±á¸³á¸µá¸·á¸¹á¸»á¸½á¸¿á¹á¹ƒá¹…ṇṉṋá¹á¹á¹‘ṓṕṗṙṛá¹á¹Ÿá¹¡á¹£á¹¥á¹§á¹©á¹«á¹­á¹¯á¹±á¹³á¹µá¹·á¹¹á¹»á¹½á¹¿áºáºƒáº…ẇẉẋáºáºáº‘ẓẕẖẗẘẙẚạảấầẩẫậắằẳẵặẹẻẽếá»á»ƒá»…ệỉịá»á»á»‘ồổỗộớá»á»Ÿá»¡á»£á»¥á»§á»©á»«á»­á»¯á»±á»³á»µá»·á»¹'; + $result = mb_strtoupper($string); + $expected = 'ḀḂḄḆḈḊḌḎá¸á¸’ḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎá¹á¹’ṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎáºáº’ẔẖẗẘẙẚẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎá»á»’ỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸ'; + $this->assertEqual($result, $expected); + + $string = 'ωkÃ¥'; + $result = mb_strtoupper($string); + $expected = 'ΩKÃ…'; + $this->assertEqual($result, $expected); + +/* +mb_strtoupper does not work for these strings. + + $string = 'ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↄ'; + $result = mb_strtoupper($string); + $expected = 'ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯↃ'; + $this->assertEqual($result, $expected); + + $string = 'â“ⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜâ“ⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ'; + $result = mb_strtoupper($string); + $expected = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀâ“ⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌâ“â“Žâ“'; + $this->assertEqual($result, $expected); + + $string = 'ⰰⰱⰲⰳⰴⰵⰶⰷⰸⰹⰺⰻⰼⰽⰾⰿⱀâ±â±‚ⱃⱄⱅⱆⱇⱈⱉⱊⱋⱌâ±â±Žâ±â±â±‘ⱒⱓⱔⱕⱖⱗⱘⱙⱚⱛⱜâ±â±ž'; + $result = mb_strtoupper($string); + $expected = 'â°€â°â°‚ⰃⰄⰅⰆⰇⰈⰉⰊⰋⰌâ°â°Žâ°â°â°‘ⰒⰓⰔⰕⰖⰗⰘⰙⰚⰛⰜâ°â°žâ°Ÿâ° â°¡â°¢â°£â°¤â°¥â°¦â°§â°¨â°©â°ªâ°«â°¬â°­â°®'; + $this->assertEqual($result, $expected); + + $string = 'â²â²ƒâ²…ⲇⲉⲋâ²â²â²‘ⲓⲕⲗⲙⲛâ²â²Ÿâ²¡â²£â²¥â²§â²©â²«â²­â²¯â²±â²³â²µâ²·â²¹â²»â²½â²¿â³â³ƒâ³…ⳇⳉⳋâ³â³â³‘ⳓⳕⳗⳙⳛâ³â³Ÿâ³¡â³£'; + $result = mb_strtoupper($string); + $expected = 'ⲀⲂⲄⲆⲈⲊⲌⲎâ²â²’ⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⲲⲴⲶⲸⲺⲼⲾⳀⳂⳄⳆⳈⳊⳌⳎâ³â³’ⳔⳖⳘⳚⳜⳞⳠⳢ'; + $this->assertEqual($result, $expected); +*/ + $string = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $result = mb_strtoupper($string); + $expected = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteStrtoupper method + * + * @access public + * @return void + */ + function testMultibyteStrtoupper() { + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $result = Multibyte::strtoupper($string); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@'; + $result = Multibyte::strtoupper($string); + $expected = '!"#$%&\'()*+,-./0123456789:;<=>?@'; + $this->assertEqual($result, $expected); + + $string = 'à'; + $result = Multibyte::strtoupper($string); + $expected = 'À'; + $this->assertEqual($result, $expected); + + $string = 'á'; + $result = Multibyte::strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'â'; + $result = Multibyte::strtoupper($string); + $expected = 'Â'; + $this->assertEqual($result, $expected); + + $string = 'ã'; + $result = Multibyte::strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'ä'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Ã¥'; + $result = Multibyte::strtoupper($string); + $expected = 'Ã…'; + $this->assertEqual($result, $expected); + + $string = 'æ'; + $result = Multibyte::strtoupper($string); + $expected = 'Æ'; + $this->assertEqual($result, $expected); + + $string = 'ç'; + $result = Multibyte::strtoupper($string); + $expected = 'Ç'; + $this->assertEqual($result, $expected); + + $string = 'è'; + $result = Multibyte::strtoupper($string); + $expected = 'È'; + $this->assertEqual($result, $expected); + + $string = 'é'; + $result = Multibyte::strtoupper($string); + $expected = 'É'; + $this->assertEqual($result, $expected); + + $string = 'ê'; + $result = Multibyte::strtoupper($string); + $expected = 'Ê'; + $this->assertEqual($result, $expected); + + $string = 'ë'; + $result = Multibyte::strtoupper($string); + $expected = 'Ë'; + $this->assertEqual($result, $expected); + + $string = 'ì'; + $result = Multibyte::strtoupper($string); + $expected = 'ÃŒ'; + $this->assertEqual($result, $expected); + + $string = 'í'; + $result = Multibyte::strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'î'; + $result = Multibyte::strtoupper($string); + $expected = 'ÃŽ'; + $this->assertEqual($result, $expected); + + $string = 'ï'; + $result = Multibyte::strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'ð'; + $result = Multibyte::strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'ñ'; + $result = Multibyte::strtoupper($string); + $expected = 'Ñ'; + $this->assertEqual($result, $expected); + + $string = 'ò'; + $result = Multibyte::strtoupper($string); + $expected = 'Ã’'; + $this->assertEqual($result, $expected); + + $string = 'ó'; + $result = Multibyte::strtoupper($string); + $expected = 'Ó'; + $this->assertEqual($result, $expected); + + $string = 'ô'; + $result = Multibyte::strtoupper($string); + $expected = 'Ô'; + $this->assertEqual($result, $expected); + + $string = 'õ'; + $result = Multibyte::strtoupper($string); + $expected = 'Õ'; + $this->assertEqual($result, $expected); + + $string = 'ö'; + $result = Multibyte::strtoupper($string); + $expected = 'Ö'; + $this->assertEqual($result, $expected); + + $string = 'ø'; + $result = Multibyte::strtoupper($string); + $expected = 'Ø'; + $this->assertEqual($result, $expected); + + $string = 'ù'; + $result = Multibyte::strtoupper($string); + $expected = 'Ù'; + $this->assertEqual($result, $expected); + + $string = 'ú'; + $result = Multibyte::strtoupper($string); + $expected = 'Ú'; + $this->assertEqual($result, $expected); + + $string = 'û'; + $result = Multibyte::strtoupper($string); + $expected = 'Û'; + $this->assertEqual($result, $expected); + + $string = 'ü'; + $result = Multibyte::strtoupper($string); + $expected = 'Ãœ'; + $this->assertEqual($result, $expected); + + $string = 'ý'; + $result = Multibyte::strtoupper($string); + $expected = 'Ã'; + $this->assertEqual($result, $expected); + + $string = 'þ'; + $result = Multibyte::strtoupper($string); + $expected = 'Þ'; + $this->assertEqual($result, $expected); + + $string = 'àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ'; + $result = Multibyte::strtoupper($string); + $expected = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä€'; + $this->assertEqual($result, $expected); + + $string = 'ă'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä‚'; + $this->assertEqual($result, $expected); + + $string = 'Ä…'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä„'; + $this->assertEqual($result, $expected); + + $string = 'ć'; + $result = Multibyte::strtoupper($string); + $expected = 'Ć'; + $this->assertEqual($result, $expected); + + $string = 'ĉ'; + $result = Multibyte::strtoupper($string); + $expected = 'Ĉ'; + $this->assertEqual($result, $expected); + + $string = 'Ä‹'; + $result = Multibyte::strtoupper($string); + $expected = 'ÄŠ'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = Multibyte::strtoupper($string); + $expected = 'ÄŒ'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = Multibyte::strtoupper($string); + $expected = 'ÄŽ'; + $this->assertEqual($result, $expected); + + $string = 'Ä‘'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä'; + $this->assertEqual($result, $expected); + + $string = 'Ä“'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä’'; + $this->assertEqual($result, $expected); + + $string = 'Ä•'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä”'; + $this->assertEqual($result, $expected); + + $string = 'Ä—'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä–'; + $this->assertEqual($result, $expected); + + $string = 'Ä™'; + $result = Multibyte::strtoupper($string); + $expected = 'Ę'; + $this->assertEqual($result, $expected); + + $string = 'Ä›'; + $result = Multibyte::strtoupper($string); + $expected = 'Äš'; + $this->assertEqual($result, $expected); + + $string = 'Ä'; + $result = Multibyte::strtoupper($string); + $expected = 'Äœ'; + $this->assertEqual($result, $expected); + + $string = 'ÄŸ'; + $result = Multibyte::strtoupper($string); + $expected = 'Äž'; + $this->assertEqual($result, $expected); + + $string = 'Ä¡'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä '; + $this->assertEqual($result, $expected); + + $string = 'Ä£'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä¢'; + $this->assertEqual($result, $expected); + + $string = 'Ä¥'; + $result = Multibyte::strtoupper($string); + $expected = 'Ĥ'; + $this->assertEqual($result, $expected); + + $string = 'ħ'; + $result = Multibyte::strtoupper($string); + $expected = 'Ħ'; + $this->assertEqual($result, $expected); + + $string = 'Ä©'; + $result = Multibyte::strtoupper($string); + $expected = 'Ĩ'; + $this->assertEqual($result, $expected); + + $string = 'Ä«'; + $result = Multibyte::strtoupper($string); + $expected = 'Ī'; + $this->assertEqual($result, $expected); + + $string = 'Ä­'; + $result = Multibyte::strtoupper($string); + $expected = 'Ĭ'; + $this->assertEqual($result, $expected); + + $string = 'į'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä®'; + $this->assertEqual($result, $expected); + + $string = 'ij'; + $result = Multibyte::strtoupper($string); + $expected = 'IJ'; + $this->assertEqual($result, $expected); + + $string = 'ĵ'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä´'; + $this->assertEqual($result, $expected); + + $string = 'Ä·'; + $result = Multibyte::strtoupper($string); + $expected = 'Ķ'; + $this->assertEqual($result, $expected); + + $string = 'ĺ'; + $result = Multibyte::strtoupper($string); + $expected = 'Ĺ'; + $this->assertEqual($result, $expected); + + $string = 'ļ'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä»'; + $this->assertEqual($result, $expected); + + $string = 'ľ'; + $result = Multibyte::strtoupper($string); + $expected = 'Ľ'; + $this->assertEqual($result, $expected); + + $string = 'Å€'; + $result = Multibyte::strtoupper($string); + $expected = 'Ä¿'; + $this->assertEqual($result, $expected); + + $string = 'Å‚'; + $result = Multibyte::strtoupper($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'Å„'; + $result = Multibyte::strtoupper($string); + $expected = 'Ń'; + $this->assertEqual($result, $expected); + + $string = 'ņ'; + $result = Multibyte::strtoupper($string); + $expected = 'Å…'; + $this->assertEqual($result, $expected); + + $string = 'ň'; + $result = Multibyte::strtoupper($string); + $expected = 'Ň'; + $this->assertEqual($result, $expected); + + $string = 'Å‹'; + $result = Multibyte::strtoupper($string); + $expected = 'ÅŠ'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = Multibyte::strtoupper($string); + $expected = 'ÅŒ'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = Multibyte::strtoupper($string); + $expected = 'ÅŽ'; + $this->assertEqual($result, $expected); + + $string = 'Å‘'; + $result = Multibyte::strtoupper($string); + $expected = 'Å'; + $this->assertEqual($result, $expected); + + $string = 'Å“'; + $result = Multibyte::strtoupper($string); + $expected = 'Å’'; + $this->assertEqual($result, $expected); + + $string = 'Å•'; + $result = Multibyte::strtoupper($string); + $expected = 'Å”'; + $this->assertEqual($result, $expected); + + $string = 'Å—'; + $result = Multibyte::strtoupper($string); + $expected = 'Å–'; + $this->assertEqual($result, $expected); + + $string = 'Å™'; + $result = Multibyte::strtoupper($string); + $expected = 'Ř'; + $this->assertEqual($result, $expected); + + $string = 'Å›'; + $result = Multibyte::strtoupper($string); + $expected = 'Åš'; + $this->assertEqual($result, $expected); + + $string = 'Å'; + $result = Multibyte::strtoupper($string); + $expected = 'Åœ'; + $this->assertEqual($result, $expected); + + $string = 'ÅŸ'; + $result = Multibyte::strtoupper($string); + $expected = 'Åž'; + $this->assertEqual($result, $expected); + + $string = 'Å¡'; + $result = Multibyte::strtoupper($string); + $expected = 'Å '; + $this->assertEqual($result, $expected); + + $string = 'Å£'; + $result = Multibyte::strtoupper($string); + $expected = 'Å¢'; + $this->assertEqual($result, $expected); + + $string = 'Å¥'; + $result = Multibyte::strtoupper($string); + $expected = 'Ť'; + $this->assertEqual($result, $expected); + + $string = 'ŧ'; + $result = Multibyte::strtoupper($string); + $expected = 'Ŧ'; + $this->assertEqual($result, $expected); + + $string = 'Å©'; + $result = Multibyte::strtoupper($string); + $expected = 'Ũ'; + $this->assertEqual($result, $expected); + + $string = 'Å«'; + $result = Multibyte::strtoupper($string); + $expected = 'Ū'; + $this->assertEqual($result, $expected); + + $string = 'Å­'; + $result = Multibyte::strtoupper($string); + $expected = 'Ŭ'; + $this->assertEqual($result, $expected); + + $string = 'ů'; + $result = Multibyte::strtoupper($string); + $expected = 'Å®'; + $this->assertEqual($result, $expected); + + $string = 'ű'; + $result = Multibyte::strtoupper($string); + $expected = 'Å°'; + $this->assertEqual($result, $expected); + + $string = 'ų'; + $result = Multibyte::strtoupper($string); + $expected = 'Ų'; + $this->assertEqual($result, $expected); + + $string = 'ŵ'; + $result = Multibyte::strtoupper($string); + $expected = 'Å´'; + $this->assertEqual($result, $expected); + + $string = 'Å·'; + $result = Multibyte::strtoupper($string); + $expected = 'Ŷ'; + $this->assertEqual($result, $expected); + + $string = 'ź'; + $result = Multibyte::strtoupper($string); + $expected = 'Ź'; + $this->assertEqual($result, $expected); + + $string = 'ż'; + $result = Multibyte::strtoupper($string); + $expected = 'Å»'; + $this->assertEqual($result, $expected); + + $string = 'ž'; + $result = Multibyte::strtoupper($string); + $expected = 'Ž'; + $this->assertEqual($result, $expected); + + $string = 'ÄăąćĉċÄÄđēĕėęěÄğġģĥħĩīĭįijĵķĺļľŀłńņňŋÅÅőœŕŗřśÅşšţťŧũūŭůűųŵŷźżž'; + $result = Multibyte::strtoupper($string); + $expected = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = Multibyte::strtoupper($string); + $expected = 'ĤĒĹĻŎ, Å´ÅŘĻĎ!'; + $this->assertEqual($result, $expected); + + $string = 'ἀι'; + $result = mb_strtoupper($string); + $expected = 'ἈΙ'; + $this->assertEqual($result, $expected); + + $string = 'ἀι'; + $result = Multibyte::strtoupper($string); + $expected = 'ἈΙ'; + $this->assertEqual($result, $expected); + + $string = 'ÔÔƒÔ…Ô‡Ô‰Ô‹ÔÔÔÔ’'; + $result = Multibyte::strtoupper($string); + $expected = 'ԀԂԄԆԈԊԌԎÔÔ’'; + $this->assertEqual($result, $expected); + + $string = 'Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€ÖÖ‚ÖƒÖ„Ö…Ö†Ö‡'; + $result = Multibyte::strtoupper($string); + $expected = 'Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€ÕÕ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕÕŽÕÕÕ‘Õ’Õ“Õ”Õ•Õ–Ö‡'; + $this->assertEqual($result, $expected); + + $string = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $result = Multibyte::strtoupper($string); + $expected = 'ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ'; + $this->assertEqual($result, $expected); + + $string = 'á¸á¸ƒá¸…ḇḉḋá¸á¸á¸‘ḓḕḗḙḛá¸á¸Ÿá¸¡á¸£á¸¥á¸§á¸©á¸«á¸­á¸¯á¸±á¸³á¸µá¸·á¸¹á¸»á¸½á¸¿á¹á¹ƒá¹…ṇṉṋá¹á¹á¹‘ṓṕṗṙṛá¹á¹Ÿá¹¡á¹£á¹¥á¹§á¹©á¹«á¹­á¹¯á¹±á¹³á¹µá¹·á¹¹á¹»á¹½á¹¿áºáºƒáº…ẇẉẋáºáºáº‘ẓẕẖẗẘẙẚạảấầẩẫậắằẳẵặẹẻẽếá»á»ƒá»…ệỉịá»á»á»‘ồổỗộớá»á»Ÿá»¡á»£á»¥á»§á»©á»«á»­á»¯á»±á»³á»µá»·á»¹'; + $result = Multibyte::strtoupper($string); + $expected = 'ḀḂḄḆḈḊḌḎá¸á¸’ḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎá¹á¹’ṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎáºáº’ẔẖẗẘẙẚẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎá»á»’ỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸ'; + $this->assertEqual($result, $expected); + + $string = 'ωkåⅎ'; + $result = Multibyte::strtoupper($string); + $expected = 'ΩKÅℲ'; + $this->assertEqual($result, $expected); + + $string = 'ωkÃ¥'; + $result = Multibyte::strtoupper($string); + $expected = 'ΩKÃ…'; + $this->assertEqual($result, $expected); + + $string = 'ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↄ'; + $result = Multibyte::strtoupper($string); + $expected = 'ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯↃ'; + $this->assertEqual($result, $expected); + + $string = 'â“ⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜâ“ⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ'; + $result = Multibyte::strtoupper($string); + $expected = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀâ“ⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌâ“â“Žâ“'; + $this->assertEqual($result, $expected); + + $string = 'ⰰⰱⰲⰳⰴⰵⰶⰷⰸⰹⰺⰻⰼⰽⰾⰿⱀâ±â±‚ⱃⱄⱅⱆⱇⱈⱉⱊⱋⱌâ±â±Žâ±â±â±‘ⱒⱓⱔⱕⱖⱗⱘⱙⱚⱛⱜâ±â±ž'; + $result = Multibyte::strtoupper($string); + $expected = 'â°€â°â°‚ⰃⰄⰅⰆⰇⰈⰉⰊⰋⰌâ°â°Žâ°â°â°‘ⰒⰓⰔⰕⰖⰗⰘⰙⰚⰛⰜâ°â°žâ°Ÿâ° â°¡â°¢â°£â°¤â°¥â°¦â°§â°¨â°©â°ªâ°«â°¬â°­â°®'; + $this->assertEqual($result, $expected); + + $string = 'â²â²ƒâ²…ⲇⲉⲋâ²â²â²‘ⲓⲕⲗⲙⲛâ²â²Ÿâ²¡â²£â²¥â²§â²©â²«â²­â²¯â²±â²³â²µâ²·â²¹â²»â²½â²¿â³â³ƒâ³…ⳇⳉⳋâ³â³â³‘ⳓⳕⳗⳙⳛâ³â³Ÿâ³¡â³£'; + $result = Multibyte::strtoupper($string); + $expected = 'ⲀⲂⲄⲆⲈⲊⲌⲎâ²â²’ⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⲲⲴⲶⲸⲺⲼⲾⳀⳂⳄⳆⳈⳊⳌⳎâ³â³’ⳔⳖⳘⳚⳜⳞⳠⳢ'; + $this->assertEqual($result, $expected); + + $string = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $result = Multibyte::strtoupper($string); + $expected = 'ffï¬ï¬‚ffifflſtstﬓﬔﬕﬖﬗ'; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbSubstrCount method + * + * @access public + * @return void + */ + function testUsingMbSubstrCount() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSFTUVWXYZ0F12345F6789'; + $find = 'F'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÅÊËÌÃÃŽÃÃÑÒÓÔÅÕÖØÅÙÚÛÅÜÃÞ'; + $find = 'Ã…'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÙÚÂÃÄÅÆÇÈÙÚÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞÙÚ'; + $find = 'ÙÚ'; + $result = mb_substr_count($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊÅËÌÃÃŽÃÃÑÒÓÔÕÅÖØÅÙÚÅÛÜÅÃÞÅ'; + $find = 'Ã…'; + $result = mb_substr_count($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ĊĀĂĄĆĈĊČĎÄĒĔĖĊĘĚĜĞĠĢĤĦĨĪĬĮĊIJĴĶĹĻĽĿÅŃŅŇŊŌĊŎÅŒŔŖŘŚŜŞŠŢĊŤŦŨŪŬŮŰĊŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_substr_count($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĊĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅĊŇŊŌŎÅŒŔŖĊŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./012F34567F89:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghiFjklmnopqFrstuvwFxyz{|}~'; + $find = 'F'; + $result = mb_substr_count($string, $find); + $expected = 6; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥µ¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀõÂõÄÅÆǵÈ'; + $find = 'µ'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôÕÖõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉÕÖĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄÕÖĞğĠġĢģĤĥĦÕÖħĨĩĪīĬ'; + $find = 'ÕÖ'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅĵĶķĸĹŎÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšĵĶķĸĹŢţŤťŦŧŨũŪūŬŭŮůŰűŲųĵĶķĸĹŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'ĵĶķĸĹ'; + $result = mb_substr_count($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƸƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJƸNjnjÇÇŽÇÇǑǒǓƸǔǕǖǗǘǙǚƸǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƹƠơƢƣƤƥƦƧƨƩƹƪƫƬƭƮƯưƱƲƳƴƹƵƶƷƸƹƺƻƼƽƾƿǀÇǂƹǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'ƹ'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞʀɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʀʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʀʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʀʻʼ'; + $find = 'Ê€'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐЇЎÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = mb_substr_count($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСРТУФХЦЧШЩЪЫЬРЭЮЯабРвгдежзийклРмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСрТУФХЦЧШЩЪЫрЬЭЮЯабвгдежзийклмнопррÑтуфхцчшщъыь'; + $find = 'Ñ€'; + $result = mb_substr_count($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÙنقكلنمنهونىينًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✿✴✵✶✷✸✿✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†âœ¿â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = mb_substr_count($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉âºâºŠâº‹âºŒâºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âºâº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉âºâ»Šâ»‹â»Œâ»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = mb_substr_count($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽤⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¤â½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = mb_substr_count($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눺눻눼눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕눺눻눼뉖뉗뉘뉙뉚뉛뉜ë‰ëˆºëˆ»ëˆ¼ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눺눻눼'; + $result = mb_substr_count($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ﺞﺟﺠﺡﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺞﺟﺠﺡﺆﺇﺞﺟﺠﺡﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞﺟﺠﺡ'; + $result = mb_substr_count($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﻞﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻞﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»žï»¸ï»¹ï»ºï»žï»»ï»¼'; + $find = 'ﻞ'; + $result = mb_substr_count($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdkefghijklï½ï½Žï½ï½ï½‘rstuvwxkyz'; + $find = 'k'; + $result = mb_substr_count($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚klï½ï½ƒï½„efghijklï½ï½Žï½ï½ï½‘rstuvklï½ï½—xyz'; + $find = 'klï½'; + $result = mb_substr_count($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdï½ï½ï½…fghijklï½ï½Žï½ï½ï½ï½‘rstuvwxyz'; + $find = 'ï½ï½ï½…'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'ĺļ'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = mb_substr_count($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rl'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'niÄiniÄiini'; + $find = 'n'; + $result = mb_substr_count($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'moćimoćimoćmćioći'; + $find = 'ći'; + $result = mb_substr_count($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = mb_substr_count($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'H'; + $result = mb_substr_count($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteSubstrCount method + * + * @access public + * @return void + */ + function testMultibyteSubstrCount() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $find = 'F'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ABCDEFGHIJKLMNOPQFRSFTUVWXYZ0F12345F6789'; + $find = 'F'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÅÊËÌÃÃŽÃÃÑÒÓÔÅÕÖØÅÙÚÛÅÜÃÞ'; + $find = 'Ã…'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÙÚÂÃÄÅÆÇÈÙÚÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÅÙÚÛÜÃÞÙÚ'; + $find = 'ÙÚ'; + $result = Multibyte::substrCount($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊÅËÌÃÃŽÃÃÑÒÓÔÕÅÖØÅÙÚÅÛÜÅÃÞÅ'; + $find = 'Ã…'; + $result = Multibyte::substrCount($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ĊĀĂĄĆĈĊČĎÄĒĔĖĊĘĚĜĞĠĢĤĦĨĪĬĮĊIJĴĶĹĻĽĿÅŃŅŇŊŌĊŎÅŒŔŖŘŚŜŞŠŢĊŤŦŨŪŬŮŰĊŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::substrCount($string, $find); + $expected = 7; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĊĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅĊŃŅĊŇŊŌŎÅŒŔŖĊŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./012F34567F89:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghiFjklmnopqFrstuvwFxyz{|}~'; + $find = 'F'; + $result = Multibyte::substrCount($string, $find); + $expected = 6; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥µ¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀõÂõÄÅÆǵÈ'; + $find = 'µ'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôÕÖõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉÕÖĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄÕÖĞğĠġĢģĤĥĦÕÖħĨĩĪīĬ'; + $find = 'ÕÖ'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅĵĶķĸĹŎÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšĵĶķĸĹŢţŤťŦŧŨũŪūŬŭŮůŰűŲųĵĶķĸĹŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $find = 'ĵĶķĸĹ'; + $result = Multibyte::substrCount($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƸƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJƸNjnjÇÇŽÇÇǑǒǓƸǔǕǖǗǘǙǚƸǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'Ƹ'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƹƠơƢƣƤƥƦƧƨƩƹƪƫƬƭƮƯưƱƲƳƴƹƵƶƷƸƹƺƻƼƽƾƿǀÇǂƹǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $find = 'ƹ'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞʀɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʀʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʀʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʀʻʼ'; + $find = 'Ê€'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐЇЎÐÐБВГДЕЖЗИЙКЛ'; + $find = 'Ї'; + $result = Multibyte::substrCount($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСРТУФХЦЧШЩЪЫЬРЭЮЯабРвгдежзийклРмнопрÑтуфхцчшщъыь'; + $find = 'Р'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСрТУФХЦЧШЩЪЫрЬЭЮЯабвгдежзийклмнопррÑтуфхцчшщъыь'; + $find = 'Ñ€'; + $result = Multibyte::substrCount($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ÙنقكلنمنهونىينًٌÙÙŽÙ'; + $find = 'Ù†'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✿✴✵✶✷✸✿✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†âœ¿â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $find = '✿'; + $result = Multibyte::substrCount($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉âºâºŠâº‹âºŒâºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âºâº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉âºâ»Šâ»‹â»Œâ»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $find = 'âº'; + $result = Multibyte::substrCount($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽤⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¤â½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $find = '⽤'; + $result = Multibyte::substrCount($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눺눻눼눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕눺눻눼뉖뉗뉘뉙뉚뉛뉜ë‰ëˆºëˆ»ëˆ¼ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $find = '눺눻눼'; + $result = Multibyte::substrCount($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ﺞﺟﺠﺡﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺞﺟﺠﺡﺆﺇﺞﺟﺠﺡﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $find = 'ﺞﺟﺠﺡ'; + $result = Multibyte::substrCount($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﻞﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻞﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»žï»¸ï»¹ï»ºï»žï»»ï»¼'; + $find = 'ﻞ'; + $result = Multibyte::substrCount($string, $find); + $expected = 5; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdkefghijklï½ï½Žï½ï½ï½‘rstuvwxkyz'; + $find = 'k'; + $result = Multibyte::substrCount($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚klï½ï½ƒï½„efghijklï½ï½Žï½ï½ï½‘rstuvklï½ï½—xyz'; + $find = 'klï½'; + $result = Multibyte::substrCount($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdï½ï½ï½…fghijklï½ï½Žï½ï½ï½ï½‘rstuvwxyz'; + $find = 'ï½ï½ï½…'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $find = 'ï½±'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $find = 'ハ'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'Å‘'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'ĺļ'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'o'; + $result = Multibyte::substrCount($string, $find); + $expected = 2; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $find = 'rl'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $find = 'n'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'niÄiniÄiini'; + $find = 'n'; + $result = Multibyte::substrCount($string, $find); + $expected = 3; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $find = 'ć'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'moćimoćimoćmćioći'; + $find = 'ći'; + $result = Multibyte::substrCount($string, $find); + $expected = 4; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $find = 'ž'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $find = '设'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $find = '周'; + $result = Multibyte::substrCount($string, $find); + $expected = 1; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $find = 'H'; + $result = Multibyte::substrCount($string, $find); + $expected = false; + $this->assertEqual($result, $expected); + } + +/** + * testUsingMbSubstr method + * + * @access public + * @return void + */ + function testUsingMbSubstr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $result = mb_substr($string, 4, 7); + $expected = 'EFGHIJK'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $result = mb_substr($string, 4, 7); + $expected = 'ÄÅÆÇÈÉÊ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = mb_substr($string, 4, 7); + $expected = 'ĈĊČĎÄÄ’Ä”'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $result = mb_substr($string, 4, 7); + $expected = '%&\'()*+'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $result = mb_substr($string, 4); + $expected = '¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $result = mb_substr($string, 4, 7); + $expected = 'ÃÃŽÃÃÑÒÓ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $result = mb_substr($string, 4, 7); + $expected = 'ıIJijĴĵĶķ'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $result = mb_substr($string, 25); + $expected = 'ƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $result = mb_substr($string, 3); + $expected = 'ÉœÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $result = mb_substr($string, 3); + $expected = 'ЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $result = mb_substr($string, 3, 16); + $expected = 'ПРСТУФХЦЧШЩЪЫЬЭЮ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $result = mb_substr($string, 3, 6); + $expected = 'لمنهوى'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $result = mb_substr($string, 6, 14); + $expected = '✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒ'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $result = mb_substr($string, 8, 13); + $expected = '⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $result = mb_substr($string, 12, 24); + $expected = '⽑⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $result = mb_substr($string, 12, 24); + $expected = '눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $result = mb_substr($string, 12); + $expected = 'ﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $result = mb_substr($string, 24, 12); + $expected = 'ﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔ'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $result = mb_substr($string, 11, 2); + $expected = 'lï½'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $result = mb_substr($string, 7, 11); + $expected = 'ィゥェォャュョッーアイ'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $result = mb_substr($string, 13, 13); + $expected = 'ニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘ï¾’'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = mb_substr($string, 3, 4); + $expected = 'ļÅ, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $result = mb_substr($string, 3, 4); + $expected = 'lo, '; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $result = mb_substr($string, 3); + $expected = 'i'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $result = mb_substr($string, 1); + $expected = 'oći'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $result = mb_substr($string, 0, 2); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $result = mb_substr($string, 3, 3); + $expected = '设为首'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = mb_substr($string, 0, 1); + $expected = '一'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = mb_substr($string, 6); + $expected = false; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = mb_substr($string, 0); + $expected = '一二三周永é¾'; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteSubstr method + * + * @access public + * @return void + */ + function testMultibyteSubstr() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $result = Multibyte::substr($string, 4, 7); + $expected = 'EFGHIJK'; + $this->assertEqual($result, $expected); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $result = Multibyte::substr($string, 4, 7); + $expected = 'ÄÅÆÇÈÉÊ'; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $find = 'ÄŠ'; + $result = Multibyte::substr($string, 4, 7); + $expected = 'ĈĊČĎÄÄ’Ä”'; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $result = Multibyte::substr($string, 4, 7); + $expected = '%&\'()*+'; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $result = Multibyte::substr($string, 4); + $expected = '¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $result = Multibyte::substr($string, 4, 7); + $expected = 'ÃÃŽÃÃÑÒÓ'; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $result = Multibyte::substr($string, 4, 7); + $expected = 'ıIJijĴĵĶķ'; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $result = Multibyte::substr($string, 25); + $expected = 'ƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $result = Multibyte::substr($string, 3); + $expected = 'ÉœÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $result = Multibyte::substr($string, 3); + $expected = 'ЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $result = Multibyte::substr($string, 3, 16); + $expected = 'ПРСТУФХЦЧШЩЪЫЬЭЮ'; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $result = Multibyte::substr($string, 3, 6); + $expected = 'لمنهوى'; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $result = Multibyte::substr($string, 6, 14); + $expected = '✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒ'; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $result = Multibyte::substr($string, 8, 13); + $expected = '⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔'; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $result = Multibyte::substr($string, 12, 24); + $expected = '⽑⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨'; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $result = Multibyte::substr($string, 12, 24); + $expected = '눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄'; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $result = Multibyte::substr($string, 12); + $expected = 'ﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $result = Multibyte::substr($string, 24, 12); + $expected = 'ﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔ'; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $result = Multibyte::substr($string, 11, 2); + $expected = 'lï½'; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $result = Multibyte::substr($string, 7, 11); + $expected = 'ィゥェォャュョッーアイ'; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $result = Multibyte::substr($string, 13, 13); + $expected = 'ニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘ï¾’'; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = Multibyte::substr($string, 3, 4); + $expected = 'ļÅ, '; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $result = Multibyte::substr($string, 3, 4); + $expected = 'lo, '; + $this->assertEqual($result, $expected); + + $string = 'Äini'; + $result = Multibyte::substr($string, 3); + $expected = 'i'; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $result = Multibyte::substr($string, 1); + $expected = 'oći'; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $result = Multibyte::substr($string, 0, 2); + $expected = 'dr'; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $result = Multibyte::substr($string, 3, 3); + $expected = '设为首'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = Multibyte::substr($string, 0, 1); + $expected = '一'; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = Multibyte::substr($string, 6); + $expected = false; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = Multibyte::substr($string, 0); + $expected = '一二三周永é¾'; + $this->assertEqual($result, $expected); + } + +/** + * testMultibyteSubstr method + * + * @access public + * @return void + */ + function testMultibyteMimeEncode() { + $string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $result = Multibyte::mimeEncode($string); + $this->assertEqual($result, $string); + + $string = 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?w4DDgcOCw4PDhMOFw4bDh8OIw4nDisOLw4zDjcOOw4/DkMORw5LDk8OUw5U=?=' . "\r\n" . + ' =?UTF-8?B?w5bDmMOZw5rDm8Ocw53Dng==?='; + $this->assertEqual($result, $expected); + $result = Multibyte::mimeEncode($string, null, "\n"); + $expected = '=?UTF-8?B?w4DDgcOCw4PDhMOFw4bDh8OIw4nDisOLw4zDjcOOw4/DkMORw5LDk8OUw5U=?=' . "\n" . + ' =?UTF-8?B?w5bDmMOZw5rDm8Ocw53Dng==?='; + $this->assertEqual($result, $expected); + + $string = 'ĀĂĄĆĈĊČĎÄĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮIJĴĶĹĻĽĿÅŃŅŇŊŌŎÅŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŹŻŽ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?xIDEgsSExIbEiMSKxIzEjsSQxJLElMSWxJjEmsScxJ7EoMSixKTEpsSoxKo=?=' . "\r\n" . + ' =?UTF-8?B?xKzErsSyxLTEtsS5xLvEvcS/xYHFg8WFxYfFisWMxY7FkMWSxZTFlsWYxZo=?=' . "\r\n" . + ' =?UTF-8?B?xZzFnsWgxaLFpMWmxajFqsWsxa7FsMWyxbTFtsW5xbvFvQ==?='; + $this->assertEqual($result, $expected); + + $string = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?ISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xN?=' . "\r\n" . + ' =?UTF-8?B?Tk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6?=' . "\r\n" . + ' =?UTF-8?B?e3x9fg==?='; + $this->assertEqual($result, $expected); + + $string = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?wqHCosKjwqTCpcKmwqfCqMKpwqrCq8Kswq3CrsKvwrDCscKywrPCtMK1wrY=?=' . "\r\n" . + ' =?UTF-8?B?wrfCuMK5wrrCu8K8wr3CvsK/w4DDgcOCw4PDhMOFw4bDh8OI?='; + $this->assertEqual($result, $expected); + + $string = 'ÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġĢģĤĥĦħĨĩĪīĬ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?w4nDisOLw4zDjcOOw4/DkMORw5LDk8OUw5XDlsOXw5jDmcOaw5vDnMOdw54=?=' . "\r\n" . + ' =?UTF-8?B?w5/DoMOhw6LDo8Okw6XDpsOnw6jDqcOqw6vDrMOtw67Dr8Oww7HDssOzw7Q=?=' . "\r\n" . + ' =?UTF-8?B?w7XDtsO3w7jDucO6w7vDvMO9w77Dv8SAxIHEgsSDxITEhcSGxIfEiMSJxIo=?=' . "\r\n" . + ' =?UTF-8?B?xIvEjMSNxI7Ej8SQxJHEksSTxJTElcSWxJfEmMSZxJrEm8ScxJ3EnsSfxKA=?=' . "\r\n" . + ' =?UTF-8?B?xKHEosSjxKTEpcSmxKfEqMSpxKrEq8Ss?='; + $this->assertEqual($result, $expected); + + $string = 'ĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?xK3ErsSvxLDEscSyxLPEtMS1xLbEt8S4xLnEusS7xLzEvcS+xL/FgMWBxYI=?=' . "\r\n" . + ' =?UTF-8?B?xYPFhMWFxYbFh8WIxYnFisWLxYzFjcWOxY/FkMWRxZLFk8WUxZXFlsWXxZg=?=' . "\r\n" . + ' =?UTF-8?B?xZnFmsWbxZzFncWexZ/FoMWhxaLFo8WkxaXFpsWnxajFqcWqxavFrMWtxa4=?=' . "\r\n" . + ' =?UTF-8?B?xa/FsMWxxbLFs8W0xbXFtsW3xbjFucW6xbvFvMW9xb7Fv8aAxoHGgsaDxoQ=?=' . "\r\n" . + ' =?UTF-8?B?xoXGhsaHxojGicaKxovGjMaNxo7Gj8aQ?='; + $this->assertEqual($result, $expected); + + $string = 'ƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?xpHGksaTxpTGlcaWxpfGmMaZxprGm8acxp3GnsafxqDGocaixqPGpMalxqY=?=' . "\r\n" . + ' =?UTF-8?B?xqfGqMapxqrGq8asxq3GrsavxrDGscayxrPGtMa1xrbGt8a4xrnGusa7xrw=?=' . "\r\n" . + ' =?UTF-8?B?xr3Gvsa/x4DHgceCx4PHhMeFx4bHh8eIx4nHiseLx4zHjceOx4/HkMeRx5I=?=' . "\r\n" . + ' =?UTF-8?B?x5PHlMeVx5bHl8eYx5nHmsebx5zHnceex5/HoMehx6LHo8ekx6XHpsenx6g=?=' . "\r\n" . + ' =?UTF-8?B?x6nHqserx6zHrceux6/HsMexx7LHs8e0?='; + $this->assertEqual($result, $expected); + + $string = 'əɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?yZnJmsmbyZzJncmeyZ/JoMmhyaLJo8mkyaXJpsmnyajJqcmqyavJrMmtya4=?=' . "\r\n" . + ' =?UTF-8?B?ya/JsMmxybLJs8m0ybXJtsm3ybjJucm6ybvJvMm9yb7Jv8qAyoHKgsqDyoQ=?=' . "\r\n" . + ' =?UTF-8?B?yoXKhsqHyojKicqKyovKjMqNyo7Kj8qQypHKksqTypTKlcqWypfKmMqZypo=?=' . "\r\n" . + ' =?UTF-8?B?ypvKnMqdyp7Kn8qgyqHKosqjyqTKpcqmyqfKqMqpyqrKq8qsyq3KrsqvyrA=?=' . "\r\n" . + ' =?UTF-8?B?yrHKssqzyrTKtcq2yrfKuMq5yrrKu8q8?='; + $this->assertEqual($result, $expected); + + $string = 'ЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?0IDQgdCC0IPQhNCF0IbQh9CI0InQitCL0IzQjdCO0I/QkNCR0JLQk9CU0JU=?=' . "\r\n" . + ' =?UTF-8?B?0JbQl9CY0JnQmtCb?='; + $this->assertEqual($result, $expected); + + $string = 'ÐœÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыь'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?0JzQndCe0J/QoNCh0KLQo9Ck0KXQptCn0KjQqdCq0KvQrNCt0K7Qr9Cw0LE=?=' . "\r\n" . + ' =?UTF-8?B?0LLQs9C00LXQttC30LjQudC60LvQvNC90L7Qv9GA0YHRgtGD0YTRhdGG0Yc=?=' . "\r\n" . + ' =?UTF-8?B?0YjRidGK0YvRjA==?='; + $this->assertEqual($result, $expected); + + $string = 'ÙقكلمنهوىيًٌÙÙŽÙ'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM2Y3ZjtmP?='; + $this->assertEqual($result, $expected); + + $string = '✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââž'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?4pyw4pyx4pyy4pyz4py04py14py24py34py44py54py64py74py84py94py+?=' . "\r\n" . + ' =?UTF-8?B?4py/4p2A4p2B4p2C4p2D4p2E4p2F4p2G4p2H4p2I4p2J4p2K4p2L4p2M4p2N?=' . "\r\n" . + ' =?UTF-8?B?4p2O4p2P4p2Q4p2R4p2S4p2T4p2U4p2V4p2W4p2X4p2Y4p2Z4p2a4p2b4p2c?=' . "\r\n" . + ' =?UTF-8?B?4p2d4p2e?='; + $this->assertEqual($result, $expected); + + $string = '⺀âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» '; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?4rqA4rqB4rqC4rqD4rqE4rqF4rqG4rqH4rqI4rqJ4rqK4rqL4rqM4rqN4rqO?=' . "\r\n" . + ' =?UTF-8?B?4rqP4rqQ4rqR4rqS4rqT4rqU4rqV4rqW4rqX4rqY4rqZ4rqb4rqc4rqd4rqe?=' . "\r\n" . + ' =?UTF-8?B?4rqf4rqg4rqh4rqi4rqj4rqk4rql4rqm4rqn4rqo4rqp4rqq4rqr4rqs4rqt?=' . "\r\n" . + ' =?UTF-8?B?4rqu4rqv4rqw4rqx4rqy4rqz4rq04rq14rq24rq34rq44rq54rq64rq74rq8?=' . "\r\n" . + ' =?UTF-8?B?4rq94rq+4rq/4ruA4ruB4ruC4ruD4ruE4ruF4ruG4ruH4ruI4ruJ4ruK4ruL?=' . "\r\n" . + ' =?UTF-8?B?4ruM4ruN4ruO4ruP4ruQ4ruR4ruS4ruT4ruU4ruV4ruW4ruX4ruY4ruZ4rua?=' . "\r\n" . + ' =?UTF-8?B?4rub4ruc4rud4rue4ruf4rug?='; + $this->assertEqual($result, $expected); + + $string = '⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?4r2F4r2G4r2H4r2I4r2J4r2K4r2L4r2M4r2N4r2O4r2P4r2Q4r2R4r2S4r2T?=' . "\r\n" . + ' =?UTF-8?B?4r2U4r2V4r2W4r2X4r2Y4r2Z4r2a4r2b4r2c4r2d4r2e4r2f4r2g4r2h4r2i?=' . "\r\n" . + ' =?UTF-8?B?4r2j4r2k4r2l4r2m4r2n4r2o4r2p4r2q4r2r4r2s4r2t4r2u4r2v4r2w4r2x?=' . "\r\n" . + ' =?UTF-8?B?4r2y4r2z4r204r214r224r234r244r254r264r274r284r294r2+4r2/?='; + $this->assertEqual($result, $expected); + + $string = '눡눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?64ih64ii64ij64ik64il64im64in64io64ip64iq64ir64is64it64iu64iv?=' . "\r\n" . + ' =?UTF-8?B?64iw64ix64iy64iz64i064i164i264i364i464i564i664i764i864i964i+?=' . "\r\n" . + ' =?UTF-8?B?64i/64mA64mB64mC64mD64mE64mF64mG64mH64mI64mJ64mK64mL64mM64mN?=' . "\r\n" . + ' =?UTF-8?B?64mO64mP64mQ64mR64mS64mT64mU64mV64mW64mX64mY64mZ64ma64mb64mc?=' . "\r\n" . + ' =?UTF-8?B?64md64me64mf64mg64mh64mi64mj64mk64ml64mm64mn64mo64mp64mq64mr?=' . "\r\n" . + ' =?UTF-8?B?64ms64mt64mu64mv64mw64mx64my64mz64m064m164m264m364m464m564m6?=' . "\r\n" . + ' =?UTF-8?B?64m764m864m964m+64m/64qA64qB64qC64qD64qE?='; + $this->assertEqual($result, $expected); + + $string = 'ﹰﹱﹲﹳﹴ﹵ﹶﹷﹸﹹﹺﹻﹼﹽﹾﹿﺀïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?77mw77mx77my77mz77m077m177m277m377m477m577m677m777m877m977m+?=' . "\r\n" . + ' =?UTF-8?B?77m/77qA77qB77qC77qD77qE77qF77qG77qH77qI77qJ77qK77qL77qM77qN?=' . "\r\n" . + ' =?UTF-8?B?77qO77qP77qQ77qR77qS77qT77qU77qV77qW77qX77qY77qZ77qa77qb77qc?=' . "\r\n" . + ' =?UTF-8?B?77qd77qe77qf77qg77qh77qi77qj77qk77ql77qm77qn77qo77qp77qq77qr?=' . "\r\n" . + ' =?UTF-8?B?77qs77qt77qu77qv77qw?='; + $this->assertEqual($result, $expected); + + $string = 'ﺱﺲﺳﺴﺵﺶﺷﺸﺹﺺﺻﺼﺽﺾﺿﻀï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?77qx77qy77qz77q077q177q277q377q477q577q677q777q877q977q+77q/?=' . "\r\n" . + ' =?UTF-8?B?77uA77uB77uC77uD77uE77uF77uG77uH77uI77uJ77uK77uL77uM77uN77uO?=' . "\r\n" . + ' =?UTF-8?B?77uP77uQ77uR77uS77uT77uU77uV77uW77uX77uY77uZ77ua77ub77uc77ud?=' . "\r\n" . + ' =?UTF-8?B?77ue77uf77ug77uh77ui77uj77uk77ul77um77un77uo77up77uq77ur77us?=' . "\r\n" . + ' =?UTF-8?B?77ut77uu77uv77uw77ux77uy77uz77u077u177u277u377u477u577u677u7?=' . "\r\n" . + ' =?UTF-8?B?77u8?='; + $this->assertEqual($result, $expected); + + $string = 'ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?772B772C772D772E772F772G772H772I772J772K772L772M772N772O772P?=' . "\r\n" . + ' =?UTF-8?B?772Q772R772S772T772U772V772W772X772Y772Z772a?='; + $this->assertEqual($result, $expected); + + $string = '。「」、・ヲァィゥェォャュョッーアイウエオカキク'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?772h772i772j772k772l772m772n772o772p772q772r772s772t772u772v?=' . "\r\n" . + ' =?UTF-8?B?772w772x772y772z77207721772277237724?='; + $this->assertEqual($result, $expected); + + $string = 'ケコサシスセソタï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾ž'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?77257726772777287729772+772/776A776B776C776D776E776F776G776H?=' . "\r\n" . + ' =?UTF-8?B?776I776J776K776L776M776N776O776P776Q776R776S776T776U776V776W?=' . "\r\n" . + ' =?UTF-8?B?776X776Y776Z776a776b776c776d776e?='; + $this->assertEqual($result, $expected); + + $string = 'ĤēĺļÅ, ŴőřļÄ!'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?xKTEk8S6xLzFjywgxbTFkcWZxLzEjyE=?='; + $this->assertEqual($result, $expected); + + $string = 'Hello, World!'; + $result = Multibyte::mimeEncode($string); + $this->assertEqual($result, $string); + + $string = 'Äini'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?xI1pbmk=?='; + $this->assertEqual($result, $expected); + + $string = 'moći'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?bW/Eh2k=?='; + $this->assertEqual($result, $expected); + + $string = 'državni'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?ZHLFvmF2bmk=?='; + $this->assertEqual($result, $expected); + + $string = '把百度设为首页'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?5oqK55m+5bqm6K6+5Li66aaW6aG1?='; + $this->assertEqual($result, $expected); + + $string = '一二三周永é¾'; + $result = Multibyte::mimeEncode($string); + $expected = '=?UTF-8?B?5LiA5LqM5LiJ5ZGo5rC46b6N?='; + $this->assertEqual($result, $expected); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/object.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/object.test.php new file mode 100644 index 000000000..36efe658c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/object.test.php @@ -0,0 +1,866 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', array('Object', 'Controller', 'Model')); + +/** + * RequestActionPost class + * + * @package cake + * @subpackage cake.tests.cases.libs.object + */ +class RequestActionPost extends CakeTestModel { + +/** + * name property + * + * @var string 'ControllerPost' + * @access public + */ + var $name = 'RequestActionPost'; + +/** + * useTable property + * + * @var string 'posts' + * @access public + */ + var $useTable = 'posts'; +} + +/** + * RequestActionController class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class RequestActionController extends Controller { + +/** +* uses property +* +* @var array +* @access public +*/ + var $uses = array('RequestActionPost'); + +/** +* test_request_action method +* +* @access public +* @return void +*/ + function test_request_action() { + return 'This is a test'; + } + +/** +* another_ra_test method +* +* @param mixed $id +* @param mixed $other +* @access public +* @return void +*/ + function another_ra_test($id, $other) { + return $id + $other; + } + +/** + * normal_request_action method + * + * @access public + * @return void + */ + function normal_request_action() { + return 'Hello World'; + } + +/** + * returns $this->here + * + * @return void + */ + function return_here() { + return $this->here; + } + +/** + * paginate_request_action method + * + * @access public + * @return void + */ + function paginate_request_action() { + $data = $this->paginate(); + return true; + } + +/** + * post pass, testing post passing + * + * @return array + */ + function post_pass() { + return $this->data; + } + +/** + * test param passing and parsing. + * + * @return array + */ + function params_pass() { + return $this->params; + } +} + +/** + * RequestActionPersistentController class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class RequestActionPersistentController extends Controller { + +/** +* uses property +* +* @var array +* @access public +*/ + var $uses = array('PersisterOne'); + +/** +* persistModel property +* +* @var array +* @access public +*/ + var $persistModel = true; + +/** + * post pass, testing post passing + * + * @return array + */ + function index() { + return 'This is a test'; + } +} + +/** + * TestObject class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class TestObject extends Object { + +/** + * firstName property + * + * @var string 'Joel' + * @access public + */ + var $firstName = 'Joel'; + +/** + * lastName property + * + * @var string 'Moss' + * @access public + */ + var $lastName = 'Moss'; + +/** + * methodCalls property + * + * @var array + * @access public + */ + var $methodCalls = array(); + +/** + * emptyMethod method + * + * @access public + * @return void + */ + function emptyMethod() { + $this->methodCalls[] = 'emptyMethod'; + } + +/** + * oneParamMethod method + * + * @param mixed $param + * @access public + * @return void + */ + function oneParamMethod($param) { + $this->methodCalls[] = array('oneParamMethod' => array($param)); + } + +/** + * twoParamMethod method + * + * @param mixed $param + * @param mixed $param2 + * @access public + * @return void + */ + function twoParamMethod($param, $param2) { + $this->methodCalls[] = array('twoParamMethod' => array($param, $param2)); + } + +/** + * threeParamMethod method + * + * @param mixed $param + * @param mixed $param2 + * @param mixed $param3 + * @access public + * @return void + */ + function threeParamMethod($param, $param2, $param3) { + $this->methodCalls[] = array('threeParamMethod' => array($param, $param2, $param3)); + } + /** + * fourParamMethod method + * + * @param mixed $param + * @param mixed $param2 + * @param mixed $param3 + * @param mixed $param4 + * @access public + * @return void + */ + function fourParamMethod($param, $param2, $param3, $param4) { + $this->methodCalls[] = array('fourParamMethod' => array($param, $param2, $param3, $param4)); + } + /** + * fiveParamMethod method + * + * @param mixed $param + * @param mixed $param2 + * @param mixed $param3 + * @param mixed $param4 + * @param mixed $param5 + * @access public + * @return void + */ + function fiveParamMethod($param, $param2, $param3, $param4, $param5) { + $this->methodCalls[] = array('fiveParamMethod' => array($param, $param2, $param3, $param4, $param5)); + } + +/** + * crazyMethod method + * + * @param mixed $param + * @param mixed $param2 + * @param mixed $param3 + * @param mixed $param4 + * @param mixed $param5 + * @param mixed $param6 + * @param mixed $param7 + * @access public + * @return void + */ + function crazyMethod($param, $param2, $param3, $param4, $param5, $param6, $param7 = null) { + $this->methodCalls[] = array('crazyMethod' => array($param, $param2, $param3, $param4, $param5, $param6, $param7)); + } + +/** + * methodWithOptionalParam method + * + * @param mixed $param + * @access public + * @return void + */ + function methodWithOptionalParam($param = null) { + $this->methodCalls[] = array('methodWithOptionalParam' => array($param)); + } + +/** + * testPersist + * + * @return void + */ + function testPersist($name, $return = null, &$object, $type = null) { + return $this->_persist($name, $return, $object, $type); + } +} + +/** + * ObjectTestModel class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ObjectTestModel extends CakeTestModel { + var $useTable = false; + var $name = 'ObjectTestModel'; +} + +/** + * Object Test class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ObjectTest extends CakeTestCase { + +/** + * fixtures + * + * @var string + */ + var $fixtures = array('core.post', 'core.test_plugin_comment', 'core.comment'); + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->object = new TestObject(); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + unset($this->object); + } + +/** + * endTest + * + * @access public + * @return void + */ + function endTest() { + App::build(); + } + +/** + * testLog method + * + * @access public + * @return void + */ + function testLog() { + @unlink(LOGS . 'error.log'); + $this->assertTrue($this->object->log('Test warning 1')); + $this->assertTrue($this->object->log(array('Test' => 'warning 2'))); + $result = file(LOGS . 'error.log'); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Error: Test warning 1$/', $result[0]); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Error: Array$/', $result[1]); + $this->assertPattern('/^\($/', $result[2]); + $this->assertPattern('/\[Test\] => warning 2$/', $result[3]); + $this->assertPattern('/^\)$/', $result[4]); + unlink(LOGS . 'error.log'); + + @unlink(LOGS . 'error.log'); + $this->assertTrue($this->object->log('Test warning 1', LOG_WARNING)); + $this->assertTrue($this->object->log(array('Test' => 'warning 2'), LOG_WARNING)); + $result = file(LOGS . 'error.log'); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning 1$/', $result[0]); + $this->assertPattern('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Array$/', $result[1]); + $this->assertPattern('/^\($/', $result[2]); + $this->assertPattern('/\[Test\] => warning 2$/', $result[3]); + $this->assertPattern('/^\)$/', $result[4]); + unlink(LOGS . 'error.log'); + } + +/** + * testSet method + * + * @access public + * @return void + */ + function testSet() { + $this->object->_set('a string'); + $this->assertEqual($this->object->firstName, 'Joel'); + + $this->object->_set(array('firstName')); + $this->assertEqual($this->object->firstName, 'Joel'); + + $this->object->_set(array('firstName' => 'Ashley')); + $this->assertEqual($this->object->firstName, 'Ashley'); + + $this->object->_set(array('firstName' => 'Joel', 'lastName' => 'Moose')); + $this->assertEqual($this->object->firstName, 'Joel'); + $this->assertEqual($this->object->lastName, 'Moose'); + } + +/** + * testPersist method + * + * @access public + * @return void + */ + function testPersist() { + ClassRegistry::flush(); + + $cacheDisable = Configure::read('Cache.disable'); + Configure::write('Cache.disable', false); + @unlink(CACHE . 'persistent' . DS . 'testmodel.php'); + $test = new stdClass; + $this->assertFalse($this->object->testPersist('TestModel', null, $test)); + $this->assertFalse($this->object->testPersist('TestModel', true, $test)); + $this->assertTrue($this->object->testPersist('TestModel', null, $test)); + $this->assertTrue(file_exists(CACHE . 'persistent' . DS . 'testmodel.php')); + $this->assertTrue($this->object->testPersist('TestModel', true, $test)); + $this->assertEqual($this->object->TestModel, $test); + + @unlink(CACHE . 'persistent' . DS . 'testmodel.php'); + + $model =& new ObjectTestModel(); + $expected = ClassRegistry::keys(); + + ClassRegistry::flush(); + $data = array('object_test_model' => $model); + $this->assertFalse($this->object->testPersist('ObjectTestModel', true, $data)); + $this->assertTrue(file_exists(CACHE . 'persistent' . DS . 'objecttestmodel.php')); + + $this->object->testPersist('ObjectTestModel', true, $model, 'registry'); + + $result = ClassRegistry::keys(); + $this->assertEqual($result, $expected); + + $newModel = ClassRegistry::getObject('object_test_model'); + $this->assertEqual('ObjectTestModel', $newModel->name); + + @unlink(CACHE . 'persistent' . DS . 'objecttestmodel.php'); + + Configure::write('Cache.disable', $cacheDisable); + } + +/** + * testPersistWithRequestAction method + * + * @access public + * @return void + */ + function testPersistWithBehavior() { + ClassRegistry::flush(); + + $cacheDisable = Configure::read('Cache.disable'); + Configure::write('Cache.disable', false); + + App::build(array( + 'models' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS), + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins'. DS), + 'behaviors' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models'. DS . 'behaviors' . DS), + ), true); + + $this->assertFalse(class_exists('PersisterOneBehaviorBehavior')); + $this->assertFalse(class_exists('PersisterTwoBehaviorBehavior')); + $this->assertFalse(class_exists('TestPluginPersisterBehavior')); + $this->assertFalse(class_exists('TestPluginAuthors')); + + $Controller = new RequestActionPersistentController(); + $Controller->persistModel = true; + $Controller->constructClasses(); + + $this->assertTrue(file_exists(CACHE . 'persistent' . DS . 'persisterone.php')); + $this->assertTrue(file_exists(CACHE . 'persistent' . DS . 'persisteroneregistry.php')); + + $contents = file_get_contents(CACHE . 'persistent' . DS . 'persisteroneregistry.php'); + $contents = str_replace('"PersisterOne"', '"PersisterTwo"', $contents); + $contents = str_replace('persister_one', 'persister_two', $contents); + $contents = str_replace('test_plugin_comment', 'test_plugin_authors', $contents); + $result = file_put_contents(CACHE . 'persistent' . DS . 'persisteroneregistry.php', $contents); + + $this->assertTrue(class_exists('PersisterOneBehaviorBehavior')); + $this->assertTrue(class_exists('TestPluginPersisterOneBehavior')); + $this->assertTrue(class_exists('TestPluginComment')); + $this->assertFalse(class_exists('PersisterTwoBehaviorBehavior')); + $this->assertFalse(class_exists('TestPluginPersisterTwoBehavior')); + $this->assertFalse(class_exists('TestPluginAuthors')); + + $Controller = new RequestActionPersistentController(); + $Controller->persistModel = true; + $Controller->constructClasses(); + + $this->assertTrue(class_exists('PersisterOneBehaviorBehavior')); + $this->assertTrue(class_exists('PersisterTwoBehaviorBehavior')); + $this->assertTrue(class_exists('TestPluginPersisterTwoBehavior')); + $this->assertTrue(class_exists('TestPluginAuthors')); + + @unlink(CACHE . 'persistent' . DS . 'persisterone.php'); + @unlink(CACHE . 'persistent' . DS . 'persisteroneregistry.php'); + } + +/** + * testPersistWithBehaviorAndRequestAction method + * + * @see testPersistWithBehavior + * @access public + * @return void + */ + function testPersistWithBehaviorAndRequestAction() { + ClassRegistry::flush(); + + $cacheDisable = Configure::read('Cache.disable'); + Configure::write('Cache.disable', false); + + $this->assertFalse(class_exists('ContainableBehavior')); + + App::build(array( + 'models' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS), + 'behaviors' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models'. DS . 'behaviors' . DS), + ), true); + + $this->assertFalse(class_exists('PersistOneBehaviorBehavior')); + $this->assertFalse(class_exists('PersistTwoBehaviorBehavior')); + + $Controller = new RequestActionPersistentController(); + $Controller->persistModel = true; + $Controller->constructClasses(); + + $this->assertTrue(file_exists(CACHE . 'persistent' . DS . 'persisterone.php')); + $this->assertTrue(file_exists(CACHE . 'persistent' . DS . 'persisteroneregistry.php')); + + $keys = ClassRegistry::keys(); + $this->assertEqual($keys, array( + 'persister_one', + 'comment', + 'test_plugin_comment', + 'test_plugin.test_plugin_comment', + 'persister_one_behavior_behavior', + 'test_plugin_persister_one_behavior', + 'test_plugin.test_plugin_persister_one_behavior' + )); + + ob_start(); + $Controller->set('content_for_layout', 'cool'); + $Controller->render('index', 'ajax', '/layouts/ajax'); + $result = ob_get_clean(); + + $keys = ClassRegistry::keys(); + $this->assertEqual($keys, array( + 'persister_one', + 'comment', + 'test_plugin_comment', + 'test_plugin.test_plugin_comment', + 'persister_one_behavior_behavior', + 'test_plugin_persister_one_behavior', + 'test_plugin.test_plugin_persister_one_behavior', + 'view' + )); + $result = $this->object->requestAction('/request_action_persistent/index'); + $expected = 'This is a test'; + $this->assertEqual($result, $expected); + + @unlink(CACHE . 'persistent' . DS . 'persisterone.php'); + @unlink(CACHE . 'persistent' . DS . 'persisteroneregistry.php'); + + $Controller = new RequestActionPersistentController(); + $Controller->persistModel = true; + $Controller->constructClasses(); + + @unlink(CACHE . 'persistent' . DS . 'persisterone.php'); + @unlink(CACHE . 'persistent' . DS . 'persisteroneregistry.php'); + + Configure::write('Cache.disable', $cacheDisable); + } + +/** + * testToString method + * + * @access public + * @return void + */ + function testToString() { + $result = strtolower($this->object->toString()); + $this->assertEqual($result, 'testobject'); + } + +/** + * testMethodDispatching method + * + * @access public + * @return void + */ + function testMethodDispatching() { + $this->object->emptyMethod(); + $expected = array('emptyMethod'); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->oneParamMethod('Hello'); + $expected[] = array('oneParamMethod' => array('Hello')); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->twoParamMethod(true, false); + $expected[] = array('twoParamMethod' => array(true, false)); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->threeParamMethod(true, false, null); + $expected[] = array('threeParamMethod' => array(true, false, null)); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->crazyMethod(1, 2, 3, 4, 5, 6, 7); + $expected[] = array('crazyMethod' => array(1, 2, 3, 4, 5, 6, 7)); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object = new TestObject(); + $this->assertIdentical($this->object->methodCalls, array()); + + $this->object->dispatchMethod('emptyMethod'); + $expected = array('emptyMethod'); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->dispatchMethod('oneParamMethod', array('Hello')); + $expected[] = array('oneParamMethod' => array('Hello')); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->dispatchMethod('twoParamMethod', array(true, false)); + $expected[] = array('twoParamMethod' => array(true, false)); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->dispatchMethod('threeParamMethod', array(true, false, null)); + $expected[] = array('threeParamMethod' => array(true, false, null)); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->dispatchMethod('fourParamMethod', array(1, 2, 3, 4)); + $expected[] = array('fourParamMethod' => array(1, 2, 3, 4)); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->dispatchMethod('fiveParamMethod', array(1, 2, 3, 4, 5)); + $expected[] = array('fiveParamMethod' => array(1, 2, 3, 4, 5)); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->dispatchMethod('crazyMethod', array(1, 2, 3, 4, 5, 6, 7)); + $expected[] = array('crazyMethod' => array(1, 2, 3, 4, 5, 6, 7)); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->dispatchMethod('methodWithOptionalParam', array('Hello')); + $expected[] = array('methodWithOptionalParam' => array("Hello")); + $this->assertIdentical($this->object->methodCalls, $expected); + + $this->object->dispatchMethod('methodWithOptionalParam'); + $expected[] = array('methodWithOptionalParam' => array(null)); + $this->assertIdentical($this->object->methodCalls, $expected); + } + +/** + * testRequestAction method + * + * @access public + * @return void + */ + function testRequestAction() { + App::build(array( + 'models' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS), + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS), + 'controllers' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'controllers' . DS) + )); + $result = $this->object->requestAction(''); + $this->assertFalse($result); + + $result = $this->object->requestAction('/request_action/test_request_action'); + $expected = 'This is a test'; + $this->assertEqual($result, $expected);; + + $result = $this->object->requestAction('/request_action/another_ra_test/2/5'); + $expected = 7; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction('/tests_apps/index', array('return')); + $expected = 'This is the TestsAppsController index view'; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction('/tests_apps/some_method'); + $expected = 5; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction('/request_action/paginate_request_action'); + $this->assertTrue($result); + + $result = $this->object->requestAction('/request_action/normal_request_action'); + $expected = 'Hello World'; + $this->assertEqual($result, $expected); + + App::build(); + } + +/** + * test requestAction() and plugins. + * + * @return void + */ + function testRequestActionPlugins() { + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + )); + App::objects('plugin', null, false); + Router::reload(); + + $result = $this->object->requestAction('/test_plugin/tests/index', array('return')); + $expected = 'test plugin index'; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction('/test_plugin/tests/index/some_param', array('return')); + $expected = 'test plugin index'; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction( + array('controller' => 'tests', 'action' => 'index', 'plugin' => 'test_plugin'), array('return') + ); + $expected = 'test plugin index'; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction('/test_plugin/tests/some_method'); + $expected = 25; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction( + array('controller' => 'tests', 'action' => 'some_method', 'plugin' => 'test_plugin') + ); + $expected = 25; + $this->assertEqual($result, $expected); + + App::build(); + App::objects('plugin', null, false); + } + +/** + * test requestAction() with arrays. + * + * @return void + */ + function testRequestActionArray() { + App::build(array( + 'models' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS), + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS), + 'controllers' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'controllers' . DS) + )); + + $result = $this->object->requestAction( + array('controller' => 'request_action', 'action' => 'test_request_action') + ); + $expected = 'This is a test'; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction( + array('controller' => 'request_action', 'action' => 'another_ra_test'), + array('pass' => array('5', '7')) + ); + $expected = 12; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction( + array('controller' => 'tests_apps', 'action' => 'index'), array('return') + ); + $expected = 'This is the TestsAppsController index view'; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction(array('controller' => 'tests_apps', 'action' => 'some_method')); + $expected = 5; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction( + array('controller' => 'request_action', 'action' => 'normal_request_action') + ); + $expected = 'Hello World'; + $this->assertEqual($result, $expected); + + $result = $this->object->requestAction( + array('controller' => 'request_action', 'action' => 'paginate_request_action') + ); + $this->assertTrue($result); + + $result = $this->object->requestAction( + array('controller' => 'request_action', 'action' => 'paginate_request_action'), + array('pass' => array(5), 'named' => array('param' => 'value')) + ); + $this->assertTrue($result); + + App::build(); + } + +/** + * Test that requestAction() is populating $this->params properly + * + * @access public + * @return void + */ + function testRequestActionParamParseAndPass() { + $result = $this->object->requestAction('/request_action/params_pass'); + $this->assertTrue(isset($result['url']['url'])); + $this->assertEqual($result['url']['url'], '/request_action/params_pass'); + $this->assertEqual($result['controller'], 'request_action'); + $this->assertEqual($result['action'], 'params_pass'); + $this->assertEqual($result['form'], array()); + $this->assertEqual($result['plugin'], null); + + $result = $this->object->requestAction('/request_action/params_pass/sort:desc/limit:5'); + $expected = array('sort' => 'desc', 'limit' => 5,); + $this->assertEqual($result['named'], $expected); + + $result = $this->object->requestAction( + array('controller' => 'request_action', 'action' => 'params_pass'), + array('named' => array('sort' => 'desc', 'limit' => 5)) + ); + $this->assertEqual($result['named'], $expected); + } + +/** + * test requestAction and POST parameter passing, and not passing when url is an array. + * + * @access public + * @return void + */ + function testRequestActionPostPassing() { + $_tmp = $_POST; + + $_POST = array('data' => array( + 'item' => 'value' + )); + $result = $this->object->requestAction(array('controller' => 'request_action', 'action' => 'post_pass')); + $expected = array(); + $this->assertEqual($expected, $result); + + $result = $this->object->requestAction(array('controller' => 'request_action', 'action' => 'post_pass'), array('data' => $_POST['data'])); + $expected = $_POST['data']; + $this->assertEqual($expected, $result); + + $result = $this->object->requestAction('/request_action/post_pass'); + $expected = $_POST['data']; + $this->assertEqual($expected, $result); + + $_POST = $_tmp; + } + +/** + * testCakeError + * + * @return void + */ + function testCakeError() { + + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/overloadable.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/overloadable.test.php new file mode 100644 index 000000000..2b0fd31a8 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/overloadable.test.php @@ -0,0 +1,39 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Overloadable'); + +/** + * OverloadableTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class OverloadableTest extends CakeTestCase { + +/** + * skip method + * + * @access public + * @return void + */ + function skip() { + $this->skipIf(true, ' %s OverloadableTest not implemented'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/router.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/router.test.php new file mode 100644 index 000000000..0d199655d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/router.test.php @@ -0,0 +1,2672 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', array('Router')); + +if (!defined('FULL_BASE_URL')) { + define('FULL_BASE_URL', 'http://cakephp.org'); +} + +/** + * RouterTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class RouterTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->_routing = Configure::read('Routing'); + Configure::write('Routing', array('admin' => null, 'prefixes' => array())); + Router::reload(); + $this->router =& Router::getInstance(); + } + +/** + * end the test and reset the environment + * + * @return void + */ + function endTest() { + Configure::write('Routing', $this->_routing); + } + +/** + * testReturnedInstanceReference method + * + * @access public + * @return void + */ + function testReturnedInstanceReference() { + $this->router->testVar = 'test'; + $this->assertIdentical($this->router, Router::getInstance()); + unset($this->router->testVar); + } + +/** + * testFullBaseURL method + * + * @access public + * @return void + */ + function testFullBaseURL() { + $this->assertPattern('/^http(s)?:\/\//', Router::url('/', true)); + $this->assertPattern('/^http(s)?:\/\//', Router::url(null, true)); + $this->assertPattern('/^http(s)?:\/\//', Router::url(array('full_base' => true))); + $this->assertIdentical(FULL_BASE_URL . '/', Router::url(array('full_base' => true))); + } + +/** + * testRouteDefaultParams method + * + * @access public + * @return void + */ + function testRouteDefaultParams() { + Router::connect('/:controller', array('controller' => 'posts')); + $this->assertEqual(Router::url(array('action' => 'index')), '/'); + } + +/** + * testRouterIdentity method + * + * @access public + * @return void + */ + function testRouterIdentity() { + $router2 = new Router(); + $this->assertEqual(get_object_vars($this->router), get_object_vars($router2)); + } + +/** + * testResourceRoutes method + * + * @access public + * @return void + */ + function testResourceRoutes() { + Router::mapResources('Posts'); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $result = Router::parse('/posts'); + $this->assertEqual($result, array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'index', '[method]' => 'GET')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $result = Router::parse('/posts/13'); + $this->assertEqual($result, array('pass' => array('13'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'view', 'id' => '13', '[method]' => 'GET')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $result = Router::parse('/posts'); + $this->assertEqual($result, array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'add', '[method]' => 'POST')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $result = Router::parse('/posts/13'); + $this->assertEqual($result, array('pass' => array('13'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'edit', 'id' => '13', '[method]' => 'PUT')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + + $result = Router::parse('/posts/475acc39-a328-44d3-95fb-015000000000'); + $this->assertEqual($result, array('pass' => array('475acc39-a328-44d3-95fb-015000000000'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'edit', 'id' => '475acc39-a328-44d3-95fb-015000000000', '[method]' => 'PUT')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + + $_SERVER['REQUEST_METHOD'] = 'DELETE'; + $result = Router::parse('/posts/13'); + $this->assertEqual($result, array('pass' => array('13'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'delete', 'id' => '13', '[method]' => 'DELETE')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $result = Router::parse('/posts/add'); + $this->assertEqual($result, array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'add')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + + Router::reload(); + Router::mapResources('Posts', array('id' => '[a-z0-9_]+')); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $result = Router::parse('/posts/add'); + $this->assertEqual($result, array('pass' => array('add'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'view', 'id' => 'add', '[method]' => 'GET')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $result = Router::parse('/posts/name'); + $this->assertEqual($result, array('pass' => array('name'), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'edit', 'id' => 'name', '[method]' => 'PUT')); + $this->assertEqual($this->router->__resourceMapped, array('posts')); + } + +/** + * testMultipleResourceRoute method + * + * @access public + * @return void + */ + function testMultipleResourceRoute() { + Router::connect('/:controller', array('action' => 'index', '[method]' => array('GET', 'POST'))); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $result = Router::parse('/posts'); + $this->assertEqual($result, array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'index', '[method]' => array('GET', 'POST'))); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $result = Router::parse('/posts'); + $this->assertEqual($result, array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'posts', 'action' => 'index', '[method]' => array('GET', 'POST'))); + } + +/** + * testGenerateUrlResourceRoute method + * + * @access public + * @return void + */ + function testGenerateUrlResourceRoute() { + Router::mapResources('Posts'); + + $result = Router::url(array('controller' => 'posts', 'action' => 'index', '[method]' => 'GET')); + $expected = '/posts'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action' => 'view', '[method]' => 'GET', 'id' => 10)); + $expected = '/posts/10'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action' => 'add', '[method]' => 'POST')); + $expected = '/posts'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action' => 'edit', '[method]' => 'PUT', 'id' => 10)); + $expected = '/posts/10'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action' => 'delete', '[method]' => 'DELETE', 'id' => 10)); + $expected = '/posts/10'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action' => 'edit', '[method]' => 'POST', 'id' => 10)); + $expected = '/posts/10'; + $this->assertEqual($result, $expected); + } + +/** + * testUrlNormalization method + * + * @access public + * @return void + */ + function testUrlNormalization() { + $expected = '/users/logout'; + + $result = Router::normalize('/users/logout/'); + $this->assertEqual($result, $expected); + + $result = Router::normalize('//users//logout//'); + $this->assertEqual($result, $expected); + + $result = Router::normalize('users/logout'); + $this->assertEqual($result, $expected); + + $result = Router::normalize(array('controller' => 'users', 'action' => 'logout')); + $this->assertEqual($result, $expected); + + $result = Router::normalize('/'); + $this->assertEqual($result, '/'); + + $result = Router::normalize('http://google.com/'); + $this->assertEqual($result, 'http://google.com/'); + + $result = Router::normalize('http://google.com//'); + $this->assertEqual($result, 'http://google.com//'); + + $result = Router::normalize('/users/login/scope://foo'); + $this->assertEqual($result, '/users/login/scope:/foo'); + + $result = Router::normalize('/recipe/recipes/add'); + $this->assertEqual($result, '/recipe/recipes/add'); + + Router::setRequestInfo(array(array(), array('base' => '/us'))); + $result = Router::normalize('/us/users/logout/'); + $this->assertEqual($result, '/users/logout'); + + Router::reload(); + + Router::setRequestInfo(array(array(), array('base' => '/cake_12'))); + $result = Router::normalize('/cake_12/users/logout/'); + $this->assertEqual($result, '/users/logout'); + + Router::reload(); + $_back = Configure::read('App.baseUrl'); + Configure::write('App.baseUrl', '/'); + + Router::setRequestInfo(array(array(), array('base' => '/'))); + $result = Router::normalize('users/login'); + $this->assertEqual($result, '/users/login'); + Configure::write('App.baseUrl', $_back); + + Router::reload(); + Router::setRequestInfo(array(array(), array('base' => 'beer'))); + $result = Router::normalize('beer/admin/beers_tags/add'); + $this->assertEqual($result, '/admin/beers_tags/add'); + + $result = Router::normalize('/admin/beers_tags/add'); + $this->assertEqual($result, '/admin/beers_tags/add'); + } + +/** + * test generation of basic urls. + * + * @access public + * @return void + */ + function testUrlGenerationBasic() { + extract(Router::getNamedExpressions()); + + Router::setRequestInfo(array( + array( + 'pass' => array(), 'action' => 'index', 'plugin' => null, 'controller' => 'subscribe', + 'admin' => true, 'url' => array('url' => '') + ), + array( + 'base' => '/magazine', 'here' => '/magazine', + 'webroot' => '/magazine/', 'passedArgs' => array('page' => 2), 'namedArgs' => array('page' => 2), + ) + )); + $result = Router::url(); + $this->assertEqual('/magazine', $result); + + Router::reload(); + + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + $out = Router::url(array('controller' => 'pages', 'action' => 'display', 'home')); + $this->assertEqual($out, '/'); + + Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); + $result = Router::url(array('controller' => 'pages', 'action' => 'display', 'about')); + $expected = '/pages/about'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/:plugin/:id/*', array('controller' => 'posts', 'action' => 'view'), array('id' => $ID)); + Router::parse('/'); + + $result = Router::url(array('plugin' => 'cake_plugin', 'controller' => 'posts', 'action' => 'view', 'id' => '1')); + $expected = '/cake_plugin/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('plugin' => 'cake_plugin', 'controller' => 'posts', 'action' => 'view', 'id' => '1', '0')); + $expected = '/cake_plugin/1/0'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/:controller/:action/:id', array(), array('id' => $ID)); + Router::parse('/'); + + $result = Router::url(array('controller' => 'posts', 'action' => 'view', 'id' => '1')); + $expected = '/posts/view/1'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/:controller/:id', array('action' => 'view')); + Router::parse('/'); + + $result = Router::url(array('controller' => 'posts', 'action' => 'view', 'id' => '1')); + $expected = '/posts/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0')); + $expected = '/posts/index/0'; + $this->assertEqual($result, $expected); + + Router::connect('/view/*', array('controller' => 'posts', 'action' => 'view')); + Router::promote(); + $result = Router::url(array('controller' => 'posts', 'action' => 'view', '1')); + $expected = '/view/1'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::setRequestInfo(array( + array('pass' => array(), 'action' => 'index', 'plugin' => null, 'controller' => 'real_controller_name', 'url' => array('url' => '')), + array( + 'base' => '/', 'here' => '/', + 'webroot' => '/', 'passedArgs' => array('page' => 2), 'namedArgs' => array('page' => 2), + ) + )); + Router::connect('short_controller_name/:action/*', array('controller' => 'real_controller_name')); + Router::parse('/'); + + $result = Router::url(array('controller' => 'real_controller_name', 'page' => '1')); + $expected = '/short_controller_name/index/page:1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'add')); + $expected = '/short_controller_name/add'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::parse('/'); + Router::setRequestInfo(array( + array('pass' => array(), 'action' => 'index', 'plugin' => null, 'controller' => 'users', 'url' => array('url' => 'users')), + array( + 'base' => '/', 'here' => '/', + 'webroot' => '/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array(), + ) + )); + + $result = Router::url(array('action' => 'login')); + $expected = '/users/login'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/page/*', array('plugin' => null, 'controller' => 'pages', 'action' => 'view')); + Router::parse('/'); + + $result = Router::url(array('plugin' => 'my_plugin', 'controller' => 'pages', 'action' => 'view', 'my-page')); + $expected = '/my_plugin/pages/view/my-page'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/contact/:action', array('plugin' => 'contact', 'controller' => 'contact')); + Router::parse('/'); + + $result = Router::url(array('plugin' => 'contact', 'controller' => 'contact', 'action' => 'me')); + + $expected = '/contact/me'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::setRequestInfo(array( + array( + 'pass' => array(), 'action' => 'index', 'plugin' => 'myplugin', 'controller' => 'mycontroller', + 'admin' => false, 'url' => array('url' => array()) + ), + array( + 'base' => '/', 'here' => '/', + 'webroot' => '/', 'passedArgs' => array(), 'namedArgs' => array(), + ) + )); + + $result = Router::url(array('plugin' => null, 'controller' => 'myothercontroller')); + $expected = '/myothercontroller'; + $this->assertEqual($result, $expected); + } + +/** + * Test generation of routes with query string parameters. + * + * @return void + **/ + function testUrlGenerationWithQueryStrings() { + $result = Router::url(array('controller' => 'posts', 'action'=>'index', '0', '?' => 'var=test&var2=test2')); + $expected = '/posts/index/0?var=test&var2=test2'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', '0', '?' => 'var=test&var2=test2')); + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', '0', '?' => array('var' => 'test', 'var2' => 'test2'))); + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', '0', '?' => array('var' => null))); + $this->assertEqual($result, '/posts/index/0'); + + $result = Router::url(array('controller' => 'posts', '0', '?' => 'var=test&var2=test2', '#' => 'unencoded string %')); + $expected = '/posts/index/0?var=test&var2=test2#unencoded+string+%25'; + $this->assertEqual($result, $expected); + } + +/** + * test that regex validation of keyed route params is working. + * + * @return void + **/ + function testUrlGenerationWithRegexQualifiedParams() { + Router::connect( + ':language/galleries', + array('controller' => 'galleries', 'action' => 'index'), + array('language' => '[a-z]{3}') + ); + + Router::connect( + '/:language/:admin/:controller/:action/*', + array('admin' => 'admin'), + array('language' => '[a-z]{3}', 'admin' => 'admin') + ); + + Router::connect('/:language/:controller/:action/*', + array(), + array('language' => '[a-z]{3}') + ); + + $result = Router::url(array('admin' => false, 'language' => 'dan', 'action' => 'index', 'controller' => 'galleries')); + $expected = '/dan/galleries'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('admin' => false, 'language' => 'eng', 'action' => 'index', 'controller' => 'galleries')); + $expected = '/eng/galleries'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/:language/pages', + array('controller' => 'pages', 'action' => 'index'), + array('language' => '[a-z]{3}') + ); + Router::connect('/:language/:controller/:action/*', array(), array('language' => '[a-z]{3}')); + + $result = Router::url(array('language' => 'eng', 'action' => 'index', 'controller' => 'pages')); + $expected = '/eng/pages'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('language' => 'eng', 'controller' => 'pages')); + $this->assertEqual($result, $expected); + + $result = Router::url(array('language' => 'eng', 'controller' => 'pages', 'action' => 'add')); + $expected = '/eng/pages/add'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/forestillinger/:month/:year/*', + array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar'), + array('month' => '0[1-9]|1[012]', 'year' => '[12][0-9]{3}') + ); + Router::parse('/'); + + $result = Router::url(array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'month' => 10, 'year' => 2007, 'min-forestilling')); + $expected = '/forestillinger/10/2007/min-forestilling'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/kalender/:month/:year/*', + array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar'), + array('month' => '0[1-9]|1[012]', 'year' => '[12][0-9]{3}') + ); + Router::connect('/kalender/*', array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar')); + Router::parse('/'); + + $result = Router::url(array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'min-forestilling')); + $expected = '/kalender/min-forestilling'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'year' => 2007, 'month' => 10, 'min-forestilling')); + $expected = '/kalender/10/2007/min-forestilling'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/:controller/:action/*', array(), array( + 'controller' => 'source|wiki|commits|tickets|comments|view', + 'action' => 'branches|history|branch|logs|view|start|add|edit|modify' + )); + Router::defaults(false); + $result = Router::parse('/foo/bar'); + $expected = array('pass' => array(), 'named' => array()); + $this->assertEqual($result, $expected); + } + +/** + * Test url generation with an admin prefix + * + * @access public + * @return void + */ + function testUrlGenerationWithAdminPrefix() { + Configure::write('Routing.admin', 'admin'); + Router::reload(); + + Router::connectNamed(array('event', 'lang')); + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + Router::connect('/pages/contact_us', array('controller' => 'pages', 'action' => 'contact_us')); + Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); + Router::connect('/reset/*', array('admin' => true, 'controller' => 'users', 'action' => 'reset')); + Router::connect('/tests', array('controller' => 'tests', 'action' => 'index')); + Router::parseExtensions('rss'); + + Router::setRequestInfo(array( + array('pass' => array(), 'named' => array(), 'controller' => 'registrations', 'action' => 'admin_index', 'plugin' => '', 'prefix' => 'admin', 'admin' => true, 'url' => array('ext' => 'html', 'url' => 'admin/registrations/index'), 'form' => array()), + array('base' => '', 'here' => '/admin/registrations/index', 'webroot' => '/') + )); + + $result = Router::url(array('page' => 2)); + $expected = '/admin/registrations/index/page:2'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::setRequestInfo(array( + array( + 'pass' => array(), 'action' => 'admin_index', 'plugin' => null, 'controller' => 'subscriptions', + 'admin' => true, 'url' => array('url' => 'admin/subscriptions/index/page:2'), + ), + array( + 'base' => '/magazine', 'here' => '/magazine/admin/subscriptions/index/page:2', + 'webroot' => '/magazine/', 'passedArgs' => array('page' => 2), + ) + )); + Router::parse('/'); + + $result = Router::url(array('page' => 3)); + $expected = '/magazine/admin/subscriptions/index/page:3'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/admin/subscriptions/:action/*', array('controller' => 'subscribe', 'admin' => true, 'prefix' => 'admin')); + Router::parse('/'); + Router::setRequestInfo(array( + array( + 'pass' => array(), 'action' => 'admin_index', 'plugin' => null, 'controller' => 'subscribe', + 'admin' => true, 'url' => array('url' => 'admin/subscriptions/edit/1') + ), + array( + 'base' => '/magazine', 'here' => '/magazine/admin/subscriptions/edit/1', + 'webroot' => '/magazine/', 'passedArgs' => array('page' => 2), 'namedArgs' => array('page' => 2), + ) + )); + + $result = Router::url(array('action' => 'edit', 1)); + $expected = '/magazine/admin/subscriptions/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('admin' => true, 'controller' => 'users', 'action' => 'login')); + $expected = '/magazine/admin/users/login'; + $this->assertEqual($result, $expected); + + + Router::reload(); + Router::setRequestInfo(array( + array('pass' => array(), 'admin' => true, 'action' => 'index', 'plugin' => null, 'controller' => 'users', 'url' => array('url' => 'users')), + array( + 'base' => '/', 'here' => '/', + 'webroot' => '/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array(), + ) + )); + Router::connect('/page/*', array('controller' => 'pages', 'action' => 'view', 'admin' => true, 'prefix' => 'admin')); + Router::parse('/'); + + $result = Router::url(array('admin' => true, 'controller' => 'pages', 'action' => 'view', 'my-page')); + $expected = '/page/my-page'; + $this->assertEqual($result, $expected); + + Configure::write('Routing.admin', 'admin'); + Router::reload(); + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'pages', 'action' => 'admin_add', 'pass' => array(), 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/pages/add')), + array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/admin/pages/add', 'webroot' => '/') + )); + Router::parse('/'); + + $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'add', 'id' => false)); + $expected = '/admin/pages/add'; + $this->assertEqual($result, $expected); + + + Router::reload(); + Router::parse('/'); + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'pages', 'action' => 'admin_add', 'pass' => array(), 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/pages/add')), + array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/admin/pages/add', 'webroot' => '/') + )); + + $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'add', 'id' => false)); + $expected = '/admin/pages/add'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/admin/:controller/:action/:id', array('admin' => true), array('id' => '[0-9]+')); + Router::parse('/'); + Router::setRequestInfo(array( + array ('plugin' => null, 'controller' => 'pages', 'action' => 'admin_edit', 'pass' => array('284'), 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/pages/edit/284')), + array ('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/admin/pages/edit/284', 'webroot' => '/') + )); + + $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'edit', 'id' => '284')); + $expected = '/admin/pages/edit/284'; + $this->assertEqual($result, $expected); + + + Router::reload(); + Router::parse('/'); + Router::setRequestInfo(array( + array ('plugin' => null, 'controller' => 'pages', 'action' => 'admin_add', 'pass' => array(), 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/pages/add')), + array ('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/admin/pages/add', 'webroot' => '/') + )); + + $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'add', 'id' => false)); + $expected = '/admin/pages/add'; + $this->assertEqual($result, $expected); + + + Router::reload(); + Router::parse('/'); + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'pages', 'action' => 'admin_edit', 'pass' => array('284'), 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/pages/edit/284')), + array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/admin/pages/edit/284', 'webroot' => '/') + )); + + $result = Router::url(array('plugin' => null, 'controller' => 'pages', 'action' => 'edit', 284)); + $expected = '/admin/pages/edit/284'; + $this->assertEqual($result, $expected); + + + Router::reload(); + Router::connect('/admin/posts/*', array('controller' => 'posts', 'action' => 'index', 'admin' => true)); + Router::parse('/'); + Router::setRequestInfo(array( + array('pass' => array(), 'action' => 'admin_index', 'plugin' => null, 'controller' => 'posts', 'prefix' => 'admin', 'admin' => true, 'url' => array('url' => 'admin/posts')), + array('base' => '', 'here' => '/admin/posts', 'webroot' => '/') + )); + + $result = Router::url(array('all')); + $expected = '/admin/posts/all'; + $this->assertEqual($result, $expected); + } + +/** + * testUrlGenerationWithExtensions method + * + * @access public + * @return void + */ + function testUrlGenerationWithExtensions() { + Router::parse('/'); + $result = Router::url(array('plugin' => null, 'controller' => 'articles', 'action' => 'add', 'id' => null, 'ext' => 'json')); + $expected = '/articles/add.json'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('plugin' => null, 'controller' => 'articles', 'action' => 'add', 'ext' => 'json')); + $expected = '/articles/add.json'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'id' => null, 'ext' => 'json')); + $expected = '/articles.json'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('plugin' => null, 'controller' => 'articles', 'action' => 'index', 'ext' => 'json')); + $expected = '/articles.json'; + $this->assertEqual($result, $expected); + } + +/** + * testPluginUrlGeneration method + * + * @access public + * @return void + */ + function testUrlGenerationPlugins() { + Router::setRequestInfo(array( + array( + 'controller' => 'controller', 'action' => 'index', 'form' => array(), + 'url' => array(), 'plugin' => 'test' + ), + array( + 'base' => '/base', 'here' => '/clients/sage/portal/donations', 'webroot' => '/base/', + 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array() + ) + )); + + $this->assertEqual(Router::url('read/1'), '/base/test/controller/read/1'); + + Router::reload(); + Router::connect('/:lang/:plugin/:controller/*', array('action' => 'index')); + + Router::setRequestInfo(array( + array( + 'lang' => 'en', + 'plugin' => 'shows', 'controller' => 'shows', 'action' => 'index', 'pass' => + array(), 'form' => array(), 'url' => + array('url' => 'en/shows/')), + array('plugin' => NULL, 'controller' => NULL, 'action' => NULL, 'base' => '', + 'here' => '/en/shows/', 'webroot' => '/') + )); + + Router::parse('/en/shows/'); + + $result = Router::url(array( + 'lang' => 'en', + 'controller' => 'shows', 'action' => 'index', 'page' => '1', + )); + $expected = '/en/shows/shows/page:1'; + $this->assertEqual($result, $expected); + } + +/** + * test that you can leave active plugin routes with plugin = null + * + * @return void + */ + function testCanLeavePlugin() { + Router::reload(); + Router::connect( + '/admin/other/:controller/:action/*', + array( + 'admin' => 1, + 'plugin' => 'aliased', + 'prefix' => 'admin' + ) + ); + Router::setRequestInfo(array( + array( + 'pass' => array(), + 'admin' => true, + 'prefix' => 'admin', + 'plugin' => 'this', + 'action' => 'admin_index', + 'controller' => 'interesting', + 'url' => array('url' => 'admin/this/interesting/index'), + ), + array( + 'base' => '', + 'here' => '/admin/this/interesting/index', + 'webroot' => '/', + 'passedArgs' => array(), + ) + )); + $result = Router::url(array('plugin' => null, 'controller' => 'posts', 'action' => 'index')); + $this->assertEqual($result, '/admin/posts'); + + $result = Router::url(array('controller' => 'posts', 'action' => 'index')); + $this->assertEqual($result, '/admin/this/posts'); + + $result = Router::url(array('plugin' => 'aliased', 'controller' => 'posts', 'action' => 'index')); + $this->assertEqual($result, '/admin/other/posts/index'); + } + +/** + * testUrlParsing method + * + * @access public + * @return void + */ + function testUrlParsing() { + extract(Router::getNamedExpressions()); + + Router::connect('/posts/:value/:somevalue/:othervalue/*', array('controller' => 'posts', 'action' => 'view'), array('value','somevalue', 'othervalue')); + $result = Router::parse('/posts/2007/08/01/title-of-post-here'); + $expected = array('value' => '2007', 'somevalue' => '08', 'othervalue' => '01', 'controller' => 'posts', 'action' => 'view', 'plugin' =>'', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); + $this->assertEqual($result, $expected); + + $this->router->routes = array(); + Router::connect('/posts/:year/:month/:day/*', array('controller' => 'posts', 'action' => 'view'), array('year' => $Year, 'month' => $Month, 'day' => $Day)); + $result = Router::parse('/posts/2007/08/01/title-of-post-here'); + $expected = array('year' => '2007', 'month' => '08', 'day' => '01', 'controller' => 'posts', 'action' => 'view', 'plugin' =>'', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); + $this->assertEqual($result, $expected); + + $this->router->routes = array(); + Router::connect('/posts/:day/:year/:month/*', array('controller' => 'posts', 'action' => 'view'), array('year' => $Year, 'month' => $Month, 'day' => $Day)); + $result = Router::parse('/posts/01/2007/08/title-of-post-here'); + $expected = array('day' => '01', 'year' => '2007', 'month' => '08', 'controller' => 'posts', 'action' => 'view', 'plugin' =>'', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); + $this->assertEqual($result, $expected); + + $this->router->routes = array(); + Router::connect('/posts/:month/:day/:year/*', array('controller' => 'posts', 'action' => 'view'), array('year' => $Year, 'month' => $Month, 'day' => $Day)); + $result = Router::parse('/posts/08/01/2007/title-of-post-here'); + $expected = array('month' => '08', 'day' => '01', 'year' => '2007', 'controller' => 'posts', 'action' => 'view', 'plugin' =>'', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); + $this->assertEqual($result, $expected); + + $this->router->routes = array(); + Router::connect('/posts/:year/:month/:day/*', array('controller' => 'posts', 'action' => 'view')); + $result = Router::parse('/posts/2007/08/01/title-of-post-here'); + $expected = array('year' => '2007', 'month' => '08', 'day' => '01', 'controller' => 'posts', 'action' => 'view', 'plugin' =>'', 'pass' => array('0' => 'title-of-post-here'), 'named' => array()); + $this->assertEqual($result, $expected); + + Router::reload(); + $result = Router::parse('/pages/display/home'); + $expected = array('plugin' => null, 'pass' => array('home'), 'controller' => 'pages', 'action' => 'display', 'named' => array()); + $this->assertEqual($result, $expected); + + $result = Router::parse('pages/display/home/'); + $this->assertEqual($result, $expected); + + $result = Router::parse('pages/display/home'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/page/*', array('controller' => 'test')); + $result = Router::parse('/page/my-page'); + $expected = array('pass' => array('my-page'), 'plugin' => null, 'controller' => 'test', 'action' => 'index'); + + Router::reload(); + Router::connect('/:language/contact', array('language' => 'eng', 'plugin' => 'contact', 'controller' => 'contact', 'action' => 'index'), array('language' => '[a-z]{3}')); + $result = Router::parse('/eng/contact'); + $expected = array('pass' => array(), 'named' => array(), 'language' => 'eng', 'plugin' => 'contact', 'controller' => 'contact', 'action' => 'index'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/forestillinger/:month/:year/*', + array('plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar'), + array('month' => '0[1-9]|1[012]', 'year' => '[12][0-9]{3}') + ); + + $result = Router::parse('/forestillinger/10/2007/min-forestilling'); + $expected = array('pass' => array('min-forestilling'), 'plugin' => 'shows', 'controller' => 'shows', 'action' => 'calendar', 'year' => 2007, 'month' => 10, 'named' => array()); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/:controller/:action/*'); + Router::connect('/', array('plugin' => 'pages', 'controller' => 'pages', 'action' => 'display')); + $result = Router::parse('/'); + $expected = array('pass' => array(), 'named' => array(), 'controller' => 'pages', 'action' => 'display', 'plugin' => 'pages'); + $this->assertEqual($result, $expected); + + $result = Router::parse('/posts/edit/0'); + $expected = array('pass' => array(0), 'named' => array(), 'controller' => 'posts', 'action' => 'edit', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/posts/:id::url_title', array('controller' => 'posts', 'action' => 'view'), array('pass' => array('id', 'url_title'), 'id' => '[\d]+')); + $result = Router::parse('/posts/5:sample-post-title'); + $expected = array('pass' => array('5', 'sample-post-title'), 'named' => array(), 'id' => 5, 'url_title' => 'sample-post-title', 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/posts/:id::url_title/*', array('controller' => 'posts', 'action' => 'view'), array('pass' => array('id', 'url_title'), 'id' => '[\d]+')); + $result = Router::parse('/posts/5:sample-post-title/other/params/4'); + $expected = array('pass' => array('5', 'sample-post-title', 'other', 'params', '4'), 'named' => array(), 'id' => 5, 'url_title' => 'sample-post-title', 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/posts/:url_title-(uuid::id)', array('controller' => 'posts', 'action' => 'view'), array('pass' => array('id', 'url_title'), 'id' => $UUID)); + $result = Router::parse('/posts/sample-post-title-(uuid:47fc97a9-019c-41d1-a058-1fa3cbdd56cb)'); + $expected = array('pass' => array('47fc97a9-019c-41d1-a058-1fa3cbdd56cb', 'sample-post-title'), 'named' => array(), 'id' => '47fc97a9-019c-41d1-a058-1fa3cbdd56cb', 'url_title' => 'sample-post-title', 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('named' => false)); + $result = Router::parse('/posts/view/foo:bar/routing:fun'); + $expected = array('pass' => array('foo:bar', 'routing:fun'), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('named' => array('foo', 'answer'))); + $result = Router::parse('/posts/view/foo:bar/routing:fun/answer:42'); + $expected = array('pass' => array('routing:fun'), 'named' => array('foo' => 'bar', 'answer' => '42'), 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('named' => array('foo', 'answer'), 'greedy' => true)); + $result = Router::parse('/posts/view/foo:bar/routing:fun/answer:42'); + $expected = array('pass' => array(), 'named' => array('foo' => 'bar', 'routing' => 'fun', 'answer' => '42'), 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); + $this->assertEqual($result, $expected); + } + +/** + * test that the persist key works. + * + * @return void + */ + function testPersistentParameters() { + Router::reload(); + Router::connect( + '/:lang/:color/posts/view/*', + array('controller' => 'posts', 'action' => 'view'), + array('persist' => array('lang', 'color') + )); + Router::connect( + '/:lang/:color/posts/index', + array('controller' => 'posts', 'action' => 'index'), + array('persist' => array('lang') + )); + Router::connect('/:lang/:color/posts/edit/*', array('controller' => 'posts', 'action' => 'edit')); + Router::connect('/about', array('controller' => 'pages', 'action' => 'view', 'about')); + Router::parse('/en/red/posts/view/5'); + + Router::setRequestInfo(array( + array('controller' => 'posts', 'action' => 'view', 'lang' => 'en', 'color' => 'red', 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '/', 'here' => '/en/red/posts/view/5', 'webroot' => '/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array()) + )); + $expected = '/en/red/posts/view/6'; + $result = Router::url(array('controller' => 'posts', 'action' => 'view', 6)); + $this->assertEqual($result, $expected); + + $expected = '/en/blue/posts/index'; + $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'color' => 'blue')); + $this->assertEqual($result, $expected); + + $expected = '/posts/edit/6'; + $result = Router::url(array('controller' => 'posts', 'action' => 'edit', 6, 'color' => null, 'lang' => null)); + $this->assertEqual($result, $expected); + + $expected = '/posts'; + $result = Router::url(array('controller' => 'posts', 'action' => 'index')); + $this->assertEqual($result, $expected); + + $expected = '/posts/edit/7'; + $result = Router::url(array('controller' => 'posts', 'action' => 'edit', 7)); + $this->assertEqual($result, $expected); + + $expected = '/about'; + $result = Router::url(array('controller' => 'pages', 'action' => 'view', 'about')); + $this->assertEqual($result, $expected); + } + +/** + * testUuidRoutes method + * + * @access public + * @return void + */ + function testUuidRoutes() { + Router::connect( + '/subjects/add/:category_id', + array('controller' => 'subjects', 'action' => 'add'), + array('category_id' => '\w{8}-\w{4}-\w{4}-\w{4}-\w{12}') + ); + $result = Router::parse('/subjects/add/4795d601-19c8-49a6-930e-06a8b01d17b7'); + $expected = array('pass' => array(), 'named' => array(), 'category_id' => '4795d601-19c8-49a6-930e-06a8b01d17b7', 'plugin' => null, 'controller' => 'subjects', 'action' => 'add'); + $this->assertEqual($result, $expected); + } + +/** + * testRouteSymmetry method + * + * @access public + * @return void + */ + function testRouteSymmetry() { + Router::connect( + "/:extra/page/:slug/*", + array('controller' => 'pages', 'action' => 'view', 'extra' => null), + array("extra" => '[a-z1-9_]*', "slug" => '[a-z1-9_]+', "action" => 'view') + ); + + $result = Router::parse('/some_extra/page/this_is_the_slug'); + $expected = array('pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'view', 'slug' => 'this_is_the_slug', 'extra' => 'some_extra'); + $this->assertEqual($result, $expected); + + $result = Router::parse('/page/this_is_the_slug'); + $expected = array('pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'view', 'slug' => 'this_is_the_slug', 'extra' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect( + "/:extra/page/:slug/*", + array('controller' => 'pages', 'action' => 'view', 'extra' => null), + array("extra" => '[a-z1-9_]*', "slug" => '[a-z1-9_]+') + ); + Router::parse('/'); + + $result = Router::url(array('admin' => null, 'plugin' => null, 'controller' => 'pages', 'action' => 'view', 'slug' => 'this_is_the_slug', 'extra' => null)); + $expected = '/page/this_is_the_slug'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('admin' => null, 'plugin' => null, 'controller' => 'pages', 'action' => 'view', 'slug' => 'this_is_the_slug', 'extra' => 'some_extra')); + $expected = '/some_extra/page/this_is_the_slug'; + $this->assertEqual($result, $expected); + } + +/** + * Test that Routing.prefixes and Routing.admin are used when a Router instance is created + * or reset + * + * @return void + */ + function testRoutingPrefixesSetting() { + $restore = Configure::read('Routing'); + + Configure::write('Routing.admin', 'admin'); + Configure::write('Routing.prefixes', array('member', 'super_user')); + Router::reload(); + $result = Router::prefixes(); + $expected = array('admin', 'member', 'super_user'); + $this->assertEqual($result, $expected); + + Configure::write('Routing.prefixes', 'member'); + Router::reload(); + $result = Router::prefixes(); + $expected = array('admin', 'member'); + $this->assertEqual($result, $expected); + + Configure::write('Routing', $restore); + } + +/** + * test compatibility with old Routing.admin config setting. + * + * @access public + * @return void + * @todo Once Routing.admin is removed update these tests. + */ + function testAdminRoutingCompatibility() { + Configure::write('Routing.admin', 'admin'); + + Router::reload(); + Router::connect('/admin', array('admin' => true, 'controller' => 'users')); + $result = Router::parse('/admin'); + + $expected = array('pass' => array(), 'named' => array(), 'plugin' => '', 'controller' => 'users', 'action' => 'index', 'admin' => true, 'prefix' => 'admin'); + $this->assertEqual($result, $expected); + + $result = Router::url(array('admin' => true, 'controller' => 'posts', 'action' => 'index', '0', '?' => 'var=test&var2=test2')); + $expected = '/admin/posts/index/0?var=test&var2=test2'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::parse('/'); + $result = Router::url(array('admin' => false, 'controller' => 'posts', 'action' => 'index', '0', '?' => 'var=test&var2=test2')); + $expected = '/posts/index/0?var=test&var2=test2'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::setRequestInfo(array( + array('admin' => true, 'controller' => 'controller', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '/', 'here' => '/', 'webroot' => '/base/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array()) + )); + + Router::parse('/'); + $result = Router::url(array('admin' => false, 'controller' => 'posts', 'action' => 'index', '0', '?' => 'var=test&var2=test2')); + $expected = '/posts/index/0?var=test&var2=test2'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0', '?' => 'var=test&var2=test2')); + $expected = '/admin/posts/index/0?var=test&var2=test2'; + $this->assertEqual($result, $expected); + + Router::reload(); + $result = Router::parse('admin/users/view/'); + $expected = array('pass' => array(), 'named' => array(), 'controller' => 'users', 'action' => 'view', 'plugin' => null, 'prefix' => 'admin', 'admin' => true); + $this->assertEqual($result, $expected); + + Configure::write('Routing.admin', 'beheer'); + + Router::reload(); + Router::setRequestInfo(array( + array('beheer' => true, 'controller' => 'posts', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '/', 'here' => '/beheer/posts/index', 'webroot' => '/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array()) + )); + + $result = Router::parse('beheer/users/view/'); + $expected = array('pass' => array(), 'named' => array(), 'controller' => 'users', 'action' => 'view', 'plugin' => null, 'prefix' => 'beheer', 'beheer' => true); + $this->assertEqual($result, $expected); + + + $result = Router::url(array('controller' => 'posts', 'action' => 'index', '0', '?' => 'var=test&var2=test2')); + $expected = '/beheer/posts/index/0?var=test&var2=test2'; + $this->assertEqual($result, $expected); + } + +/** + * Test prefix routing and plugin combinations + * + * @return void + */ + function testPrefixRoutingAndPlugins() { + Configure::write('Routing.prefixes', array('admin')); + $paths = App::path('plugins'); + App::build(array( + 'plugins' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS + ) + ), true); + App::objects('plugin', null, false); + + Router::reload(); + Router::setRequestInfo(array( + array('admin' => true, 'controller' => 'controller', 'action' => 'action', + 'form' => array(), 'url' => array(), 'plugin' => null, 'prefix' => 'admin'), + array('base' => '/', 'here' => '/', 'webroot' => '/base/', 'passedArgs' => array(), + 'argSeparator' => ':', 'namedArgs' => array()) + )); + Router::parse('/'); + + $result = Router::url(array('plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index')); + $expected = '/admin/test_plugin'; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::parse('/'); + Router::setRequestInfo(array( + array( + 'plugin' => 'test_plugin', 'controller' => 'show_tickets', 'action' => 'admin_edit', + 'pass' => array('6'), 'prefix' => 'admin', 'admin' => true, 'form' => array(), + 'url' => array('url' => 'admin/shows/show_tickets/edit/6') + ), + array( + 'plugin' => null, 'controller' => null, 'action' => null, 'base' => '', + 'here' => '/admin/shows/show_tickets/edit/6', 'webroot' => '/' + ) + )); + + $result = Router::url(array( + 'plugin' => 'test_plugin', 'controller' => 'show_tickets', 'action' => 'edit', 6, + 'admin' => true, 'prefix' => 'admin' + )); + $expected = '/admin/test_plugin/show_tickets/edit/6'; + $this->assertEqual($result, $expected); + + $result = Router::url(array( + 'plugin' => 'test_plugin', 'controller' => 'show_tickets', 'action' => 'index', 'admin' => true + )); + $expected = '/admin/test_plugin/show_tickets'; + $this->assertEqual($result, $expected); + + App::build(array('plugins' => $paths)); + } + +/** + * testExtensionParsingSetting method + * + * @access public + * @return void + */ + function testExtensionParsingSetting() { + $router =& Router::getInstance(); + $this->assertFalse($this->router->__parseExtensions); + + $router->parseExtensions(); + $this->assertTrue($this->router->__parseExtensions); + } + +/** + * testExtensionParsing method + * + * @access public + * @return void + */ + function testExtensionParsing() { + Router::parseExtensions(); + + $result = Router::parse('/posts.rss'); + $expected = array('plugin' => null, 'controller' => 'posts', 'action' => 'index', 'url' => array('ext' => 'rss'), 'pass'=> array(), 'named' => array()); + $this->assertEqual($result, $expected); + + $result = Router::parse('/posts/view/1.rss'); + $expected = array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'pass' => array('1'), 'named' => array(), 'url' => array('ext' => 'rss'), 'named' => array()); + $this->assertEqual($result, $expected); + + $result = Router::parse('/posts/view/1.rss?query=test'); + $this->assertEqual($result, $expected); + + $result = Router::parse('/posts/view/1.atom'); + $expected['url'] = array('ext' => 'atom'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::parseExtensions('rss', 'xml'); + + $result = Router::parse('/posts.xml'); + $expected = array('plugin' => null, 'controller' => 'posts', 'action' => 'index', 'url' => array('ext' => 'xml'), 'pass'=> array(), 'named' => array()); + $this->assertEqual($result, $expected); + + $result = Router::parse('/posts.atom?hello=goodbye'); + $expected = array('plugin' => null, 'controller' => 'posts.atom', 'action' => 'index', 'pass' => array(), 'named' => array(), 'url' => array('ext' => 'html')); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::parseExtensions(); + $result = $this->router->__parseExtension('/posts.atom'); + $expected = array('ext' => 'atom', 'url' => '/posts'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/controller/action', array('controller' => 'controller', 'action' => 'action', 'url' => array('ext' => 'rss'))); + $result = Router::parse('/controller/action'); + $expected = array('controller' => 'controller', 'action' => 'action', 'plugin' => null, 'url' => array('ext' => 'rss'), 'named' => array(), 'pass' => array()); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::parseExtensions('rss'); + Router::connect('/controller/action', array('controller' => 'controller', 'action' => 'action', 'url' => array('ext' => 'rss'))); + $result = Router::parse('/controller/action'); + $expected = array('controller' => 'controller', 'action' => 'action', 'plugin' => null, 'url' => array('ext' => 'rss'), 'named' => array(), 'pass' => array()); + $this->assertEqual($result, $expected); + } + +/** + * testQuerystringGeneration method + * + * @access public + * @return void + */ + function testQuerystringGeneration() { + $result = Router::url(array('controller' => 'posts', 'action'=>'index', '0', '?' => 'var=test&var2=test2')); + $expected = '/posts/index/0?var=test&var2=test2'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action'=>'index', '0', '?' => array('var' => 'test', 'var2' => 'test2'))); + $this->assertEqual($result, $expected); + + $expected .= '&more=test+data'; + $result = Router::url(array('controller' => 'posts', 'action'=>'index', '0', '?' => array('var' => 'test', 'var2' => 'test2', 'more' => 'test data'))); + $this->assertEqual($result, $expected); + +// Test bug #4614 + $restore = ini_get('arg_separator.output'); + ini_set('arg_separator.output', '&'); + $result = Router::url(array('controller' => 'posts', 'action'=>'index', '0', '?' => array('var' => 'test', 'var2' => 'test2', 'more' => 'test data'))); + $this->assertEqual($result, $expected); + ini_set('arg_separator.output', $restore); + + $result = Router::url(array('controller' => 'posts', 'action'=>'index', '0', '?' => array('var' => 'test', 'var2' => 'test2')), array('escape' => true)); + $expected = '/posts/index/0?var=test&var2=test2'; + $this->assertEqual($result, $expected); + } + +/** + * testConnectNamed method + * + * @access public + * @return void + */ + function testConnectNamed() { + $named = Router::connectNamed(false, array('default' => true)); + $this->assertFalse($named['greedy']); + $this->assertEqual(array_keys($named['rules']), $named['default']); + + Router::reload(); + Router::connect('/foo/*', array('controller' => 'bar', 'action' => 'fubar')); + Router::connectNamed(array(), array('argSeparator' => '=')); + $result = Router::parse('/foo/param1=value1/param2=value2'); + $expected = array('pass' => array(), 'named' => array('param1' => 'value1', 'param2' => 'value2'), 'controller' => 'bar', 'action' => 'fubar', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/controller/action/*', array('controller' => 'controller', 'action' => 'action'), array('named' => array('param1' => 'value[\d]'))); + Router::connectNamed(array(), array('greedy' => false, 'argSeparator' => '=')); + $result = Router::parse('/controller/action/param1=value1/param2=value2'); + $expected = array('pass' => array('param2=value2'), 'named' => array('param1' => 'value1'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/:controller/:action/*'); + Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); + $result = Router::parse('/categories/index?limit=5'); + $this->assertTrue(empty($result['named'])); + } + +/** + * testNamedArgsUrlGeneration method + * + * @access public + * @return void + */ + function testNamedArgsUrlGeneration() { + $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'published' => 1, 'deleted' => 1)); + $expected = '/posts/index/published:1/deleted:1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'posts', 'action' => 'index', 'published' => 0, 'deleted' => 0)); + $expected = '/posts/index/published:0/deleted:0'; + $this->assertEqual($result, $expected); + + Router::reload(); + extract(Router::getNamedExpressions()); + Router::connectNamed(array('file'=> '[\w\.\-]+\.(html|png)')); + Router::connect('/', array('controller' => 'graphs', 'action' => 'index')); + Router::connect('/:id/*', array('controller' => 'graphs', 'action' => 'view'), array('id' => $ID)); + + $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 'id' => 12, 'file' => 'asdf.png')); + $expected = '/12/file:asdf.png'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'graphs', 'action' => 'view', 12, 'file' => 'asdf.foo')); + $expected = '/graphs/view/12/file:asdf.foo'; + $this->assertEqual($result, $expected); + + Configure::write('Routing.admin', 'admin'); + + Router::reload(); + Router::setRequestInfo(array( + array('admin' => true, 'controller' => 'controller', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '/', 'here' => '/', 'webroot' => '/base/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array()) + )); + Router::parse('/'); + + $result = Router::url(array('page' => 1, 0 => null, 'sort' => 'controller', 'direction' => 'asc', 'order' => null)); + $expected = "/admin/controller/index/page:1/sort:controller/direction:asc"; + $this->assertEqual($result, $expected); + + Router::reload(); + Router::setRequestInfo(array( + array('admin' => true, 'controller' => 'controller', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '/', 'here' => '/', 'webroot' => '/base/', 'passedArgs' => array('type'=> 'whatever'), 'argSeparator' => ':', 'namedArgs' => array('type'=> 'whatever')) + )); + + $result = Router::parse('/admin/controller/index/type:whatever'); + $result = Router::url(array('type'=> 'new')); + $expected = "/admin/controller/index/type:new"; + $this->assertEqual($result, $expected); + } + +/** + * testNamedArgsUrlParsing method + * + * @access public + * @return void + */ + function testNamedArgsUrlParsing() { + $Router =& Router::getInstance(); + Router::reload(); + $result = Router::parse('/controller/action/param1:value1:1/param2:value2:3/param:value'); + $expected = array('pass' => array(), 'named' => array('param1' => 'value1:1', 'param2' => 'value2:3', 'param' => 'value'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + $result = Router::connectNamed(false); + $this->assertEqual(array_keys($result['rules']), array()); + $this->assertFalse($result['greedy']); + $result = Router::parse('/controller/action/param1:value1:1/param2:value2:3/param:value'); + $expected = array('pass' => array('param1:value1:1', 'param2:value2:3', 'param:value'), 'named' => array(), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + $result = Router::connectNamed(true); + $this->assertEqual(array_keys($result['rules']), $Router->named['default']); + $this->assertTrue($result['greedy']); + Router::reload(); + Router::connectNamed(array('param1' => 'not-matching')); + $result = Router::parse('/controller/action/param1:value1:1/param2:value2:3/param:value'); + $expected = array('pass' => array('param1:value1:1'), 'named' => array('param2' => 'value2:3', 'param' => 'value'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/foo/:action/*', array('controller' => 'bar'), array('named' => array('param1' => array('action' => 'index')), 'greedy' => true)); + $result = Router::parse('/foo/index/param1:value1:1/param2:value2:3/param:value'); + $expected = array('pass' => array(), 'named' => array('param1' => 'value1:1', 'param2' => 'value2:3', 'param' => 'value'), 'controller' => 'bar', 'action' => 'index', 'plugin' => null); + $this->assertEqual($result, $expected); + + $result = Router::parse('/foo/view/param1:value1:1/param2:value2:3/param:value'); + $expected = array('pass' => array('param1:value1:1'), 'named' => array('param2' => 'value2:3', 'param' => 'value'), 'controller' => 'bar', 'action' => 'view', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connectNamed(array('param1' => '[\d]', 'param2' => '[a-z]', 'param3' => '[\d]')); + $result = Router::parse('/controller/action/param1:1/param2:2/param3:3'); + $expected = array('pass' => array('param2:2'), 'named' => array('param1' => '1', 'param3' => '3'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connectNamed(array('param1' => '[\d]', 'param2' => true, 'param3' => '[\d]')); + $result = Router::parse('/controller/action/param1:1/param2:2/param3:3'); + $expected = array('pass' => array(), 'named' => array('param1' => '1', 'param2' => '2', 'param3' => '3'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connectNamed(array('param1' => 'value[\d]+:[\d]+'), array('greedy' => false)); + $result = Router::parse('/controller/action/param1:value1:1/param2:value2:3/param3:value'); + $expected = array('pass' => array('param2:value2:3', 'param3:value'), 'named' => array('param1' => 'value1:1'), 'controller' => 'controller', 'action' => 'action', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/foo/*', array('controller' => 'bar', 'action' => 'fubar'), array('named' => array('param1' => 'value[\d]:[\d]'))); + Router::connectNamed(array(), array('greedy' => false)); + $result = Router::parse('/foo/param1:value1:1/param2:value2:3/param3:value'); + $expected = array('pass' => array('param2:value2:3', 'param3:value'), 'named' => array('param1' => 'value1:1'), 'controller' => 'bar', 'action' => 'fubar', 'plugin' => null); + $this->assertEqual($result, $expected); + } + +/** + * test url generation with legacy (1.2) style prefix routes. + * + * @access public + * @return void + * @todo Remove tests related to legacy style routes. + * @see testUrlGenerationWithAutoPrefixes + */ + function testUrlGenerationWithLegacyPrefixes() { + Router::reload(); + Router::connect('/protected/:controller/:action/*', array( + 'prefix' => 'protected', + 'protected' => true + )); + Router::parse('/'); + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'images', 'action' => 'index', 'pass' => array(), 'prefix' => null, 'admin' => false, 'form' => array(), 'url' => array('url' => 'images/index')), + array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/images/index', 'webroot' => '/') + )); + + $result = Router::url(array('protected' => true)); + $expected = '/protected/images/index'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'images', 'action' => 'add')); + $expected = '/images/add'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => true)); + $expected = '/protected/images/add'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'edit', 1)); + $expected = '/images/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'edit', 1, 'protected' => true)); + $expected = '/protected/images/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'protected_edit', 1, 'protected' => true)); + $expected = '/protected/images/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'edit', 1, 'protected' => true)); + $expected = '/protected/images/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1)); + $expected = '/others/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true)); + $expected = '/protected/others/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'page' => 1)); + $expected = '/protected/others/edit/1/page:1'; + $this->assertEqual($result, $expected); + + Router::connectNamed(array('random')); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'random' => 'my-value')); + $expected = '/protected/others/edit/1/random:my-value'; + $this->assertEqual($result, $expected); + } + +/** + * test newer style automatically generated prefix routes. + * + * @return void + */ + function testUrlGenerationWithAutoPrefixes() { + Configure::write('Routing.prefixes', array('protected')); + Router::reload(); + Router::parse('/'); + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'images', 'action' => 'index', 'pass' => array(), 'prefix' => null, 'protected' => false, 'form' => array(), 'url' => array('url' => 'images/index')), + array('base' => '', 'here' => '/images/index', 'webroot' => '/') + )); + + $result = Router::url(array('controller' => 'images', 'action' => 'add')); + $expected = '/images/add'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => true)); + $expected = '/protected/images/add'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'edit', 1)); + $expected = '/images/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'edit', 1, 'protected' => true)); + $expected = '/protected/images/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'protected_edit', 1, 'protected' => true)); + $expected = '/protected/images/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'protectededit', 1, 'protected' => true)); + $expected = '/protected/images/protectededit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'edit', 1, 'protected' => true)); + $expected = '/protected/images/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1)); + $expected = '/others/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true)); + $expected = '/protected/others/edit/1'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'page' => 1)); + $expected = '/protected/others/edit/1/page:1'; + $this->assertEqual($result, $expected); + + Router::connectNamed(array('random')); + $result = Router::url(array('controller' => 'others', 'action' => 'edit', 1, 'protected' => true, 'random' => 'my-value')); + $expected = '/protected/others/edit/1/random:my-value'; + $this->assertEqual($result, $expected); + } + +/** + * test that auto-generated prefix routes persist + * + * @return void + */ + function testAutoPrefixRoutePersistence() { + Configure::write('Routing.prefixes', array('protected')); + Router::reload(); + Router::parse('/'); + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'images', 'action' => 'index', 'pass' => array(), 'prefix' => 'protected', 'protected' => true, 'form' => array(), 'url' => array('url' => 'protected/images/index')), + array('base' => '', 'here' => '/protected/images/index', 'webroot' => '/') + )); + + $result = Router::url(array('controller' => 'images', 'action' => 'add')); + $expected = '/protected/images/add'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => false)); + $expected = '/images/add'; + $this->assertEqual($result, $expected); + } + +/** + * test that setting a prefix override the current one + * + * @return void + */ + function testPrefixOverride() { + Configure::write('Routing.prefixes', array('protected', 'admin')); + Router::reload(); + Router::parse('/'); + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'images', 'action' => 'index', 'pass' => array(), 'prefix' => 'protected', 'protected' => true, 'form' => array(), 'url' => array('url' => 'protected/images/index')), + array('base' => '', 'here' => '/protected/images/index', 'webroot' => '/') + )); + + $result = Router::url(array('controller' => 'images', 'action' => 'add', 'admin' => true)); + $expected = '/admin/images/add'; + $this->assertEqual($result, $expected); + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'images', 'action' => 'index', 'pass' => array(), 'prefix' => 'admin', 'admin' => true, 'form' => array(), 'url' => array('url' => 'admin/images/index')), + array('base' => '', 'here' => '/admin/images/index', 'webroot' => '/') + )); + $result = Router::url(array('controller' => 'images', 'action' => 'add', 'protected' => true)); + $expected = '/protected/images/add'; + $this->assertEqual($result, $expected); + } + +/** + * testRemoveBase method + * + * @access public + * @return void + */ + function testRemoveBase() { + Router::setRequestInfo(array( + array('controller' => 'controller', 'action' => 'index', 'form' => array(), 'url' => array(), 'bare' => 0, 'plugin' => null), + array('base' => '/base', 'here' => '/', 'webroot' => '/base/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array()) + )); + + $result = Router::url(array('controller' => 'my_controller', 'action' => 'my_action')); + $expected = '/base/my_controller/my_action'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'my_controller', 'action' => 'my_action', 'base' => false)); + $expected = '/my_controller/my_action'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'my_controller', 'action' => 'my_action', 'base' => true)); + $expected = '/base/my_controller/my_action/base:1'; + $this->assertEqual($result, $expected); + } + +/** + * testPagesUrlParsing method + * + * @access public + * @return void + */ + function testPagesUrlParsing() { + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); + + $result = Router::parse('/'); + $expected = array('pass'=> array('home'), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); + $this->assertEqual($result, $expected); + + $result = Router::parse('/pages/home/'); + $expected = array('pass' => array('home'), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + + $result = Router::parse('/pages/display/home/parameter:value'); + $expected = array('pass' => array('home'), 'named' => array('parameter' => 'value'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + + $result = Router::parse('/'); + $expected = array('pass' => array('home'), 'named' => array(), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); + $this->assertEqual($result, $expected); + + $result = Router::parse('/pages/display/home/event:value'); + $expected = array('pass' => array('home'), 'named' => array('event' => 'value'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); + $this->assertEqual($result, $expected); + + $result = Router::parse('/pages/display/home/event:Val_u2'); + $expected = array('pass' => array('home'), 'named' => array('event' => 'Val_u2'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); + $this->assertEqual($result, $expected); + + $result = Router::parse('/pages/display/home/event:val-ue'); + $expected = array('pass' => array('home'), 'named' => array('event' => 'val-ue'), 'plugin' => null, 'controller' => 'pages', 'action' => 'display'); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/', array('controller' => 'posts', 'action' => 'index')); + Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); + $result = Router::parse('/pages/contact/'); + + $expected = array('pass'=>array('contact'), 'named' => array(), 'plugin'=> null, 'controller'=>'pages', 'action'=>'display'); + $this->assertEqual($result, $expected); + } + +/** + * test that requests with a trailing dot don't loose the do. + * + * @return void + */ + function testParsingWithTrailingPeriod() { + Router::reload(); + $result = Router::parse('/posts/view/something.'); + $this->assertEqual($result['pass'][0], 'something.', 'Period was chopped off %s'); + + $result = Router::parse('/posts/view/something. . .'); + $this->assertEqual($result['pass'][0], 'something. . .', 'Period was chopped off %s'); + } + +/** + * test that requests with a trailing dot don't loose the do. + * + * @return void + */ + function testParsingWithTrailingPeriodAndParseExtensions() { + Router::reload(); + Router::parseExtensions('json'); + + $result = Router::parse('/posts/view/something.'); + $this->assertEqual($result['pass'][0], 'something.', 'Period was chopped off %s'); + + $result = Router::parse('/posts/view/something. . .'); + $this->assertEqual($result['pass'][0], 'something. . .', 'Period was chopped off %s'); + } + +/** + * test that patterns work for :action + * + * @return void + */ + function testParsingWithPatternOnAction() { + Router::reload(); + Router::connect( + '/blog/:action/*', + array('controller' => 'blog_posts'), + array('action' => 'other|actions') + ); + $result = Router::parse('/blog/other'); + $expected = array( + 'plugin' => null, + 'controller' => 'blog_posts', + 'action' => 'other', + 'pass' => array(), + 'named' => array() + ); + $this->assertEqual($expected, $result); + + $result = Router::parse('/blog/foobar'); + $expected = array( + 'plugin' => null, + 'controller' => 'blog', + 'action' => 'foobar', + 'pass' => array(), + 'named' => array() + ); + $this->assertEqual($expected, $result); + + $result = Router::url(array('controller' => 'blog_posts', 'action' => 'foo')); + $this->assertEqual('/blog_posts/foo', $result); + + $result = Router::url(array('controller' => 'blog_posts', 'action' => 'actions')); + $this->assertEqual('/blog/actions', $result); + } + +/** + * testParsingWithPrefixes method + * + * @access public + * @return void + */ + function testParsingWithPrefixes() { + $adminParams = array('prefix' => 'admin', 'admin' => true); + Router::connect('/admin/:controller', $adminParams); + Router::connect('/admin/:controller/:action', $adminParams); + Router::connect('/admin/:controller/:action/*', $adminParams); + + Router::setRequestInfo(array( + array('controller' => 'controller', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '/base', 'here' => '/', 'webroot' => '/base/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array()) + )); + + $result = Router::parse('/admin/posts/'); + $expected = array('pass' => array(), 'named' => array(), 'prefix' => 'admin', 'plugin' => null, 'controller' => 'posts', 'action' => 'index', 'admin' => true); + $this->assertEqual($result, $expected); + + $result = Router::parse('/admin/posts'); + $this->assertEqual($result, $expected); + + $result = Router::url(array('admin' => true, 'controller' => 'posts')); + $expected = '/base/admin/posts'; + $this->assertEqual($result, $expected); + + $result = Router::prefixes(); + $expected = array('admin'); + $this->assertEqual($result, $expected); + + Router::reload(); + + $prefixParams = array('prefix' => 'members', 'members' => true); + Router::connect('/members/:controller', $prefixParams); + Router::connect('/members/:controller/:action', $prefixParams); + Router::connect('/members/:controller/:action/*', $prefixParams); + + Router::setRequestInfo(array( + array('controller' => 'controller', 'action' => 'index', 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '/base', 'here' => '/', 'webroot' => '/', 'passedArgs' => array(), 'argSeparator' => ':', 'namedArgs' => array()) + )); + + $result = Router::parse('/members/posts/index'); + $expected = array('pass' => array(), 'named' => array(), 'prefix' => 'members', 'plugin' => null, 'controller' => 'posts', 'action' => 'index', 'members' => true); + $this->assertEqual($result, $expected); + + $result = Router::url(array('members' => true, 'controller' => 'posts', 'action' =>'index', 'page' => 2)); + $expected = '/base/members/posts/index/page:2'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('members' => true, 'controller' => 'users', 'action' => 'add')); + $expected = '/base/members/users/add'; + $this->assertEqual($result, $expected); + + $result = Router::parse('/posts/index'); + $expected = array('pass' => array(), 'named' => array(), 'plugin' => null, 'controller' => 'posts', 'action' => 'index'); + $this->assertEqual($result, $expected); + } + +/** + * Tests URL generation with flags and prefixes in and out of context + * + * @access public + * @return void + */ + function testUrlWritingWithPrefixes() { + Router::connect('/company/:controller/:action/*', array('prefix' => 'company', 'company' => true)); + Router::connect('/login', array('controller' => 'users', 'action' => 'login')); + + $result = Router::url(array('controller' => 'users', 'action' => 'login', 'company' => true)); + $expected = '/company/users/login'; + $this->assertEqual($result, $expected); + + Router::setRequestInfo(array( + array('controller' => 'users', 'action' => 'login', 'company' => true, 'form' => array(), 'url' => array(), 'plugin' => null), + array('base' => '/', 'here' => '/', 'webroot' => '/base/') + )); + + $result = Router::url(array('controller' => 'users', 'action' => 'login', 'company' => false)); + $expected = '/login'; + $this->assertEqual($result, $expected); + } + +/** + * test url generation with prefixes and custom routes + * + * @return void + */ + function testUrlWritingWithPrefixesAndCustomRoutes() { + Router::connect( + '/admin/login', + array('controller' => 'users', 'action' => 'login', 'prefix' => 'admin', 'admin' => true) + ); + Router::setRequestInfo(array( + array('controller' => 'posts', 'action' => 'index', 'admin' => true, 'prefix' => 'admin', + 'form' => array(), 'url' => array(), 'plugin' => null + ), + array('base' => '/', 'here' => '/', 'webroot' => '/') + )); + $result = Router::url(array('controller' => 'users', 'action' => 'login', 'admin' => true)); + $this->assertEqual($result, '/admin/login'); + + $result = Router::url(array('controller' => 'users', 'action' => 'login')); + $this->assertEqual($result, '/admin/login'); + + $result = Router::url(array('controller' => 'users', 'action' => 'admin_login')); + $this->assertEqual($result, '/admin/login'); + } + +/** + * testPassedArgsOrder method + * + * @access public + * @return void + */ + function testPassedArgsOrder() { + Router::connect('/test-passed/*', array('controller' => 'pages', 'action' => 'display', 'home')); + Router::connect('/test2/*', array('controller' => 'pages', 'action' => 'display', 2)); + Router::connect('/test/*', array('controller' => 'pages', 'action' => 'display', 1)); + Router::parse('/'); + + $result = Router::url(array('controller' => 'pages', 'action' => 'display', 1, 'whatever')); + $expected = '/test/whatever'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'pages', 'action' => 'display', 2, 'whatever')); + $expected = '/test2/whatever'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('controller' => 'pages', 'action' => 'display', 'home', 'whatever')); + $expected = '/test-passed/whatever'; + $this->assertEqual($result, $expected); + + Configure::write('Routing.prefixes', array('admin')); + Router::reload(); + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'images', 'action' => 'index', 'pass' => array(), 'named' => array(), 'prefix' => 'protected', 'protected' => true, 'form' => array(), 'url' => array ('url' => 'protected/images/index')), + array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/protected/images/index', 'webroot' => '/') + )); + + Router::connect('/protected/:controller/:action/*', array( + 'controller' => 'users', + 'action' => 'index', + 'prefix' => 'protected' + )); + + Router::parse('/'); + $result = Router::url(array('controller' => 'images', 'action' => 'add')); + $expected = '/protected/images/add'; + $this->assertEqual($result, $expected); + + $result = Router::prefixes(); + $expected = array('admin', 'protected'); + $this->assertEqual($result, $expected); + } + +/** + * testRegexRouteMatching method + * + * @access public + * @return void + */ + function testRegexRouteMatching() { + Router::connect('/:locale/:controller/:action/*', array(), array('locale' => 'dan|eng')); + + $result = Router::parse('/test/test_action'); + $expected = array('pass' => array(), 'named' => array(), 'controller' => 'test', 'action' => 'test_action', 'plugin' => null); + $this->assertEqual($result, $expected); + + $result = Router::parse('/eng/test/test_action'); + $expected = array('pass' => array(), 'named' => array(), 'locale' => 'eng', 'controller' => 'test', 'action' => 'test_action', 'plugin' => null); + $this->assertEqual($result, $expected); + + $result = Router::parse('/badness/test/test_action'); + $expected = array('pass' => array('test_action'), 'named' => array(), 'controller' => 'badness', 'action' => 'test', 'plugin' => null); + $this->assertEqual($result, $expected); + + Router::reload(); + Router::connect('/:locale/:controller/:action/*', array(), array('locale' => 'dan|eng')); + + Router::setRequestInfo(array( + array('plugin' => null, 'controller' => 'test', 'action' => 'index', 'pass' => array(), 'form' => array(), 'url' => array ('url' => 'test/test_action')), + array('plugin' => null, 'controller' => null, 'action' => null, 'base' => '', 'here' => '/test/test_action', 'webroot' => '/') + )); + + $result = Router::url(array('action' => 'test_another_action')); + $expected = '/test/test_another_action'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'test_another_action', 'locale' => 'eng')); + $expected = '/eng/test/test_another_action'; + $this->assertEqual($result, $expected); + + $result = Router::url(array('action' => 'test_another_action', 'locale' => 'badness')); + $expected = '/test/test_another_action/locale:badness'; + $this->assertEqual($result, $expected); + } + +/** + * testStripPlugin + * + * @return void + * @access public + */ + function testStripPlugin() { + $pluginName = 'forums'; + $url = 'example.com/' . $pluginName . '/'; + $expected = 'example.com'; + + $this->assertEqual(Router::stripPlugin($url, $pluginName), $expected); + $this->assertEqual(Router::stripPlugin($url), $url); + $this->assertEqual(Router::stripPlugin($url, null), $url); + } +/** + * testCurentRoute + * + * This test needs some improvement and actual requestAction() usage + * + * @return void + * @access public + */ + function testCurrentRoute() { + $url = array('controller' => 'pages', 'action' => 'display', 'government'); + Router::connect('/government', $url); + Router::parse('/government'); + $route =& Router::currentRoute(); + $this->assertEqual(array_merge($url, array('plugin' => null)), $route->defaults); + } +/** + * testRequestRoute + * + * @return void + * @access public + */ + function testRequestRoute() { + $url = array('controller' => 'products', 'action' => 'display', 5); + Router::connect('/government', $url); + Router::parse('/government'); + $route =& Router::requestRoute(); + $this->assertEqual(array_merge($url, array('plugin' => null)), $route->defaults); + + // test that the first route is matched + $newUrl = array('controller' => 'products', 'action' => 'display', 6); + Router::connect('/government', $url); + Router::parse('/government'); + $route =& Router::requestRoute(); + $this->assertEqual(array_merge($url, array('plugin' => null)), $route->defaults); + + // test that an unmatched route does not change the current route + $newUrl = array('controller' => 'products', 'action' => 'display', 6); + Router::connect('/actor', $url); + Router::parse('/government'); + $route =& Router::requestRoute(); + $this->assertEqual(array_merge($url, array('plugin' => null)), $route->defaults); + } +/** + * testGetParams + * + * @return void + * @access public + */ + function testGetParams() { + $paths = array('base' => '/', 'here' => '/products/display/5', 'webroot' => '/webroot'); + $params = array('param1' => '1', 'param2' => '2'); + Router::setRequestInfo(array($params, $paths)); + $expected = array( + 'plugin' => null, 'controller' => false, 'action' => false, + 'param1' => '1', 'param2' => '2' + ); + $this->assertEqual(Router::getparams(), $expected); + $this->assertEqual(Router::getparam('controller'), false); + $this->assertEqual(Router::getparam('param1'), '1'); + $this->assertEqual(Router::getparam('param2'), '2'); + + Router::reload(); + + $params = array('controller' => 'pages', 'action' => 'display'); + Router::setRequestInfo(array($params, $paths)); + $expected = array('plugin' => null, 'controller' => 'pages', 'action' => 'display'); + $this->assertEqual(Router::getparams(), $expected); + $this->assertEqual(Router::getparams(true), $expected); + } + +/** + * test that connectDefaults() can disable default route connection + * + * @return void + */ + function testDefaultsMethod() { + Router::defaults(false); + Router::connect('/test/*', array('controller' => 'pages', 'action' => 'display', 2)); + $result = Router::parse('/posts/edit/5'); + $this->assertFalse(isset($result['controller'])); + $this->assertFalse(isset($result['action'])); + } + +/** + * test that the required default routes are connected. + * + * @return void + */ + function testConnectDefaultRoutes() { + App::build(array( + 'plugins' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS + ) + ), true); + App::objects('plugin', null, false); + Router::reload(); + + $result = Router::url(array('plugin' => 'plugin_js', 'controller' => 'js_file', 'action' => 'index')); + $this->assertEqual($result, '/plugin_js/js_file'); + + $result = Router::parse('/plugin_js/js_file'); + $expected = array( + 'plugin' => 'plugin_js', 'controller' => 'js_file', 'action' => 'index', + 'named' => array(), 'pass' => array() + ); + $this->assertEqual($result, $expected); + + $result = Router::url(array('plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index')); + $this->assertEqual($result, '/test_plugin'); + + $result = Router::parse('/test_plugin'); + $expected = array( + 'plugin' => 'test_plugin', 'controller' => 'test_plugin', 'action' => 'index', + 'named' => array(), 'pass' => array() + ); + + $this->assertEqual($result, $expected, 'Plugin shortcut route broken. %s'); + } + +/** + * test using a custom route class for route connection + * + * @return void + */ + function testUsingCustomRouteClass() { + Mock::generate('CakeRoute', 'MockConnectedRoute'); + $routes = Router::connect( + '/:slug', + array('controller' => 'posts', 'action' => 'view'), + array('routeClass' => 'MockConnectedRoute', 'slug' => '[a-z_-]+') + ); + $this->assertTrue(is_a($routes[0], 'MockConnectedRoute'), 'Incorrect class used. %s'); + $expected = array('controller' => 'posts', 'action' => 'view', 'slug' => 'test'); + $routes[0]->setReturnValue('parse', $expected); + $result = Router::parse('/test'); + $this->assertEqual($result, $expected); + } + +/** + * test reversing parameter arrays back into strings. + * + * @return void + */ + function testRouterReverse() { + $params = array( + 'controller' => 'posts', + 'action' => 'view', + 'pass' => array(1), + 'named' => array(), + 'url' => array(), + 'autoRender' => 1, + 'bare' => 1, + 'return' => 1, + 'requested' => 1 + ); + $result = Router::reverse($params); + $this->assertEqual($result, '/posts/view/1'); + + $params = array( + 'controller' => 'posts', + 'action' => 'index', + 'pass' => array(1), + 'named' => array('page' => 1, 'sort' => 'Article.title', 'direction' => 'desc'), + 'url' => array() + ); + $result = Router::reverse($params); + $this->assertEqual($result, '/posts/index/1/page:1/sort:Article.title/direction:desc'); + + Router::connect('/:lang/:controller/:action/*', array(), array('lang' => '[a-z]{3}')); + $params = array( + 'lang' => 'eng', + 'controller' => 'posts', + 'action' => 'view', + 'pass' => array(1), + 'named' => array(), + 'url' => array('url' => 'eng/posts/view/1') + ); + $result = Router::reverse($params); + $this->assertEqual($result, '/eng/posts/view/1'); + + $params = array( + 'lang' => 'eng', + 'controller' => 'posts', + 'action' => 'view', + 'pass' => array(1), + 'named' => array(), + 'url' => array('url' => 'eng/posts/view/1', 'foo' => 'bar', 'baz' => 'quu'), + 'paging' => array(), + 'models' => array() + ); + $result = Router::reverse($params); + $this->assertEqual($result, '/eng/posts/view/1?foo=bar&baz=quu'); + } +} + +/** + * Test case for CakeRoute + * + * @package cake.tests.cases.libs. + **/ +class CakeRouteTestCase extends CakeTestCase { +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->_routing = Configure::read('Routing'); + Configure::write('Routing', array('admin' => null, 'prefixes' => array())); + Router::reload(); + } + +/** + * end the test and reset the environment + * + * @return void + **/ + function endTest() { + Configure::write('Routing', $this->_routing); + } + +/** + * Test the construction of a CakeRoute + * + * @return void + **/ + function testConstruction() { + $route =& new CakeRoute('/:controller/:action/:id', array(), array('id' => '[0-9]+')); + + $this->assertEqual($route->template, '/:controller/:action/:id'); + $this->assertEqual($route->defaults, array()); + $this->assertEqual($route->options, array('id' => '[0-9]+')); + $this->assertFalse($route->compiled()); + } + +/** + * test Route compiling. + * + * @return void + **/ + function testBasicRouteCompiling() { + $route =& new CakeRoute('/', array('controller' => 'pages', 'action' => 'display', 'home')); + $result = $route->compile(); + $expected = '#^/*$#'; + $this->assertEqual($result, $expected); + $this->assertEqual($route->keys, array()); + + $route =& new CakeRoute('/:controller/:action', array('controller' => 'posts')); + $result = $route->compile(); + + $this->assertPattern($result, '/posts/edit'); + $this->assertPattern($result, '/posts/super_delete'); + $this->assertNoPattern($result, '/posts'); + $this->assertNoPattern($result, '/posts/super_delete/1'); + + $route =& new CakeRoute('/posts/foo:id', array('controller' => 'posts', 'action' => 'view')); + $result = $route->compile(); + + $this->assertPattern($result, '/posts/foo:1'); + $this->assertPattern($result, '/posts/foo:param'); + $this->assertNoPattern($result, '/posts'); + $this->assertNoPattern($result, '/posts/'); + + $this->assertEqual($route->keys, array('id')); + + $route =& new CakeRoute('/:plugin/:controller/:action/*', array('plugin' => 'test_plugin', 'action' => 'index')); + $result = $route->compile(); + $this->assertPattern($result, '/test_plugin/posts/index'); + $this->assertPattern($result, '/test_plugin/posts/edit/5'); + $this->assertPattern($result, '/test_plugin/posts/edit/5/name:value/nick:name'); + } + +/** + * test route names with - in them. + * + * @return void + */ + function testHyphenNames() { + $route =& new CakeRoute('/articles/:date-from/:date-to', array( + 'controller' => 'articles', 'action' => 'index' + )); + $expected = array( + 'controller' => 'articles', + 'action' => 'index', + 'date-from' => '2009-07-31', + 'date-to' => '2010-07-31', + 'named' => array(), + 'pass' => array() + ); + $result = $route->parse('/articles/2009-07-31/2010-07-31'); + $this->assertEqual($result, $expected); + } + +/** + * test that route parameters that overlap don't cause errors. + * + * @return void + */ + function testRouteParameterOverlap() { + $route =& new CakeRoute('/invoices/add/:idd/:id', array('controller' => 'invoices', 'action' => 'add')); + $result = $route->compile(); + $this->assertPattern($result, '/invoices/add/1/3'); + + $route =& new CakeRoute('/invoices/add/:id/:idd', array('controller' => 'invoices', 'action' => 'add')); + $result = $route->compile(); + $this->assertPattern($result, '/invoices/add/1/3'); + } + +/** + * test compiling routes with keys that have patterns + * + * @return void + **/ + function testRouteCompilingWithParamPatterns() { + extract(Router::getNamedExpressions()); + + $route = new CakeRoute( + '/:controller/:action/:id', + array(), + array('id' => $ID) + ); + $result = $route->compile(); + $this->assertPattern($result, '/posts/edit/1'); + $this->assertPattern($result, '/posts/view/518098'); + $this->assertNoPattern($result, '/posts/edit/name-of-post'); + $this->assertNoPattern($result, '/posts/edit/4/other:param'); + $this->assertEqual($route->keys, array('controller', 'action', 'id')); + + $route =& new CakeRoute( + '/:lang/:controller/:action/:id', + array('controller' => 'testing4'), + array('id' => $ID, 'lang' => '[a-z]{3}') + ); + $result = $route->compile(); + $this->assertPattern($result, '/eng/posts/edit/1'); + $this->assertPattern($result, '/cze/articles/view/1'); + $this->assertNoPattern($result, '/language/articles/view/2'); + $this->assertNoPattern($result, '/eng/articles/view/name-of-article'); + $this->assertEqual($route->keys, array('lang', 'controller', 'action', 'id')); + + foreach (array(':', '@', ';', '$', '-') as $delim) { + $route =& new CakeRoute('/posts/:id' . $delim . ':title'); + $result = $route->compile(); + + $this->assertPattern($result, '/posts/1' . $delim . 'name-of-article'); + $this->assertPattern($result, '/posts/13244' . $delim . 'name-of_Article[]'); + $this->assertNoPattern($result, '/posts/11!nameofarticle'); + $this->assertNoPattern($result, '/posts/11'); + + $this->assertEqual($route->keys, array('id', 'title')); + } + + $route =& new CakeRoute( + '/posts/:id::title/:year', + array('controller' => 'posts', 'action' => 'view'), + array('id' => $ID, 'year' => $Year, 'title' => '[a-z-_]+') + ); + $result = $route->compile(); + $this->assertPattern($result, '/posts/1:name-of-article/2009/'); + $this->assertPattern($result, '/posts/13244:name-of-article/1999'); + $this->assertNoPattern($result, '/posts/hey_now:nameofarticle'); + $this->assertNoPattern($result, '/posts/:nameofarticle/2009'); + $this->assertNoPattern($result, '/posts/:nameofarticle/01'); + $this->assertEqual($route->keys, array('id', 'title', 'year')); + + $route =& new CakeRoute( + '/posts/:url_title-(uuid::id)', + array('controller' => 'posts', 'action' => 'view'), + array('pass' => array('id', 'url_title'), 'id' => $ID) + ); + $result = $route->compile(); + $this->assertPattern($result, '/posts/some_title_for_article-(uuid:12534)/'); + $this->assertPattern($result, '/posts/some_title_for_article-(uuid:12534)'); + $this->assertNoPattern($result, '/posts/'); + $this->assertNoPattern($result, '/posts/nameofarticle'); + $this->assertNoPattern($result, '/posts/nameofarticle-12347'); + $this->assertEqual($route->keys, array('url_title', 'id')); + } + +/** + * test more complex route compiling & parsing with mid route greedy stars + * and optional routing parameters + * + * @return void + */ + function testComplexRouteCompilingAndParsing() { + extract(Router::getNamedExpressions()); + + $route =& new CakeRoute( + '/posts/:month/:day/:year/*', + array('controller' => 'posts', 'action' => 'view'), + array('year' => $Year, 'month' => $Month, 'day' => $Day) + ); + $result = $route->compile(); + $this->assertPattern($result, '/posts/08/01/2007/title-of-post'); + $result = $route->parse('/posts/08/01/2007/title-of-post'); + + $this->assertEqual(count($result), 8); + $this->assertEqual($result['controller'], 'posts'); + $this->assertEqual($result['action'], 'view'); + $this->assertEqual($result['year'], '2007'); + $this->assertEqual($result['month'], '08'); + $this->assertEqual($result['day'], '01'); + + $route =& new CakeRoute( + "/:extra/page/:slug/*", + array('controller' => 'pages', 'action' => 'view', 'extra' => null), + array("extra" => '[a-z1-9_]*', "slug" => '[a-z1-9_]+', "action" => 'view') + ); + $result = $route->compile(); + + $this->assertPattern($result, '/some_extra/page/this_is_the_slug'); + $this->assertPattern($result, '/page/this_is_the_slug'); + $this->assertEqual($route->keys, array('extra', 'slug')); + $this->assertEqual($route->options, array('extra' => '[a-z1-9_]*', 'slug' => '[a-z1-9_]+', 'action' => 'view')); + $expected = array( + 'controller' => 'pages', + 'action' => 'view', + 'extra' => null, + ); + $this->assertEqual($route->defaults, $expected); + + $route =& new CakeRoute( + '/:controller/:action/*', + array('project' => false), + array( + 'controller' => 'source|wiki|commits|tickets|comments|view', + 'action' => 'branches|history|branch|logs|view|start|add|edit|modify' + ) + ); + $this->assertFalse($route->parse('/chaw_test/wiki')); + + $result = $route->compile(); + $this->assertNoPattern($result, '/some_project/source'); + $this->assertPattern($result, '/source/view'); + $this->assertPattern($result, '/source/view/other/params'); + $this->assertNoPattern($result, '/chaw_test/wiki'); + $this->assertNoPattern($result, '/source/wierd_action'); + } + +/** + * test that routes match their pattern. + * + * @return void + **/ + function testMatchBasic() { + $route = new CakeRoute('/:controller/:action/:id', array('plugin' => null)); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null)); + $this->assertFalse($result); + + $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 0)); + $this->assertFalse($result); + + $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => 1)); + $this->assertEqual($result, '/posts/view/1'); + + $route =& new CakeRoute('/', array('controller' => 'pages', 'action' => 'display', 'home')); + $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'home')); + $this->assertEqual($result, '/'); + + $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'about')); + $this->assertFalse($result); + + + $route =& new CakeRoute('/pages/*', array('controller' => 'pages', 'action' => 'display')); + $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'home')); + $this->assertEqual($result, '/pages/home'); + + $result = $route->match(array('controller' => 'pages', 'action' => 'display', 'about')); + $this->assertEqual($result, '/pages/about'); + + + $route =& new CakeRoute('/blog/:action', array('controller' => 'posts')); + $result = $route->match(array('controller' => 'posts', 'action' => 'view')); + $this->assertEqual($result, '/blog/view'); + + $result = $route->match(array('controller' => 'nodes', 'action' => 'view')); + $this->assertFalse($result); + + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 1)); + $this->assertFalse($result); + + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'id' => 2)); + $this->assertFalse($result); + + + $route =& new CakeRoute('/foo/:controller/:action', array('action' => 'index')); + $result = $route->match(array('controller' => 'posts', 'action' => 'view')); + $this->assertEqual($result, '/foo/posts/view'); + + + $route =& new CakeRoute('/:plugin/:id/*', array('controller' => 'posts', 'action' => 'view')); + $result = $route->match(array('plugin' => 'test', 'controller' => 'posts', 'action' => 'view', 'id' => '1')); + $this->assertEqual($result, '/test/1/'); + + $result = $route->match(array('plugin' => 'fo', 'controller' => 'posts', 'action' => 'view', 'id' => '1', '0')); + $this->assertEqual($result, '/fo/1/0'); + + $result = $route->match(array('plugin' => 'fo', 'controller' => 'nodes', 'action' => 'view', 'id' => 1)); + $this->assertFalse($result); + + $result = $route->match(array('plugin' => 'fo', 'controller' => 'posts', 'action' => 'edit', 'id' => 1)); + $this->assertFalse($result); + + $route =& new CakeRoute('/admin/subscriptions/:action/*', array( + 'controller' => 'subscribe', 'admin' => true, 'prefix' => 'admin' + )); + + $url = array('controller' => 'subscribe', 'admin' => true, 'action' => 'edit', 1); + $result = $route->match($url); + $expected = '/admin/subscriptions/edit/1'; + $this->assertEqual($result, $expected); + + $route =& new CakeRoute('/articles/:date-from/:date-to', array( + 'controller' => 'articles', 'action' => 'index' + )); + $url = array( + 'controller' => 'articles', + 'action' => 'index', + 'date-from' => '2009-07-31', + 'date-to' => '2010-07-31' + ); + + $result = $route->match($url); + $expected = '/articles/2009-07-31/2010-07-31'; + $this->assertEqual($result, $expected); + } + +/** + * test match() with greedy routes, named parameters and passed args. + * + * @return void + */ + function testMatchWithNamedParametersAndPassedArgs() { + Router::connectNamed(true); + + $route = new CakeRoute('/:controller/:action/*', array('plugin' => null)); + $result = $route->match(array('controller' => 'posts', 'action' => 'index', 'plugin' => null, 'page' => 1)); + $this->assertEqual($result, '/posts/index/page:1'); + + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5)); + $this->assertEqual($result, '/posts/view/5'); + + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'plugin' => null, 5, 'page' => 1, 'limit' => 20, 'order' => 'title')); + $this->assertEqual($result, '/posts/view/5/page:1/limit:20/order:title'); + + + $route =& new CakeRoute('/test2/*', array('controller' => 'pages', 'action' => 'display', 2)); + $result = $route->match(array('controller' => 'pages', 'action' => 'display', 1)); + $this->assertFalse($result); + + $result = $route->match(array('controller' => 'pages', 'action' => 'display', 2, 'something')); + $this->assertEqual($result, '/test2/something'); + + $result = $route->match(array('controller' => 'pages', 'action' => 'display', 5, 'something')); + $this->assertFalse($result); + } + +/** + * test that match with patterns works. + * + * @return void + */ + function testMatchWithPatterns() { + $route =& new CakeRoute('/:controller/:action/:id', array('plugin' => null), array('id' => '[0-9]+')); + $result = $route->match(array('controller' => 'posts', 'action' => 'view', 'id' => 'foo')); + $this->assertFalse($result); + + $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => '9')); + $this->assertEqual($result, '/posts/view/9'); + + $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => '922')); + $this->assertEqual($result, '/posts/view/922'); + + $result = $route->match(array('plugin' => null, 'controller' => 'posts', 'action' => 'view', 'id' => 'a99')); + $this->assertFalse($result); + } + +/** + * test that patterns work for :action + * + * @return void + */ + function testPatternOnAction() { + $route =& new CakeRoute( + '/blog/:action/*', + array('controller' => 'blog_posts'), + array('action' => 'other|actions') + ); + $result = $route->match(array('controller' => 'blog_posts', 'action' => 'foo')); + $this->assertFalse($result); + + $result = $route->match(array('controller' => 'blog_posts', 'action' => 'actions')); + $this->assertTrue($result); + + $result = $route->parse('/blog/other'); + $expected = array('controller' => 'blog_posts', 'action' => 'other', 'pass' => array(), 'named' => array()); + $this->assertEqual($expected, $result); + + $result = $route->parse('/blog/foobar'); + $this->assertFalse($result); + } + +/** + * test persistParams ability to persist parameters from $params and remove params. + * + * @return void + */ + function testPersistParams() { + $route =& new CakeRoute( + '/:lang/:color/blog/:action', + array('controller' => 'posts'), + array('persist' => array('lang', 'color')) + ); + $url = array('controller' => 'posts', 'action' => 'index'); + $params = array('lang' => 'en', 'color' => 'blue'); + $result = $route->persistParams($url, $params); + $this->assertEqual($result['lang'], 'en'); + $this->assertEqual($result['color'], 'blue'); + + $url = array('controller' => 'posts', 'action' => 'index', 'color' => 'red'); + $params = array('lang' => 'en', 'color' => 'blue'); + $result = $route->persistParams($url, $params); + $this->assertEqual($result['lang'], 'en'); + $this->assertEqual($result['color'], 'red'); + } + +/** + * test the parse method of CakeRoute. + * + * @return void + */ + function testParse() { + extract(Router::getNamedExpressions()); + $route =& new CakeRoute('/:controller/:action/:id', array('controller' => 'testing4', 'id' => null), array('id' => $ID)); + $route->compile(); + $result = $route->parse('/posts/view/1'); + $this->assertEqual($result['controller'], 'posts'); + $this->assertEqual($result['action'], 'view'); + $this->assertEqual($result['id'], '1'); + + $route =& new Cakeroute( + '/admin/:controller', + array('prefix' => 'admin', 'admin' => 1, 'action' => 'index') + ); + $route->compile(); + $result = $route->parse('/admin/'); + $this->assertFalse($result); + + $result = $route->parse('/admin/posts'); + $this->assertEqual($result['controller'], 'posts'); + $this->assertEqual($result['action'], 'index'); + } +} + +/** + * test case for PluginShortRoute + * + * @package cake.tests.libs + */ +class PluginShortRouteTestCase extends CakeTestCase { +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->_routing = Configure::read('Routing'); + Configure::write('Routing', array('admin' => null, 'prefixes' => array())); + Router::reload(); + } + +/** + * end the test and reset the environment + * + * @return void + **/ + function endTest() { + Configure::write('Routing', $this->_routing); + } + +/** + * test the parsing of routes. + * + * @return void + */ + function testParsing() { + $route =& new PluginShortRoute('/:plugin', array('action' => 'index'), array('plugin' => 'foo|bar')); + + $result = $route->parse('/foo'); + $this->assertEqual($result['plugin'], 'foo'); + $this->assertEqual($result['controller'], 'foo'); + $this->assertEqual($result['action'], 'index'); + + $result = $route->parse('/wrong'); + $this->assertFalse($result, 'Wrong plugin name matched %s'); + } + +/** + * test the reverse routing of the plugin shortcut urls. + * + * @return void + */ + function testMatch() { + $route =& new PluginShortRoute('/:plugin', array('action' => 'index'), array('plugin' => 'foo|bar')); + + $result = $route->match(array('plugin' => 'foo', 'controller' => 'posts', 'action' => 'index')); + $this->assertFalse($result, 'plugin controller mismatch was converted. %s'); + + $result = $route->match(array('plugin' => 'foo', 'controller' => 'foo', 'action' => 'index')); + $this->assertEqual($result, '/foo'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/sanitize.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/sanitize.test.php new file mode 100644 index 000000000..34bb5f497 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/sanitize.test.php @@ -0,0 +1,523 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5428 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Sanitize'); + +/** + * DataTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class SanitizeDataTest extends CakeTestModel { + +/** + * name property + * + * @var string 'SanitizeDataTest' + * @access public + */ + var $name = 'SanitizeDataTest'; + +/** + * useTable property + * + * @var string 'data_tests' + * @access public + */ + var $useTable = 'data_tests'; +} + +/** + * Article class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class SanitizeArticle extends CakeTestModel { + +/** + * name property + * + * @var string 'Article' + * @access public + */ + var $name = 'SanitizeArticle'; + +/** + * useTable property + * + * @var string 'articles' + * @access public + */ + var $useTable = 'articles'; +} + +/** + * SanitizeTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class SanitizeTest extends CakeTestCase { + +/** + * autoFixtures property + * + * @var bool false + * @access public + */ + var $autoFixtures = false; + +/** + * fixtures property + * + * @var array + * @access public + */ + var $fixtures = array('core.data_test', 'core.article'); + +/** + * startTest method + * + * @param mixed $method + * @access public + * @return void + */ + function startTest($method) { + parent::startTest($method); + $this->_initDb(); + } + +/** + * testEscapeAlphaNumeric method + * + * @access public + * @return void + */ + function testEscapeAlphaNumeric() { + $resultAlpha = Sanitize::escape('abc', 'test_suite'); + $this->assertEqual($resultAlpha, 'abc'); + + $resultNumeric = Sanitize::escape('123', 'test_suite'); + $this->assertEqual($resultNumeric, '123'); + + $resultNumeric = Sanitize::escape(1234, 'test_suite'); + $this->assertEqual($resultNumeric, 1234); + + $resultNumeric = Sanitize::escape(1234.23, 'test_suite'); + $this->assertEqual($resultNumeric, 1234.23); + + $resultNumeric = Sanitize::escape('#1234.23', 'test_suite'); + $this->assertEqual($resultNumeric, '#1234.23'); + + $resultNull = Sanitize::escape(null, 'test_suite'); + $this->assertEqual($resultNull, null); + + $resultNull = Sanitize::escape(false, 'test_suite'); + $this->assertEqual($resultNull, false); + + $resultNull = Sanitize::escape(true, 'test_suite'); + $this->assertEqual($resultNull, true); + } + +/** + * testClean method + * + * @access public + * @return void + */ + function testClean() { + $string = 'test & "quote" \'other\' ;.$ symbol.' . "\r" . 'another line'; + $expected = 'test & "quote" 'other' ;.$ symbol.another line'; + $result = Sanitize::clean($string, array('connection' => 'test_suite')); + $this->assertEqual($result, $expected); + + $string = 'test & "quote" \'other\' ;.$ symbol.' . "\r" . 'another line'; + $expected = 'test & ' . Sanitize::escape('"quote"', 'test_suite') . ' ' . Sanitize::escape('\'other\'', 'test_suite') . ' ;.$ symbol.another line'; + $result = Sanitize::clean($string, array('encode' => false, 'connection' => 'test_suite')); + $this->assertEqual($result, $expected); + + $string = 'test & "quote" \'other\' ;.$ \\$ symbol.' . "\r" . 'another line'; + $expected = 'test & "quote" \'other\' ;.$ $ symbol.another line'; + $result = Sanitize::clean($string, array('encode' => false, 'escape' => false, 'connection' => 'test_suite')); + $this->assertEqual($result, $expected); + + $string = 'test & "quote" \'other\' ;.$ \\$ symbol.' . "\r" . 'another line'; + $expected = 'test & "quote" \'other\' ;.$ \\$ symbol.another line'; + $result = Sanitize::clean($string, array('encode' => false, 'escape' => false, 'dollar' => false, 'connection' => 'test_suite')); + $this->assertEqual($result, $expected); + + $string = 'test & "quote" \'other\' ;.$ symbol.' . "\r" . 'another line'; + $expected = 'test & "quote" \'other\' ;.$ symbol.' . "\r" . 'another line'; + $result = Sanitize::clean($string, array('encode' => false, 'escape' => false, 'carriage' => false, 'connection' => 'test_suite')); + $this->assertEqual($result, $expected); + + $array = array(array('test & "quote" \'other\' ;.$ symbol.' . "\r" . 'another line')); + $expected = array(array('test & "quote" 'other' ;.$ symbol.another line')); + $result = Sanitize::clean($array, array('connection' => 'test_suite')); + $this->assertEqual($result, $expected); + + $array = array(array('test & "quote" \'other\' ;.$ \\$ symbol.' . "\r" . 'another line')); + $expected = array(array('test & "quote" \'other\' ;.$ $ symbol.another line')); + $result = Sanitize::clean($array, array('encode' => false, 'escape' => false, 'connection' => 'test_suite')); + $this->assertEqual($result, $expected); + + $array = array(array('test odd Ä spacesé')); + $expected = array(array('test odd Ä spacesé')); + $result = Sanitize::clean($array, array('odd_spaces' => false, 'escape' => false, 'connection' => 'test_suite')); + $this->assertEqual($result, $expected); + + $array = array(array('\\$', array('key' => 'test & "quote" \'other\' ;.$ \\$ symbol.' . "\r" . 'another line'))); + $expected = array(array('$', array('key' => 'test & "quote" \'other\' ;.$ $ symbol.another line'))); + $result = Sanitize::clean($array, array('encode' => false, 'escape' => false)); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = Sanitize::clean($string); + $this->assertEqual($string, $expected); + + $data = array( + 'Grant' => array( + 'title' => '2 o clock grant', + 'grant_peer_review_id' => 3, + 'institution_id' => 5, + 'created_by' => 1, + 'modified_by' => 1, + 'created' => '2010-07-15 14:11:00', + 'modified' => '2010-07-19 10:45:41' + ), + 'GrantsMember' => array( + 0 => array( + 'id' => 68, + 'grant_id' => 120, + 'member_id' => 16, + 'program_id' => 29, + 'pi_percent_commitment' => 1 + ) + ) + ); + $result = Sanitize::clean($data); + $this->assertEqual($result, $data); + } + +/** + * testHtml method + * + * @access public + * @return void + */ + function testHtml() { + $string = '

    This is a test string & so is this

    '; + $expected = 'This is a test string & so is this'; + $result = Sanitize::html($string, array('remove' => true)); + $this->assertEqual($result, $expected); + + $string = 'The "lazy" dog \'jumped\' & flew over the moon. If (1+1) = 2 is true, (2-1) = 1 is also true'; + $expected = 'The "lazy" dog 'jumped' & flew over the moon. If (1+1) = 2 <em>is</em> true, (2-1) = 1 is also true'; + $result = Sanitize::html($string); + $this->assertEqual($result, $expected); + + $string = 'The "lazy" dog \'jumped\''; + $expected = 'The "lazy" dog \'jumped\''; + $result = Sanitize::html($string, array('quotes' => ENT_COMPAT)); + $this->assertEqual($result, $expected); + + $string = 'The "lazy" dog \'jumped\''; + $result = Sanitize::html($string, array('quotes' => ENT_NOQUOTES)); + $this->assertEqual($result, $string); + + $string = 'The "lazy" dog \'jumped\' & flew over the moon. If (1+1) = 2 is true, (2-1) = 1 is also true'; + $expected = 'The "lazy" dog 'jumped' & flew over the moon. If (1+1) = 2 <em>is</em> true, (2-1) = 1 is also true'; + $result = Sanitize::html($string); + $this->assertEqual($result, $expected); + } + +/** + * testStripWhitespace method + * + * @access public + * @return void + */ + function testStripWhitespace() { + $string = "This sentence \t\t\t has lots of \n\n white\nspace \rthat \r\n needs to be \t \n trimmed."; + $expected = "This sentence has lots of whitespace that needs to be trimmed."; + $result = Sanitize::stripWhitespace($string); + $this->assertEqual($result, $expected); + } + +/** + * testParanoid method + * + * @access public + * @return void + */ + function testParanoid() { + $string = 'I would like to !%@#% & dance & sing ^$&*()-+'; + $expected = 'Iwouldliketodancesing'; + $result = Sanitize::paranoid($string); + $this->assertEqual($result, $expected); + + $string = array('This |s th% s0ng that never ends it g*es', + 'on and on my friends, b^ca#use it is the', + 'so&g th===t never ends.'); + $expected = array('This s th% s0ng that never ends it g*es', + 'on and on my friends bcause it is the', + 'sog tht never ends.'); + $result = Sanitize::paranoid($string, array('%', '*', '.', ' ')); + $this->assertEqual($result, $expected); + + $string = "anything' OR 1 = 1"; + $expected = 'anythingOR11'; + $result = Sanitize::paranoid($string); + $this->assertEqual($result, $expected); + + $string = "x' AND email IS NULL; --"; + $expected = 'xANDemailISNULL'; + $result = Sanitize::paranoid($string); + $this->assertEqual($result, $expected); + + $string = "x' AND 1=(SELECT COUNT(*) FROM users); --"; + $expected = "xAND1SELECTCOUNTFROMusers"; + $result = Sanitize::paranoid($string); + $this->assertEqual($result, $expected); + + $string = "x'; DROP TABLE members; --"; + $expected = "xDROPTABLEmembers"; + $result = Sanitize::paranoid($string); + $this->assertEqual($result, $expected); + } + +/** + * testStripImages method + * + * @access public + * @return void + */ + function testStripImages() { + $string = 'my image'; + $expected = 'my image
    '; + $result = Sanitize::stripImages($string); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = Sanitize::stripImages($string); + $this->assertEqual($result, $expected); + + $string = 'test image alt'; + $expected = 'test image alt
    '; + $result = Sanitize::stripImages($string); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = Sanitize::stripImages($string); + $this->assertEqual($result, $expected); + } + +/** + * testStripScripts method + * + * @access public + * @return void + */ + function testStripScripts() { + $string = ''; + $expected = ''; + $result = Sanitize::stripScripts($string); + $this->assertEqual($result, $expected); + + $string = '' . "\n" . '' . "\n" . '' . "\n" . ''; + $expected = "\n" . '' . "\n" . ''."\n".''; + $result = Sanitize::stripScripts($string); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = Sanitize::stripScripts($string); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = Sanitize::stripScripts($string); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = Sanitize::stripScripts($string); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = Sanitize::stripScripts($string); + $this->assertEqual($result, $expected); + + $string = << + + +text +HTML; + $expected = "text\n\ntext"; + $result = Sanitize::stripScripts($string); + $this->assertEqual($result, $expected); + + $string = << + + +text +HTML; + $expected = "text\n\ntext"; + $result = Sanitize::stripScripts($string); + $this->assertEqual($result, $expected); + } + +/** + * testStripAll method + * + * @access public + * @return void + */ + function testStripAll() { + $string = '"/>'; + $expected ='"/>'; + $result = Sanitize::stripAll($string); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = Sanitize::stripAll($string); + $this->assertEqual($result, $expected); + + $string = '<'; + $expected = '<'; + $result = Sanitize::stripAll($string); + $this->assertEqual($result, $expected); + + $string = ''."\n". + "

    This is ok \t\n text

    \n". + ''."\n". + ''; + $expected = '

    This is ok text

    '; + $result = Sanitize::stripAll($string); + $this->assertEqual($result, $expected); + + } + +/** + * testStripTags method + * + * @access public + * @return void + */ + function testStripTags() { + $string = '

    Headline

    My Link could go to a bad site

    '; + $expected = 'Headline

    My Link could go to a bad site

    '; + $result = Sanitize::stripTags($string, 'h2', 'a'); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ' '; + $result = Sanitize::stripTags($string, 'script'); + $this->assertEqual($result, $expected); + + $string = '

    Important

    Additional information here . Read even more here

    '; + $expected = 'Important

    Additional information here . Read even more here

    '; + $result = Sanitize::stripTags($string, 'h2', 'a'); + $this->assertEqual($result, $expected); + + $string = '

    Important

    Additional information here . Read even more here

    '; + $expected = 'Important

    Additional information here . Read even more here

    '; + $result = Sanitize::stripTags($string, 'h2', 'a', 'img'); + $this->assertEqual($result, $expected); + + $string = 'Important message!
    This message will self destruct!'; + $expected = 'Important message!
    This message will self destruct!'; + $result = Sanitize::stripTags($string, 'b'); + $this->assertEqual($result, $expected); + + $string = 'Important message!
    This message will self destruct!'; + $expected = 'Important message!
    This message will self destruct!'; + $result = Sanitize::stripTags($string, 'b'); + $this->assertEqual($result, $expected); + + $string = '

    Important

    Additional information here . Read even more here

    '; + $expected = 'Important

    Additional information here . Read even more here

    '; + $result = Sanitize::stripTags($string, 'h2', 'a', 'img'); + $this->assertEqual($result, $expected); + } + +/** + * testFormatColumns method + * + * @access public + * @return void + */ + function testFormatColumns() { + $this->loadFixtures('DataTest', 'Article'); + + $this->DataTest =& new SanitizeDataTest(array('alias' => 'DataTest')); + $data = array('DataTest' => array( + 'id' => 'z', + 'count' => '12a', + 'float' => '2.31456', + 'updated' => '2008-01-01' + ) + ); + $this->DataTest->set($data); + $expected = array('DataTest' => array( + 'id' => '0', + 'count' => '12', + 'float' => 2.31456, + 'updated' => '2008-01-01 00:00:00', + )); + Sanitize::formatColumns($this->DataTest); + $result = $this->DataTest->data; + $this->assertEqual($result, $expected); + + $this->Article =& new SanitizeArticle(array('alias' => 'Article')); + $data = array('Article' => array( + 'id' => 'ZB', + 'user_id' => '12', + 'title' => 'title of article', + 'body' => 'body text', + 'published' => 'QQQQQQQ', + )); + $this->Article->set($data); + $expected = array('Article' => array( + 'id' => '0', + 'user_id' => '12', + 'title' => 'title of article', + 'body' => 'body text', + 'published' => 'QQQQQQQ', + )); + Sanitize::formatColumns($this->Article); + $result = $this->Article->data; + $this->assertEqual($result, $expected); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/security.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/security.test.php new file mode 100644 index 000000000..ec2819a62 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/security.test.php @@ -0,0 +1,173 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5432 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Security'); + +/** + * SecurityTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class SecurityTest extends CakeTestCase { + +/** + * sut property + * + * @var mixed null + * @access public + */ + var $sut = null; + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + $this->sut =& Security::getInstance(); + } + +/** + * testInactiveMins method + * + * @access public + * @return void + */ + function testInactiveMins() { + Configure::write('Security.level', 'high'); + $this->assertEqual(10, Security::inactiveMins()); + + Configure::write('Security.level', 'medium'); + $this->assertEqual(100, Security::inactiveMins()); + + Configure::write('Security.level', 'low'); + $this->assertEqual(300, Security::inactiveMins()); + } + +/** + * testGenerateAuthkey method + * + * @access public + * @return void + */ + function testGenerateAuthkey() { + $this->assertEqual(strlen(Security::generateAuthKey()), 40); + } + +/** + * testValidateAuthKey method + * + * @access public + * @return void + */ + function testValidateAuthKey() { + $authKey = Security::generateAuthKey(); + $this->assertTrue(Security::validateAuthKey($authKey)); + } + +/** + * testHash method + * + * @access public + * @return void + */ + function testHash() { + $Security =& Security::getInstance(); + $_hashType = $Security->hashType; + + $key = 'someKey'; + $hash = 'someHash'; + + $this->assertIdentical(strlen(Security::hash($key, null, false)), 40); + $this->assertIdentical(strlen(Security::hash($key, 'sha1', false)), 40); + $this->assertIdentical(strlen(Security::hash($key, null, true)), 40); + $this->assertIdentical(strlen(Security::hash($key, 'sha1', true)), 40); + + $result = Security::hash($key, null, $hash); + $this->assertIdentical($result, 'e38fcb877dccb6a94729a81523851c931a46efb1'); + + $result = Security::hash($key, 'sha1', $hash); + $this->assertIdentical($result, 'e38fcb877dccb6a94729a81523851c931a46efb1'); + + $hashType = 'sha1'; + Security::setHash($hashType); + $this->assertIdentical($this->sut->hashType, $hashType); + $this->assertIdentical(strlen(Security::hash($key, null, true)), 40); + $this->assertIdentical(strlen(Security::hash($key, null, false)), 40); + + $this->assertIdentical(strlen(Security::hash($key, 'md5', false)), 32); + $this->assertIdentical(strlen(Security::hash($key, 'md5', true)), 32); + + $hashType = 'md5'; + Security::setHash($hashType); + $this->assertIdentical($this->sut->hashType, $hashType); + $this->assertIdentical(strlen(Security::hash($key, null, false)), 32); + $this->assertIdentical(strlen(Security::hash($key, null, true)), 32); + + if (!function_exists('hash') && !function_exists('mhash')) { + $this->assertIdentical(strlen(Security::hash($key, 'sha256', false)), 32); + $this->assertIdentical(strlen(Security::hash($key, 'sha256', true)), 32); + } else { + $this->assertIdentical(strlen(Security::hash($key, 'sha256', false)), 64); + $this->assertIdentical(strlen(Security::hash($key, 'sha256', true)), 64); + } + + Security::setHash($_hashType); + } + +/** + * testCipher method + * + * @access public + * @return void + */ + function testCipher() { + $length = 10; + $txt = ''; + for ($i = 0; $i < $length; $i++) { + $txt .= mt_rand(0, 255); + } + $key = 'my_key'; + $result = Security::cipher($txt, $key); + $this->assertEqual(Security::cipher($result, $key), $txt); + + $txt = ''; + $key = 'my_key'; + $result = Security::cipher($txt, $key); + $this->assertEqual(Security::cipher($result, $key), $txt); + + $txt = 'some_text'; + $key = ''; + $result = Security::cipher($txt, $key); + $this->assertError(); + $this->assertIdentical($result, ''); + + $txt = 123456; + $key = 'my_key'; + $result = Security::cipher($txt, $key); + $this->assertEqual(Security::cipher($result, $key), $txt); + + $txt = '123456'; + $key = 'my_key'; + $result = Security::cipher($txt, $key); + $this->assertEqual(Security::cipher($result, $key), $txt); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/set.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/set.test.php new file mode 100644 index 000000000..33ca7283e --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/set.test.php @@ -0,0 +1,3098 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Set'); + +/** + * SetTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class SetTest extends CakeTestCase { + +/** + * testNumericKeyExtraction method + * + * @access public + * @return void + */ + function testNumericKeyExtraction() { + $data = array('plugin' => null, 'controller' => '', 'action' => '', 1, 'whatever'); + $this->assertIdentical(Set::extract($data, '{n}'), array(1, 'whatever')); + $this->assertIdentical(Set::diff($data, Set::extract($data, '{n}')), array('plugin' => null, 'controller' => '', 'action' => '')); + } + +/** + * testEnum method + * + * @access public + * @return void + */ + function testEnum() { + $result = Set::enum(1, 'one, two'); + $this->assertIdentical($result, 'two'); + $result = Set::enum(2, 'one, two'); + $this->assertNull($result); + + $set = array('one', 'two'); + $result = Set::enum(0, $set); + $this->assertIdentical($result, 'one'); + $result = Set::enum(1, $set); + $this->assertIdentical($result, 'two'); + + $result = Set::enum(1, array('one', 'two')); + $this->assertIdentical($result, 'two'); + $result = Set::enum(2, array('one', 'two')); + $this->assertNull($result); + + $result = Set::enum('first', array('first' => 'one', 'second' => 'two')); + $this->assertIdentical($result, 'one'); + $result = Set::enum('third', array('first' => 'one', 'second' => 'two')); + $this->assertNull($result); + + $result = Set::enum('no', array('no' => 0, 'yes' => 1)); + $this->assertIdentical($result, 0); + $result = Set::enum('not sure', array('no' => 0, 'yes' => 1)); + $this->assertNull($result); + + $result = Set::enum(0); + $this->assertIdentical($result, 'no'); + $result = Set::enum(1); + $this->assertIdentical($result, 'yes'); + $result = Set::enum(2); + $this->assertNull($result); + } + +/** + * testFilter method + * + * @access public + * @return void + */ + function testFilter() { + $result = Set::filter(array('0', false, true, 0, array('one thing', 'I can tell you', 'is you got to be', false))); + $expected = array('0', 2 => true, 3 => 0, 4 => array('one thing', 'I can tell you', 'is you got to be', false)); + $this->assertIdentical($result, $expected); + } + +/** + * testNumericArrayCheck method + * + * @access public + * @return void + */ + function testNumericArrayCheck() { + $data = array('one'); + $this->assertTrue(Set::numeric(array_keys($data))); + + $data = array(1 => 'one'); + $this->assertFalse(Set::numeric($data)); + + $data = array('one'); + $this->assertFalse(Set::numeric($data)); + + $data = array('one' => 'two'); + $this->assertFalse(Set::numeric($data)); + + $data = array('one' => 1); + $this->assertTrue(Set::numeric($data)); + + $data = array(0); + $this->assertTrue(Set::numeric($data)); + + $data = array('one', 'two', 'three', 'four', 'five'); + $this->assertTrue(Set::numeric(array_keys($data))); + + $data = array(1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five'); + $this->assertTrue(Set::numeric(array_keys($data))); + + $data = array('1' => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five'); + $this->assertTrue(Set::numeric(array_keys($data))); + + $data = array('one', 2 => 'two', 3 => 'three', 4 => 'four', 'a' => 'five'); + $this->assertFalse(Set::numeric(array_keys($data))); + } + +/** + * testKeyCheck method + * + * @access public + * @return void + */ + function testKeyCheck() { + $data = array('Multi' => array('dimensonal' => array('array'))); + $this->assertTrue(Set::check($data, 'Multi.dimensonal')); + $this->assertFalse(Set::check($data, 'Multi.dimensonal.array')); + + $data = array( + array( + 'Article' => array('id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), + 'User' => array('id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), + 'Comment' => array( + array('id' => '1', 'article_id' => '1', 'user_id' => '2', 'comment' => 'First Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31'), + array('id' => '2', 'article_id' => '1', 'user_id' => '4', 'comment' => 'Second Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31'), + ), + 'Tag' => array( + array('id' => '1', 'tag' => 'tag1', 'created' => '2007-03-18 12:22:23', 'updated' => '2007-03-18 12:24:31'), + array('id' => '2', 'tag' => 'tag2', 'created' => '2007-03-18 12:24:23', 'updated' => '2007-03-18 12:26:31') + ) + ), + array( + 'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'), + 'User' => array('id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), + 'Comment' => array(), + 'Tag' => array() + ) + ); + $this->assertTrue(Set::check($data, '0.Article.user_id')); + $this->assertTrue(Set::check($data, '0.Comment.0.id')); + $this->assertFalse(Set::check($data, '0.Comment.0.id.0')); + $this->assertTrue(Set::check($data, '0.Article.user_id')); + $this->assertFalse(Set::check($data, '0.Article.user_id.a')); + } + +/** + * testMerge method + * + * @access public + * @return void + */ + function testMerge() { + $r = Set::merge(array('foo')); + $this->assertIdentical($r, array('foo')); + + $r = Set::merge('foo'); + $this->assertIdentical($r, array('foo')); + + $r = Set::merge('foo', 'bar'); + $this->assertIdentical($r, array('foo', 'bar')); + + if (substr(PHP_VERSION, 0, 1) >= 5) { + $r = eval('class StaticSetCaller{static function merge($a, $b){return Set::merge($a, $b);}} return StaticSetCaller::merge("foo", "bar");'); + $this->assertIdentical($r, array('foo', 'bar')); + } + + $r = Set::merge('foo', array('user' => 'bob', 'no-bar'), 'bar'); + $this->assertIdentical($r, array('foo', 'user' => 'bob', 'no-bar', 'bar')); + + $a = array('foo', 'foo2'); + $b = array('bar', 'bar2'); + $this->assertIdentical(Set::merge($a, $b), array('foo', 'foo2', 'bar', 'bar2')); + + $a = array('foo' => 'bar', 'bar' => 'foo'); + $b = array('foo' => 'no-bar', 'bar' => 'no-foo'); + $this->assertIdentical(Set::merge($a, $b), array('foo' => 'no-bar', 'bar' => 'no-foo')); + + $a = array('users' => array('bob', 'jim')); + $b = array('users' => array('lisa', 'tina')); + $this->assertIdentical(Set::merge($a, $b), array('users' => array('bob', 'jim', 'lisa', 'tina'))); + + $a = array('users' => array('jim', 'bob')); + $b = array('users' => 'none'); + $this->assertIdentical(Set::merge($a, $b), array('users' => 'none')); + + $a = array('users' => array('lisa' => array('id' => 5, 'pw' => 'secret')), 'cakephp'); + $b = array('users' => array('lisa' => array('pw' => 'new-pass', 'age' => 23)), 'ice-cream'); + $this->assertIdentical(Set::merge($a, $b), array('users' => array('lisa' => array('id' => 5, 'pw' => 'new-pass', 'age' => 23)), 'cakephp', 'ice-cream')); + + $c = array('users' => array('lisa' => array('pw' => 'you-will-never-guess', 'age' => 25, 'pet' => 'dog')), 'chocolate'); + $expected = array('users' => array('lisa' => array('id' => 5, 'pw' => 'you-will-never-guess', 'age' => 25, 'pet' => 'dog')), 'cakephp', 'ice-cream', 'chocolate'); + $this->assertIdentical(Set::merge($a, $b, $c), $expected); + + $this->assertIdentical(Set::merge($a, $b, array(), $c), $expected); + + $r = Set::merge($a, $b, $c); + $this->assertIdentical($r, $expected); + + $a = array('Tree', 'CounterCache', + 'Upload' => array('folder' => 'products', + 'fields' => array('image_1_id', 'image_2_id', 'image_3_id', 'image_4_id', 'image_5_id'))); + $b = array('Cacheable' => array('enabled' => false), + 'Limit', + 'Bindable', + 'Validator', + 'Transactional'); + + $expected = array('Tree', 'CounterCache', + 'Upload' => array('folder' => 'products', + 'fields' => array('image_1_id', 'image_2_id', 'image_3_id', 'image_4_id', 'image_5_id')), + 'Cacheable' => array('enabled' => false), + 'Limit', + 'Bindable', + 'Validator', + 'Transactional'); + + $this->assertIdentical(Set::merge($a, $b), $expected); + + $expected = array('Tree' => null, 'CounterCache' => null, + 'Upload' => array('folder' => 'products', + 'fields' => array('image_1_id', 'image_2_id', 'image_3_id', 'image_4_id', 'image_5_id')), + 'Cacheable' => array('enabled' => false), + 'Limit' => null, + 'Bindable' => null, + 'Validator' => null, + 'Transactional' => null); + + $this->assertIdentical(Set::normalize(Set::merge($a, $b)), $expected); + } + +/** + * testSort method + * + * @access public + * @return void + */ + function testSort() { + $a = array( + 0 => array('Person' => array('name' => 'Jeff'), 'Friend' => array(array('name' => 'Nate'))), + 1 => array('Person' => array('name' => 'Tracy'),'Friend' => array(array('name' => 'Lindsay'))) + ); + $b = array( + 0 => array('Person' => array('name' => 'Tracy'),'Friend' => array(array('name' => 'Lindsay'))), + 1 => array('Person' => array('name' => 'Jeff'), 'Friend' => array(array('name' => 'Nate'))) + + ); + $a = Set::sort($a, '{n}.Friend.{n}.name', 'asc'); + $this->assertIdentical($a, $b); + + $b = array( + 0 => array('Person' => array('name' => 'Jeff'), 'Friend' => array(array('name' => 'Nate'))), + 1 => array('Person' => array('name' => 'Tracy'),'Friend' => array(array('name' => 'Lindsay'))) + ); + $a = array( + 0 => array('Person' => array('name' => 'Tracy'),'Friend' => array(array('name' => 'Lindsay'))), + 1 => array('Person' => array('name' => 'Jeff'), 'Friend' => array(array('name' => 'Nate'))) + + ); + $a = Set::sort($a, '{n}.Friend.{n}.name', 'desc'); + $this->assertIdentical($a, $b); + + $a = array( + 0 => array('Person' => array('name' => 'Jeff'), 'Friend' => array(array('name' => 'Nate'))), + 1 => array('Person' => array('name' => 'Tracy'),'Friend' => array(array('name' => 'Lindsay'))), + 2 => array('Person' => array('name' => 'Adam'),'Friend' => array(array('name' => 'Bob'))) + ); + $b = array( + 0 => array('Person' => array('name' => 'Adam'),'Friend' => array(array('name' => 'Bob'))), + 1 => array('Person' => array('name' => 'Jeff'), 'Friend' => array(array('name' => 'Nate'))), + 2 => array('Person' => array('name' => 'Tracy'),'Friend' => array(array('name' => 'Lindsay'))) + ); + $a = Set::sort($a, '{n}.Person.name', 'asc'); + $this->assertIdentical($a, $b); + + $a = array( + array(7,6,4), + array(3,4,5), + array(3,2,1), + ); + + $b = array( + array(3,2,1), + array(3,4,5), + array(7,6,4), + ); + + $a = Set::sort($a, '{n}.{n}', 'asc'); + $this->assertIdentical($a, $b); + + $a = array( + array(7,6,4), + array(3,4,5), + array(3,2,array(1,1,1)), + ); + + $b = array( + array(3,2,array(1,1,1)), + array(3,4,5), + array(7,6,4), + ); + + $a = Set::sort($a, '{n}', 'asc'); + $this->assertIdentical($a, $b); + + $a = array( + 0 => array('Person' => array('name' => 'Jeff')), + 1 => array('Shirt' => array('color' => 'black')) + ); + $b = array( + 0 => array('Shirt' => array('color' => 'black')), + 1 => array('Person' => array('name' => 'Jeff')), + ); + $a = Set::sort($a, '{n}.Person.name', 'ASC'); + $this->assertIdentical($a, $b); + + $names = array( + array('employees' => array(array('name' => array('first' => 'John', 'last' => 'Doe')))), + array('employees' => array(array('name' => array('first' => 'Jane', 'last' => 'Doe')))), + array('employees' => array(array('name' => array()))), + array('employees' => array(array('name' => array()))) + ); + $result = Set::sort($names, '{n}.employees.0.name', 'asc', 1); + $expected = array( + array('employees' => array(array('name' => array('first' => 'John', 'last' => 'Doe')))), + array('employees' => array(array('name' => array('first' => 'Jane', 'last' => 'Doe')))), + array('employees' => array(array('name' => array()))), + array('employees' => array(array('name' => array()))) + ); + $this->assertEqual($result, $expected); + } + +/** + * test sorting with out of order keys. + * + * @return void + */ + function testSortWithOutOfOrderKeys() { + $data = array( + 9 => array('class' => 510, 'test2' => 2), + 1 => array('class' => 500, 'test2' => 1), + 2 => array('class' => 600, 'test2' => 2), + 5 => array('class' => 625, 'test2' => 4), + 0 => array('class' => 605, 'test2' => 3), + ); + $expected = array( + array('class' => 500, 'test2' => 1), + array('class' => 510, 'test2' => 2), + array('class' => 600, 'test2' => 2), + array('class' => 605, 'test2' => 3), + array('class' => 625, 'test2' => 4), + ); + $result = Set::sort($data, '{n}.class', 'asc'); + $this->assertEqual($expected, $result); + + $result = Set::sort($data, '{n}.test2', 'asc'); + $this->assertEqual($expected, $result); + } + +/** + * testExtract method + * + * @access public + * @return void + */ + function testExtract() { + $a = array( + array( + 'Article' => array('id' => '1', 'user_id' => '1', 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), + 'User' => array('id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), + 'Comment' => array( + array('id' => '1', 'article_id' => '1', 'user_id' => '2', 'comment' => 'First Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31'), + array('id' => '2', 'article_id' => '1', 'user_id' => '4', 'comment' => 'Second Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31'), + ), + 'Tag' => array( + array('id' => '1', 'tag' => 'tag1', 'created' => '2007-03-18 12:22:23', 'updated' => '2007-03-18 12:24:31'), + array('id' => '2', 'tag' => 'tag2', 'created' => '2007-03-18 12:24:23', 'updated' => '2007-03-18 12:26:31') + ), + 'Deep' => array( + 'Nesting' => array( + 'test' => array( + 1 => 'foo', + 2 => array( + 'and' => array('more' => 'stuff') + ) + ) + ) + ) + ), + array( + 'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'), + 'User' => array('id' => '2', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), + 'Comment' => array(), + 'Tag' => array() + ), + array( + 'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'), + 'User' => array('id' => '3', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), + 'Comment' => array(), + 'Tag' => array() + ), + array( + 'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'), + 'User' => array('id' => '4', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), + 'Comment' => array(), + 'Tag' => array() + ), + array( + 'Article' => array('id' => '3', 'user_id' => '1', 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31'), + 'User' => array('id' => '5', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'), + 'Comment' => array(), + 'Tag' => array() + ) + ); + $b = array('Deep' => $a[0]['Deep']); + $c = array( + array('a' => array('I' => array('a' => 1))), + array( + 'a' => array( + 2 + ) + ), + array('a' => array('II' => array('a' => 3, 'III' => array('a' => array('foo' => 4))))), + ); + + $expected = array(array('a' => $c[2]['a'])); + $r = Set::extract('/a/II[a=3]/..', $c); + $this->assertEqual($r, $expected); + + $expected = array(1, 2, 3, 4, 5); + $this->assertEqual(Set::extract('/User/id', $a), $expected); + + $expected = array(1, 2, 3, 4, 5); + $this->assertEqual(Set::extract('/User/id', $a), $expected); + + $expected = array( + array('id' => 1), array('id' => 2), array('id' => 3), array('id' => 4), array('id' => 5) + ); + + $r = Set::extract('/User/id', $a, array('flatten' => false)); + $this->assertEqual($r, $expected); + + $expected = array(array('test' => $a[0]['Deep']['Nesting']['test'])); + $this->assertEqual(Set::extract('/Deep/Nesting/test', $a), $expected); + $this->assertEqual(Set::extract('/Deep/Nesting/test', $b), $expected); + + $expected = array(array('test' => $a[0]['Deep']['Nesting']['test'])); + $r = Set::extract('/Deep/Nesting/test/1/..', $a); + $this->assertEqual($r, $expected); + + $expected = array(array('test' => $a[0]['Deep']['Nesting']['test'])); + $r = Set::extract('/Deep/Nesting/test/2/and/../..', $a); + $this->assertEqual($r, $expected); + + $expected = array(array('test' => $a[0]['Deep']['Nesting']['test'])); + $r = Set::extract('/Deep/Nesting/test/2/../../../Nesting/test/2/..', $a); + $this->assertEqual($r, $expected); + + $expected = array(2); + $r = Set::extract('/User[2]/id', $a); + $this->assertEqual($r, $expected); + + $expected = array(4, 5); + $r = Set::extract('/User[id>3]/id', $a); + $this->assertEqual($r, $expected); + + $expected = array(2, 3); + $r = Set::extract('/User[id>1][id<=3]/id', $a); + $this->assertEqual($r, $expected); + + $expected = array(array('I'), array('II')); + $r = Set::extract('/a/@*', $c); + $this->assertEqual($r, $expected); + + $single = array( + 'User' => array( + 'id' => 4, + 'name' => 'Neo', + ) + ); + $tricky = array( + 0 => array( + 'User' => array( + 'id' => 1, + 'name' => 'John', + ) + ), + 1 => array( + 'User' => array( + 'id' => 2, + 'name' => 'Bob', + ) + ), + 2 => array( + 'User' => array( + 'id' => 3, + 'name' => 'Tony', + ) + ), + 'User' => array( + 'id' => 4, + 'name' => 'Neo', + ) + ); + + $expected = array(1, 2, 3, 4); + $r = Set::extract('/User/id', $tricky); + $this->assertEqual($r, $expected); + + $expected = array(4); + $r = Set::extract('/User/id', $single); + $this->assertEqual($r, $expected); + + $expected = array(1, 3); + $r = Set::extract('/User[name=/n/]/id', $tricky); + $this->assertEqual($r, $expected); + + $expected = array(4); + $r = Set::extract('/User[name=/N/]/id', $tricky); + $this->assertEqual($r, $expected); + + $expected = array(1, 3, 4); + $r = Set::extract('/User[name=/N/i]/id', $tricky); + $this->assertEqual($r, $expected); + + $expected = array(array('id', 'name'), array('id', 'name'), array('id', 'name'), array('id', 'name')); + $r = Set::extract('/User/@*', $tricky); + $this->assertEqual($r, $expected); + + $common = array( + array( + 'Article' => array( + 'id' => 1, + 'name' => 'Article 1', + ), + 'Comment' => array( + array( + 'id' => 1, + 'user_id' => 5, + 'article_id' => 1, + 'text' => 'Comment 1', + ), + array( + 'id' => 2, + 'user_id' => 23, + 'article_id' => 1, + 'text' => 'Comment 2', + ), + array( + 'id' => 3, + 'user_id' => 17, + 'article_id' => 1, + 'text' => 'Comment 3', + ), + ), + ), + array( + 'Article' => array( + 'id' => 2, + 'name' => 'Article 2', + ), + 'Comment' => array( + array( + 'id' => 4, + 'user_id' => 2, + 'article_id' => 2, + 'text' => 'Comment 4', + 'addition' => '', + ), + array( + 'id' => 5, + 'user_id' => 23, + 'article_id' => 2, + 'text' => 'Comment 5', + 'addition' => 'foo', + ), + ), + ), + array( + 'Article' => array( + 'id' => 3, + 'name' => 'Article 3', + ), + 'Comment' => array(), + ) + ); + + $r = Set::extract('/Comment/id', $common); + $expected = array(1, 2, 3, 4, 5); + $this->assertEqual($r, $expected); + + $expected = array(1, 2, 4, 5); + $r = Set::extract('/Comment[id!=3]/id', $common); + $this->assertEqual($r, $expected); + + $r = Set::extract('/', $common); + $this->assertEqual($r, $common); + + $expected = array(1, 2, 4, 5); + $r = Set::extract($common, '/Comment[id!=3]/id'); + $this->assertEqual($r, $expected); + + $expected = array($common[0]['Comment'][2]); + $r = Set::extract($common, '/Comment/2'); + $this->assertEqual($r, $expected); + + $expected = array($common[0]['Comment'][0]); + $r = Set::extract($common, '/Comment[1]/.[id=1]'); + $this->assertEqual($r, $expected); + + $expected = array($common[1]['Comment'][1]); + $r = Set::extract($common, '/1/Comment/.[2]'); + $this->assertEqual($r, $expected); + + $expected = array(); + $r = Set::extract('/User/id', array()); + $this->assertEqual($r, $expected); + + $expected = array(5); + $r = Set::extract('/Comment/id[:last]', $common); + $this->assertEqual($r, $expected); + + $expected = array(1); + $r = Set::extract('/Comment/id[:first]', $common); + $this->assertEqual($r, $expected); + + $expected = array(3); + $r = Set::extract('/Article[:last]/id', $common); + $this->assertEqual($r, $expected); + + $expected = array(array('Comment' => $common[1]['Comment'][0])); + $r = Set::extract('/Comment[addition=]', $common); + $this->assertEqual($r, $expected); + + $habtm = array( + array( + 'Post' => array( + 'id' => 1, + 'title' => 'great post', + ), + 'Comment' => array( + array( + 'id' => 1, + 'text' => 'foo', + 'User' => array( + 'id' => 1, + 'name' => 'bob' + ), + ), + array( + 'id' => 2, + 'text' => 'bar', + 'User' => array( + 'id' => 2, + 'name' => 'tod' + ), + ), + ), + ), + array( + 'Post' => array( + 'id' => 2, + 'title' => 'fun post', + ), + 'Comment' => array( + array( + 'id' => 3, + 'text' => '123', + 'User' => array( + 'id' => 3, + 'name' => 'dan' + ), + ), + array( + 'id' => 4, + 'text' => '987', + 'User' => array( + 'id' => 4, + 'name' => 'jim' + ), + ), + ), + ), + ); + + $r = Set::extract('/Comment/User[name=/bob|dan/]/..', $habtm); + $this->assertEqual($r[0]['Comment']['User']['name'], 'bob'); + $this->assertEqual($r[1]['Comment']['User']['name'], 'dan'); + $this->assertEqual(count($r), 2); + + $r = Set::extract('/Comment/User[name=/bob|tod/]/..', $habtm); + $this->assertEqual($r[0]['Comment']['User']['name'], 'bob'); + + $this->assertEqual($r[1]['Comment']['User']['name'], 'tod'); + $this->assertEqual(count($r), 2); + + $tree = array( + array( + 'Category' => array('name' => 'Category 1'), + 'children' => array(array('Category' => array('name' => 'Category 1.1'))) + ), + array( + 'Category' => array('name' => 'Category 2'), + 'children' => array( + array('Category' => array('name' => 'Category 2.1')), + array('Category' => array('name' => 'Category 2.2')) + ) + ), + array( + 'Category' => array('name' => 'Category 3'), + 'children' => array(array('Category' => array('name' => 'Category 3.1'))) + ) + ); + + $expected = array(array('Category' => $tree[1]['Category'])); + $r = Set::extract('/Category[name=Category 2]', $tree); + $this->assertEqual($r, $expected); + + $expected = array( + array('Category' => $tree[1]['Category'], 'children' => $tree[1]['children']) + ); + $r = Set::extract('/Category[name=Category 2]/..', $tree); + $this->assertEqual($r, $expected); + + $expected = array( + array('children' => $tree[1]['children'][0]), + array('children' => $tree[1]['children'][1]) + ); + $r = Set::extract('/Category[name=Category 2]/../children', $tree); + $this->assertEqual($r, $expected); + + $habtm = array( + array( + 'Post' => array( + 'id' => 1, + 'title' => 'great post', + ), + 'Comment' => array( + array( + 'id' => 1, + 'text' => 'foo', + 'User' => array( + 'id' => 1, + 'name' => 'bob' + ), + ), + array( + 'id' => 2, + 'text' => 'bar', + 'User' => array( + 'id' => 2, + 'name' => 'tod' + ), + ), + ), + ), + array( + 'Post' => array( + 'id' => 2, + 'title' => 'fun post', + ), + 'Comment' => array( + array( + 'id' => 3, + 'text' => '123', + 'User' => array( + 'id' => 3, + 'name' => 'dan' + ), + ), + array( + 'id' => 4, + 'text' => '987', + 'User' => array( + 'id' => 4, + 'name' => 'jim' + ), + ), + ), + ), + ); + + $r = Set::extract('/Comment/User[name=/\w+/]/..', $habtm); + $this->assertEqual($r[0]['Comment']['User']['name'], 'bob'); + $this->assertEqual($r[1]['Comment']['User']['name'], 'tod'); + $this->assertEqual($r[2]['Comment']['User']['name'], 'dan'); + $this->assertEqual($r[3]['Comment']['User']['name'], 'dan'); + $this->assertEqual(count($r), 4); + + $r = Set::extract('/Comment/User[name=/[a-z]+/]/..', $habtm); + $this->assertEqual($r[0]['Comment']['User']['name'], 'bob'); + $this->assertEqual($r[1]['Comment']['User']['name'], 'tod'); + $this->assertEqual($r[2]['Comment']['User']['name'], 'dan'); + $this->assertEqual($r[3]['Comment']['User']['name'], 'dan'); + $this->assertEqual(count($r), 4); + + $r = Set::extract('/Comment/User[name=/bob|dan/]/..', $habtm); + $this->assertEqual($r[0]['Comment']['User']['name'], 'bob'); + $this->assertEqual($r[1]['Comment']['User']['name'], 'dan'); + $this->assertEqual(count($r), 2); + + $r = Set::extract('/Comment/User[name=/bob|tod/]/..', $habtm); + $this->assertEqual($r[0]['Comment']['User']['name'], 'bob'); + $this->assertEqual($r[1]['Comment']['User']['name'], 'tod'); + $this->assertEqual(count($r), 2); + + $mixedKeys = array( + 'User' => array( + 0 => array( + 'id' => 4, + 'name' => 'Neo' + ), + 1 => array( + 'id' => 5, + 'name' => 'Morpheus' + ), + 'stringKey' => array() + ) + ); + $expected = array('Neo', 'Morpheus'); + $r = Set::extract('/User/name', $mixedKeys); + $this->assertEqual($r, $expected); + + $f = array( + array( + 'file' => array( + 'name' => 'zipfile.zip', + 'type' => 'application/zip', + 'tmp_name' => '/tmp/php178.tmp', + 'error' => 0, + 'size' => '564647' + ) + ), + array( + 'file' => array( + 'name' => 'zipfile2.zip', + 'type' => 'application/x-zip-compressed', + 'tmp_name' => '/tmp/php179.tmp', + 'error' => 0, + 'size' => '354784' + ) + ), + array( + 'file' => array( + 'name' => 'picture.jpg', + 'type' => 'image/jpeg', + 'tmp_name' => '/tmp/php180.tmp', + 'error' => 0, + 'size' => '21324' + ) + ) + ); + $expected = array(array('name' => 'zipfile2.zip','type' => 'application/x-zip-compressed','tmp_name' => '/tmp/php179.tmp','error' => 0,'size' => '354784')); + $r = Set::extract('/file/.[type=application/x-zip-compressed]', $f); + $this->assertEqual($r, $expected); + + $expected = array(array('name' => 'zipfile.zip','type' => 'application/zip','tmp_name' => '/tmp/php178.tmp','error' => 0,'size' => '564647')); + $r = Set::extract('/file/.[type=application/zip]', $f); + $this->assertEqual($r, $expected); + + $f = array( + array( + 'file' => array( + 'name' => 'zipfile.zip', + 'type' => 'application/zip', + 'tmp_name' => '/tmp/php178.tmp', + 'error' => 0, + 'size' => '564647' + ) + ), + array( + 'file' => array( + 'name' => 'zipfile2.zip', + 'type' => 'application/x zip compressed', + 'tmp_name' => '/tmp/php179.tmp', + 'error' => 0, + 'size' => '354784' + ) + ), + array( + 'file' => array( + 'name' => 'picture.jpg', + 'type' => 'image/jpeg', + 'tmp_name' => '/tmp/php180.tmp', + 'error' => 0, + 'size' => '21324' + ) + ) + ); + $expected = array(array('name' => 'zipfile2.zip','type' => 'application/x zip compressed','tmp_name' => '/tmp/php179.tmp','error' => 0,'size' => '354784')); + $r = Set::extract('/file/.[type=application/x zip compressed]', $f); + $this->assertEqual($r, $expected); + + $expected = array( + array('name' => 'zipfile.zip','type' => 'application/zip','tmp_name' => '/tmp/php178.tmp','error' => 0,'size' => '564647'), + array('name' => 'zipfile2.zip','type' => 'application/x zip compressed','tmp_name' => '/tmp/php179.tmp','error' => 0,'size' => '354784') + ); + $r = Set::extract('/file/.[tmp_name=/tmp\/php17/]', $f); + $this->assertEqual($r, $expected); + + $hasMany = array( + 'Node' => array( + 'id' => 1, + 'name' => 'First', + 'state' => 50 + ), + 'ParentNode' => array( + 0 => array( + 'id' => 2, + 'name' => 'Second', + 'state' => 60, + ) + ) + ); + $result = Set::extract('/ParentNode/name', $hasMany); + $expected = array('Second'); + $this->assertEqual($result, $expected); + + $data = array( + array( + 'Category' => array( + 'id' => 1, + 'name' => 'First' + ), + 0 => array( + 'value' => 50 + ) + ), + array( + 'Category' => array( + 'id' => 2, + 'name' => 'Second' + ), + 0 => array( + 'value' => 60 + ) + ) + ); + $expected = array( + array( + 'Category' => array( + 'id' => 1, + 'name' => 'First' + ), + 0 => array( + 'value' => 50 + ) + ) + ); + $result = Set::extract('/Category[id=1]/..', $data); + $this->assertEqual($result, $expected); + } + +/** + * test parent selectors with extract + * + * @return void + */ + function testExtractParentSelector() { + $tree = array( + array( + 'Category' => array( + 'name' => 'Category 1' + ), + 'children' => array( + array( + 'Category' => array( + 'name' => 'Category 1.1' + ) + ) + ) + ), + array( + 'Category' => array( + 'name' => 'Category 2' + ), + 'children' => array( + array( + 'Category' => array( + 'name' => 'Category 2.1' + ) + ), + array( + 'Category' => array( + 'name' => 'Category 2.2' + ) + ), + ) + ), + array( + 'Category' => array( + 'name' => 'Category 3' + ), + 'children' => array( + array( + 'Category' => array( + 'name' => 'Category 3.1' + ) + ) + ) + ) + ); + $expected = array(array('Category' => $tree[1]['Category'])); + $r = Set::extract('/Category[name=Category 2]', $tree); + $this->assertEqual($r, $expected); + + $expected = array(array('Category' => $tree[1]['Category'], 'children' => $tree[1]['children'])); + $r = Set::extract('/Category[name=Category 2]/..', $tree); + $this->assertEqual($r, $expected); + + $expected = array(array('children' => $tree[1]['children'][0]), array('children' => $tree[1]['children'][1])); + $r = Set::extract('/Category[name=Category 2]/../children', $tree); + $this->assertEqual($r, $expected); + + $single = array( + array( + 'CallType' => array( + 'name' => 'Internal Voice' + ), + 'x' => array( + 'hour' => 7 + ) + ) + ); + + $expected = array(7); + $r = Set::extract('/CallType[name=Internal Voice]/../x/hour', $single); + $this->assertEqual($r, $expected); + + $multiple = array( + array( + 'CallType' => array( + 'name' => 'Internal Voice' + ), + 'x' => array( + 'hour' => 7 + ) + ), + array( + 'CallType' => array( + 'name' => 'Internal Voice' + ), + 'x' => array( + 'hour' => 2 + ) + ), + array( + 'CallType' => array( + 'name' => 'Internal Voice' + ), + 'x' => array( + 'hour' => 1 + ) + ) + ); + + $expected = array(7,2,1); + $r = Set::extract('/CallType[name=Internal Voice]/../x/hour', $multiple); + $this->assertEqual($r, $expected); + + $a = array( + 'Model' => array( + '0' => array( + 'id' => 18, + 'SubModelsModel' => array( + 'id' => 1, + 'submodel_id' => 66, + 'model_id' => 18, + 'type' => 1 + ), + ), + '1' => array( + 'id' => 0, + 'SubModelsModel' => array( + 'id' => 2, + 'submodel_id' => 66, + 'model_id' => 0, + 'type' => 1 + ), + ), + '2' => array( + 'id' => 17, + 'SubModelsModel' => array( + 'id' => 3, + 'submodel_id' => 66, + 'model_id' => 17, + 'type' => 2 + ), + ), + '3' => array( + 'id' => 0, + 'SubModelsModel' => array( + 'id' => 4, + 'submodel_id' => 66, + 'model_id' => 0, + 'type' => 2 + ) + ) + ) + ); + + $expected = array( + array( + 'Model' => array( + 'id' => 17, + 'SubModelsModel' => array( + 'id' => 3, + 'submodel_id' => 66, + 'model_id' => 17, + 'type' => 2 + ), + ) + ), + array( + 'Model' => array( + 'id' => 0, + 'SubModelsModel' => array( + 'id' => 4, + 'submodel_id' => 66, + 'model_id' => 0, + 'type' => 2 + ) + ) + ) + ); + $r = Set::extract('/Model/SubModelsModel[type=2]/..', $a); + $this->assertEqual($r, $expected); + } + +/** + * test that extract() still works when arrays don't contain a 0 index. + * + * @return void + */ + function testExtractWithNonZeroArrays() { + $nonZero = array( + 1 => array( + 'User' => array( + 'id' => 1, + 'name' => 'John', + ) + ), + 2 => array( + 'User' => array( + 'id' => 2, + 'name' => 'Bob', + ) + ), + 3 => array( + 'User' => array( + 'id' => 3, + 'name' => 'Tony', + ) + ) + ); + $expected = array(1, 2, 3); + $r = Set::extract('/User/id', $nonZero); + $this->assertEqual($r, $expected); + + $expected = array( + array('User' => array('id' => 1, 'name' => 'John')), + array('User' => array('id' => 2, 'name' => 'Bob')), + array('User' => array('id' => 3, 'name' => 'Tony')), + ); + $result = Set::extract('/User', $nonZero); + $this->assertEqual($result, $expected); + + $nonSequential = array( + 'User' => array( + 0 => array('id' => 1), + 2 => array('id' => 2), + 6 => array('id' => 3), + 9 => array('id' => 4), + 3 => array('id' => 5), + ), + ); + + $nonZero = array( + 'User' => array( + 2 => array('id' => 1), + 4 => array('id' => 2), + 6 => array('id' => 3), + 9 => array('id' => 4), + 3 => array('id' => 5), + ), + ); + + $expected = array(1, 2, 3, 4, 5); + $this->assertEqual(Set::extract('/User/id', $nonSequential), $expected); + + $result = Set::extract('/User/id', $nonZero); + $this->assertEqual($result, $expected, 'Failed non zero array key extract'); + + $expected = array(1, 2, 3, 4, 5); + $this->assertEqual(Set::extract('/User/id', $nonSequential), $expected); + + $result = Set::extract('/User/id', $nonZero); + $this->assertEqual($result, $expected, 'Failed non zero array key extract'); + + $startingAtOne = array( + 'Article' => array( + 1 => array( + 'id' => 1, + 'approved' => 1, + ), + ) + ); + + $expected = array(0 => array('Article' => array('id' => 1, 'approved' => 1))); + $result = Set::extract('/Article[approved=1]', $startingAtOne); + $this->assertEqual($result, $expected); + + $items = array( + 240 => array( + 'A' => array( + 'field1' => 'a240', + 'field2' => 'a240', + ), + 'B' => array( + 'field1' => 'b240', + 'field2' => 'b240' + ), + ) + ); + + $expected = array( + 0 => 'b240' + ); + + $result = Set::extract('/B/field1', $items); + $this->assertIdentical($result, $expected); + $this->assertIdentical($result, Set::extract('{n}.B.field1', $items)); + } +/** + * testExtractWithArrays method + * + * @access public + * @return void + */ + function testExtractWithArrays() { + $data = array( + 'Level1' => array( + 'Level2' => array('test1', 'test2'), + 'Level2bis' => array('test3', 'test4') + ) + ); + $this->assertEqual(Set::extract('/Level1/Level2', $data), array(array('Level2' => array('test1', 'test2')))); + $this->assertEqual(Set::extract('/Level1/Level2bis', $data), array(array('Level2bis' => array('test3', 'test4')))); + } + +/** + * test extract() with elements that have non-array children. + * + * @return void + */ + function testExtractWithNonArrayElements() { + $data = array( + 'node' => array( + array('foo'), + 'bar' + ) + ); + $result = Set::extract('/node', $data); + $expected = array( + array('node' => array('foo')), + 'bar' + ); + $this->assertEqual($result, $expected); + + $data = array( + 'node' => array( + 'foo' => array('bar'), + 'bar' => array('foo') + ) + ); + $result = Set::extract('/node', $data); + $expected = array( + array('foo' => array('bar')), + array('bar' => array('foo')), + ); + $this->assertEqual($result, $expected); + + $data = array( + 'node' => array( + 'foo' => array( + 'bar' + ), + 'bar' => 'foo' + ) + ); + $result = Set::extract('/node', $data); + $expected = array( + array('foo' => array('bar')), + 'foo' + ); + $this->assertEqual($result, $expected); + } + +/** + * testMatches method + * + * @access public + * @return void + */ + function testMatches() { + $a = array( + array('Article' => array('id' => 1, 'title' => 'Article 1')), + array('Article' => array('id' => 2, 'title' => 'Article 2')), + array('Article' => array('id' => 3, 'title' => 'Article 3')) + ); + + $this->assertTrue(Set::matches(array('id=2'), $a[1]['Article'])); + $this->assertFalse(Set::matches(array('id>2'), $a[1]['Article'])); + $this->assertTrue(Set::matches(array('id>=2'), $a[1]['Article'])); + $this->assertFalse(Set::matches(array('id>=3'), $a[1]['Article'])); + $this->assertTrue(Set::matches(array('id<=2'), $a[1]['Article'])); + $this->assertFalse(Set::matches(array('id<2'), $a[1]['Article'])); + $this->assertTrue(Set::matches(array('id>1'), $a[1]['Article'])); + $this->assertTrue(Set::matches(array('id>1', 'id<3', 'id!=0'), $a[1]['Article'])); + + $this->assertTrue(Set::matches(array('3'), null, 3)); + $this->assertTrue(Set::matches(array('5'), null, 5)); + + $this->assertTrue(Set::matches(array('id'), $a[1]['Article'])); + $this->assertTrue(Set::matches(array('id', 'title'), $a[1]['Article'])); + $this->assertFalse(Set::matches(array('non-existant'), $a[1]['Article'])); + + $this->assertTrue(Set::matches('/Article[id=2]', $a)); + $this->assertFalse(Set::matches('/Article[id=4]', $a)); + $this->assertTrue(Set::matches(array(), $a)); + + $r = array( + 'Attachment' => array( + 'keep' => array() + ), + 'Comment' => array( + 'keep' => array( + 'Attachment' => array( + 'fields' => array( + 0 => 'attachment', + ), + ), + ) + ), + 'User' => array( + 'keep' => array() + ), + 'Article' => array( + 'keep' => array( + 'Comment' => array( + 'fields' => array( + 0 => 'comment', + 1 => 'published', + ), + ), + 'User' => array( + 'fields' => array( + 0 => 'user', + ), + ), + ) + ) + ); + + $this->assertTrue(Set::matches('/Article/keep/Comment', $r)); + $this->assertEqual(Set::extract('/Article/keep/Comment/fields', $r), array('comment', 'published')); + $this->assertEqual(Set::extract('/Article/keep/User/fields', $r), array('user')); + + + } + +/** + * testSetExtractReturnsEmptyArray method + * + * @access public + * @return void + */ + function testSetExtractReturnsEmptyArray() { + + $this->assertIdentical(Set::extract(array(), '/Post/id'), array()); + + $this->assertIdentical(Set::extract('/Post/id', array()), array()); + + $this->assertIdentical(Set::extract('/Post/id', array( + array('Post' => array('name' => 'bob')), + array('Post' => array('name' => 'jim')) + )), array()); + + $this->assertIdentical(Set::extract(array(), 'Message.flash'), null); + + } + +/** + * testClassicExtract method + * + * @access public + * @return void + */ + function testClassicExtract() { + $a = array( + array('Article' => array('id' => 1, 'title' => 'Article 1')), + array('Article' => array('id' => 2, 'title' => 'Article 2')), + array('Article' => array('id' => 3, 'title' => 'Article 3')) + ); + + $result = Set::extract($a, '{n}.Article.id'); + $expected = array( 1, 2, 3 ); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a, '{n}.Article.title'); + $expected = array( 'Article 1', 'Article 2', 'Article 3' ); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a, '1.Article.title'); + $expected = 'Article 2'; + $this->assertIdentical($result, $expected); + + $result = Set::extract($a, '3.Article.title'); + $expected = null; + $this->assertIdentical($result, $expected); + + $a = array( + array( + 'Article' => array('id' => 1, 'title' => 'Article 1', + 'User' => array('id' => 1, 'username' => 'mariano.iglesias')) + ), + array( + 'Article' => array('id' => 2, 'title' => 'Article 2', + 'User' => array('id' => 1, 'username' => 'mariano.iglesias')) + ), + array( + 'Article' => array('id' => 3, 'title' => 'Article 3', + 'User' => array('id' => 2, 'username' => 'phpnut')) + ) + ); + + $result = Set::extract($a, '{n}.Article.User.username'); + $expected = array( 'mariano.iglesias', 'mariano.iglesias', 'phpnut' ); + $this->assertIdentical($result, $expected); + + $a = array( + array('Article' => array('id' => 1, 'title' => 'Article 1', + 'Comment' => array( + array('id' => 10, 'title' => 'Comment 10'), + array('id' => 11, 'title' => 'Comment 11'), + array('id' => 12, 'title' => 'Comment 12')))), + array('Article' => array('id' => 2, 'title' => 'Article 2', + 'Comment' => array( + array('id' => 13, 'title' => 'Comment 13'), + array('id' => 14, 'title' => 'Comment 14')))), + array('Article' => array('id' => 3, 'title' => 'Article 3'))); + + $result = Set::extract($a, '{n}.Article.Comment.{n}.id'); + $expected = array (array(10, 11, 12), array(13, 14), null); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a, '{n}.Article.Comment.{n}.title'); + $expected = array( + array('Comment 10', 'Comment 11', 'Comment 12'), + array('Comment 13', 'Comment 14'), + null + ); + $this->assertIdentical($result, $expected); + + $a = array(array('1day' => '20 sales'), array('1day' => '2 sales')); + $result = Set::extract($a, '{n}.1day'); + $expected = array('20 sales', '2 sales'); + $this->assertIdentical($result, $expected); + + $a = array( + 'pages' => array('name' => 'page'), + 'fruites' => array('name' => 'fruit'), + 0 => array('name' => 'zero') + ); + $result = Set::extract($a, '{s}.name'); + $expected = array('page','fruit'); + $this->assertIdentical($result, $expected); + + $a = array( + 0 => array('pages' => array('name' => 'page')), + 1 => array('fruites'=> array('name' => 'fruit')), + 'test' => array(array('name' => 'jippi')), + 'dot.test' => array(array('name' => 'jippi')) + ); + + $result = Set::extract($a, '{n}.{s}.name'); + $expected = array(0 => array('page'), 1 => array('fruit')); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a, '{s}.{n}.name'); + $expected = array(array('jippi'), array('jippi')); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a,'{\w+}.{\w+}.name'); + $expected = array( + array('pages' => 'page'), + array('fruites' => 'fruit'), + 'test' => array('jippi'), + 'dot.test' => array('jippi') + ); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a,'{\d+}.{\w+}.name'); + $expected = array(array('pages' => 'page'), array('fruites' => 'fruit')); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a,'{n}.{\w+}.name'); + $expected = array(array('pages' => 'page'), array('fruites' => 'fruit')); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a,'{s}.{\d+}.name'); + $expected = array(array('jippi'), array('jippi')); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a,'{s}'); + $expected = array(array(array('name' => 'jippi')), array(array('name' => 'jippi'))); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a,'{[a-z]}'); + $expected = array( + 'test' => array(array('name' => 'jippi')), + 'dot.test' => array(array('name' => 'jippi')) + ); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a, '{dot\.test}.{n}'); + $expected = array('dot.test' => array(array('name' => 'jippi'))); + $this->assertIdentical($result, $expected); + + $a = new stdClass(); + $a->articles = array( + array('Article' => array('id' => 1, 'title' => 'Article 1')), + array('Article' => array('id' => 2, 'title' => 'Article 2')), + array('Article' => array('id' => 3, 'title' => 'Article 3'))); + + $result = Set::extract($a, 'articles.{n}.Article.id'); + $expected = array( 1, 2, 3 ); + $this->assertIdentical($result, $expected); + + $result = Set::extract($a, 'articles.{n}.Article.title'); + $expected = array( 'Article 1', 'Article 2', 'Article 3' ); + $this->assertIdentical($result, $expected); + } + +/** + * testInsert method + * + * @access public + * @return void + */ + function testInsert() { + $a = array( + 'pages' => array('name' => 'page') + ); + + $result = Set::insert($a, 'files', array('name' => 'files')); + $expected = array( + 'pages' => array('name' => 'page'), + 'files' => array('name' => 'files') + ); + $this->assertIdentical($result, $expected); + + $a = array( + 'pages' => array('name' => 'page') + ); + $result = Set::insert($a, 'pages.name', array()); + $expected = array( + 'pages' => array('name' => array()), + ); + $this->assertIdentical($result, $expected); + + $a = array( + 'pages' => array( + 0 => array('name' => 'main'), + 1 => array('name' => 'about') + ) + ); + + $result = Set::insert($a, 'pages.1.vars', array('title' => 'page title')); + $expected = array( + 'pages' => array( + 0 => array('name' => 'main'), + 1 => array('name' => 'about', 'vars' => array('title' => 'page title')) + ) + ); + $this->assertIdentical($result, $expected); + } + +/** + * testRemove method + * + * @access public + * @return void + */ + function testRemove() { + $a = array( + 'pages' => array('name' => 'page'), + 'files' => array('name' => 'files') + ); + + $result = Set::remove($a, 'files', array('name' => 'files')); + $expected = array( + 'pages' => array('name' => 'page') + ); + $this->assertIdentical($result, $expected); + + $a = array( + 'pages' => array( + 0 => array('name' => 'main'), + 1 => array('name' => 'about', 'vars' => array('title' => 'page title')) + ) + ); + + $result = Set::remove($a, 'pages.1.vars', array('title' => 'page title')); + $expected = array( + 'pages' => array( + 0 => array('name' => 'main'), + 1 => array('name' => 'about') + ) + ); + $this->assertIdentical($result, $expected); + + $result = Set::remove($a, 'pages.2.vars', array('title' => 'page title')); + $expected = $a; + $this->assertIdentical($result, $expected); + } + +/** + * testCheck method + * + * @access public + * @return void + */ + function testCheck() { + $set = array( + 'My Index 1' => array('First' => 'The first item') + ); + $this->assertTrue(Set::check($set, 'My Index 1.First')); + $this->assertTrue(Set::check($set, 'My Index 1')); + $this->assertTrue(Set::check($set, array())); + + $set = array( + 'My Index 1' => array('First' => array('Second' => array('Third' => array('Fourth' => 'Heavy. Nesting.')))) + ); + $this->assertTrue(Set::check($set, 'My Index 1.First.Second')); + $this->assertTrue(Set::check($set, 'My Index 1.First.Second.Third')); + $this->assertTrue(Set::check($set, 'My Index 1.First.Second.Third.Fourth')); + $this->assertFalse(Set::check($set, 'My Index 1.First.Seconds.Third.Fourth')); + } + +/** + * testWritingWithFunkyKeys method + * + * @access public + * @return void + */ + function testWritingWithFunkyKeys() { + $set = Set::insert(array(), 'Session Test', "test"); + $this->assertEqual(Set::extract($set, 'Session Test'), 'test'); + + $set = Set::remove($set, 'Session Test'); + $this->assertFalse(Set::check($set, 'Session Test')); + + $this->assertTrue($set = Set::insert(array(), 'Session Test.Test Case', "test")); + $this->assertTrue(Set::check($set, 'Session Test.Test Case')); + } + +/** + * testDiff method + * + * @access public + * @return void + */ + function testDiff() { + $a = array( + 0 => array('name' => 'main'), + 1 => array('name' => 'about') + ); + $b = array( + 0 => array('name' => 'main'), + 1 => array('name' => 'about'), + 2 => array('name' => 'contact') + ); + + $result = Set::diff($a, $b); + $expected = array( + 2 => array('name' => 'contact') + ); + $this->assertIdentical($result, $expected); + + $result = Set::diff($a, array()); + $expected = $a; + $this->assertIdentical($result, $expected); + + $result = Set::diff(array(), $b); + $expected = $b; + $this->assertIdentical($result, $expected); + + $b = array( + 0 => array('name' => 'me'), + 1 => array('name' => 'about') + ); + + $result = Set::diff($a, $b); + $expected = array( + 0 => array('name' => 'main') + ); + $this->assertIdentical($result, $expected); + + $a = array(); + $b = array('name' => 'bob', 'address' => 'home'); + $result = Set::diff($a, $b); + $this->assertIdentical($result, $b); + + + $a = array('name' => 'bob', 'address' => 'home'); + $b = array(); + $result = Set::diff($a, $b); + $this->assertIdentical($result, $a); + + $a = array('key' => true, 'another' => false, 'name' => 'me'); + $b = array('key' => 1, 'another' => 0); + $expected = array('name' => 'me'); + $result = Set::diff($a, $b); + $this->assertIdentical($result, $expected); + + $a = array('key' => 'value', 'another' => null, 'name' => 'me'); + $b = array('key' => 'differentValue', 'another' => null); + $expected = array('key' => 'value', 'name' => 'me'); + $result = Set::diff($a, $b); + $this->assertIdentical($result, $expected); + + $a = array('key' => 'value', 'another' => null, 'name' => 'me'); + $b = array('key' => 'differentValue', 'another' => 'value'); + $expected = array('key' => 'value', 'another' => null, 'name' => 'me'); + $result = Set::diff($a, $b); + $this->assertIdentical($result, $expected); + + $a = array('key' => 'value', 'another' => null, 'name' => 'me'); + $b = array('key' => 'differentValue', 'another' => 'value'); + $expected = array('key' => 'differentValue', 'another' => 'value', 'name' => 'me'); + $result = Set::diff($b, $a); + $this->assertIdentical($result, $expected); + + $a = array('key' => 'value', 'another' => null, 'name' => 'me'); + $b = array(0 => 'differentValue', 1 => 'value'); + $expected = $a + $b; + $result = Set::diff($a, $b); + $this->assertIdentical($result, $expected); + } + +/** + * testContains method + * + * @access public + * @return void + */ + function testContains() { + $a = array( + 0 => array('name' => 'main'), + 1 => array('name' => 'about') + ); + $b = array( + 0 => array('name' => 'main'), + 1 => array('name' => 'about'), + 2 => array('name' => 'contact'), + 'a' => 'b' + ); + + $this->assertTrue(Set::contains($a, $a)); + $this->assertFalse(Set::contains($a, $b)); + $this->assertTrue(Set::contains($b, $a)); + } + +/** + * testCombine method + * + * @access public + * @return void + */ + function testCombine() { + $result = Set::combine(array(), '{n}.User.id', '{n}.User.Data'); + $this->assertFalse($result); + $result = Set::combine('', '{n}.User.id', '{n}.User.Data'); + $this->assertFalse($result); + + $a = array( + array('User' => array('id' => 2, 'group_id' => 1, + 'Data' => array('user' => 'mariano.iglesias','name' => 'Mariano Iglesias'))), + array('User' => array('id' => 14, 'group_id' => 2, + 'Data' => array('user' => 'phpnut', 'name' => 'Larry E. Masters'))), + array('User' => array('id' => 25, 'group_id' => 1, + 'Data' => array('user' => 'gwoo','name' => 'The Gwoo')))); + $result = Set::combine($a, '{n}.User.id'); + $expected = array(2 => null, 14 => null, 25 => null); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.non-existant'); + $expected = array(2 => null, 14 => null, 25 => null); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.Data'); + $expected = array( + 2 => array('user' => 'mariano.iglesias', 'name' => 'Mariano Iglesias'), + 14 => array('user' => 'phpnut', 'name' => 'Larry E. Masters'), + 25 => array('user' => 'gwoo', 'name' => 'The Gwoo')); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.Data.name'); + $expected = array( + 2 => 'Mariano Iglesias', + 14 => 'Larry E. Masters', + 25 => 'The Gwoo'); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id'); + $expected = array( + 1 => array( + 2 => array('user' => 'mariano.iglesias', 'name' => 'Mariano Iglesias'), + 25 => array('user' => 'gwoo', 'name' => 'The Gwoo')), + 2 => array( + 14 => array('user' => 'phpnut', 'name' => 'Larry E. Masters'))); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id'); + $expected = array( + 1 => array( + 2 => 'Mariano Iglesias', + 25 => 'The Gwoo'), + 2 => array( + 14 => 'Larry E. Masters')); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id'); + $expected = array(2 => null, 14 => null, 25 => null); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.Data'); + $expected = array( + 2 => array('user' => 'mariano.iglesias', 'name' => 'Mariano Iglesias'), + 14 => array('user' => 'phpnut', 'name' => 'Larry E. Masters'), + 25 => array('user' => 'gwoo', 'name' => 'The Gwoo')); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.Data.name'); + $expected = array(2 => 'Mariano Iglesias', 14 => 'Larry E. Masters', 25 => 'The Gwoo'); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id'); + $expected = array( + 1 => array( + 2 => array('user' => 'mariano.iglesias', 'name' => 'Mariano Iglesias'), + 25 => array('user' => 'gwoo', 'name' => 'The Gwoo')), + 2 => array( + 14 => array('user' => 'phpnut', 'name' => 'Larry E. Masters'))); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id'); + $expected = array( + 1 => array( + 2 => 'Mariano Iglesias', + 25 => 'The Gwoo'), + 2 => array( + 14 => 'Larry E. Masters')); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, '{n}.User.id', array('{0}: {1}', '{n}.User.Data.user', '{n}.User.Data.name'), '{n}.User.group_id'); + $expected = array ( + 1 => array ( + 2 => 'mariano.iglesias: Mariano Iglesias', + 25 => 'gwoo: The Gwoo'), + 2 => array (14 => 'phpnut: Larry E. Masters')); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, array('{0}: {1}', '{n}.User.Data.user', '{n}.User.Data.name'), '{n}.User.id'); + $expected = array('mariano.iglesias: Mariano Iglesias' => 2, 'phpnut: Larry E. Masters' => 14, 'gwoo: The Gwoo' => 25); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, array('{1}: {0}', '{n}.User.Data.user', '{n}.User.Data.name'), '{n}.User.id'); + $expected = array('Mariano Iglesias: mariano.iglesias' => 2, 'Larry E. Masters: phpnut' => 14, 'The Gwoo: gwoo' => 25); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, array('%1$s: %2$d', '{n}.User.Data.user', '{n}.User.id'), '{n}.User.Data.name'); + $expected = array('mariano.iglesias: 2' => 'Mariano Iglesias', 'phpnut: 14' => 'Larry E. Masters', 'gwoo: 25' => 'The Gwoo'); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, array('%2$d: %1$s', '{n}.User.Data.user', '{n}.User.id'), '{n}.User.Data.name'); + $expected = array('2: mariano.iglesias' => 'Mariano Iglesias', '14: phpnut' => 'Larry E. Masters', '25: gwoo' => 'The Gwoo'); + $this->assertIdentical($result, $expected); + + $b = new stdClass(); + $b->users = array( + array('User' => array('id' => 2, 'group_id' => 1, + 'Data' => array('user' => 'mariano.iglesias','name' => 'Mariano Iglesias'))), + array('User' => array('id' => 14, 'group_id' => 2, + 'Data' => array('user' => 'phpnut', 'name' => 'Larry E. Masters'))), + array('User' => array('id' => 25, 'group_id' => 1, + 'Data' => array('user' => 'gwoo','name' => 'The Gwoo')))); + $result = Set::combine($b, 'users.{n}.User.id'); + $expected = array(2 => null, 14 => null, 25 => null); + $this->assertIdentical($result, $expected); + + $result = Set::combine($b, 'users.{n}.User.id', 'users.{n}.User.non-existant'); + $expected = array(2 => null, 14 => null, 25 => null); + $this->assertIdentical($result, $expected); + + $result = Set::combine($a, 'fail', 'fail'); + $this->assertEqual($result, array()); + } + +/** + * testMapReverse method + * + * @access public + * @return void + */ + function testMapReverse() { + $result = Set::reverse(null); + $this->assertEqual($result, null); + + $result = Set::reverse(false); + $this->assertEqual($result, false); + + $expected = array( + 'Array1' => array( + 'Array1Data1' => 'Array1Data1 value 1', 'Array1Data2' => 'Array1Data2 value 2'), + 'Array2' => array( + 0 => array('Array2Data1' => 1, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 1 => array('Array2Data1' => 2, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 2 => array('Array2Data1' => 3, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 3 => array('Array2Data1' => 4, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 4 => array('Array2Data1' => 5, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4')), + 'Array3' => array( + 0 => array('Array3Data1' => 1, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 1 => array('Array3Data1' => 2, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 2 => array('Array3Data1' => 3, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 3 => array('Array3Data1' => 4, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 4 => array('Array3Data1' => 5, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'))); + $map = Set::map($expected, true); + $this->assertEqual($map->Array1->Array1Data1, $expected['Array1']['Array1Data1']); + $this->assertEqual($map->Array2[0]->Array2Data1, $expected['Array2'][0]['Array2Data1']); + + $result = Set::reverse($map); + $this->assertIdentical($result, $expected); + + $expected = array( + 'Post' => array('id'=> 1, 'title' => 'First Post'), + 'Comment' => array( + array('id'=> 1, 'title' => 'First Comment'), + array('id'=> 2, 'title' => 'Second Comment') + ), + 'Tag' => array( + array('id'=> 1, 'title' => 'First Tag'), + array('id'=> 2, 'title' => 'Second Tag') + ), + ); + $map = Set::map($expected); + $this->assertIdentical($map->title, $expected['Post']['title']); + foreach ($map->Comment as $comment) { + $ids[] = $comment->id; + } + $this->assertIdentical($ids, array(1, 2)); + + $expected = array( + 'Array1' => array( + 'Array1Data1' => 'Array1Data1 value 1', 'Array1Data2' => 'Array1Data2 value 2', 'Array1Data3' => 'Array1Data3 value 3','Array1Data4' => 'Array1Data4 value 4', + 'Array1Data5' => 'Array1Data5 value 5', 'Array1Data6' => 'Array1Data6 value 6', 'Array1Data7' => 'Array1Data7 value 7', 'Array1Data8' => 'Array1Data8 value 8'), + 'string' => 1, + 'another' => 'string', + 'some' => 'thing else', + 'Array2' => array( + 0 => array('Array2Data1' => 1, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 1 => array('Array2Data1' => 2, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 2 => array('Array2Data1' => 3, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 3 => array('Array2Data1' => 4, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 4 => array('Array2Data1' => 5, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4')), + 'Array3' => array( + 0 => array('Array3Data1' => 1, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 1 => array('Array3Data1' => 2, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 2 => array('Array3Data1' => 3, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 3 => array('Array3Data1' => 4, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 4 => array('Array3Data1' => 5, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'))); + $map = Set::map($expected, true); + $result = Set::reverse($map); + $this->assertIdentical($result, $expected); + + $expected = array( + 'Array1' => array( + 'Array1Data1' => 'Array1Data1 value 1', 'Array1Data2' => 'Array1Data2 value 2', 'Array1Data3' => 'Array1Data3 value 3','Array1Data4' => 'Array1Data4 value 4', + 'Array1Data5' => 'Array1Data5 value 5', 'Array1Data6' => 'Array1Data6 value 6', 'Array1Data7' => 'Array1Data7 value 7', 'Array1Data8' => 'Array1Data8 value 8'), + 'string' => 1, + 'another' => 'string', + 'some' => 'thing else', + 'Array2' => array( + 0 => array('Array2Data1' => 1, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 1 => array('Array2Data1' => 2, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 2 => array('Array2Data1' => 3, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 3 => array('Array2Data1' => 4, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4'), + 4 => array('Array2Data1' => 5, 'Array2Data2' => 'Array2Data2 value 2', 'Array2Data3' => 'Array2Data3 value 2', 'Array2Data4' => 'Array2Data4 value 4')), + 'string2' => 1, + 'another2' => 'string', + 'some2' => 'thing else', + 'Array3' => array( + 0 => array('Array3Data1' => 1, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 1 => array('Array3Data1' => 2, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 2 => array('Array3Data1' => 3, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 3 => array('Array3Data1' => 4, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4'), + 4 => array('Array3Data1' => 5, 'Array3Data2' => 'Array3Data2 value 2', 'Array3Data3' => 'Array3Data3 value 2', 'Array3Data4' => 'Array3Data4 value 4')), + 'string3' => 1, + 'another3' => 'string', + 'some3' => 'thing else'); + $map = Set::map($expected, true); + $result = Set::reverse($map); + $this->assertIdentical($result, $expected); + + $expected = array('User' => array('psword'=> 'whatever', 'Icon' => array('id'=> 851))); + $map = Set::map($expected); + $result = Set::reverse($map); + $this->assertIdentical($result, $expected); + + $expected = array('User' => array('psword'=> 'whatever', 'Icon' => array('id'=> 851))); + $class = new stdClass; + $class->User = new stdClass; + $class->User->psword = 'whatever'; + $class->User->Icon = new stdClass; + $class->User->Icon->id = 851; + $result = Set::reverse($class); + $this->assertIdentical($result, $expected); + + $expected = array('User' => array('psword'=> 'whatever', 'Icon' => array('id'=> 851), 'Profile' => array('name' => 'Some Name', 'address' => 'Some Address'))); + $class = new stdClass; + $class->User = new stdClass; + $class->User->psword = 'whatever'; + $class->User->Icon = new stdClass; + $class->User->Icon->id = 851; + $class->User->Profile = new stdClass; + $class->User->Profile->name = 'Some Name'; + $class->User->Profile->address = 'Some Address'; + + $result = Set::reverse($class); + $this->assertIdentical($result, $expected); + + $expected = array('User' => array('psword'=> 'whatever', + 'Icon' => array('id'=> 851), + 'Profile' => array('name' => 'Some Name', 'address' => 'Some Address'), + 'Comment' => array( + array('id' => 1, 'article_id' => 1, 'user_id' => 1, 'comment' => 'First Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31'), + array('id' => 2, 'article_id' => 1, 'user_id' => 2, 'comment' => 'Second Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31')))); + + $class = new stdClass; + $class->User = new stdClass; + $class->User->psword = 'whatever'; + $class->User->Icon = new stdClass; + $class->User->Icon->id = 851; + $class->User->Profile = new stdClass; + $class->User->Profile->name = 'Some Name'; + $class->User->Profile->address = 'Some Address'; + $class->User->Comment = new stdClass; + $class->User->Comment->{'0'} = new stdClass; + $class->User->Comment->{'0'}->id = 1; + $class->User->Comment->{'0'}->article_id = 1; + $class->User->Comment->{'0'}->user_id = 1; + $class->User->Comment->{'0'}->comment = 'First Comment for First Article'; + $class->User->Comment->{'0'}->published = 'Y'; + $class->User->Comment->{'0'}->created = '2007-03-18 10:47:23'; + $class->User->Comment->{'0'}->updated = '2007-03-18 10:49:31'; + $class->User->Comment->{'1'} = new stdClass; + $class->User->Comment->{'1'}->id = 2; + $class->User->Comment->{'1'}->article_id = 1; + $class->User->Comment->{'1'}->user_id = 2; + $class->User->Comment->{'1'}->comment = 'Second Comment for First Article'; + $class->User->Comment->{'1'}->published = 'Y'; + $class->User->Comment->{'1'}->created = '2007-03-18 10:47:23'; + $class->User->Comment->{'1'}->updated = '2007-03-18 10:49:31'; + + $result = Set::reverse($class); + $this->assertIdentical($result, $expected); + + $expected = array('User' => array('psword'=> 'whatever', + 'Icon' => array('id'=> 851), + 'Profile' => array('name' => 'Some Name', 'address' => 'Some Address'), + 'Comment' => array( + array('id' => 1, 'article_id' => 1, 'user_id' => 1, 'comment' => 'First Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31'), + array('id' => 2, 'article_id' => 1, 'user_id' => 2, 'comment' => 'Second Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31')))); + + $class = new stdClass; + $class->User = new stdClass; + $class->User->psword = 'whatever'; + $class->User->Icon = new stdClass; + $class->User->Icon->id = 851; + $class->User->Profile = new stdClass; + $class->User->Profile->name = 'Some Name'; + $class->User->Profile->address = 'Some Address'; + $class->User->Comment = array(); + $comment = new stdClass; + $comment->id = 1; + $comment->article_id = 1; + $comment->user_id = 1; + $comment->comment = 'First Comment for First Article'; + $comment->published = 'Y'; + $comment->created = '2007-03-18 10:47:23'; + $comment->updated = '2007-03-18 10:49:31'; + $comment2 = new stdClass; + $comment2->id = 2; + $comment2->article_id = 1; + $comment2->user_id = 2; + $comment2->comment = 'Second Comment for First Article'; + $comment2->published = 'Y'; + $comment2->created = '2007-03-18 10:47:23'; + $comment2->updated = '2007-03-18 10:49:31'; + $class->User->Comment = array($comment, $comment2); + $result = Set::reverse($class); + $this->assertIdentical($result, $expected); + + $model = new Model(array('id' => false, 'name' => 'Model', 'table' => false)); + $expected = array( + 'Behaviors' => array('modelName' => 'Model', '_attached' => array(), '_disabled' => array(), '__methods' => array(), '__mappedMethods' => array()), + 'useDbConfig' => 'default', 'useTable' => false, 'displayField' => null, 'id' => false, 'data' => array(), 'table' => 'models', 'primaryKey' => 'id', '_schema' => null, 'validate' => array(), + 'validationErrors' => array(), 'tablePrefix' => null, 'name' => 'Model', 'alias' => 'Model', 'tableToModel' => array(), 'logTransactions' => false, 'cacheQueries' => false, + 'belongsTo' => array(), 'hasOne' => array(), 'hasMany' => array(), 'hasAndBelongsToMany' => array(), 'actsAs' => null, 'whitelist' => array(), 'cacheSources' => true, + 'findQueryType' => null, 'recursive' => 1, 'order' => null, 'virtualFields' => array(), + '__associationKeys' => array( + 'belongsTo' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'counterCache'), + 'hasOne' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'dependent'), + 'hasMany' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'dependent', 'exclusive', 'finderQuery', 'counterQuery'), + 'hasAndBelongsToMany' => array('className', 'joinTable', 'with', 'foreignKey', 'associationForeignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'unique', 'finderQuery', 'deleteQuery', 'insertQuery')), + '__associations' => array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'), '__backAssociation' => array(), '__insertID' => null, '__numRows' => null, '__affectedRows' => null, + '_findMethods' => array('all' => true, 'first' => true, 'count' => true, 'neighbors' => true, 'list' => true, 'threaded' => true)); + $result = Set::reverse($model); + + ksort($result); + ksort($expected); + + $this->assertIdentical($result, $expected); + + $class = new stdClass; + $class->User = new stdClass; + $class->User->id = 100; + $class->someString = 'this is some string'; + $class->Profile = new stdClass; + $class->Profile->name = 'Joe Mamma'; + + $result = Set::reverse($class); + $expected = array('User' => array('id' => '100'), 'someString'=> 'this is some string', 'Profile' => array('name' => 'Joe Mamma')); + $this->assertEqual($result, $expected); + + $class = new stdClass; + $class->User = new stdClass; + $class->User->id = 100; + $class->User->_name_ = 'User'; + $class->Profile = new stdClass; + $class->Profile->name = 'Joe Mamma'; + $class->Profile->_name_ = 'Profile'; + + $result = Set::reverse($class); + $expected = array('User' => array('id' => '100'), 'Profile' => array('name' => 'Joe Mamma')); + $this->assertEqual($result, $expected); + } + +/** + * testFormatting method + * + * @access public + * @return void + */ + function testFormatting() { + $data = array( + array('Person' => array('first_name' => 'Nate', 'last_name' => 'Abele', 'city' => 'Boston', 'state' => 'MA', 'something' => '42')), + array('Person' => array('first_name' => 'Larry', 'last_name' => 'Masters', 'city' => 'Boondock', 'state' => 'TN', 'something' => '{0}')), + array('Person' => array('first_name' => 'Garrett', 'last_name' => 'Woodworth', 'city' => 'Venice Beach', 'state' => 'CA', 'something' => '{1}'))); + + $result = Set::format($data, '{1}, {0}', array('{n}.Person.first_name', '{n}.Person.last_name')); + $expected = array('Abele, Nate', 'Masters, Larry', 'Woodworth, Garrett'); + $this->assertEqual($result, $expected); + + $result = Set::format($data, '{0}, {1}', array('{n}.Person.last_name', '{n}.Person.first_name')); + $this->assertEqual($result, $expected); + + $result = Set::format($data, '{0}, {1}', array('{n}.Person.city', '{n}.Person.state')); + $expected = array('Boston, MA', 'Boondock, TN', 'Venice Beach, CA'); + $this->assertEqual($result, $expected); + + $result = Set::format($data, '{{0}, {1}}', array('{n}.Person.city', '{n}.Person.state')); + $expected = array('{Boston, MA}', '{Boondock, TN}', '{Venice Beach, CA}'); + $this->assertEqual($result, $expected); + + $result = Set::format($data, '{{0}, {1}}', array('{n}.Person.something', '{n}.Person.something')); + $expected = array('{42, 42}', '{{0}, {0}}', '{{1}, {1}}'); + $this->assertEqual($result, $expected); + + $result = Set::format($data, '{%2$d, %1$s}', array('{n}.Person.something', '{n}.Person.something')); + $expected = array('{42, 42}', '{0, {0}}', '{0, {1}}'); + $this->assertEqual($result, $expected); + + $result = Set::format($data, '{%1$s, %1$s}', array('{n}.Person.something', '{n}.Person.something')); + $expected = array('{42, 42}', '{{0}, {0}}', '{{1}, {1}}'); + $this->assertEqual($result, $expected); + + $result = Set::format($data, '%2$d, %1$s', array('{n}.Person.first_name', '{n}.Person.something')); + $expected = array('42, Nate', '0, Larry', '0, Garrett'); + $this->assertEqual($result, $expected); + + $result = Set::format($data, '%1$s, %2$d', array('{n}.Person.first_name', '{n}.Person.something')); + $expected = array('Nate, 42', 'Larry, 0', 'Garrett, 0'); + $this->assertEqual($result, $expected); + } + +/** + * testCountDim method + * + * @access public + * @return void + */ + function testCountDim() { + $data = array('one', '2', 'three'); + $result = Set::countDim($data); + $this->assertEqual($result, 1); + + $data = array('1' => '1.1', '2', '3'); + $result = Set::countDim($data); + $this->assertEqual($result, 1); + + $data = array('1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => '3.1.1')); + $result = Set::countDim($data); + $this->assertEqual($result, 2); + + $data = array('1' => '1.1', '2', '3' => array('3.1' => '3.1.1')); + $result = Set::countDim($data); + $this->assertEqual($result, 1); + + $data = array('1' => '1.1', '2', '3' => array('3.1' => '3.1.1')); + $result = Set::countDim($data, true); + $this->assertEqual($result, 2); + + $data = array('1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => array('3.1.1' => '3.1.1.1'))); + $result = Set::countDim($data); + $this->assertEqual($result, 2); + + $data = array('1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => array('3.1.1' => '3.1.1.1'))); + $result = Set::countDim($data, true); + $this->assertEqual($result, 3); + + $data = array('1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => '2.1.1.1'))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1'))); + $result = Set::countDim($data, true); + $this->assertEqual($result, 4); + + $data = array('1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => array('2.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1'))); + $result = Set::countDim($data, true); + $this->assertEqual($result, 5); + + $data = array('1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => array('2.1.1.1' => '2.1.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1'))); + $result = Set::countDim($data, true); + $this->assertEqual($result, 5); + + $set = array('1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => array('2.1.1.1' => '2.1.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1'))); + $result = Set::countDim($set, false, 0); + $this->assertEqual($result, 2); + + $result = Set::countDim($set, true); + $this->assertEqual($result, 5); + } + +/** + * testMapNesting method + * + * @access public + * @return void + */ + function testMapNesting() { + $expected = array( + array( + "IndexedPage" => array( + "id" => 1, + "url" => 'http://blah.com/', + 'hash' => '68a9f053b19526d08e36c6a9ad150737933816a5', + 'headers' => array( + 'Date' => "Wed, 14 Nov 2007 15:51:42 GMT", + 'Server' => "Apache", + 'Expires' => "Thu, 19 Nov 1981 08:52:00 GMT", + 'Cache-Control' => "private", + 'Pragma' => "no-cache", + 'Content-Type' => "text/html; charset=UTF-8", + 'X-Original-Transfer-Encoding' => "chunked", + 'Content-Length' => "50210", + ), + 'meta' => array( + 'keywords' => array('testing','tests'), + 'description'=>'describe me', + ), + 'get_vars' => '', + 'post_vars' => array(), + 'cookies' => array('PHPSESSID' => "dde9896ad24595998161ffaf9e0dbe2d"), + 'redirect' => '', + 'created' => "1195055503", + 'updated' => "1195055503", + ) + ), + array( + "IndexedPage" => array( + "id" => 2, + "url" => 'http://blah.com/', + 'hash' => '68a9f053b19526d08e36c6a9ad150737933816a5', + 'headers' => array( + 'Date' => "Wed, 14 Nov 2007 15:51:42 GMT", + 'Server' => "Apache", + 'Expires' => "Thu, 19 Nov 1981 08:52:00 GMT", + 'Cache-Control' => "private", + 'Pragma' => "no-cache", + 'Content-Type' => "text/html; charset=UTF-8", + 'X-Original-Transfer-Encoding' => "chunked", + 'Content-Length' => "50210", + ), + 'meta' => array( + 'keywords' => array('testing','tests'), + 'description'=>'describe me', + ), + 'get_vars' => '', + 'post_vars' => array(), + 'cookies' => array('PHPSESSID' => "dde9896ad24595998161ffaf9e0dbe2d"), + 'redirect' => '', + 'created' => "1195055503", + 'updated' => "1195055503", + ), + ) + ); + + $mapped = Set::map($expected); + $ids = array(); + + foreach($mapped as $object) { + $ids[] = $object->id; + } + $this->assertEqual($ids, array(1, 2)); + $this->assertEqual(get_object_vars($mapped[0]->headers), $expected[0]['IndexedPage']['headers']); + + $result = Set::reverse($mapped); + $this->assertIdentical($result, $expected); + + $data = array( + array( + "IndexedPage" => array( + "id" => 1, + "url" => 'http://blah.com/', + 'hash' => '68a9f053b19526d08e36c6a9ad150737933816a5', + 'get_vars' => '', + 'redirect' => '', + 'created' => "1195055503", + 'updated' => "1195055503", + ) + ), + array( + "IndexedPage" => array( + "id" => 2, + "url" => 'http://blah.com/', + 'hash' => '68a9f053b19526d08e36c6a9ad150737933816a5', + 'get_vars' => '', + 'redirect' => '', + 'created' => "1195055503", + 'updated' => "1195055503", + ), + ) + ); + $mapped = Set::map($data); + + $expected = new stdClass(); + $expected->_name_ = 'IndexedPage'; + $expected->id = 2; + $expected->url = 'http://blah.com/'; + $expected->hash = '68a9f053b19526d08e36c6a9ad150737933816a5'; + $expected->get_vars = ''; + $expected->redirect = ''; + $expected->created = "1195055503"; + $expected->updated = "1195055503"; + $this->assertIdentical($mapped[1], $expected); + + $ids = array(); + + foreach($mapped as $object) { + $ids[] = $object->id; + } + $this->assertEqual($ids, array(1, 2)); + + $result = Set::map(null); + $expected = null; + $this->assertEqual($result, $expected); + } + +/** + * testNestedMappedData method + * + * @access public + * @return void + */ + function testNestedMappedData() { + $result = Set::map(array( + array( + 'Post' => array('id' => '1', 'author_id' => '1', 'title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), + 'Author' => array('id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', 'test' => 'working'), + ) + , array( + 'Post' => array('id' => '2', 'author_id' => '3', 'title' => 'Second Post', 'body' => 'Second Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'), + 'Author' => array('id' => '3', 'user' => 'larry', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:20:23', 'updated' => '2007-03-17 01:22:31', 'test' => 'working'), + ) + )); + + $expected = new stdClass; + $expected->_name_ = 'Post'; + $expected->id = '1'; + $expected->author_id = '1'; + $expected->title = 'First Post'; + $expected->body = 'First Post Body'; + $expected->published = 'Y'; + $expected->created = "2007-03-18 10:39:23"; + $expected->updated = "2007-03-18 10:41:31"; + + $expected->Author = new stdClass; + $expected->Author->id = '1'; + $expected->Author->user = 'mariano'; + $expected->Author->password = '5f4dcc3b5aa765d61d8327deb882cf99'; + $expected->Author->created = "2007-03-17 01:16:23"; + $expected->Author->updated = "2007-03-17 01:18:31"; + $expected->Author->test = "working"; + $expected->Author->_name_ = 'Author'; + + $expected2 = new stdClass; + $expected2->_name_ = 'Post'; + $expected2->id = '2'; + $expected2->author_id = '3'; + $expected2->title = 'Second Post'; + $expected2->body = 'Second Post Body'; + $expected2->published = 'Y'; + $expected2->created = "2007-03-18 10:41:23"; + $expected2->updated = "2007-03-18 10:43:31"; + + $expected2->Author = new stdClass; + $expected2->Author->id = '3'; + $expected2->Author->user = 'larry'; + $expected2->Author->password = '5f4dcc3b5aa765d61d8327deb882cf99'; + $expected2->Author->created = "2007-03-17 01:20:23"; + $expected2->Author->updated = "2007-03-17 01:22:31"; + $expected2->Author->test = "working"; + $expected2->Author->_name_ = 'Author'; + + $test = array(); + $test[0] = $expected; + $test[1] = $expected2; + + $this->assertIdentical($test, $result); + + $result = Set::map( + array( + 'Post' => array('id' => '1', 'author_id' => '1', 'title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'), + 'Author' => array('id' => '1', 'user' => 'mariano', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31', 'test' => 'working'), + ) + ); + $expected = new stdClass; + $expected->_name_ = 'Post'; + $expected->id = '1'; + $expected->author_id = '1'; + $expected->title = 'First Post'; + $expected->body = 'First Post Body'; + $expected->published = 'Y'; + $expected->created = "2007-03-18 10:39:23"; + $expected->updated = "2007-03-18 10:41:31"; + + $expected->Author = new stdClass; + $expected->Author->id = '1'; + $expected->Author->user = 'mariano'; + $expected->Author->password = '5f4dcc3b5aa765d61d8327deb882cf99'; + $expected->Author->created = "2007-03-17 01:16:23"; + $expected->Author->updated = "2007-03-17 01:18:31"; + $expected->Author->test = "working"; + $expected->Author->_name_ = 'Author'; + $this->assertIdentical($expected, $result); + + //Case where extra HABTM fields come back in a result + $data = array( + 'User' => array( + 'id' => 1, + 'email' => 'user@example.com', + 'first_name' => 'John', + 'last_name' => 'Smith', + ), + 'Piece' => array( + array( + 'id' => 1, + 'title' => 'Moonlight Sonata', + 'composer' => 'Ludwig van Beethoven', + 'PiecesUser' => array( + 'id' => 1, + 'created' => '2008-01-01 00:00:00', + 'modified' => '2008-01-01 00:00:00', + 'piece_id' => 1, + 'user_id' => 2, + ) + ), + array( + 'id' => 2, + 'title' => 'Moonlight Sonata 2', + 'composer' => 'Ludwig van Beethoven', + 'PiecesUser' => array( + 'id' => 2, + 'created' => '2008-01-01 00:00:00', + 'modified' => '2008-01-01 00:00:00', + 'piece_id' => 2, + 'user_id' => 2, + ) + ) + ) + ); + + $result = Set::map($data); + + $expected = new stdClass(); + $expected->_name_ = 'User'; + $expected->id = 1; + $expected->email = 'user@example.com'; + $expected->first_name = 'John'; + $expected->last_name = 'Smith'; + + $piece = new stdClass(); + $piece->id = 1; + $piece->title = 'Moonlight Sonata'; + $piece->composer = 'Ludwig van Beethoven'; + + $piece->PiecesUser = new stdClass(); + $piece->PiecesUser->id = 1; + $piece->PiecesUser->created = '2008-01-01 00:00:00'; + $piece->PiecesUser->modified = '2008-01-01 00:00:00'; + $piece->PiecesUser->piece_id = 1; + $piece->PiecesUser->user_id = 2; + $piece->PiecesUser->_name_ = 'PiecesUser'; + + $piece->_name_ = 'Piece'; + + + $piece2 = new stdClass(); + $piece2->id = 2; + $piece2->title = 'Moonlight Sonata 2'; + $piece2->composer = 'Ludwig van Beethoven'; + + $piece2->PiecesUser = new stdClass(); + $piece2->PiecesUser->id = 2; + $piece2->PiecesUser->created = '2008-01-01 00:00:00'; + $piece2->PiecesUser->modified = '2008-01-01 00:00:00'; + $piece2->PiecesUser->piece_id = 2; + $piece2->PiecesUser->user_id = 2; + $piece2->PiecesUser->_name_ = 'PiecesUser'; + + $piece2->_name_ = 'Piece'; + + $expected->Piece = array($piece, $piece2); + + $this->assertIdentical($expected, $result); + + //Same data, but should work if _name_ has been manually defined: + $data = array( + 'User' => array( + 'id' => 1, + 'email' => 'user@example.com', + 'first_name' => 'John', + 'last_name' => 'Smith', + '_name_' => 'FooUser', + ), + 'Piece' => array( + array( + 'id' => 1, + 'title' => 'Moonlight Sonata', + 'composer' => 'Ludwig van Beethoven', + '_name_' => 'FooPiece', + 'PiecesUser' => array( + 'id' => 1, + 'created' => '2008-01-01 00:00:00', + 'modified' => '2008-01-01 00:00:00', + 'piece_id' => 1, + 'user_id' => 2, + '_name_' => 'FooPiecesUser', + ) + ), + array( + 'id' => 2, + 'title' => 'Moonlight Sonata 2', + 'composer' => 'Ludwig van Beethoven', + '_name_' => 'FooPiece', + 'PiecesUser' => array( + 'id' => 2, + 'created' => '2008-01-01 00:00:00', + 'modified' => '2008-01-01 00:00:00', + 'piece_id' => 2, + 'user_id' => 2, + '_name_' => 'FooPiecesUser', + ) + ) + ) + ); + + $result = Set::map($data); + + $expected = new stdClass(); + $expected->_name_ = 'FooUser'; + $expected->id = 1; + $expected->email = 'user@example.com'; + $expected->first_name = 'John'; + $expected->last_name = 'Smith'; + + $piece = new stdClass(); + $piece->id = 1; + $piece->title = 'Moonlight Sonata'; + $piece->composer = 'Ludwig van Beethoven'; + $piece->_name_ = 'FooPiece'; + $piece->PiecesUser = new stdClass(); + $piece->PiecesUser->id = 1; + $piece->PiecesUser->created = '2008-01-01 00:00:00'; + $piece->PiecesUser->modified = '2008-01-01 00:00:00'; + $piece->PiecesUser->piece_id = 1; + $piece->PiecesUser->user_id = 2; + $piece->PiecesUser->_name_ = 'FooPiecesUser'; + + $piece2 = new stdClass(); + $piece2->id = 2; + $piece2->title = 'Moonlight Sonata 2'; + $piece2->composer = 'Ludwig van Beethoven'; + $piece2->_name_ = 'FooPiece'; + $piece2->PiecesUser = new stdClass(); + $piece2->PiecesUser->id = 2; + $piece2->PiecesUser->created = '2008-01-01 00:00:00'; + $piece2->PiecesUser->modified = '2008-01-01 00:00:00'; + $piece2->PiecesUser->piece_id = 2; + $piece2->PiecesUser->user_id = 2; + $piece2->PiecesUser->_name_ = 'FooPiecesUser'; + + $expected->Piece = array($piece, $piece2); + + $this->assertIdentical($expected, $result); + } + +/** + * testPushDiff method + * + * @access public + * @return void + */ + function testPushDiff() { + $array1 = array('ModelOne' => array('id'=>1001, 'field_one'=>'a1.m1.f1', 'field_two'=>'a1.m1.f2')); + $array2 = array('ModelTwo' => array('id'=>1002, 'field_one'=>'a2.m2.f1', 'field_two'=>'a2.m2.f2')); + + $result = Set::pushDiff($array1, $array2); + + $this->assertIdentical($result, $array1 + $array2); + + $array3 = array('ModelOne' => array('id'=>1003, 'field_one'=>'a3.m1.f1', 'field_two'=>'a3.m1.f2', 'field_three'=>'a3.m1.f3')); + $result = Set::pushDiff($array1, $array3); + + $expected = array('ModelOne' => array('id'=>1001, 'field_one'=>'a1.m1.f1', 'field_two'=>'a1.m1.f2', 'field_three'=>'a3.m1.f3')); + $this->assertIdentical($result, $expected); + + + $array1 = array( + 0 => array('ModelOne' => array('id'=>1001, 'field_one'=>'s1.0.m1.f1', 'field_two'=>'s1.0.m1.f2')), + 1 => array('ModelTwo' => array('id'=>1002, 'field_one'=>'s1.1.m2.f2', 'field_two'=>'s1.1.m2.f2'))); + $array2 = array( + 0 => array('ModelOne' => array('id'=>1001, 'field_one'=>'s2.0.m1.f1', 'field_two'=>'s2.0.m1.f2')), + 1 => array('ModelTwo' => array('id'=>1002, 'field_one'=>'s2.1.m2.f2', 'field_two'=>'s2.1.m2.f2'))); + + $result = Set::pushDiff($array1, $array2); + $this->assertIdentical($result, $array1); + + $array3 = array(0 => array('ModelThree' => array('id'=>1003, 'field_one'=>'s3.0.m3.f1', 'field_two'=>'s3.0.m3.f2'))); + + $result = Set::pushDiff($array1, $array3); + $expected = array( + 0 => array('ModelOne' => array('id'=>1001, 'field_one'=>'s1.0.m1.f1', 'field_two'=>'s1.0.m1.f2'), + 'ModelThree' => array('id'=>1003, 'field_one'=>'s3.0.m3.f1', 'field_two'=>'s3.0.m3.f2')), + 1 => array('ModelTwo' => array('id'=>1002, 'field_one'=>'s1.1.m2.f2', 'field_two'=>'s1.1.m2.f2'))); + $this->assertIdentical($result, $expected); + + $result = Set::pushDiff($array1, null); + $this->assertIdentical($result, $array1); + + $result = Set::pushDiff($array1, $array2); + $this->assertIdentical($result, $array1+$array2); + } + +/** + * testSetApply method + * @access public + * @return void + * + */ + function testApply() { + $data = array( + array('Movie' => array('id' => 1, 'title' => 'movie 3', 'rating' => 5)), + array('Movie' => array('id' => 1, 'title' => 'movie 1', 'rating' => 1)), + array('Movie' => array('id' => 1, 'title' => 'movie 2', 'rating' => 3)) + ); + + $result = Set::apply('/Movie/rating', $data, 'array_sum'); + $expected = 9; + $this->assertEqual($result, $expected); + + if (PHP5) { + $result = Set::apply('/Movie/rating', $data, 'array_product'); + $expected = 15; + $this->assertEqual($result, $expected); + } + + $result = Set::apply('/Movie/title', $data, 'ucfirst', array('type' => 'map')); + $expected = array('Movie 3', 'Movie 1', 'Movie 2'); + $this->assertEqual($result, $expected); + + $result = Set::apply('/Movie/title', $data, 'strtoupper', array('type' => 'map')); + $expected = array('MOVIE 3', 'MOVIE 1', 'MOVIE 2'); + $this->assertEqual($result, $expected); + + $result = Set::apply('/Movie/rating', $data, array('SetTest', '_method'), array('type' => 'reduce')); + $expected = 9; + $this->assertEqual($result, $expected); + + $result = Set::apply('/Movie/rating', $data, 'strtoupper', array('type' => 'non existing type')); + $expected = null; + $this->assertEqual($result, $expected); + + } + +/** + * Helper method to test Set::apply() + * + * @access protected + * @return void + */ + function _method($val1, $val2) { + $val1 += $val2; + return $val1; + } + +/** + * testXmlSetReverse method + * + * @access public + * @return void + */ + function testXmlSetReverse() { + App::import('Core', 'Xml'); + + $string = ' + + + Cake PHP Google Group + http://groups.google.com/group/cake-php + Search this group before posting anything. There are over 20,000 posts and it&#39;s very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database. + en + + constructng result array when using findall + http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f + i'm using cakephp to construct a logical data model array that will be <br> passed to a flex app. I have the following model association: <br> ServiceDay-&gt;(hasMany)ServiceTi me-&gt;(hasMany)ServiceTimePrice. So what <br> the current output from my findall is something like this example: <br> <p>Array( <br> [0] =&gt; Array( + http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f + bmil...@gmail.com(bpscrugs) + Fri, 28 Dec 2007 00:44:14 UT + + + Re: share views between actions? + http://groups.google.com/group/cake-php/msg/8b350d898707dad8 + Then perhaps you might do us all a favour and refrain from replying to <br> things you do not understand. That goes especially for asinine comments. <br> Indeed. <br> To sum up: <br> No comment. <br> In my day, a simple &quot;RTFM&quot; would suffice. I'll keep in mind to ignore any <br> further responses from you. <br> You (and I) were referring to the *online documentation*, not other + http://groups.google.com/group/cake-php/msg/8b350d898707dad8 + subtropolis.z...@gmail.com(subtropolis zijn) + Fri, 28 Dec 2007 00:45:01 UT + + + '; + $xml = new Xml($string); + $result = Set::reverse($xml); + $expected = array('Rss' => array( + 'version' => '2.0', + 'Channel' => array( + 'title' => 'Cake PHP Google Group', + 'link' => 'http://groups.google.com/group/cake-php', + 'description' => 'Search this group before posting anything. There are over 20,000 posts and it's very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database.', + 'language' => 'en', + 'Item' => array( + array( + 'title' => 'constructng result array when using findall', + 'link' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f', + 'description' => "i'm using cakephp to construct a logical data model array that will be
    passed to a flex app. I have the following model association:
    ServiceDay->(hasMany)ServiceTi me->(hasMany)ServiceTimePrice. So what
    the current output from my findall is something like this example:

    Array(
    [0] => Array(", + 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f'), + 'author' => 'bmil...@gmail.com(bpscrugs)', + 'pubDate' => 'Fri, 28 Dec 2007 00:44:14 UT', + ), + array( + 'title' => 'Re: share views between actions?', + 'link' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8', + 'description' => 'Then perhaps you might do us all a favour and refrain from replying to
    things you do not understand. That goes especially for asinine comments.
    Indeed.
    To sum up:
    No comment.
    In my day, a simple "RTFM" would suffice. I\'ll keep in mind to ignore any
    further responses from you.
    You (and I) were referring to the *online documentation*, not other', + 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8'), + 'author' => 'subtropolis.z...@gmail.com(subtropolis zijn)', + 'pubDate' => 'Fri, 28 Dec 2007 00:45:01 UT' + ) + ) + ) + )); + $this->assertEqual($result, $expected); + $string =''; + + $xml = new Xml($string); + $result = Set::reverse($xml); + $expected = array('Data' => array('Post' => array('title' => 'Title of this post', 'description' => 'cool'))); + $this->assertEqual($result, $expected); + + $xml = new Xml('An example of a correctly reversed XMLNode'); + $result = Set::reverse($xml); + $expected = array('Example' => + array( + 'Item' => array( + 'title' => 'An example of a correctly reversed XMLNode', + 'desc' => array(), + ) + ) + ); + $this->assertIdentical($result, $expected); + + $xml = new Xml('title1title2'); + $result = Set::reverse($xml); + $expected = + array('Example' => array( + 'Item' => array( + 'attr' => '123', + 'Titles' => array( + 'Title' => array('title1', 'title2') + ) + ) + ) + ); + $this->assertIdentical($result, $expected); + + $xml = new Xml('listtextforitems'); + $result = Set::reverse($xml); + $expected = + array('Example' => array( + 'attr' => 'ex_attr', + 'Item' => array( + 'attr' => '123', + 'titles' => 'list', + 'value' => 'textforitems' + ) + ) + ); + $this->assertIdentical($result, $expected); + + $string = ' + + + Cake PHP Google Group + http://groups.google.com/group/cake-php + Search this group before posting anything. There are over 20,000 posts and it&#39;s very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database. + en + + constructng result array when using findall + http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f + i'm using cakephp to construct a logical data model array that will be <br> passed to a flex app. I have the following model association: <br> ServiceDay-&gt;(hasMany)ServiceTi me-&gt;(hasMany)ServiceTimePrice. So what <br> the current output from my findall is something like this example: <br> <p>Array( <br> [0] =&gt; Array( + cakephp + + + http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f + bmil...@gmail.com(bpscrugs) + Fri, 28 Dec 2007 00:44:14 UT + + + Re: share views between actions? + http://groups.google.com/group/cake-php/msg/8b350d898707dad8 + Then perhaps you might do us all a favour and refrain from replying to <br> things you do not understand. That goes especially for asinine comments. <br> Indeed. <br> To sum up: <br> No comment. <br> In my day, a simple &quot;RTFM&quot; would suffice. I'll keep in mind to ignore any <br> further responses from you. <br> You (and I) were referring to the *online documentation*, not other + cakephp + + + http://groups.google.com/group/cake-php/msg/8b350d898707dad8 + subtropolis.z...@gmail.com(subtropolis zijn) + Fri, 28 Dec 2007 00:45:01 UT + + + '; + + $xml = new Xml($string); + $result = Set::reverse($xml); + + $expected = array('Rss' => array( + 'version' => '2.0', + 'Channel' => array( + 'title' => 'Cake PHP Google Group', + 'link' => 'http://groups.google.com/group/cake-php', + 'description' => 'Search this group before posting anything. There are over 20,000 posts and it's very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database.', + 'language' => 'en', + 'Item' => array( + array( + 'title' => 'constructng result array when using findall', + 'link' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f', + 'description' => "i'm using cakephp to construct a logical data model array that will be
    passed to a flex app. I have the following model association:
    ServiceDay->(hasMany)ServiceTi me->(hasMany)ServiceTimePrice. So what
    the current output from my findall is something like this example:

    Array(
    [0] => Array(", + 'creator' => 'cakephp', + 'Category' => array('cakephp', 'model'), + 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f'), + 'author' => 'bmil...@gmail.com(bpscrugs)', + 'pubDate' => 'Fri, 28 Dec 2007 00:44:14 UT', + ), + array( + 'title' => 'Re: share views between actions?', + 'link' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8', + 'description' => 'Then perhaps you might do us all a favour and refrain from replying to
    things you do not understand. That goes especially for asinine comments.
    Indeed.
    To sum up:
    No comment.
    In my day, a simple "RTFM" would suffice. I\'ll keep in mind to ignore any
    further responses from you.
    You (and I) were referring to the *online documentation*, not other', + 'creator' => 'cakephp', + 'Category' => array('cakephp', 'model'), + 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8'), + 'author' => 'subtropolis.z...@gmail.com(subtropolis zijn)', + 'pubDate' => 'Fri, 28 Dec 2007 00:45:01 UT' + ) + ) + ) + )); + $this->assertEqual($result, $expected); + + $text = ' + + + xri://$xrds*simple + 2008-04-13T07:34:58Z + + http://oauth.net/core/1.0/endpoint/authorize + http://oauth.net/core/1.0/parameters/auth-header + http://oauth.net/core/1.0/parameters/uri-query + https://ma.gnolia.com/oauth/authorize + http://ma.gnolia.com/oauth/authorize + + + + xri://$xrds*simple + + http://oauth.net/discovery/1.0 + #oauth + + + '; + + $xml = new Xml($text); + $result = Set::reverse($xml); + + $expected = array('XRDS' => array( + 'xmlns' => 'xri://$xrds', + 'XRD' => array( + array( + 'xml:id' => 'oauth', + 'xmlns' => 'xri://$XRD*($v*2.0)', + 'version' => '2.0', + 'Type' => 'xri://$xrds*simple', + 'Expires' => '2008-04-13T07:34:58Z', + 'Service' => array( + 'Type' => array( + 'http://oauth.net/core/1.0/endpoint/authorize', + 'http://oauth.net/core/1.0/parameters/auth-header', + 'http://oauth.net/core/1.0/parameters/uri-query' + ), + 'URI' => array( + array( + 'value' => 'https://ma.gnolia.com/oauth/authorize', + 'priority' => '10', + ), + array( + 'value' => 'http://ma.gnolia.com/oauth/authorize', + 'priority' => '20' + ) + ) + ) + ), + array( + 'xmlns' => 'xri://$XRD*($v*2.0)', + 'version' => '2.0', + 'Type' => 'xri://$xrds*simple', + 'Service' => array( + 'priority' => '10', + 'Type' => 'http://oauth.net/discovery/1.0', + 'URI' => '#oauth' + ) + ) + ) + )); + $this->assertEqual($result, $expected); + } + +/** + * testStrictKeyCheck method + * + * @access public + * @return void + */ + function testStrictKeyCheck() { + $set = array('a' => 'hi'); + $this->assertFalse(Set::check($set, 'a.b')); + } + +/** + * Tests Set::flatten + * + * @access public + * @return void + */ + function testFlatten() { + $data = array('Larry', 'Curly', 'Moe'); + $result = Set::flatten($data); + $this->assertEqual($result, $data); + + $data[9] = 'Shemp'; + $result = Set::flatten($data); + $this->assertEqual($result, $data); + + $data = array( + array( + 'Post' => array('id' => '1', 'author_id' => '1', 'title' => 'First Post'), + 'Author' => array('id' => '1', 'user' => 'nate', 'password' => 'foo'), + ), + array( + 'Post' => array('id' => '2', 'author_id' => '3', 'title' => 'Second Post', 'body' => 'Second Post Body'), + 'Author' => array('id' => '3', 'user' => 'larry', 'password' => null), + ) + ); + + $result = Set::flatten($data); + $expected = array( + '0.Post.id' => '1', '0.Post.author_id' => '1', '0.Post.title' => 'First Post', '0.Author.id' => '1', + '0.Author.user' => 'nate', '0.Author.password' => 'foo', '1.Post.id' => '2', '1.Post.author_id' => '3', + '1.Post.title' => 'Second Post', '1.Post.body' => 'Second Post Body', '1.Author.id' => '3', + '1.Author.user' => 'larry', '1.Author.password' => null + ); + $this->assertEqual($result, $expected); + } + +/** + * test normalization + * + * @return void + */ + function testNormalizeStrings() { + $result = Set::normalize('one,two,three'); + $expected = array('one' => null, 'two' => null, 'three' => null); + $this->assertEqual($expected, $result); + + $result = Set::normalize('one two three', true, ' '); + $expected = array('one' => null, 'two' => null, 'three' => null); + $this->assertEqual($expected, $result); + + $result = Set::normalize('one , two , three ', true, ',', true); + $expected = array('one' => null, 'two' => null, 'three' => null); + $this->assertEqual($expected, $result); + } + +/** + * test normalizing arrays + * + * @return void + */ + function testNormalizeArrays() { + $result = Set::normalize(array('one', 'two', 'three')); + $expected = array('one' => null, 'two' => null, 'three' => null); + $this->assertEqual($expected, $result); + + $result = Set::normalize(array('one', 'two', 'three'), false); + $expected = array('one', 'two', 'three'); + $this->assertEqual($expected, $result); + + $result = Set::normalize(array('one' => 1, 'two' => 2, 'three' => 3, 'four'), false); + $expected = array('one' => 1, 'two' => 2, 'three' => 3, 'four' => null); + $this->assertEqual($expected, $result); + + $result = Set::normalize(array('one' => 1, 'two' => 2, 'three' => 3, 'four')); + $expected = array('one' => 1, 'two' => 2, 'three' => 3, 'four' => null); + $this->assertEqual($expected, $result); + + $result = Set::normalize(array('one' => array('a', 'b', 'c' => 'cee'), 'two' => 2, 'three')); + $expected = array('one' => array('a', 'b', 'c' => 'cee'), 'two' => 2, 'three' => null); + $this->assertEqual($expected, $result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/string.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/string.test.php new file mode 100644 index 000000000..da284d35d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/string.test.php @@ -0,0 +1,310 @@ +assertTrue($match); + } + +/** + * testMultipleUuidGeneration method + * + * @access public + * @return void + */ + function testMultipleUuidGeneration() { + $check = array(); + $count = mt_rand(10, 1000); + $pattern = "/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/"; + + for($i = 0; $i < $count; $i++) { + $result = String::uuid(); + $match = preg_match($pattern, $result); + $this->assertTrue($match); + $this->assertFalse(in_array($result, $check)); + $check[] = $result; + } + } + +/** + * testInsert method + * + * @access public + * @return void + */ + function testInsert() { + $string = 'some string'; + $expected = 'some string'; + $result = String::insert($string, array()); + $this->assertEqual($result, $expected); + + $string = '2 + 2 = :sum. Cake is :adjective.'; + $expected = '2 + 2 = 4. Cake is yummy.'; + $result = String::insert($string, array('sum' => '4', 'adjective' => 'yummy')); + $this->assertEqual($result, $expected); + + $string = '2 + 2 = %sum. Cake is %adjective.'; + $result = String::insert($string, array('sum' => '4', 'adjective' => 'yummy'), array('before' => '%')); + $this->assertEqual($result, $expected); + + $string = '2 + 2 = 2sum2. Cake is 9adjective9.'; + $result = String::insert($string, array('sum' => '4', 'adjective' => 'yummy'), array('format' => '/([\d])%s\\1/')); + $this->assertEqual($result, $expected); + + $string = '2 + 2 = 12sum21. Cake is 23adjective45.'; + $expected = '2 + 2 = 4. Cake is 23adjective45.'; + $result = String::insert($string, array('sum' => '4', 'adjective' => 'yummy'), array('format' => '/([\d])([\d])%s\\2\\1/')); + $this->assertEqual($result, $expected); + + $string = ':web :web_site'; + $expected = 'www http'; + $result = String::insert($string, array('web' => 'www', 'web_site' => 'http')); + $this->assertEqual($result, $expected); + + $string = '2 + 2 = .'; + $expected = '2 + 2 = '4', 'adjective' => 'yummy'), array('before' => '<', 'after' => '>')); + $this->assertEqual($result, $expected); + + $string = '2 + 2 = \:sum. Cake is :adjective.'; + $expected = '2 + 2 = :sum. Cake is yummy.'; + $result = String::insert($string, array('sum' => '4', 'adjective' => 'yummy')); + $this->assertEqual($result, $expected); + + $string = '2 + 2 = !:sum. Cake is :adjective.'; + $result = String::insert($string, array('sum' => '4', 'adjective' => 'yummy'), array('escape' => '!')); + $this->assertEqual($result, $expected); + + $string = '2 + 2 = \%sum. Cake is %adjective.'; + $expected = '2 + 2 = %sum. Cake is yummy.'; + $result = String::insert($string, array('sum' => '4', 'adjective' => 'yummy'), array('before' => '%')); + $this->assertEqual($result, $expected); + + $string = ':a :b \:a :a'; + $expected = '1 2 :a 1'; + $result = String::insert($string, array('a' => 1, 'b' => 2)); + $this->assertEqual($result, $expected); + + $string = ':a :b :c'; + $expected = '2 3'; + $result = String::insert($string, array('b' => 2, 'c' => 3), array('clean' => true)); + $this->assertEqual($result, $expected); + + $string = ':a :b :c'; + $expected = '1 3'; + $result = String::insert($string, array('a' => 1, 'c' => 3), array('clean' => true)); + $this->assertEqual($result, $expected); + + $string = ':a :b :c'; + $expected = '2 3'; + $result = String::insert($string, array('b' => 2, 'c' => 3), array('clean' => true)); + $this->assertEqual($result, $expected); + + $string = ':a, :b and :c'; + $expected = '2 and 3'; + $result = String::insert($string, array('b' => 2, 'c' => 3), array('clean' => true)); + $this->assertEqual($result, $expected); + + $string = '":a, :b and :c"'; + $expected = '"1, 2"'; + $result = String::insert($string, array('a' => 1, 'b' => 2), array('clean' => true)); + $this->assertEqual($result, $expected); + + $string = '"${a}, ${b} and ${c}"'; + $expected = '"1, 2"'; + $result = String::insert($string, array('a' => 1, 'b' => 2), array('before' => '${', 'after' => '}', 'clean' => true)); + $this->assertEqual($result, $expected); + + $string = ':alt'; + $expected = ''; + $result = String::insert($string, array('src' => 'foo'), array('clean' => 'html')); + + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = String::insert($string, array('src' => 'foo'), array('clean' => 'html')); + $this->assertEqual($result, $expected); + + $string = ''; + $expected = ''; + $result = String::insert($string, array('src' => 'foo', 'extra' => 'bar'), array('clean' => 'html')); + $this->assertEqual($result, $expected); + + $result = String::insert("this is a ? string", "test"); + $expected = "this is a test string"; + $this->assertEqual($result, $expected); + + $result = String::insert("this is a ? string with a ? ? ?", array('long', 'few?', 'params', 'you know')); + $expected = "this is a long string with a few? params you know"; + $this->assertEqual($result, $expected); + + $result = String::insert('update saved_urls set url = :url where id = :id', array('url' => 'http://www.testurl.com/param1:url/param2:id','id' => 1)); + $expected = "update saved_urls set url = http://www.testurl.com/param1:url/param2:id where id = 1"; + $this->assertEqual($result, $expected); + + $result = String::insert('update saved_urls set url = :url where id = :id', array('id' => 1, 'url' => 'http://www.testurl.com/param1:url/param2:id')); + $expected = "update saved_urls set url = http://www.testurl.com/param1:url/param2:id where id = 1"; + $this->assertEqual($result, $expected); + + $result = String::insert(':me cake. :subject :verb fantastic.', array('me' => 'I :verb', 'subject' => 'cake', 'verb' => 'is')); + $expected = "I :verb cake. cake is fantastic."; + $this->assertEqual($result, $expected); + + $result = String::insert(':I.am: :not.yet: passing.', array('I.am' => 'We are'), array('before' => ':', 'after' => ':', 'clean' => array('replacement' => ' of course', 'method' => 'text'))); + $expected = "We are of course passing."; + $this->assertEqual($result, $expected); + + $result = String::insert( + ':I.am: :not.yet: passing.', + array('I.am' => 'We are'), + array('before' => ':', 'after' => ':', 'clean' => true) + ); + $expected = "We are passing."; + $this->assertEqual($result, $expected); + + $result = String::insert('?-pended result', array('Pre')); + $expected = "Pre-pended result"; + $this->assertEqual($result, $expected); + + $string = 'switching :timeout / :timeout_count'; + $expected = 'switching 5 / 10'; + $result = String::insert($string, array('timeout' => 5, 'timeout_count' => 10)); + $this->assertEqual($result, $expected); + + $string = 'switching :timeout / :timeout_count'; + $expected = 'switching 5 / 10'; + $result = String::insert($string, array('timeout_count' => 10, 'timeout' => 5)); + $this->assertEqual($result, $expected); + + $string = 'switching :timeout_count by :timeout'; + $expected = 'switching 10 by 5'; + $result = String::insert($string, array('timeout' => 5, 'timeout_count' => 10)); + $this->assertEqual($result, $expected); + + $string = 'switching :timeout_count by :timeout'; + $expected = 'switching 10 by 5'; + $result = String::insert($string, array('timeout_count' => 10, 'timeout' => 5)); + $this->assertEqual($result, $expected); + } + +/** + * test Clean Insert + * + * @return void + */ + function testCleanInsert() { + $result = String::cleanInsert(':incomplete', array( + 'clean' => true, 'before' => ':', 'after' => '' + )); + $this->assertEqual($result, ''); + + $result = String::cleanInsert(':incomplete', array( + 'clean' => array('method' => 'text', 'replacement' => 'complete'), + 'before' => ':', 'after' => '') + ); + $this->assertEqual($result, 'complete'); + + $result = String::cleanInsert(':in.complete', array( + 'clean' => true, 'before' => ':', 'after' => '' + )); + $this->assertEqual($result, ''); + + $result = String::cleanInsert(':in.complete and', array( + 'clean' => true, 'before' => ':', 'after' => '') + ); + $this->assertEqual($result, ''); + + $result = String::cleanInsert(':in.complete or stuff', array( + 'clean' => true, 'before' => ':', 'after' => '' + )); + $this->assertEqual($result, 'stuff'); + + $result = String::cleanInsert( + '

    Text here

    ', + array('clean' => 'html', 'before' => ':', 'after' => '') + ); + $this->assertEqual($result, '

    Text here

    '); + } + +/** + * Tests that non-insertable variables (i.e. arrays) are skipped when used as values in + * String::insert(). + * + * @return void + */ + function testAutoIgnoreBadInsertData() { + $data = array('foo' => 'alpha', 'bar' => 'beta', 'fale' => array()); + $result = String::insert('(:foo > :bar || :fale!)', $data, array('clean' => 'text')); + $this->assertEqual($result, '(alpha > beta || !)'); + } + +/** + * testTokenize method + * + * @access public + * @return void + */ + function testTokenize() { + $result = String::tokenize('A,(short,boring test)'); + $expected = array('A', '(short,boring test)'); + $this->assertEqual($result, $expected); + + $result = String::tokenize('A,(short,more interesting( test)'); + $expected = array('A', '(short,more interesting( test)'); + $this->assertEqual($result, $expected); + + $result = String::tokenize('A,(short,very interesting( test))'); + $expected = array('A', '(short,very interesting( test))'); + $this->assertEqual($result, $expected); + + $result = String::tokenize('"single tag"', ' ', '"', '"'); + $expected = array('"single tag"'); + $this->assertEqual($expected, $result); + + $result = String::tokenize('tagA "single tag" tagB', ' ', '"', '"'); + $expected = array('tagA', '"single tag"', 'tagB'); + $this->assertEqual($expected, $result); + } + + function testReplaceWithQuestionMarkInString() { + $string = ':a, :b and :c?'; + $expected = '2 and 3?'; + $result = String::insert($string, array('b' => 2, 'c' => 3), array('clean' => true)); + $this->assertEqual($expected, $result); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/test_manager.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/test_manager.test.php new file mode 100644 index 000000000..a55149988 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/test_manager.test.php @@ -0,0 +1,120 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ + +/** + * TestManagerTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class TestManagerTest extends CakeTestCase { + +/** + * setUp method + * + * @return void + * @access public + */ + function setUp() { + $this->TestManager =& new TestManager(); + $this->Reporter =& new CakeHtmlReporter(); + } + +/** + * testRunAllTests method + * + * @return void + * @access public + */ + function testRunAllTests() { + $folder =& new Folder($this->TestManager->_getTestsPath()); + $extension = str_replace('.', '\.', $this->TestManager->getExtension('test')); + $out = $folder->findRecursive('.*' . $extension); + + $reporter =& new CakeHtmlReporter(); + $list = $this->TestManager->runAllTests($reporter, true); + + $this->assertEqual(count($out), count($list)); + } + +/** + * testRunTestCase method + * + * @return void + * @access public + */ + function testRunTestCase() { + $file = md5(time()); + $result = $this->TestManager->runTestCase($file, $this->Reporter); + $this->assertError('Test case ' . $file . ' cannot be found'); + $this->assertFalse($result); + + $file = str_replace(CORE_TEST_CASES, '', __FILE__); + $result = $this->TestManager->runTestCase($file, $this->Reporter, true); + $this->assertTrue($result); + } + +/** + * testRunGroupTest method + * + * @return void + * @access public + */ + function testRunGroupTest() { + } + +/** + * testAddTestCasesFromDirectory method + * + * @return void + * @access public + */ + function testAddTestCasesFromDirectory() { + } + +/** + * testAddTestFile method + * + * @return void + * @access public + */ + function testAddTestFile() { + } + +/** + * testGetTestCaseList method + * + * @return void + * @access public + */ + function testGetTestCaseList() { + } + +/** + * testGetGroupTestList method + * + * @return void + * @access public + */ + function testGetGroupTestList() { + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/validation.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/validation.test.php new file mode 100644 index 000000000..f9908c071 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/validation.test.php @@ -0,0 +1,2209 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', 'Validation'); + +/** + * CustomValidator class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class CustomValidator { + +/** + * Makes sure that a given $email address is valid and unique + * + * @param string $email + * @return boolean + * @access public + */ + function customValidate($check) { + return preg_match('/^[0-9]{3}$/', $check); + } +} + +/** + * TestNlValidation class + * + * Used to test pass through of Validation + * + * @package cake.tests.cases.libs + */ +class TestNlValidation { +/** + * postal function, for testing postal pass through. + * + * @param string $check + * @return void + */ + function postal($check) { + return true; + } +/** + * ssn function for testing ssn pass through + * + * @return void + */ + function ssn($check) { + return true; + } +} + +/** + * TestDeValidation class + * + * Used to test pass through of Validation + * + * @package cake.tests.cases.libs + */ +class TestDeValidation { +/** + * phone function, for testing phone pass through. + * + * @param string $check + * @return void + */ + function phone($check) { + return true; + } +} + +/** + * Test Case for Validation Class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ValidationTest extends CakeTestCase { + +/** + * Validation property + * + * @var mixed null + * @access public + */ + var $Validation = null; + +/** + * setup method + * + * @access public + * @return void + */ + function setUp() { + $this->Validation =& Validation::getInstance(); + $this->_appEncoding = Configure::read('App.encoding'); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + Configure::write('App.encoding', $this->_appEncoding); + } + +/** + * testNotEmpty method + * + * @access public + * @return void + */ + function testNotEmpty() { + $this->assertTrue(Validation::notEmpty('abcdefg')); + $this->assertTrue(Validation::notEmpty('fasdf ')); + $this->assertTrue(Validation::notEmpty('fooo'.chr(243).'blabla')); + $this->assertTrue(Validation::notEmpty('abçÄĕʑʘπй')); + $this->assertTrue(Validation::notEmpty('José')); + $this->assertTrue(Validation::notEmpty('é')); + $this->assertTrue(Validation::notEmpty('Ï€')); + $this->assertFalse(Validation::notEmpty("\t ")); + $this->assertFalse(Validation::notEmpty("")); + + } + +/** + * testNotEmptyISO88591Encoding method + * + * @return void + * @access public + */ + function testNotEmptyISO88591AppEncoding() { + Configure::write('App.encoding', 'ISO-8859-1'); + $this->assertTrue(Validation::notEmpty('abcdefg')); + $this->assertTrue(Validation::notEmpty('fasdf ')); + $this->assertTrue(Validation::notEmpty('fooo'.chr(243).'blabla')); + $this->assertTrue(Validation::notEmpty('abçÄĕʑʘπй')); + $this->assertTrue(Validation::notEmpty('José')); + $this->assertTrue(Validation::notEmpty(utf8_decode('José'))); + $this->assertFalse(Validation::notEmpty("\t ")); + $this->assertFalse(Validation::notEmpty("")); + } + +/** + * testAlphaNumeric method + * + * @access public + * @return void + */ + function testAlphaNumeric() { + $this->assertTrue(Validation::alphaNumeric('frferrf')); + $this->assertTrue(Validation::alphaNumeric('12234')); + $this->assertTrue(Validation::alphaNumeric('1w2e2r3t4y')); + $this->assertTrue(Validation::alphaNumeric('0')); + $this->assertTrue(Validation::alphaNumeric('abçÄĕʑʘπй')); + $this->assertTrue(Validation::alphaNumeric('ˇˆๆゞ')); + $this->assertTrue(Validation::alphaNumeric('×à¸ã‚アꀀ豈')); + $this->assertTrue(Validation::alphaNumeric('Džᾈᾨ')); + $this->assertTrue(Validation::alphaNumeric('ÆΔΩЖÇ')); + + + $this->assertFalse(Validation::alphaNumeric('12 234')); + $this->assertFalse(Validation::alphaNumeric('dfd 234')); + $this->assertFalse(Validation::alphaNumeric("\n")); + $this->assertFalse(Validation::alphaNumeric("\t")); + $this->assertFalse(Validation::alphaNumeric("\r")); + $this->assertFalse(Validation::alphaNumeric(' ')); + $this->assertFalse(Validation::alphaNumeric('')); + } + +/** + * testAlphaNumericPassedAsArray method + * + * @access public + * @return void + */ + function testAlphaNumericPassedAsArray() { + $this->assertTrue(Validation::alphaNumeric(array('check' => 'frferrf'))); + $this->assertTrue(Validation::alphaNumeric(array('check' => '12234'))); + $this->assertTrue(Validation::alphaNumeric(array('check' => '1w2e2r3t4y'))); + $this->assertTrue(Validation::alphaNumeric(array('check' => '0'))); + $this->assertFalse(Validation::alphaNumeric(array('check' => '12 234'))); + $this->assertFalse(Validation::alphaNumeric(array('check' => 'dfd 234'))); + $this->assertFalse(Validation::alphaNumeric(array('check' => "\n"))); + $this->assertFalse(Validation::alphaNumeric(array('check' => "\t"))); + $this->assertFalse(Validation::alphaNumeric(array('check' => "\r"))); + $this->assertFalse(Validation::alphaNumeric(array('check' => ' '))); + $this->assertFalse(Validation::alphaNumeric(array('check' => ''))); + } + +/** + * testBetween method + * + * @access public + * @return void + */ + function testBetween() { + $this->assertTrue(Validation::between('abcdefg', 1, 7)); + $this->assertTrue(Validation::between('', 0, 7)); + $this->assertTrue(Validation::between('×à¸ã‚アꀀ豈', 1, 7)); + + $this->assertFalse(Validation::between('abcdefg', 1, 6)); + $this->assertFalse(Validation::between('ÆΔΩЖÇ', 1, 3)); + } + +/** + * testBlank method + * + * @access public + * @return void + */ + function testBlank() { + $this->assertTrue(Validation::blank('')); + $this->assertTrue(Validation::blank(' ')); + $this->assertTrue(Validation::blank("\n")); + $this->assertTrue(Validation::blank("\t")); + $this->assertTrue(Validation::blank("\r")); + $this->assertFalse(Validation::blank(' Blank')); + $this->assertFalse(Validation::blank('Blank')); + } + +/** + * testBlankAsArray method + * + * @access public + * @return void + */ + function testBlankAsArray() { + $this->assertTrue(Validation::blank(array('check' => ''))); + $this->assertTrue(Validation::blank(array('check' => ' '))); + $this->assertTrue(Validation::blank(array('check' => "\n"))); + $this->assertTrue(Validation::blank(array('check' => "\t"))); + $this->assertTrue(Validation::blank(array('check' => "\r"))); + $this->assertFalse(Validation::blank(array('check' => ' Blank'))); + $this->assertFalse(Validation::blank(array('check' => 'Blank'))); + } + +/** + * testcc method + * + * @access public + * @return void + */ + function testcc() { + //American Express + $this->assertTrue(Validation::cc('370482756063980', array('amex'))); + $this->assertTrue(Validation::cc('349106433773483', array('amex'))); + $this->assertTrue(Validation::cc('344671486204764', array('amex'))); + $this->assertTrue(Validation::cc('344042544509943', array('amex'))); + $this->assertTrue(Validation::cc('377147515754475', array('amex'))); + $this->assertTrue(Validation::cc('375239372816422', array('amex'))); + $this->assertTrue(Validation::cc('376294341957707', array('amex'))); + $this->assertTrue(Validation::cc('341779292230411', array('amex'))); + $this->assertTrue(Validation::cc('341646919853372', array('amex'))); + $this->assertTrue(Validation::cc('348498616319346', array('amex'))); + //BankCard + $this->assertTrue(Validation::cc('5610745867413420', array('bankcard'))); + $this->assertTrue(Validation::cc('5610376649499352', array('bankcard'))); + $this->assertTrue(Validation::cc('5610091936000694', array('bankcard'))); + $this->assertTrue(Validation::cc('5602248780118788', array('bankcard'))); + $this->assertTrue(Validation::cc('5610631567676765', array('bankcard'))); + $this->assertTrue(Validation::cc('5602238211270795', array('bankcard'))); + $this->assertTrue(Validation::cc('5610173951215470', array('bankcard'))); + $this->assertTrue(Validation::cc('5610139705753702', array('bankcard'))); + $this->assertTrue(Validation::cc('5602226032150551', array('bankcard'))); + $this->assertTrue(Validation::cc('5602223993735777', array('bankcard'))); + //Diners Club 14 + $this->assertTrue(Validation::cc('30155483651028', array('diners'))); + $this->assertTrue(Validation::cc('36371312803821', array('diners'))); + $this->assertTrue(Validation::cc('38801277489875', array('diners'))); + $this->assertTrue(Validation::cc('30348560464296', array('diners'))); + $this->assertTrue(Validation::cc('30349040317708', array('diners'))); + $this->assertTrue(Validation::cc('36567413559978', array('diners'))); + $this->assertTrue(Validation::cc('36051554732702', array('diners'))); + $this->assertTrue(Validation::cc('30391842198191', array('diners'))); + $this->assertTrue(Validation::cc('30172682197745', array('diners'))); + $this->assertTrue(Validation::cc('30162056566641', array('diners'))); + $this->assertTrue(Validation::cc('30085066927745', array('diners'))); + $this->assertTrue(Validation::cc('36519025221976', array('diners'))); + $this->assertTrue(Validation::cc('30372679371044', array('diners'))); + $this->assertTrue(Validation::cc('38913939150124', array('diners'))); + $this->assertTrue(Validation::cc('36852899094637', array('diners'))); + $this->assertTrue(Validation::cc('30138041971120', array('diners'))); + $this->assertTrue(Validation::cc('36184047836838', array('diners'))); + $this->assertTrue(Validation::cc('30057460264462', array('diners'))); + $this->assertTrue(Validation::cc('38980165212050', array('diners'))); + $this->assertTrue(Validation::cc('30356516881240', array('diners'))); + $this->assertTrue(Validation::cc('38744810033182', array('diners'))); + $this->assertTrue(Validation::cc('30173638706621', array('diners'))); + $this->assertTrue(Validation::cc('30158334709185', array('diners'))); + $this->assertTrue(Validation::cc('30195413721186', array('diners'))); + $this->assertTrue(Validation::cc('38863347694793', array('diners'))); + $this->assertTrue(Validation::cc('30275627009113', array('diners'))); + $this->assertTrue(Validation::cc('30242860404971', array('diners'))); + $this->assertTrue(Validation::cc('30081877595151', array('diners'))); + $this->assertTrue(Validation::cc('38053196067461', array('diners'))); + $this->assertTrue(Validation::cc('36520379984870', array('diners'))); + //2004 MasterCard/Diners Club Alliance International 14 + $this->assertTrue(Validation::cc('36747701998969', array('diners'))); + $this->assertTrue(Validation::cc('36427861123159', array('diners'))); + $this->assertTrue(Validation::cc('36150537602386', array('diners'))); + $this->assertTrue(Validation::cc('36582388820610', array('diners'))); + $this->assertTrue(Validation::cc('36729045250216', array('diners'))); + //2004 MasterCard/Diners Club Alliance US & Canada 16 + $this->assertTrue(Validation::cc('5597511346169950', array('diners'))); + $this->assertTrue(Validation::cc('5526443162217562', array('diners'))); + $this->assertTrue(Validation::cc('5577265786122391', array('diners'))); + $this->assertTrue(Validation::cc('5534061404676989', array('diners'))); + $this->assertTrue(Validation::cc('5545313588374502', array('diners'))); + //Discover + $this->assertTrue(Validation::cc('6011802876467237', array('disc'))); + $this->assertTrue(Validation::cc('6506432777720955', array('disc'))); + $this->assertTrue(Validation::cc('6011126265283942', array('disc'))); + $this->assertTrue(Validation::cc('6502187151579252', array('disc'))); + $this->assertTrue(Validation::cc('6506600836002298', array('disc'))); + $this->assertTrue(Validation::cc('6504376463615189', array('disc'))); + $this->assertTrue(Validation::cc('6011440907005377', array('disc'))); + $this->assertTrue(Validation::cc('6509735979634270', array('disc'))); + $this->assertTrue(Validation::cc('6011422366775856', array('disc'))); + $this->assertTrue(Validation::cc('6500976374623323', array('disc'))); + //enRoute + $this->assertTrue(Validation::cc('201496944158937', array('enroute'))); + $this->assertTrue(Validation::cc('214945833739665', array('enroute'))); + $this->assertTrue(Validation::cc('214982692491187', array('enroute'))); + $this->assertTrue(Validation::cc('214901395949424', array('enroute'))); + $this->assertTrue(Validation::cc('201480676269187', array('enroute'))); + $this->assertTrue(Validation::cc('214911922887807', array('enroute'))); + $this->assertTrue(Validation::cc('201485025457250', array('enroute'))); + $this->assertTrue(Validation::cc('201402662758866', array('enroute'))); + $this->assertTrue(Validation::cc('214981579370225', array('enroute'))); + $this->assertTrue(Validation::cc('201447595859877', array('enroute'))); + //JCB 15 digit + $this->assertTrue(Validation::cc('210034762247893', array('jcb'))); + $this->assertTrue(Validation::cc('180078671678892', array('jcb'))); + $this->assertTrue(Validation::cc('180010559353736', array('jcb'))); + $this->assertTrue(Validation::cc('210095474464258', array('jcb'))); + $this->assertTrue(Validation::cc('210006675562188', array('jcb'))); + $this->assertTrue(Validation::cc('210063299662662', array('jcb'))); + $this->assertTrue(Validation::cc('180032506857825', array('jcb'))); + $this->assertTrue(Validation::cc('210057919192738', array('jcb'))); + $this->assertTrue(Validation::cc('180031358949367', array('jcb'))); + $this->assertTrue(Validation::cc('180033802147846', array('jcb'))); + //JCB 16 digit + $this->assertTrue(Validation::cc('3096806857839939', array('jcb'))); + $this->assertTrue(Validation::cc('3158699503187091', array('jcb'))); + $this->assertTrue(Validation::cc('3112549607186579', array('jcb'))); + $this->assertTrue(Validation::cc('3112332922425604', array('jcb'))); + $this->assertTrue(Validation::cc('3112001541159239', array('jcb'))); + $this->assertTrue(Validation::cc('3112162495317841', array('jcb'))); + $this->assertTrue(Validation::cc('3337562627732768', array('jcb'))); + $this->assertTrue(Validation::cc('3337107161330775', array('jcb'))); + $this->assertTrue(Validation::cc('3528053736003621', array('jcb'))); + $this->assertTrue(Validation::cc('3528915255020360', array('jcb'))); + $this->assertTrue(Validation::cc('3096786059660921', array('jcb'))); + $this->assertTrue(Validation::cc('3528264799292320', array('jcb'))); + $this->assertTrue(Validation::cc('3096469164130136', array('jcb'))); + $this->assertTrue(Validation::cc('3112127443822853', array('jcb'))); + $this->assertTrue(Validation::cc('3096849995802328', array('jcb'))); + $this->assertTrue(Validation::cc('3528090735127407', array('jcb'))); + $this->assertTrue(Validation::cc('3112101006819234', array('jcb'))); + $this->assertTrue(Validation::cc('3337444428040784', array('jcb'))); + $this->assertTrue(Validation::cc('3088043154151061', array('jcb'))); + $this->assertTrue(Validation::cc('3088295969414866', array('jcb'))); + $this->assertTrue(Validation::cc('3158748843158575', array('jcb'))); + $this->assertTrue(Validation::cc('3158709206148538', array('jcb'))); + $this->assertTrue(Validation::cc('3158365159575324', array('jcb'))); + $this->assertTrue(Validation::cc('3158671691305165', array('jcb'))); + $this->assertTrue(Validation::cc('3528523028771093', array('jcb'))); + $this->assertTrue(Validation::cc('3096057126267870', array('jcb'))); + $this->assertTrue(Validation::cc('3158514047166834', array('jcb'))); + $this->assertTrue(Validation::cc('3528274546125962', array('jcb'))); + $this->assertTrue(Validation::cc('3528890967705733', array('jcb'))); + $this->assertTrue(Validation::cc('3337198811307545', array('jcb'))); + //Maestro (debit card) + $this->assertTrue(Validation::cc('5020147409985219', array('maestro'))); + $this->assertTrue(Validation::cc('5020931809905616', array('maestro'))); + $this->assertTrue(Validation::cc('5020412965470224', array('maestro'))); + $this->assertTrue(Validation::cc('5020129740944022', array('maestro'))); + $this->assertTrue(Validation::cc('5020024696747943', array('maestro'))); + $this->assertTrue(Validation::cc('5020581514636509', array('maestro'))); + $this->assertTrue(Validation::cc('5020695008411987', array('maestro'))); + $this->assertTrue(Validation::cc('5020565359718977', array('maestro'))); + $this->assertTrue(Validation::cc('6339931536544062', array('maestro'))); + $this->assertTrue(Validation::cc('6465028615704406', array('maestro'))); + //Mastercard + $this->assertTrue(Validation::cc('5580424361774366', array('mc'))); + $this->assertTrue(Validation::cc('5589563059318282', array('mc'))); + $this->assertTrue(Validation::cc('5387558333690047', array('mc'))); + $this->assertTrue(Validation::cc('5163919215247175', array('mc'))); + $this->assertTrue(Validation::cc('5386742685055055', array('mc'))); + $this->assertTrue(Validation::cc('5102303335960674', array('mc'))); + $this->assertTrue(Validation::cc('5526543403964565', array('mc'))); + $this->assertTrue(Validation::cc('5538725892618432', array('mc'))); + $this->assertTrue(Validation::cc('5119543573129778', array('mc'))); + $this->assertTrue(Validation::cc('5391174753915767', array('mc'))); + $this->assertTrue(Validation::cc('5510994113980714', array('mc'))); + $this->assertTrue(Validation::cc('5183720260418091', array('mc'))); + $this->assertTrue(Validation::cc('5488082196086704', array('mc'))); + $this->assertTrue(Validation::cc('5484645164161834', array('mc'))); + $this->assertTrue(Validation::cc('5171254350337031', array('mc'))); + $this->assertTrue(Validation::cc('5526987528136452', array('mc'))); + $this->assertTrue(Validation::cc('5504148941409358', array('mc'))); + $this->assertTrue(Validation::cc('5240793507243615', array('mc'))); + $this->assertTrue(Validation::cc('5162114693017107', array('mc'))); + $this->assertTrue(Validation::cc('5163104807404753', array('mc'))); + $this->assertTrue(Validation::cc('5590136167248365', array('mc'))); + $this->assertTrue(Validation::cc('5565816281038948', array('mc'))); + $this->assertTrue(Validation::cc('5467639122779531', array('mc'))); + $this->assertTrue(Validation::cc('5297350261550024', array('mc'))); + $this->assertTrue(Validation::cc('5162739131368058', array('mc'))); + //Solo 16 + $this->assertTrue(Validation::cc('6767432107064987', array('solo'))); + $this->assertTrue(Validation::cc('6334667758225411', array('solo'))); + $this->assertTrue(Validation::cc('6767037421954068', array('solo'))); + $this->assertTrue(Validation::cc('6767823306394854', array('solo'))); + $this->assertTrue(Validation::cc('6334768185398134', array('solo'))); + $this->assertTrue(Validation::cc('6767286729498589', array('solo'))); + $this->assertTrue(Validation::cc('6334972104431261', array('solo'))); + $this->assertTrue(Validation::cc('6334843427400616', array('solo'))); + $this->assertTrue(Validation::cc('6767493947881311', array('solo'))); + $this->assertTrue(Validation::cc('6767194235798817', array('solo'))); + //Solo 18 + $this->assertTrue(Validation::cc('676714834398858593', array('solo'))); + $this->assertTrue(Validation::cc('676751666435130857', array('solo'))); + $this->assertTrue(Validation::cc('676781908573924236', array('solo'))); + $this->assertTrue(Validation::cc('633488724644003240', array('solo'))); + $this->assertTrue(Validation::cc('676732252338067316', array('solo'))); + $this->assertTrue(Validation::cc('676747520084495821', array('solo'))); + $this->assertTrue(Validation::cc('633465488901381957', array('solo'))); + $this->assertTrue(Validation::cc('633487484858610484', array('solo'))); + $this->assertTrue(Validation::cc('633453764680740694', array('solo'))); + $this->assertTrue(Validation::cc('676768613295414451', array('solo'))); + //Solo 19 + $this->assertTrue(Validation::cc('6767838565218340113', array('solo'))); + $this->assertTrue(Validation::cc('6767760119829705181', array('solo'))); + $this->assertTrue(Validation::cc('6767265917091593668', array('solo'))); + $this->assertTrue(Validation::cc('6767938856947440111', array('solo'))); + $this->assertTrue(Validation::cc('6767501945697390076', array('solo'))); + $this->assertTrue(Validation::cc('6334902868716257379', array('solo'))); + $this->assertTrue(Validation::cc('6334922127686425532', array('solo'))); + $this->assertTrue(Validation::cc('6334933119080706440', array('solo'))); + $this->assertTrue(Validation::cc('6334647959628261714', array('solo'))); + $this->assertTrue(Validation::cc('6334527312384101382', array('solo'))); + //Switch 16 + $this->assertTrue(Validation::cc('5641829171515733', array('switch'))); + $this->assertTrue(Validation::cc('5641824852820809', array('switch'))); + $this->assertTrue(Validation::cc('6759129648956909', array('switch'))); + $this->assertTrue(Validation::cc('6759626072268156', array('switch'))); + $this->assertTrue(Validation::cc('5641822698388957', array('switch'))); + $this->assertTrue(Validation::cc('5641827123105470', array('switch'))); + $this->assertTrue(Validation::cc('5641823755819553', array('switch'))); + $this->assertTrue(Validation::cc('5641821939587682', array('switch'))); + $this->assertTrue(Validation::cc('4936097148079186', array('switch'))); + $this->assertTrue(Validation::cc('5641829739125009', array('switch'))); + $this->assertTrue(Validation::cc('5641822860725507', array('switch'))); + $this->assertTrue(Validation::cc('4936717688865831', array('switch'))); + $this->assertTrue(Validation::cc('6759487613615441', array('switch'))); + $this->assertTrue(Validation::cc('5641821346840617', array('switch'))); + $this->assertTrue(Validation::cc('5641825793417126', array('switch'))); + $this->assertTrue(Validation::cc('5641821302759595', array('switch'))); + $this->assertTrue(Validation::cc('6759784969918837', array('switch'))); + $this->assertTrue(Validation::cc('5641824910667036', array('switch'))); + $this->assertTrue(Validation::cc('6759139909636173', array('switch'))); + $this->assertTrue(Validation::cc('6333425070638022', array('switch'))); + $this->assertTrue(Validation::cc('5641823910382067', array('switch'))); + $this->assertTrue(Validation::cc('4936295218139423', array('switch'))); + $this->assertTrue(Validation::cc('6333031811316199', array('switch'))); + $this->assertTrue(Validation::cc('4936912044763198', array('switch'))); + $this->assertTrue(Validation::cc('4936387053303824', array('switch'))); + $this->assertTrue(Validation::cc('6759535838760523', array('switch'))); + $this->assertTrue(Validation::cc('6333427174594051', array('switch'))); + $this->assertTrue(Validation::cc('5641829037102700', array('switch'))); + $this->assertTrue(Validation::cc('5641826495463046', array('switch'))); + $this->assertTrue(Validation::cc('6333480852979946', array('switch'))); + $this->assertTrue(Validation::cc('5641827761302876', array('switch'))); + $this->assertTrue(Validation::cc('5641825083505317', array('switch'))); + $this->assertTrue(Validation::cc('6759298096003991', array('switch'))); + $this->assertTrue(Validation::cc('4936119165483420', array('switch'))); + $this->assertTrue(Validation::cc('4936190990500993', array('switch'))); + $this->assertTrue(Validation::cc('4903356467384927', array('switch'))); + $this->assertTrue(Validation::cc('6333372765092554', array('switch'))); + $this->assertTrue(Validation::cc('5641821330950570', array('switch'))); + $this->assertTrue(Validation::cc('6759841558826118', array('switch'))); + $this->assertTrue(Validation::cc('4936164540922452', array('switch'))); + //Switch 18 + $this->assertTrue(Validation::cc('493622764224625174', array('switch'))); + $this->assertTrue(Validation::cc('564182823396913535', array('switch'))); + $this->assertTrue(Validation::cc('675917308304801234', array('switch'))); + $this->assertTrue(Validation::cc('675919890024220298', array('switch'))); + $this->assertTrue(Validation::cc('633308376862556751', array('switch'))); + $this->assertTrue(Validation::cc('564182377633208779', array('switch'))); + $this->assertTrue(Validation::cc('564182870014926787', array('switch'))); + $this->assertTrue(Validation::cc('675979788553829819', array('switch'))); + $this->assertTrue(Validation::cc('493668394358130935', array('switch'))); + $this->assertTrue(Validation::cc('493637431790930965', array('switch'))); + $this->assertTrue(Validation::cc('633321438601941513', array('switch'))); + $this->assertTrue(Validation::cc('675913800898840986', array('switch'))); + $this->assertTrue(Validation::cc('564182592016841547', array('switch'))); + $this->assertTrue(Validation::cc('564182428380440899', array('switch'))); + $this->assertTrue(Validation::cc('493696376827623463', array('switch'))); + $this->assertTrue(Validation::cc('675977939286485757', array('switch'))); + $this->assertTrue(Validation::cc('490302699502091579', array('switch'))); + $this->assertTrue(Validation::cc('564182085013662230', array('switch'))); + $this->assertTrue(Validation::cc('493693054263310167', array('switch'))); + $this->assertTrue(Validation::cc('633321755966697525', array('switch'))); + $this->assertTrue(Validation::cc('675996851719732811', array('switch'))); + $this->assertTrue(Validation::cc('493699211208281028', array('switch'))); + $this->assertTrue(Validation::cc('493697817378356614', array('switch'))); + $this->assertTrue(Validation::cc('675968224161768150', array('switch'))); + $this->assertTrue(Validation::cc('493669416873337627', array('switch'))); + $this->assertTrue(Validation::cc('564182439172549714', array('switch'))); + $this->assertTrue(Validation::cc('675926914467673598', array('switch'))); + $this->assertTrue(Validation::cc('564182565231977809', array('switch'))); + $this->assertTrue(Validation::cc('675966282607849002', array('switch'))); + $this->assertTrue(Validation::cc('493691609704348548', array('switch'))); + $this->assertTrue(Validation::cc('675933118546065120', array('switch'))); + $this->assertTrue(Validation::cc('493631116677238592', array('switch'))); + $this->assertTrue(Validation::cc('675921142812825938', array('switch'))); + $this->assertTrue(Validation::cc('633338311815675113', array('switch'))); + $this->assertTrue(Validation::cc('633323539867338621', array('switch'))); + $this->assertTrue(Validation::cc('675964912740845663', array('switch'))); + $this->assertTrue(Validation::cc('633334008833727504', array('switch'))); + $this->assertTrue(Validation::cc('493631941273687169', array('switch'))); + $this->assertTrue(Validation::cc('564182971729706785', array('switch'))); + $this->assertTrue(Validation::cc('633303461188963496', array('switch'))); + //Switch 19 + $this->assertTrue(Validation::cc('6759603460617628716', array('switch'))); + $this->assertTrue(Validation::cc('4936705825268647681', array('switch'))); + $this->assertTrue(Validation::cc('5641829846600479183', array('switch'))); + $this->assertTrue(Validation::cc('6759389846573792530', array('switch'))); + $this->assertTrue(Validation::cc('4936189558712637603', array('switch'))); + $this->assertTrue(Validation::cc('5641822217393868189', array('switch'))); + $this->assertTrue(Validation::cc('4903075563780057152', array('switch'))); + $this->assertTrue(Validation::cc('4936510653566569547', array('switch'))); + $this->assertTrue(Validation::cc('4936503083627303364', array('switch'))); + $this->assertTrue(Validation::cc('4936777334398116272', array('switch'))); + $this->assertTrue(Validation::cc('5641823876900554860', array('switch'))); + $this->assertTrue(Validation::cc('6759619236903407276', array('switch'))); + $this->assertTrue(Validation::cc('6759011470269978117', array('switch'))); + $this->assertTrue(Validation::cc('6333175833997062502', array('switch'))); + $this->assertTrue(Validation::cc('6759498728789080439', array('switch'))); + $this->assertTrue(Validation::cc('4903020404168157841', array('switch'))); + $this->assertTrue(Validation::cc('6759354334874804313', array('switch'))); + $this->assertTrue(Validation::cc('6759900856420875115', array('switch'))); + $this->assertTrue(Validation::cc('5641827269346868860', array('switch'))); + $this->assertTrue(Validation::cc('5641828995047453870', array('switch'))); + $this->assertTrue(Validation::cc('6333321884754806543', array('switch'))); + $this->assertTrue(Validation::cc('6333108246283715901', array('switch'))); + $this->assertTrue(Validation::cc('6759572372800700102', array('switch'))); + $this->assertTrue(Validation::cc('4903095096797974933', array('switch'))); + $this->assertTrue(Validation::cc('6333354315797920215', array('switch'))); + $this->assertTrue(Validation::cc('6759163746089433755', array('switch'))); + $this->assertTrue(Validation::cc('6759871666634807647', array('switch'))); + $this->assertTrue(Validation::cc('5641827883728575248', array('switch'))); + $this->assertTrue(Validation::cc('4936527975051407847', array('switch'))); + $this->assertTrue(Validation::cc('5641823318396882141', array('switch'))); + $this->assertTrue(Validation::cc('6759123772311123708', array('switch'))); + $this->assertTrue(Validation::cc('4903054736148271088', array('switch'))); + $this->assertTrue(Validation::cc('4936477526808883952', array('switch'))); + $this->assertTrue(Validation::cc('4936433964890967966', array('switch'))); + $this->assertTrue(Validation::cc('6333245128906049344', array('switch'))); + $this->assertTrue(Validation::cc('4936321036970553134', array('switch'))); + $this->assertTrue(Validation::cc('4936111816358702773', array('switch'))); + $this->assertTrue(Validation::cc('4936196077254804290', array('switch'))); + $this->assertTrue(Validation::cc('6759558831206830183', array('switch'))); + $this->assertTrue(Validation::cc('5641827998830403137', array('switch'))); + //VISA 13 digit + $this->assertTrue(Validation::cc('4024007174754', array('visa'))); + $this->assertTrue(Validation::cc('4104816460717', array('visa'))); + $this->assertTrue(Validation::cc('4716229700437', array('visa'))); + $this->assertTrue(Validation::cc('4539305400213', array('visa'))); + $this->assertTrue(Validation::cc('4728260558665', array('visa'))); + $this->assertTrue(Validation::cc('4929100131792', array('visa'))); + $this->assertTrue(Validation::cc('4024007117308', array('visa'))); + $this->assertTrue(Validation::cc('4539915491024', array('visa'))); + $this->assertTrue(Validation::cc('4539790901139', array('visa'))); + $this->assertTrue(Validation::cc('4485284914909', array('visa'))); + $this->assertTrue(Validation::cc('4782793022350', array('visa'))); + $this->assertTrue(Validation::cc('4556899290685', array('visa'))); + $this->assertTrue(Validation::cc('4024007134774', array('visa'))); + $this->assertTrue(Validation::cc('4333412341316', array('visa'))); + $this->assertTrue(Validation::cc('4539534204543', array('visa'))); + $this->assertTrue(Validation::cc('4485640373626', array('visa'))); + $this->assertTrue(Validation::cc('4929911445746', array('visa'))); + $this->assertTrue(Validation::cc('4539292550806', array('visa'))); + $this->assertTrue(Validation::cc('4716523014030', array('visa'))); + $this->assertTrue(Validation::cc('4024007125152', array('visa'))); + $this->assertTrue(Validation::cc('4539758883311', array('visa'))); + $this->assertTrue(Validation::cc('4024007103258', array('visa'))); + $this->assertTrue(Validation::cc('4916933155767', array('visa'))); + $this->assertTrue(Validation::cc('4024007159672', array('visa'))); + $this->assertTrue(Validation::cc('4716935544871', array('visa'))); + $this->assertTrue(Validation::cc('4929415177779', array('visa'))); + $this->assertTrue(Validation::cc('4929748547896', array('visa'))); + $this->assertTrue(Validation::cc('4929153468612', array('visa'))); + $this->assertTrue(Validation::cc('4539397132104', array('visa'))); + $this->assertTrue(Validation::cc('4485293435540', array('visa'))); + $this->assertTrue(Validation::cc('4485799412720', array('visa'))); + $this->assertTrue(Validation::cc('4916744757686', array('visa'))); + $this->assertTrue(Validation::cc('4556475655426', array('visa'))); + $this->assertTrue(Validation::cc('4539400441625', array('visa'))); + $this->assertTrue(Validation::cc('4485437129173', array('visa'))); + $this->assertTrue(Validation::cc('4716253605320', array('visa'))); + $this->assertTrue(Validation::cc('4539366156589', array('visa'))); + $this->assertTrue(Validation::cc('4916498061392', array('visa'))); + $this->assertTrue(Validation::cc('4716127163779', array('visa'))); + $this->assertTrue(Validation::cc('4024007183078', array('visa'))); + $this->assertTrue(Validation::cc('4041553279654', array('visa'))); + $this->assertTrue(Validation::cc('4532380121960', array('visa'))); + $this->assertTrue(Validation::cc('4485906062491', array('visa'))); + $this->assertTrue(Validation::cc('4539365115149', array('visa'))); + $this->assertTrue(Validation::cc('4485146516702', array('visa'))); + //VISA 16 digit + $this->assertTrue(Validation::cc('4916375389940009', array('visa'))); + $this->assertTrue(Validation::cc('4929167481032610', array('visa'))); + $this->assertTrue(Validation::cc('4485029969061519', array('visa'))); + $this->assertTrue(Validation::cc('4485573845281759', array('visa'))); + $this->assertTrue(Validation::cc('4485669810383529', array('visa'))); + $this->assertTrue(Validation::cc('4929615806560327', array('visa'))); + $this->assertTrue(Validation::cc('4556807505609535', array('visa'))); + $this->assertTrue(Validation::cc('4532611336232890', array('visa'))); + $this->assertTrue(Validation::cc('4532201952422387', array('visa'))); + $this->assertTrue(Validation::cc('4485073797976290', array('visa'))); + $this->assertTrue(Validation::cc('4024007157580969', array('visa'))); + $this->assertTrue(Validation::cc('4053740470212274', array('visa'))); + $this->assertTrue(Validation::cc('4716265831525676', array('visa'))); + $this->assertTrue(Validation::cc('4024007100222966', array('visa'))); + $this->assertTrue(Validation::cc('4539556148303244', array('visa'))); + $this->assertTrue(Validation::cc('4532449879689709', array('visa'))); + $this->assertTrue(Validation::cc('4916805467840986', array('visa'))); + $this->assertTrue(Validation::cc('4532155644440233', array('visa'))); + $this->assertTrue(Validation::cc('4467977802223781', array('visa'))); + $this->assertTrue(Validation::cc('4539224637000686', array('visa'))); + $this->assertTrue(Validation::cc('4556629187064965', array('visa'))); + $this->assertTrue(Validation::cc('4532970205932943', array('visa'))); + $this->assertTrue(Validation::cc('4821470132041850', array('visa'))); + $this->assertTrue(Validation::cc('4916214267894485', array('visa'))); + $this->assertTrue(Validation::cc('4024007169073284', array('visa'))); + $this->assertTrue(Validation::cc('4716783351296122', array('visa'))); + $this->assertTrue(Validation::cc('4556480171913795', array('visa'))); + $this->assertTrue(Validation::cc('4929678411034997', array('visa'))); + $this->assertTrue(Validation::cc('4682061913519392', array('visa'))); + $this->assertTrue(Validation::cc('4916495481746474', array('visa'))); + $this->assertTrue(Validation::cc('4929007108460499', array('visa'))); + $this->assertTrue(Validation::cc('4539951357838586', array('visa'))); + $this->assertTrue(Validation::cc('4716482691051558', array('visa'))); + $this->assertTrue(Validation::cc('4916385069917516', array('visa'))); + $this->assertTrue(Validation::cc('4929020289494641', array('visa'))); + $this->assertTrue(Validation::cc('4532176245263774', array('visa'))); + $this->assertTrue(Validation::cc('4556242273553949', array('visa'))); + $this->assertTrue(Validation::cc('4481007485188614', array('visa'))); + $this->assertTrue(Validation::cc('4716533372139623', array('visa'))); + $this->assertTrue(Validation::cc('4929152038152632', array('visa'))); + $this->assertTrue(Validation::cc('4539404037310550', array('visa'))); + $this->assertTrue(Validation::cc('4532800925229140', array('visa'))); + $this->assertTrue(Validation::cc('4916845885268360', array('visa'))); + $this->assertTrue(Validation::cc('4394514669078434', array('visa'))); + $this->assertTrue(Validation::cc('4485611378115042', array('visa'))); + //Visa Electron + $this->assertTrue(Validation::cc('4175003346287100', array('electron'))); + $this->assertTrue(Validation::cc('4913042516577228', array('electron'))); + $this->assertTrue(Validation::cc('4917592325659381', array('electron'))); + $this->assertTrue(Validation::cc('4917084924450511', array('electron'))); + $this->assertTrue(Validation::cc('4917994610643999', array('electron'))); + $this->assertTrue(Validation::cc('4175005933743585', array('electron'))); + $this->assertTrue(Validation::cc('4175008373425044', array('electron'))); + $this->assertTrue(Validation::cc('4913119763664154', array('electron'))); + $this->assertTrue(Validation::cc('4913189017481812', array('electron'))); + $this->assertTrue(Validation::cc('4913085104968622', array('electron'))); + $this->assertTrue(Validation::cc('4175008803122021', array('electron'))); + $this->assertTrue(Validation::cc('4913294453962489', array('electron'))); + $this->assertTrue(Validation::cc('4175009797419290', array('electron'))); + $this->assertTrue(Validation::cc('4175005028142917', array('electron'))); + $this->assertTrue(Validation::cc('4913940802385364', array('electron'))); + //Voyager + $this->assertTrue(Validation::cc('869940697287073', array('voyager'))); + $this->assertTrue(Validation::cc('869934523596112', array('voyager'))); + $this->assertTrue(Validation::cc('869958670174621', array('voyager'))); + $this->assertTrue(Validation::cc('869921250068209', array('voyager'))); + $this->assertTrue(Validation::cc('869972521242198', array('voyager'))); + } + +/** + * testLuhn method + * + * @access public + * @return void + */ + function testLuhn() { + $this->Validation->deep = true; + + //American Express + $this->Validation->check = '370482756063980'; + $this->assertTrue($this->Validation->_luhn()); + //BankCard + $this->Validation->check = '5610745867413420'; + $this->assertTrue($this->Validation->_luhn()); + //Diners Club 14 + $this->Validation->check = '30155483651028'; + $this->assertTrue($this->Validation->_luhn()); + //2004 MasterCard/Diners Club Alliance International 14 + $this->Validation->check = '36747701998969'; + $this->assertTrue($this->Validation->_luhn()); + //2004 MasterCard/Diners Club Alliance US & Canada 16 + $this->Validation->check = '5597511346169950'; + $this->assertTrue($this->Validation->_luhn()); + //Discover + $this->Validation->check = '6011802876467237'; + $this->assertTrue($this->Validation->_luhn()); + //enRoute + $this->Validation->check = '201496944158937'; + $this->assertTrue($this->Validation->_luhn()); + //JCB 15 digit + $this->Validation->check = '210034762247893'; + $this->assertTrue($this->Validation->_luhn()); + //JCB 16 digit + $this->Validation->check = '3096806857839939'; + $this->assertTrue($this->Validation->_luhn()); + //Maestro (debit card) + $this->Validation->check = '5020147409985219'; + $this->assertTrue($this->Validation->_luhn()); + //Mastercard + $this->Validation->check = '5580424361774366'; + $this->assertTrue($this->Validation->_luhn()); + //Solo 16 + $this->Validation->check = '6767432107064987'; + $this->assertTrue($this->Validation->_luhn()); + //Solo 18 + $this->Validation->check = '676714834398858593'; + $this->assertTrue($this->Validation->_luhn()); + //Solo 19 + $this->Validation->check = '6767838565218340113'; + $this->assertTrue($this->Validation->_luhn()); + //Switch 16 + $this->Validation->check = '5641829171515733'; + $this->assertTrue($this->Validation->_luhn()); + //Switch 18 + $this->Validation->check = '493622764224625174'; + $this->assertTrue($this->Validation->_luhn()); + //Switch 19 + $this->Validation->check = '6759603460617628716'; + $this->assertTrue($this->Validation->_luhn()); + //VISA 13 digit + $this->Validation->check = '4024007174754'; + $this->assertTrue($this->Validation->_luhn()); + //VISA 16 digit + $this->Validation->check = '4916375389940009'; + $this->assertTrue($this->Validation->_luhn()); + //Visa Electron + $this->Validation->check = '4175003346287100'; + $this->assertTrue($this->Validation->_luhn()); + //Voyager + $this->Validation->check = '869940697287073'; + $this->assertTrue($this->Validation->_luhn()); + + $this->Validation->check = '0000000000000000'; + $this->assertFalse($this->Validation->_luhn()); + + $this->Validation->check = '869940697287173'; + $this->assertFalse($this->Validation->_luhn()); + } + +/** + * testCustomRegexForCc method + * + * @access public + * @return void + */ + function testCustomRegexForCc() { + $this->assertTrue(Validation::cc('12332105933743585', null, null, '/123321\\d{11}/')); + $this->assertFalse(Validation::cc('1233210593374358', null, null, '/123321\\d{11}/')); + $this->assertFalse(Validation::cc('12312305933743585', null, null, '/123321\\d{11}/')); + } + +/** + * testCustomRegexForCcWithLuhnCheck method + * + * @access public + * @return void + */ + function testCustomRegexForCcWithLuhnCheck() { + $this->assertTrue(Validation::cc('12332110426226941', null, true, '/123321\\d{11}/')); + $this->assertFalse(Validation::cc('12332105933743585', null, true, '/123321\\d{11}/')); + $this->assertFalse(Validation::cc('12332105933743587', null, true, '/123321\\d{11}/')); + $this->assertFalse(Validation::cc('12312305933743585', null, true, '/123321\\d{11}/')); + } + +/** + * testFastCc method + * + * @access public + * @return void + */ + function testFastCc() { + // too short + $this->assertFalse(Validation::cc('123456789012')); + //American Express + $this->assertTrue(Validation::cc('370482756063980')); + //Diners Club 14 + $this->assertTrue(Validation::cc('30155483651028')); + //2004 MasterCard/Diners Club Alliance International 14 + $this->assertTrue(Validation::cc('36747701998969')); + //2004 MasterCard/Diners Club Alliance US & Canada 16 + $this->assertTrue(Validation::cc('5597511346169950')); + //Discover + $this->assertTrue(Validation::cc('6011802876467237')); + //Mastercard + $this->assertTrue(Validation::cc('5580424361774366')); + //VISA 13 digit + $this->assertTrue(Validation::cc('4024007174754')); + //VISA 16 digit + $this->assertTrue(Validation::cc('4916375389940009')); + //Visa Electron + $this->assertTrue(Validation::cc('4175003346287100')); + } + +/** + * testAllCc method + * + * @access public + * @return void + */ + function testAllCc() { + //American Express + $this->assertTrue(Validation::cc('370482756063980', 'all')); + //BankCard + $this->assertTrue(Validation::cc('5610745867413420', 'all')); + //Diners Club 14 + $this->assertTrue(Validation::cc('30155483651028', 'all')); + //2004 MasterCard/Diners Club Alliance International 14 + $this->assertTrue(Validation::cc('36747701998969', 'all')); + //2004 MasterCard/Diners Club Alliance US & Canada 16 + $this->assertTrue(Validation::cc('5597511346169950', 'all')); + //Discover + $this->assertTrue(Validation::cc('6011802876467237', 'all')); + //enRoute + $this->assertTrue(Validation::cc('201496944158937', 'all')); + //JCB 15 digit + $this->assertTrue(Validation::cc('210034762247893', 'all')); + //JCB 16 digit + $this->assertTrue(Validation::cc('3096806857839939', 'all')); + //Maestro (debit card) + $this->assertTrue(Validation::cc('5020147409985219', 'all')); + //Mastercard + $this->assertTrue(Validation::cc('5580424361774366', 'all')); + //Solo 16 + $this->assertTrue(Validation::cc('6767432107064987', 'all')); + //Solo 18 + $this->assertTrue(Validation::cc('676714834398858593', 'all')); + //Solo 19 + $this->assertTrue(Validation::cc('6767838565218340113', 'all')); + //Switch 16 + $this->assertTrue(Validation::cc('5641829171515733', 'all')); + //Switch 18 + $this->assertTrue(Validation::cc('493622764224625174', 'all')); + //Switch 19 + $this->assertTrue(Validation::cc('6759603460617628716', 'all')); + //VISA 13 digit + $this->assertTrue(Validation::cc('4024007174754', 'all')); + //VISA 16 digit + $this->assertTrue(Validation::cc('4916375389940009', 'all')); + //Visa Electron + $this->assertTrue(Validation::cc('4175003346287100', 'all')); + //Voyager + $this->assertTrue(Validation::cc('869940697287073', 'all')); + } + +/** + * testAllCcDeep method + * + * @access public + * @return void + */ + function testAllCcDeep() { + //American Express + $this->assertTrue(Validation::cc('370482756063980', 'all', true)); + //BankCard + $this->assertTrue(Validation::cc('5610745867413420', 'all', true)); + //Diners Club 14 + $this->assertTrue(Validation::cc('30155483651028', 'all', true)); + //2004 MasterCard/Diners Club Alliance International 14 + $this->assertTrue(Validation::cc('36747701998969', 'all', true)); + //2004 MasterCard/Diners Club Alliance US & Canada 16 + $this->assertTrue(Validation::cc('5597511346169950', 'all', true)); + //Discover + $this->assertTrue(Validation::cc('6011802876467237', 'all', true)); + //enRoute + $this->assertTrue(Validation::cc('201496944158937', 'all', true)); + //JCB 15 digit + $this->assertTrue(Validation::cc('210034762247893', 'all', true)); + //JCB 16 digit + $this->assertTrue(Validation::cc('3096806857839939', 'all', true)); + //Maestro (debit card) + $this->assertTrue(Validation::cc('5020147409985219', 'all', true)); + //Mastercard + $this->assertTrue(Validation::cc('5580424361774366', 'all', true)); + //Solo 16 + $this->assertTrue(Validation::cc('6767432107064987', 'all', true)); + //Solo 18 + $this->assertTrue(Validation::cc('676714834398858593', 'all', true)); + //Solo 19 + $this->assertTrue(Validation::cc('6767838565218340113', 'all', true)); + //Switch 16 + $this->assertTrue(Validation::cc('5641829171515733', 'all', true)); + //Switch 18 + $this->assertTrue(Validation::cc('493622764224625174', 'all', true)); + //Switch 19 + $this->assertTrue(Validation::cc('6759603460617628716', 'all', true)); + //VISA 13 digit + $this->assertTrue(Validation::cc('4024007174754', 'all', true)); + //VISA 16 digit + $this->assertTrue(Validation::cc('4916375389940009', 'all', true)); + //Visa Electron + $this->assertTrue(Validation::cc('4175003346287100', 'all', true)); + //Voyager + $this->assertTrue(Validation::cc('869940697287073', 'all', true)); + } + +/** + * testComparison method + * + * @access public + * @return void + */ + function testComparison() { + $this->assertFalse(Validation::comparison(7, null, 6)); + $this->assertTrue(Validation::comparison(7, 'is greater', 6)); + $this->assertTrue(Validation::comparison(7, '>', 6)); + $this->assertTrue(Validation::comparison(6, 'is less', 7)); + $this->assertTrue(Validation::comparison(6, '<', 7)); + $this->assertTrue(Validation::comparison(7, 'greater or equal', 7)); + $this->assertTrue(Validation::comparison(7, '>=', 7)); + $this->assertTrue(Validation::comparison(7, 'greater or equal', 6)); + $this->assertTrue(Validation::comparison(7, '>=', 6)); + $this->assertTrue(Validation::comparison(6, 'less or equal', 7)); + $this->assertTrue(Validation::comparison(6, '<=', 7)); + $this->assertTrue(Validation::comparison(7, 'equal to', 7)); + $this->assertTrue(Validation::comparison(7, '==', 7)); + $this->assertTrue(Validation::comparison(7, 'not equal', 6)); + $this->assertTrue(Validation::comparison(7, '!=', 6)); + $this->assertFalse(Validation::comparison(6, 'is greater', 7)); + $this->assertFalse(Validation::comparison(6, '>', 7)); + $this->assertFalse(Validation::comparison(7, 'is less', 6)); + $this->assertFalse(Validation::comparison(7, '<', 6)); + $this->assertFalse(Validation::comparison(6, 'greater or equal', 7)); + $this->assertFalse(Validation::comparison(6, '>=', 7)); + $this->assertFalse(Validation::comparison(6, 'greater or equal', 7)); + $this->assertFalse(Validation::comparison(6, '>=', 7)); + $this->assertFalse(Validation::comparison(7, 'less or equal', 6)); + $this->assertFalse(Validation::comparison(7, '<=', 6)); + $this->assertFalse(Validation::comparison(7, 'equal to', 6)); + $this->assertFalse(Validation::comparison(7, '==', 6)); + $this->assertFalse(Validation::comparison(7, 'not equal', 7)); + $this->assertFalse(Validation::comparison(7, '!=', 7)); + } + +/** + * testComparisonAsArray method + * + * @access public + * @return void + */ + function testComparisonAsArray() { + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => 'is greater', 'check2' => 6))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => '>', 'check2' => 6))); + $this->assertTrue(Validation::comparison(array('check1' => 6, 'operator' => 'is less', 'check2' => 7))); + $this->assertTrue(Validation::comparison(array('check1' => 6, 'operator' => '<', 'check2' => 7))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => 'greater or equal', 'check2' => 7))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => '>=', 'check2' => 7))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => 'greater or equal','check2' => 6))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => '>=', 'check2' => 6))); + $this->assertTrue(Validation::comparison(array('check1' => 6, 'operator' => 'less or equal', 'check2' => 7))); + $this->assertTrue(Validation::comparison(array('check1' => 6, 'operator' => '<=', 'check2' => 7))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => 'equal to', 'check2' => 7))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => '==', 'check2' => 7))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => 'not equal', 'check2' => 6))); + $this->assertTrue(Validation::comparison(array('check1' => 7, 'operator' => '!=', 'check2' => 6))); + $this->assertFalse(Validation::comparison(array('check1' => 6, 'operator' => 'is greater', 'check2' => 7))); + $this->assertFalse(Validation::comparison(array('check1' => 6, 'operator' => '>', 'check2' => 7))); + $this->assertFalse(Validation::comparison(array('check1' => 7, 'operator' => 'is less', 'check2' => 6))); + $this->assertFalse(Validation::comparison(array('check1' => 7, 'operator' => '<', 'check2' => 6))); + $this->assertFalse(Validation::comparison(array('check1' => 6, 'operator' => 'greater or equal', 'check2' => 7))); + $this->assertFalse(Validation::comparison(array('check1' => 6, 'operator' => '>=', 'check2' => 7))); + $this->assertFalse(Validation::comparison(array('check1' => 6, 'operator' => 'greater or equal', 'check2' => 7))); + $this->assertFalse(Validation::comparison(array('check1' => 6, 'operator' => '>=', 'check2' => 7))); + $this->assertFalse(Validation::comparison(array('check1' => 7, 'operator' => 'less or equal', 'check2' => 6))); + $this->assertFalse(Validation::comparison(array('check1' => 7, 'operator' => '<=', 'check2' => 6))); + $this->assertFalse(Validation::comparison(array('check1' => 7, 'operator' => 'equal to', 'check2' => 6))); + $this->assertFalse(Validation::comparison(array('check1' => 7, 'operator' => '==','check2' => 6))); + $this->assertFalse(Validation::comparison(array('check1' => 7, 'operator' => 'not equal', 'check2' => 7))); + $this->assertFalse(Validation::comparison(array('check1' => 7, 'operator' => '!=', 'check2' => 7))); + } + +/** + * testCustom method + * + * @access public + * @return void + */ + function testCustom() { + $this->assertTrue(Validation::custom('12345', '/(?assertFalse(Validation::custom('Text', '/(?assertFalse(Validation::custom('123.45', '/(?assertFalse(Validation::custom('missing regex')); + } + +/** + * testCustomAsArray method + * + * @access public + * @return void + */ + function testCustomAsArray() { + $this->assertTrue(Validation::custom(array('check' => '12345', 'regex' => '/(?assertFalse(Validation::custom(array('check' => 'Text', 'regex' => '/(?assertFalse(Validation::custom(array('check' => '123.45', 'regex' => '/(?assertTrue(Validation::date('27-12-2006', array('dmy'))); + $this->assertTrue(Validation::date('27.12.2006', array('dmy'))); + $this->assertTrue(Validation::date('27/12/2006', array('dmy'))); + $this->assertTrue(Validation::date('27 12 2006', array('dmy'))); + $this->assertFalse(Validation::date('00-00-0000', array('dmy'))); + $this->assertFalse(Validation::date('00.00.0000', array('dmy'))); + $this->assertFalse(Validation::date('00/00/0000', array('dmy'))); + $this->assertFalse(Validation::date('00 00 0000', array('dmy'))); + $this->assertFalse(Validation::date('31-11-2006', array('dmy'))); + $this->assertFalse(Validation::date('31.11.2006', array('dmy'))); + $this->assertFalse(Validation::date('31/11/2006', array('dmy'))); + $this->assertFalse(Validation::date('31 11 2006', array('dmy'))); + } + +/** + * testDateDdmmyyyyLeapYear method + * + * @access public + * @return void + */ + function testDateDdmmyyyyLeapYear() { + $this->assertTrue(Validation::date('29-02-2004', array('dmy'))); + $this->assertTrue(Validation::date('29.02.2004', array('dmy'))); + $this->assertTrue(Validation::date('29/02/2004', array('dmy'))); + $this->assertTrue(Validation::date('29 02 2004', array('dmy'))); + $this->assertFalse(Validation::date('29-02-2006', array('dmy'))); + $this->assertFalse(Validation::date('29.02.2006', array('dmy'))); + $this->assertFalse(Validation::date('29/02/2006', array('dmy'))); + $this->assertFalse(Validation::date('29 02 2006', array('dmy'))); + } + +/** + * testDateDdmmyy method + * + * @access public + * @return void + */ + function testDateDdmmyy() { + $this->assertTrue(Validation::date('27-12-06', array('dmy'))); + $this->assertTrue(Validation::date('27.12.06', array('dmy'))); + $this->assertTrue(Validation::date('27/12/06', array('dmy'))); + $this->assertTrue(Validation::date('27 12 06', array('dmy'))); + $this->assertFalse(Validation::date('00-00-00', array('dmy'))); + $this->assertFalse(Validation::date('00.00.00', array('dmy'))); + $this->assertFalse(Validation::date('00/00/00', array('dmy'))); + $this->assertFalse(Validation::date('00 00 00', array('dmy'))); + $this->assertFalse(Validation::date('31-11-06', array('dmy'))); + $this->assertFalse(Validation::date('31.11.06', array('dmy'))); + $this->assertFalse(Validation::date('31/11/06', array('dmy'))); + $this->assertFalse(Validation::date('31 11 06', array('dmy'))); + } + +/** + * testDateDdmmyyLeapYear method + * + * @access public + * @return void + */ + function testDateDdmmyyLeapYear() { + $this->assertTrue(Validation::date('29-02-04', array('dmy'))); + $this->assertTrue(Validation::date('29.02.04', array('dmy'))); + $this->assertTrue(Validation::date('29/02/04', array('dmy'))); + $this->assertTrue(Validation::date('29 02 04', array('dmy'))); + $this->assertFalse(Validation::date('29-02-06', array('dmy'))); + $this->assertFalse(Validation::date('29.02.06', array('dmy'))); + $this->assertFalse(Validation::date('29/02/06', array('dmy'))); + $this->assertFalse(Validation::date('29 02 06', array('dmy'))); + } + +/** + * testDateDmyy method + * + * @access public + * @return void + */ + function testDateDmyy() { + $this->assertTrue(Validation::date('7-2-06', array('dmy'))); + $this->assertTrue(Validation::date('7.2.06', array('dmy'))); + $this->assertTrue(Validation::date('7/2/06', array('dmy'))); + $this->assertTrue(Validation::date('7 2 06', array('dmy'))); + $this->assertFalse(Validation::date('0-0-00', array('dmy'))); + $this->assertFalse(Validation::date('0.0.00', array('dmy'))); + $this->assertFalse(Validation::date('0/0/00', array('dmy'))); + $this->assertFalse(Validation::date('0 0 00', array('dmy'))); + $this->assertFalse(Validation::date('32-2-06', array('dmy'))); + $this->assertFalse(Validation::date('32.2.06', array('dmy'))); + $this->assertFalse(Validation::date('32/2/06', array('dmy'))); + $this->assertFalse(Validation::date('32 2 06', array('dmy'))); + } + +/** + * testDateDmyyLeapYear method + * + * @access public + * @return void + */ + function testDateDmyyLeapYear() { + $this->assertTrue(Validation::date('29-2-04', array('dmy'))); + $this->assertTrue(Validation::date('29.2.04', array('dmy'))); + $this->assertTrue(Validation::date('29/2/04', array('dmy'))); + $this->assertTrue(Validation::date('29 2 04', array('dmy'))); + $this->assertFalse(Validation::date('29-2-06', array('dmy'))); + $this->assertFalse(Validation::date('29.2.06', array('dmy'))); + $this->assertFalse(Validation::date('29/2/06', array('dmy'))); + $this->assertFalse(Validation::date('29 2 06', array('dmy'))); + } + +/** + * testDateDmyyyy method + * + * @access public + * @return void + */ + function testDateDmyyyy() { + $this->assertTrue(Validation::date('7-2-2006', array('dmy'))); + $this->assertTrue(Validation::date('7.2.2006', array('dmy'))); + $this->assertTrue(Validation::date('7/2/2006', array('dmy'))); + $this->assertTrue(Validation::date('7 2 2006', array('dmy'))); + $this->assertFalse(Validation::date('0-0-0000', array('dmy'))); + $this->assertFalse(Validation::date('0.0.0000', array('dmy'))); + $this->assertFalse(Validation::date('0/0/0000', array('dmy'))); + $this->assertFalse(Validation::date('0 0 0000', array('dmy'))); + $this->assertFalse(Validation::date('32-2-2006', array('dmy'))); + $this->assertFalse(Validation::date('32.2.2006', array('dmy'))); + $this->assertFalse(Validation::date('32/2/2006', array('dmy'))); + $this->assertFalse(Validation::date('32 2 2006', array('dmy'))); + } + +/** + * testDateDmyyyyLeapYear method + * + * @access public + * @return void + */ + function testDateDmyyyyLeapYear() { + $this->assertTrue(Validation::date('29-2-2004', array('dmy'))); + $this->assertTrue(Validation::date('29.2.2004', array('dmy'))); + $this->assertTrue(Validation::date('29/2/2004', array('dmy'))); + $this->assertTrue(Validation::date('29 2 2004', array('dmy'))); + $this->assertFalse(Validation::date('29-2-2006', array('dmy'))); + $this->assertFalse(Validation::date('29.2.2006', array('dmy'))); + $this->assertFalse(Validation::date('29/2/2006', array('dmy'))); + $this->assertFalse(Validation::date('29 2 2006', array('dmy'))); + } + +/** + * testDateMmddyyyy method + * + * @access public + * @return void + */ + function testDateMmddyyyy() { + $this->assertTrue(Validation::date('12-27-2006', array('mdy'))); + $this->assertTrue(Validation::date('12.27.2006', array('mdy'))); + $this->assertTrue(Validation::date('12/27/2006', array('mdy'))); + $this->assertTrue(Validation::date('12 27 2006', array('mdy'))); + $this->assertFalse(Validation::date('00-00-0000', array('mdy'))); + $this->assertFalse(Validation::date('00.00.0000', array('mdy'))); + $this->assertFalse(Validation::date('00/00/0000', array('mdy'))); + $this->assertFalse(Validation::date('00 00 0000', array('mdy'))); + $this->assertFalse(Validation::date('11-31-2006', array('mdy'))); + $this->assertFalse(Validation::date('11.31.2006', array('mdy'))); + $this->assertFalse(Validation::date('11/31/2006', array('mdy'))); + $this->assertFalse(Validation::date('11 31 2006', array('mdy'))); + } + +/** + * testDateMmddyyyyLeapYear method + * + * @access public + * @return void + */ + function testDateMmddyyyyLeapYear() { + $this->assertTrue(Validation::date('02-29-2004', array('mdy'))); + $this->assertTrue(Validation::date('02.29.2004', array('mdy'))); + $this->assertTrue(Validation::date('02/29/2004', array('mdy'))); + $this->assertTrue(Validation::date('02 29 2004', array('mdy'))); + $this->assertFalse(Validation::date('02-29-2006', array('mdy'))); + $this->assertFalse(Validation::date('02.29.2006', array('mdy'))); + $this->assertFalse(Validation::date('02/29/2006', array('mdy'))); + $this->assertFalse(Validation::date('02 29 2006', array('mdy'))); + } + +/** + * testDateMmddyy method + * + * @access public + * @return void + */ + function testDateMmddyy() { + $this->assertTrue(Validation::date('12-27-06', array('mdy'))); + $this->assertTrue(Validation::date('12.27.06', array('mdy'))); + $this->assertTrue(Validation::date('12/27/06', array('mdy'))); + $this->assertTrue(Validation::date('12 27 06', array('mdy'))); + $this->assertFalse(Validation::date('00-00-00', array('mdy'))); + $this->assertFalse(Validation::date('00.00.00', array('mdy'))); + $this->assertFalse(Validation::date('00/00/00', array('mdy'))); + $this->assertFalse(Validation::date('00 00 00', array('mdy'))); + $this->assertFalse(Validation::date('11-31-06', array('mdy'))); + $this->assertFalse(Validation::date('11.31.06', array('mdy'))); + $this->assertFalse(Validation::date('11/31/06', array('mdy'))); + $this->assertFalse(Validation::date('11 31 06', array('mdy'))); + } + +/** + * testDateMmddyyLeapYear method + * + * @access public + * @return void + */ + function testDateMmddyyLeapYear() { + $this->assertTrue(Validation::date('02-29-04', array('mdy'))); + $this->assertTrue(Validation::date('02.29.04', array('mdy'))); + $this->assertTrue(Validation::date('02/29/04', array('mdy'))); + $this->assertTrue(Validation::date('02 29 04', array('mdy'))); + $this->assertFalse(Validation::date('02-29-06', array('mdy'))); + $this->assertFalse(Validation::date('02.29.06', array('mdy'))); + $this->assertFalse(Validation::date('02/29/06', array('mdy'))); + $this->assertFalse(Validation::date('02 29 06', array('mdy'))); + } + +/** + * testDateMdyy method + * + * @access public + * @return void + */ + function testDateMdyy() { + $this->assertTrue(Validation::date('2-7-06', array('mdy'))); + $this->assertTrue(Validation::date('2.7.06', array('mdy'))); + $this->assertTrue(Validation::date('2/7/06', array('mdy'))); + $this->assertTrue(Validation::date('2 7 06', array('mdy'))); + $this->assertFalse(Validation::date('0-0-00', array('mdy'))); + $this->assertFalse(Validation::date('0.0.00', array('mdy'))); + $this->assertFalse(Validation::date('0/0/00', array('mdy'))); + $this->assertFalse(Validation::date('0 0 00', array('mdy'))); + $this->assertFalse(Validation::date('2-32-06', array('mdy'))); + $this->assertFalse(Validation::date('2.32.06', array('mdy'))); + $this->assertFalse(Validation::date('2/32/06', array('mdy'))); + $this->assertFalse(Validation::date('2 32 06', array('mdy'))); + } + +/** + * testDateMdyyLeapYear method + * + * @access public + * @return void + */ + function testDateMdyyLeapYear() { + $this->assertTrue(Validation::date('2-29-04', array('mdy'))); + $this->assertTrue(Validation::date('2.29.04', array('mdy'))); + $this->assertTrue(Validation::date('2/29/04', array('mdy'))); + $this->assertTrue(Validation::date('2 29 04', array('mdy'))); + $this->assertFalse(Validation::date('2-29-06', array('mdy'))); + $this->assertFalse(Validation::date('2.29.06', array('mdy'))); + $this->assertFalse(Validation::date('2/29/06', array('mdy'))); + $this->assertFalse(Validation::date('2 29 06', array('mdy'))); + } + +/** + * testDateMdyyyy method + * + * @access public + * @return void + */ + function testDateMdyyyy() { + $this->assertTrue(Validation::date('2-7-2006', array('mdy'))); + $this->assertTrue(Validation::date('2.7.2006', array('mdy'))); + $this->assertTrue(Validation::date('2/7/2006', array('mdy'))); + $this->assertTrue(Validation::date('2 7 2006', array('mdy'))); + $this->assertFalse(Validation::date('0-0-0000', array('mdy'))); + $this->assertFalse(Validation::date('0.0.0000', array('mdy'))); + $this->assertFalse(Validation::date('0/0/0000', array('mdy'))); + $this->assertFalse(Validation::date('0 0 0000', array('mdy'))); + $this->assertFalse(Validation::date('2-32-2006', array('mdy'))); + $this->assertFalse(Validation::date('2.32.2006', array('mdy'))); + $this->assertFalse(Validation::date('2/32/2006', array('mdy'))); + $this->assertFalse(Validation::date('2 32 2006', array('mdy'))); + } + +/** + * testDateMdyyyyLeapYear method + * + * @access public + * @return void + */ + function testDateMdyyyyLeapYear() { + $this->assertTrue(Validation::date('2-29-2004', array('mdy'))); + $this->assertTrue(Validation::date('2.29.2004', array('mdy'))); + $this->assertTrue(Validation::date('2/29/2004', array('mdy'))); + $this->assertTrue(Validation::date('2 29 2004', array('mdy'))); + $this->assertFalse(Validation::date('2-29-2006', array('mdy'))); + $this->assertFalse(Validation::date('2.29.2006', array('mdy'))); + $this->assertFalse(Validation::date('2/29/2006', array('mdy'))); + $this->assertFalse(Validation::date('2 29 2006', array('mdy'))); + } + +/** + * testDateYyyymmdd method + * + * @access public + * @return void + */ + function testDateYyyymmdd() { + $this->assertTrue(Validation::date('2006-12-27', array('ymd'))); + $this->assertTrue(Validation::date('2006.12.27', array('ymd'))); + $this->assertTrue(Validation::date('2006/12/27', array('ymd'))); + $this->assertTrue(Validation::date('2006 12 27', array('ymd'))); + $this->assertFalse(Validation::date('2006-11-31', array('ymd'))); + $this->assertFalse(Validation::date('2006.11.31', array('ymd'))); + $this->assertFalse(Validation::date('2006/11/31', array('ymd'))); + $this->assertFalse(Validation::date('2006 11 31', array('ymd'))); + } + +/** + * testDateYyyymmddLeapYear method + * + * @access public + * @return void + */ + function testDateYyyymmddLeapYear() { + $this->assertTrue(Validation::date('2004-02-29', array('ymd'))); + $this->assertTrue(Validation::date('2004.02.29', array('ymd'))); + $this->assertTrue(Validation::date('2004/02/29', array('ymd'))); + $this->assertTrue(Validation::date('2004 02 29', array('ymd'))); + $this->assertFalse(Validation::date('2006-02-29', array('ymd'))); + $this->assertFalse(Validation::date('2006.02.29', array('ymd'))); + $this->assertFalse(Validation::date('2006/02/29', array('ymd'))); + $this->assertFalse(Validation::date('2006 02 29', array('ymd'))); + } + +/** + * testDateYymmdd method + * + * @access public + * @return void + */ + function testDateYymmdd() { + $this->assertTrue(Validation::date('06-12-27', array('ymd'))); + $this->assertTrue(Validation::date('06.12.27', array('ymd'))); + $this->assertTrue(Validation::date('06/12/27', array('ymd'))); + $this->assertTrue(Validation::date('06 12 27', array('ymd'))); + $this->assertFalse(Validation::date('12/27/2600', array('ymd'))); + $this->assertFalse(Validation::date('12.27.2600', array('ymd'))); + $this->assertFalse(Validation::date('12/27/2600', array('ymd'))); + $this->assertFalse(Validation::date('12 27 2600', array('ymd'))); + $this->assertFalse(Validation::date('06-11-31', array('ymd'))); + $this->assertFalse(Validation::date('06.11.31', array('ymd'))); + $this->assertFalse(Validation::date('06/11/31', array('ymd'))); + $this->assertFalse(Validation::date('06 11 31', array('ymd'))); + } + +/** + * testDateYymmddLeapYear method + * + * @access public + * @return void + */ + function testDateYymmddLeapYear() { + $this->assertTrue(Validation::date('2004-02-29', array('ymd'))); + $this->assertTrue(Validation::date('2004.02.29', array('ymd'))); + $this->assertTrue(Validation::date('2004/02/29', array('ymd'))); + $this->assertTrue(Validation::date('2004 02 29', array('ymd'))); + $this->assertFalse(Validation::date('2006-02-29', array('ymd'))); + $this->assertFalse(Validation::date('2006.02.29', array('ymd'))); + $this->assertFalse(Validation::date('2006/02/29', array('ymd'))); + $this->assertFalse(Validation::date('2006 02 29', array('ymd'))); + } + +/** + * testDateDdMMMMyyyy method + * + * @access public + * @return void + */ + function testDateDdMMMMyyyy() { + $this->assertTrue(Validation::date('27 December 2006', array('dMy'))); + $this->assertTrue(Validation::date('27 Dec 2006', array('dMy'))); + $this->assertFalse(Validation::date('2006 Dec 27', array('dMy'))); + $this->assertFalse(Validation::date('2006 December 27', array('dMy'))); + } + +/** + * testDateDdMMMMyyyyLeapYear method + * + * @access public + * @return void + */ + function testDateDdMMMMyyyyLeapYear() { + $this->assertTrue(Validation::date('29 February 2004', array('dMy'))); + $this->assertFalse(Validation::date('29 February 2006', array('dMy'))); + } + +/** + * testDateMmmmDdyyyy method + * + * @access public + * @return void + */ + function testDateMmmmDdyyyy() { + $this->assertTrue(Validation::date('December 27, 2006', array('Mdy'))); + $this->assertTrue(Validation::date('Dec 27, 2006', array('Mdy'))); + $this->assertTrue(Validation::date('December 27 2006', array('Mdy'))); + $this->assertTrue(Validation::date('Dec 27 2006', array('Mdy'))); + $this->assertFalse(Validation::date('27 Dec 2006', array('Mdy'))); + $this->assertFalse(Validation::date('2006 December 27', array('Mdy'))); + } + +/** + * testDateMmmmDdyyyyLeapYear method + * + * @access public + * @return void + */ + function testDateMmmmDdyyyyLeapYear() { + $this->assertTrue(Validation::date('February 29, 2004', array('Mdy'))); + $this->assertTrue(Validation::date('Feb 29, 2004', array('Mdy'))); + $this->assertTrue(Validation::date('February 29 2004', array('Mdy'))); + $this->assertTrue(Validation::date('Feb 29 2004', array('Mdy'))); + $this->assertFalse(Validation::date('February 29, 2006', array('Mdy'))); + } + +/** + * testDateMy method + * + * @access public + * @return void + */ + function testDateMy() { + $this->assertTrue(Validation::date('December 2006', array('My'))); + $this->assertTrue(Validation::date('Dec 2006', array('My'))); + $this->assertTrue(Validation::date('December/2006', array('My'))); + $this->assertTrue(Validation::date('Dec/2006', array('My'))); + } + +/** + * testDateMyNumeric method + * + * @access public + * @return void + */ + function testDateMyNumeric() { + $this->assertTrue(Validation::date('12/2006', array('my'))); + $this->assertTrue(Validation::date('12-2006', array('my'))); + $this->assertTrue(Validation::date('12.2006', array('my'))); + $this->assertTrue(Validation::date('12 2006', array('my'))); + $this->assertFalse(Validation::date('12/06', array('my'))); + $this->assertFalse(Validation::date('12-06', array('my'))); + $this->assertFalse(Validation::date('12.06', array('my'))); + $this->assertFalse(Validation::date('12 06', array('my'))); + } + +/** + * testTime method + * + * @access public + * @return void + */ + function testTime() { + $this->assertTrue(Validation::time('00:00')); + $this->assertTrue(Validation::time('23:59')); + $this->assertFalse(Validation::time('24:00')); + $this->assertTrue(Validation::time('12:00')); + $this->assertTrue(Validation::time('12:01')); + $this->assertTrue(Validation::time('12:01am')); + $this->assertTrue(Validation::time('12:01pm')); + $this->assertTrue(Validation::time('1pm')); + $this->assertTrue(Validation::time('01:00')); + $this->assertFalse(Validation::time('1:00')); + $this->assertTrue(Validation::time('1:00pm')); + $this->assertFalse(Validation::time('13:00pm')); + $this->assertFalse(Validation::time('9:00')); + } + +/** + * testBoolean method + * + * @access public + * @return void + */ + function testBoolean() { + $this->assertTrue(Validation::boolean('0')); + $this->assertTrue(Validation::boolean('1')); + $this->assertTrue(Validation::boolean(0)); + $this->assertTrue(Validation::boolean(1)); + $this->assertTrue(Validation::boolean(true)); + $this->assertTrue(Validation::boolean(false)); + $this->assertFalse(Validation::boolean('true')); + $this->assertFalse(Validation::boolean('false')); + $this->assertFalse(Validation::boolean('-1')); + $this->assertFalse(Validation::boolean('2')); + $this->assertFalse(Validation::boolean('Boo!')); + } + +/** + * testDateCustomRegx method + * + * @access public + * @return void + */ + function testDateCustomRegx() { + $this->assertTrue(Validation::date('2006-12-27', null, '%^(19|20)[0-9]{2}[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$%')); + $this->assertFalse(Validation::date('12-27-2006', null, '%^(19|20)[0-9]{2}[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$%')); + } + +/** + * testDecimal method + * + * @access public + * @return void + */ + function testDecimal() { + $this->assertTrue(Validation::decimal('+1234.54321')); + $this->assertTrue(Validation::decimal('-1234.54321')); + $this->assertTrue(Validation::decimal('1234.54321')); + $this->assertTrue(Validation::decimal('+0123.45e6')); + $this->assertTrue(Validation::decimal('-0123.45e6')); + $this->assertTrue(Validation::decimal('0123.45e6')); + $this->assertFalse(Validation::decimal('string')); + $this->assertFalse(Validation::decimal('1234')); + $this->assertFalse(Validation::decimal('-1234')); + $this->assertFalse(Validation::decimal('+1234')); + } + +/** + * testDecimalWithPlaces method + * + * @access public + * @return void + */ + function testDecimalWithPlaces() { + $this->assertTrue(Validation::decimal('.27', '2')); + $this->assertTrue(Validation::decimal(.27, 2)); + $this->assertTrue(Validation::decimal(-.27, 2)); + $this->assertTrue(Validation::decimal(+.27, 2)); + $this->assertTrue(Validation::decimal('.277', '3')); + $this->assertTrue(Validation::decimal(.277, 3)); + $this->assertTrue(Validation::decimal(-.277, 3)); + $this->assertTrue(Validation::decimal(+.277, 3)); + $this->assertTrue(Validation::decimal('1234.5678', '4')); + $this->assertTrue(Validation::decimal(1234.5678, 4)); + $this->assertTrue(Validation::decimal(-1234.5678, 4)); + $this->assertTrue(Validation::decimal(+1234.5678, 4)); + $this->assertFalse(Validation::decimal('1234.5678', '3')); + $this->assertFalse(Validation::decimal(1234.5678, 3)); + $this->assertFalse(Validation::decimal(-1234.5678, 3)); + $this->assertFalse(Validation::decimal(+1234.5678, 3)); + } + +/** + * testDecimalCustomRegex method + * + * @access public + * @return void + */ + function testDecimalCustomRegex() { + $this->assertTrue(Validation::decimal('1.54321', null, '/^[-+]?[0-9]+(\\.[0-9]+)?$/s')); + $this->assertFalse(Validation::decimal('.54321', null, '/^[-+]?[0-9]+(\\.[0-9]+)?$/s')); + } + +/** + * testEmail method + * + * @access public + * @return void + */ + function testEmail() { + $this->assertTrue(Validation::email('abc.efg@domain.com')); + $this->assertTrue(Validation::email('efg@domain.com')); + $this->assertTrue(Validation::email('abc-efg@domain.com')); + $this->assertTrue(Validation::email('abc_efg@domain.com')); + $this->assertTrue(Validation::email('raw@test.ra.ru')); + $this->assertTrue(Validation::email('abc-efg@domain-hyphened.com')); + $this->assertTrue(Validation::email("p.o'malley@domain.com")); + $this->assertTrue(Validation::email('abc+efg@domain.com')); + $this->assertTrue(Validation::email('abc&efg@domain.com')); + $this->assertTrue(Validation::email('abc.efg@12345.com')); + $this->assertTrue(Validation::email('abc.efg@12345.co.jp')); + $this->assertTrue(Validation::email('abc@g.cn')); + $this->assertTrue(Validation::email('abc@x.com')); + $this->assertTrue(Validation::email('henrik@sbcglobal.net')); + $this->assertTrue(Validation::email('sani@sbcglobal.net')); + + // all ICANN TLDs + $this->assertTrue(Validation::email('abc@example.aero')); + $this->assertTrue(Validation::email('abc@example.asia')); + $this->assertTrue(Validation::email('abc@example.biz')); + $this->assertTrue(Validation::email('abc@example.cat')); + $this->assertTrue(Validation::email('abc@example.com')); + $this->assertTrue(Validation::email('abc@example.coop')); + $this->assertTrue(Validation::email('abc@example.edu')); + $this->assertTrue(Validation::email('abc@example.gov')); + $this->assertTrue(Validation::email('abc@example.info')); + $this->assertTrue(Validation::email('abc@example.int')); + $this->assertTrue(Validation::email('abc@example.jobs')); + $this->assertTrue(Validation::email('abc@example.mil')); + $this->assertTrue(Validation::email('abc@example.mobi')); + $this->assertTrue(Validation::email('abc@example.museum')); + $this->assertTrue(Validation::email('abc@example.name')); + $this->assertTrue(Validation::email('abc@example.net')); + $this->assertTrue(Validation::email('abc@example.org')); + $this->assertTrue(Validation::email('abc@example.pro')); + $this->assertTrue(Validation::email('abc@example.tel')); + $this->assertTrue(Validation::email('abc@example.travel')); + $this->assertTrue(Validation::email('someone@st.t-com.hr')); + + // strange, but technically valid email addresses + $this->assertTrue(Validation::email('S=postmaster/OU=rz/P=uni-frankfurt/A=d400/C=de@gateway.d400.de')); + $this->assertTrue(Validation::email('customer/department=shipping@example.com')); + $this->assertTrue(Validation::email('$A12345@example.com')); + $this->assertTrue(Validation::email('!def!xyz%abc@example.com')); + $this->assertTrue(Validation::email('_somename@example.com')); + + // invalid addresses + $this->assertFalse(Validation::email('abc@example')); + $this->assertFalse(Validation::email('abc@example.c')); + $this->assertFalse(Validation::email('abc@example.com.')); + $this->assertFalse(Validation::email('abc.@example.com')); + $this->assertFalse(Validation::email('abc@example..com')); + $this->assertFalse(Validation::email('abc@example.com.a')); + $this->assertFalse(Validation::email('abc@example.toolong')); + $this->assertFalse(Validation::email('abc;@example.com')); + $this->assertFalse(Validation::email('abc@example.com;')); + $this->assertFalse(Validation::email('abc@efg@example.com')); + $this->assertFalse(Validation::email('abc@@example.com')); + $this->assertFalse(Validation::email('abc efg@example.com')); + $this->assertFalse(Validation::email('abc,efg@example.com')); + $this->assertFalse(Validation::email('abc@sub,example.com')); + $this->assertFalse(Validation::email("abc@sub'example.com")); + $this->assertFalse(Validation::email('abc@sub/example.com')); + $this->assertFalse(Validation::email('abc@yahoo!.com')); + $this->assertFalse(Validation::email("Nyrée.surname@example.com")); + $this->assertFalse(Validation::email('abc@example_underscored.com')); + $this->assertFalse(Validation::email('raw@test.ra.ru....com')); + } + +/** + * testEmailDeep method + * + * @access public + * @return void + */ + function testEmailDeep() { + $found = gethostbynamel('example.abcd'); + if ($this->skipIf($found, 'Your DNS service responds for non-existant domains, skipping deep email checks. %s')) { + return; + } + $this->assertTrue(Validation::email('abc.efg@cakephp.org', true)); + $this->assertFalse(Validation::email('abc.efg@caphpkeinvalid.com', true)); + $this->assertFalse(Validation::email('abc@example.abcd', true)); + } + +/** + * testEmailCustomRegex method + * + * @access public + * @return void + */ + function testEmailCustomRegex() { + $this->assertTrue(Validation::email('abc.efg@cakephp.org', null, '/^[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i')); + $this->assertFalse(Validation::email('abc.efg@com.caphpkeinvalid', null, '/^[A-Z0-9._%-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i')); + } + +/** + * testEqualTo method + * + * @access public + * @return void + */ + function testEqualTo() { + $this->assertTrue(Validation::equalTo("1", "1")); + $this->assertFalse(Validation::equalTo(1, "1")); + $this->assertFalse(Validation::equalTo("", null)); + $this->assertFalse(Validation::equalTo("", false)); + $this->assertFalse(Validation::equalTo(0, false)); + $this->assertFalse(Validation::equalTo(null, false)); + } + +/** + * testIp method + * + * @access public + * @return void + */ + function testIpv4() { + $this->assertTrue(Validation::ip('0.0.0.0', 'IPv4')); + $this->assertTrue(Validation::ip('192.168.1.156', 'IPv4')); + $this->assertTrue(Validation::ip('255.255.255.255', 'IPv4')); + + $this->assertFalse(Validation::ip('127.0.0', 'IPv4')); + $this->assertFalse(Validation::ip('127.0.0.a', 'IPv4')); + $this->assertFalse(Validation::ip('127.0.0.256', 'IPv4')); + + $this->assertFalse(Validation::ip('2001:0db8:85a3:0000:0000:8a2e:0370:7334', 'IPv4')); + } + +/** + * testIp v6 + * + * @access public + * @return void + */ + function testIpv6() { + $this->assertTrue(Validation::ip('2001:0db8:85a3:0000:0000:8a2e:0370:7334', 'IPv6')); + $this->assertTrue(Validation::ip('2001:db8:85a3:0:0:8a2e:370:7334', 'IPv6')); + $this->assertTrue(Validation::ip('2001:db8:85a3::8a2e:370:7334', 'IPv6')); + $this->assertTrue(Validation::ip('2001:0db8:0000:0000:0000:0000:1428:57ab', 'IPv6')); + $this->assertTrue(Validation::ip('2001:0db8:0000:0000:0000::1428:57ab', 'IPv6')); + $this->assertTrue(Validation::ip('2001:0db8:0:0:0:0:1428:57ab', 'IPv6')); + $this->assertTrue(Validation::ip('2001:0db8:0:0::1428:57ab', 'IPv6')); + $this->assertTrue(Validation::ip('2001:0db8::1428:57ab', 'IPv6')); + $this->assertTrue(Validation::ip('2001:db8::1428:57ab', 'IPv6')); + $this->assertTrue(Validation::ip('0000:0000:0000:0000:0000:0000:0000:0001', 'IPv6')); + $this->assertTrue(Validation::ip('::1', 'IPv6')); + $this->assertTrue(Validation::ip('::ffff:12.34.56.78', 'IPv6')); + $this->assertTrue(Validation::ip('::ffff:0c22:384e', 'IPv6')); + $this->assertTrue(Validation::ip('2001:0db8:1234:0000:0000:0000:0000:0000', 'IPv6')); + $this->assertTrue(Validation::ip('2001:0db8:1234:ffff:ffff:ffff:ffff:ffff', 'IPv6')); + $this->assertTrue(Validation::ip('2001:db8:a::123', 'IPv6')); + $this->assertTrue(Validation::ip('fe80::', 'IPv6')); + $this->assertTrue(Validation::ip('::ffff:192.0.2.128', 'IPv6')); + $this->assertTrue(Validation::ip('::ffff:c000:280', 'IPv6')); + + $this->assertFalse(Validation::ip('123', 'IPv6')); + $this->assertFalse(Validation::ip('ldkfj', 'IPv6')); + $this->assertFalse(Validation::ip('2001::FFD3::57ab', 'IPv6')); + $this->assertFalse(Validation::ip('2001:db8:85a3::8a2e:37023:7334', 'IPv6')); + $this->assertFalse(Validation::ip('2001:db8:85a3::8a2e:370k:7334', 'IPv6')); + $this->assertFalse(Validation::ip('1:2:3:4:5:6:7:8:9', 'IPv6')); + $this->assertFalse(Validation::ip('1::2::3', 'IPv6')); + $this->assertFalse(Validation::ip('1:::3:4:5', 'IPv6')); + $this->assertFalse(Validation::ip('1:2:3::4:5:6:7:8:9', 'IPv6')); + $this->assertFalse(Validation::ip('::ffff:2.3.4', 'IPv6')); + $this->assertFalse(Validation::ip('::ffff:257.1.2.3', 'IPv6')); + + $this->assertFalse(Validation::ip('0.0.0.0', 'IPv6')); + } + +/** + * testIpBoth method + * + * @return void + * @access public + */ + function testIpBoth() { + $this->assertTrue(Validation::ip('0.0.0.0')); + $this->assertTrue(Validation::ip('192.168.1.156')); + $this->assertTrue(Validation::ip('255.255.255.255')); + + $this->assertFalse(Validation::ip('127.0.0')); + $this->assertFalse(Validation::ip('127.0.0.a')); + $this->assertFalse(Validation::ip('127.0.0.256')); + + $this->assertTrue(Validation::ip('2001:0db8:85a3:0000:0000:8a2e:0370:7334')); + $this->assertTrue(Validation::ip('2001:db8:85a3:0:0:8a2e:370:7334')); + $this->assertTrue(Validation::ip('2001:db8:85a3::8a2e:370:7334')); + + $this->assertFalse(Validation::ip('2001:db8:85a3::8a2e:37023:7334')); + $this->assertFalse(Validation::ip('2001:db8:85a3::8a2e:370k:7334')); + $this->assertFalse(Validation::ip('1:2:3:4:5:6:7:8:9')); + } + +/** + * testMaxLength method + * + * @access public + * @return void + */ + function testMaxLength() { + $this->assertTrue(Validation::maxLength('ab', 3)); + $this->assertTrue(Validation::maxLength('abc', 3)); + $this->assertTrue(Validation::maxLength('ÆΔΩЖÇ', 10)); + + $this->assertFalse(Validation::maxLength('abcd', 3)); + $this->assertFalse(Validation::maxLength('ÆΔΩЖÇ', 3)); + } + +/** + * testMinLength method + * + * @access public + * @return void + */ + function testMinLength() { + $this->assertFalse(Validation::minLength('ab', 3)); + $this->assertFalse(Validation::minLength('ÆΔΩЖÇ', 10)); + + $this->assertTrue(Validation::minLength('abc', 3)); + $this->assertTrue(Validation::minLength('abcd', 3)); + $this->assertTrue(Validation::minLength('ÆΔΩЖÇ', 2)); + } + +/** + * testUrl method + * + * @access public + * @return void + */ + function testUrl() { + $this->assertTrue(Validation::url('http://www.cakephp.org')); + $this->assertTrue(Validation::url('http://cakephp.org')); + $this->assertTrue(Validation::url('http://www.cakephp.org/somewhere#anchor')); + $this->assertTrue(Validation::url('http://192.168.0.1')); + $this->assertTrue(Validation::url('https://www.cakephp.org')); + $this->assertTrue(Validation::url('https://cakephp.org')); + $this->assertTrue(Validation::url('https://www.cakephp.org/somewhere#anchor')); + $this->assertTrue(Validation::url('https://192.168.0.1')); + $this->assertTrue(Validation::url('ftps://www.cakephp.org/pub/cake')); + $this->assertTrue(Validation::url('ftps://cakephp.org/pub/cake')); + $this->assertTrue(Validation::url('ftps://192.168.0.1/pub/cake')); + $this->assertTrue(Validation::url('ftp://www.cakephp.org/pub/cake')); + $this->assertTrue(Validation::url('ftp://cakephp.org/pub/cake')); + $this->assertTrue(Validation::url('ftp://192.168.0.1/pub/cake')); + $this->assertFalse(Validation::url('ftps://256.168.0.1/pub/cake')); + $this->assertFalse(Validation::url('ftp://256.168.0.1/pub/cake')); + $this->assertTrue(Validation::url('https://my.domain.com/gizmo/app?class=MySip;proc=start')); + $this->assertTrue(Validation::url('www.domain.tld')); + $this->assertFalse(Validation::url('http://w_w.domain.co_m')); + $this->assertFalse(Validation::url('http://www.domain.12com')); + $this->assertFalse(Validation::url('http://www.domain.longttldnotallowed')); + $this->assertFalse(Validation::url('http://www.-invaliddomain.tld')); + $this->assertFalse(Validation::url('http://www.domain.-invalidtld')); + $this->assertTrue(Validation::url('http://123456789112345678921234567893123456789412345678951234567896123.com')); + $this->assertFalse(Validation::url('http://this-domain-is-too-loooooong-by-icann-rules-maximum-length-is-63.com')); + $this->assertTrue(Validation::url('http://www.domain.com/blogs/index.php?blog=6&tempskin=_rss2')); + $this->assertTrue(Validation::url('http://www.domain.com/blogs/parenth()eses.php')); + $this->assertTrue(Validation::url('http://www.domain.com/index.php?get=params&get2=params')); + $this->assertTrue(Validation::url('http://www.domain.com/ndex.php?get=params&get2=params#anchor')); + $this->assertFalse(Validation::url('http://www.domain.com/fakeenco%ode')); + $this->assertTrue(Validation::url('http://www.domain.com/real%20url%20encodeing')); + $this->assertTrue(Validation::url('http://en.wikipedia.org/wiki/Architectural_pattern_(computer_science)')); + $this->assertFalse(Validation::url('http://en.(wikipedia).org/')); + $this->assertFalse(Validation::url('www.cakephp.org', true)); + $this->assertTrue(Validation::url('http://www.cakephp.org', true)); + $this->assertTrue(Validation::url('http://example.com/~userdir/')); + $this->assertTrue(Validation::url('http://example.com/~userdir/subdir/index.html')); + $this->assertTrue(Validation::url('http://www.zwischenraume.de')); + $this->assertTrue(Validation::url('http://www.zwischenraume.cz')); + $this->assertTrue(Validation::url('http://www.last.fm/music/浜崎ã‚ゆã¿'), 'utf8 path failed'); + $this->assertTrue(Validation::url('http://www.electrohome.ro/images/239537750-284232-215_300[1].jpg')); + + $this->assertTrue(Validation::url('http://cakephp.org:80')); + $this->assertTrue(Validation::url('http://cakephp.org:443')); + $this->assertTrue(Validation::url('http://cakephp.org:2000')); + $this->assertTrue(Validation::url('http://cakephp.org:27000')); + $this->assertTrue(Validation::url('http://cakephp.org:65000')); + + $this->assertTrue(Validation::url('[2001:0db8::1428:57ab]')); + $this->assertTrue(Validation::url('[::1]')); + $this->assertTrue(Validation::url('[2001:0db8::1428:57ab]:80')); + $this->assertTrue(Validation::url('[::1]:80')); + $this->assertTrue(Validation::url('http://[2001:0db8::1428:57ab]')); + $this->assertTrue(Validation::url('http://[::1]')); + $this->assertTrue(Validation::url('http://[2001:0db8::1428:57ab]:80')); + $this->assertTrue(Validation::url('http://[::1]:80')); + + $this->assertFalse(Validation::url('[1::2::3]')); + } + + function testUuid() { + $this->assertTrue(Validation::uuid('550e8400-e29b-11d4-a716-446655440000')); + $this->assertFalse(Validation::uuid('BRAP-e29b-11d4-a716-446655440000')); + $this->assertTrue(Validation::uuid('550E8400-e29b-11D4-A716-446655440000')); + $this->assertFalse(Validation::uuid('550e8400-e29b11d4-a716-446655440000')); + $this->assertFalse(Validation::uuid('550e8400-e29b-11d4-a716-4466440000')); + $this->assertFalse(Validation::uuid('550e8400-e29b-11d4-a71-446655440000')); + $this->assertFalse(Validation::uuid('550e8400-e29b-11d-a716-446655440000')); + $this->assertFalse(Validation::uuid('550e8400-e29-11d4-a716-446655440000')); + } + +/** + * testInList method + * + * @access public + * @return void + */ + function testInList() { + $this->assertTrue(Validation::inList('one', array('one', 'two'))); + $this->assertTrue(Validation::inList('two', array('one', 'two'))); + $this->assertFalse(Validation::inList('three', array('one', 'two'))); + } + +/** + * testRange method + * + * @access public + * @return void + */ + function testRange() { + $this->assertFalse(Validation::range(20, 100, 1)); + $this->assertTrue(Validation::range(20, 1, 100)); + $this->assertFalse(Validation::range(.5, 1, 100)); + $this->assertTrue(Validation::range(.5, 0, 100)); + $this->assertTrue(Validation::range(5)); + $this->assertTrue(Validation::range(-5, -10, 1)); + $this->assertFalse(Validation::range('word')); + } + +/** + * testExtension method + * + * @access public + * @return void + */ + function testExtension() { + $this->assertTrue(Validation::extension('extension.jpeg')); + $this->assertTrue(Validation::extension('extension.JPEG')); + $this->assertTrue(Validation::extension('extension.gif')); + $this->assertTrue(Validation::extension('extension.GIF')); + $this->assertTrue(Validation::extension('extension.png')); + $this->assertTrue(Validation::extension('extension.jpg')); + $this->assertTrue(Validation::extension('extension.JPG')); + $this->assertFalse(Validation::extension('noextension')); + $this->assertTrue(Validation::extension('extension.pdf', array('PDF'))); + $this->assertFalse(Validation::extension('extension.jpg', array('GIF'))); + $this->assertTrue(Validation::extension(array('extension.JPG', 'extension.gif', 'extension.png'))); + $this->assertTrue(Validation::extension(array('file' => array('name' => 'file.jpg')))); + $this->assertTrue(Validation::extension(array('file1' => array('name' => 'file.jpg'), + 'file2' => array('name' => 'file.jpg'), + 'file3' => array('name' => 'file.jpg')))); + $this->assertFalse(Validation::extension(array('file1' => array('name' => 'file.jpg'), + 'file2' => array('name' => 'file.jpg'), + 'file3' => array('name' => 'file.jpg')), array('gif'))); + + $this->assertFalse(Validation::extension(array('noextension', 'extension.JPG', 'extension.gif', 'extension.png'))); + $this->assertFalse(Validation::extension(array('extension.pdf', 'extension.JPG', 'extension.gif', 'extension.png'))); + } + +/** + * testMoney method + * + * @access public + * @return void + */ + function testMoney() { + $this->assertTrue(Validation::money('$100')); + $this->assertTrue(Validation::money('$100.11')); + $this->assertTrue(Validation::money('$100.112')); + $this->assertFalse(Validation::money('$100.1')); + $this->assertFalse(Validation::money('$100.1111')); + $this->assertFalse(Validation::money('text')); + + $this->assertTrue(Validation::money('100', 'right')); + $this->assertTrue(Validation::money('100.11$', 'right')); + $this->assertTrue(Validation::money('100.112$', 'right')); + $this->assertFalse(Validation::money('100.1$', 'right')); + $this->assertFalse(Validation::money('100.1111$', 'right')); + + $this->assertTrue(Validation::money('€100')); + $this->assertTrue(Validation::money('€100.11')); + $this->assertTrue(Validation::money('€100.112')); + $this->assertFalse(Validation::money('€100.1')); + $this->assertFalse(Validation::money('€100.1111')); + + $this->assertTrue(Validation::money('100', 'right')); + $this->assertTrue(Validation::money('100.11€', 'right')); + $this->assertTrue(Validation::money('100.112€', 'right')); + $this->assertFalse(Validation::money('100.1€', 'right')); + $this->assertFalse(Validation::money('100.1111€', 'right')); + } + +/** + * Test Multiple Select Validation + * + * @access public + * @return void + */ + function testMultiple() { + $this->assertTrue(Validation::multiple(array(0, 1, 2, 3))); + $this->assertTrue(Validation::multiple(array(50, 32, 22, 0))); + $this->assertTrue(Validation::multiple(array('str', 'var', 'enum', 0))); + $this->assertFalse(Validation::multiple('')); + $this->assertFalse(Validation::multiple(null)); + $this->assertFalse(Validation::multiple(array())); + $this->assertFalse(Validation::multiple(array(0))); + $this->assertFalse(Validation::multiple(array('0'))); + + $this->assertTrue(Validation::multiple(array(0, 3, 4, 5), array('in' => range(0, 10)))); + $this->assertFalse(Validation::multiple(array(0, 15, 20, 5), array('in' => range(0, 10)))); + $this->assertFalse(Validation::multiple(array(0, 5, 10, 11), array('in' => range(0, 10)))); + $this->assertFalse(Validation::multiple(array('boo', 'foo', 'bar'), array('in' => array('foo', 'bar', 'baz')))); + + $this->assertTrue(Validation::multiple(array(0, 5, 10, 11), array('max' => 3))); + $this->assertFalse(Validation::multiple(array(0, 5, 10, 11, 55), array('max' => 3))); + $this->assertTrue(Validation::multiple(array('foo', 'bar', 'baz'), array('max' => 3))); + $this->assertFalse(Validation::multiple(array('foo', 'bar', 'baz', 'squirrel'), array('max' => 3))); + + $this->assertTrue(Validation::multiple(array(0, 5, 10, 11), array('min' => 3))); + $this->assertTrue(Validation::multiple(array(0, 5, 10, 11, 55), array('min' => 3))); + $this->assertFalse(Validation::multiple(array('foo', 'bar', 'baz'), array('min' => 5))); + $this->assertFalse(Validation::multiple(array('foo', 'bar', 'baz', 'squirrel'), array('min' => 10))); + + $this->assertTrue(Validation::multiple(array(0, 5, 9), array('in' => range(0, 10), 'max' => 5))); + $this->assertFalse(Validation::multiple(array(0, 5, 9, 8, 6, 2, 1), array('in' => range(0, 10), 'max' => 5))); + $this->assertFalse(Validation::multiple(array(0, 5, 9, 8, 11), array('in' => range(0, 10), 'max' => 5))); + + $this->assertFalse(Validation::multiple(array(0, 5, 9), array('in' => range(0, 10), 'max' => 5, 'min' => 3))); + $this->assertFalse(Validation::multiple(array(0, 5, 9, 8, 6, 2, 1), array('in' => range(0, 10), 'max' => 5, 'min' => 2))); + $this->assertFalse(Validation::multiple(array(0, 5, 9, 8, 11), array('in' => range(0, 10), 'max' => 5, 'min' => 2))); + } + +/** + * testNumeric method + * + * @access public + * @return void + */ + function testNumeric() { + $this->assertFalse(Validation::numeric('teststring')); + $this->assertFalse(Validation::numeric('1.1test')); + $this->assertFalse(Validation::numeric('2test')); + + $this->assertTrue(Validation::numeric('2')); + $this->assertTrue(Validation::numeric(2)); + $this->assertTrue(Validation::numeric(2.2)); + $this->assertTrue(Validation::numeric('2.2')); + } + +/** + * testPhone method + * + * @access public + * @return void + */ + function testPhone() { + $this->assertFalse(Validation::phone('teststring')); + $this->assertFalse(Validation::phone('1-(33)-(333)-(4444)')); + $this->assertFalse(Validation::phone('1-(33)-3333-4444')); + $this->assertFalse(Validation::phone('1-(33)-33-4444')); + $this->assertFalse(Validation::phone('1-(33)-3-44444')); + $this->assertFalse(Validation::phone('1-(33)-3-444')); + $this->assertFalse(Validation::phone('1-(33)-3-44')); + + $this->assertFalse(Validation::phone('(055) 999-9999')); + $this->assertFalse(Validation::phone('(155) 999-9999')); + $this->assertFalse(Validation::phone('(595) 999-9999')); + $this->assertFalse(Validation::phone('(555) 099-9999')); + $this->assertFalse(Validation::phone('(555) 199-9999')); + + $this->assertTrue(Validation::phone('1 (222) 333 4444')); + $this->assertTrue(Validation::phone('+1 (222) 333 4444')); + $this->assertTrue(Validation::phone('(222) 333 4444')); + + $this->assertTrue(Validation::phone('1-(333)-333-4444')); + $this->assertTrue(Validation::phone('1.(333)-333-4444')); + $this->assertTrue(Validation::phone('1.(333).333-4444')); + $this->assertTrue(Validation::phone('1.(333).333.4444')); + $this->assertTrue(Validation::phone('1-333-333-4444')); + } + +/** + * testPostal method + * + * @access public + * @return void + */ + function testPostal() { + $this->assertFalse(Validation::postal('111', null, 'de')); + $this->assertFalse(Validation::postal('1111', null, 'de')); + $this->assertTrue(Validation::postal('13089', null, 'de')); + + $this->assertFalse(Validation::postal('111', null, 'be')); + $this->assertFalse(Validation::postal('0123', null, 'be')); + $this->assertTrue(Validation::postal('1204', null, 'be')); + + $this->assertFalse(Validation::postal('111', null, 'it')); + $this->assertFalse(Validation::postal('1111', null, 'it')); + $this->assertTrue(Validation::postal('13089', null, 'it')); + + $this->assertFalse(Validation::postal('111', null, 'uk')); + $this->assertFalse(Validation::postal('1111', null, 'uk')); + $this->assertFalse(Validation::postal('AZA 0AB', null, 'uk')); + $this->assertFalse(Validation::postal('X0A 0ABC', null, 'uk')); + $this->assertTrue(Validation::postal('X0A 0AB', null, 'uk')); + $this->assertTrue(Validation::postal('AZ0A 0AA', null, 'uk')); + $this->assertTrue(Validation::postal('A89 2DD', null, 'uk')); + + $this->assertFalse(Validation::postal('111', null, 'ca')); + $this->assertFalse(Validation::postal('1111', null, 'ca')); + $this->assertFalse(Validation::postal('D2A 0A0', null, 'ca')); + $this->assertFalse(Validation::postal('BAA 0ABC', null, 'ca')); + $this->assertFalse(Validation::postal('B2A AABC', null, 'ca')); + $this->assertFalse(Validation::postal('B2A 2AB', null, 'ca')); + $this->assertTrue(Validation::postal('X0A 0A2', null, 'ca')); + $this->assertTrue(Validation::postal('G4V 4C3', null, 'ca')); + $this->assertTrue(Validation::postal('L4J8D6', null, 'ca')); + + $this->assertFalse(Validation::postal('111', null, 'us')); + $this->assertFalse(Validation::postal('1111', null, 'us')); + $this->assertFalse(Validation::postal('130896', null, 'us')); + $this->assertFalse(Validation::postal('13089-33333', null, 'us')); + $this->assertFalse(Validation::postal('13089-333', null, 'us')); + $this->assertFalse(Validation::postal('13A89-4333', null, 'us')); + $this->assertTrue(Validation::postal('13089-3333', null, 'us')); + + $this->assertFalse(Validation::postal('111')); + $this->assertFalse(Validation::postal('1111')); + $this->assertFalse(Validation::postal('130896')); + $this->assertFalse(Validation::postal('13089-33333')); + $this->assertFalse(Validation::postal('13089-333')); + $this->assertFalse(Validation::postal('13A89-4333')); + $this->assertTrue(Validation::postal('13089-3333')); + } + +/** + * test that phone and postal pass to other classes. + * + * @return void + */ + function testPhonePostalSsnPass() { + $this->assertTrue(Validation::postal('text', null, 'testNl')); + $this->assertTrue(Validation::phone('text', null, 'testDe')); + $this->assertTrue(Validation::ssn('text', null, 'testNl')); + } + +/** + * test the pass through calling of an alternate locale with postal() + * + * @return void + **/ + function testPassThroughMethod() { + $this->assertTrue(Validation::postal('text', null, 'testNl')); + + $this->expectError('Could not find AUTOFAILValidation class, unable to complete validation.'); + Validation::postal('text', null, 'AUTOFAIL'); + + $this->expectError('Method phone does not exist on TestNlValidation unable to complete validation.'); + Validation::phone('text', null, 'testNl'); + } + +/** + * testSsn method + * + * @access public + * @return void + */ + function testSsn() { + $this->assertFalse(Validation::ssn('111-333', null, 'dk')); + $this->assertFalse(Validation::ssn('111111-333', null, 'dk')); + $this->assertTrue(Validation::ssn('111111-3334', null, 'dk')); + + $this->assertFalse(Validation::ssn('1118333', null, 'nl')); + $this->assertFalse(Validation::ssn('1234567890', null, 'nl')); + $this->assertFalse(Validation::ssn('12345A789', null, 'nl')); + $this->assertTrue(Validation::ssn('123456789', null, 'nl')); + + $this->assertFalse(Validation::ssn('11-33-4333', null, 'us')); + $this->assertFalse(Validation::ssn('113-3-4333', null, 'us')); + $this->assertFalse(Validation::ssn('111-33-333', null, 'us')); + $this->assertTrue(Validation::ssn('111-33-4333', null, 'us')); + } + +/** + * testUserDefined method + * + * @access public + * @return void + */ + function testUserDefined() { + $validator = new CustomValidator; + $this->assertFalse(Validation::userDefined('33', $validator, 'customValidate')); + $this->assertFalse(Validation::userDefined('3333', $validator, 'customValidate')); + $this->assertTrue(Validation::userDefined('333', $validator, 'customValidate')); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helper.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helper.test.php new file mode 100644 index 000000000..75d644d7d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helper.test.php @@ -0,0 +1,804 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Core', array('View', 'Helper')); + +/** + * HelperTestPost class + * + * @package cake + * @subpackage cake.tests.cases.libs.view + */ +class HelperTestPost extends Model { + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '8'), + 'title' => array('type' => 'string', 'null' => false, 'default' => '', 'length' => '255'), + 'body' => array('type' => 'string', 'null' => true, 'default' => '', 'length' => ''), + 'number' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '8'), + 'date' => array('type' => 'date', 'null' => true, 'default' => '', 'length' => ''), + 'created' => array('type' => 'date', 'null' => true, 'default' => '', 'length' => ''), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => '', 'length' => null) + ); + return $this->_schema; + } + +/** + * hasAndBelongsToMany property + * + * @var array + * @access public + */ + var $hasAndBelongsToMany = array('HelperTestTag'=> array('with' => 'HelperTestPostsTag')); +} + +/** + * HelperTestComment class + * + * @package cake + * @subpackage cake.tests.cases.libs.view + */ +class HelperTestComment extends Model { + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '8'), + 'author_id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '8'), + 'title' => array('type' => 'string', 'null' => false, 'default' => '', 'length' => '255'), + 'body' => array('type' => 'string', 'null' => true, 'default' => '', 'length' => ''), + 'created' => array('type' => 'date', 'null' => true, 'default' => '', 'length' => ''), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => '', 'length' => null) + ); + return $this->_schema; + } +} + +/** + * HelperTestTag class + * + * @package cake + * @subpackage cake.tests.cases.libs.view + */ +class HelperTestTag extends Model { + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + $this->_schema = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '8'), + 'name' => array('type' => 'string', 'null' => false, 'default' => '', 'length' => '255'), + 'created' => array('type' => 'date', 'null' => true, 'default' => '', 'length' => ''), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => '', 'length' => null) + ); + return $this->_schema; + } +} + +/** + * HelperTestPostsTag class + * + * @package cake + * @subpackage cake.tests.cases.libs.view + */ +class HelperTestPostsTag extends Model { + +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; + +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + $this->_schema = array( + 'helper_test_post_id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '8'), + 'helper_test_tag_id' => array('type' => 'integer', 'null' => false, 'default' => '', 'length' => '8'), + ); + return $this->_schema; + } +} + +class TestHelper extends Helper { +/** + * expose a method as public + * + * @param string $options + * @param string $exclude + * @param string $insertBefore + * @param string $insertAfter + * @return void + */ + function parseAttributes($options, $exclude = null, $insertBefore = ' ', $insertAfter = null) { + return $this->_parseAttributes($options, $exclude, $insertBefore, $insertAfter); + } +} + +/** + * HelperTest class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class HelperTest extends CakeTestCase { + +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + ClassRegistry::flush(); + Router::reload(); + $null = null; + $this->View = new View($null); + $this->Helper = new Helper(); + ClassRegistry::addObject('HelperTestPost', new HelperTestPost()); + ClassRegistry::addObject('HelperTestComment', new HelperTestComment()); + ClassRegistry::addObject('HelperTestTag', new HelperTestTag()); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Helper, $this->View); + ClassRegistry::flush(); + } + +/** + * testFormFieldNameParsing method + * + * @access public + * @return void + */ + function testSetEntity() { + // PHP4 reference hack + ClassRegistry::removeObject('view'); + ClassRegistry::addObject('view', $this->View); + + $this->Helper->setEntity('HelperTestPost.id'); + $this->assertFalse($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + + $this->Helper->setEntity('HelperTestComment.body'); + $this->assertFalse($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestComment'); + $this->assertEqual($this->View->field, 'body'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('HelperTestPost', true); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, null); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('_Token.fields'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'fields'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, '_Token'); + $this->assertEqual($this->View->fieldSuffix, null); + + + $this->Helper->setEntity('id'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('HelperTestComment.body'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'body'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, 'HelperTestComment'); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('body'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'body'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('Something.else'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'else'); + $this->assertEqual($this->View->modelId, false); + $this->assertEqual($this->View->association, 'Something'); + $this->assertEqual($this->View->fieldSuffix, ''); + + $this->Helper->setEntity('5.id'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, '5'); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->assertEqual($this->View->entity(), array('HelperTestPost', 5, 'id')); + + $this->Helper->setEntity('0.id'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, '0'); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->assertEqual($this->View->entity(), array('HelperTestPost', 0, 'id')); + + $this->Helper->setEntity('5.created.month'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'created'); + $this->assertEqual($this->View->modelId, '5'); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, 'month'); + + $this->Helper->setEntity('HelperTestComment.5.id'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, '5'); + $this->assertEqual($this->View->association, 'HelperTestComment'); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('HelperTestComment.id.time'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, 'HelperTestComment'); + $this->assertEqual($this->View->fieldSuffix, 'time'); + + $this->Helper->setEntity('HelperTestTag'); + $this->assertTrue($this->View->modelScope); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'HelperTestTag'); + $this->assertEqual($this->View->modelId, ''); + $this->assertEqual($this->View->association, 'HelperTestTag'); + $this->assertEqual($this->View->fieldSuffix, ''); + + $this->Helper->setEntity(null); + $this->Helper->setEntity('ModelThatDoesntExist.field_that_doesnt_exist'); + $this->assertFalse($this->View->modelScope); + $this->assertEqual($this->View->model, 'ModelThatDoesntExist'); + $this->assertEqual($this->View->field, 'field_that_doesnt_exist'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + } + +/** + * test that 'view' doesn't break things. + * + * @return void + */ + function testSetEntityWithView() { + $this->assertNull($this->Helper->setEntity('Allow.view.group_id')); + $this->assertNull($this->Helper->setEntity('Allow.view')); + $this->assertNull($this->Helper->setEntity('View.view')); + } + +/** + * test getting values from Helper + * + * @return void + */ + function testValue() { + $this->Helper->data = array('fullname' => 'This is me'); + $this->Helper->setEntity('fullname'); + $result = $this->Helper->value('fullname'); + $this->assertEqual($result, 'This is me'); + + $this->Helper->data = array('Post' => array('name' => 'First Post')); + $this->Helper->setEntity('Post.name'); + $result = $this->Helper->value('Post.name'); + $this->assertEqual($result, 'First Post'); + + $this->Helper->data = array('Post' => array(2 => array('name' => 'First Post'))); + $this->Helper->setEntity('Post.2.name'); + $result = $this->Helper->value('Post.2.name'); + $this->assertEqual($result, 'First Post'); + + $this->Helper->data = array('Post' => array(2 => array('created' => array('year' => '2008')))); + $this->Helper->setEntity('Post.2.created'); + $result = $this->Helper->value('Post.2.created'); + $this->assertEqual($result, array('year' => '2008')); + + $this->Helper->data = array('Post' => array(2 => array('created' => array('year' => '2008')))); + $this->Helper->setEntity('Post.2.created.year'); + $result = $this->Helper->value('Post.2.created.year'); + $this->assertEqual($result, '2008'); + + $this->Helper->data = array('HelperTestTag' => array('HelperTestTag' => '')); + $this->Helper->setEntity('HelperTestTag.HelperTestTag'); + $result = $this->Helper->value('HelperTestTag.HelperTestTag'); + $this->assertEqual($result, ''); + + $this->Helper->data = array('HelperTestTag' => array('HelperTestTag' => array(2, 3, 4))); + $this->Helper->setEntity('HelperTestTag.HelperTestTag'); + $result = $this->Helper->value('HelperTestTag.HelperTestTag'); + $this->assertEqual($result, array(2, 3, 4)); + + $this->Helper->data = array( + 'HelperTestTag' => array( + array('id' => 3), + array('id' => 5) + ) + ); + $this->Helper->setEntity('HelperTestTag.HelperTestTag'); + $result = $this->Helper->value('HelperTestTag.HelperTestTag'); + $this->assertEqual($result, array(3 => 3, 5 => 5)); + + $this->Helper->data = array('zero' => 0); + $this->Helper->setEntity('zero'); + $result = $this->Helper->value(array('default' => 'something'), 'zero'); + $this->assertEqual($result, array('value' => 0)); + + $this->Helper->data = array('zero' => '0'); + $result = $this->Helper->value(array('default' => 'something'), 'zero'); + $this->assertEqual($result, array('value' => '0')); + + $this->Helper->setEntity('inexistent'); + $result = $this->Helper->value(array('default' => 'something'), 'inexistent'); + $this->assertEqual($result, array('value' => 'something')); + } + +/** + * Ensure HTML escaping of url params. So link addresses are valid and not exploited + * + * @return void + */ + function testUrlConversion() { + $result = $this->Helper->url('/controller/action/1'); + $this->assertEqual($result, '/controller/action/1'); + + $result = $this->Helper->url('/controller/action/1?one=1&two=2'); + $this->assertEqual($result, '/controller/action/1?one=1&two=2'); + + $result = $this->Helper->url(array('controller' => 'posts', 'action' => 'index', 'page' => '1" onclick="alert(\'XSS\');"')); + $this->assertEqual($result, "/posts/index/page:1" onclick="alert('XSS');""); + + $result = $this->Helper->url('/controller/action/1/param:this+one+more'); + $this->assertEqual($result, '/controller/action/1/param:this+one+more'); + + $result = $this->Helper->url('/controller/action/1/param:this%20one%20more'); + $this->assertEqual($result, '/controller/action/1/param:this%20one%20more'); + + $result = $this->Helper->url('/controller/action/1/param:%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24'); + $this->assertEqual($result, '/controller/action/1/param:%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24'); + + $result = $this->Helper->url(array( + 'controller' => 'posts', 'action' => 'index', 'param' => '%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24' + )); + $this->assertEqual($result, "/posts/index/param:%7Baround%20here%7D%5Bthings%5D%5Bare%5D%24%24"); + + $result = $this->Helper->url(array( + 'controller' => 'posts', 'action' => 'index', 'page' => '1', + '?' => array('one' => 'value', 'two' => 'value', 'three' => 'purple') + )); + $this->assertEqual($result, "/posts/index/page:1?one=value&two=value&three=purple"); + } + +/** + * test assetTimestamp application + * + * @return void + */ + function testAssetTimestamp() { + $_timestamp = Configure::read('Asset.timestamp'); + $_debug = Configure::read('debug'); + + Configure::write('Asset.timestamp', false); + $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css'); + $this->assertEqual($result, CSS_URL . 'cake.generic.css'); + + Configure::write('Asset.timestamp', true); + Configure::write('debug', 0); + $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css'); + $this->assertEqual($result, CSS_URL . 'cake.generic.css'); + + Configure::write('Asset.timestamp', true); + Configure::write('debug', 2); + $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css'); + $this->assertPattern('/' . preg_quote(CSS_URL . 'cake.generic.css?', '/') . '[0-9]+/', $result); + + Configure::write('Asset.timestamp', 'force'); + Configure::write('debug', 0); + $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css'); + $this->assertPattern('/' . preg_quote(CSS_URL . 'cake.generic.css?', '/') . '[0-9]+/', $result); + + $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css?someparam'); + $this->assertEqual($result, CSS_URL . 'cake.generic.css?someparam'); + + $this->Helper->webroot = '/some/dir/'; + $result = $this->Helper->assetTimestamp('/some/dir/' . CSS_URL . 'cake.generic.css'); + $this->assertPattern('/' . preg_quote(CSS_URL . 'cake.generic.css?', '/') . '[0-9]+/', $result); + + Configure::write('debug', $_debug); + Configure::write('Asset.timestamp', $_timestamp); + } + +/** + * test assetTimestamp with plugins and themes + * + * @return void + */ + function testAssetTimestampPluginsAndThemes() { + $_timestamp = Configure::read('Asset.timestamp'); + Configure::write('Asset.timestamp', 'force'); + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS), + )); + + $result = $this->Helper->assetTimestamp('/test_plugin/css/test_plugin_asset.css'); + $this->assertPattern('#/test_plugin/css/test_plugin_asset.css\?[0-9]+$#', $result, 'Missing timestamp plugin'); + + $result = $this->Helper->assetTimestamp('/test_plugin/css/i_dont_exist.css'); + $this->assertPattern('#/test_plugin/css/i_dont_exist.css\?$#', $result, 'No error on missing file'); + + $result = $this->Helper->assetTimestamp('/theme/test_theme/js/theme.js'); + $this->assertPattern('#/theme/test_theme/js/theme.js\?[0-9]+$#', $result, 'Missing timestamp theme'); + + $result = $this->Helper->assetTimestamp('/theme/test_theme/js/non_existant.js'); + $this->assertPattern('#/theme/test_theme/js/non_existant.js\?$#', $result, 'No error on missing file'); + + App::build(); + Configure::write('Asset.timestamp', $_timestamp); + } + +/** + * testFieldsWithSameName method + * + * @access public + * @return void + */ + function testFieldsWithSameName() { + // PHP4 reference hack + ClassRegistry::removeObject('view'); + ClassRegistry::addObject('view', $this->View); + + $this->Helper->setEntity('HelperTestTag', true); + + $this->Helper->setEntity('HelperTestTag.id'); + $this->assertEqual($this->View->model, 'HelperTestTag'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('My.id'); + $this->assertEqual($this->View->model, 'HelperTestTag'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, 'My'); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('MyOther.id'); + $this->assertEqual($this->View->model, 'HelperTestTag'); + $this->assertEqual($this->View->field, 'id'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, 'MyOther'); + $this->assertEqual($this->View->fieldSuffix, null); + + } + +/** + * testFieldSameAsModel method + * + * @access public + * @return void + */ + function testFieldSameAsModel() { + // PHP4 reference hack + ClassRegistry::removeObject('view'); + ClassRegistry::addObject('view', $this->View); + + $this->Helper->setEntity('HelperTestTag', true); + + $this->Helper->setEntity('helper_test_post'); + $this->assertEqual($this->View->model, 'HelperTestTag'); + $this->assertEqual($this->View->field, 'helper_test_post'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('HelperTestTag'); + $this->assertEqual($this->View->model, 'HelperTestTag'); + $this->assertEqual($this->View->field, 'HelperTestTag'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + $this->assertEqual($this->View->entityPath, 'HelperTestTag'); + } + +/** + * testFieldSuffixForDate method + * + * @access public + * @return void + */ + function testFieldSuffixForDate() { + // PHP4 reference hack + ClassRegistry::removeObject('view'); + ClassRegistry::addObject('view', $this->View); + + $this->Helper->setEntity('HelperTestPost', true); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, null); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, null); + + $this->Helper->setEntity('date.month'); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->field, 'date'); + $this->assertEqual($this->View->modelId, null); + $this->assertEqual($this->View->association, null); + $this->assertEqual($this->View->fieldSuffix, 'month'); + } + +/** + * testMulitDimensionValue method + * + * @access public + * @return void + */ + function testMulitDimensionValue() { + $this->Helper->data = array(); + for ($i = 0; $i < 2; $i++) { + $this->Helper->data['Model'][$i] = 'what'; + $result[] = $this->Helper->value("Model.{$i}"); + $this->Helper->data['Model'][$i] = array(); + for ($j = 0; $j < 2; $j++) { + $this->Helper->data['Model'][$i][$j] = 'how'; + $result[] = $this->Helper->value("Model.{$i}.{$j}"); + } + } + $expected = array('what', 'how', 'how', 'what', 'how', 'how'); + $this->assertEqual($result, $expected); + + $this->Helper->data['HelperTestComment']['5']['id'] = 'ok'; + $result = $this->Helper->value('HelperTestComment.5.id'); + $this->assertEqual($result, 'ok'); + + $this->Helper->setEntity('HelperTestPost', true); + $this->Helper->data['HelperTestPost']['5']['created']['month'] = '10'; + $result = $this->Helper->value('5.created.month'); + $this->assertEqual($result, 10); + + $this->Helper->data['HelperTestPost']['0']['id'] = 100; + $result = $this->Helper->value('0.id'); + $this->assertEqual($result, 100); + } + +/** + * testClean method + * + * @access public + * @return void + */ + function testClean() { + $result = $this->Helper->clean(array()); + $this->assertEqual($result, null); + + $result = $this->Helper->clean(array('', 'something else')); + $this->assertEqual($result, array('with something', 'something else')); + + $result = $this->Helper->clean(''); + $this->assertEqual($result, 'with something'); + + $result = $this->Helper->clean(''); + $this->assertNoPattern('#Helper->clean(""); + $this->assertNoPattern('#Helper->clean(''); + $this->assertEqual($result, ''); + + $result = $this->Helper->clean('<script>alert(document.cookie)</script>'); + $this->assertEqual($result, '&lt;script&gt;alert(document.cookie)&lt;/script&gt;'); + } + +/** + * testMultiDimensionalField method + * + * @access public + * @return void + */ + function testMultiDimensionalField() { + // PHP4 reference hack + ClassRegistry::removeObject('view'); + ClassRegistry::addObject('view', $this->View); + + $this->Helper->setEntity('HelperTestPost', true); + + $this->Helper->setEntity('HelperTestPost.2.HelperTestComment.1.title'); + $this->assertEqual($this->View->model, 'HelperTestPost'); + $this->assertEqual($this->View->association, 'HelperTestComment'); + $this->assertEqual($this->View->modelId,2); + $this->assertEqual($this->View->field, 'title'); + + $this->Helper->setEntity('HelperTestPost.1.HelperTestComment.1.HelperTestTag.1.created'); + $this->assertEqual($this->View->field,'created'); + $this->assertEqual($this->View->association,'HelperTestTag'); + $this->assertEqual($this->View->modelId,1); + + $this->Helper->setEntity('HelperTestPost.0.HelperTestComment.1.HelperTestTag.1.fake'); + $this->assertEqual($this->View->model,'HelperTestPost'); + $this->assertEqual($this->View->association,'HelperTestTag'); + $this->assertEqual($this->View->field,null); + + $this->Helper->setEntity('1.HelperTestComment.1.HelperTestTag.created.year'); + $this->assertEqual($this->View->model,'HelperTestPost'); + $this->assertEqual($this->View->association,'HelperTestTag'); + $this->assertEqual($this->View->field,'created'); + $this->assertEqual($this->View->modelId,1); + $this->assertEqual($this->View->fieldSuffix,'year'); + + $this->Helper->data['HelperTestPost'][2]['HelperTestComment'][1]['title'] = 'My Title'; + $result = $this->Helper->value('HelperTestPost.2.HelperTestComment.1.title'); + $this->assertEqual($result,'My Title'); + + $this->Helper->data['HelperTestPost'][2]['HelperTestComment'][1]['created']['year'] = 2008; + $result = $this->Helper->value('HelperTestPost.2.HelperTestComment.1.created.year'); + $this->assertEqual($result,2008); + + $this->Helper->data[2]['HelperTestComment'][1]['created']['year'] = 2008; + $result = $this->Helper->value('HelperTestPost.2.HelperTestComment.1.created.year'); + $this->assertEqual($result,2008); + + $this->Helper->data['HelperTestPost']['title'] = 'My Title'; + $result = $this->Helper->value('title'); + $this->assertEqual($result,'My Title'); + + $this->Helper->data['My']['title'] = 'My Title'; + $result = $this->Helper->value('My.title'); + $this->assertEqual($result,'My Title'); + } + + function testWebrootPaths() { + $this->Helper->webroot = '/'; + $result = $this->Helper->webroot('/img/cake.power.gif'); + $expected = '/img/cake.power.gif'; + $this->assertEqual($result, $expected); + + $this->Helper->theme = 'test_theme'; + + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) + )); + + $result = $this->Helper->webroot('/img/cake.power.gif'); + $expected = '/theme/test_theme/img/cake.power.gif'; + $this->assertEqual($result, $expected); + + $result = $this->Helper->webroot('/img/test.jpg'); + $expected = '/theme/test_theme/img/test.jpg'; + $this->assertEqual($result, $expected); + + $webRoot = Configure::read('App.www_root'); + Configure::write('App.www_root', TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'webroot' . DS); + + $result = $this->Helper->webroot('/img/cake.power.gif'); + $expected = '/theme/test_theme/img/cake.power.gif'; + $this->assertEqual($result, $expected); + + $result = $this->Helper->webroot('/img/test.jpg'); + $expected = '/theme/test_theme/img/test.jpg'; + $this->assertEqual($result, $expected); + + $result = $this->Helper->webroot('/img/cake.icon.gif'); + $expected = '/img/cake.icon.gif'; + $this->assertEqual($result, $expected); + + $result = $this->Helper->webroot('/img/cake.icon.gif?some=param'); + $expected = '/img/cake.icon.gif?some=param'; + $this->assertEqual($result, $expected); + + Configure::write('App.www_root', $webRoot); + } + +/** + * test parsing attributes. + * + * @return void + */ + function testParseAttributeCompact() { + $helper =& new TestHelper(); + $compact = array('compact', 'checked', 'declare', 'readonly', 'disabled', + 'selected', 'defer', 'ismap', 'nohref', 'noshade', 'nowrap', 'multiple', 'noresize'); + + foreach ($compact as $attribute) { + foreach (array('true', true, 1, '1', $attribute) as $value) { + $attrs = array($attribute => $value); + $expected = ' ' . $attribute . '="' . $attribute . '"'; + $this->assertEqual($helper->parseAttributes($attrs), $expected, '%s Failed on ' . $value); + } + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/ajax.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/ajax.test.php new file mode 100644 index 000000000..d06bf4a71 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/ajax.test.php @@ -0,0 +1,910 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.view.helpers + * @since CakePHP(tm) v 1.2.0.4206 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + define('CAKEPHP_UNIT_TEST_EXECUTION', 1); +} +App::import('Helper', array('Html', 'Form', 'Javascript', 'Ajax')); +/** + * AjaxTestController class + * + * @package cake + * @subpackage cake.tests.cases.libs.view.helpers + */ +class AjaxTestController extends Controller { +/** + * name property + * + * @var string 'AjaxTest' + * @access public + */ + var $name = 'AjaxTest'; +/** + * uses property + * + * @var mixed null + * @access public + */ + var $uses = null; +} +/** + * PostAjaxTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.view.helpers + */ +class PostAjaxTest extends Model { +/** + * primaryKey property + * + * @var string 'id' + * @access public + */ + var $primaryKey = 'id'; +/** + * useTable property + * + * @var bool false + * @access public + */ + var $useTable = false; +/** + * schema method + * + * @access public + * @return void + */ + function schema() { + return array( + 'id' => array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'), + 'name' => array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'created' => array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated' => array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + ); + } +} +/** + * TestAjaxHelper class + * + * @package cake + * @subpackage cake.tests.cases.libs.view.helpers + */ +class TestAjaxHelper extends AjaxHelper { +/** + * stop method + * + * @access public + * @return void + */ + function _stop() { + } +} +/** + * TestJavascriptHelper class + * + * @package cake + * @subpackage cake.tests.cases.libs.view.helpers + */ +class TestJavascriptHelper extends JavascriptHelper { +/** + * codeBlocks property + * + * @var mixed + * @access public + */ + var $codeBlocks; +/** + * codeBlock method + * + * @param mixed $parameter + * @access public + * @return void + */ + function codeBlock($parameter) { + if (empty($this->codeBlocks)) { + $this->codeBlocks = array(); + } + $this->codeBlocks[] = $parameter; + } +} +/** + * AjaxTest class + * + * @package cake + * @subpackage cake.tests.cases.libs.view.helpers + */ +class AjaxHelperTest extends CakeTestCase { +/** + * Regexp for CDATA start block + * + * @var string + */ + var $cDataStart = 'preg:/^\/\/[\s\r\n]*/'; +/** + * setUp method + * + * @access public + * @return void + */ + function setUp() { + Router::reload(); + $this->Ajax =& new TestAjaxHelper(); + $this->Ajax->Html =& new HtmlHelper(); + $this->Ajax->Form =& new FormHelper(); + $this->Ajax->Javascript =& new JavascriptHelper(); + $this->Ajax->Form->Html =& $this->Ajax->Html; + $view =& new View(new AjaxTestController()); + ClassRegistry::addObject('view', $view); + ClassRegistry::addObject('PostAjaxTest', new PostAjaxTest()); + + $this->Ajax->Form->params = array( + 'plugin' => null, + 'action' => 'view', + 'controller' => 'users' + ); + } +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Ajax); + ClassRegistry::flush(); + } +/** + * testEvalScripts method + * + * @access public + * @return void + */ + function testEvalScripts() { + $result = $this->Ajax->link('Test Link', 'http://www.cakephp.org', array('id' => 'link1', 'update' => 'content', 'evalScripts' => false)); + $expected = array( + 'a' => array('id' => 'link1', 'onclick' => ' event.returnValue = false; return false;', 'href' => 'http://www.cakephp.org'), + 'Test Link', + '/a', + array('script' => array('type' => 'text/javascript')), + $this->cDataStart, + "Event.observe('link1', 'click', function(event) { new Ajax.Updater('content','http://www.cakephp.org', {asynchronous:true, evalScripts:false, requestHeaders:['X-Update', 'content']}) }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Ajax->link('Test Link', 'http://www.cakephp.org', array('id' => 'link1', 'update' => 'content')); + $expected = array( + 'a' => array('id' => 'link1', 'onclick' => ' event.returnValue = false; return false;', 'href' => 'http://www.cakephp.org'), + 'Test Link', + '/a', + array('script' => array('type' => 'text/javascript')), + $this->cDataStart, + "Event.observe('link1', 'click', function(event) { new Ajax.Updater('content','http://www.cakephp.org', {asynchronous:true, evalScripts:true, requestHeaders:['X-Update', 'content']}) }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + } +/** + * testAutoComplete method + * + * @access public + * @return void + */ + function testAutoComplete() { + $result = $this->Ajax->autoComplete('PostAjaxTest.title' , '/posts', array('minChars' => 2)); + $this->assertPattern('/^]+name="data\[PostAjaxTest\]\[title\]"[^<>]+autocomplete="off"[^<>]+\/>/', $result); + $this->assertPattern('/]+id="PostAjaxTestTitle_autoComplete"[^<>]*><\/div>/', $result); + $this->assertPattern('/]+class="auto_complete"[^<>]*><\/div>/', $result); + $this->assertPattern('/<\/div>\s+'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('script'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('scriptaculous.js?load=effects'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('some.json.libary'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('jquery-1.1.2'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('jquery-1.1.2'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('/plugin/js/jquery-1.1.2'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('/some_other_path/myfile.1.2.2.min.js'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('some_other_path/myfile.1.2.2.min.js'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('some_other_path/myfile.1.2.2.min'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('http://example.com/jquery.js'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link(array('prototype.js', 'scriptaculous.js')); + $this->assertPattern('/^\s*]*><\/script>/', $result); + $this->assertPattern('/<\/script>\s*]+>/', $result); + $this->assertPattern('/]*><\/script>\s*$/', $result); + + $result = $this->Javascript->link('jquery-1.1.2', false); + $resultScripts = $this->View->scripts(); + reset($resultScripts); + $expected = ''; + $this->assertNull($result); + $this->assertEqual(count($resultScripts), 1); + $this->assertEqual(current($resultScripts), $expected); + } + +/** + * testFilteringAndTimestamping method + * + * @access public + * @return void + */ + function testFilteringAndTimestamping() { + if ($this->skipIf(!is_writable(JS), 'JavaScript directory not writable, skipping JS asset timestamp tests. %s')) { + return; + } + + cache(str_replace(WWW_ROOT, '', JS) . '__cake_js_test.js', 'alert("test")', '+999 days', 'public'); + $timestamp = substr(strtotime('now'), 0, 8); + + Configure::write('Asset.timestamp', true); + $result = $this->Javascript->link('__cake_js_test'); + $this->assertPattern('/^]+src=".*js\/__cake_js_test\.js\?' . $timestamp . '[0-9]{2}"[^<>]*>/', $result); + + $debug = Configure::read('debug'); + Configure::write('debug', 0); + $result = $this->Javascript->link('__cake_js_test'); + $expected = ''; + $this->assertEqual($result, $expected); + + Configure::write('Asset.timestamp', 'force'); + $result = $this->Javascript->link('__cake_js_test'); + $this->assertPattern('/^]+src=".*js\/__cake_js_test.js\?' . $timestamp . '[0-9]{2}"[^<>]*>/', $result); + + Configure::write('debug', $debug); + Configure::write('Asset.timestamp', false); + + $old = Configure::read('Asset.filter.js'); + + Configure::write('Asset.filter.js', 'js.php'); + $result = $this->Javascript->link('__cake_js_test'); + $this->assertPattern('/^]+src=".*cjs\/__cake_js_test\.js"[^<>]*>/', $result); + + Configure::write('Asset.filter.js', true); + $result = $this->Javascript->link('jquery-1.1.2'); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->link('folderjs/jquery-1.1.2'); + $expected = ''; + $this->assertEqual($result, $expected); + + if ($old === null) { + Configure::delete('Asset.filter.js'); + } + + $debug = Configure::read('debug'); + $webroot = $this->Javascript->webroot; + + Configure::write('debug', 0); + Configure::write('Asset.timestamp', 'force'); + + $this->Javascript->webroot = '/testing/'; + $result = $this->Javascript->link('__cake_js_test'); + $this->assertPattern('/^]+src="\/testing\/js\/__cake_js_test\.js\?\d+"[^<>]*>/', $result); + + $this->Javascript->webroot = '/testing/longer/'; + $result = $this->Javascript->link('__cake_js_test'); + $this->assertPattern('/^]+src="\/testing\/longer\/js\/__cake_js_test\.js\?\d+"[^<>]*>/', $result); + + $this->Javascript->webroot = $webroot; + Configure::write('debug', $debug); + + unlink(JS . '__cake_js_test.js'); + } + +/** + * testValue method + * + * @access public + * @return void + */ + function testValue() { + $result = $this->Javascript->value(array('title' => 'New thing', 'indexes' => array(5, 6, 7, 8))); + $expected = '{"title":"New thing","indexes":[5,6,7,8]}'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->value(null); + $this->assertEqual($result, 'null'); + + $result = $this->Javascript->value(true); + $this->assertEqual($result, 'true'); + + $result = $this->Javascript->value(false); + $this->assertEqual($result, 'false'); + + $result = $this->Javascript->value(5); + $this->assertEqual($result, '5'); + + $result = $this->Javascript->value(floatval(5.3)); + $this->assertPattern('/^5.3[0]+$/', $result); + + $result = $this->Javascript->value(''); + $expected = '""'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->value('CakePHP' . "\n" . 'Rapid Development Framework'); + $expected = '"CakePHP\\nRapid Development Framework"'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->value('CakePHP' . "\r\n" . 'Rapid Development Framework' . "\r" . 'For PHP'); + $expected = '"CakePHP\\nRapid Development Framework\\nFor PHP"'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->value('CakePHP: "Rapid Development Framework"'); + $expected = '"CakePHP: \\"Rapid Development Framework\\""'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->value('CakePHP: \'Rapid Development Framework\''); + $expected = '"CakePHP: \\\'Rapid Development Framework\\\'"'; + $this->assertEqual($result, $expected); + } + +/** + * testObjectGeneration method + * + * @access public + * @return void + */ + function testObjectGeneration() { + $object = array('title' => 'New thing', 'indexes' => array(5, 6, 7, 8)); + $result = $this->Javascript->object($object); + $expected = '{"title":"New thing","indexes":[5,6,7,8]}'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->object(array('default' => 0)); + $expected = '{"default":0}'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->object(array( + '2007' => array( + 'Spring' => array('1' => array('id' => 1, 'name' => 'Josh'), '2' => array('id' => 2, 'name' => 'Becky')), + 'Fall' => array('1' => array('id' => 1, 'name' => 'Josh'), '2' => array('id' => 2, 'name' => 'Becky')) + ), '2006' => array( + 'Spring' => array('1' => array('id' => 1, 'name' => 'Josh'), '2' => array('id' => 2, 'name' => 'Becky')), + 'Fall' => array('1' => array('id' => 1, 'name' => 'Josh'), '2' => array('id' => 2, 'name' => 'Becky') + )) + )); + $expected = '{"2007":{"Spring":{"1":{"id":1,"name":"Josh"},"2":{"id":2,"name":"Becky"}},"Fall":{"1":{"id":1,"name":"Josh"},"2":{"id":2,"name":"Becky"}}},"2006":{"Spring":{"1":{"id":1,"name":"Josh"},"2":{"id":2,"name":"Becky"}},"Fall":{"1":{"id":1,"name":"Josh"},"2":{"id":2,"name":"Becky"}}}}'; + $this->assertEqual($result, $expected); + + if (ini_get('precision') >= 12) { + $number = 3.141592653589; + if (!$this->Javascript->useNative) { + $number = sprintf("%.11f", $number); + } + + $result = $this->Javascript->object(array('Object' => array(true, false, 1, '02101', 0, -1, 3.141592653589, "1"))); + $expected = '{"Object":[true,false,1,"02101",0,-1,' . $number . ',"1"]}'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->object(array('Object' => array(true => true, false, -3.141592653589, -10))); + $expected = '{"Object":{"1":true,"2":false,"3":' . (-1 * $number) . ',"4":-10}}'; + $this->assertEqual($result, $expected); + } + + $result = $this->Javascript->object(new TestJavascriptObject()); + $expected = '{"property1":"value1","property2":2}'; + $this->assertEqual($result, $expected); + + $object = array('title' => 'New thing', 'indexes' => array(5, 6, 7, 8)); + $result = $this->Javascript->object($object, array('block' => true)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + '{"title":"New thing","indexes":[5,6,7,8]}', + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $object = array('title' => 'New thing', 'indexes' => array(5, 6, 7, 8), 'object' => array('inner' => array('value' => 1))); + $result = $this->Javascript->object($object); + $expected = '{"title":"New thing","indexes":[5,6,7,8],"object":{"inner":{"value":1}}}'; + $this->assertEqual($result, $expected); + + foreach (array('true' => true, 'false' => false, 'null' => null) as $expected => $data) { + $result = $this->Javascript->object($data); + $this->assertEqual($result, $expected); + } + + $object = array('title' => 'New thing', 'indexes' => array(5, 6, 7, 8), 'object' => array('inner' => array('value' => 1))); + $result = $this->Javascript->object($object, array('prefix' => 'PREFIX', 'postfix' => 'POSTFIX')); + $this->assertPattern('/^PREFIX/', $result); + $this->assertPattern('/POSTFIX$/', $result); + $this->assertNoPattern('/.PREFIX./', $result); + $this->assertNoPattern('/.POSTFIX./', $result); + + if ($this->Javascript->useNative) { + $this->Javascript->useNative = false; + $this->testObjectGeneration(); + $this->Javascript->useNative = true; + } + } + +/** + * testObjectNonNative method + * + * @access public + * @return void + */ + function testObjectNonNative() { + $oldNative = $this->Javascript->useNative; + $this->Javascript->useNative = false; + + $object = array( + 'Object' => array( + 'key1' => 'val1', + 'key2' => 'val2', + 'key3' => 'val3' + ) + ); + + $expected = '{"Object":{"key1":val1,"key2":"val2","key3":val3}}'; + $result = $this->Javascript->object($object, array('quoteKeys' => false, 'stringKeys' => array('key1', 'key3'))); + $this->assertEqual($result, $expected); + + $expected = '{?Object?:{?key1?:"val1",?key2?:"val2",?key3?:"val3"}}'; + $result = $this->Javascript->object($object, array('q' => '?')); + $this->assertEqual($result, $expected); + + $expected = '{?Object?:{?key1?:"val1",?key2?:val2,?key3?:"val3"}}'; + $result = $this->Javascript->object($object, array( + 'q' => '?', 'stringKeys' => array('key3', 'key1') + )); + $this->assertEqual($result, $expected); + + $expected = '{?Object?:{?key1?:val1,?key2?:"val2",?key3?:val3}}'; + $result = $this->Javascript->object($object, array( + 'q' => '?', 'stringKeys' => array('key3', 'key1'), 'quoteKeys' => false + )); + $this->assertEqual($result, $expected); + + $this->Javascript->useNative = $oldNative; + } + +/** + * testScriptBlock method + * + * @access public + * @return void + */ + function testScriptBlock() { + $result = $this->Javascript->codeBlock('something'); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + 'something', + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->codeBlock('something', array('allowCache' => true, 'safe' => false)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + 'something', + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->codeBlock('something', array('allowCache' => false, 'safe' => false)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + 'something', + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->codeBlock('something', true); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + 'something', + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->codeBlock('something', false); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + 'something', + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->codeBlock('something', array('safe' => false)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + 'something', + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->codeBlock('something', array('safe' => true)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + 'something', + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->codeBlock(null, array('safe' => true, 'allowCache' => false)); + $this->assertNull($result); + echo 'this is some javascript'; + + $result = $this->Javascript->blockEnd(); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + 'this is some javascript', + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->codeBlock(); + $this->assertNull($result); + echo "alert('hey');"; + $result = $this->Javascript->blockEnd(); + + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "alert('hey');", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $this->Javascript->cacheEvents(false, true); + $this->assertFalse($this->Javascript->inBlock); + + $result = $this->Javascript->codeBlock(); + $this->assertIdentical($result, null); + $this->assertTrue($this->Javascript->inBlock); + echo 'alert("this is a buffered script");'; + + $result = $this->Javascript->blockEnd(); + $this->assertIdentical($result, null); + $this->assertFalse($this->Javascript->inBlock); + + $result = $this->Javascript->getCache(); + $this->assertEqual('alert("this is a buffered script");', $result); + } + +/** + * testOutOfLineScriptWriting method + * + * @access public + * @return void + */ + function testOutOfLineScriptWriting() { + echo $this->Javascript->codeBlock('$(document).ready(function() { });', array('inline' => false)); + + $this->Javascript->codeBlock(null, array('inline' => false)); + echo '$(function(){ });'; + $this->Javascript->blockEnd(); + $script = $this->View->scripts(); + + $this->assertEqual(count($script), 2); + $this->assertPattern('/' . preg_quote('$(document).ready(function() { });', '/') . '/', $script[0]); + $this->assertPattern('/' . preg_quote('$(function(){ });', '/') . '/', $script[1]); + } + +/** + * testEvent method + * + * @access public + * @return void + */ + function testEvent() { + $result = $this->Javascript->event('myId', 'click', 'something();'); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "Event.observe($('myId'), 'click', function(event) { something(); }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->event('myId', 'click', 'something();', array('safe' => false)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + "Event.observe($('myId'), 'click', function(event) { something(); }, false);", + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->event('myId', 'click'); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "Event.observe($('myId'), 'click', function(event) { }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->event('myId', 'click', 'something();', false); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "Event.observe($('myId'), 'click', function(event) { something(); }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->event('myId', 'click', 'something();', array('useCapture' => true)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "Event.observe($('myId'), 'click', function(event) { something(); }, true);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->event('document', 'load'); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "Event.observe(document, 'load', function(event) { }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->event('$(\'myId\')', 'click', 'something();', array('safe' => false)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + "Event.observe($('myId'), 'click', function(event) { something(); }, false);", + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->event('\'document\'', 'load', 'something();', array('safe' => false)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + "Event.observe('document', 'load', function(event) { something(); }, false);", + '/script' + ); + $this->assertTags($result, $expected); + + $this->Javascript->cacheEvents(); + $result = $this->Javascript->event('myId', 'click', 'something();'); + $this->assertNull($result); + + $result = $this->Javascript->getCache(); + $this->assertPattern('/^' . str_replace('/', '\\/', preg_quote('Event.observe($(\'myId\'), \'click\', function(event) { something(); }, false);')) . '$/s', $result); + + $result = $this->Javascript->event('#myId', 'alert(event);'); + $this->assertNull($result); + + $result = $this->Javascript->getCache(); + $this->assertPattern('/^\s*var Rules = {\s*\'#myId\': function\(element, event\)\s*{\s*alert\(event\);\s*}\s*}\s*EventSelectors\.start\(Rules\);\s*$/s', $result); + } + +/** + * testWriteEvents method + * + * @access public + * @return void + */ + function testWriteEvents() { + $this->Javascript->cacheEvents(); + $result = $this->Javascript->event('myId', 'click', 'something();'); + $this->assertNull($result); + + $result = $this->Javascript->writeEvents(); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "Event.observe($('myId'), 'click', function(event) { something(); }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->getCache(); + $this->assertTrue(empty($result)); + + $this->Javascript->cacheEvents(); + $result = $this->Javascript->event('myId', 'click', 'something();'); + $this->assertNull($result); + + $result = $this->Javascript->writeEvents(false); + $resultScripts = $this->View->scripts(); + reset($resultScripts); + $this->assertNull($result); + $this->assertEqual(count($resultScripts), 1); + $result = current($resultScripts); + + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "Event.observe($('myId'), 'click', function(event) { something(); }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->getCache(); + $this->assertTrue(empty($result)); + } + +/** + * testEscapeScript method + * + * @access public + * @return void + */ + function testEscapeScript() { + $result = $this->Javascript->escapeScript(''); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeScript('CakePHP' . "\n" . 'Rapid Development Framework'); + $expected = 'CakePHP\\nRapid Development Framework'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeScript('CakePHP' . "\r\n" . 'Rapid Development Framework' . "\r" . 'For PHP'); + $expected = 'CakePHP\\nRapid Development Framework\\nFor PHP'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeScript('CakePHP: "Rapid Development Framework"'); + $expected = 'CakePHP: \\"Rapid Development Framework\\"'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeScript('CakePHP: \'Rapid Development Framework\''); + $expected = 'CakePHP: \\\'Rapid Development Framework\\\''; + $this->assertEqual($result, $expected); + } + +/** + * testEscapeString method + * + * @access public + * @return void + */ + function testEscapeString() { + $result = $this->Javascript->escapeString(''); + $expected = ''; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeString('CakePHP' . "\n" . 'Rapid Development Framework'); + $expected = 'CakePHP\\nRapid Development Framework'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeString('CakePHP' . "\r\n" . 'Rapid Development Framework' . "\r" . 'For PHP'); + $expected = 'CakePHP\\nRapid Development Framework\\nFor PHP'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeString('CakePHP: "Rapid Development Framework"'); + $expected = 'CakePHP: \\"Rapid Development Framework\\"'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeString('CakePHP: \'Rapid Development Framework\''); + $expected = "CakePHP: \\'Rapid Development Framework\\'"; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeString('my \\"string\\"'); + $expected = 'my \\\\\\"string\\\\\\"'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeString('my string\nanother line'); + $expected = 'my string\\\nanother line'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeString('String with \n string that looks like newline'); + $expected = 'String with \\\n string that looks like newline'; + $this->assertEqual($result, $expected); + + $result = $this->Javascript->escapeString('String with \n string that looks like newline'); + $expected = 'String with \\\n string that looks like newline'; + $this->assertEqual($result, $expected); + } + +/** + * test string escaping and compare to json_encode() + * + * @return void + */ + function testStringJsonEncodeCompliance() { + if (!function_exists('json_encode')) { + return; + } + $this->Javascript->useNative = false; + $data = array(); + $data['mystring'] = "simple string"; + $this->assertEqual(json_encode($data), $this->Javascript->object($data)); + + $data['mystring'] = "strïng with spécial chârs"; + $this->assertEqual(json_encode($data), $this->Javascript->object($data)); + + $data['mystring'] = "a two lines\nstring"; + $this->assertEqual(json_encode($data), $this->Javascript->object($data)); + + $data['mystring'] = "a \t tabbed \t string"; + $this->assertEqual(json_encode($data), $this->Javascript->object($data)); + + $data['mystring'] = "a \"double-quoted\" string"; + $this->assertEqual(json_encode($data), $this->Javascript->object($data)); + + $data['mystring'] = 'a \\"double-quoted\\" string'; + $this->assertEqual(json_encode($data), $this->Javascript->object($data)); + } + +/** + * test that text encoded with Javascript::object decodes properly + * + * @return void + */ + function testObjectDecodeCompatibility() { + if (!function_exists('json_decode')) { + return; + } + $this->Javascript->useNative = false; + + $data = array("simple string"); + $result = $this->Javascript->object($data); + $this->assertEqual(json_decode($result), $data); + + $data = array('my \"string\"'); + $result = $this->Javascript->object($data); + $this->assertEqual(json_decode($result), $data); + + $data = array('my \\"string\\"'); + $result = $this->Javascript->object($data); + $this->assertEqual(json_decode($result), $data); + } + +/** + * testAfterRender method + * + * @access public + * @return void + */ + function testAfterRender() { + $this->Javascript->cacheEvents(); + $result = $this->Javascript->event('myId', 'click', 'something();'); + $this->assertNull($result); + + ob_start(); + $this->Javascript->afterRender(); + $result = ob_get_clean(); + + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "Event.observe($('myId'), 'click', function(event) { something(); }, false);", + $this->cDataEnd, + '/script' + ); + $this->assertTags($result, $expected); + + $result = $this->Javascript->getCache(); + $this->assertTrue(empty($result)); + + $old = $this->Javascript->enabled; + $this->Javascript->enabled = false; + + $this->Javascript->cacheEvents(); + $result = $this->Javascript->event('myId', 'click', 'something();'); + $this->assertNull($result); + + ob_start(); + $this->Javascript->afterRender(); + $result = ob_get_clean(); + + $this->assertTrue(empty($result)); + + $result = $this->Javascript->getCache(); + $this->assertPattern('/^' . str_replace('/', '\\/', preg_quote('Event.observe($(\'myId\'), \'click\', function(event) { something(); }, false);')) . '$/s', $result); + + $this->Javascript->enabled = $old; + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/jquery_engine.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/jquery_engine.test.php new file mode 100644 index 000000000..b14144dcf --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/jquery_engine.test.php @@ -0,0 +1,367 @@ + + * Copyright 2006-2010, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2006-2010, Cake Software Foundation, Inc. + * @link http://cakephp.org CakePHP Project + * @package cake.tests + * @subpackage cake.tests.cases.views.helpers + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +App::import('Helper', array('Html', 'Js', 'JqueryEngine')); + +class JqueryEngineHelperTestCase extends CakeTestCase { +/** + * startTest + * + * @return void + */ + function startTest() { + $this->Jquery =& new JqueryEngineHelper(); + } + +/** + * end test + * + * @return void + */ + function endTest() { + unset($this->Jquery); + } + +/** + * test selector method + * + * @return void + */ + function testSelector() { + $result = $this->Jquery->get('#content'); + $this->assertEqual($result, $this->Jquery); + $this->assertEqual($this->Jquery->selection, '$("#content")'); + + $result = $this->Jquery->get('document'); + $this->assertEqual($result, $this->Jquery); + $this->assertEqual($this->Jquery->selection, '$(document)'); + + $result = $this->Jquery->get('window'); + $this->assertEqual($result, $this->Jquery); + $this->assertEqual($this->Jquery->selection, '$(window)'); + + $result = $this->Jquery->get('ul'); + $this->assertEqual($result, $this->Jquery); + $this->assertEqual($this->Jquery->selection, '$("ul")'); + } + +/** + * test event binding + * + * @return void + */ + function testEvent() { + $this->Jquery->get('#myLink'); + $result = $this->Jquery->event('click', 'doClick', array('wrap' => false)); + $expected = '$("#myLink").bind("click", doClick);'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->event('click', '$(this).show();', array('stop' => false)); + $expected = '$("#myLink").bind("click", function (event) {$(this).show();});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->event('click', '$(this).hide();'); + $expected = '$("#myLink").bind("click", function (event) {$(this).hide();'."\n".'return false;});'; + $this->assertEqual($result, $expected); + } + +/** + * test dom ready event creation + * + * @return void + */ + function testDomReady() { + $result = $this->Jquery->domReady('foo.name = "bar";'); + $expected = '$(document).ready(function () {foo.name = "bar";});'; + $this->assertEqual($result, $expected); + } + +/** + * test Each method + * + * @return void + */ + function testEach() { + $this->Jquery->get('#foo'); + $result = $this->Jquery->each('$(this).hide();'); + $expected = '$("#foo").each(function () {$(this).hide();});'; + $this->assertEqual($result, $expected); + } + +/** + * test Effect generation + * + * @return void + */ + function testEffect() { + $this->Jquery->get('#foo'); + $result = $this->Jquery->effect('show'); + $expected = '$("#foo").show();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->effect('hide'); + $expected = '$("#foo").hide();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->effect('hide', array('speed' => 'fast')); + $expected = '$("#foo").hide("fast");'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->effect('fadeIn'); + $expected = '$("#foo").fadeIn();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->effect('fadeOut'); + $expected = '$("#foo").fadeOut();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->effect('slideIn'); + $expected = '$("#foo").slideDown();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->effect('slideOut'); + $expected = '$("#foo").slideUp();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->effect('slideDown'); + $expected = '$("#foo").slideDown();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->effect('slideUp'); + $expected = '$("#foo").slideUp();'; + $this->assertEqual($result, $expected); + } + +/** + * Test Request Generation + * + * @return void + */ + function testRequest() { + $result = $this->Jquery->request(array('controller' => 'posts', 'action' => 'view', 1)); + $expected = '$.ajax({url:"\\/posts\\/view\\/1"});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->request(array('controller' => 'posts', 'action' => 'view', 1), array( + 'update' => '#content' + )); + $expected = '$.ajax({dataType:"html", success:function (data, textStatus) {$("#content").html(data);}, url:"\/posts\/view\/1"});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->request('/people/edit/1', array( + 'method' => 'post', + 'before' => 'doBefore', + 'complete' => 'doComplete', + 'success' => 'doSuccess', + 'error' => 'handleError', + 'type' => 'json', + 'data' => array('name' => 'jim', 'height' => '185cm'), + 'wrapCallbacks' => false + )); + $expected = '$.ajax({beforeSend:doBefore, complete:doComplete, data:"name=jim&height=185cm", dataType:"json", error:handleError, success:doSuccess, type:"post", url:"\\/people\\/edit\\/1"});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->request('/people/edit/1', array( + 'update' => '#updated', + 'success' => 'doFoo', + 'method' => 'post', + 'wrapCallbacks' => false + )); + $expected = '$.ajax({dataType:"html", success:function (data, textStatus) {doFoo$("#updated").html(data);}, type:"post", url:"\\/people\\/edit\\/1"});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->request('/people/edit/1', array( + 'update' => '#updated', + 'success' => 'doFoo', + 'method' => 'post', + 'dataExpression' => true, + 'data' => '$("#someId").serialize()', + 'wrapCallbacks' => false + )); + $expected = '$.ajax({data:$("#someId").serialize(), dataType:"html", success:function (data, textStatus) {doFoo$("#updated").html(data);}, type:"post", url:"\\/people\\/edit\\/1"});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->request('/people/edit/1', array( + 'success' => 'doFoo', + 'before' => 'doBefore', + 'method' => 'post', + 'dataExpression' => true, + 'data' => '$("#someId").serialize()', + )); + $expected = '$.ajax({beforeSend:function (XMLHttpRequest) {doBefore}, data:$("#someId").serialize(), success:function (data, textStatus) {doFoo}, type:"post", url:"\\/people\\/edit\\/1"});'; + $this->assertEqual($result, $expected); + } + +/** + * test that alternate jQuery object values work for request() + * + * @return void + */ + function testRequestWithAlternateJqueryObject() { + $this->Jquery->jQueryObject = '$j'; + + $result = $this->Jquery->request('/people/edit/1', array( + 'update' => '#updated', + 'success' => 'doFoo', + 'method' => 'post', + 'dataExpression' => true, + 'data' => '$j("#someId").serialize()', + 'wrapCallbacks' => false + )); + $expected = '$j.ajax({data:$j("#someId").serialize(), dataType:"html", success:function (data, textStatus) {doFoo$j("#updated").html(data);}, type:"post", url:"\\/people\\/edit\\/1"});'; + $this->assertEqual($result, $expected); + } + +/** + * test sortable list generation + * + * @return void + */ + function testSortable() { + $this->Jquery->get('#myList'); + $result = $this->Jquery->sortable(array( + 'distance' => 5, + 'containment' => 'parent', + 'start' => 'onStart', + 'complete' => 'onStop', + 'sort' => 'onSort', + 'wrapCallbacks' => false + )); + $expected = '$("#myList").sortable({containment:"parent", distance:5, sort:onSort, start:onStart, stop:onStop});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->sortable(array( + 'distance' => 5, + 'containment' => 'parent', + 'start' => 'onStart', + 'complete' => 'onStop', + 'sort' => 'onSort', + )); + $expected = '$("#myList").sortable({containment:"parent", distance:5, sort:function (event, ui) {onSort}, start:function (event, ui) {onStart}, stop:function (event, ui) {onStop}});'; + $this->assertEqual($result, $expected); + } + +/** + * test drag() method + * + * @return void + */ + function testDrag() { + $this->Jquery->get('#element'); + $result = $this->Jquery->drag(array( + 'container' => '#content', + 'start' => 'onStart', + 'drag' => 'onDrag', + 'stop' => 'onStop', + 'snapGrid' => array(10, 10), + 'wrapCallbacks' => false + )); + $expected = '$("#element").draggable({containment:"#content", drag:onDrag, grid:[10,10], start:onStart, stop:onStop});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->drag(array( + 'container' => '#content', + 'start' => 'onStart', + 'drag' => 'onDrag', + 'stop' => 'onStop', + 'snapGrid' => array(10, 10), + )); + $expected = '$("#element").draggable({containment:"#content", drag:function (event, ui) {onDrag}, grid:[10,10], start:function (event, ui) {onStart}, stop:function (event, ui) {onStop}});'; + $this->assertEqual($result, $expected); + } + +/** + * test drop() method + * + * @return void + */ + function testDrop() { + $this->Jquery->get('#element'); + $result = $this->Jquery->drop(array( + 'accept' => '.items', + 'hover' => 'onHover', + 'leave' => 'onExit', + 'drop' => 'onDrop', + 'wrapCallbacks' => false + )); + $expected = '$("#element").droppable({accept:".items", drop:onDrop, out:onExit, over:onHover});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->drop(array( + 'accept' => '.items', + 'hover' => 'onHover', + 'leave' => 'onExit', + 'drop' => 'onDrop', + )); + $expected = '$("#element").droppable({accept:".items", drop:function (event, ui) {onDrop}, out:function (event, ui) {onExit}, over:function (event, ui) {onHover}});'; + $this->assertEqual($result, $expected); + } + +/** + * test slider generation + * + * @return void + */ + function testSlider() { + $this->Jquery->get('#element'); + $result = $this->Jquery->slider(array( + 'complete' => 'onComplete', + 'change' => 'onChange', + 'min' => 0, + 'max' => 10, + 'value' => 2, + 'direction' => 'vertical', + 'wrapCallbacks' => false + )); + $expected = '$("#element").slider({change:onChange, max:10, min:0, orientation:"vertical", stop:onComplete, value:2});'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->slider(array( + 'complete' => 'onComplete', + 'change' => 'onChange', + 'min' => 0, + 'max' => 10, + 'value' => 2, + 'direction' => 'vertical', + )); + $expected = '$("#element").slider({change:function (event, ui) {onChange}, max:10, min:0, orientation:"vertical", stop:function (event, ui) {onComplete}, value:2});'; + $this->assertEqual($result, $expected); + } + +/** + * test the serializeForm method + * + * @return void + */ + function testSerializeForm() { + $this->Jquery->get('#element'); + $result = $this->Jquery->serializeForm(array('isForm' => false)); + $expected = '$("#element").closest("form").serialize();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->serializeForm(array('isForm' => true)); + $expected = '$("#element").serialize();'; + $this->assertEqual($result, $expected); + + $result = $this->Jquery->serializeForm(array('isForm' => false, 'inline' => true)); + $expected = '$("#element").closest("form").serialize()'; + $this->assertEqual($result, $expected); + } +} diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/js.test.php b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/js.test.php new file mode 100644 index 000000000..dd8b279e5 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/cases/libs/view/helpers/js.test.php @@ -0,0 +1,833 @@ + + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.libs.view.helpers + * @since CakePHP(tm) v 1.3 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +App::import('Helper', array('Js', 'Html', 'Form')); +App::import('Core', array('View', 'ClassRegistry')); + +Mock::generate('JsBaseEngineHelper', 'TestJsEngineHelper', array('methodOne')); +Mock::generate('View', 'JsHelperMockView'); + +class OptionEngineHelper extends JsBaseEngineHelper { + var $_optionMap = array( + 'request' => array( + 'complete' => 'success', + 'request' => 'beforeSend', + 'type' => 'dataType' + ) + ); + +/** + * test method for testing option mapping + * + * @return array + */ + function testMap($options = array()) { + return $this->_mapOptions('request', $options); + } +/** + * test method for option parsing + * + * @return void + */ + function testParseOptions($options, $safe = array()) { + return $this->_parseOptions($options, $safe); + } +} + +/** + * JsHelper TestCase. + * + * @package cake + * @subpackage cake.tests.cases.libs.view.helpers + */ +class JsHelperTestCase extends CakeTestCase { +/** + * Regexp for CDATA start block + * + * @var string + */ + var $cDataStart = 'preg:/^\/\/[\s\r\n]*/'; + +/** + * startTest method + * + * @access public + * @return void + */ + function startTest() { + $this->_asset = Configure::read('Asset.timestamp'); + Configure::write('Asset.timestamp', false); + + $this->Js =& new JsHelper('JsBase'); + $this->Js->Html =& new HtmlHelper(); + $this->Js->Form =& new FormHelper(); + $this->Js->Form->Html =& new HtmlHelper(); + $this->Js->JsBaseEngine =& new JsBaseEngineHelper(); + + $view =& new JsHelperMockView(); + ClassRegistry::addObject('view', $view); + } + +/** + * endTest method + * + * @access public + * @return void + */ + function endTest() { + Configure::write('Asset.timestamp', $this->_asset); + ClassRegistry::removeObject('view'); + unset($this->Js); + } + +/** + * Switches $this->Js to a mocked engine. + * + * @return void + */ + function _useMock() { + $this->Js =& new JsHelper(array('TestJs')); + $this->Js->TestJsEngine =& new TestJsEngineHelper($this); + $this->Js->Html =& new HtmlHelper(); + $this->Js->Form =& new FormHelper(); + $this->Js->Form->Html =& new HtmlHelper(); + } + +/** + * test object construction + * + * @return void + */ + function testConstruction() { + $js =& new JsHelper(); + $this->assertEqual($js->helpers, array('Html', 'Form', 'JqueryEngine')); + + $js =& new JsHelper(array('mootools')); + $this->assertEqual($js->helpers, array('Html', 'Form', 'mootoolsEngine')); + + $js =& new JsHelper('prototype'); + $this->assertEqual($js->helpers, array('Html', 'Form', 'prototypeEngine')); + + $js =& new JsHelper('MyPlugin.Dojo'); + $this->assertEqual($js->helpers, array('Html', 'Form', 'MyPlugin.DojoEngine')); + } + +/** + * test that methods dispatch internally and to the engine class + * + * @return void + */ + function testMethodDispatching() { + $this->_useMock(); + $this->Js->TestJsEngine->expectOnce('dispatchMethod', array(new PatternExpectation('/methodOne/i'), array())); + + $this->Js->methodOne(); + + $this->Js->TestEngine =& new StdClass(); + $this->expectError(); + $this->Js->someMethodThatSurelyDoesntExist(); + } + +/** + * Test that method dispatching respects buffer parameters and bufferedMethods Lists. + * + * @return void + */ + function testMethodDispatchWithBuffering() { + $this->_useMock(); + + $this->Js->TestJsEngine->bufferedMethods = array('event', 'sortables'); + $this->Js->TestJsEngine->setReturnValue('dispatchMethod', 'This is an event call', array('event', '*')); + + $this->Js->event('click', 'foo'); + $result = $this->Js->getBuffer(); + $this->assertEqual(count($result), 1); + $this->assertEqual($result[0], 'This is an event call'); + + $result = $this->Js->event('click', 'foo', array('buffer' => false)); + $buffer = $this->Js->getBuffer(); + $this->assertTrue(empty($buffer)); + $this->assertEqual($result, 'This is an event call'); + + $result = $this->Js->event('click', 'foo', false); + $buffer = $this->Js->getBuffer(); + $this->assertTrue(empty($buffer)); + $this->assertEqual($result, 'This is an event call'); + + $this->Js->TestJsEngine->setReturnValue('dispatchMethod', 'I am not buffered.', array('effect', '*')); + + $result = $this->Js->effect('slideIn'); + $buffer = $this->Js->getBuffer(); + $this->assertTrue(empty($buffer)); + $this->assertEqual($result, 'I am not buffered.'); + + $result = $this->Js->effect('slideIn', true); + $buffer = $this->Js->getBuffer(); + $this->assertNull($result); + $this->assertEqual(count($buffer), 1); + $this->assertEqual($buffer[0], 'I am not buffered.'); + + $result = $this->Js->effect('slideIn', array('speed' => 'slow'), true); + $buffer = $this->Js->getBuffer(); + $this->assertNull($result); + $this->assertEqual(count($buffer), 1); + $this->assertEqual($buffer[0], 'I am not buffered.'); + + $result = $this->Js->effect('slideIn', array('speed' => 'slow', 'buffer' => true)); + $buffer = $this->Js->getBuffer(); + $this->assertNull($result); + $this->assertEqual(count($buffer), 1); + $this->assertEqual($buffer[0], 'I am not buffered.'); + } + +/** + * test that writeScripts generates scripts inline. + * + * @return void + */ + function testWriteScriptsNoFile() { + $this->_useMock(); + $this->Js->buffer('one = 1;'); + $this->Js->buffer('two = 2;'); + $result = $this->Js->writeBuffer(array('onDomReady' => false, 'cache' => false, 'clear' => false)); + $expected = array( + 'script' => array('type' => 'text/javascript'), + $this->cDataStart, + "one = 1;\ntwo = 2;", + $this->cDataEnd, + '/script', + ); + $this->assertTags($result, $expected, true); + + $this->Js->TestJsEngine->expectAtLeastOnce('domReady'); + $result = $this->Js->writeBuffer(array('onDomReady' => true, 'cache' => false, 'clear' => false)); + + ClassRegistry::removeObject('view'); + $view =& new JsHelperMockView(); + ClassRegistry::addObject('view', $view); + + $view->expectCallCount('addScript', 1); + $view->expectAt(0, 'addScript', array(new PatternExpectation('/one\s\=\s1;\ntwo\s\=\s2;/'))); + $result = $this->Js->writeBuffer(array('onDomReady' => false, 'inline' => false, 'cache' => false)); + } + +/** + * test that writing the buffer with inline = false includes a script tag. + * + * @return void + */ + function testWriteBufferNotInline() { + $this->Js->set('foo', 1); + + $view =& new JsHelperMockView(); + ClassRegistry::removeObject('view'); + ClassRegistry::addObject('view', $view); + $view->expectCallCount('addScript', 1); + + $pattern = new PatternExpectation('# \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/multi_cache.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/multi_cache.ctp new file mode 100644 index 000000000..86bc4d3ae --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/multi_cache.ctp @@ -0,0 +1,40 @@ + +

    This is regular text

    + +

    A. Layout Before Content

    + log('1. layout before content') ?> +
    +element('nocache/plain'); ?> + +

    C. Layout After Test Element But Before Content

    + log('3. layout after test element but before content') ?> +
    + + +

    E. Layout After Content

    + log('5. layout after content') ?> +
    +

    Additional regular text.

    +element('nocache/contains_nocache'); stub?> + +

    G. Layout After Content And After Element With No Cache Tags

    + log('7. layout after content and after element with no cache tags') ?> +
    \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/rss/default.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/rss/default.ctp new file mode 100644 index 000000000..94067f2bf --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/rss/default.ctp @@ -0,0 +1,17 @@ +header(); + +if (!isset($channel)) { + $channel = array(); +} +if (!isset($channel['title'])) { + $channel['title'] = $title_for_layout; +} + +echo $rss->document( + $rss->channel( + array(), $channel, $content_for_layout + ) +); + +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/xml/default.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/xml/default.ctp new file mode 100644 index 000000000..566ca2158 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/layouts/xml/default.ctp @@ -0,0 +1,2 @@ +header(); ?> + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/pages/empty b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/pages/empty new file mode 100644 index 000000000..e69de29bb diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/pages/extract.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/pages/extract.ctp new file mode 100644 index 000000000..e3eede1c1 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/pages/extract.ctp @@ -0,0 +1,14 @@ + 10); + +// Plural +__n('You have %d new message.', 'You have %d new messages.', $count); +__n('You deleted %d message.', 'You deleted %d messages.', $messages['count']); + +// Domain Plural +__dn('domain', 'You have %d new message (domain).', 'You have %d new messages (domain).', '10'); +__dn('domain', 'You deleted %d message (domain).', 'You deleted %d messages (domain).', $messages['count']); + +// Duplicated Message +__('Editing this Page'); \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/pages/home.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/pages/home.ctp new file mode 100644 index 000000000..830a21fae --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/pages/home.ctp @@ -0,0 +1,82 @@ +

    Sweet, "Test App" got Baked by CakePHP!

    + + 0): + Debugger::checkSecurityKeys(); +endif; +?> +

    +'; + __('Your tmp directory is writable.'); + echo ''; + else: + echo ''; + __('Your tmp directory is NOT writable.'); + echo ''; + endif; +?> +

    +

    +'; + printf(__('The %s is being used for caching. To change the config edit APP/config/core.php ', true), ''. $settings['engine'] . 'Engine'); + echo ''; + else: + echo ''; + __('Your cache is NOT working. Please check the settings in APP/config/core.php'); + echo ''; + endif; +?> +

    +

    +'; + __('Your database configuration file is present.'); + $filePresent = true; + echo ''; + else: + echo ''; + __('Your database configuration file is NOT present.'); + echo '
    '; + __('Rename config/database.php.default to config/database.php'); + echo '
    '; + endif; +?> +

    +getDataSource('default'); +?> +

    +isConnected()): + echo ''; + __('Cake is able to connect to the database.'); + echo ''; + else: + echo ''; + __('Cake is NOT able to connect to the database.'); + echo ''; + endif; +?> +

    + +

    +

    +', APP . 'views' . DS . 'layouts' . DS . 'default.ctp.
    ', APP . 'webroot' . DS . 'css'); +?> +

    \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/cache_empty_sections.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/cache_empty_sections.ctp new file mode 100644 index 000000000..ab6fb3ae9 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/cache_empty_sections.ctp @@ -0,0 +1,2 @@ +View Content + diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/cache_form.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/cache_form.ctp new file mode 100644 index 000000000..87ec9cf5b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/cache_form.ctp @@ -0,0 +1,14 @@ +
    + + create('User');?> +
    + + input('username'); + echo $form->input('email'); + echo $form->input('password'); + ?> +
    + end('Submit');?> +
    +
    \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/helper_overwrite.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/helper_overwrite.ctp new file mode 100644 index 000000000..a3b6928fb --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/helper_overwrite.ctp @@ -0,0 +1,4 @@ +Html->link('Test link', '#'); +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/index.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/index.ctp new file mode 100644 index 000000000..ff145eeed --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/index.ctp @@ -0,0 +1 @@ +posts index \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/multiple_nocache.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/multiple_nocache.ctp new file mode 100644 index 000000000..eb397e492 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/multiple_nocache.ctp @@ -0,0 +1,15 @@ +--view start-- + + + + +this view has 3 nocache blocks + + + + + + + + +--view end-- \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/nocache_multiple_element.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/nocache_multiple_element.ctp new file mode 100644 index 000000000..a72788305 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/nocache_multiple_element.ctp @@ -0,0 +1,9 @@ + + + + + + + + +element('nocache/sub1'); ?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/scaffold.edit.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/scaffold.edit.ctp new file mode 100644 index 000000000..ff8239012 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/scaffold.edit.ctp @@ -0,0 +1 @@ +test_app posts add/edit scaffold view \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/sequencial_nocache.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/sequencial_nocache.ctp new file mode 100644 index 000000000..27dd47fd2 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/sequencial_nocache.ctp @@ -0,0 +1,24 @@ + +

    Content

    + +

    D. In View File

    + log('4. in view file') ?> +
    \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/test_nocache_tags.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/test_nocache_tags.ctp new file mode 100644 index 000000000..d1bf80e72 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/posts/test_nocache_tags.ctp @@ -0,0 +1,144 @@ + +

    + + + + + +

    +

    + + ' . $settings['engine']; + __(' is being used to cache, to change this edit config/core.php '); + echo '

    '; + + echo 'Settings:
      '; + foreach ($settings as $name => $value): + echo '
    • ' . $name . ': ' . $value . '
    • '; + endforeach; + echo '
    '; + + else: + __('NOT working.'); + echo '
    '; + if (is_writable(TMP)): + __('Edit: config/core.php to insure you have the newset version of this file and the variable $cakeCache set properly'); + endif; + endif; + ?> + +

    +

    + + '; + __('Rename config/database.php.default to config/database.php'); + endif; + ?> + +

    +getDataSource('default'); +?> +

    + + isConnected()): + __(' is able to '); + else: + __(' is NOT able to '); + endif; + __('connect to the database.'); + ?> + +

    + +

    + +

    +

    +
    +
    +
    + +

    +

    +

    +
    +
    + +

    +

    +

    + +

    +

    + +

    + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/scaffolds/empty b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/scaffolds/empty new file mode 100644 index 000000000..e69de29bb diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/tests_apps/index.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/tests_apps/index.ctp new file mode 100644 index 000000000..36aecfa1d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/tests_apps/index.ctp @@ -0,0 +1 @@ +This is the TestsAppsController index view \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/elements/test_element.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/elements/test_element.ctp new file mode 100644 index 000000000..745dddbf2 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/elements/test_element.ctp @@ -0,0 +1 @@ +Hi, I'm the test element. \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/layouts/default.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/layouts/default.ctp new file mode 100644 index 000000000..89107d393 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/layouts/default.ctp @@ -0,0 +1 @@ +default test_theme layout \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/plugins/test_plugin/layouts/plugin_default.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/plugins/test_plugin/layouts/plugin_default.ctp new file mode 100644 index 000000000..9e1e449c2 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/plugins/test_plugin/layouts/plugin_default.ctp @@ -0,0 +1 @@ +test_plugin test_plugin_theme default layout \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/plugins/test_plugin/tests/index.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/plugins/test_plugin/tests/index.ctp new file mode 100644 index 000000000..edf7ab4b2 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/plugins/test_plugin/tests/index.ctp @@ -0,0 +1 @@ +test plugin index theme view \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/posts/index.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/posts/index.ctp new file mode 100644 index 000000000..3ec978c95 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/posts/index.ctp @@ -0,0 +1 @@ +posts index themed view \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/posts/scaffold.index.ctp b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/posts/scaffold.index.ctp new file mode 100644 index 000000000..a7829877c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/posts/scaffold.index.ctp @@ -0,0 +1 @@ +I'm a themed scaffold file. \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/css/test_asset.css b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/css/test_asset.css new file mode 100644 index 000000000..e8b09dd29 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/css/test_asset.css @@ -0,0 +1 @@ +this is the test asset css file \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/css/theme_webroot.css b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/css/theme_webroot.css new file mode 100644 index 000000000..12e29a565 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/css/theme_webroot.css @@ -0,0 +1 @@ +theme webroot css file \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/flash/theme_test.swf b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/flash/theme_test.swf new file mode 100644 index 000000000..cfe782b7a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/flash/theme_test.swf @@ -0,0 +1 @@ +this is just a test to load swf file from the theme. \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/img/cake.power.gif b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/img/cake.power.gif new file mode 100644 index 000000000..8f8d570a2 Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/img/cake.power.gif differ diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/img/test.jpg b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/img/test.jpg new file mode 100644 index 000000000..ca5197ae8 Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/img/test.jpg differ diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/js/one/theme_one.js b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/js/one/theme_one.js new file mode 100644 index 000000000..d29bcbc94 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/js/one/theme_one.js @@ -0,0 +1 @@ +nested theme js file \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/js/theme.js b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/js/theme.js new file mode 100644 index 000000000..ec17940de --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/js/theme.js @@ -0,0 +1 @@ +root theme js file \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/pdfs/theme_test.pdf b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/pdfs/theme_test.pdf new file mode 100644 index 000000000..9c1dcbcc4 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/views/themed/test_theme/webroot/pdfs/theme_test.pdf @@ -0,0 +1 @@ +this is just a test to load pdf file from the theme. \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/css/theme_webroot.css b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/css/theme_webroot.css new file mode 100644 index 000000000..f65d3e08a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/css/theme_webroot.css @@ -0,0 +1 @@ +override the theme webroot css file \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/css/webroot_test.css b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/css/webroot_test.css new file mode 100644 index 000000000..83c3bc4d7 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/css/webroot_test.css @@ -0,0 +1 @@ +this is the webroot test asset css file \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/img/cake.power.gif b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/img/cake.power.gif new file mode 100644 index 000000000..8f8d570a2 Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/img/cake.power.gif differ diff --git a/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/img/test.jpg b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/img/test.jpg new file mode 100644 index 000000000..ca5197ae8 Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/cake/tests/test_app/webroot/theme/test_theme/img/test.jpg differ diff --git a/code/ryzom/tools/server/www/webtt/docs/INSTALL b/code/ryzom/tools/server/www/webtt/docs/INSTALL new file mode 100644 index 000000000..3137f86e9 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/docs/INSTALL @@ -0,0 +1,31 @@ +1. Web-Based Translation Tool use ryzom translation pipeline, so you need to setup it first. + +2. Change path/to/translation to your translation pipeline path in app/config/database.php + +3. Add following VirtualHost config to your apache configuration. + + + DocumentRoot /path/to/webtt + + php_admin_value open_basedir none + php_flag short_open_tag on + php_value memory_limit 256M + php_flag output_buffering on + + ServerName webtt.your.domain + + +4. Change permissions to tmp directory. + +# chmod -R o+w webtt/app/tmp + +5. Create MySQL database with default user and WebTT tables. + +# mysql -uroot +CREATE DATABASE webtt2; +GRANT ALL ON webtt2.* TO webtt@localhost IDENTIFIED BY 'webtt77'; +# cat db/webtt2.db | mysql -uwebtt -pwebtt77 webtt2 + +6. Go to http://webtt.your.domain and register new user for user access. + +7. For administrator access, go to http://webtt.your.domain/admin and log in as admin/newpass diff --git a/code/ryzom/tools/server/www/webtt/docs/db/CakePHP_Associations b/code/ryzom/tools/server/www/webtt/docs/db/CakePHP_Associations new file mode 100644 index 000000000..addae59e6 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/docs/db/CakePHP_Associations @@ -0,0 +1,69 @@ +Name: Language +DB Table: `languages` +Associations: + Language hasMany TranslationFile + +Name: TranslationFile +DB Table: `translation_files` +Associations: + TranslationFile belongsTo Language + TranslationFile hasMany ImportedTranslationFile + TranslationFile hasMany Identifier + +Name: ImportedTranslationFile +DB Table: `imported_translation_files` +Associations: + TranslationFile belongsTo TranslationFile + TranslationFile hasMany FileIdentifier + +Name: Identifier +DB Table: `identifiers` +Associations: + Identifier belongsTo TranslationFile + Identifier hasMany Translation + Identifier hasMany Comment + Identifier hasMany FileIdentifier + Identifier hasMany IdentifierColumn + Identifier hasOne BestTranslation + +Name: IdentifierColumn +DB Table: `identifier_columns` +Associations: + IdentifierColumn belongsTo Identifier + IdentifierColumn hasMany Translation + +Name: FileIdentifier +DB Table: `file_identifiers` +Associations: + FileIdentifier belongsTo ImportedTranslationFile + FileIdentifier belongsTo Identifier + +Name: Translation +DB Table: `translations` +Associations: + Translation belongsTo Identifier + Translation belongsTo IdentifierColumn + Translation belongsTo User + Translation belongsTo ParentTranslation + Translation hasMany Vote + Translation hasMany ChildTranslation + +Name: User +DB Table: `users` +Associations: + User hasMany Translation + User hasMany Vote + User hasMany Comment + +Name: Vote +DB Table: `votes` +Associations: + Vote belongsTo Translation + Vote belongsTo User + +Name: Comment +DB Table: `comments` +Associations: + Comment belongsTo Identifier + Comment belongsTo User + diff --git a/code/ryzom/tools/server/www/webtt/docs/db/erd.png b/code/ryzom/tools/server/www/webtt/docs/db/erd.png new file mode 100644 index 000000000..d49b4e49c Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/docs/db/erd.png differ diff --git a/code/ryzom/tools/server/www/webtt/docs/db/webtt2.db b/code/ryzom/tools/server/www/webtt/docs/db/webtt2.db new file mode 100644 index 000000000..41089a9d0 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/docs/db/webtt2.db @@ -0,0 +1,239 @@ +-- MySQL dump 10.13 Distrib 5.1.51, for pc-linux-gnu (x86_64) +-- +-- Host: localhost Database: webtt2 +-- ------------------------------------------------------ +-- Server version 5.1.51 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `comments` +-- + +DROP TABLE IF EXISTS `comments`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `comments` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `translation_id` int(10) unsigned DEFAULT NULL, + `identifier_id` int(10) unsigned DEFAULT NULL, + `user_id` int(10) unsigned DEFAULT NULL, + `comment` text, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `identifier_id` (`identifier_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `file_identifiers` +-- + +DROP TABLE IF EXISTS `file_identifiers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `file_identifiers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `imported_translation_file_id` int(10) unsigned DEFAULT NULL, + `command` varchar(50) DEFAULT NULL, + `translation_index` int(10) unsigned DEFAULT NULL, + `identifier_id` int(10) unsigned DEFAULT NULL, + `arguments` text, + `reference_string` text, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `imported_translation_file_id` (`imported_translation_file_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `identifier_columns` +-- + +DROP TABLE IF EXISTS `identifier_columns`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `identifier_columns` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `identifier_id` int(10) unsigned DEFAULT NULL, + `column_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, + `reference_string` text, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `identifier_id` (`identifier_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `identifiers` +-- + +DROP TABLE IF EXISTS `identifiers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `identifiers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `language_id` int(10) unsigned DEFAULT NULL, + `translation_file_id` int(10) unsigned DEFAULT NULL, + `translation_index` int(10) unsigned DEFAULT NULL, + `identifier` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, + `arguments` text, + `reference_string` text, + `context_description` text, + `translated` tinyint(1) DEFAULT '0', + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `translation_file_id` (`translation_file_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `imported_translation_files` +-- + +DROP TABLE IF EXISTS `imported_translation_files`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `imported_translation_files` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `language_id` int(10) unsigned DEFAULT NULL, + `translation_file_id` int(10) unsigned DEFAULT NULL, + `filename` varchar(255) DEFAULT NULL, + `merged` tinyint(1) DEFAULT '0', + `file_last_modified_date` int(11) DEFAULT NULL, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `translation_file_id` (`translation_file_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `languages` +-- + +DROP TABLE IF EXISTS `languages`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `languages` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(50) DEFAULT NULL, + `code` varchar(10) DEFAULT NULL, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `code` (`code`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `translation_files` +-- + +DROP TABLE IF EXISTS `translation_files`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `translation_files` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `language_id` int(10) unsigned DEFAULT NULL, + `filename_template` varchar(255) DEFAULT NULL, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `language_id` (`language_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `translations` +-- + +DROP TABLE IF EXISTS `translations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `translations` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `parent_id` int(10) unsigned DEFAULT NULL, + `identifier_id` int(10) unsigned DEFAULT NULL, + `identifier_column_id` int(10) unsigned DEFAULT NULL, + `translation_text` text, + `user_id` int(10) unsigned DEFAULT NULL, + `best` tinyint(1) DEFAULT NULL, + `translation_hash` varchar(32) DEFAULT NULL, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `best` (`best`), + KEY `identifier_column_id` (`identifier_column_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `users` +-- + +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(100) DEFAULT NULL, + `email` varchar(200) DEFAULT NULL, + `activated` tinyint(1) DEFAULT NULL, + `username` varchar(40) DEFAULT NULL, + `password` varchar(100) DEFAULT NULL, + `role` varchar(20) DEFAULT NULL, + `confirm_hash` varchar(40) DEFAULT NULL, + `last_login` datetime DEFAULT NULL, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `users` WRITE; +/*!40000 ALTER TABLE `users` DISABLE KEYS */; + +INSERT INTO `users` VALUES (1,'Admin','',1,'admin','9ff60bfc5939c7863518e202cba4dff81da316be','admin',NULL,NULL,NULL,NULL); + +/*!40000 ALTER TABLE `users` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `votes` +-- + +DROP TABLE IF EXISTS `votes`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `votes` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `translation_id` int(10) unsigned DEFAULT NULL, + `user_id` int(10) unsigned DEFAULT NULL, + `created` datetime DEFAULT NULL, + `modified` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/code/ryzom/tools/server/www/webtt/index.php b/code/ryzom/tools/server/www/webtt/index.php new file mode 100644 index 000000000..9fd58145e --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/index.php @@ -0,0 +1,55 @@ + 1.2` are compatible with CakePHP 1.2.x. These releases of DebugKit will not work with CakePHP 1.3. +* `1.3` is compatible with CakePHP 1.3.x only. It will not work with CakePHP 1.2. + diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/build.py b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/build.py new file mode 100755 index 000000000..ba75dcf62 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/build.py @@ -0,0 +1,130 @@ +#! /usr/bin/env python + +import sys, os +import tarfile, zipfile, gzip, bz2 +from optparse import OptionParser + +""" +Builds packaged releases of DebugKit so I don't have to do things manually. + +Excludes itself (build.py), .gitignore, .DS_Store and the .git folder from the archives. +""" +def main(): + parser = OptionParser(); + parser.add_option('-o', '--output-dir', dest="output_dir", + help="write the packages to DIR", metavar="DIR") + parser.add_option('-p', '--prefix-name', dest="prefix", + help="prefix used for the generated files") + parser.add_option('-k', '--skip', dest="skip", default="", + help="A comma separated list of files to skip") + parser.add_option('-s', '--source-dir', dest="source", default=".", + help="The source directory for the build process") + + (options, args) = parser.parse_args() + + if options.output_dir == '' or options.output_dir == options.source: + print 'Requires an output dir, and that output dir cannot be the same as the source one!' + exit() + + # append .git and build.py to the skip files + skip = options.skip.split(',') + skip.extend(['.git', '.gitignore', '.DS_Store', 'build.py']) + + # get list of files in top level dir. + files = os.listdir(options.source) + + os.chdir(options.source) + + # filter the files, I couldn't figure out how to do it in a more concise way. + for f in files[:]: + try: + skip.index(f) + files.remove(f) + except ValueError: + pass + + # make a boring tar file + destfile = ''.join([options.output_dir, options.prefix]) + tar_file_name = destfile + '.tar' + tar = tarfile.open(tar_file_name, 'w'); + for f in files: + tar.add(f) + tar.close() + print "Generated tar file" + + # make the gzip + if make_gzip(tar_file_name, destfile): + print "Generated gzip file" + else: + print "Could not generate gzip file" + + # make the bz2 + if make_bz2(tar_file_name, destfile): + print "Generated bz2 file" + else: + print "Could not generate bz2 file" + + # make the zip file + zip_recursive(destfile + '.zip', options.source, files) + print "Generated zip file\n" + +def make_gzip(tar_file, destination): + """ + Takes a tar_file and destination. Compressess the tar file and creates + a .tar.gzip + """ + tar_contents = open(tar_file, 'rb') + gzipfile = gzip.open(destination + '.tar.gz', 'wb') + gzipfile.writelines(tar_contents) + gzipfile.close() + tar_contents.close() + return True + +def make_bz2(tar_file, destination): + """ + Takes a tar_file and destination. Compressess the tar file and creates + a .tar.bz2 + """ + tar_contents = open(tar_file, 'rb') + bz2file = bz2.BZ2File(destination + '.tar.bz2', 'wb') + bz2file.writelines(tar_contents) + bz2file.close() + tar_contents.close() + return True + +def zip_recursive(destination, source_dir, rootfiles): + """ + Recursively zips source_dir into destination. + rootfiles should contain a list of files in the top level directory that + are to be included. Any top level files not in rootfiles will be omitted + from the zip file. + """ + zipped = zipfile.ZipFile(destination, 'w', zipfile.ZIP_DEFLATED) + + for root, dirs, files in os.walk(source_dir): + inRoot = False + if root == source_dir: + inRoot = True + + if inRoot: + for d in dirs: + try: + rootfiles.index(d) + except ValueError: + dirs.remove(d) + + for f in files[:]: + if inRoot: + try: + rootfiles.index(f) + except ValueError: + continue + + fullpath = os.path.join(root, f) + zipped.write(fullpath) + zipped.close() + return destination + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/controllers/components/toolbar.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/controllers/components/toolbar.php new file mode 100644 index 000000000..1066cf75f --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/controllers/components/toolbar.php @@ -0,0 +1,700 @@ + false, + 'autoRun' => true + ); + +/** + * Controller instance reference + * + * @var object + */ + var $controller; + +/** + * Components used by DebugToolbar + * + * @var array + */ + var $components = array('RequestHandler', 'Session'); + +/** + * The default panels the toolbar uses. + * which panels are used can be configured when attaching the component + * + * @var array + */ + var $_defaultPanels = array('history', 'session', 'request', 'sqlLog', 'timer', 'log', 'variables'); + +/** + * Loaded panel objects. + * + * @var array + */ + var $panels = array(); + +/** + * javascript files component will be using + * + * @var array + **/ + var $javascript = array( + 'behavior' => '/debug_kit/js/js_debug_toolbar' + ); + +/** + * CacheKey used for the cache file. + * + * @var string + **/ + var $cacheKey = 'toolbar_cache'; + +/** + * Duration of the debug kit history cache + * + * @var string + **/ + var $cacheDuration = '+4 hours'; + +/** + * initialize + * + * If debug is off the component will be disabled and not do any further time tracking + * or load the toolbar helper. + * + * @return bool + **/ + function initialize(&$controller, $settings) { + $this->settings = am($this->settings, $settings); + if (!Configure::read('debug') && empty($this->settings['forceEnable'])) { + $this->enabled = false; + return false; + } + if ($this->settings['autoRun'] == false && !isset($controller->params['url']['debug'])) { + $this->enabled = false; + return false; + } + App::import('Vendor', 'DebugKit.DebugKitDebugger'); + + DebugKitDebugger::setMemoryPoint(__d('debug_kit', 'Component initialization', true)); + DebugKitDebugger::startTimer('componentInit', __d('debug_kit', 'Component initialization and startup', true)); + + $panels = $this->_defaultPanels; + if (isset($settings['panels'])) { + $panels = $this->_makePanelList($settings['panels']); + unset($settings['panels']); + } + + $this->cacheKey .= $this->Session->read('Config.userAgent'); + if (in_array('history', $panels) || (isset($settings['history']) && $settings['history'] !== false)) { + $this->_createCacheConfig(); + } + + $this->_loadPanels($panels, $settings); + + $this->_set($settings); + $this->controller =& $controller; + return false; + } + +/** + * Go through user panels and remove default panels as indicated. + * + * @param array $userPanels The list of panels ther user has added removed. + * @return array Array of panels to use. + **/ + function _makePanelList($userPanels) { + $panels = $this->_defaultPanels; + foreach ($userPanels as $key => $value) { + if (is_numeric($key)) { + $panels[] = $value; + } + if (is_string($key) && $value === false) { + $index = array_search($key, $panels); + if ($index !== false) { + unset($panels[$index]); + } + } + } + return $panels; + } + +/** + * Component Startup + * + * @return bool + **/ + function startup(&$controller) { + $currentViewClass = $controller->view; + $this->_makeViewClass($currentViewClass); + $controller->view = 'DebugKit.Debug'; + $isHtml = ( + !isset($controller->params['url']['ext']) || + (isset($controller->params['url']['ext']) && $controller->params['url']['ext'] == 'html') + ); + + if (!$this->RequestHandler->isAjax() && $isHtml) { + $format = 'Html'; + } else { + $format = 'FirePhp'; + } + $controller->helpers['DebugKit.Toolbar'] = array( + 'output' => sprintf('DebugKit.%sToolbar', $format), + 'cacheKey' => $this->cacheKey, + 'cacheConfig' => 'debug_kit', + 'forceEnable' => $this->settings['forceEnable'], + ); + $panels = array_keys($this->panels); + foreach ($panels as $panelName) { + $this->panels[$panelName]->startup($controller); + } + DebugKitDebugger::stopTimer('componentInit'); + DebugKitDebugger::startTimer('controllerAction', __d('debug_kit', 'Controller action', true)); + DebugKitDebugger::setMemoryPoint(__d('debug_kit', 'Controller action start', true)); + } + +/** + * beforeRedirect callback + * + * @return void + **/ + function beforeRedirect(&$controller) { + if (!class_exists('DebugKitDebugger')) { + return null; + } + DebugKitDebugger::stopTimer('controllerAction'); + $vars = $this->_gatherVars($controller); + $this->_saveState($controller, $vars); + } + +/** + * beforeRender callback + * + * Calls beforeRender on all the panels and set the aggregate to the controller. + * + * @return void + **/ + function beforeRender(&$controller) { + if (!class_exists('DebugKitDebugger')) { + return null; + } + DebugKitDebugger::stopTimer('controllerAction'); + $vars = $this->_gatherVars($controller); + $this->_saveState($controller, $vars); + + $controller->set(array('debugToolbarPanels' => $vars, 'debugToolbarJavascript' => $this->javascript)); + DebugKitDebugger::startTimer('controllerRender', __d('debug_kit', 'Render Controller Action', true)); + DebugKitDebugger::setMemoryPoint(__d('debug_kit', 'Controller render start', true)); + } + +/** + * Load a toolbar state from cache + * + * @param int $key + * @return array + **/ + function loadState($key) { + $history = Cache::read($this->cacheKey, 'debug_kit'); + if (isset($history[$key])) { + return $history[$key]; + } + return array(); + } + +/** + * Create the cache config for the history + * + * @return void + * @access protected + **/ + function _createCacheConfig() { + if (Configure::read('Cache.disable') !== true) { + Cache::config('debug_kit', array( + 'duration' => $this->cacheDuration, + 'engine' => 'File', + 'path' => CACHE + )); + Cache::config('default'); + } + } + +/** + * collects the panel contents + * + * @return array Array of all panel beforeRender() + * @access protected + **/ + function _gatherVars(&$controller) { + $vars = array(); + $panels = array_keys($this->panels); + + foreach ($panels as $panelName) { + $panel =& $this->panels[$panelName]; + $panelName = Inflector::underscore($panelName); + $vars[$panelName]['content'] = $panel->beforeRender($controller); + $elementName = Inflector::underscore($panelName) . '_panel'; + if (isset($panel->elementName)) { + $elementName = $panel->elementName; + } + $vars[$panelName]['elementName'] = $elementName; + $vars[$panelName]['plugin'] = $panel->plugin; + $vars[$panelName]['title'] = $panel->title; + $vars[$panelName]['disableTimer'] = true; + } + return $vars; + } + +/** + * Load Panels used in the debug toolbar + * + * @return void + * @access protected + **/ + function _loadPanels($panels, $settings) { + foreach ($panels as $panel) { + $className = $panel . 'Panel'; + if (!class_exists($className) && !App::import('Vendor', $className)) { + trigger_error(sprintf(__d('debug_kit', 'Could not load DebugToolbar panel %s', true), $panel), E_USER_WARNING); + continue; + } + list($plugin, $className) = pluginSplit($className); + $panelObj =& new $className($settings); + if (is_subclass_of($panelObj, 'DebugPanel') || is_subclass_of($panelObj, 'debugpanel')) { + list(, $panel) = pluginSplit($panel); + $this->panels[$panel] =& $panelObj; + } + } + } +/** + * Makes the DoppleGangerView class if it doesn't already exist. + * This allows DebugView to be compatible with all view classes. + * + * @param string $baseClassName + * @access protected + * @return void + */ + function _makeViewClass($baseClassName) { + if (!class_exists('DoppelGangerView')) { + $parent = strtolower($baseClassName) === 'view' ? false : true; + App::import('View', $baseClassName, $parent); + if (strpos($baseClassName, '.') !== false) { + list($plugin, $baseClassName) = explode('.', $baseClassName); + } + if (strpos($baseClassName, 'View') === false) { + $baseClassName .= 'View'; + } + $class = "class DoppelGangerView extends $baseClassName {}"; + $this->_eval($class); + } + } + +/** + * Method wrapper for eval() for testing uses. + * + * @return void + **/ + function _eval($code) { + eval($code); + } + +/** + * Save the current state of the toolbar varibles to the cache file. + * + * @param object $controller Controller instance + * @param array $vars Vars to save. + * @access protected + * @return void + **/ + function _saveState(&$controller, $vars) { + $config = Cache::config('debug_kit'); + if (empty($config) || !isset($this->panels['history'])) { + return; + } + $history = Cache::read($this->cacheKey, 'debug_kit'); + if (empty($history)) { + $history = array(); + } + if (count($history) == $this->panels['history']->history) { + array_pop($history); + } + unset($vars['history']); + array_unshift($history, $vars); + Cache::write($this->cacheKey, $history, 'debug_kit'); + } +} + +/** + * Debug Panel + * + * Abstract class for debug panels. + * + * @package cake.debug_kit + */ +class DebugPanel extends Object { +/** + * Defines which plugin this panel is from so the element can be located. + * + * @var string + */ + var $plugin = null; + +/** + * Defines the title for displaying on the toolbar. If null, the class name will be used. + * Overriding this allows you to define a custom name in the toolbar. + * + * @var string + */ + var $title = null; + +/** + * Provide a custom element name for this panel. If null, the underscored version of the class + * name will be used. + * + * @var string + */ + var $elementName = null; + +/** + * startup the panel + * + * Pull information from the controller / request + * + * @param object $controller Controller reference. + * @return void + **/ + function startup(&$controller) { } + +/** + * Prepare output vars before Controller Rendering. + * + * @param object $controller Controller reference. + * @return void + **/ + function beforeRender(&$controller) { } +} + +/** + * History Panel + * + * Provides debug information on previous requests. + * + * @package cake.debug_kit.panels + **/ +class HistoryPanel extends DebugPanel { + + var $plugin = 'debug_kit'; + +/** + * Number of history elements to keep + * + * @var string + **/ + var $history = 5; + +/** + * Constructor + * + * @param array $settings Array of settings. + * @return void + **/ + function __construct($settings) { + if (isset($settings['history'])) { + $this->history = $settings['history']; + } + } + +/** + * beforeRender callback function + * + * @return array contents for panel + **/ + function beforeRender(&$controller) { + $cacheKey = $controller->Toolbar->cacheKey; + $toolbarHistory = Cache::read($cacheKey, 'debug_kit'); + $historyStates = array(); + if (is_array($toolbarHistory) && !empty($toolbarHistory)) { + $prefix = array(); + if (!empty($controller->params['prefix'])) { + $prefix[$controller->params['prefix']] = false; + } + foreach ($toolbarHistory as $i => $state) { + if (!isset($state['request']['content']['params']['url']['url'])) { + continue; + } + $historyStates[] = array( + 'title' => $state['request']['content']['params']['url']['url'], + 'url' => array_merge($prefix, array( + 'plugin' => 'debug_kit', + 'controller' => 'toolbar_access', + 'action' => 'history_state', + $i + 1)) + ); + } + } + if (count($historyStates) >= $this->history) { + array_pop($historyStates); + } + return $historyStates; + } +} + +/** + * Variables Panel + * + * Provides debug information on the View variables. + * + * @package cake.debug_kit.panels + **/ +class VariablesPanel extends DebugPanel { + + var $plugin = 'debug_kit'; + +/** + * beforeRender callback + * + * @return array + **/ + function beforeRender(&$controller) { + return array_merge($controller->viewVars, array('$this->data' => $controller->data)); + } +} + +/** + * Session Panel + * + * Provides debug information on the Session contents. + * + * @package cake.debug_kit.panels + **/ +class SessionPanel extends DebugPanel { + + var $plugin = 'debug_kit'; + +/** + * beforeRender callback + * + * @param object $controller + * @access public + * @return array + */ + function beforeRender(&$controller) { + $sessions = $controller->Toolbar->Session->read(); + return $sessions; + } +} + +/** + * Request Panel + * + * Provides debug information on the Current request params. + * + * @package cake.debug_kit.panels + **/ +class RequestPanel extends DebugPanel { + + var $plugin = 'debug_kit'; + +/** + * beforeRender callback - grabs request params + * + * @return array + **/ + function beforeRender(&$controller) { + $out = array(); + $out['params'] = $controller->params; + if (isset($controller->Cookie)) { + $out['cookie'] = $controller->Cookie->read(); + } + $out['get'] = $_GET; + $out['currentRoute'] = Router::currentRoute(); + return $out; + } +} + +/** + * Timer Panel + * + * Provides debug information on all timers used in a request. + * + * @package cake.debug_kit.panels + **/ +class TimerPanel extends DebugPanel { + + var $plugin = 'debug_kit'; + +/** + * startup - add in necessary helpers + * + * @return void + **/ + function startup(&$controller) { + if (!in_array('Number', $controller->helpers)) { + $controller->helpers[] = 'Number'; + } + if (!in_array('SimpleGraph', $controller->helpers)) { + $controller->helpers[] = 'DebugKit.SimpleGraph'; + } + } +} + +/** + * SqlLog Panel + * + * Provides debug information on the SQL logs and provides links to an ajax explain interface. + * + * @package cake.debug_kit.panels + **/ +class SqlLogPanel extends DebugPanel { + + var $plugin = 'debug_kit'; + +/** + * Minimum number of Rows Per Millisecond that must be returned by a query before an explain + * is done. + * + * @var int + **/ + var $slowRate = 20; + +/** + * Gets the connection names that should have logs + dumps generated. + * + * @param string $controller + * @access public + * @return void + */ + function beforeRender(&$controller) { + if (!class_exists('ConnectionManager')) { + return array(); + } + $connections = array(); + + $dbConfigs = ConnectionManager::sourceList(); + foreach ($dbConfigs as $configName) { + $driver = null; + $db =& ConnectionManager::getDataSource($configName); + if ( + (empty($db->config['driver']) && empty($db->config['datasource'])) || + !$db->isInterfaceSupported('getLog') + ) { + continue; + } + + if (isset($db->config['driver'])) { + $driver = $db->config['driver']; + } + if (empty($driver) && isset($db->config['datasource'])) { + $driver = $db->config['datasource']; + } + $explain = false; + $isExplainable = ($driver === 'mysql' || $driver === 'mysqli' || $driver === 'postgres'); + if ($isExplainable) { + $explain = true; + } + $connections[$configName] = $explain; + } + return array('connections' => $connections, 'threshold' => $this->slowRate); + } +} + +/** + * Log Panel - Reads log entries made this request. + * + * @package cake.debug_kit.panels + */ +class LogPanel extends DebugPanel { + + var $plugin = 'debug_kit'; + +/** + * Constructor - sets up the log listener. + * + * @return void + */ + function __construct($settings) { + parent::__construct(); + if (!class_exists('CakeLog')) { + App::import('Core', 'CakeLog'); + } + $existing = CakeLog::configured(); + if (empty($existing)) { + CakeLog::config('default', array( + 'engine' => 'FileLog' + )); + } + CakeLog::config('debug_kit_log_panel', array( + 'engine' => 'DebugKitLogListener', + 'panel' => $this + )); + } + +/** + * beforeRender Callback + * + * @return array + **/ + function beforeRender(&$controller) { + $logs = $this->logger->logs; + return $logs; + } +} + +/** + * A CakeLog listener which saves having to munge files or other configured loggers. + * + * @package debug_kit.components + */ +class DebugKitLogListener { + + var $logs = array(); + +/** + * Makes the reverse link needed to get the logs later. + * + * @return void + */ + function DebugKitLogListener($options) { + $options['panel']->logger =& $this; + } + +/** + * Captures log messages in memory + * + * @return void + */ + function write($type, $message) { + if (!isset($this->logs[$type])) { + $this->logs[$type] = array(); + } + $this->logs[$type][] = array(date('Y-m-d H:i:s'), $message); + } +} + diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/controllers/toolbar_access_controller.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/controllers/toolbar_access_controller.php new file mode 100644 index 000000000..5e6afdf31 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/controllers/toolbar_access_controller.php @@ -0,0 +1,111 @@ + array('output' => 'DebugKit.HtmlToolbar'), + 'Javascript', 'Number', 'DebugKit.SimpleGraph' + ); + +/** + * Components + * + * @var array + **/ + var $components = array('RequestHandler', 'DebugKit.Toolbar'); + +/** + * Uses + * + * @var array + **/ + var $uses = array('DebugKit.ToolbarAccess'); + +/** + * beforeFilter callback + * + * @return void + **/ + function beforeFilter() { + parent::beforeFilter(); + if (isset($this->Toolbar)) { + $this->Toolbar->enabled = false; + } + $this->helpers['DebugKit.Toolbar']['cacheKey'] = $this->Toolbar->cacheKey; + $this->helpers['DebugKit.Toolbar']['cacheConfig'] = 'debug_kit'; + } + +/** + * Get a stored history state from the toolbar cache. + * + * @return void + **/ + function history_state($key = null) { + if (Configure::read('debug') == 0) { + return $this->redirect($this->referer()); + } + $oldState = $this->Toolbar->loadState($key); + $this->set('toolbarState', $oldState); + $this->set('debugKitInHistoryMode', true); + } + +/** + * Run SQL explain/profiling on queries. Checks the hash + the hashed queries, + * if there is mismatch a 404 will be rendered. If debug == 0 a 404 will also be + * rendered. No explain will be run if a 404 is made. + * + * @return void + */ + function sql_explain() { + if ( + !$this->RequestHandler->isPost() || + empty($this->data['log']['sql']) || + empty($this->data['log']['ds']) || + empty($this->data['log']['hash']) || + Configure::read('debug') == 0 + ) { + $this->cakeError('error404', array(array( + 'message' => 'Invalid parameters' + ))); + } + App::import('Core', 'Security'); + $hash = Security::hash($this->data['log']['sql'] . $this->data['log']['ds'], null, true); + if ($hash !== $this->data['log']['hash']) { + $this->cakeError('error404', array(array( + 'message' => 'Invalid parameters' + ))); + } + $result = $this->ToolbarAccess->explainQuery($this->data['log']['ds'], $this->data['log']['sql']); + $this->set(compact('result')); + } +} \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/debug_kit_app_controller.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/debug_kit_app_controller.php new file mode 100644 index 000000000..3592cb5ed --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/debug_kit_app_controller.php @@ -0,0 +1,22 @@ + +# No version information was available in the source files. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: debug_kit-\n" +"POT-Creation-Date: 2009-05-27 09:47+0200\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: Andy Dawson \n" +"Language-Team:\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Basepath: ../../../\n" + +#: controllers/components/toolbar.php:91 +msgid "Component initialization and startup" +msgstr "" + +#: controllers/components/toolbar.php:140 +msgid "Controller Action" +msgstr "" + +#: controllers/components/toolbar.php:167 +msgid "Render Controller Action" +msgstr "" + +#: controllers/components/toolbar.php:231 +msgid "Could not load DebugToolbar panel %s" +msgstr "" + +#: views/elements/debug_toolbar.ctp:25 +msgid "There are no active panels. You must enable a panel to see its output." +msgstr "" + +#: views/elements/history_panel.ctp:21 +msgid "Request History" +msgstr "" + +#: views/elements/history_panel.ctp:23 +msgid "No previous requests logged." +msgstr "" + +#: views/elements/history_panel.ctp:25 +msgid "previous requests available" +msgstr "" + +#: views/elements/history_panel.ctp:27 +msgid "Restore to current request" +msgstr "" + +#: views/elements/log_panel.ctp:21 +msgid "Logs" +msgstr "" + +#: views/elements/log_panel.ctp:28 +msgid "Time" +msgstr "" + +#: views/elements/log_panel.ctp:28 +#: views/elements/timer_panel.ctp:54 +msgid "Message" +msgstr "" + +#: views/elements/log_panel.ctp:37 +msgid "There were no log entries made this request" +msgstr "" + +#: views/elements/request_panel.ctp:21 +msgid "Request" +msgstr "" + +#: views/elements/request_panel.ctp:35 +msgid "Current Route" +msgstr "" + +#: views/elements/session_panel.ctp:21 +msgid "Session" +msgstr "" + +#: views/elements/sql_log_panel.ctp:21 +msgid "Sql Logs" +msgstr "" + +#: views/elements/sql_log_panel.ctp:31 +msgid "toggle (%s) query explains for %s" +msgstr "" + +#: views/elements/sql_log_panel.ctp:39 +msgid "No slow queries!, or your database does not support EXPLAIN" +msgstr "" + +#: views/elements/sql_log_panel.ctp:44 +msgid "No active database connections" +msgstr "" + +#: views/elements/timer_panel.ctp:33 +msgid "Memory" +msgstr "" + +#: views/elements/timer_panel.ctp:35 +msgid "Current Memory Use" +msgstr "" + +#: views/elements/timer_panel.ctp:39 +msgid "Peak Memory Use" +msgstr "" + +#: views/elements/timer_panel.ctp:43 +msgid "Timers" +msgstr "" + +#: views/elements/timer_panel.ctp:45 +msgid "%s (ms)" +msgstr "" + +#: views/elements/timer_panel.ctp:46 +msgid "Total Request Time:" +msgstr "" + +#: views/elements/timer_panel.ctp:54 +msgid "Time in ms" +msgstr "" + +#: views/elements/timer_panel.ctp:54 +msgid "Graph" +msgstr "" + +#: views/elements/variables_panel.ctp:21 +msgid "View Variables" +msgstr "" + +#: views/helpers/simple_graph.php:79 +msgid "Starting %sms into the request, taking %sms" +msgstr "" + diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/locale/eng/LC_MESSAGES/debug_kit.po b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/locale/eng/LC_MESSAGES/debug_kit.po new file mode 100644 index 000000000..9aec83297 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/locale/eng/LC_MESSAGES/debug_kit.po @@ -0,0 +1,135 @@ +# LANGUAGE translation of CakePHP Application +# Copyright YEAR NAME +# No version information was available in the source files. +# +#, fuzzy +msgid "" +msgstr "Project-Id-Version: PROJECT VERSION\n" + "POT-Creation-Date: 2009-05-27 09:47+0200\n" + "PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" + "Last-Translator: NAME \n" + "Language-Team: LANGUAGE \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: controllers/components/toolbar.php:91 +msgid "Component initialization and startup" +msgstr "" + +#: controllers/components/toolbar.php:140 +msgid "Controller Action" +msgstr "" + +#: controllers/components/toolbar.php:167 +msgid "Render Controller Action" +msgstr "" + +#: controllers/components/toolbar.php:231 +msgid "Could not load DebugToolbar panel %s" +msgstr "" + +#: views/elements/debug_toolbar.ctp:25 +msgid "There are no active panels. You must enable a panel to see its output." +msgstr "" + +#: views/elements/history_panel.ctp:21 +msgid "Request History" +msgstr "" + +#: views/elements/history_panel.ctp:23 +msgid "No previous requests logged." +msgstr "" + +#: views/elements/history_panel.ctp:25 +msgid "previous requests available" +msgstr "" + +#: views/elements/history_panel.ctp:27 +msgid "Restore to current request" +msgstr "" + +#: views/elements/log_panel.ctp:21 +msgid "Logs" +msgstr "" + +#: views/elements/log_panel.ctp:28 +msgid "Time" +msgstr "" + +#: views/elements/log_panel.ctp:28 views/elements/timer_panel.ctp:54 +msgid "Message" +msgstr "" + +#: views/elements/log_panel.ctp:37 +msgid "There were no log entries made this request" +msgstr "" + +#: views/elements/request_panel.ctp:21 +msgid "Request" +msgstr "" + +#: views/elements/request_panel.ctp:35 +msgid "Current Route" +msgstr "" + +#: views/elements/session_panel.ctp:21 +msgid "Session" +msgstr "" + +#: views/elements/sql_log_panel.ctp:21 +msgid "Sql Logs" +msgstr "" + +#: views/elements/sql_log_panel.ctp:31 +msgid "toggle (%s) query explains for %s" +msgstr "" + +#: views/elements/sql_log_panel.ctp:39 +msgid "No slow queries!, or your database does not support EXPLAIN" +msgstr "" + +#: views/elements/sql_log_panel.ctp:44 +msgid "No active database connections" +msgstr "" + +#: views/elements/timer_panel.ctp:33 +msgid "Memory" +msgstr "" + +#: views/elements/timer_panel.ctp:35 +msgid "Current Memory Use" +msgstr "" + +#: views/elements/timer_panel.ctp:39 +msgid "Peak Memory Use" +msgstr "" + +#: views/elements/timer_panel.ctp:43 +msgid "Timers" +msgstr "" + +#: views/elements/timer_panel.ctp:45 +msgid "%s (ms)" +msgstr "" + +#: views/elements/timer_panel.ctp:46 +msgid "Total Request Time:" +msgstr "" + +#: views/elements/timer_panel.ctp:54 +msgid "Time in ms" +msgstr "" + +#: views/elements/timer_panel.ctp:54 +msgid "Graph" +msgstr "" + +#: views/elements/variables_panel.ctp:21 +msgid "View Variables" +msgstr "" + +#: views/helpers/simple_graph.php:79 +msgid "Starting %sms into the request, taking %sms" +msgstr "" diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/locale/spa/LC_MESSAGES/debug_kit.po b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/locale/spa/LC_MESSAGES/debug_kit.po new file mode 100644 index 000000000..0833a4942 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/locale/spa/LC_MESSAGES/debug_kit.po @@ -0,0 +1,135 @@ +# LANGUAGE translation of CakePHP Application +# Copyright YEAR NAME +# No version information was available in the source files. +# +#, fuzzy +msgid "" +msgstr "Project-Id-Version: PROJECT VERSION\n" + "POT-Creation-Date: 2009-05-27 09:47+0200\n" + "PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" + "Last-Translator: NAME \n" + "Language-Team: LANGUAGE \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: controllers/components/toolbar.php:91 +msgid "Component initialization and startup" +msgstr "" + +#: controllers/components/toolbar.php:140 +msgid "Controller Action" +msgstr "" + +#: controllers/components/toolbar.php:167 +msgid "Render Controller Action" +msgstr "" + +#: controllers/components/toolbar.php:231 +msgid "Could not load DebugToolbar panel %s" +msgstr "" + +#: views/elements/debug_toolbar.ctp:25 +msgid "There are no active panels. You must enable a panel to see its output." +msgstr "" + +#: views/elements/history_panel.ctp:21 +msgid "Request History" +msgstr "" + +#: views/elements/history_panel.ctp:23 +msgid "No previous requests logged." +msgstr "" + +#: views/elements/history_panel.ctp:25 +msgid "previous requests available" +msgstr "" + +#: views/elements/history_panel.ctp:27 +msgid "Restore to current request" +msgstr "" + +#: views/elements/log_panel.ctp:21 +msgid "Logs" +msgstr "" + +#: views/elements/log_panel.ctp:28 +msgid "Time" +msgstr "" + +#: views/elements/log_panel.ctp:28 views/elements/timer_panel.ctp:54 +msgid "Message" +msgstr "" + +#: views/elements/log_panel.ctp:37 +msgid "There were no log entries made this request" +msgstr "" + +#: views/elements/request_panel.ctp:21 +msgid "Request" +msgstr "" + +#: views/elements/request_panel.ctp:35 +msgid "Current Route" +msgstr "" + +#: views/elements/session_panel.ctp:21 +msgid "Session" +msgstr "" + +#: views/elements/sql_log_panel.ctp:21 +msgid "Sql Logs" +msgstr "" + +#: views/elements/sql_log_panel.ctp:31 +msgid "toggle (%s) query explains for %s" +msgstr "" + +#: views/elements/sql_log_panel.ctp:39 +msgid "No slow queries!, or your database does not support EXPLAIN" +msgstr "" + +#: views/elements/sql_log_panel.ctp:44 +msgid "No active database connections" +msgstr "" + +#: views/elements/timer_panel.ctp:33 +msgid "Memory" +msgstr "" + +#: views/elements/timer_panel.ctp:35 +msgid "Current Memory Use" +msgstr "" + +#: views/elements/timer_panel.ctp:39 +msgid "Peak Memory Use" +msgstr "" + +#: views/elements/timer_panel.ctp:43 +msgid "Timers" +msgstr "" + +#: views/elements/timer_panel.ctp:45 +msgid "%s (ms)" +msgstr "" + +#: views/elements/timer_panel.ctp:46 +msgid "Total Request Time:" +msgstr "" + +#: views/elements/timer_panel.ctp:54 +msgid "Time in ms" +msgstr "" + +#: views/elements/timer_panel.ctp:54 +msgid "Graph" +msgstr "" + +#: views/elements/variables_panel.ctp:21 +msgid "View Variables" +msgstr "" + +#: views/helpers/simple_graph.php:79 +msgid "Starting %sms into the request, taking %sms" +msgstr "" \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/models/behaviors/timed.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/models/behaviors/timed.php new file mode 100644 index 000000000..28eadec74 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/models/behaviors/timed.php @@ -0,0 +1,105 @@ +settings[$Model->alias] = array_merge($this->_defaults, $settings); + } else { + $this->settings[$Model->alias] = $this->_defaults; + } + } + +/** + * beforeFind, starts a timer for a find operation. + * + * @param Model $Model + * @param array $queryData Array of query data (not modified) + * @return boolean true + */ + function beforeFind(&$Model, $queryData){ + DebugKitDebugger::startTimer($Model->alias . '_find', $Model->alias . '->find()'); + return true; + } + +/** + * afterFind, stops a timer for a find operation. + * + * @param Model $Model + * @param array $results Array of results + * @return boolean true. + */ + function afterFind(&$Model, $results){ + DebugKitDebugger::stopTimer($Model->alias . '_find'); + return true; + } + +/** + * beforeSave, starts a time before a save is initiated. + * + * @param Model $Model + * @return boolean true + */ + function beforeSave(&$Model){ + DebugKitDebugger::startTimer($Model->alias . '_save', $Model->alias . '->save()'); + return true; + } + +/** + * afterSave, stop the timer started from a save. + * + * @param string $Model + * @param string $created + * @return void + */ + function afterSave(&$Model, $created) { + DebugKitDebugger::stopTimer($Model->alias . '_save'); + return true; + } + } + diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/models/toolbar_access.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/models/toolbar_access.php new file mode 100644 index 000000000..ab1fea99b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/models/toolbar_access.php @@ -0,0 +1,56 @@ +config['driver']; + + $return = array(); + if ($driver === 'mysqli' || $driver === 'mysql' || $driver === 'postgres') { + $explained = $db->query('EXPLAIN ' . $query); + if ($driver === 'postgres') { + $queryPlan = array(); + foreach ($explained as $postgreValue) { + $queryPlan[] = array($postgreValue[0]['QUERY PLAN']); + } + $return = array_merge(array(array('')), $queryPlan); + } else { + $keys = array_keys($explained[0][0]); + foreach ($explained as $mysqlValue) { + $queryPlan[] = array_values($mysqlValue[0]); + } + $return = array_merge(array($keys), $queryPlan); + } + } + return $return; + } +} \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/behaviors/timed.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/behaviors/timed.test.php new file mode 100644 index 000000000..3fbbd614b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/behaviors/timed.test.php @@ -0,0 +1,77 @@ +Article =& new Model(array('ds' => 'test_suite', 'table' => 'articles', 'name' => 'Article')); + $this->Article->Behaviors->attach('DebugKit.Timed'); + } + +/** + * end a test + * + * @return void + */ + function endTest() { + unset($this->Article); + ClassRegistry::flush(); + DebugKitDebugger::clearTimers(); + } + +/** + * test find timers + * + * @return void + */ + function testFindTimers() { + $timers = DebugKitDebugger::getTimers(false); + $this->assertEqual(count($timers), 1); + + $this->Article->find('all'); + $result = DebugKitDebugger::getTimers(false); + $this->assertEqual(count($result), 2); + + $this->Article->find('all'); + $result = DebugKitDebugger::getTimers(false); + $this->assertEqual(count($result), 3); + } + +/** + * test save timers + * + * @return void + */ + function testSaveTimers() { + $timers = DebugKitDebugger::getTimers(false); + $this->assertEqual(count($timers), 1); + + $this->Article->save(array('user_id' => 1, 'title' => 'test', 'body' => 'test')); + $result = DebugKitDebugger::getTimers(false); + $this->assertEqual(count($result), 2); + } +} \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/controllers/components/toolbar.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/controllers/components/toolbar.test.php new file mode 100644 index 000000000..05f464ed6 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/controllers/components/toolbar.test.php @@ -0,0 +1,580 @@ +_loadPanels($panels, $settings); + } + + function _eval($code) { + if ($this->evalTest) { + $this->evalCode = $code; + return; + } + eval($code); + } +} + +Mock::generate('DebugPanel'); + +if (!class_exists('AppController')) { + class AppController extends Controller { + + } +} + +class TestPanel extends DebugPanel { + +} + +/** +* DebugToolbar Test case +*/ +class DebugToolbarTestCase extends CakeTestCase { +/** + * fixtures. + * + * @var array + **/ + var $fixtures = array('core.article'); +/** + * Start test callback + * + * @access public + * @return void + **/ + function startTest() { + Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); + $this->Controller =& new Controller(); + $this->Controller->params = Router::parse('/'); + $this->Controller->params['url']['url'] = '/'; + $this->Controller->uses = array(); + $this->Controller->components = array('TestToolBar'); + $this->Controller->constructClasses(); + $this->Controller->Toolbar =& $this->Controller->TestToolBar; + + $this->_server = $_SERVER; + $this->_paths = array(); + $this->_paths['plugins'] = App::path('plugins'); + $this->_paths['views'] = App::path('views'); + $this->_paths['vendors'] = App::path('vendors'); + $this->_paths['controllers'] = App::path('controllers'); + Configure::write('Cache.disable', false); + } +/** + * endTest + * + * @return void + **/ + function endTest() { + $_SERVER = $this->_server; + App::build(array( + 'plugins' => $this->_paths['plugins'], + 'views' => $this->_paths['views'], + 'controllers' => $this->_paths['controllers'], + 'vendors' => $this->_paths['vendors'] + ), true); + Configure::write('Cache.disable', true); + + unset($this->Controller); + if (class_exists('DebugKitDebugger')) { + DebugKitDebugger::clearTimers(); + DebugKitDebugger::clearMemoryPoints(); + } + } +/** + * test Loading of panel classes + * + * @return void + **/ + function testLoadPanels() { + $this->Controller->Toolbar->loadPanels(array('session', 'request')); + $this->assertTrue(is_a($this->Controller->Toolbar->panels['session'], 'SessionPanel')); + $this->assertTrue(is_a($this->Controller->Toolbar->panels['request'], 'RequestPanel')); + + $this->Controller->Toolbar->loadPanels(array('history'), array('history' => 10)); + $this->assertEqual($this->Controller->Toolbar->panels['history']->history, 10); + + $this->expectError(); + $this->Controller->Toolbar->loadPanels(array('randomNonExisting', 'request')); + } +/** + * test Loading of panel classes from a plugin + * + * @return void + **/ + function testLoadPluginPanels() { + $this->Controller->Toolbar->loadPanels(array('plugin.test')); + $this->assertTrue(is_a($this->Controller->Toolbar->panels['test'], 'TestPanel')); + } +/** + * test generating a DoppelGangerView with a pluginView. + * + * If $this->Controller->Toolbar->startup() has been previously called, + * DoppelGangerView class has already been defined. + * + * @return void + **/ + function testPluginViewParsing() { + if (class_exists('DoppelGangerView')) { + $this->skipIf(true, 'Class DoppelGangerView already defined, skipping %s'); + return; + } + App::import('Vendor', 'DebugKit.DebugKitDebugger'); + $this->Controller->Toolbar->evalTest = true; + $this->Controller->view = 'Plugin.OtherView'; + $this->Controller->Toolbar->startup($this->Controller); + $this->assertPattern('/class DoppelGangerView extends OtherView/', $this->Controller->Toolbar->evalCode); + } +/** + * test loading of vendor panels from test_app folder + * + * @access public + * @return void + */ + function testVendorPanels() { + $debugKitPath = App::pluginPath('DebugKit'); + $noDir = (empty($debugKitPath) || !file_exists($debugKitPath)); + $skip = $this->skipIf($noDir, 'Could not find debug_kit in plugin paths, skipping %s'); + if ($skip) { + return; + } + + App::build(array( + 'vendors' => array($debugKitPath . 'tests' . DS . 'test_app' . DS . 'vendors' . DS) + )); + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('test'), + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->assertTrue(isset($this->Controller->Toolbar->panels['test'])); + $this->assertTrue(is_a($this->Controller->Toolbar->panels['test'], 'TestPanel')); + } +/** + * test initialize + * + * @return void + * @access public + **/ + function testInitialize() { + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + + $this->assertFalse(empty($this->Controller->Toolbar->panels)); + + $timers = DebugKitDebugger::getTimers(); + $this->assertTrue(isset($timers['componentInit'])); + $memory = DebugKitDebugger::getMemoryPoints(); + $this->assertTrue(isset($memory['Component initialization'])); + } +/** + * test initialize w/ custom panels and defaults + * + * @return void + * @access public + **/ + function testInitializeCustomPanelsWithDefaults() { + $this->Controller->components = array( + 'DebugKit.Toolbar' => array('panels' => array('test')) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + + $expected = array('history', 'session', 'request', 'sqlLog', 'timer', 'log', 'variables', 'test'); + $this->assertEqual($expected, array_keys($this->Controller->Toolbar->panels)); + } + +/** + * test syntax for removing panels + * + * @return void + **/ + function testInitializeRemovingPanels() { + unset($this->Controller->Toolbar); + $this->Controller->components = array( + 'DebugKit.Toolbar' => array('panels' => array('session' => false, 'history' => false, 'test')) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + + $expected = array('request', 'sqlLog', 'timer', 'log', 'variables', 'test'); + $this->assertEqual($expected, array_keys($this->Controller->Toolbar->panels)); + } +/** + * ensure that enabled = false when debug == 0 on initialize + * + * @return void + **/ + function testDebugDisableOnInitialize() { + $_debug = Configure::read('debug'); + Configure::write('debug', 0); + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->assertFalse($this->Controller->Toolbar->enabled); + + Configure::write('debug', $_debug); + } +/** + * test that passing in forceEnable will enable the toolbar even if debug = 0 + * + * @return void + **/ + function testForceEnable() { + unset($this->Controller->Toolbar); + $_debug = Configure::read('debug'); + Configure::write('debug', 0); + $this->Controller->components = array('DebugKit.Toolbar' => array('forceEnable' => true)); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->assertTrue($this->Controller->Toolbar->enabled); + + Configure::write('debug', $_debug); + } +/** + * Test disabling autoRunning of toolbar + * + * @return void + **/ + function testAutoRunSettingFalse() { + $this->Controller->components = array('DebugKit.Toolbar' => array('autoRun' => false)); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->assertFalse($this->Controller->Toolbar->enabled); + } +/** + * test autorun = false with query string param + * + * @return void + **/ + function testAutoRunSettingWithQueryString() { + $this->Controller->params['url']['debug'] = true; + $this->Controller->components = array('DebugKit.Toolbar' => array('autoRun' => false)); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->assertTrue($this->Controller->Toolbar->enabled); + } +/** + * test startup + * + * @return void + **/ + function testStartup() { + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('MockDebug') + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Toolbar->panels['MockDebug']->expectOnce('startup'); + $this->Controller->Toolbar->startup($this->Controller); + + $this->assertEqual($this->Controller->view, 'DebugKit.Debug'); + $this->assertTrue(isset($this->Controller->helpers['DebugKit.Toolbar'])); + + $this->assertEqual($this->Controller->helpers['DebugKit.Toolbar']['output'], 'DebugKit.HtmlToolbar'); + $this->assertEqual($this->Controller->helpers['DebugKit.Toolbar']['cacheConfig'], 'debug_kit'); + $this->assertTrue(isset($this->Controller->helpers['DebugKit.Toolbar']['cacheKey'])); + + $timers = DebugKitDebugger::getTimers(); + $this->assertTrue(isset($timers['controllerAction'])); + $memory = DebugKitDebugger::getMemoryPoints(); + $this->assertTrue(isset($memory['Controller action start'])); + } +/** + * Test that cache config generation works. + * + * @return void + **/ + function testCacheConfigGeneration() { + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + + $results = Cache::config('debug_kit'); + $this->assertTrue(is_array($results)); + } +/** + * test state saving of toolbar + * + * @return void + **/ + function testStateSaving() { + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $configName = 'debug_kit'; + $this->Controller->Toolbar->cacheKey = 'toolbar_history'; + + $this->Controller->Component->startup($this->Controller); + $this->Controller->set('test', 'testing'); + $this->Controller->Component->beforeRender($this->Controller); + + $result = Cache::read('toolbar_history', $configName); + $this->assertEqual($result[0]['variables']['content']['test'], 'testing'); + Cache::delete('toolbar_history', $configName); + } +/** + * Test Before Render callback + * + * @return void + **/ + function testBeforeRender() { + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('MockDebug', 'session') + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Toolbar->panels['MockDebug']->expectOnce('beforeRender'); + $this->Controller->Toolbar->beforeRender($this->Controller); + + $this->assertTrue(isset($this->Controller->viewVars['debugToolbarPanels'])); + $vars = $this->Controller->viewVars['debugToolbarPanels']; + + $expected = array( + 'plugin' => 'debug_kit', + 'elementName' => 'session_panel', + 'content' => $this->Controller->Toolbar->Session->read(), + 'disableTimer' => true, + 'title' => '' + ); + $this->assertEqual($expected, $vars['session']); + + $memory = DebugKitDebugger::getMemoryPoints(); + $this->assertTrue(isset($memory['Controller render start'])); + } +/** + * test that vars are gathered and state is saved on beforeRedirect + * + * @return void + **/ + function testBeforeRedirect() { + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('MockDebug', 'session', 'history') + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + + $configName = 'debug_kit'; + $this->Controller->Toolbar->cacheKey = 'toolbar_history'; + Cache::delete('toolbar_history', $configName); + + DebugKitDebugger::startTimer('controllerAction', 'testing beforeRedirect'); + $this->Controller->Toolbar->panels['MockDebug']->expectOnce('beforeRender'); + $this->Controller->Toolbar->beforeRedirect($this->Controller); + + $result = Cache::read('toolbar_history', $configName); + $this->assertTrue(isset($result[0]['session'])); + $this->assertTrue(isset($result[0]['mock_debug'])); + + $timers = DebugKitDebugger::getTimers(); + $this->assertTrue(isset($timers['controllerAction'])); + } +/** + * test that loading state (accessing cache) works. + * + * @return void + **/ + function testLoadState() { + $this->Controller->Toolbar->cacheKey = 'toolbar_history'; + + $data = array(0 => array('my data')); + Cache::write('toolbar_history', $data, 'debug_kit'); + $result = $this->Controller->Toolbar->loadState(0); + $this->assertEqual($result, $data[0]); + } +/** + * test the Log panel log reading. + * + * @return void + **/ + function testLogPanel() { + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('log', 'session', 'history' => false, 'variables' => false, 'sqlLog' => false, + 'timer' => false) + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + + sleep(1); + $this->Controller->log('This is a log I made this request'); + $this->Controller->log('This is the second log I made this request'); + $this->Controller->log('This time in the debug log!', LOG_DEBUG); + + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->viewVars['debugToolbarPanels']['log']; + + $this->assertEqual(count($result['content']), 2); + $this->assertEqual(count($result['content']['error']), 2); + $this->assertEqual(count($result['content']['debug']), 1); + + $this->assertEqual(trim($result['content']['debug'][0][1]), 'This time in the debug log!'); + $this->assertEqual(trim($result['content']['error'][0][1]), 'This is a log I made this request'); + + $data = array( + 'Post' => array( + 'id' => 1, + 'title' => 'post!', + 'body' => 'some text here', + 'created' => '2009-11-07 23:23:23' + ), + 'Comment' => array( + 'id' => 23 + ) + ); + $this->Controller->log($data); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->viewVars['debugToolbarPanels']['log']; + $this->assertPattern('/\[created\] => 2009-11-07 23:23:23/', $result['content']['error'][2][1]); + $this->assertPattern('/\[Comment\] => Array/', $result['content']['error'][2][1]); + } + +/** + * test that creating the log panel creates the default file logger if none + * are configured. This stops DebugKit from mucking with the default auto-magic log config + * + * @return void + */ + function testLogPanelConstructCreatingDefaultLogConfiguration() { + CakeLog::drop('default'); + CakeLog::drop('debug_kit_log_panel'); + + $panel =& new LogPanel(array()); + $configured = CakeLog::configured(); + + $this->assertTrue(in_array('default', $configured)); + $this->assertTrue(in_array('debug_kit_log_panel', $configured)); + } + +/** + * Test that history state urls set prefix = null and admin = null so generated urls do not + * adopt these params. + * + * @return void + **/ + function testHistoryUrlGenerationWithPrefixes() { + $configName = 'debug_kit'; + $this->Controller->params = array( + 'controller' => 'posts', + 'action' => 'edit', + 'admin' => 1, + 'prefix' => 'admin', + 'plugin' => 'cms', + 'url' => array( + 'url' => '/admin/cms/posts/edit/' + ) + ); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Toolbar->cacheKey = 'url_test'; + $this->Controller->Component->beforeRender($this->Controller); + + $result = $this->Controller->Toolbar->panels['history']->beforeRender($this->Controller); + $expected = array( + 'plugin' => 'debug_kit', 'controller' => 'toolbar_access', 'action' => 'history_state', + 0 => 1, 'admin' => false + ); + $this->assertEqual($result[0]['url'], $expected); + Cache::delete('url_test', $configName); + } +/** + * Test that the FireCake toolbar is used on AJAX requests + * + * @return void + **/ + function testAjaxToolbar() { + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->assertEqual($this->Controller->helpers['DebugKit.Toolbar']['output'], 'DebugKit.FirePhpToolbar'); + } +/** + * Test that the toolbar does not interfere with requestAction + * + * @return void + **/ + function testNoRequestActionInterference() { + $debugKitPath = App::pluginPath('DebugKit'); + $noDir = (empty($debugKitPath) || !file_exists($debugKitPath)); + $skip = $this->skipIf($noDir, 'Could not find debug_kit in plugin paths, skipping %s'); + if ($skip) { + return; + } + + App::build(array( + 'controllers' => $debugKitPath . 'tests' . DS . 'test_app' . DS . 'controllers' . DS, + 'views' => array( + $debugKitPath . 'tests' . DS . 'test_app' . DS . 'views' . DS, + CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS . 'libs' . DS . 'view' . DS + ), + 'plugins' => $this->_paths['plugins'] + )); + Router::reload(); + + $result = $this->Controller->requestAction('/debug_kit_test/request_action_return', array('return')); + $this->assertEqual($result, 'I am some value from requestAction.'); + + $result = $this->Controller->requestAction('/debug_kit_test/request_action_render', array('return')); + $this->assertEqual($result, 'I have been rendered.'); + } +/** + * test the sqlLog panel parsing of db->showLog + * + * @return void + **/ + function testSqlLogPanel() { + App::import('Core', 'Model'); + $Article =& new Model(array('ds' => 'test_suite', 'name' => 'Article')); + $Article->find('first', array('conditions' => array('Article.id' => 1))); + + $this->Controller->components = array( + 'DebugKit.Toolbar' => array( + 'panels' => array('SqlLog') + ) + ); + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->viewVars['debugToolbarPanels']['sql_log']; + + $this->assertTrue(isset($result['content']['connections']['test_suite'])); + $this->assertTrue(isset($result['content']['threshold'])); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/models/toolbar_access.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/models/toolbar_access.test.php new file mode 100644 index 000000000..008f4fe10 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/models/toolbar_access.test.php @@ -0,0 +1,65 @@ +Model =& new ToolbarAccess(); + } + +/** + * endTest + * + * @return void + */ + function endTest() { + unset($this->Model); + } + +/** + * test that explain query returns arrays of query information. + * + * @return void + */ + function testExplainQuery() { + $db =& ConnectionManager::getDataSource('test_suite'); + $sql = 'SELECT * FROM ' . $db->fullTableName('posts') . ';'; + $result = $this->Model->explainQuery('test_suite', $sql); + + $this->assertTrue(is_array($result)); + $this->assertFalse(empty($result)); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/test_objects.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/test_objects.php new file mode 100644 index 000000000..67b01a7df --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/test_objects.php @@ -0,0 +1,53 @@ +sentHeaders[$name] = $value; + } +/** + * skip client detection as headers are not being sent. + * + * @access public + * @return void + */ + function detectClientExtension() { + return true; + } +/** + * Reset the fireCake + * + * @return void + **/ + function reset() { + $_this =& FireCake::getInstance(); + $_this->sentHeaders = array(); + $_this->_messageIndex = 1; + } +} + + diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/vendors/debug_kit_debugger.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/vendors/debug_kit_debugger.test.php new file mode 100644 index 000000000..536e89922 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/vendors/debug_kit_debugger.test.php @@ -0,0 +1,232 @@ +assertTrue(DebugKitDebugger::startTimer('test1', 'this is my first test')); + usleep(5000); + $this->assertTrue(DebugKitDebugger::stopTimer('test1')); + $elapsed = DebugKitDebugger::elapsedTime('test1'); + $this->assertTrue($elapsed > 0.0050); + + $this->assertTrue(DebugKitDebugger::startTimer('test2', 'this is my second test')); + sleep(1); + $this->assertTrue(DebugKitDebugger::stopTimer('test2')); + $elapsed = DebugKitDebugger::elapsedTime('test2'); + $this->assertTrue($elapsed > 1); + + DebugKitDebugger::startTimer('test3'); + $this->assertFalse(DebugKitDebugger::elapsedTime('test3')); + $this->assertFalse(DebugKitDebugger::stopTimer('wrong')); + } +/** + * test timers with no names. + * + * @return void + **/ + function testAnonymousTimers() { + $this->assertTrue(DebugKitDebugger::startTimer()); + usleep(2000); + $this->assertTrue(DebugKitDebugger::stopTimer()); + $timers = DebugKitDebugger::getTimers(); + + $this->assertEqual(count($timers), 2); + end($timers); + $key = key($timers); + $lineNo = __LINE__ - 8; + + $file = Debugger::trimPath(__FILE__); + $expected = $file . ' line ' . $lineNo; + $this->assertEqual($key, $expected); + + $timer = $timers[$expected]; + $this->assertTrue($timer['time'] > 0.0020); + $this->assertEqual($timers[$expected]['message'], $expected); + } +/** + * Assert that nested anonymous timers don't get mixed up. + * + * @return void + **/ + function testNestedAnonymousTimers() { + $this->assertTrue(DebugKitDebugger::startTimer()); + usleep(100); + $this->assertTrue(DebugKitDebugger::startTimer()); + usleep(100); + $this->assertTrue(DebugKitDebugger::stopTimer()); + $this->assertTrue(DebugKitDebugger::stopTimer()); + + $timers = DebugKitDebugger::getTimers(); + $this->assertEqual(count($timers), 3, 'incorrect number of timers %s'); + $firstTimerLine = __LINE__ -9; + $secondTimerLine = __LINE__ -8; + $file = Debugger::trimPath(__FILE__); + + $this->assertTrue(isset($timers[$file . ' line ' . $firstTimerLine]), 'first timer is not set %s'); + $this->assertTrue(isset($timers[$file . ' line ' . $secondTimerLine]), 'second timer is not set %s'); + + $firstTimer = $timers[$file . ' line ' . $firstTimerLine]; + $secondTimer = $timers[$file . ' line ' . $secondTimerLine]; + $this->assertTrue($firstTimer['time'] > $secondTimer['time']); + } +/** + * test that calling startTimer with the same name does not overwrite previous timers + * and instead adds new ones. + * + * @return void + **/ + function testRepeatTimers() { + DebugKitDebugger::startTimer('my timer', 'This is the first call'); + usleep(100); + DebugKitDebugger::startTimer('my timer', 'This is the second call'); + usleep(100); + + DebugKitDebugger::stopTimer('my timer'); + DebugKitDebugger::stopTimer('my timer'); + + $timers = DebugKitDebugger::getTimers(); + $this->assertEqual(count($timers), 3, 'wrong timer count %s'); + + $this->assertTrue(isset($timers['my timer'])); + $this->assertTrue(isset($timers['my timer #2'])); + + $this->assertTrue($timers['my timer']['time'] > $timers['my timer #2']['time'], 'timer 2 is longer? %s'); + $this->assertEqual($timers['my timer']['message'], 'This is the first call'); + $this->assertEqual($timers['my timer #2']['message'], 'This is the second call #2'); + } +/** + * testRequestTime + * + * @access public + * @return void + */ + function testRequestTime() { + $result1 = DebugKitDebugger::requestTime(); + usleep(50); + $result2 = DebugKitDebugger::requestTime(); + $this->assertTrue($result1 < $result2); + } +/** + * test getting all the set timers. + * + * @return void + **/ + function testGetTimers() { + DebugKitDebugger::startTimer('test1', 'this is my first test'); + DebugKitDebugger::stopTimer('test1'); + usleep(50); + DebugKitDebugger::startTimer('test2'); + DebugKitDebugger::stopTimer('test2'); + $timers = DebugKitDebugger::getTimers(); + + $this->assertEqual(count($timers), 3); + $this->assertTrue(is_float($timers['test1']['time'])); + $this->assertTrue(isset($timers['test1']['message'])); + $this->assertTrue(isset($timers['test2']['message'])); + } +/** + * test memory usage + * + * @return void + **/ + function testMemoryUsage() { + $result = DebugKitDebugger::getMemoryUse(); + $this->assertTrue(is_int($result)); + + $result = DebugKitDebugger::getPeakMemoryUse(); + $this->assertTrue(is_int($result)); + } +/** + * test _output switch to firePHP + * + * @return void + */ + function testOutput() { + $firecake =& FireCake::getInstance('TestFireCake'); + Debugger::invoke(DebugKitDebugger::getInstance('DebugKitDebugger')); + Debugger::output('fb'); + $foo .= ''; + $result = $firecake->sentHeaders; + + $this->assertPattern('/GROUP_START/', $result['X-Wf-1-1-1-1']); + $this->assertPattern('/ERROR/', $result['X-Wf-1-1-1-2']); + $this->assertPattern('/GROUP_END/', $result['X-Wf-1-1-1-5']); + + Debugger::invoke(Debugger::getInstance('Debugger')); + Debugger::output(); + } +/** + * test making memory use markers. + * + * @return void + **/ + function testMemorySettingAndGetting() { + $result = DebugKitDebugger::setMemoryPoint('test marker'); + $this->assertTrue($result); + + $result = DebugKitDebugger::getMemoryPoints(true); + $this->assertEqual(count($result), 1); + $this->assertTrue(isset($result['test marker'])); + $this->assertTrue(is_numeric($result['test marker'])); + + $result = DebugKitDebugger::getMemoryPoints(); + $this->assertTrue(empty($result)); + + DebugKitDebugger::setMemoryPoint('test marker'); + DebugKitDebugger::setMemoryPoint('test marker'); + $result = DebugKitDebugger::getMemoryPoints(); + $this->assertEqual(count($result), 2); + $this->assertTrue(isset($result['test marker'])); + $this->assertTrue(isset($result['test marker #2'])); + } + +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/vendors/fire_cake.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/vendors/fire_cake.test.php new file mode 100644 index 000000000..80abdfc73 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/vendors/fire_cake.test.php @@ -0,0 +1,340 @@ +firecake =& FireCake::getInstance('TestFireCake'); + } +/** + * test getInstance cheat. + * + * If this fails the rest of the test is going to fail too. + * + * @return void + **/ + function testGetInstanceOverride() { + $instance =& FireCake::getInstance(); + $instance2 =& FireCake::getInstance(); + $this->assertReference($instance, $instance2); + $this->assertIsA($instance, 'FireCake'); + $this->assertIsA($instance, 'TestFireCake', 'Stored instance is not a copy of TestFireCake, test case is broken.'); + } +/** + * testsetoption + * + * @return void + **/ + function testSetOptions() { + FireCake::setOptions(array('includeLineNumbers' => false)); + $this->assertEqual($this->firecake->options['includeLineNumbers'], false); + } +/** + * test Log() + * + * @access public + * @return void + */ + function testLog() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::log('Testing'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 1); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '26|[{"Type":"LOG"},"Testing"]|'); + + FireCake::log('Testing', 'log-info'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '45|[{"Type":"LOG","Label":"log-info"},"Testing"]|'); + } +/** + * test info() + * + * @access public + * @return void + */ + function testInfo() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::info('I have information'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 1); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '38|[{"Type":"INFO"},"I have information"]|'); + + FireCake::info('I have information', 'info-label'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '59|[{"Type":"INFO","Label":"info-label"},"I have information"]|'); + } +/** + * test info() + * + * @access public + * @return void + */ + function testWarn() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::warn('A Warning'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 1); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '29|[{"Type":"WARN"},"A Warning"]|'); + + FireCake::warn('A Warning', 'Bzzz'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '44|[{"Type":"WARN","Label":"Bzzz"},"A Warning"]|'); + } +/** + * test error() + * + * @access public + * @return void + **/ + function testError() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::error('An error'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 1); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '29|[{"Type":"ERROR"},"An error"]|'); + + FireCake::error('An error', 'wonky'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '45|[{"Type":"ERROR","Label":"wonky"},"An error"]|'); + } +/** + * test dump() + * + * @return void + **/ + function testDump() { + FireCake::dump('mydump', array('one' => 1, 'two' => 2)); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-2-1-1'], '28|{"mydump":{"one":1,"two":2}}|'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-2'])); + } +/** + * test table() generation + * + * @return void + **/ + function testTable() { + $table[] = array('Col 1 Heading','Col 2 Heading'); + $table[] = array('Row 1 Col 1','Row 1 Col 2'); + $table[] = array('Row 2 Col 1','Row 2 Col 2'); + $table[] = array('Row 3 Col 1','Row 3 Col 2'); + FireCake::table('myTrace', $table); + $expected = '162|[{"Type":"TABLE","Label":"myTrace"},[["Col 1 Heading","Col 2 Heading"],["Row 1 Col 1","Row 1 Col 2"],["Row 2 Col 1","Row 2 Col 2"],["Row 3 Col 1","Row 3 Col 2"]]]|'; + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], $expected); + } +/** + * testStringEncoding + * + * @return void + **/ + function testStringEncode() { + $vars = array(1,2,3); + $result = $this->firecake->stringEncode($vars); + $this->assertEqual($result, array(1,2,3)); + + $this->firecake->setOptions(array('maxArrayDepth' => 3)); + $deep = array(1 => array(2 => array(3))); + $result = $this->firecake->stringEncode($deep); + $this->assertEqual($result, array(1 => array(2 => '** Max Array Depth (3) **'))); + } +/** + * test object encoding + * + * @return void + **/ + function testStringEncodeObjects() { + $obj =& FireCake::getInstance(); + $result = $this->firecake->stringEncode($obj); + + $this->assertTrue(is_array($result)); + $this->assertEqual($result['_defaultOptions']['useNativeJsonEncode'], true); + $this->assertEqual($result['_encodedObjects'][0], '** Recursion (TestFireCake) **'); + } +/** + * test trace() + * + * @return void + **/ + function testTrace() { + FireCake::trace('myTrace'); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-Protocol-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Plugin-1'])); + $this->assertTrue(isset($this->firecake->sentHeaders['X-Wf-1-Structure-1'])); + $dump = $this->firecake->sentHeaders['X-Wf-1-1-1-1']; + $this->assertPattern('/"Message":"myTrace"/', $dump); + $this->assertPattern('/"Trace":\[/', $dump); + } +/** + * test enabling and disabling of FireCake output + * + * @return void + **/ + function testEnableDisable() { + FireCake::disable(); + FireCake::trace('myTrace'); + $this->assertTrue(empty($this->firecake->sentHeaders)); + + FireCake::enable(); + FireCake::trace('myTrace'); + $this->assertFalse(empty($this->firecake->sentHeaders)); + } + +/** + * test correct line continuation markers on multi line headers. + * + * @access public + * @return void + */ + function testMultiLineOutput() { + $skip = $this->skipIf(!PHP5, 'Output is not long enough with PHP4'); + if ($skip) { + return; + } + FireCake::trace('myTrace'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 4); + $header = $this->firecake->sentHeaders['X-Wf-1-1-1-1']; + $this->assertEqual(substr($header, -2), '|\\'); + + $header = $this->firecake->sentHeaders['X-Wf-1-1-1-2']; + $this->assertEqual(substr($header, -2), '|\\'); + + $header = $this->firecake->sentHeaders['X-Wf-1-1-1-3']; + $this->assertEqual(substr($header, -2), '|\\'); + + $header = $this->firecake->sentHeaders['X-Wf-1-1-1-4']; + $this->assertEqual(substr($header, -1), '|'); + } +/** + * test inclusion of line numbers + * + * @return void + **/ + function testIncludeLineNumbers() { + FireCake::setOptions(array('includeLineNumbers' => true)); + FireCake::info('Testing'); + $result = $this->firecake->sentHeaders['X-Wf-1-1-1-1']; + $this->assertPattern('/"File"\:".*fire_cake.test.php/', $result); + $this->assertPattern('/"Line"\:\d+/', $result); + } +/** + * test Group messages + * + * @return void + **/ + function testGroup() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::group('test'); + FireCake::info('my info'); + FireCake::groupEnd(); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '63|[{"Collapsed":"true","Type":"GROUP_START","Label":"test"},null]|'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-3'], '27|[{"Type":"GROUP_END"},null]|'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 3); + } +/** + * test fb() parameter parsing + * + * @return void + **/ + function testFbParameterParsing() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::fb('Test'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '23|[{"Type":"LOG"},"Test"]|'); + + FireCake::fb('Test', 'warn'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-2'], '24|[{"Type":"WARN"},"Test"]|'); + + FireCake::fb('Test', 'Custom label', 'warn'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-3'], '47|[{"Type":"WARN","Label":"Custom label"},"Test"]|'); + + $this->expectError(); + $this->assertFalse(FireCake::fb('Test', 'Custom label', 'warn', 'more parameters')); + + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-Index'], 3); + } +/** + * Test defaulting to log if incorrect message type is used + * + * @return void + **/ + function testIncorrectMessageType() { + FireCake::setOptions(array('includeLineNumbers' => false)); + FireCake::fb('Hello World', 'foobared'); + $this->assertEqual($this->firecake->sentHeaders['X-Wf-1-1-1-1'], '30|[{"Type":"LOG"},"Hello World"]|'); + } +/** + * testClientExtensionDetection. + * + * @return void + **/ + function testDetectClientExtension() { + $back = env('HTTP_USER_AGENT'); + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4 FirePHP/0.2.1'; + $this->assertTrue(FireCake::detectClientExtension()); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4 FirePHP/0.0.4'; + $this->assertFalse(FireCake::detectClientExtension()); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4'; + $this->assertFalse(FireCake::detectClientExtension()); + $_SERVER['HTTP_USER_AGENT'] = $back; + } +/** + * test of Non Native JSON encoding. + * + * @return void + **/ + function testNonNativeEncoding() { + FireCake::setOptions(array('useNativeJsonEncode' => false)); + $json = FireCake::jsonEncode(array('one' => 1, 'two' => 2)); + $this->assertEqual($json, '{"one":1,"two":2}'); + + $json = FireCake::jsonEncode(array(1,2,3)); + $this->assertEqual($json, '[1,2,3]'); + + $json = FireCake::jsonEncode(FireCake::getInstance()); + $this->assertPattern('/"options"\:\{"maxObjectDepth"\:\d*,/', $json); + } +/** + * reset the FireCake counters and headers. + * + * @access public + * @return void + */ + function tearDown() { + TestFireCake::reset(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/debug.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/debug.test.php new file mode 100644 index 000000000..606ab0630 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/debug.test.php @@ -0,0 +1,161 @@ + 'pages', 'action' => 'display', 'home')); + Router::parse('/'); + $this->Controller =& new Controller(); + $this->View =& new DebugView($this->Controller, false); + $this->_debug = Configure::read('debug'); + $this->_paths = array(); + $this->_paths['plugins'] = App::path('plugins'); + $this->_paths['views'] = App::path('views'); + $this->_paths['vendors'] = App::path('vendors'); + $this->_paths['controllers'] = App::path('controllers'); + } +/** + * tear down function + * + * @return void + **/ + function endTest() { + App::build(array( + 'plugins' => $this->_paths['plugins'], + 'views' => $this->_paths['views'], + 'vendors' => $this->_paths['vendors'], + 'controllers' => $this->_paths['controllers'] + )); + + unset($this->View, $this->Controller); + DebugKitDebugger::clearTimers(); + Configure::write('debug', $this->_debug); + } +/** + * start Case - switch view paths + * + * @return void + **/ + function startCase() { + App::build(array( + 'views' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, + APP . 'plugins' . DS . 'debug_kit' . DS . 'views'. DS, + ROOT . DS . LIBS . 'view' . DS + ) + ), true); + } +/** + * test that element timers are working + * + * @return void + **/ + function testElementTimers() { + $result = $this->View->element('test_element'); + $expected = << +this is the test element + + +TEXT; + $this->assertEqual($result, $expected); + + $result = DebugKitDebugger::getTimers(); + $this->assertTrue(isset($result['render_test_element.ctp'])); + } +/** + * test rendering and ensure that timers are being set. + * + * @access public + * @return void + */ + function testRenderTimers() { + $this->Controller->viewPath = 'posts'; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index'), + 'base' => null, + 'here' => '/posts/index', + ); + $this->Controller->layout = 'default'; + $View =& new DebugView($this->Controller, false); + $View->render('index'); + + $result = DebugKitDebugger::getTimers(); + $this->assertEqual(count($result), 4); + $this->assertTrue(isset($result['viewRender'])); + $this->assertTrue(isset($result['render_default.ctp'])); + $this->assertTrue(isset($result['render_index.ctp'])); + + $result = DebugKitDebugger::getMemoryPoints(); + $this->assertTrue(isset($result['View render complete'])); + } +/** + * Test for correct loading of helpers into custom view + * + * @return void + */ + function testLoadHelpers() { + $loaded = array(); + $result = $this->View->_loadHelpers($loaded, array('Html', 'Javascript', 'Number')); + $this->assertTrue(is_object($result['Html'])); + $this->assertTrue(is_object($result['Javascript'])); + $this->assertTrue(is_object($result['Number'])); + } +/** + * test that $out is returned when a layout is rendered instead of the empty + * $this->output. As this causes issues with requestAction() + * + * @return void + **/ + function testProperReturnUnderRequestAction() { + $testapp = App::pluginPath('DebugKit') . 'tests' . DS . 'test_app' . DS . 'views' . DS; + App::build(array('views' => array($testapp))); + + $this->View->set('test', 'I have been rendered.'); + $this->View->action = 'request_action_render'; + $this->View->name = 'DebugKitTest'; + $this->View->viewPath = 'debug_kit_test'; + $this->View->layout = false; + $result = $this->View->render('request_action_render'); + + $this->assertEqual($result, 'I have been rendered.'); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/fire_php_toolbar.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/fire_php_toolbar.test.php new file mode 100644 index 000000000..dd269483b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/fire_php_toolbar.test.php @@ -0,0 +1,149 @@ + 'pages', 'action' => 'display', 'home')); + Router::parse('/'); + + $this->Toolbar =& new ToolbarHelper(array('output' => 'DebugKit.FirePhpToolbar')); + $this->Toolbar->FirePhpToolbar =& new FirePhpToolbarHelper(); + + $this->Controller =& new Controller(); + if (isset($this->_debug)) { + Configure::write('debug', $this->_debug); + } + } +/** + * start Case - switch view paths + * + * @return void + **/ + function startCase() { + $this->_viewPaths = App::build('views'); + App::build(array( + 'views' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, + APP . 'plugins' . DS . 'debug_kit' . DS . 'views'. DS, + ROOT . DS . LIBS . 'view' . DS + )), true); + $this->_debug = Configure::read('debug'); + $this->firecake =& FireCake::getInstance(); + } +/** + * test neat array (dump)creation + * + * @return void + */ + function testMakeNeatArray() { + $this->Toolbar->makeNeatArray(array(1,2,3)); + $result = $this->firecake->sentHeaders; + $this->assertTrue(isset($result['X-Wf-1-1-1-1'])); + $this->assertPattern('/\[1,2,3\]/', $result['X-Wf-1-1-1-1']); + } +/** + * testAfterlayout element rendering + * + * @return void + */ + function testAfterLayout(){ + $this->Controller->viewPath = 'posts'; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index', 'ext' => 'xml'), + 'base' => null, + 'here' => '/posts/index', + ); + $this->Controller->layout = 'default'; + $this->Controller->uses = null; + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->render(); + $this->assertNoPattern('/debug-toolbar/', $result); + $result = $this->firecake->sentHeaders; + $this->assertTrue(is_array($result)); + } +/** + * test starting a panel + * + * @return void + **/ + function testPanelStart() { + $this->Toolbar->panelStart('My Panel', 'my_panel'); + $result = $this->firecake->sentHeaders; + $this->assertPattern('/GROUP_START.+My Panel/', $result['X-Wf-1-1-1-1']); + } +/** + * test ending a panel + * + * @return void + **/ + function testPanelEnd() { + $this->Toolbar->panelEnd(); + $result = $this->firecake->sentHeaders; + $this->assertPattern('/GROUP_END/', $result['X-Wf-1-1-1-1']); + } +/** + * endTest() + * + * @return void + */ + function endTest() { + TestFireCake::reset(); + } +/** + * reset the view paths + * + * @return void + **/ + function endCase() { + Configure::write('viewPaths', $this->_viewPaths); + } +/** + * tearDown + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Toolbar, $this->Controller); + ClassRegistry::removeObject('view'); + ClassRegistry::flush(); + Router::reload(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/html_toolbar.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/html_toolbar.test.php new file mode 100644 index 000000000..766edd621 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/html_toolbar.test.php @@ -0,0 +1,352 @@ + 'pages', 'action' => 'display', 'home')); + Router::parse('/'); + + $this->Toolbar =& new ToolbarHelper(array('output' => 'DebugKit.HtmlToolbar')); + $this->Toolbar->HtmlToolbar =& new HtmlToolbarHelper(); + $this->Toolbar->HtmlToolbar->Html =& new HtmlHelper(); + $this->Toolbar->HtmlToolbar->Form =& new FormHelper(); + + $this->Controller =& new Controller(); + if (isset($this->_debug)) { + Configure::write('debug', $this->_debug); + } + } +/** + * start Case - switch view paths + * + * @return void + **/ + function startCase() { + $this->_viewPaths = App::path('views'); + App::build(array( + 'views' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, + APP . 'plugins' . DS . 'debug_kit' . DS . 'views'. DS, + ROOT . DS . LIBS . 'view' . DS + )), true); + $this->_debug = Configure::read('debug'); + } +/** + * test Neat Array formatting + * + * @return void + **/ + function testMakeNeatArray() { + $in = false; + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = null; + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = true; + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array(); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array('key' => 'value'); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array('key' => null); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array('key' => 'value', 'foo' => 'bar'); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + + $in = array( + 'key' => 'value', + 'foo' => array( + 'this' => 'deep', + 'another' => 'value' + ) + ); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + ' array('class' => 'neat-array depth-1')), + 'assertTags($result, $expected); + + $in = array( + 'key' => 'value', + 'foo' => array( + 'this' => 'deep', + 'another' => 'value' + ), + 'lotr' => array( + 'gandalf' => 'wizard', + 'bilbo' => 'hobbit' + ) + ); + $result = $this->Toolbar->makeNeatArray($in, 1); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0 expanded'), + ' array('class' => 'neat-array depth-1')), + ' array('class' => 'neat-array depth-1')), + 'assertTags($result, $expected); + + $result = $this->Toolbar->makeNeatArray($in, 2); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0 expanded'), + ' array('class' => 'neat-array depth-1 expanded')), + ' array('class' => 'neat-array depth-1 expanded')), + 'assertTags($result, $expected); + + $in = array('key' => 'value', 'array' => array()); + $result = $this->Toolbar->makeNeatArray($in); + $expected = array( + 'ul' => array('class' => 'neat-array depth-0'), + 'assertTags($result, $expected); + } + +/** + * Test injection of toolbar + * + * @return void + **/ + function testInjectToolbar() { + $this->Controller->viewPath = 'posts'; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index'), + 'base' => null, + 'here' => '/posts/index', + ); + $this->Controller->helpers = array('Html', 'Javascript', 'Session', 'DebugKit.Toolbar'); + $this->Controller->layout = 'default'; + $this->Controller->uses = null; + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->render(); + $result = str_replace(array("\n", "\r"), '', $result); + $this->assertPattern('#
    .+
    .*#', $result); + } + +/** + * test injection of javascript + * + * @return void + **/ + function testJavascriptInjection() { + $this->Controller->viewPath = 'posts'; + $this->Controller->uses = null; + $this->Controller->action = 'index'; + $this->Controller->params = array( + 'action' => 'index', + 'controller' => 'posts', + 'plugin' => null, + 'url' => array('url' => 'posts/index'), + 'base' => '/', + 'here' => '/posts/index', + ); + $this->Controller->helpers = array('Javascript', 'Html', 'Session'); + $this->Controller->components = array('DebugKit.Toolbar'); + $this->Controller->layout = 'default'; + $this->Controller->constructClasses(); + $this->Controller->Component->initialize($this->Controller); + $this->Controller->Component->startup($this->Controller); + $this->Controller->Component->beforeRender($this->Controller); + $result = $this->Controller->render(); + $result = str_replace(array("\n", "\r"), '', $result); + $this->assertPattern('#\s?#', $result); + } + +/** + * test message creation + * + * @return void + */ + function testMessage() { + $result = $this->Toolbar->message('test', 'one, two'); + $expected = array( + 'assertTags($result, $expected); + } +/** + * Test Table generation + * + * @return void + */ + function testTable() { + $rows = array( + array(1,2), + array(3,4), + ); + $result = $this->Toolbar->table($rows); + $expected = array( + 'table' => array('class' =>'debug-table'), + array('tr' => array('class' => 'odd')), + ' array('class' => 'even')), + 'assertTags($result, $expected); + } +/** + * test starting a panel + * + * @return void + **/ + function testStartPanel() { + $result = $this->Toolbar->panelStart('My Panel', 'my_panel'); + $expected = array( + 'a' => array('href' => '#my_panel'), + 'My Panel', + '/a' + ); + $this->assertTags($result, $expected); + } +/** + * test ending a panel + * + * @return void + **/ + function testPanelEnd() { + $result = $this->Toolbar->panelEnd(); + $this->assertNull($result); + } +/** + * reset the view paths + * + * @return void + **/ + function endCase() { + App::build(); + } + +/** + * tearDown + * + * @access public + * @return void + */ + function tearDown() { + unset($this->Toolbar, $this->Controller); + ClassRegistry::removeObject('view'); + ClassRegistry::flush(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/toolbar.test.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/toolbar.test.php new file mode 100644 index 000000000..81c914e73 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/cases/views/helpers/toolbar.test.php @@ -0,0 +1,170 @@ + 'pages', 'action' => 'display', 'home')); + Router::parse('/'); + + $this->Toolbar =& new ToolbarHelper(array( + 'output' => 'MockBackendHelper', + 'cacheKey' => 'debug_kit_toolbar_test_case', + 'cacheConfig' => 'default' + )); + $this->Toolbar->MockBackend = new MockBackendHelper(); + + $this->Controller =& ClassRegistry::init('Controller'); + if (isset($this->_debug)) { + Configure::write('debug', $this->_debug); + } + } +/** + * start Case - switch view paths + * + * @return void + **/ + function startCase() { + $this->_viewPaths = App::path('views'); + App::build(array( + 'views' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, + APP . 'plugins' . DS . 'debug_kit' . DS . 'views'. DS, + ROOT . DS . LIBS . 'view' . DS + )), true); + $this->_debug = Configure::read('debug'); + } +/** + * test cache writing for views. + * + * @return void + **/ + function testCacheWrite() { + $result = $this->Toolbar->writeCache('test', array('stuff', 'to', 'cache')); + $this->assertTrue($result); + } +/** + * Ensure that the cache writing only affects the + * top most level of the history stack. As this is where the current request is stored. + * + * @return void + **/ + function testOnlyWritingToFirstElement() { + $values = array( + array('test' => array('content' => array('first', 'values'))), + array('test' => array('content' => array('second', 'values'))), + ); + Cache::write('debug_kit_toolbar_test_case', $values, 'default'); + $this->Toolbar->writeCache('test', array('new', 'values')); + + $result = $this->Toolbar->readCache('test'); + $this->assertEqual($result, array('new', 'values')); + + $result = $this->Toolbar->readCache('test', 1); + $this->assertEqual($result, array('second', 'values')); + } +/** + * test cache reading for views + * + * @return void + **/ + function testCacheRead() { + $result = $this->Toolbar->writeCache('test', array('stuff', 'to', 'cache')); + $this->assertTrue($result, 'Cache write failed %s'); + + $result = $this->Toolbar->readCache('test'); + $this->assertEqual($result, array('stuff', 'to', 'cache'), 'Cache value is wrong %s'); + + $result = $this->Toolbar->writeCache('test', array('new', 'stuff')); + $this->assertTrue($result, 'Cache write failed %s'); + + $result = $this->Toolbar->readCache('test'); + $this->assertEqual($result, array('new', 'stuff'), 'Cache value is wrong %s'); + } +/** + * Test that reading/writing doesn't work with no cache config. + * + * @return void + **/ + function testNoCacheConfigPresent() { + $this->Toolbar = new ToolbarHelper(array('output' => 'MockBackendHelper')); + + $result = $this->Toolbar->writeCache('test', array('stuff', 'to', 'cache')); + $this->assertFalse($result, 'Writing to cache succeeded with no cache config %s'); + + $result = $this->Toolbar->readCache('test'); + $this->assertFalse($result, 'Reading cache succeeded with no cache config %s'); + } +/** + * ensure that getQueryLogs works and writes to the cache so the history panel will + * work. + * + * @return void + */ + function testGetQueryLogs() { + $model =& new Model(array('ds' => 'test_suite', 'table' => 'posts', 'name' => 'Post')); + $model->find('all'); + $model->find('first'); + + $result = $this->Toolbar->getQueryLogs('test_suite', array('cache' => false)); + $this->assertTrue(is_array($result)); + $this->assertTrue(count($result) >= 2, 'Should be more than 2 queries in the log %s'); + $this->assertTrue(isset($result[0]['actions'])); + + $model->find('first'); + Cache::delete('debug_kit_toolbar_test_case', 'default'); + $result = $this->Toolbar->getQueryLogs('test_suite', array('cache' => true)); + + $cached = $this->Toolbar->readCache('sql_log'); + $this->assertTrue(isset($cached['test_suite'])); + $this->assertEqual($cached['test_suite'][0], $result[0]); + } +/** + * reset the view paths + * + * @return void + **/ + function endCase() { + App::build(array('views' => $this->_viewPaths), true); + Cache::delete('debug_kit_toolbar_test_case', 'default'); + } +/** + * endTest + * + * @access public + * @return void + */ + function endTest() { + unset($this->Toolbar, $this->Controller); + ClassRegistry::removeObject('view'); + ClassRegistry::flush(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/groups/view_group.group.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/groups/view_group.group.php new file mode 100644 index 000000000..4f5625545 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/groups/view_group.group.php @@ -0,0 +1,44 @@ +autoRender = false; + return 'I am some value from requestAction.'; + } + + function request_action_render() { + $this->set('test', 'I have been rendered.'); + } + +} \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/test_app/vendors/test_panel.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/test_app/vendors/test_panel.php new file mode 100644 index 000000000..3b004d3f7 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/test_app/vendors/test_panel.php @@ -0,0 +1,24 @@ +testPanel = true; + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/test_app/views/debug_kit_test/request_action_render.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/test_app/views/debug_kit_test/request_action_render.ctp new file mode 100644 index 000000000..172d541d7 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/tests/test_app/views/debug_kit_test/request_action_render.ctp @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/debug_kit_debugger.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/debug_kit_debugger.php new file mode 100644 index 000000000..862ce627d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/debug_kit_debugger.php @@ -0,0 +1,400 @@ +__benchmarks) { + return; + } + $timers = array_values(DebugKitDebugger::getTimers()); + $end = end($timers); + echo '

  • '; + echo ''; + echo ''; + $i = 0; + foreach ($timers as $timer) { + $indent = 0; + for ($j = 0; $j < $i; $j++) { + if (($timers[$j]['end']) > ($timer['start']) && ($timers[$j]['end']) > ($timer['end'])) { + $indent++; + } + } + $indent = str_repeat(' » ', $indent); + + extract($timer); + $start = round($start * 1000, 0); + $end = round($end * 1000, 0); + $time = round($time * 1000, 0); + echo ""; + $i++; + } + echo '
    Debug timer info
    MessageStart Time (ms)End Time (ms)Duration (ms)
    {$indent}$message$start$end$time
    '; + } +/** + * Start an benchmarking timer. + * + * @param string $name The name of the timer to start. + * @param string $message A message for your timer + * @return bool true + * @static + **/ + function startTimer($name = null, $message = null) { + $start = getMicrotime(); + $_this =& DebugKitDebugger::getInstance(); + + if (!$name) { + $named = false; + $calledFrom = debug_backtrace(); + $_name = $name = Debugger::trimpath($calledFrom[0]['file']) . ' line ' . $calledFrom[0]['line']; + } else { + $named = true; + } + + if (!$message) { + $message = $name; + } + + $_name = $name; + $i = 1; + while (isset($_this->__benchmarks[$name])) { + $i++; + $name = $_name . ' #' . $i; + } + + if ($i > 1) { + $message .= ' #' . $i; + } + + $_this->__benchmarks[$name] = array( + 'start' => $start, + 'message' => $message, + 'named' => $named + ); + return true; + } +/** + * Stop a benchmarking timer. + * + * $name should be the same as the $name used in startTimer(). + * + * @param string $name The name of the timer to end. + * @access public + * @return boolean true if timer was ended, false if timer was not started. + * @static + */ + function stopTimer($name = null) { + $end = getMicrotime(); + $_this =& DebugKitDebugger::getInstance(); + if (!$name) { + $names = array_reverse(array_keys($_this->__benchmarks)); + foreach($names as $name) { + if (!empty($_this->__benchmarks[$name]['end'])) { + continue; + } + if (empty($_this->__benchmarks[$name]['named'])) { + break; + } + } + } else { + $i = 1; + $_name = $name; + while (isset($_this->__benchmarks[$name])) { + if (empty($_this->__benchmarks[$name]['end'])) { + break; + } + $i++; + $name = $_name . ' #' . $i; + } + } + if (!isset($_this->__benchmarks[$name])) { + return false; + } + $_this->__benchmarks[$name]['end'] = $end; + return true; + } +/** + * Get all timers that have been started and stopped. + * Calculates elapsed time for each timer. If clear is true, will delete existing timers + * + * @param bool $clear false + * @return array + * @access public + **/ + function getTimers($clear = false) { + $_this =& DebugKitDebugger::getInstance(); + $start = DebugKitDebugger::requestStartTime(); + $now = getMicrotime(); + + $times = array(); + if (!empty($_this->__benchmarks)) { + $firstTimer = current($_this->__benchmarks); + $_end = $firstTimer['start']; + } else { + $_end = $now; + } + $times['Core Processing (Derived)'] = array( + 'message' => __d('debug_kit', 'Core Processing (Derived)', true), + 'start' => 0, + 'end' => $_end - $start, + 'time' => round($_end - $start, 6), + 'named' => null + ); + foreach ($_this->__benchmarks as $name => $timer) { + if (!isset($timer['end'])) { + $timer['end'] = $now; + } + $times[$name] = array_merge($timer, array( + 'start' => $timer['start'] - $start, + 'end' => $timer['end'] - $start, + 'time' => DebugKitDebugger::elapsedTime($name) + )); + } + if ($clear) { + $_this->__benchmarks = array(); + } + return $times; + } +/** + * Clear all existing timers + * + * @return bool true + **/ + function clearTimers() { + $_this =& DebugKitDebugger::getInstance(); + $_this->__benchmarks = array(); + return true; + } +/** + * Get the difference in time between the timer start and timer end. + * + * @param $name string the name of the timer you want elapsed time for. + * @param $precision int the number of decimal places to return, defaults to 5. + * @return float number of seconds elapsed for timer name, 0 on missing key + * @static + **/ + function elapsedTime($name = 'default', $precision = 5) { + $_this =& DebugKitDebugger::getInstance(); + if (!isset($_this->__benchmarks[$name]['start']) || !isset($_this->__benchmarks[$name]['end'])) { + return 0; + } + return round($_this->__benchmarks[$name]['end'] - $_this->__benchmarks[$name]['start'], $precision); + } +/** + * Get the total execution time until this point + * + * @access public + * @return float elapsed time in seconds since script start. + * @static + */ + function requestTime() { + $start = DebugKitDebugger::requestStartTime(); + $now = getMicroTime(); + return ($now - $start); + } +/** + * get the time the current request started. + * + * @access public + * @return float time of request start + * @static + */ + function requestStartTime() { + if (defined('TIME_START')) { + $startTime = TIME_START; + } else if (isset($GLOBALS['TIME_START'])) { + $startTime = $GLOBALS['TIME_START']; + } else { + $startTime = env('REQUEST_TIME'); + } + return $startTime; + } +/** + * get current memory usage + * + * @return integer number of bytes ram currently in use. 0 if memory_get_usage() is not available. + * @static + **/ + function getMemoryUse() { + if (!function_exists('memory_get_usage')) { + return 0; + } + return memory_get_usage(); + } +/** + * Get peak memory use + * + * @return integer peak memory use (in bytes). Returns 0 if memory_get_peak_usage() is not available + * @static + **/ + function getPeakMemoryUse() { + if (!function_exists('memory_get_peak_usage')) { + return 0; + } + return memory_get_peak_usage(); + } +/** + * Stores a memory point in the internal tracker. + * Takes a optional message name which can be used to identify the memory point. + * If no message is supplied a debug_backtrace will be done to identifty the memory point. + * If you don't have memory_get_xx methods this will not work. + * + * @param string $message Message to identify this memory point. + * @return boolean + **/ + function setMemoryPoint($message = null) { + $memoryUse = DebugKitDebugger::getMemoryUse(); + if (!$message) { + $named = false; + $trace = debug_backtrace(); + $message = Debugger::trimpath($trace[0]['file']) . ' line ' . $trace[0]['line']; + } + $self =& DebugKitDebugger::getInstance(); + if (isset($self->__memoryPoints[$message])) { + $originalMessage = $message; + $i = 1; + while (isset($self->__memoryPoints[$message])) { + $i++; + $message = $originalMessage . ' #' . $i; + } + } + $self->__memoryPoints[$message] = $memoryUse; + return true; + } +/** + * Get all the stored memory points + * + * @param boolean $clear Whether you want to clear the memory points as well. Defaults to false. + * @return array Array of memory marks stored so far. + **/ + function getMemoryPoints($clear = false) { + $self =& DebugKitDebugger::getInstance(); + $marks = $self->__memoryPoints; + if ($clear) { + $self->__memoryPoints = array(); + } + return $marks; + } +/** + * Clear out any existing memory points + * + * @return void + **/ + function clearMemoryPoints() { + $self =& DebugKitDebugger::getInstance(); + $self->__memoryPoints = array(); + } +/** + * Handles object conversion to debug string. + * + * @param string $var Object to convert + * @access protected + */ + function _output($data = array()) { + extract($data); + if (is_array($level)) { + $error = $level['error']; + $code = $level['code']; + if (isset($level['helpID'])) { + $helpID = $level['helpID']; + } else { + $helpID = ''; + } + $description = $level['description']; + $file = $level['file']; + $line = $level['line']; + $context = $level['context']; + $level = $level['level']; + } + $files = $this->trace(array('start' => 2, 'format' => 'points')); + $listing = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1); + $trace = $this->trace(array('start' => 2, 'depth' => '20')); + + if ($this->_outputFormat == 'fb') { + $kontext = array(); + foreach ((array)$context as $var => $value) { + $kontext[] = "\${$var}\t=\t" . $this->exportVar($value, 1); + } + $this->_fireError($error, $code, $description, $file, $line, $trace, $kontext); + } else { + $data = compact( + 'level', 'error', 'code', 'helpID', 'description', 'file', 'path', 'line', 'context' + ); + echo parent::_output($data); + } + } +/** + * Create a FirePHP error message + * + * @param string $error Name of error + * @param string $code Code of error + * @param string $description Description of error + * @param string $file File error occured in + * @param string $line Line error occured on + * @param string $trace Stack trace at time of error + * @param string $context context of error + * @return void + * @access protected + */ + function _fireError($error, $code, $description, $file, $line, $trace, $context) { + $name = $error . ' - ' . $description; + $message = "$error $code $description on line: $line in file: $file"; + FireCake::group($name); + FireCake::error($message, $name); + FireCake::log($context, 'Context'); + FireCake::log($trace, 'Trace'); + FireCake::groupEnd(); + } +} + + +Debugger::invoke(DebugKitDebugger::getInstance('DebugKitDebugger')); +Debugger::getInstance('DebugKitDebugger'); diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/fire_cake.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/fire_cake.php new file mode 100644 index 000000000..1b7835ccb --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/fire_cake.php @@ -0,0 +1,546 @@ + 10, + 'maxArrayDepth' => 20, + 'useNativeJsonEncode' => true, + 'includeLineNumbers' => true, + ); +/** + * Message Levels for messages sent via FirePHP + * + * @var array + */ + var $_levels = array( + 'log' => 'LOG', + 'info' => 'INFO', + 'warn' => 'WARN', + 'error' => 'ERROR', + 'dump' => 'DUMP', + 'trace' => 'TRACE', + 'exception' => 'EXCEPTION', + 'table' => 'TABLE', + 'groupStart' => 'GROUP_START', + 'groupEnd' => 'GROUP_END', + ); + + var $_version = '0.2.1'; +/** + * internal messageIndex counter + * + * @var int + * @access protected + */ + var $_messageIndex = 1; +/** + * stack of objects encoded by stringEncode() + * + * @var array + **/ + var $_encodedObjects = array(); +/** + * methodIndex to include in tracebacks when using includeLineNumbers + * + * @var array + **/ + var $_methodIndex = array('info', 'log', 'warn', 'error', 'table', 'trace'); +/** + * FireCake output status + * + * @var bool + **/ + var $_enabled = true; +/** + * get Instance of the singleton + * + * @param string $class Class instance to store in the singleton. Used with subclasses and Tests. + * @access public + * @static + * @return void + */ + function &getInstance($class = null) { + static $instance = array(); + if (!empty($class)) { + if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) { + $instance[0] =& new $class(); + $instance[0]->setOptions(); + } + } + if (!isset($instance[0]) || !$instance[0]) { + $instance[0] =& new FireCake(); + $instance[0]->setOptions(); + } + return $instance[0]; + } +/** + * setOptions + * + * @param array $options Array of options to set. + * @access public + * @static + * @return void + */ + function setOptions($options = array()) { + $_this =& FireCake::getInstance(); + if (empty($_this->options)) { + $_this->options = array_merge($_this->_defaultOptions, $options); + } else { + $_this->options = array_merge($_this->options, $options); + } + } +/** + * Return boolean based on presence of FirePHP extension + * + * @access public + * @return boolean + **/ + function detectClientExtension() { + $ua = FireCake::getUserAgent(); + if (!preg_match('/\sFirePHP\/([\.|\d]*)\s?/si', $ua, $match) || !version_compare($match[1], '0.0.6', '>=')) { + return false; + } + return true; + } +/** + * Get the Current UserAgent + * + * @access public + * @static + * @return string UserAgent string of active client connection + **/ + function getUserAgent() { + return env('HTTP_USER_AGENT'); + } +/** + * Disable FireCake output + * All subsequent output calls will not be run. + * + * @return void + **/ + function disable() { + $_this =& FireCake::getInstance(); + $_this->_enabled = false; + } +/** + * Enable FireCake output + * + * @return void + **/ + function enable() { + $_this =& FireCake::getInstance(); + $_this->_enabled = true; + } +/** + * Convenience wrapper for LOG messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function log($message, $label = null) { + FireCake::fb($message, $label, 'log'); + } +/** + * Convenience wrapper for WARN messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function warn($message, $label = null) { + FireCake::fb($message, $label, 'warn'); + } +/** + * Convenience wrapper for INFO messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function info($message, $label = null) { + FireCake::fb($message, $label, 'info'); + } +/** + * Convenience wrapper for ERROR messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function error($message, $label = null) { + FireCake::fb($message, $label, 'error'); + } +/** + * Convenience wrapper for TABLE messages + * + * @param string $message Message to log + * @param string $label Label for message (optional) + * @access public + * @static + * @return void + */ + function table($label, $message) { + FireCake::fb($message, $label, 'table'); + } +/** + * Convenience wrapper for DUMP messages + * + * @param string $message Message to log + * @param string $label Unique label for message + * @access public + * @static + * @return void + */ + function dump($label, $message) { + FireCake::fb($message, $label, 'dump'); + } +/** + * Convenience wrapper for TRACE messages + * + * @param string $label Label for message (optional) + * @access public + * @return void + */ + function trace($label) { + FireCake::fb($label, 'trace'); + } +/** + * Convenience wrapper for GROUP messages + * Messages following the group call will be nested in a group block + * + * @param string $label Label for group (optional) + * @access public + * @return void + */ + function group($label) { + FireCake::fb(null, $label, 'groupStart'); + } +/** + * Convenience wrapper for GROUPEND messages + * Closes a group block + * + * @param string $label Label for group (optional) + * @access public + * @return void + */ + function groupEnd() { + FireCake::fb(null, null, 'groupEnd'); + } +/** + * fb - Send messages with FireCake to FirePHP + * + * Much like FirePHP's fb() this method can be called with various parameter counts + * fb($message) - Just send a message defaults to LOG type + * fb($message, $type) - Send a message with a specific type + * fb($message, $label, $type) - Send a message with a custom label and type. + * + * @param mixed $message Message to output. For other parameters see usage above. + * @static + * @return void + **/ + function fb($message) { + $_this =& FireCake::getInstance(); + + if (headers_sent($filename, $linenum)) { + trigger_error(sprintf(__d('debug_kit', 'Headers already sent in %s on line %s. Cannot send log data to FirePHP.', true), $filename, $linenum), E_USER_WARNING); + return false; + } + if (!$_this->_enabled || !$_this->detectClientExtension()) { + return false; + } + + $args = func_get_args(); + $type = $label = null; + switch (count($args)) { + case 1: + $type = $_this->_levels['log']; + break; + case 2: + $type = $args[1]; + break; + case 3: + $type = $args[2]; + $label = $args[1]; + break; + default: + trigger_error(__d('debug_kit', 'Incorrect parameter count for FireCake::fb()', true), E_USER_WARNING); + return false; + } + if (isset($_this->_levels[$type])) { + $type = $_this->_levels[$type]; + } else { + $type = $_this->_levels['log']; + } + + $meta = array(); + $skipFinalObjectEncode = false; + if ($type == $_this->_levels['trace']) { + $trace = debug_backtrace(); + if (!$trace) { + return false; + } + $message = $_this->_parseTrace($trace, $args[0]); + $skipFinalObjectEncode = true; + } + + if ($_this->options['includeLineNumbers']) { + if (!isset($meta['file']) || !isset($meta['line'])) { + $trace = debug_backtrace(); + for ($i = 0, $len = count($trace); $i < $len ; $i++) { + $keySet = (isset($trace[$i]['class']) && isset($trace[$i]['function'])); + $selfCall = ($keySet && + strtolower($trace[$i]['class']) == 'firecake' && + in_array($trace[$i]['function'], $_this->_methodIndex) + ); + if ($selfCall) { + $meta['File'] = isset($trace[$i]['file']) ? Debugger::trimPath($trace[$i]['file']) : ''; + $meta['Line'] = isset($trace[$i]['line']) ? $trace[$i]['line'] : ''; + break; + } + } + } + } + + $structureIndex = 1; + if ($type == $_this->_levels['dump']) { + $structureIndex = 2; + $_this->_sendHeader('X-Wf-1-Structure-2','http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1'); + } else { + $_this->_sendHeader('X-Wf-1-Structure-1','http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'); + } + + $_this->_sendHeader('X-Wf-Protocol-1', 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'); + $_this->_sendHeader('X-Wf-1-Plugin-1', 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'. $_this->_version); + if ($type == $_this->_levels['groupStart']) { + $meta['Collapsed'] = 'true'; + } + if ($type == $_this->_levels['dump']) { + $dump = $_this->jsonEncode($message); + $msg = '{"' . $label .'":' . $dump .'}'; + } else { + $meta['Type'] = $type; + if ($label !== null) { + $meta['Label'] = $label; + } + $msg = '[' . $_this->jsonEncode($meta) . ',' . $_this->jsonEncode($message, $skipFinalObjectEncode).']'; + } + + $lines = explode("\n", chunk_split($msg, 5000, "\n")); + + foreach ($lines as $i => $line) { + if (empty($line)) { + continue; + } + $header = 'X-Wf-1-' . $structureIndex . '-1-' . $_this->_messageIndex; + if (count($lines) > 2) { + $first = ($i == 0) ? strlen($msg) : ''; + $end = ($i < count($lines) - 2) ? '\\' : ''; + $message = $first . '|' . $line . '|' . $end; + $_this->_sendHeader($header, $message); + } else { + $_this->_sendHeader($header, strlen($line) . '|' . $line . '|'); + } + $_this->_messageIndex++; + if ($_this->_messageIndex > 99999) { + trigger_error(__d('debug_kit', 'Maximum number (99,999) of messages reached!', true), E_USER_WARNING); + } + } + $_this->_sendHeader('X-Wf-1-Index', $_this->_messageIndex - 1); + return true; + } +/** + * Parse a debug backtrace + * + * @param array $trace Debug backtrace output + * @access protected + * @return array + **/ + function _parseTrace($trace, $messageName) { + $message = array(); + for ($i = 0, $len = count($trace); $i < $len ; $i++) { + $keySet = (isset($trace[$i]['class']) && isset($trace[$i]['function'])); + $selfCall = ($keySet && $trace[$i]['class'] == 'FireCake'); + if (!$selfCall) { + $message = array( + 'Class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : '', + 'Type' => isset($trace[$i]['type']) ? $trace[$i]['type'] : '', + 'Function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : '', + 'Message' => $messageName, + 'File' => isset($trace[$i]['file']) ? Debugger::trimPath($trace[$i]['file']) : '', + 'Line' => isset($trace[$i]['line']) ? $trace[$i]['line'] : '', + 'Args' => isset($trace[$i]['args']) ? $this->stringEncode($trace[$i]['args']) : '', + 'Trace' => $this->_escapeTrace(array_splice($trace, $i + 1)) + ); + break; + } + } + return $message; + } +/** + * Fix a trace for use in output + * + * @param mixed $trace Trace to fix + * @access protected + * @static + * @return string + **/ + function _escapeTrace($trace) { + for ($i = 0, $len = count($trace); $i < $len; $i++) { + if (isset($trace[$i]['file'])) { + $trace[$i]['file'] = Debugger::trimPath($trace[$i]['file']); + } + if (isset($trace[$i]['args'])) { + $trace[$i]['args'] = $this->stringEncode($trace[$i]['args']); + } + } + return $trace; + } +/** + * Encode non string objects to string. + * Filter out recursion, so no errors are raised by json_encode or $javascript->object() + * + * @param mixed $object Object or variable to encode to string. + * @param int $objectDepth Current Depth in object chains. + * @param int $arrayDepth Current Depth in array chains. + * @static + * @return void + **/ + function stringEncode($object, $objectDepth = 1, $arrayDepth = 1) { + $_this =& FireCake::getInstance(); + $return = array(); + if (is_resource($object)) { + return '** ' . (string)$object . '**'; + } + if (is_object($object)) { + if ($objectDepth == $_this->options['maxObjectDepth']) { + return '** Max Object Depth (' . $_this->options['maxObjectDepth'] . ') **'; + } + foreach ($_this->_encodedObjects as $encoded) { + if ($encoded === $object) { + return '** Recursion (' . get_class($object) . ') **'; + } + } + $_this->_encodedObjects[] =& $object; + + $return['__className'] = $class = get_class($object); + $properties = (array)$object; + foreach ($properties as $name => $property) { + $return[$name] = FireCake::stringEncode($property, 1, $objectDepth + 1); + } + array_pop($_this->_encodedObjects); + } + if (is_array($object)) { + if ($arrayDepth == $_this->options['maxArrayDepth']) { + return '** Max Array Depth ('. $_this->options['maxArrayDepth'] . ') **'; + } + foreach ($object as $key => $value) { + $return[$key] = FireCake::stringEncode($value, 1, $arrayDepth + 1); + } + } + if (is_string($object) || is_numeric($object) || is_bool($object) || is_null($object)) { + return $object; + } + return $return; + } +/** + * Encode an object into JSON + * + * @param mixed $object Object or array to json encode + * @param boolean $doIt + * @access public + * @static + * @return string + **/ + function jsonEncode($object, $skipEncode = false) { + $_this =& FireCake::getInstance(); + if (!$skipEncode) { + $object = FireCake::stringEncode($object); + } + + if (function_exists('json_encode') && $_this->options['useNativeJsonEncode']) { + return json_encode($object); + } else { + return FireCake::_jsonEncode($object); + } + } +/** + * jsonEncode Helper method for PHP4 compatibility + * + * @param mixed $object Something to encode + * @access protected + * @static + * @return string + **/ + function _jsonEncode($object) { + if (!class_exists('JavascriptHelper')) { + App::import('Helper', 'Javascript'); + } + $javascript =& new JavascriptHelper(); + $javascript->useNative = false; + if (is_string($object)) { + return '"' . $javascript->escapeString($object) . '"'; + } + return $javascript->object($object); + } +/** + * Send Headers - write headers. + * + * @access protected + * @return void + **/ + function _sendHeader($name, $value) { + header($name . ': ' . $value); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/shells/benchmark.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/shells/benchmark.php new file mode 100644 index 000000000..eb2a3dcc4 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/shells/benchmark.php @@ -0,0 +1,167 @@ +args) || count($this->args) > 1) { + return $this->help(); + } + + $url = $this->args[0]; + $defaults = array('t' => 100, 'n' => 10); + $options = array_merge($defaults, $this->params); + $times = array(); + + $this->out(String::insert(__d('debug_kit', '-> Testing :url', true), compact('url'))); + $this->out(""); + for ($i = 0; $i < $options['n']; $i++) { + if (floor($options['t'] - array_sum($times)) <= 0 || $options['n'] <= 1) { + break; + } + + $start = microtime(true); + file_get_contents($url); + $stop = microtime(true); + + $times[] = $stop - $start; + } + $this->_results($times); + } +/** + * Prints calculated results + * + * @param array $times Array of time values + * @return void + * @access protected + */ + function _results($times) { + $duration = array_sum($times); + $requests = count($times); + + $this->out(String::insert(__d('debug_kit', 'Total Requests made: :requests', true), compact('requests'))); + $this->out(String::insert(__d('debug_kit', 'Total Time elapsed: :duration (seconds)', true), compact('duration'))); + + $this->out(""); + + $this->out(String::insert(__d('debug_kit', 'Requests/Second: :rps req/sec', true), array( + 'rps' => round($requests / $duration, 3) + ))); + + $this->out(String::insert(__d('debug_kit', 'Average request time: :average-time seconds', true), array( + 'average-time' => round($duration / $requests, 3) + ))); + + $this->out(String::insert(__d('debug_kit', 'Standard deviation of average request time: :std-dev', true), array( + 'std-dev' => round($this->_deviation($times, true), 3) + ))); + + $this->out(String::insert(__d('debug_kit', 'Longest/shortest request: :longest sec/:shortest sec', true), array( + 'longest' => round(max($times), 3), + 'shortest' => round(min($times), 3) + ))); + + $this->out(""); + + } +/** + * One-pass, numerically stable calculation of population variance. + * + * Donald E. Knuth (1998). + * The Art of Computer Programming, volume 2: Seminumerical Algorithms, 3rd edn., + * p. 232. Boston: Addison-Wesley. + * + * @param array $times Array of values + * @param boolean $sample If true, calculates an unbiased estimate of the population + * variance from a finite sample. + * @return float Variance + * @access protected + */ + function _variance($times, $sample = true) { + $n = $mean = $M2 = 0; + + foreach($times as $time){ + $n += 1; + $delta = $time - $mean; + $mean = $mean + $delta/$n; + $M2 = $M2 + $delta*($time - $mean); + } + + if ($sample) $n -= 1; + + return $M2/$n; + } +/** + * Calculate the standard deviation. + * + * @param array $times Array of values + * @return float Standard deviation + * @access protected + */ + function _deviation($times, $sample = true) { + return sqrt($this->_variance($times, $sample)); + } +/** + * Help for Benchmark shell + * + * @return void + * @access public + */ + function help() { + $this->out(__d('debug_kit', "DebugKit Benchmark Shell", true)); + $this->out(""); + $this->out(__d('debug_kit', "\tAllows you to obtain some rough benchmarking statistics \n\tabout a fully qualified URL.", true)); + $this->out(""); + $this->out(__d('debug_kit', "\tUse:", true)); + $this->out(__d('debug_kit', "\t\tcake benchmark [-n iterations] [-t timeout] url", true)); + $this->out(""); + $this->out(__d('debug_kit', "\tParams:", true)); + $this->out(__d('debug_kit', "\t\t-n Number of iterations to perform. Defaults to 10. \n\t\t Must be an integer.", true)); + $this->out(__d('debug_kit', "\t\t-t Maximum total time for all iterations, in seconds. \n\t\t Defaults to 100. Must be an integer.", true)); + $this->out(""); + $this->out(__d('debug_kit', "\tIf a single iteration takes more than the \n\ttimeout specified, only one request will be made.", true)); + $this->out(""); + $this->out(__d('debug_kit', "\tExample Use:", true)); + $this->out(__d('debug_kit', "\t\tcake benchmark -n 10 -t 100 http://localhost/testsite", true)); + $this->out(""); + $this->out(__d('debug_kit', "\tNote that this benchmark does not include browser render time", true)); + $this->out(""); + $this->hr(); + $this->out(""); + } +} + diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/shells/whitespace.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/shells/whitespace.php new file mode 100644 index 000000000..d5185d454 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/vendors/shells/whitespace.php @@ -0,0 +1,69 @@ +findRecursive('.*\.php'); + $this->out("Checking *.php in ".ROOT); + foreach($r as $file) { + $c = file_get_contents($file); + if (preg_match('/^[\n\r|\n\r|\n|\r|\s]+\<\?php/',$c)) { + $this->out('!!!contains leading whitespaces: '.$this->shortPath($file)); + } + if (preg_match('/\?\>[\n\r|\n\r|\n|\r|\s]+$/',$c)) { + $this->out('!!!contains trailing whitespaces: '.$this->shortPath($file)); + } + } + } + +/** + * Much like main() except files are modified. Be sure to have + * backups or use version control. + * + * @return void + */ + function trim() { + $siteRoot = new Folder(ROOT); + + $r = $siteRoot->findRecursive('.*\.php'); + $this->out("Checking *.php in ".ROOT); + foreach($r as $file) { + $c = file_get_contents($file); + if (preg_match('/^[\n\r|\n\r|\n|\r|\s]+\<\?php/', $c) || preg_match('/\?\>[\n\r|\n\r|\n|\r|\s]+$/', $c)) { + $this->out('trimming' . $this->shortPath($file)); + $c = preg_replace('/^[\n\r|\n\r|\n|\r|\s]+\<\?php/', '[\n\r|\n\r|\n|\r|\s]+$/', '?>', $c); + file_put_contents($file, $c); + } + } + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/debug.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/debug.php new file mode 100644 index 000000000..43601e72d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/debug.php @@ -0,0 +1,100 @@ +params['url']['ext']) && $this->params['url']['ext'] === 'html') || + !isset($this->params['url']['ext']) + ); + if ($isHtml) { + $out .= sprintf("\n", __d('debug_kit', 'Starting to render', true), $name); + } + $out .= parent::element($name, $params, $loadHelpers); + if ($isHtml) { + $out .= sprintf("\n\n", __d('debug_kit', 'Finished', true), $name); + } + return $out; + } + +/** + * Renders view for given action and layout. If $file is given, that is used + * for a view filename (e.g. customFunkyView.ctp). + * Adds timers, for all subsequent rendering, and injects the debugKit toolbar. + * + * @param string $action Name of action to render for + * @param string $layout Layout to use + * @param string $file Custom filename for view + * @return string Rendered Element + */ + function render($action = null, $layout = null, $file = null) { + DebugKitDebugger::startTimer('viewRender', __d('debug_kit', 'Rendering View', true)); + + $out = parent::render($action, $layout, $file); + + DebugKitDebugger::stopTimer('viewRender'); + DebugKitDebugger::stopTimer('controllerRender'); + DebugKitDebugger::setMemoryPoint(__d('debug_kit', 'View render complete', true)); + + if (empty($this->params['requested']) && isset($this->loaded['toolbar'])) { + $backend = $this->loaded['toolbar']->getName(); + $this->loaded['toolbar']->{$backend}->send(); + } + if (empty($this->output)) { + return $out; + } + return $this->output; + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/debug_toolbar.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/debug_toolbar.ctp new file mode 100644 index 000000000..327f0eb85 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/debug_toolbar.ctp @@ -0,0 +1,58 @@ + +
    + +

    + + + +
    diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/history_panel.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/history_panel.ctp new file mode 100644 index 000000000..8f4cdab4d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/history_panel.ctp @@ -0,0 +1,195 @@ + +

    + +

    + + +
      +
    • link(__d('debug_kit', 'Restore to current request', true), + '#', array('class' => 'history-link', 'id' => 'history-restore-current')); ?> +
    • + +
    • link($previous['title'], $previous['url'], array('class' => 'history-link')); ?>
    • + +
    + + + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/log_panel.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/log_panel.ctp new file mode 100644 index 000000000..1751df991 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/log_panel.ctp @@ -0,0 +1,40 @@ + +

    +
    + $logs): ?> +

    + 0): + $headers = array(__d('debug_kit', 'Time', true), __d('debug_kit', 'Message', true)); + $rows = array(); + for ($i = 0; $i < $len; $i += 1): + $rows[] = array( + $logs[$i][0], h($logs[$i][1]) + ); + endfor; + echo $toolbar->table($rows, $headers, array('title' => $logName)); + else: ?> +

    + + +
    \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/request_panel.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/request_panel.ctp new file mode 100644 index 000000000..310090ea9 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/request_panel.ctp @@ -0,0 +1,36 @@ + +

    +

    Cake Params

    +makeNeatArray($content['params']); ?> + +

    $_GET

    +makeNeatArray($content['get']); ?> + +

    Cookie

    + + makeNeatArray($content['cookie']); ?> + +

    To view Cookies, add CookieComponent to Controller

    + + +

    +makeNeatArray($content['currentRoute']); ?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/session_panel.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/session_panel.ctp new file mode 100644 index 000000000..c729a8273 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/session_panel.ctp @@ -0,0 +1,22 @@ + +

    +makeNeatArray($content); ?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/sql_log_panel.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/sql_log_panel.ctp new file mode 100755 index 000000000..148c694dc --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/sql_log_panel.ctp @@ -0,0 +1,105 @@ +readCache('sql_log', $this->params['pass'][0]); +} +?> +

    + + $explain): ?> +
    +

    + getQueryLogs($dbName, array( + 'explain' => $explain, 'threshold' => $content['threshold'] + )); + else: + $queryLog = $content[$dbName]; + endif; + echo $toolbar->table($queryLog, $headers, array('title' => 'SQL Log ' . $dbName)); + ?> +

    +
    + +

    +
    +
    + +message('Warning', __d('debug_kit', 'No active database connections', true)); +endif; ?> + + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/timer_panel.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/timer_panel.ctp new file mode 100644 index 000000000..c5c010937 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/timer_panel.ctp @@ -0,0 +1,108 @@ +readCache('timer', $this->params['pass'][0]); + if (is_array($content)): + extract($content); + endif; +endif; +?> +
    +

    +
    + message(__d('debug_kit', 'Peak Memory Use', true), $number->toReadableSize($peakMemory)); ?> +
    + + $value): + $rows[] = array($key, $number->toReadableSize($value)); + endforeach; + + echo $toolbar->table($rows, $headers); + ?> +
    + +
    +

    +
    + precision($requestTime * 1000, 0)); ?> + message(__d('debug_kit', 'Total Request Time:', true), $totalTime)?> +
    + $timeInfo): + $indent = 0; + for ($j = 0; $j < $i; $j++) { + if (($values[$j]['end'] > $timeInfo['start']) && ($values[$j]['end']) > ($timeInfo['end'])) { + $indent++; + } + } + $indent = str_repeat(' » ', $indent); + $rows[] = array( + $indent . $timeInfo['message'], + $number->precision($timeInfo['time'] * 1000, 2), + $simpleGraph->bar( + $number->precision($timeInfo['time'] * 1000, 2), + $number->precision($timeInfo['start'] * 1000, 2), + array( + 'max' => $maxTime * 1000, + 'requestTime' => $requestTime * 1000, + ) + ) + ); + $i++; +endforeach; + +if (strtolower($toolbar->getName()) == 'firephptoolbar'): + for ($i = 0, $len = count($rows); $i < $len; $i++): + unset($rows[$i][2]); + endfor; + unset($headers[2]); +endif; + +echo $toolbar->table($rows, $headers, array('title' => 'Timers')); + +if (!isset($debugKitInHistoryMode)): + $toolbar->writeCache('timer', compact('timers', 'currentMemory', 'peakMemory', 'requestTime')); +endif; +?> +
    \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/variables_panel.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/variables_panel.ctp new file mode 100644 index 000000000..3be537d6a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/elements/variables_panel.ctp @@ -0,0 +1,25 @@ + +

    +validationErrors'] = $this->validationErrors; +$content['Loaded Helpers'] = array_keys($this->loaded); +echo $toolbar->makeNeatArray($content); ?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/fire_php_toolbar.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/fire_php_toolbar.php new file mode 100644 index 000000000..e8bb814af --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/fire_php_toolbar.php @@ -0,0 +1,99 @@ + 'firePHP', 'forceEnable' => false); +/** + * send method + * + * @return void + * @access protected + */ + function send() { + $view =& ClassRegistry::getObject('view'); + $view->element('debug_toolbar', array('plugin' => 'debug_kit', 'disableTimer' => true)); + } +/** + * makeNeatArray. + * + * wraps FireCake::dump() allowing panel elements to continue functioning + * + * @param string $values + * @return void + */ + function makeNeatArray($values) { + FireCake::info($values); + } +/** + * Create a simple message + * + * @param string $label Label of message + * @param string $message Message content + * @return void + */ + function message($label, $message) { + FireCake::log($message, $label); + } +/** + * Generate a table with FireCake + * + * @param array $rows Rows to print + * @param array $headers Headers for table + * @param array $options Additional options and params + * @return void + */ + function table($rows, $headers, $options = array()) { + $title = $headers[0]; + if (isset($options['title'])) { + $title = $options['title']; + } + foreach ($rows as $i => $row) { + $rows[$i] = array_values($row); + } + array_unshift($rows, $headers); + FireCake::table($title, $rows); + } +/** + * Start a panel which is a 'Group' in FirePHP + * + * @return void + **/ + function panelStart($title, $anchor) { + FireCake::group($title); + } +/** + * End a panel (Group) + * + * @return void + **/ + function panelEnd() { + FireCake::groupEnd(); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/html_toolbar.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/html_toolbar.php new file mode 100755 index 000000000..f772870ce --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/html_toolbar.php @@ -0,0 +1,191 @@ + 'html', 'forceEnable' => false); +/** + * Recursively goes through an array and makes neat HTML out of it. + * + * @param mixed $values Array to make pretty. + * @param int $openDepth Depth to add open class + * @param int $currentDepth current depth. + * @return string + **/ + function makeNeatArray($values, $openDepth = 0, $currentDepth = 0) { + $className ="neat-array depth-$currentDepth"; + if ($openDepth > $currentDepth) { + $className .= ' expanded'; + } + $nextDepth = $currentDepth + 1; + $out = "
      "; + if (!is_array($values)) { + if (is_bool($values)) { + $values = array($values); + } + if (is_null($values)) { + $values = array(null); + } + } + if (empty($values)) { + $values[] = '(empty)'; + } + foreach ($values as $key => $value) { + $out .= '
    • ' . $key . ''; + if ($value === null) { + $value = '(null)'; + } + if ($value === false) { + $value = '(false)'; + } + if ($value === true) { + $value = '(true)'; + } + if (empty($value) && $value != 0) { + $value = '(empty)'; + } + + if (is_object($value)) { + $value = Set::reverse($value, true); + } + + if (is_array($value) && !empty($value)) { + $out .= $this->makeNeatArray($value, $openDepth, $nextDepth); + } else { + $out .= h($value); + } + $out .= '
    • '; + } + $out .= '
    '; + return $out; + } +/** + * Create an HTML message + * + * @param string $label label content + * @param string $message message content + * @return string + */ + function message($label, $message) { + return sprintf('

    %s %s

    ', $label, $message); + } +/** + * Start a panel. + * make a link and anchor. + * + * @return void + **/ + function panelStart($title, $anchor) { + $link = $this->Html->link($title, '#' . $anchor); + return $link; + } +/** + * Create a table. + * + * @param array $rows Rows to make. + * @param array $headers Optional header row. + * @return string + */ + function table($rows, $headers = array()) { + $out = ''; + if (!empty($headers)) { + $out .= $this->Html->tableHeaders($headers); + } + $out .= $this->Html->tableCells($rows, array('class' => 'odd'), array('class' => 'even'), false, false); + $out .= '
    '; + return $out; + } +/** + * send method + * + * @return void + * @access public + */ + function send() { + if (!$this->settings['forceEnable'] && Configure::read('debug') == 0) { + return; + } + $view =& ClassRegistry::getObject('view'); + $head = $this->Html->css('/debug_kit/css/debug_toolbar'); + if (isset($view->viewVars['debugToolbarJavascript'])) { + foreach ($view->viewVars['debugToolbarJavascript'] as $script) { + if ($script) { + $head .= $this->Html->script($script); + } + } + } + if (preg_match('##', $view->output)) { + $view->output = preg_replace('##', $head . "\n", $view->output, 1); + } + $toolbar = $view->element('debug_toolbar', array('plugin' => 'debug_kit', 'disableTimer' => true)); + if (preg_match('##', $view->output)) { + $view->output = preg_replace('##', $toolbar . "\n", $view->output, 1); + } + } +/** + * Generates a SQL explain link for a given query + * + * @param string $sql SQL query string you want an explain link for. + * @return string Rendered Html link or '' if the query is not a select/describe + */ + function explainLink($sql, $connection) { + if (!preg_match('/^(SELECT)/i', $sql)) { + return ''; + } + App::import('Core', 'Security'); + $hash = Security::hash($sql . $connection, null, true); + $url = array( + 'plugin' => 'debug_kit', + 'controller' => 'toolbar_access', + 'action' => 'sql_explain' + ); + foreach (Router::prefixes() as $prefix) { + $url[$prefix] = false; + } + $this->explainLinkUid = (isset($this->explainLinkUid) ? $this->explainLinkUid + 1 : 0); + $uid = $this->explainLinkUid . '_' . rand(0, 10000); + $form = $this->Form->create('log', array('url' => $url, 'id' => "logForm{$uid}")); + $form .= $this->Form->hidden('log.ds', array('id' => "logDs{$uid}", 'value' => $connection)); + $form .= $this->Form->hidden('log.sql', array('id' => "logSql{$uid}", 'value' => $sql)); + $form .= $this->Form->hidden('log.hash', array('id' => "logHash{$uid}", 'value' => $hash)); + $form .= $this->Form->submit(__d('debug_kit', 'Explain', true), array( + 'div' => false, + 'class' => 'sql-explain-link' + )); + $form .= $this->Form->end(); + return $form; + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/simple_graph.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/simple_graph.php new file mode 100644 index 000000000..22c17865c --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/simple_graph.php @@ -0,0 +1,84 @@ + (int) Maximum value in the graphs + * - width => (int) + * - valueType => string (value, percentage) + * - style => array + * + * @var array + */ + var $__defaultSettings = array( + 'max' => 100, + 'width' => 350, + 'valueType' => 'value', + ); +/** + * bar method + * + * @param $value Value to be graphed + * @param $offset how much indentation + * @param $options Graph options + * @return string Html graph + * @access public + */ + function bar($value, $offset, $options = array()) { + $settings = array_merge($this->__defaultSettings, $options); + extract($settings); + + $graphValue = ($value / $max) * $width; + $graphValue = max(round($graphValue), 1); + + if ($valueType == 'percentage') { + $graphOffset = 0; + } else { + $graphOffset = ($offset / $max) * $width; + $graphOffset = round($graphOffset); + } + return $this->Html->div( + 'debug-kit-graph-bar', + $this->Html->div( + 'debug-kit-graph-bar-value', + ' ', + array( + 'style' => "margin-left: {$graphOffset}px; width: {$graphValue}px", + 'title' => sprintf(__d('debug_kit', "Starting %sms into the request, taking %sms", true), $offset, $value), + ) + ), + array('style' => "width: {$width}px;"), + false + ); + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/toolbar.php b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/toolbar.php new file mode 100644 index 000000000..1ae3a9734 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/helpers/toolbar.php @@ -0,0 +1,182 @@ +_myName = strtolower(get_class($this)); + $this->settings = am($this->settings, $options); + + if ($this->_myName !== 'toolbarhelper') { + return; + } + + if (!isset($options['output'])) { + $options['output'] = 'DebugKit.HtmlToolbar'; + } + App::import('Helper', $options['output']); + $className = $options['output']; + if (strpos($options['output'], '.') !== false) { + list($plugin, $className) = explode('.', $options['output']); + } + $this->_backEndClassName = $className; + $this->helpers[$options['output']] = $options; + if (isset($options['cacheKey']) && isset($options['cacheConfig'])) { + $this->_cacheKey = $options['cacheKey']; + $this->_cacheConfig = $options['cacheConfig']; + $this->_cacheEnabled = true; + } + } +/** + * Get the name of the backend Helper + * used to conditionally trigger toolbar output + * + * @return string + **/ + function getName() { + return $this->_backEndClassName; + } +/** + * call__ + * + * Allows method calls on backend helper + * + * @param string $method + * @param mixed $params + * @access public + * @return void + */ + function call__($method, $params) { + if (method_exists($this->{$this->_backEndClassName}, $method)) { + return $this->{$this->_backEndClassName}->dispatchMethod($method, $params); + } + } +/** + * Allows for writing to panel cache from view. + * Some panels generate all variables in the view by + * necessity ie. Timer. Using this method, will allow you to replace in full + * the content for a panel. + * + * @param string $name Name of the panel you are replacing. + * @param string $content Content to write to the panel. + * @return boolean Sucess of write. + **/ + function writeCache($name, $content) { + if (!$this->_cacheEnabled) { + return false; + } + $existing = (array)Cache::read($this->_cacheKey, $this->_cacheConfig); + $existing[0][$name]['content'] = $content; + return Cache::write($this->_cacheKey, $existing, $this->_cacheConfig); + } +/** + * Read the toolbar + * + * @param string $name Name of the panel you want cached data for + * @return mixed Boolean false on failure, array of data otherwise. + **/ + function readCache($name, $index = 0) { + if (!$this->_cacheEnabled) { + return false; + } + $existing = (array)Cache::read($this->_cacheKey, $this->_cacheConfig); + if (!isset($existing[$index][$name]['content'])) { + return false; + } + return $existing[$index][$name]['content']; + } +/** + * Gets the query logs for the given connection names. + * + * ### Options + * + * - explain - Whether explain links should be generated for this connection. + * - cache - Whether the toolbar_state Cache should be updated. + * - threshold - The threshold at which a visual 'maybe slow' flag should be added. + * results with rows/ms lower than $threshold will be marked. + * + * @param string $connection Connection name to get logs for. + * @param array $options Options for the query log retrieval. + * @return array Array of data to be converted into a table. + */ + function getQueryLogs($connection, $options = array()) { + $options += array('explain' => false, 'cache' => true, 'threshold' => 20); + App::import('Model', 'ConnectionManager'); + $db =& ConnectionManager::getDataSource($connection); + + if (!$db->isInterfaceSupported('getLog')) { + return array(); + } + + $out = array(); + $log = $db->getLog(); + foreach ($log['log'] as $i => $query) { + $isSlow = ( + $query['took'] > 0 && + $query['numRows'] / $query['took'] != 1 && + $query['numRows'] / $query['took'] <= $options['threshold'] + ); + $query['actions'] = ''; + $isHtml = ($this->getName() == 'HtmlToolbar'); + if ($isSlow && $isHtml) { + $query['actions'] = sprintf( + '%s', + __d('debug_kit', 'maybe slow', true) + ); + } elseif ($isSlow) { + $query['actions'] = '*'; + } + if ($options['explain'] && $isHtml) { + $query['actions'] .= $this->explainLink($query['query'], $connection); + } + if ($isHtml) { + $query['query'] = h($query['query']); + } + $out[] = $query; + } + if ($options['cache']) { + $existing = $this->readCache('sql_log'); + $existing[$connection] = $out; + $this->writeCache('sql_log', $existing); + } + return $out; + } +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/toolbar_access/history_state.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/toolbar_access/history_state.ctp new file mode 100644 index 000000000..21a1dcfef --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/toolbar_access/history_state.ctp @@ -0,0 +1,29 @@ + $panel) { + $panels[$panelName] = $this->element($panel['elementName'], array( + 'content' => $panel['content'], + 'plugin' => $panel['plugin'] + )); +} +echo $javascript->object($panels); +Configure::write('debug', 0); +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/toolbar_access/sql_explain.ctp b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/toolbar_access/sql_explain.ctp new file mode 100644 index 000000000..a38939005 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/views/toolbar_access/sql_explain.ctp @@ -0,0 +1,12 @@ + +tableHeaders($headers); +echo $html->tableCells($result); +?> +
    + \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/css/debug_toolbar.css b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/css/debug_toolbar.css new file mode 100644 index 000000000..46939561a --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/css/debug_toolbar.css @@ -0,0 +1,377 @@ +/* @override http://localhost/mark_story/site/debug_kit/css/debug_toolbar.css */ +#debug-kit-toolbar { + position: fixed; + top: 0px; + right:0px; + width: 100%; + height: 1%; + overflow: visible; + z-index:10000; + font-family: helvetica, arial, sans-serif; + font-size: 12px; + direction: ltr; +} +#debug-kit-toolbar img { + border:0; + outline:0; +} + +/* panel tabs */ +#debug-kit-toolbar #panel-tabs { + float: right; + list-style: none; + margin: 0; +} +#debug-kit-toolbar .panel-tab { + clear: none; + float: left; + margin: 0; + padding: 0; + list-style: none; +} +#debug-kit-toolbar .panel-tab > a { + float: left; + clear: none; + background: #efefef; + background: -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#cacaca)); + background: -moz-linear-gradient(top, #efefef, #cacaca); + color: #222; + padding: 6px; + border-right: 1px solid #ccc; + border-bottom: 1px solid #aaa; + font-size: 12px; + line-height: 16px; + margin: 0; + display: block; + text-decoration:none; + text-shadow:1px 1px #eee; + -moz-text-shadow:1px 1px #eee; + -webkit-text-shadow:1px 1px #eee; +} +#debug-kit-toolbar .panel-tab .active { + background: #fff; + background: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#fff)); + background: -moz-linear-gradient(top, #f5f5f5, #fff); +} +#debug-kit-toolbar .panel-tab > a:hover { + background: #fff; + background: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#fff)); + background: -moz-linear-gradient(top, #f5f5f5, #fff); + text-decoration:underline; +} +#debug-kit-toolbar .panel-tab.icon a { + padding: 4px; +} +#debug-kit-toolbar .panel-tab a.edit-value { + float: none; + display: inline; +} + +/* Hovering over link shows tab, useful for no js */ +#debug-kit-toolbar .panel-tab a:hover + .panel-content, +#debug-kit-toolbar .panel-tab a + .panel-content:hover { + display: block; +} +#debug-kit-toolbar .panel-tab.icon a { + -webkit-border-top-left-radius: 8px 8px; + -webkit-border-bottom-left-radius: 8px 8px; + -moz-border-radius-topleft: 8px; + -moz-border-radius-bottomleft: 8px +} +#debug-kit-toolbar .panel-tab.icon img { + display:block; +} + +/* panel content */ +#debug-kit-toolbar .panel-content { + position: absolute; + text-align: left; + width: auto; + top:28px; + right:0px; + background: #fff; + color: #000; + width:100%; + box-shadow:0px 5px 6px #ccc; + height: 200px; + overflow: hidden; +} + +#debug-kit-toolbar .panel-resize-region { + padding:15px; + position: absolute; + top: 0px; + bottom: 14px; + left: 0px; + right: 0px; + overflow: auto; +} + +#debug-kit-toolbar .ui-control { + background:#ccc; + background: -webkit-gradient(linear, left top, left bottom, from(#d6d6d6), to(#c2c2c2)); + background: -moz-linear-gradient(top, #d6d6d6, #c2c2c2); + text-align:center; + border-top:1px solid #afafaf; + border-bottom:1px solid #7c7c7c; + color:#666; + text-shadow: 1px 1px #eee; + -webkit-text-shadow: 1px 1px #eee; + -moz-text-shadow: 1px 1px #eee; +} +#debug-kit-toolbar .ui-button { + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} +#debug-kit-toolbar .ui-button:hover { + text-decoration: none; + background:#ccc; + background: -webkit-gradient(linear, left top, left bottom, from(#c2c2c2), to(#d6d6d6)); + background: -moz-linear-gradient(top, #c2c2c2, #d6d6d6); +} +#debug-kit-toolbar .panel-resize-handle { + cursor: row-resize; + height:14px; + line-height: 14px; + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; +} +#debug-kit-toolbar .panel-maximize, +#debug-kit-toolbar .panel-minimize { + float: right; + display: block; + width: 12px; + height: 12px; + line-height: 12px; + border-left: 1px solid #afafaf; + border-right: 1px solid #7c7c7c; +} +#debug-kit-toolbar .panel-maximize { + float: right; + margin: 10px 20px 0 0; + z-index: 999; + position: relative; +} +#debug-kit-toolbar .panel-minimize { + float: right; + margin: 10px 10px 0 0; + z-index: 999; + position: relative; +} + +/* Hide panel content by default */ +#debug-kit-toolbar .panel-content { + display: none; +} +.panel-content p { + margin: 1em 0; +} +.panel-content h2 { + padding: 0; + margin-top:0; +} +.panel-content h3 { + padding: 0; + margin-top: 1em; +} +.panel-content .info { + padding: 4px; + border-top: 1px dashed #6c6cff; + border-bottom: 1px dashed #6c6cff; +} +#debug-kit-toolbar h1, +#debug-kit-toolbar h2, +#debug-kit-toolbar h3, +#debug-kit-toolbar h4, +#debug-kit-toolbar h5, +#debug-kit-toolbar th { + color: #5d1717; + font-family: "Trebuchet MS", trebuchet, helvetica, arial, sans-serif; + margin-bottom:0.6em; + background:none; +} +#debug-kit-toolbar h1 { + font-size: 18px; +} +#debug-kit-toolbar h2 { + font-size: 16px; +} +#debug-kit-toolbar h4 { + font-size: 14px; +} + + +/* panel tables */ +#debug-kit-toolbar table.debug-table { + width: 100%; + border: 0; + clear:both; + margin-bottom: 20px; +} +#debug-kit-toolbar table.debug-table td, +#debug-kit-toolbar table.debug-table th { + text-align: left; + border: 0; + padding: 3px; +} +#debug-kit-toolbar table.debug-table th { + border-bottom: 1px solid #222; + background: 0; + color: #252525; +} +#debug-kit-toolbar table.debug-table tr:nth-child(2n+1) td { + background:#f6f6f6; +} +#debug-kit-toolbar table.debug-table tr.even td { + background:#f7f7f7; +} +#debug-kit-toolbar .debug-timers .debug-table td:nth-child(2), +#debug-kit-toolbar .debug-timers .debug-table th:nth-child(2) { + text-align:right; +} + +/** code tables **/ +#debug-kit-toolbar .code-table td { + white-space: pre; + font-family: monaco, Consolas, "courier new", courier, monospaced; +} +#debug-kit-toolbar .code-table td:first-child { + width: 15%; +} +#debug-kit-toolbar .code-table td:last-child { + width: 80%; +} + +#debug-kit-toolbar .panel-content.request { + display: block; +} + +/** Neat Array styles **/ +#debug-kit-toolbar .neat-array, +#debug-kit-toolbar .neat-array li { + list-style:none; + list-style-image:none; +} +.neat-array { + padding: 1px 2px 1px 20px; + background: #CE9E23; + list-style: none; + margin: 0 0 1em 0; +} +.neat-array .neat-array { + padding: 0 0 0 20px; + margin:0; + border-top:1px solid #CE9E23; +} +.neat-array li { + background: #FEF6E5; + border-top: 1px solid #CE9E23; + border-bottom: 1px solid #CE9E23; + margin: 0; + line-height: 1.5em; +} +.neat-array li:hover { + background: #fff; +} +.neat-array li strong { + padding: 0 8px; + font-weight: bold; +} + + +/* expandable sections */ +.neat-array li.expandable { + cursor: pointer; +} +.neat-array .expanded { + border-bottom:0; +} +.neat-array li.expandable.expanded > strong:before { + content: 'v '; +} +.neat-array li.expandable.collapsed > strong:before, +.neat-array li.expandable.expanded .expandable.collapsed > strong:before { + content: '> '; +} +.neat-array li { + cursor: default; +} + +#debug-kit-toolbar .debug-kit-graph-bar, +#debug-kit-toolbar .debug-kit-graph-bar-value { + margin: 0; + padding: 0px; + border: none; + overflow: hidden; + height: 10px; +} +#debug-kit-toolbar .debug-kit-graph-bar { + background-color: #ddd; + padding:2px; +} +#debug-kit-toolbar .debug-kit-graph-bar-value { + background-color: #CE9E23; +} + +/* Sql Log */ +#sql_log-tab td, +#sql_log-tab .slow-query-container p { + font-family: Monaco, 'Consolas', "Courier New", Courier, monospaced; +} +#debug-kit-toolbar #sql_log-tab a.show-slow { + display:block; + margin: 3px; + float:none; +} +#sql_log-tab .slow-query-container p { + display:block; + clear:both; + margin: 20px 0 5px; +} +#debug-kit-toolbar #sql_log-tab .panel-content-data a { + background: none; + border:none; +} +#sql_log-tab .slow-query { + background:#e79302; + font-size:9px; + color:#fff; + padding: 2px; + white-space:nowrap; +} +#sql_log-tab input[type=submit] { + border: 0; + background: transparent; + cursor: pointer; + font-size: 12px; + font-family: Monaco, 'Consolas', "Courier New", Courier, monospaced; +} +#sql_log-tab input[type=submit]:hover { + color: darkred; +} + +/* previous panels */ +#debug-kit-toolbar .panel-history { + display: none; + background:#eeffff; +} +#debug-kit-toolbar #history-tab ul { + margin: 20px 0 0 20px; +} +#debug-kit-toolbar #history-tab li { + margin: 0 0 5px 0; +} +#debug-kit-toolbar #history-tab .panel-content-data a { + float: none; + display:block; +} +#debug-kit-toolbar #history-tab a.active { + background: #FEF6E5; +} +#debug-kit-toolbar #history-tab a.loading:after { + content : ' Loading...'; + font-style:italic; +} diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/img/cake.icon.png b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/img/cake.icon.png new file mode 100644 index 000000000..394fa42d5 Binary files /dev/null and b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/img/cake.icon.png differ diff --git a/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/js/js_debug_toolbar.js b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/js/js_debug_toolbar.js new file mode 100644 index 000000000..57cc1cc08 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/plugins/debug_kit/webroot/js/js_debug_toolbar.js @@ -0,0 +1,767 @@ +/** + * Debug Toolbar Javascript. + * + * Creates the DEBUGKIT namespace and provides methods for extending + * and enhancing the Html toolbar. Includes library agnostic Event, Element, + * Cookie and Request wrappers. + * + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org + * @package debug_kit + * @subpackage debug_kit.views.helpers + * @since DebugKit 0.1 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +var DEBUGKIT = function () { + var undef; + return { + module: function (newmodule) { + if (this[newmodule] === undef) { + this[newmodule] = {}; + return this[newmodule]; + } + return this[newmodule]; + } + }; +}() ; + +DEBUGKIT.loader = function () { + return { + //list of methods to run on startup. + _startup: [], + + //register a new method to be run on dom ready. + register: function (method) { + this._startup.push(method); + }, + + init: function () { + for (var i = 0, callback; callback = this._startup[i]; i++) { + callback.init(); + } + } + }; +}(); + +//Util module and Element utility class. +DEBUGKIT.module('Util'); +DEBUGKIT.Util.Element = { + + //test if an element is a name node. + nodeName: function (element, name) { + return element.nodeName && element.nodeName.toLowerCase() == name.toLowerCase(); + }, + + //return a boolean if the element has the classname + hasClass: function (element, className) { + if (!element.className) { + return false; + } + return element.className.indexOf(className) > -1; + }, + + addClass: function (element, className) { + if (!element.className) { + element.className = className; + return; + } + element.className = element.className.replace(/^(.*)$/, '$1 ' + className); + }, + + removeClass: function (element, className) { + if (DEBUGKIT.Util.isArray(element)) { + DEBUGKIT.Util.Collection.apply(element, function (element) { + DEBUGKIT.Util.Element.removeClass(element, className); + }); + } + if (!element.className) { + return false; + } + element.className = element.className.replace(new RegExp(' ?(' + className +') ?'), ''); + }, + + swapClass: function (element, removeClass, addClass) { + if (!element.className) { + return false; + } + element.className = element.className.replace(removeClass, addClass); + }, + + show: function (element) { + element.style.display = 'block'; + }, + + hide: function (element) { + element.style.display = 'none'; + }, + + //go between hide() and show() depending on element.style.display + toggle: function (element) { + if (element.style.display == 'none') { + this.show(element); + return; + } + this.hide(element); + }, + + _walk: function (element, walk) { + var sibling = element[walk]; + while (true) { + if (sibling.nodeType == 1) { + break; + } + sibling = sibling[walk]; + } + return sibling; + }, + + getNext: function (element) { + return this._walk(element, 'nextSibling'); + }, + + getPrevious: function (element) { + return this._walk(element, 'previousSibling'); + }, + + //get or set an element's height, omit value to get, add value (integer) to set. + height: function (element, value) { + //get value + if (value === undefined) { + return parseInt(this.getStyle(element, 'height')); + } + element.style.height = value + 'px'; + }, + + //gets the style in css format for property + getStyle: function (element, property) { + if (element.currentStyle) { + property = property.replace(/-[a-z]/g, function (match) { + return match.charAt(1).toUpperCase(); + }); + return element.currentStyle[property]; + } + if (window.getComputedStyle) { + return document.defaultView.getComputedStyle(element, null).getPropertyValue(property); + } + } +}; + +DEBUGKIT.Util.Collection = { + /* + Apply the passed function to each item in the collection. + The current element in the collection will be `this` in the callback + The callback is also passed the element and the index as arguments. + Optionally you can supply a binding parameter to change `this` in the callback. + */ + apply: function (collection, callback, binding) { + var name, thisVar, i = 0, len = collection.length; + + if (len === undefined) { + for (name in collection) { + thisVar = (binding === undefined) ? collection[name] : binding; + callback.apply(thisVar, [collection[name], name]); + } + } else { + for (; i < len; i++) { + thisVar = (binding === undefined) ? collection[i] : binding; + callback.apply(thisVar, [collection[i], i]); + } + } + } +} + + +//Event binding +DEBUGKIT.Util.Event = function () { + var _listeners = {}, + _eventId = 0; + + var preventDefault = function () { + this.returnValue = false; + } + + var stopPropagation = function () { + this.cancelBubble = true; + } + + // Fixes IE's broken event object, adds in common methods + properties. + var fixEvent = function (event) { + if (!event.preventDefault) { + event.preventDefault = preventDefault; + } + if (!event.stopPropagation) { + event.stopPropagation = stopPropagation; + } + if (!event.target) { + event.target = event.srcElement || document; + } + if (event.pageX == null && event.clientX != null) { + var doc = document.body; + event.pageX = event.clientX + (doc.scrollLeft || 0) - (doc.clientLeft || 0); + event.pageY = event.clientY + (doc.scrollTop || 0) - (doc.clientTop || 0); + } + return event; + } + + return { + // bind an event listener of type to element, handler is your method. + addEvent: function(element, type, handler, capture) { + capture = (capture === undefined) ? false : capture; + + var callback = function (event) { + event = fixEvent(event || window.event); + handler.apply(element, [event]); + }; + + if (element.addEventListener) { + element.addEventListener(type, callback, capture); + } else if (element.attachEvent) { + type = 'on' + type; + element.attachEvent(type, callback); + } else { + type = 'on' + type; + element[type] = callback; + } + _listeners[++_eventId] = {element: element, type: type, handler: callback}; + }, + + // destroy an event listener. requires the exact same function as was used for attaching + // the event. + removeEvent: function (element, type, handler) { + if (element.removeEventListener) { + element.removeEventListener(type, handler, false); + } else if (element.detachEvent) { + type = 'on' + type; + element.detachEvent(type, handler); + } else { + type = 'on' + type; + element[type] = null; + } + }, + + // bind an event to the DOMContentLoaded or other similar event. + domready: function(callback) { + if (document.addEventListener) { + return document.addEventListener("DOMContentLoaded", callback, false); + } + + if (document.all && !window.opera) { + //Define a "blank" external JavaScript tag + document.write('', 'js'); + $this->mapHandler('comment', 'ignore'); + $this->addEntryPattern('', 'comment'); + } + + /** + * Pattern matches to start and end a tag. + * @param string $tag Name of tag to scan for. + * @access private + */ + protected function addTag($tag) { + $this->addSpecialPattern("", 'text', 'acceptEndToken'); + $this->addEntryPattern("<$tag", 'text', 'tag'); + } + + /** + * Pattern matches to parse the inside of a tag + * including the attributes and their quoting. + * @access private + */ + protected function addInTagTokens() { + $this->mapHandler('tag', 'acceptStartToken'); + $this->addSpecialPattern('\s+', 'tag', 'ignore'); + $this->addAttributeTokens(); + $this->addExitPattern('/>', 'tag'); + $this->addExitPattern('>', 'tag'); + } + + /** + * Matches attributes that are either single quoted, + * double quoted or unquoted. + * @access private + */ + protected function addAttributeTokens() { + $this->mapHandler('dq_attribute', 'acceptAttributeToken'); + $this->addEntryPattern('=\s*"', 'tag', 'dq_attribute'); + $this->addPattern("\\\\\"", 'dq_attribute'); + $this->addExitPattern('"', 'dq_attribute'); + $this->mapHandler('sq_attribute', 'acceptAttributeToken'); + $this->addEntryPattern("=\s*'", 'tag', 'sq_attribute'); + $this->addPattern("\\\\'", 'sq_attribute'); + $this->addExitPattern("'", 'sq_attribute'); + $this->mapHandler('uq_attribute', 'acceptAttributeToken'); + $this->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute'); + } +} + +/** + * Converts HTML tokens into selected SAX events. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHtmlSaxParser { + private $lexer; + private $listener; + private $tag; + private $attributes; + private $current_attribute; + + /** + * Sets the listener. + * @param SimplePhpPageBuilder $listener SAX event handler. + * @access public + */ + function __construct($listener) { + $this->listener = $listener; + $this->lexer = $this->createLexer($this); + $this->tag = ''; + $this->attributes = array(); + $this->current_attribute = ''; + } + + /** + * Runs the content through the lexer which + * should call back to the acceptors. + * @param string $raw Page text to parse. + * @return boolean False if parse error. + * @access public + */ + function parse($raw) { + return $this->lexer->parse($raw); + } + + /** + * Sets up the matching lexer. Starts in 'text' mode. + * @param SimpleSaxParser $parser Event generator, usually $self. + * @return SimpleLexer Lexer suitable for this parser. + * @access public + */ + static function createLexer(&$parser) { + return new SimpleHtmlLexer($parser); + } + + /** + * Accepts a token from the tag mode. If the + * starting element completes then the element + * is dispatched and the current attributes + * set back to empty. The element or attribute + * name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptStartToken($token, $event) { + if ($event == LEXER_ENTER) { + $this->tag = strtolower(substr($token, 1)); + return true; + } + if ($event == LEXER_EXIT) { + $success = $this->listener->startElement( + $this->tag, + $this->attributes); + $this->tag = ''; + $this->attributes = array(); + return $success; + } + if ($token != '=') { + $this->current_attribute = strtolower(html_entity_decode($token, ENT_QUOTES)); + $this->attributes[$this->current_attribute] = ''; + } + return true; + } + + /** + * Accepts a token from the end tag mode. + * The element name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEndToken($token, $event) { + if (! preg_match('/<\/(.*)>/', $token, $matches)) { + return false; + } + return $this->listener->endElement(strtolower($matches[1])); + } + + /** + * Part of the tag data. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptAttributeToken($token, $event) { + if ($this->current_attribute) { + if ($event == LEXER_UNMATCHED) { + $this->attributes[$this->current_attribute] .= + html_entity_decode($token, ENT_QUOTES); + } + if ($event == LEXER_SPECIAL) { + $this->attributes[$this->current_attribute] .= + preg_replace('/^=\s*/' , '', html_entity_decode($token, ENT_QUOTES)); + } + } + return true; + } + + /** + * A character entity. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEntityToken($token, $event) { + } + + /** + * Character data between tags regarded as + * important. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptTextToken($token, $event) { + return $this->listener->addContent($token); + } + + /** + * Incoming data to be ignored. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function ignore($token, $event) { + return true; + } +} + +/** + * SAX event handler. Maintains a list of + * open tags and dispatches them as they close. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePhpPageBuilder { + private $tags; + private $page; + private $private_content_tag; + private $open_forms = array(); + private $complete_forms = array(); + private $frameset = false; + private $loading_frames = array(); + private $frameset_nesting_level = 0; + private $left_over_labels = array(); + + /** + * Frees up any references so as to allow the PHP garbage + * collection from unset() to work. + * @access public + */ + function free() { + unset($this->tags); + unset($this->page); + unset($this->private_content_tags); + $this->open_forms = array(); + $this->complete_forms = array(); + $this->frameset = false; + $this->loading_frames = array(); + $this->frameset_nesting_level = 0; + $this->left_over_labels = array(); + } + + /** + * This builder is always available. + * @return boolean Always true. + */ + function can() { + return true; + } + + /** + * Reads the raw content and send events + * into the page to be built. + * @param $response SimpleHttpResponse Fetched response. + * @return SimplePage Newly parsed page. + * @access public + */ + function parse($response) { + $this->tags = array(); + $this->page = $this->createPage($response); + $parser = $this->createParser($this); + $parser->parse($response->getContent()); + $this->acceptPageEnd(); + $page = $this->page; + $this->free(); + return $page; + } + + /** + * Creates an empty page. + * @return SimplePage New unparsed page. + * @access protected + */ + protected function createPage($response) { + return new SimplePage($response); + } + + /** + * Creates the parser used with the builder. + * @param SimplePhpPageBuilder $listener Target of parser. + * @return SimpleSaxParser Parser to generate + * events for the builder. + * @access protected + */ + protected function createParser(&$listener) { + return new SimpleHtmlSaxParser($listener); + } + + /** + * Start of element event. Opens a new tag. + * @param string $name Element name. + * @param hash $attributes Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + $factory = new SimpleTagBuilder(); + $tag = $factory->createTag($name, $attributes); + if (! $tag) { + return true; + } + if ($tag->getTagName() == 'label') { + $this->acceptLabelStart($tag); + $this->openTag($tag); + return true; + } + if ($tag->getTagName() == 'form') { + $this->acceptFormStart($tag); + return true; + } + if ($tag->getTagName() == 'frameset') { + $this->acceptFramesetStart($tag); + return true; + } + if ($tag->getTagName() == 'frame') { + $this->acceptFrame($tag); + return true; + } + if ($tag->isPrivateContent() && ! isset($this->private_content_tag)) { + $this->private_content_tag = &$tag; + } + if ($tag->expectEndTag()) { + $this->openTag($tag); + return true; + } + $this->acceptTag($tag); + return true; + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + if ($name == 'label') { + $this->acceptLabelEnd(); + return true; + } + if ($name == 'form') { + $this->acceptFormEnd(); + return true; + } + if ($name == 'frameset') { + $this->acceptFramesetEnd(); + return true; + } + if ($this->hasNamedTagOnOpenTagStack($name)) { + $tag = array_pop($this->tags[$name]); + if ($tag->isPrivateContent() && $this->private_content_tag->getTagName() == $name) { + unset($this->private_content_tag); + } + $this->addContentTagToOpenTags($tag); + $this->acceptTag($tag); + return true; + } + return true; + } + + /** + * Test to see if there are any open tags awaiting + * closure that match the tag name. + * @param string $name Element name. + * @return boolean True if any are still open. + * @access private + */ + protected function hasNamedTagOnOpenTagStack($name) { + return isset($this->tags[$name]) && (count($this->tags[$name]) > 0); + } + + /** + * Unparsed, but relevant data. The data is added + * to every open tag. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + if (isset($this->private_content_tag)) { + $this->private_content_tag->addContent($text); + } else { + $this->addContentToAllOpenTags($text); + } + return true; + } + + /** + * Any content fills all currently open tags unless it + * is part of an option tag. + * @param string $text May include unparsed tags. + * @access private + */ + protected function addContentToAllOpenTags($text) { + foreach (array_keys($this->tags) as $name) { + for ($i = 0, $count = count($this->tags[$name]); $i < $count; $i++) { + $this->tags[$name][$i]->addContent($text); + } + } + } + + /** + * Parsed data in tag form. The parsed tag is added + * to every open tag. Used for adding options to select + * fields only. + * @param SimpleTag $tag Option tags only. + * @access private + */ + protected function addContentTagToOpenTags(&$tag) { + if ($tag->getTagName() != 'option') { + return; + } + foreach (array_keys($this->tags) as $name) { + for ($i = 0, $count = count($this->tags[$name]); $i < $count; $i++) { + $this->tags[$name][$i]->addTag($tag); + } + } + } + + /** + * Opens a tag for receiving content. Multiple tags + * will be receiving input at the same time. + * @param SimpleTag $tag New content tag. + * @access private + */ + protected function openTag($tag) { + $name = $tag->getTagName(); + if (! in_array($name, array_keys($this->tags))) { + $this->tags[$name] = array(); + } + $this->tags[$name][] = $tag; + } + + /** + * Adds a tag to the page. + * @param SimpleTag $tag Tag to accept. + * @access public + */ + protected function acceptTag($tag) { + if ($tag->getTagName() == "a") { + $this->page->addLink($tag); + } elseif ($tag->getTagName() == "base") { + $this->page->setBase($tag->getAttribute('href')); + } elseif ($tag->getTagName() == "title") { + $this->page->setTitle($tag); + } elseif ($this->isFormElement($tag->getTagName())) { + for ($i = 0; $i < count($this->open_forms); $i++) { + $this->open_forms[$i]->addWidget($tag); + } + $this->last_widget = $tag; + } + } + + /** + * Opens a label for a described widget. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + protected function acceptLabelStart($tag) { + $this->label = $tag; + unset($this->last_widget); + } + + /** + * Closes the most recently opened label. + * @access public + */ + protected function acceptLabelEnd() { + if (isset($this->label)) { + if (isset($this->last_widget)) { + $this->last_widget->setLabel($this->label->getText()); + unset($this->last_widget); + } else { + $this->left_over_labels[] = SimpleTestCompatibility::copy($this->label); + } + unset($this->label); + } + } + + /** + * Tests to see if a tag is a possible form + * element. + * @param string $name HTML element name. + * @return boolean True if form element. + * @access private + */ + protected function isFormElement($name) { + return in_array($name, array('input', 'button', 'textarea', 'select')); + } + + /** + * Opens a form. New widgets go here. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + protected function acceptFormStart($tag) { + $this->open_forms[] = new SimpleForm($tag, $this->page); + } + + /** + * Closes the most recently opened form. + * @access public + */ + protected function acceptFormEnd() { + if (count($this->open_forms)) { + $this->complete_forms[] = array_pop($this->open_forms); + } + } + + /** + * Opens a frameset. A frameset may contain nested + * frameset tags. + * @param SimpleFramesetTag $tag Tag to accept. + * @access public + */ + protected function acceptFramesetStart($tag) { + if (! $this->isLoadingFrames()) { + $this->frameset = $tag; + } + $this->frameset_nesting_level++; + } + + /** + * Closes the most recently opened frameset. + * @access public + */ + protected function acceptFramesetEnd() { + if ($this->isLoadingFrames()) { + $this->frameset_nesting_level--; + } + } + + /** + * Takes a single frame tag and stashes it in + * the current frame set. + * @param SimpleFrameTag $tag Tag to accept. + * @access public + */ + protected function acceptFrame($tag) { + if ($this->isLoadingFrames()) { + if ($tag->getAttribute('src')) { + $this->loading_frames[] = $tag; + } + } + } + + /** + * Test to see if in the middle of reading + * a frameset. + * @return boolean True if inframeset. + * @access private + */ + protected function isLoadingFrames() { + return $this->frameset and $this->frameset_nesting_level > 0; + } + + /** + * Marker for end of complete page. Any work in + * progress can now be closed. + * @access public + */ + protected function acceptPageEnd() { + while (count($this->open_forms)) { + $this->complete_forms[] = array_pop($this->open_forms); + } + foreach ($this->left_over_labels as $label) { + for ($i = 0, $count = count($this->complete_forms); $i < $count; $i++) { + $this->complete_forms[$i]->attachLabelBySelector( + new SimpleById($label->getFor()), + $label->getText()); + } + } + $this->page->setForms($this->complete_forms); + $this->page->setFrames($this->loading_frames); + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/reflection_php4.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/reflection_php4.php new file mode 100644 index 000000000..39801ea1b --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/reflection_php4.php @@ -0,0 +1,136 @@ +_interface = $interface; + } + + /** + * Checks that a class has been declared. + * @return boolean True if defined. + * @access public + */ + function classExists() { + return class_exists($this->_interface); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classExistsSansAutoload() { + return class_exists($this->_interface); + } + + /** + * Checks that a class or interface has been + * declared. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExists() { + return class_exists($this->_interface); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExistsSansAutoload() { + return class_exists($this->_interface); + } + + /** + * Gets the list of methods on a class or + * interface. + * @returns array List of method names. + * @access public + */ + function getMethods() { + return get_class_methods($this->_interface); + } + + /** + * Gets the list of interfaces from a class. If the + * class name is actually an interface then just that + * interface is returned. + * @returns array List of interfaces. + * @access public + */ + function getInterfaces() { + return array(); + } + + /** + * Finds the parent class name. + * @returns string Parent class name. + * @access public + */ + function getParent() { + return strtolower(get_parent_class($this->_interface)); + } + + /** + * Determines if the class is abstract, which for PHP 4 + * will never be the case. + * @returns boolean True if abstract. + * @access public + */ + function isAbstract() { + return false; + } + + /** + * Determines if the the entity is an interface, which for PHP 4 + * will never be the case. + * @returns boolean True if interface. + * @access public + */ + function isInterface() { + return false; + } + + /** + * Scans for final methods, but as it's PHP 4 there + * aren't any. + * @returns boolean True if the class has a final method. + * @access public + */ + function hasFinal() { + return false; + } + + /** + * Gets the source code matching the declaration + * of a method. + * @param string $method Method name. + * @access public + */ + function getSignature($method) { + return "function &$method()"; + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/reflection_php5.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/reflection_php5.php new file mode 100644 index 000000000..43d8a7b28 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/reflection_php5.php @@ -0,0 +1,386 @@ +interface = $interface; + } + + /** + * Checks that a class has been declared. Versions + * before PHP5.0.2 need a check that it's not really + * an interface. + * @return boolean True if defined. + * @access public + */ + function classExists() { + if (! class_exists($this->interface)) { + return false; + } + $reflection = new ReflectionClass($this->interface); + return ! $reflection->isInterface(); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classExistsSansAutoload() { + return class_exists($this->interface, false); + } + + /** + * Checks that a class or interface has been + * declared. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExists() { + return $this->classOrInterfaceExistsWithAutoload($this->interface, true); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExistsSansAutoload() { + return $this->classOrInterfaceExistsWithAutoload($this->interface, false); + } + + /** + * Needed to select the autoload feature in PHP5 + * for classes created dynamically. + * @param string $interface Class or interface name. + * @param boolean $autoload True totriggerautoload. + * @return boolean True if interface defined. + * @access private + */ + protected function classOrInterfaceExistsWithAutoload($interface, $autoload) { + if (function_exists('interface_exists')) { + if (interface_exists($this->interface, $autoload)) { + return true; + } + } + return class_exists($this->interface, $autoload); + } + + /** + * Gets the list of methods on a class or + * interface. + * @returns array List of method names. + * @access public + */ + function getMethods() { + return array_unique(get_class_methods($this->interface)); + } + + /** + * Gets the list of interfaces from a class. If the + * class name is actually an interface then just that + * interface is returned. + * @returns array List of interfaces. + * @access public + */ + function getInterfaces() { + $reflection = new ReflectionClass($this->interface); + if ($reflection->isInterface()) { + return array($this->interface); + } + return $this->onlyParents($reflection->getInterfaces()); + } + + /** + * Gets the list of methods for the implemented + * interfaces only. + * @returns array List of enforced method signatures. + * @access public + */ + function getInterfaceMethods() { + $methods = array(); + foreach ($this->getInterfaces() as $interface) { + $methods = array_merge($methods, get_class_methods($interface)); + } + return array_unique($methods); + } + + /** + * Checks to see if the method signature has to be tightly + * specified. + * @param string $method Method name. + * @returns boolean True if enforced. + * @access private + */ + protected function isInterfaceMethod($method) { + return in_array($method, $this->getInterfaceMethods()); + } + + /** + * Finds the parent class name. + * @returns string Parent class name. + * @access public + */ + function getParent() { + $reflection = new ReflectionClass($this->interface); + $parent = $reflection->getParentClass(); + if ($parent) { + return $parent->getName(); + } + return false; + } + + /** + * Trivially determines if the class is abstract. + * @returns boolean True if abstract. + * @access public + */ + function isAbstract() { + $reflection = new ReflectionClass($this->interface); + return $reflection->isAbstract(); + } + + /** + * Trivially determines if the class is an interface. + * @returns boolean True if interface. + * @access public + */ + function isInterface() { + $reflection = new ReflectionClass($this->interface); + return $reflection->isInterface(); + } + + /** + * Scans for final methods, as they screw up inherited + * mocks by not allowing you to override them. + * @returns boolean True if the class has a final method. + * @access public + */ + function hasFinal() { + $reflection = new ReflectionClass($this->interface); + foreach ($reflection->getMethods() as $method) { + if ($method->isFinal()) { + return true; + } + } + return false; + } + + /** + * Whittles a list of interfaces down to only the + * necessary top level parents. + * @param array $interfaces Reflection API interfaces + * to reduce. + * @returns array List of parent interface names. + * @access private + */ + protected function onlyParents($interfaces) { + $parents = array(); + $blacklist = array(); + foreach ($interfaces as $interface) { + foreach($interfaces as $possible_parent) { + if ($interface->getName() == $possible_parent->getName()) { + continue; + } + if ($interface->isSubClassOf($possible_parent)) { + $blacklist[$possible_parent->getName()] = true; + } + } + if (!isset($blacklist[$interface->getName()])) { + $parents[] = $interface->getName(); + } + } + return $parents; + } + + /** + * Checks whether a method is abstract or not. + * @param string $name Method name. + * @return bool true if method is abstract, else false + * @access private + */ + protected function isAbstractMethod($name) { + $interface = new ReflectionClass($this->interface); + if (! $interface->hasMethod($name)) { + return false; + } + return $interface->getMethod($name)->isAbstract(); + } + + /** + * Checks whether a method is the constructor. + * @param string $name Method name. + * @return bool true if method is the constructor + * @access private + */ + protected function isConstructor($name) { + return ($name == '__construct') || ($name == $this->interface); + } + + /** + * Checks whether a method is abstract in all parents or not. + * @param string $name Method name. + * @return bool true if method is abstract in parent, else false + * @access private + */ + protected function isAbstractMethodInParents($name) { + $interface = new ReflectionClass($this->interface); + $parent = $interface->getParentClass(); + while($parent) { + if (! $parent->hasMethod($name)) { + return false; + } + if ($parent->getMethod($name)->isAbstract()) { + return true; + } + $parent = $parent->getParentClass(); + } + return false; + } + + /** + * Checks whether a method is static or not. + * @param string $name Method name + * @return bool true if method is static, else false + * @access private + */ + protected function isStaticMethod($name) { + $interface = new ReflectionClass($this->interface); + if (! $interface->hasMethod($name)) { + return false; + } + return $interface->getMethod($name)->isStatic(); + } + + /** + * Writes the source code matching the declaration + * of a method. + * @param string $name Method name. + * @return string Method signature up to last + * bracket. + * @access public + */ + function getSignature($name) { + if ($name == '__set') { + return 'function __set($key, $value)'; + } + if ($name == '__call') { + return 'function __call($method, $arguments)'; + } + if (version_compare(phpversion(), '5.1.0', '>=')) { + if (in_array($name, array('__get', '__isset', $name == '__unset'))) { + return "function {$name}(\$key)"; + } + } + if ($name == '__toString') { + return "function $name()"; + } + + // This wonky try-catch is a work around for a faulty method_exists() + // in early versions of PHP 5 which would return false for static + // methods. The Reflection classes work fine, but hasMethod() + // doesn't exist prior to PHP 5.1.0, so we need to use a more crude + // detection method. + try { + $interface = new ReflectionClass($this->interface); + $interface->getMethod($name); + } catch (ReflectionException $e) { + return "function $name()"; + } + return $this->getFullSignature($name); + } + + /** + * For a signature specified in an interface, full + * details must be replicated to be a valid implementation. + * @param string $name Method name. + * @return string Method signature up to last + * bracket. + * @access private + */ + protected function getFullSignature($name) { + $interface = new ReflectionClass($this->interface); + $method = $interface->getMethod($name); + $reference = $method->returnsReference() ? '&' : ''; + $static = $method->isStatic() ? 'static ' : ''; + return "{$static}function $reference$name(" . + implode(', ', $this->getParameterSignatures($method)) . + ")"; + } + + /** + * Gets the source code for each parameter. + * @param ReflectionMethod $method Method object from + * reflection API + * @return array List of strings, each + * a snippet of code. + * @access private + */ + protected function getParameterSignatures($method) { + $signatures = array(); + foreach ($method->getParameters() as $parameter) { + $signature = ''; + $type = $parameter->getClass(); + if (is_null($type) && version_compare(phpversion(), '5.1.0', '>=') && $parameter->isArray()) { + $signature .= 'array '; + } elseif (!is_null($type)) { + $signature .= $type->getName() . ' '; + } + if ($parameter->isPassedByReference()) { + $signature .= '&'; + } + $signature .= '$' . $this->suppressSpurious($parameter->getName()); + if ($this->isOptional($parameter)) { + $signature .= ' = null'; + } + $signatures[] = $signature; + } + return $signatures; + } + + /** + * The SPL library has problems with the + * Reflection library. In particular, you can + * get extra characters in parameter names :(. + * @param string $name Parameter name. + * @return string Cleaner name. + * @access private + */ + protected function suppressSpurious($name) { + return str_replace(array('[', ']', ' '), '', $name); + } + + /** + * Test of a reflection parameter being optional + * that works with early versions of PHP5. + * @param reflectionParameter $parameter Is this optional. + * @return boolean True if optional. + * @access private + */ + protected function isOptional($parameter) { + if (method_exists($parameter, 'isOptional')) { + return $parameter->isOptional(); + } + return false; + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/remote.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/remote.php new file mode 100644 index 000000000..4bb37b7c5 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/remote.php @@ -0,0 +1,115 @@ +url = $url; + $this->dry_url = $dry_url ? $dry_url : $url; + $this->size = false; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->url; + } + + /** + * Runs the top level test for this class. Currently + * reads the data as a single chunk. I'll fix this + * once I have added iteration to the browser. + * @param SimpleReporter $reporter Target of test results. + * @returns boolean True if no failures. + * @access public + */ + function run($reporter) { + $browser = $this->createBrowser(); + $xml = $browser->get($this->url); + if (! $xml) { + trigger_error('Cannot read remote test URL [' . $this->url . ']'); + return false; + } + $parser = $this->createParser($reporter); + if (! $parser->parse($xml)) { + trigger_error('Cannot parse incoming XML from [' . $this->url . ']'); + return false; + } + return true; + } + + /** + * Creates a new web browser object for fetching + * the XML report. + * @return SimpleBrowser New browser. + * @access protected + */ + protected function createBrowser() { + return new SimpleBrowser(); + } + + /** + * Creates the XML parser. + * @param SimpleReporter $reporter Target of test results. + * @return SimpleTestXmlListener XML reader. + * @access protected + */ + protected function createParser($reporter) { + return new SimpleTestXmlParser($reporter); + } + + /** + * Accessor for the number of subtests. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + if ($this->size === false) { + $browser = $this->createBrowser(); + $xml = $browser->get($this->dry_url); + if (! $xml) { + trigger_error('Cannot read remote test URL [' . $this->dry_url . ']'); + return false; + } + $reporter = new SimpleReporter(); + $parser = $this->createParser($reporter); + if (! $parser->parse($xml)) { + trigger_error('Cannot parse incoming XML from [' . $this->dry_url . ']'); + return false; + } + $this->size = $reporter->getTestCaseCount(); + } + return $this->size; + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/reporter.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/reporter.php new file mode 100644 index 000000000..bf8d2ac11 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/reporter.php @@ -0,0 +1,445 @@ +character_set = $character_set; + } + + /** + * Paints the top of the web page setting the + * title to the name of the starting test. + * @param string $test_name Name class of test. + * @access public + */ + function paintHeader($test_name) { + $this->sendNoCacheHeaders(); + print ""; + print "\n\n$test_name\n"; + print "\n"; + print "\n"; + print "\n\n"; + print "

    $test_name

    \n"; + flush(); + } + + /** + * Send the headers necessary to ensure the page is + * reloaded on every request. Otherwise you could be + * scratching your head over out of date test data. + * @access public + */ + static function sendNoCacheHeaders() { + if (! headers_sent()) { + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + } + } + + /** + * Paints the CSS. Add additional styles here. + * @return string CSS code as text. + * @access protected + */ + protected function getCss() { + return ".fail { background-color: inherit; color: red; }" . + ".pass { background-color: inherit; color: green; }" . + " pre { background-color: lightgray; color: inherit; }"; + } + + /** + * Paints the end of the test with a summary of + * the passes and failures. + * @param string $test_name Name class of test. + * @access public + */ + function paintFooter($test_name) { + $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green"); + print "
    "; + print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount(); + print " test cases complete:\n"; + print "" . $this->getPassCount() . " passes, "; + print "" . $this->getFailCount() . " fails and "; + print "" . $this->getExceptionCount() . " exceptions."; + print "
    \n"; + print "\n\n"; + } + + /** + * Paints the test failure with a breadcrumbs + * trail of the nesting test suites below the + * top level test. + * @param string $message Failure message displayed in + * the context of the other tests. + */ + function paintFail($message) { + parent::paintFail($message); + print "Fail: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
    \n"; + } + + /** + * Paints a PHP error. + * @param string $message Message is ignored. + * @access public + */ + function paintError($message) { + parent::paintError($message); + print "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
    \n"; + } + + /** + * Paints a PHP exception. + * @param Exception $exception Exception to display. + * @access public + */ + function paintException($exception) { + parent::paintException($exception); + print "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print " -> " . $this->htmlEntities($message) . "
    \n"; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print "Skipped: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
    \n"; + } + + /** + * Paints formatted text such as dumped privateiables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + print '
    ' . $this->htmlEntities($message) . '
    '; + } + + /** + * Character set adjusted entity conversion. + * @param string $message Plain text or Unicode message. + * @return string Browser readable message. + * @access protected + */ + protected function htmlEntities($message) { + return htmlentities($message, ENT_COMPAT, $this->character_set); + } +} + +/** + * Sample minimal test displayer. Generates only + * failure messages and a pass count. For command + * line use. I've tried to make it look like JUnit, + * but I wanted to output the errors as they arrived + * which meant dropping the dots. + * @package SimpleTest + * @subpackage UnitTester + */ +class TextReporter extends SimpleReporter { + + /** + * Does nothing yet. The first output will + * be sent on the first test start. + */ + function __construct() { + parent::__construct(); + } + + /** + * Paints the title only. + * @param string $test_name Name class of test. + * @access public + */ + function paintHeader($test_name) { + if (! SimpleReporter::inCli()) { + header('Content-type: text/plain'); + } + print "$test_name\n"; + flush(); + } + + /** + * Paints the end of the test with a summary of + * the passes and failures. + * @param string $test_name Name class of test. + * @access public + */ + function paintFooter($test_name) { + if ($this->getFailCount() + $this->getExceptionCount() == 0) { + print "OK\n"; + } else { + print "FAILURES!!!\n"; + } + print "Test cases run: " . $this->getTestCaseProgress() . + "/" . $this->getTestCaseCount() . + ", Passes: " . $this->getPassCount() . + ", Failures: " . $this->getFailCount() . + ", Exceptions: " . $this->getExceptionCount() . "\n"; + } + + /** + * Paints the test failure as a stack trace. + * @param string $message Failure message displayed in + * the context of the other tests. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print $this->getFailCount() . ") $message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Paints a PHP error or exception. + * @param string $message Message to be shown. + * @access public + * @abstract + */ + function paintError($message) { + parent::paintError($message); + print "Exception " . $this->getExceptionCount() . "!\n$message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Paints a PHP error or exception. + * @param Exception $exception Exception to describe. + * @access public + * @abstract + */ + function paintException($exception) { + parent::paintException($exception); + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print "Exception " . $this->getExceptionCount() . "!\n$message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print "Skip: $message\n"; + } + + /** + * Paints formatted text such as dumped privateiables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + print "$message\n"; + flush(); + } +} + +/** + * Runs just a single test group, a single case or + * even a single test within that case. + * @package SimpleTest + * @subpackage UnitTester + */ +class SelectiveReporter extends SimpleReporterDecorator { + private $just_this_case = false; + private $just_this_test = false; + private $on; + + /** + * Selects the test case or group to be run, + * and optionally a specific test. + * @param SimpleScorer $reporter Reporter to receive events. + * @param string $just_this_case Only this case or group will run. + * @param string $just_this_test Only this test method will run. + */ + function __construct($reporter, $just_this_case = false, $just_this_test = false) { + if (isset($just_this_case) && $just_this_case) { + $this->just_this_case = strtolower($just_this_case); + $this->off(); + } else { + $this->on(); + } + if (isset($just_this_test) && $just_this_test) { + $this->just_this_test = strtolower($just_this_test); + } + parent::__construct($reporter); + } + + /** + * Compares criteria to actual the case/group name. + * @param string $test_case The incoming test. + * @return boolean True if matched. + * @access protected + */ + protected function matchesTestCase($test_case) { + return $this->just_this_case == strtolower($test_case); + } + + /** + * Compares criteria to actual the test name. If no + * name was specified at the beginning, then all tests + * can run. + * @param string $method The incoming test method. + * @return boolean True if matched. + * @access protected + */ + protected function shouldRunTest($test_case, $method) { + if ($this->isOn() || $this->matchesTestCase($test_case)) { + if ($this->just_this_test) { + return $this->just_this_test == strtolower($method); + } else { + return true; + } + } + return false; + } + + /** + * Switch on testing for the group or subgroup. + * @access private + */ + protected function on() { + $this->on = true; + } + + /** + * Switch off testing for the group or subgroup. + * @access private + */ + protected function off() { + $this->on = false; + } + + /** + * Is this group actually being tested? + * @return boolean True if the current test group is active. + * @access private + */ + protected function isOn() { + return $this->on; + } + + /** + * Veto everything that doesn't match the method wanted. + * @param string $test_case Name of test case. + * @param string $method Name of test method. + * @return boolean True if test should be run. + * @access public + */ + function shouldInvoke($test_case, $method) { + if ($this->shouldRunTest($test_case, $method)) { + return $this->reporter->shouldInvoke($test_case, $method); + } + return false; + } + + /** + * Paints the start of a group test. + * @param string $test_case Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_case, $size) { + if ($this->just_this_case && $this->matchesTestCase($test_case)) { + $this->on(); + } + $this->reporter->paintGroupStart($test_case, $size); + } + + /** + * Paints the end of a group test. + * @param string $test_case Name of test or other label. + * @access public + */ + function paintGroupEnd($test_case) { + $this->reporter->paintGroupEnd($test_case); + if ($this->just_this_case && $this->matchesTestCase($test_case)) { + $this->off(); + } + } +} + +/** + * Suppresses skip messages. + * @package SimpleTest + * @subpackage UnitTester + */ +class NoSkipsReporter extends SimpleReporterDecorator { + + /** + * Does nothing. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/scorer.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/scorer.php new file mode 100644 index 000000000..27776f4b6 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/scorer.php @@ -0,0 +1,875 @@ +passes = 0; + $this->fails = 0; + $this->exceptions = 0; + $this->is_dry_run = false; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + $this->is_dry_run = $is_dry; + } + + /** + * The reporter has a veto on what should be run. + * @param string $test_case_name name of test case. + * @param string $method Name of test method. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + return ! $this->is_dry_run; + } + + /** + * Can wrap the invoker in preperation for running + * a test. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + return $invoker; + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * Used for command line tools. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + if ($this->exceptions + $this->fails > 0) { + return false; + } + return true; + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + } + + /** + * Increments the pass count. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + $this->passes++; + } + + /** + * Increments the fail count. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + $this->fails++; + } + + /** + * Deals with PHP 4 throwing an error. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + $this->exceptions++; + } + + /** + * Deals with PHP 5 throwing an exception. + * @param Exception $exception The actual exception thrown. + * @access public + */ + function paintException($exception) { + $this->exceptions++; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + } + + /** + * Accessor for the number of passes so far. + * @return integer Number of passes. + * @access public + */ + function getPassCount() { + return $this->passes; + } + + /** + * Accessor for the number of fails so far. + * @return integer Number of fails. + * @access public + */ + function getFailCount() { + return $this->fails; + } + + /** + * Accessor for the number of untrapped errors + * so far. + * @return integer Number of exceptions. + * @access public + */ + function getExceptionCount() { + return $this->exceptions; + } + + /** + * Paints a simple supplementary message. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + } + + /** + * Paints a formatted ASCII message such as a + * privateiable dump. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + } + + /** + * By default just ignores user generated events. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @access public + */ + function paintSignal($type, $payload) { + } +} + +/** + * Recipient of generated test messages that can display + * page footers and headers. Also keeps track of the + * test nesting. This is the main base class on which + * to build the finished test (page based) displays. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleReporter extends SimpleScorer { + private $test_stack; + private $size; + private $progress; + + /** + * Starts the display with no results in. + * @access public + */ + function __construct() { + parent::__construct(); + $this->test_stack = array(); + $this->size = null; + $this->progress = 0; + } + + /** + * Gets the formatter for small generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return new SimpleDumper(); + } + + /** + * Paints the start of a group test. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + if (! isset($this->size)) { + $this->size = $size; + } + if (count($this->test_stack) == 0) { + $this->paintHeader($test_name); + } + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a group test. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @param integer $progress Number of test cases ending. + * @access public + */ + function paintGroupEnd($test_name) { + array_pop($this->test_stack); + if (count($this->test_stack) == 0) { + $this->paintFooter($test_name); + } + } + + /** + * Paints the start of a test case. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintCaseStart($test_name) { + if (! isset($this->size)) { + $this->size = 1; + } + if (count($this->test_stack) == 0) { + $this->paintHeader($test_name); + } + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a test case. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintCaseEnd($test_name) { + $this->progress++; + array_pop($this->test_stack); + if (count($this->test_stack) == 0) { + $this->paintFooter($test_name); + } + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($test_name) { + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a test method. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintMethodEnd($test_name) { + array_pop($this->test_stack); + } + + /** + * Paints the test document header. + * @param string $test_name First test top level + * to start. + * @access public + * @abstract + */ + function paintHeader($test_name) { + } + + /** + * Paints the test document footer. + * @param string $test_name The top level test. + * @access public + * @abstract + */ + function paintFooter($test_name) { + } + + /** + * Accessor for internal test stack. For + * subclasses that need to see the whole test + * history for display purposes. + * @return array List of methods in nesting order. + * @access public + */ + function getTestList() { + return $this->test_stack; + } + + /** + * Accessor for total test size in number + * of test cases. Null until the first + * test is started. + * @return integer Total number of cases at start. + * @access public + */ + function getTestCaseCount() { + return $this->size; + } + + /** + * Accessor for the number of test cases + * completed so far. + * @return integer Number of ended cases. + * @access public + */ + function getTestCaseProgress() { + return $this->progress; + } + + /** + * Static check for running in the comand line. + * @return boolean True if CLI. + * @access public + */ + static function inCli() { + return php_sapi_name() == 'cli'; + } +} + +/** + * For modifying the behaviour of the visual reporters. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleReporterDecorator { + protected $reporter; + + /** + * Mediates between the reporter and the test case. + * @param SimpleScorer $reporter Reporter to receive events. + */ + function __construct($reporter) { + $this->reporter = $reporter; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + $this->reporter->makeDry($is_dry); + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * Used for command line tools. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + return $this->reporter->getStatus(); + } + + /** + * The nesting of the test cases so far. Not + * all reporters have this facility. + * @return array Test list if accessible. + * @access public + */ + function getTestList() { + if (method_exists($this->reporter, 'getTestList')) { + return $this->reporter->getTestList(); + } else { + return array(); + } + } + + /** + * The reporter has a veto on what should be run. + * @param string $test_case_name Name of test case. + * @param string $method Name of test method. + * @return boolean True if test should be run. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + return $this->reporter->shouldInvoke($test_case_name, $method); + } + + /** + * Can wrap the invoker in preparation for running + * a test. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + return $this->reporter->createInvoker($invoker); + } + + /** + * Gets the formatter for privateiables and other small + * generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return $this->reporter->getDumper(); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + $this->reporter->paintGroupStart($test_name, $size); + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + $this->reporter->paintGroupEnd($test_name); + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + $this->reporter->paintCaseStart($test_name); + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + $this->reporter->paintCaseEnd($test_name); + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + $this->reporter->paintMethodStart($test_name); + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + $this->reporter->paintMethodEnd($test_name); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + $this->reporter->paintPass($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + $this->reporter->paintFail($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + $this->reporter->paintError($message); + } + + /** + * Chains to the wrapped reporter. + * @param Exception $exception Exception to show. + * @access public + */ + function paintException($exception) { + $this->reporter->paintException($exception); + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + $this->reporter->paintSkip($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + $this->reporter->paintMessage($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + $this->reporter->paintFormattedMessage($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @return boolean Should return false if this + * type of signal should fail the + * test suite. + * @access public + */ + function paintSignal($type, $payload) { + $this->reporter->paintSignal($type, $payload); + } +} + +/** + * For sending messages to multiple reporters at + * the same time. + * @package SimpleTest + * @subpackage UnitTester + */ +class MultipleReporter { + private $reporters = array(); + + /** + * Adds a reporter to the subscriber list. + * @param SimpleScorer $reporter Reporter to receive events. + * @access public + */ + function attachReporter($reporter) { + $this->reporters[] = $reporter; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->makeDry($is_dry); + } + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * If any reporter reports a failure, the whole + * suite fails. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + for ($i = 0; $i < count($this->reporters); $i++) { + if (! $this->reporters[$i]->getStatus()) { + return false; + } + } + return true; + } + + /** + * The reporter has a veto on what should be run. + * It requires all reporters to want to run the method. + * @param string $test_case_name name of test case. + * @param string $method Name of test method. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + for ($i = 0; $i < count($this->reporters); $i++) { + if (! $this->reporters[$i]->shouldInvoke($test_case_name, $method)) { + return false; + } + } + return true; + } + + /** + * Every reporter gets a chance to wrap the invoker. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + for ($i = 0; $i < count($this->reporters); $i++) { + $invoker = $this->reporters[$i]->createInvoker($invoker); + } + return $invoker; + } + + /** + * Gets the formatter for privateiables and other small + * generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return new SimpleDumper(); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintGroupStart($test_name, $size); + } + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintGroupEnd($test_name); + } + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintCaseStart($test_name); + } + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintCaseEnd($test_name); + } + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMethodStart($test_name); + } + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMethodEnd($test_name); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintPass($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintFail($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintError($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param Exception $exception Exception to display. + * @access public + */ + function paintException($exception) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintException($exception); + } + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintSkip($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMessage($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintFormattedMessage($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @return boolean Should return false if this + * type of signal should fail the + * test suite. + * @access public + */ + function paintSignal($type, $payload) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintSignal($type, $payload); + } + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/selector.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/selector.php new file mode 100644 index 000000000..ba2fed312 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/selector.php @@ -0,0 +1,141 @@ +name = $name; + } + + /** + * Accessor for name. + * @returns string $name Name to match. + */ + function getName() { + return $this->name; + } + + /** + * Compares with name attribute of widget. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + return ($widget->getName() == $this->name); + } +} + +/** + * Used to extract form elements for testing against. + * Searches by visible label or alt text. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleByLabel { + private $label; + + /** + * Stashes the name for later comparison. + * @param string $label Visible text to match. + */ + function __construct($label) { + $this->label = $label; + } + + /** + * Comparison. Compares visible text of widget or + * related label. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + if (! method_exists($widget, 'isLabel')) { + return false; + } + return $widget->isLabel($this->label); + } +} + +/** + * Used to extract form elements for testing against. + * Searches dy id attribute. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleById { + private $id; + + /** + * Stashes the name for later comparison. + * @param string $id ID atribute to match. + */ + function __construct($id) { + $this->id = $id; + } + + /** + * Comparison. Compares id attribute of widget. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + return $widget->isId($this->id); + } +} + +/** + * Used to extract form elements for testing against. + * Searches by visible label, name or alt text. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleByLabelOrName { + private $label; + + /** + * Stashes the name/label for later comparison. + * @param string $label Visible text to match. + */ + function __construct($label) { + $this->label = $label; + } + + /** + * Comparison. Compares visible text of widget or + * related label or name. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + if (method_exists($widget, 'isLabel')) { + if ($widget->isLabel($this->label)) { + return true; + } + } + return ($widget->getName() == $this->label); + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/shell_tester.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/shell_tester.php new file mode 100644 index 000000000..9a3bd389e --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/shell_tester.php @@ -0,0 +1,330 @@ +output = false; + } + + /** + * Actually runs the command. Does not trap the + * error stream output as this need PHP 4.3+. + * @param string $command The actual command line + * to run. + * @return integer Exit code. + * @access public + */ + function execute($command) { + $this->output = false; + exec($command, $this->output, $ret); + return $ret; + } + + /** + * Accessor for the last output. + * @return string Output as text. + * @access public + */ + function getOutput() { + return implode("\n", $this->output); + } + + /** + * Accessor for the last output. + * @return array Output as array of lines. + * @access public + */ + function getOutputAsList() { + return $this->output; + } +} + +/** + * Test case for testing of command line scripts and + * utilities. Usually scripts that are external to the + * PHP code, but support it in some way. + * @package SimpleTest + * @subpackage UnitTester + */ +class ShellTestCase extends SimpleTestCase { + private $current_shell; + private $last_status; + private $last_command; + + /** + * Creates an empty test case. Should be subclassed + * with test methods for a functional test case. + * @param string $label Name of test case. Will use + * the class name if none specified. + * @access public + */ + function __construct($label = false) { + parent::__construct($label); + $this->current_shell = $this->createShell(); + $this->last_status = false; + $this->last_command = ''; + } + + /** + * Executes a command and buffers the results. + * @param string $command Command to run. + * @return boolean True if zero exit code. + * @access public + */ + function execute($command) { + $shell = $this->getShell(); + $this->last_status = $shell->execute($command); + $this->last_command = $command; + return ($this->last_status === 0); + } + + /** + * Dumps the output of the last command. + * @access public + */ + function dumpOutput() { + $this->dump($this->getOutput()); + } + + /** + * Accessor for the last output. + * @return string Output as text. + * @access public + */ + function getOutput() { + $shell = $this->getShell(); + return $shell->getOutput(); + } + + /** + * Accessor for the last output. + * @return array Output as array of lines. + * @access public + */ + function getOutputAsList() { + $shell = $this->getShell(); + return $shell->getOutputAsList(); + } + + /** + * Called from within the test methods to register + * passes and failures. + * @param boolean $result Pass on true. + * @param string $message Message to display describing + * the test state. + * @return boolean True on pass + * @access public + */ + function assertTrue($result, $message = false) { + return $this->assert(new TrueExpectation(), $result, $message); + } + + /** + * Will be true on false and vice versa. False + * is the PHP definition of false, so that null, + * empty strings, zero and an empty array all count + * as false. + * @param boolean $result Pass on false. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertFalse($result, $message = '%s') { + return $this->assert(new FalseExpectation(), $result, $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value only. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertEqual($first, $second, $message = "%s") { + return $this->assert( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * a different value. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotEqual($first, $second, $message = "%s") { + return $this->assert( + new NotEqualExpectation($first), + $second, + $message); + } + + /** + * Tests the last status code from the shell. + * @param integer $status Expected status of last + * command. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertExitCode($status, $message = "%s") { + $message = sprintf($message, "Expected status code of [$status] from [" . + $this->last_command . "], but got [" . + $this->last_status . "]"); + return $this->assertTrue($status === $this->last_status, $message); + } + + /** + * Attempt to exactly match the combined STDERR and + * STDOUT output. + * @param string $expected Expected output. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertOutput($expected, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new EqualExpectation($expected), + $shell->getOutput(), + $message); + } + + /** + * Scans the output for a Perl regex. If found + * anywhere it passes, else it fails. + * @param string $pattern Regex to search for. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertOutputPattern($pattern, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new PatternExpectation($pattern), + $shell->getOutput(), + $message); + } + + /** + * If a Perl regex is found anywhere in the current + * output then a failure is generated, else a pass. + * @param string $pattern Regex to search for. + * @param $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoOutputPattern($pattern, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new NoPatternExpectation($pattern), + $shell->getOutput(), + $message); + } + + /** + * File existence check. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFileExists($path, $message = "%s") { + $message = sprintf($message, "File [$path] should exist"); + return $this->assertTrue(file_exists($path), $message); + } + + /** + * File non-existence check. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFileNotExists($path, $message = "%s") { + $message = sprintf($message, "File [$path] should not exist"); + return $this->assertFalse(file_exists($path), $message); + } + + /** + * Scans a file for a Perl regex. If found + * anywhere it passes, else it fails. + * @param string $pattern Regex to search for. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFilePattern($pattern, $path, $message = "%s") { + return $this->assert( + new PatternExpectation($pattern), + implode('', file($path)), + $message); + } + + /** + * If a Perl regex is found anywhere in the named + * file then a failure is generated, else a pass. + * @param string $pattern Regex to search for. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoFilePattern($pattern, $path, $message = "%s") { + return $this->assert( + new NoPatternExpectation($pattern), + implode('', file($path)), + $message); + } + + /** + * Accessor for current shell. Used for testing the + * the tester itself. + * @return Shell Current shell. + * @access protected + */ + protected function getShell() { + return $this->current_shell; + } + + /** + * Factory for the shell to run the command on. + * @return Shell New shell object. + * @access protected + */ + protected function createShell() { + return new SimpleShell(); + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/simpletest.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/simpletest.php new file mode 100644 index 000000000..425c869a8 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/simpletest.php @@ -0,0 +1,391 @@ +getParent()) { + SimpleTest::ignore($parent); + } + } + } + } + + /** + * Puts the object to the global pool of 'preferred' objects + * which can be retrieved with SimpleTest :: preferred() method. + * Instances of the same class are overwritten. + * @param object $object Preferred object + * @see preferred() + */ + static function prefer($object) { + $registry = &SimpleTest::getRegistry(); + $registry['Preferred'][] = $object; + } + + /** + * Retrieves 'preferred' objects from global pool. Class filter + * can be applied in order to retrieve the object of the specific + * class + * @param array|string $classes Allowed classes or interfaces. + * @return array|object|null + * @see prefer() + */ + static function preferred($classes) { + if (! is_array($classes)) { + $classes = array($classes); + } + $registry = &SimpleTest::getRegistry(); + for ($i = count($registry['Preferred']) - 1; $i >= 0; $i--) { + foreach ($classes as $class) { + if (SimpleTestCompatibility::isA($registry['Preferred'][$i], $class)) { + return $registry['Preferred'][$i]; + } + } + } + return null; + } + + /** + * Test to see if a test case is in the ignore + * list. Quite obviously the ignore list should + * be a separate object and will be one day. + * This method is internal to SimpleTest. Don't + * use it. + * @param string $class Class name to test. + * @return boolean True if should not be run. + */ + static function isIgnored($class) { + $registry = &SimpleTest::getRegistry(); + return isset($registry['IgnoreList'][strtolower($class)]); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set host + * to false to disable. This will take effect + * if there are no other proxy settings. + * @param string $proxy Proxy host as URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + */ + static function useProxy($proxy, $username = false, $password = false) { + $registry = &SimpleTest::getRegistry(); + $registry['DefaultProxy'] = $proxy; + $registry['DefaultProxyUsername'] = $username; + $registry['DefaultProxyPassword'] = $password; + } + + /** + * Accessor for default proxy host. + * @return string Proxy URL. + */ + static function getDefaultProxy() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxy']; + } + + /** + * Accessor for default proxy username. + * @return string Proxy username for authentication. + */ + static function getDefaultProxyUsername() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxyUsername']; + } + + /** + * Accessor for default proxy password. + * @return string Proxy password for authentication. + */ + static function getDefaultProxyPassword() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxyPassword']; + } + + /** + * Accessor for default HTML parsers. + * @return array List of parsers to try in + * order until one responds true + * to can(). + */ + static function getParsers() { + $registry = &SimpleTest::getRegistry(); + return $registry['Parsers']; + } + + /** + * Set the list of HTML parsers to attempt to use by default. + * @param array $parsers List of parsers to try in + * order until one responds true + * to can(). + */ + static function setParsers($parsers) { + $registry = &SimpleTest::getRegistry(); + $registry['Parsers'] = $parsers; + } + + /** + * Accessor for global registry of options. + * @return hash All stored values. + */ + protected static function &getRegistry() { + static $registry = false; + if (! $registry) { + $registry = SimpleTest::getDefaults(); + } + return $registry; + } + + /** + * Accessor for the context of the current + * test run. + * @return SimpleTestContext Current test run. + */ + static function getContext() { + static $context = false; + if (! $context) { + $context = new SimpleTestContext(); + } + return $context; + } + + /** + * Constant default values. + * @return hash All registry defaults. + */ + protected static function getDefaults() { + return array( + 'Parsers' => false, + 'MockBaseClass' => 'SimpleMock', + 'IgnoreList' => array(), + 'DefaultProxy' => false, + 'DefaultProxyUsername' => false, + 'DefaultProxyPassword' => false, + 'Preferred' => array(new HtmlReporter(), new TextReporter(), new XmlReporter())); + } + + /** + * @deprecated + */ + static function setMockBaseClass($mock_base) { + $registry = &SimpleTest::getRegistry(); + $registry['MockBaseClass'] = $mock_base; + } + + /** + * @deprecated + */ + static function getMockBaseClass() { + $registry = &SimpleTest::getRegistry(); + return $registry['MockBaseClass']; + } +} + +/** + * Container for all components for a specific + * test run. Makes things like error queues + * available to PHP event handlers, and also + * gets around some nasty reference issues in + * the mocks. + * @package SimpleTest + */ +class SimpleTestContext { + private $test; + private $reporter; + private $resources; + + /** + * Clears down the current context. + * @access public + */ + function clear() { + $this->resources = array(); + } + + /** + * Sets the current test case instance. This + * global instance can be used by the mock objects + * to send message to the test cases. + * @param SimpleTestCase $test Test case to register. + */ + function setTest($test) { + $this->clear(); + $this->test = $test; + } + + /** + * Accessor for currently running test case. + * @return SimpleTestCase Current test. + */ + function getTest() { + return $this->test; + } + + /** + * Sets the current reporter. This + * global instance can be used by the mock objects + * to send messages. + * @param SimpleReporter $reporter Reporter to register. + */ + function setReporter($reporter) { + $this->clear(); + $this->reporter = $reporter; + } + + /** + * Accessor for current reporter. + * @return SimpleReporter Current reporter. + */ + function getReporter() { + return $this->reporter; + } + + /** + * Accessor for the Singleton resource. + * @return object Global resource. + */ + function get($resource) { + if (! isset($this->resources[$resource])) { + $this->resources[$resource] = new $resource(); + } + return $this->resources[$resource]; + } +} + +/** + * Interrogates the stack trace to recover the + * failure point. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleStackTrace { + private $prefixes; + + /** + * Stashes the list of target prefixes. + * @param array $prefixes List of method prefixes + * to search for. + */ + function __construct($prefixes) { + $this->prefixes = $prefixes; + } + + /** + * Extracts the last method name that was not within + * Simpletest itself. Captures a stack trace if none given. + * @param array $stack List of stack frames. + * @return string Snippet of test report with line + * number and file. + */ + function traceMethod($stack = false) { + $stack = $stack ? $stack : $this->captureTrace(); + foreach ($stack as $frame) { + if ($this->frameLiesWithinSimpleTestFolder($frame)) { + continue; + } + if ($this->frameMatchesPrefix($frame)) { + return ' at [' . $frame['file'] . ' line ' . $frame['line'] . ']'; + } + } + return ''; + } + + /** + * Test to see if error is generated by SimpleTest itself. + * @param array $frame PHP stack frame. + * @return boolean True if a SimpleTest file. + */ + protected function frameLiesWithinSimpleTestFolder($frame) { + if (isset($frame['file'])) { + $path = substr(SIMPLE_TEST, 0, -1); + if (strpos($frame['file'], $path) === 0) { + if (dirname($frame['file']) == $path) { + return true; + } + } + } + return false; + } + + /** + * Tries to determine if the method call is an assert, etc. + * @param array $frame PHP stack frame. + * @return boolean True if matches a target. + */ + protected function frameMatchesPrefix($frame) { + foreach ($this->prefixes as $prefix) { + if (strncmp($frame['function'], $prefix, strlen($prefix)) == 0) { + return true; + } + } + return false; + } + + /** + * Grabs a current stack trace. + * @return array Fulle trace. + */ + protected function captureTrace() { + if (function_exists('debug_backtrace')) { + return array_reverse(debug_backtrace()); + } + return array(); + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/socket.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/socket.php new file mode 100644 index 000000000..06e8ca62d --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/socket.php @@ -0,0 +1,312 @@ +clearError(); + } + + /** + * Test for an outstanding error. + * @return boolean True if there is an error. + * @access public + */ + function isError() { + return ($this->error != ''); + } + + /** + * Accessor for an outstanding error. + * @return string Empty string if no error otherwise + * the error message. + * @access public + */ + function getError() { + return $this->error; + } + + /** + * Sets the internal error. + * @param string Error message to stash. + * @access protected + */ + function setError($error) { + $this->error = $error; + } + + /** + * Resets the error state to no error. + * @access protected + */ + function clearError() { + $this->setError(''); + } +} + +/** + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFileSocket extends SimpleStickyError { + private $handle; + private $is_open = false; + private $sent = ''; + private $block_size; + + /** + * Opens a socket for reading and writing. + * @param SimpleUrl $file Target URI to fetch. + * @param integer $block_size Size of chunk to read. + * @access public + */ + function __construct($file, $block_size = 1024) { + parent::__construct(); + if (! ($this->handle = $this->openFile($file, $error))) { + $file_string = $file->asString(); + $this->setError("Cannot open [$file_string] with [$error]"); + return; + } + $this->is_open = true; + $this->block_size = $block_size; + } + + /** + * Writes some data to the socket and saves alocal copy. + * @param string $message String to send to socket. + * @return boolean True if successful. + * @access public + */ + function write($message) { + return true; + } + + /** + * Reads data from the socket. The error suppresion + * is a workaround for PHP4 always throwing a warning + * with a secure socket. + * @return integer/boolean Incoming bytes. False + * on error. + * @access public + */ + function read() { + $raw = @fread($this->handle, $this->block_size); + if ($raw === false) { + $this->setError('Cannot read from socket'); + $this->close(); + } + return $raw; + } + + /** + * Accessor for socket open state. + * @return boolean True if open. + * @access public + */ + function isOpen() { + return $this->is_open; + } + + /** + * Closes the socket preventing further reads. + * Cannot be reopened once closed. + * @return boolean True if successful. + * @access public + */ + function close() { + if (!$this->is_open) return false; + $this->is_open = false; + return fclose($this->handle); + } + + /** + * Accessor for content so far. + * @return string Bytes sent only. + * @access public + */ + function getSent() { + return $this->sent; + } + + /** + * Actually opens the low level socket. + * @param SimpleUrl $file SimpleUrl file target. + * @param string $error Recipient of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + protected function openFile($file, &$error) { + return @fopen($file->asString(), 'r'); + } +} + +/** + * Wrapper for TCP/IP socket. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSocket extends SimpleStickyError { + private $handle; + private $is_open = false; + private $sent = ''; + private $lock_size; + + /** + * Opens a socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @param integer $block_size Size of chunk to read. + * @access public + */ + function __construct($host, $port, $timeout, $block_size = 255) { + parent::__construct(); + if (! ($this->handle = $this->openSocket($host, $port, $error_number, $error, $timeout))) { + $this->setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds"); + return; + } + $this->is_open = true; + $this->block_size = $block_size; + SimpleTestCompatibility::setTimeout($this->handle, $timeout); + } + + /** + * Writes some data to the socket and saves alocal copy. + * @param string $message String to send to socket. + * @return boolean True if successful. + * @access public + */ + function write($message) { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $count = fwrite($this->handle, $message); + if (! $count) { + if ($count === false) { + $this->setError('Cannot write to socket'); + $this->close(); + } + return false; + } + fflush($this->handle); + $this->sent .= $message; + return true; + } + + /** + * Reads data from the socket. The error suppresion + * is a workaround for PHP4 always throwing a warning + * with a secure socket. + * @return integer/boolean Incoming bytes. False + * on error. + * @access public + */ + function read() { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $raw = @fread($this->handle, $this->block_size); + if ($raw === false) { + $this->setError('Cannot read from socket'); + $this->close(); + } + return $raw; + } + + /** + * Accessor for socket open state. + * @return boolean True if open. + * @access public + */ + function isOpen() { + return $this->is_open; + } + + /** + * Closes the socket preventing further reads. + * Cannot be reopened once closed. + * @return boolean True if successful. + * @access public + */ + function close() { + $this->is_open = false; + return fclose($this->handle); + } + + /** + * Accessor for content so far. + * @return string Bytes sent only. + * @access public + */ + function getSent() { + return $this->sent; + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipoent of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + protected function openSocket($host, $port, &$error_number, &$error, $timeout) { + return @fsockopen($host, $port, $error_number, $error, $timeout); + } +} + +/** + * Wrapper for TCP/IP socket over TLS. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSecureSocket extends SimpleSocket { + + /** + * Opens a secure socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @access public + */ + function __construct($host, $port, $timeout) { + parent::__construct($host, $port, $timeout); + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipient of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + function openSocket($host, $port, &$error_number, &$error, $timeout) { + return parent::openSocket("tls://$host", $port, $error_number, $error, $timeout); + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/tag.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/tag.php new file mode 100644 index 000000000..afe649ec5 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/tag.php @@ -0,0 +1,1527 @@ + 'SimpleAnchorTag', + 'title' => 'SimpleTitleTag', + 'base' => 'SimpleBaseTag', + 'button' => 'SimpleButtonTag', + 'textarea' => 'SimpleTextAreaTag', + 'option' => 'SimpleOptionTag', + 'label' => 'SimpleLabelTag', + 'form' => 'SimpleFormTag', + 'frame' => 'SimpleFrameTag'); + $attributes = $this->keysToLowerCase($attributes); + if (array_key_exists($name, $map)) { + $tag_class = $map[$name]; + return new $tag_class($attributes); + } elseif ($name == 'select') { + return $this->createSelectionTag($attributes); + } elseif ($name == 'input') { + return $this->createInputTag($attributes); + } + return new SimpleTag($name, $attributes); + } + + /** + * Factory for selection fields. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + protected function createSelectionTag($attributes) { + if (isset($attributes['multiple'])) { + return new MultipleSelectionTag($attributes); + } + return new SimpleSelectionTag($attributes); + } + + /** + * Factory for input tags. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + protected function createInputTag($attributes) { + if (! isset($attributes['type'])) { + return new SimpleTextTag($attributes); + } + $type = strtolower(trim($attributes['type'])); + $map = array( + 'submit' => 'SimpleSubmitTag', + 'image' => 'SimpleImageSubmitTag', + 'checkbox' => 'SimpleCheckboxTag', + 'radio' => 'SimpleRadioButtonTag', + 'text' => 'SimpleTextTag', + 'hidden' => 'SimpleTextTag', + 'password' => 'SimpleTextTag', + 'file' => 'SimpleUploadTag'); + if (array_key_exists($type, $map)) { + $tag_class = $map[$type]; + return new $tag_class($attributes); + } + return false; + } + + /** + * Make the keys lower case for case insensitive look-ups. + * @param hash $map Hash to convert. + * @return hash Unchanged values, but keys lower case. + * @access private + */ + protected function keysToLowerCase($map) { + $lower = array(); + foreach ($map as $key => $value) { + $lower[strtolower($key)] = $value; + } + return $lower; + } +} + +/** + * HTML or XML tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTag { + private $name; + private $attributes; + private $content; + + /** + * Starts with a named tag with attributes only. + * @param string $name Tag name. + * @param hash $attributes Attribute names and + * string values. Note that + * the keys must have been + * converted to lower case. + */ + function __construct($name, $attributes) { + $this->name = strtolower(trim($name)); + $this->attributes = $attributes; + $this->content = ''; + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * The current tag should not swallow all content for + * itself as it's searchable page content. Private + * content tags are usually widgets that contain default + * values. + * @return boolean False as content is available + * to other tags by default. + * @access public + */ + function isPrivateContent() { + return false; + } + + /** + * Appends string content to the current content. + * @param string $content Additional text. + * @access public + */ + function addContent($content) { + $this->content .= (string)$content; + return $this; + } + + /** + * Adds an enclosed tag to the content. + * @param SimpleTag $tag New tag. + * @access public + */ + function addTag($tag) { + } + + /** + * Adds multiple enclosed tags to the content. + * @param array List of SimpleTag objects to be added. + */ + function addTags($tags) { + foreach ($tags as $tag) { + $this->addTag($tag); + } + } + + /** + * Accessor for tag name. + * @return string Name of tag. + * @access public + */ + function getTagName() { + return $this->name; + } + + /** + * List of legal child elements. + * @return array List of element names. + * @access public + */ + function getChildElements() { + return array(); + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return string Attribute value. + * @access public + */ + function getAttribute($label) { + $label = strtolower($label); + if (! isset($this->attributes[$label])) { + return false; + } + return (string)$this->attributes[$label]; + } + + /** + * Sets an attribute. + * @param string $label Attribute name. + * @return string $value New attribute value. + * @access protected + */ + protected function setAttribute($label, $value) { + $this->attributes[strtolower($label)] = $value; + } + + /** + * Accessor for the whole content so far. + * @return string Content as big raw string. + * @access public + */ + function getContent() { + return $this->content; + } + + /** + * Accessor for content reduced to visible text. Acts + * like a text mode browser, normalising space and + * reducing images to their alt text. + * @return string Content as plain text. + * @access public + */ + function getText() { + return SimplePage::normalise($this->content); + } + + /** + * Test to see if id attribute matches. + * @param string $id ID to test against. + * @return boolean True on match. + * @access public + */ + function isId($id) { + return ($this->getAttribute('id') == $id); + } +} + +/** + * Base url. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleBaseTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('base', $attributes); + } + + /** + * Base tag is not a block tag. + * @return boolean false + * @access public + */ + function expectEndTag() { + return false; + } +} + +/** + * Page title. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTitleTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('title', $attributes); + } +} + +/** + * Link. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAnchorTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('a', $attributes); + } + + /** + * Accessor for URL as string. + * @return string Coerced as string. + * @access public + */ + function getHref() { + $url = $this->getAttribute('href'); + if (is_bool($url)) { + $url = ''; + } + return $url; + } +} + +/** + * Form element. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleWidget extends SimpleTag { + private $value; + private $label; + private $is_set; + + /** + * Starts with a named tag with attributes only. + * @param string $name Tag name. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($name, $attributes) { + parent::__construct($name, $attributes); + $this->value = false; + $this->label = false; + $this->is_set = false; + } + + /** + * Accessor for name submitted as the key in + * GET/POST privateiables hash. + * @return string Parsed value. + * @access public + */ + function getName() { + return $this->getAttribute('name'); + } + + /** + * Accessor for default value parsed with the tag. + * @return string Parsed value. + * @access public + */ + function getDefault() { + return $this->getAttribute('value'); + } + + /** + * Accessor for currently set value or default if + * none. + * @return string Value set by form or default + * if none. + * @access public + */ + function getValue() { + if (! $this->is_set) { + return $this->getDefault(); + } + return $this->value; + } + + /** + * Sets the current form element value. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + $this->value = $value; + $this->is_set = true; + return true; + } + + /** + * Resets the form element value back to the + * default. + * @access public + */ + function resetValue() { + $this->is_set = false; + } + + /** + * Allows setting of a label externally, say by a + * label tag. + * @param string $label Label to attach. + * @access public + */ + function setLabel($label) { + $this->label = trim($label); + return $this; + } + + /** + * Reads external or internal label. + * @param string $label Label to test. + * @return boolean True is match. + * @access public + */ + function isLabel($label) { + return $this->label == trim($label); + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + if ($this->getName()) { + $encoding->add($this->getName(), $this->getValue()); + } + } +} + +/** + * Text, password and hidden field. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTextTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', ''); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Sets the current form element value. Cannot + * change the value of a hidden field. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($this->getAttribute('type') == 'hidden') { + return false; + } + return parent::setValue($value); + } +} + +/** + * Submit button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'Submit'); + } + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getValue(); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } +} + +/** + * Image button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleImageSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + if ($this->getAttribute('title')) { + return $this->getAttribute('title'); + } + return $this->getAttribute('alt'); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @param integer $x X coordinate of click. + * @param integer $y Y coordinate of click. + * @access public + */ + function write($encoding, $x = 1, $y = 1) { + if ($this->getName()) { + $encoding->add($this->getName() . '.x', $x); + $encoding->add($this->getName() . '.y', $y); + } else { + $encoding->add('x', $x); + $encoding->add('y', $y); + } + } +} + +/** + * Submit button as button tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleButtonTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * Defaults are very browser dependent. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('button', $attributes); + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getContent(); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } +} + +/** + * Content tag for text area. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTextAreaTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('textarea', $attributes); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + return $this->wrap(html_entity_decode($this->getContent(), ENT_QUOTES)); + } + + /** + * Applies word wrapping if needed. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return parent::setValue($this->wrap($value)); + } + + /** + * Test to see if text should be wrapped. + * @return boolean True if wrapping on. + * @access private + */ + function wrapIsEnabled() { + if ($this->getAttribute('cols')) { + $wrap = $this->getAttribute('wrap'); + if (($wrap == 'physical') || ($wrap == 'hard')) { + return true; + } + } + return false; + } + + /** + * Performs the formatting that is peculiar to + * this tag. There is strange behaviour in this + * one, including stripping a leading new line. + * Go figure. I am using Firefox as a guide. + * @param string $text Text to wrap. + * @return string Text wrapped with carriage + * returns and line feeds + * @access private + */ + protected function wrap($text) { + $text = str_replace("\r\r\n", "\r\n", str_replace("\n", "\r\n", $text)); + $text = str_replace("\r\n\n", "\r\n", str_replace("\r", "\r\n", $text)); + if (strncmp($text, "\r\n", strlen("\r\n")) == 0) { + $text = substr($text, strlen("\r\n")); + } + if ($this->wrapIsEnabled()) { + return wordwrap( + $text, + (integer)$this->getAttribute('cols'), + "\r\n"); + } + return $text; + } + + /** + * The content of textarea is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } +} + +/** + * File upload widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleUploadTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + if (! file_exists($this->getValue())) { + return; + } + $encoding->attach( + $this->getName(), + implode('', file($this->getValue())), + basename($this->getValue())); + } +} + +/** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSelectionTag extends SimpleWidget { + private $options; + private $choice; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('select', $attributes); + $this->options = array(); + $this->choice = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag($tag) { + if ($tag->getTagName() == 'option') { + $this->options[] = $tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + return $this; + } + + /** + * Scans options for defaults. If none, then + * the first option is selected. + * @return string Selected field. + * @access public + */ + function getDefault() { + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->getAttribute('selected') !== false) { + return $this->options[$i]->getDefault(); + } + } + if ($count > 0) { + return $this->options[0]->getDefault(); + } + return ''; + } + + /** + * Can only set allowed values. + * @param string $value New choice. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->isValue($value)) { + $this->choice = $i; + return true; + } + } + return false; + } + + /** + * Accessor for current selection value. + * @return string Value attribute or + * content of opton. + * @access public + */ + function getValue() { + if ($this->choice === false) { + return $this->getDefault(); + } + return $this->options[$this->choice]->getValue(); + } +} + +/** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ +class MultipleSelectionTag extends SimpleWidget { + private $options; + private $values; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('select', $attributes); + $this->options = array(); + $this->values = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag($tag) { + if ($tag->getTagName() == 'option') { + $this->options[] = &$tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + return $this; + } + + /** + * Scans options for defaults to populate the + * value array(). + * @return array Selected fields. + * @access public + */ + function getDefault() { + $default = array(); + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->getAttribute('selected') !== false) { + $default[] = $this->options[$i]->getDefault(); + } + } + return $default; + } + + /** + * Can only set allowed values. Any illegal value + * will result in a failure, but all correct values + * will be set. + * @param array $desired New choices. + * @return boolean True if all allowed. + * @access public + */ + function setValue($desired) { + $achieved = array(); + foreach ($desired as $value) { + $success = false; + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->isValue($value)) { + $achieved[] = $this->options[$i]->getValue(); + $success = true; + break; + } + } + if (! $success) { + return false; + } + } + $this->values = $achieved; + return true; + } + + /** + * Accessor for current selection value. + * @return array List of currently set options. + * @access public + */ + function getValue() { + if ($this->values === false) { + return $this->getDefault(); + } + return $this->values; + } +} + +/** + * Option for selection field. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleOptionTag extends SimpleWidget { + + /** + * Stashes the attributes. + */ + function __construct($attributes) { + parent::__construct('option', $attributes); + } + + /** + * Does nothing. + * @param string $value Ignored. + * @return boolean Not allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Test to see if a value matches the option. + * @param string $compare Value to compare with. + * @return boolean True if possible match. + * @access public + */ + function isValue($compare) { + $compare = trim($compare); + if (trim($this->getValue()) == $compare) { + return true; + } + return trim(strip_tags($this->getContent())) == $compare; + } + + /** + * Accessor for starting value. Will be set to + * the option label if no value exists. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('value') === false) { + return strip_tags($this->getContent()); + } + return $this->getAttribute('value'); + } + + /** + * The content of options is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } +} + +/** + * Radio button. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleRadioButtonTag extends SimpleWidget { + + /** + * Stashes the attributes. + * @param array $attributes Hash of attributes. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value sn the one in the + * "value" attribute. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked') !== false) { + return $this->getAttribute('value'); + } + return false; + } +} + +/** + * Checkbox widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCheckboxTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value in the one in the + * "value" attribute. The default for this + * attribute is "on". If this widget is set to + * true, then the usual value will be taken. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value === true) { + return parent::setValue($this->getAttribute('value')); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. The default + * value is "on". + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked') !== false) { + return $this->getAttribute('value'); + } + return false; + } +} + +/** + * A group of multiple widgets with some shared behaviour. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTagGroup { + private $widgets = array(); + + /** + * Adds a tag to the group. + * @param SimpleWidget $widget + * @access public + */ + function addWidget($widget) { + $this->widgets[] = $widget; + } + + /** + * Accessor to widget set. + * @return array All widgets. + * @access protected + */ + protected function &getWidgets() { + return $this->widgets; + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return boolean Always false. + * @access public + */ + function getAttribute($label) { + return false; + } + + /** + * Fetches the name for the widget from the first + * member. + * @return string Name of widget. + * @access public + */ + function getName() { + if (count($this->widgets) > 0) { + return $this->widgets[0]->getName(); + } + } + + /** + * Scans the widgets for one with the appropriate + * ID field. + * @param string $id ID value to try. + * @return boolean True if matched. + * @access public + */ + function isId($id) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($this->widgets[$i]->isId($id)) { + return true; + } + } + return false; + } + + /** + * Scans the widgets for one with the appropriate + * attached label. + * @param string $label Attached label to try. + * @return boolean True if matched. + * @access public + */ + function isLabel($label) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($this->widgets[$i]->isLabel($label)) { + return true; + } + } + return false; + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + $encoding->add($this->getName(), $this->getValue()); + } +} + +/** + * A group of tags with the same name within a form. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCheckboxGroup extends SimpleTagGroup { + + /** + * Accessor for current selected widget or false + * if none. + * @return string/array Widget values or false if none. + * @access public + */ + function getValue() { + $values = array(); + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + $values[] = $widgets[$i]->getValue(); + } + } + return $this->coerceValues($values); + } + + /** + * Accessor for starting value that is active. + * @return string/array Widget values or false if none. + * @access public + */ + function getDefault() { + $values = array(); + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getDefault() !== false) { + $values[] = $widgets[$i]->getDefault(); + } + } + return $this->coerceValues($values); + } + + /** + * Accessor for current set values. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean True if all values can be set. + * @access public + */ + function setValue($values) { + $values = $this->makeArray($values); + if (! $this->valuesArePossible($values)) { + return false; + } + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); + if (in_array($widgets[$i]->getAttribute('value'), $values)) { + $widgets[$i]->setValue($possible); + } else { + $widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a possible value set is legal. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean False if trying to set a + * missing value. + * @access private + */ + protected function valuesArePossible($values) { + $matches = array(); + $widgets = &$this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); + if (in_array($possible, $values)) { + $matches[] = $possible; + } + } + return ($values == $matches); + } + + /** + * Converts the output to an appropriate format. This means + * that no values is false, a single value is just that + * value and only two or more are contained in an array. + * @param array $values List of values of widgets. + * @return string/array/boolean Expected format for a tag. + * @access private + */ + protected function coerceValues($values) { + if (count($values) == 0) { + return false; + } elseif (count($values) == 1) { + return $values[0]; + } else { + return $values; + } + } + + /** + * Converts false or string into array. The opposite of + * the coercian method. + * @param string/array/boolean $value A single item is converted + * to a one item list. False + * gives an empty list. + * @return array List of values, possibly empty. + * @access private + */ + protected function makeArray($value) { + if ($value === false) { + return array(); + } + if (is_string($value)) { + return array($value); + } + return $value; + } +} + +/** + * A group of tags with the same name within a form. + * Used for radio buttons. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleRadioGroup extends SimpleTagGroup { + + /** + * Each tag is tried in turn until one is + * successfully set. The others will be + * unchecked if successful. + * @param string $value New value. + * @return boolean True if any allowed. + * @access public + */ + function setValue($value) { + if (! $this->valueIsPossible($value)) { + return false; + } + $index = false; + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if (! $widgets[$i]->setValue($value)) { + $widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a value is allowed. + * @param string Attempted value. + * @return boolean True if a valid value. + * @access private + */ + protected function valueIsPossible($value) { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getAttribute('value') == $value) { + return true; + } + } + return false; + } + + /** + * Accessor for current selected widget or false + * if none. + * @return string/boolean Value attribute or + * content of opton. + * @access public + */ + function getValue() { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + return $widgets[$i]->getValue(); + } + } + return false; + } + + /** + * Accessor for starting value that is active. + * @return string/boolean Value of first checked + * widget or false if none. + * @access public + */ + function getDefault() { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getDefault() !== false) { + return $widgets[$i]->getDefault(); + } + } + return false; + } +} + +/** + * Tag to keep track of labels. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleLabelTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('label', $attributes); + } + + /** + * Access for the ID to attach the label to. + * @return string For attribute. + * @access public + */ + function getFor() { + return $this->getAttribute('for'); + } +} + +/** + * Tag to aid parsing the form. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFormTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('form', $attributes); + } +} + +/** + * Tag to aid parsing the frames in a page. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFrameTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('frame', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } +} +?> \ No newline at end of file diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/test_case.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/test_case.php new file mode 100644 index 000000000..ba023c3b2 --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/test_case.php @@ -0,0 +1,658 @@ +label = $label; + } + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->label ? $this->label : get_class($this); + } + + /** + * This is a placeholder for skipping tests. In this + * method you place skipIf() and skipUnless() calls to + * set the skipping state. + * @access public + */ + function skip() { + } + + /** + * Will issue a message to the reporter and tell the test + * case to skip if the incoming flag is true. + * @param string $should_skip Condition causing the tests to be skipped. + * @param string $message Text of skip condition. + * @access public + */ + function skipIf($should_skip, $message = '%s') { + if ($should_skip && ! $this->should_skip) { + $this->should_skip = true; + $message = sprintf($message, 'Skipping [' . get_class($this) . ']'); + $this->reporter->paintSkip($message . $this->getAssertionLine()); + } + } + + /** + * Accessor for the private variable $_shoud_skip + * @access public + */ + function shouldSkip() { + return $this->should_skip; + } + + /** + * Will issue a message to the reporter and tell the test + * case to skip if the incoming flag is false. + * @param string $shouldnt_skip Condition causing the tests to be run. + * @param string $message Text of skip condition. + * @access public + */ + function skipUnless($shouldnt_skip, $message = false) { + $this->skipIf(! $shouldnt_skip, $message); + } + + /** + * Used to invoke the single tests. + * @return SimpleInvoker Individual test runner. + * @access public + */ + function createInvoker() { + return new SimpleErrorTrappingInvoker( + new SimpleExceptionTrappingInvoker(new SimpleInvoker($this))); + } + + /** + * Uses reflection to run every method within itself + * starting with the string "test" unless a method + * is specified. + * @param SimpleReporter $reporter Current test reporter. + * @return boolean True if all tests passed. + * @access public + */ + function run($reporter) { + $context = SimpleTest::getContext(); + $context->setTest($this); + $context->setReporter($reporter); + $this->reporter = $reporter; + $started = false; + foreach ($this->getTests() as $method) { + if ($reporter->shouldInvoke($this->getLabel(), $method)) { + $this->skip(); + if ($this->should_skip) { + break; + } + if (! $started) { + $reporter->paintCaseStart($this->getLabel()); + $started = true; + } + $invoker = $this->reporter->createInvoker($this->createInvoker()); + $invoker->before($method); + $invoker->invoke($method); + $invoker->after($method); + } + } + if ($started) { + $reporter->paintCaseEnd($this->getLabel()); + } + unset($this->reporter); + $context->setTest(null); + return $reporter->getStatus(); + } + + /** + * Gets a list of test names. Normally that will + * be all internal methods that start with the + * name "test". This method should be overridden + * if you want a different rule. + * @return array List of test names. + * @access public + */ + function getTests() { + $methods = array(); + foreach (get_class_methods(get_class($this)) as $method) { + if ($this->isTest($method)) { + $methods[] = $method; + } + } + return $methods; + } + + /** + * Tests to see if the method is a test that should + * be run. Currently any method that starts with 'test' + * is a candidate unless it is the constructor. + * @param string $method Method name to try. + * @return boolean True if test method. + * @access protected + */ + protected function isTest($method) { + if (strtolower(substr($method, 0, 4)) == 'test') { + return ! SimpleTestCompatibility::isA($this, strtolower($method)); + } + return false; + } + + /** + * Announces the start of the test. + * @param string $method Test method just started. + * @access public + */ + function before($method) { + $this->reporter->paintMethodStart($method); + $this->observers = array(); + } + + /** + * Sets up unit test wide variables at the start + * of each test method. To be overridden in + * actual user test cases. + * @access public + */ + function setUp() { + } + + /** + * Clears the data set in the setUp() method call. + * To be overridden by the user in actual user test cases. + * @access public + */ + function tearDown() { + } + + /** + * Announces the end of the test. Includes private clean up. + * @param string $method Test method just finished. + * @access public + */ + function after($method) { + for ($i = 0; $i < count($this->observers); $i++) { + $this->observers[$i]->atTestEnd($method, $this); + } + $this->reporter->paintMethodEnd($method); + } + + /** + * Sets up an observer for the test end. + * @param object $observer Must have atTestEnd() + * method. + * @access public + */ + function tell($observer) { + $this->observers[] = &$observer; + } + + /** + * @deprecated + */ + function pass($message = "Pass") { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintPass( + $message . $this->getAssertionLine()); + return true; + } + + /** + * Sends a fail event with a message. + * @param string $message Message to send. + * @access public + */ + function fail($message = "Fail") { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintFail( + $message . $this->getAssertionLine()); + return false; + } + + /** + * Formats a PHP error and dispatches it to the + * reporter. + * @param integer $severity PHP error code. + * @param string $message Text of error. + * @param string $file File error occoured in. + * @param integer $line Line number of error. + * @access public + */ + function error($severity, $message, $file, $line) { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintError( + "Unexpected PHP error [$message] severity [$severity] in [$file line $line]"); + } + + /** + * Formats an exception and dispatches it to the + * reporter. + * @param Exception $exception Object thrown. + * @access public + */ + function exception($exception) { + $this->reporter->paintException($exception); + } + + /** + * For user defined expansion of the available messages. + * @param string $type Tag for sorting the signals. + * @param mixed $payload Extra user specific information. + */ + function signal($type, $payload) { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintSignal($type, $payload); + } + + /** + * Runs an expectation directly, for extending the + * tests with new expectation classes. + * @param SimpleExpectation $expectation Expectation subclass. + * @param mixed $compare Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assert($expectation, $compare, $message = '%s') { + if ($expectation->test($compare)) { + return $this->pass(sprintf( + $message, + $expectation->overlayMessage($compare, $this->reporter->getDumper()))); + } else { + return $this->fail(sprintf( + $message, + $expectation->overlayMessage($compare, $this->reporter->getDumper()))); + } + } + + /** + * Uses a stack trace to find the line of an assertion. + * @return string Line number of first assert* + * method embedded in format string. + * @access public + */ + function getAssertionLine() { + $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip')); + return $trace->traceMethod(); + } + + /** + * Sends a formatted dump of a variable to the + * test suite for those emergency debugging + * situations. + * @param mixed $variable Variable to display. + * @param string $message Message to display. + * @return mixed The original variable. + * @access public + */ + function dump($variable, $message = false) { + $dumper = $this->reporter->getDumper(); + $formatted = $dumper->dump($variable); + if ($message) { + $formatted = $message . "\n" . $formatted; + } + $this->reporter->paintFormattedMessage($formatted); + return $variable; + } + + /** + * Accessor for the number of subtests including myelf. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + return 1; + } +} + +/** + * Helps to extract test cases automatically from a file. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleFileLoader { + + /** + * Builds a test suite from a library of test cases. + * The new suite is composed into this one. + * @param string $test_file File name of library with + * test case classes. + * @return TestSuite The new test suite. + * @access public + */ + function load($test_file) { + $existing_classes = get_declared_classes(); + $existing_globals = get_defined_vars(); + include_once($test_file); + $new_globals = get_defined_vars(); + $this->makeFileVariablesGlobal($existing_globals, $new_globals); + $new_classes = array_diff(get_declared_classes(), $existing_classes); + if (empty($new_classes)) { + $new_classes = $this->scrapeClassesFromFile($test_file); + } + $classes = $this->selectRunnableTests($new_classes); + return $this->createSuiteFromClasses($test_file, $classes); + } + + /** + * Imports new variables into the global namespace. + * @param hash $existing Variables before the file was loaded. + * @param hash $new Variables after the file was loaded. + * @access private + */ + protected function makeFileVariablesGlobal($existing, $new) { + $globals = array_diff(array_keys($new), array_keys($existing)); + foreach ($globals as $global) { + $GLOBALS[$global] = $new[$global]; + } + } + + /** + * Lookup classnames from file contents, in case the + * file may have been included before. + * Note: This is probably too clever by half. Figuring this + * out after a failed test case is going to be tricky for us, + * never mind the user. A test case should not be included + * twice anyway. + * @param string $test_file File name with classes. + * @access private + */ + protected function scrapeClassesFromFile($test_file) { + preg_match_all('~^\s*class\s+(\w+)(\s+(extends|implements)\s+\w+)*\s*\{~mi', + file_get_contents($test_file), + $matches ); + return $matches[1]; + } + + /** + * Calculates the incoming test cases. Skips abstract + * and ignored classes. + * @param array $candidates Candidate classes. + * @return array New classes which are test + * cases that shouldn't be ignored. + * @access public + */ + function selectRunnableTests($candidates) { + $classes = array(); + foreach ($candidates as $class) { + if (TestSuite::getBaseTestCase($class)) { + $reflection = new SimpleReflection($class); + if ($reflection->isAbstract()) { + SimpleTest::ignore($class); + } else { + $classes[] = $class; + } + } + } + return $classes; + } + + /** + * Builds a test suite from a class list. + * @param string $title Title of new group. + * @param array $classes Test classes. + * @return TestSuite Group loaded with the new + * test cases. + * @access public + */ + function createSuiteFromClasses($title, $classes) { + if (count($classes) == 0) { + $suite = new BadTestSuite($title, "No runnable test cases in [$title]"); + return $suite; + } + SimpleTest::ignoreParentsIfIgnored($classes); + $suite = new TestSuite($title); + foreach ($classes as $class) { + if (! SimpleTest::isIgnored($class)) { + $suite->add($class); + } + } + return $suite; + } +} + +/** + * This is a composite test class for combining + * test cases and other RunnableTest classes into + * a group test. + * @package SimpleTest + * @subpackage UnitTester + */ +class TestSuite { + private $label; + private $test_cases; + + /** + * Sets the name of the test suite. + * @param string $label Name sent at the start and end + * of the test. + * @access public + */ + function TestSuite($label = false) { + $this->label = $label; + $this->test_cases = array(); + } + + /** + * Accessor for the test name for subclasses. If the suite + * wraps a single test case the label defaults to the name of that test. + * @return string Name of the test. + * @access public + */ + function getLabel() { + if (! $this->label) { + return ($this->getSize() == 1) ? + get_class($this->test_cases[0]) : get_class($this); + } else { + return $this->label; + } + } + + /** + * Adds a test into the suite by instance or class. The class will + * be instantiated if it's a test suite. + * @param SimpleTestCase $test_case Suite or individual test + * case implementing the + * runnable test interface. + * @access public + */ + function add($test_case) { + if (! is_string($test_case)) { + $this->test_cases[] = $test_case; + } elseif (TestSuite::getBaseTestCase($test_case) == 'testsuite') { + $this->test_cases[] = new $test_case(); + } else { + $this->test_cases[] = $test_case; + } + } + + /** + * Builds a test suite from a library of test cases. + * The new suite is composed into this one. + * @param string $test_file File name of library with + * test case classes. + * @access public + */ + function addFile($test_file) { + $extractor = new SimpleFileLoader(); + $this->add($extractor->load($test_file)); + } + + /** + * Delegates to a visiting collector to add test + * files. + * @param string $path Path to scan from. + * @param SimpleCollector $collector Directory scanner. + * @access public + */ + function collect($path, $collector) { + $collector->collect($this, $path); + } + + /** + * Invokes run() on all of the held test cases, instantiating + * them if necessary. + * @param SimpleReporter $reporter Current test reporter. + * @access public + */ + function run($reporter) { + $reporter->paintGroupStart($this->getLabel(), $this->getSize()); + for ($i = 0, $count = count($this->test_cases); $i < $count; $i++) { + if (is_string($this->test_cases[$i])) { + $class = $this->test_cases[$i]; + $test = new $class(); + $test->run($reporter); + unset($test); + } else { + $this->test_cases[$i]->run($reporter); + } + } + $reporter->paintGroupEnd($this->getLabel()); + return $reporter->getStatus(); + } + + /** + * Number of contained test cases. + * @return integer Total count of cases in the group. + * @access public + */ + function getSize() { + $count = 0; + foreach ($this->test_cases as $case) { + if (is_string($case)) { + if (! SimpleTest::isIgnored($case)) { + $count++; + } + } else { + $count += $case->getSize(); + } + } + return $count; + } + + /** + * Test to see if a class is derived from the + * SimpleTestCase class. + * @param string $class Class name. + * @access public + */ + static function getBaseTestCase($class) { + while ($class = get_parent_class($class)) { + $class = strtolower($class); + if ($class == 'simpletestcase' || $class == 'testsuite') { + return $class; + } + } + return false; + } +} + +/** + * This is a failing group test for when a test suite hasn't + * loaded properly. + * @package SimpleTest + * @subpackage UnitTester + */ +class BadTestSuite { + private $label; + private $error; + + /** + * Sets the name of the test suite and error message. + * @param string $label Name sent at the start and end + * of the test. + * @access public + */ + function BadTestSuite($label, $error) { + $this->label = $label; + $this->error = $error; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->label; + } + + /** + * Sends a single error to the reporter. + * @param SimpleReporter $reporter Current test reporter. + * @access public + */ + function run($reporter) { + $reporter->paintGroupStart($this->getLabel(), $this->getSize()); + $reporter->paintFail('Bad TestSuite [' . $this->getLabel() . + '] with error [' . $this->error . ']'); + $reporter->paintGroupEnd($this->getLabel()); + return $reporter->getStatus(); + } + + /** + * Number of contained test cases. Always zero. + * @return integer Total count of cases in the group. + * @access public + */ + function getSize() { + return 0; + } +} +?> diff --git a/code/ryzom/tools/server/www/webtt/vendors/simpletest/tidy_parser.php b/code/ryzom/tools/server/www/webtt/vendors/simpletest/tidy_parser.php new file mode 100644 index 000000000..3d8b4b2ac --- /dev/null +++ b/code/ryzom/tools/server/www/webtt/vendors/simpletest/tidy_parser.php @@ -0,0 +1,382 @@ +free(); + } + + /** + * Frees up any references so as to allow the PHP garbage + * collection from unset() to work. + */ + private function free() { + unset($this->page); + $this->forms = array(); + $this->labels = array(); + } + + /** + * This builder is only available if the 'tidy' extension is loaded. + * @return boolean True if available. + */ + function can() { + return extension_loaded('tidy'); + } + + /** + * Reads the raw content the page using HTML Tidy. + * @param $response SimpleHttpResponse Fetched response. + * @return SimplePage Newly parsed page. + */ + function parse($response) { + $this->page = new SimplePage($response); + $tidied = tidy_parse_string($input = $this->insertGuards($response->getContent()), + array('output-xml' => false, 'wrap' => '0', 'indent' => 'no'), + 'latin1'); + $this->walkTree($tidied->html()); + $this->attachLabels($this->widgets_by_id, $this->labels); + $this->page->setForms($this->forms); + $page = $this->page; + $this->free(); + return $page; + } + + /** + * Stops HTMLTidy stripping content that we wish to preserve. + * @param string The raw html. + * @return string The html with guard tags inserted. + */ + private function insertGuards($html) { + return $this->insertEmptyTagGuards($this->insertTextareaSimpleWhitespaceGuards($html)); + } + + /** + * Removes the extra content added during the parse stage + * in order to preserve content we don't want stripped + * out by HTMLTidy. + * @param string The raw html. + * @return string The html with guard tags removed. + */ + private function stripGuards($html) { + return $this->stripTextareaWhitespaceGuards($this->stripEmptyTagGuards($html)); + } + + /** + * HTML tidy strips out empty tags such as