diff --git a/14_11_2011_add_myob_fields_to_products.sql b/app/14_11_2011_add_myob_fields_to_products.sql similarity index 100% rename from 14_11_2011_add_myob_fields_to_products.sql rename to app/14_11_2011_add_myob_fields_to_products.sql diff --git a/27_9_2011_shipment_invoices.sql b/app/27_9_2011_shipment_invoices.sql similarity index 100% rename from 27_9_2011_shipment_invoices.sql rename to app/27_9_2011_shipment_invoices.sql diff --git a/README.md b/app/README.md similarity index 100% rename from README.md rename to app/README.md diff --git a/aug.16.documents.sql b/app/aug.16.documents.sql similarity index 100% rename from aug.16.documents.sql rename to app/aug.16.documents.sql diff --git a/config/acl.ini.php b/app/config/acl.ini.php similarity index 100% rename from config/acl.ini.php rename to app/config/acl.ini.php diff --git a/config/bootstrap.php b/app/config/bootstrap.php similarity index 100% rename from config/bootstrap.php rename to app/config/bootstrap.php diff --git a/config/inflections.php b/app/config/inflections.php similarity index 100% rename from config/inflections.php rename to app/config/inflections.php diff --git a/config/routes.php b/app/config/routes.php similarity index 100% rename from config/routes.php rename to app/config/routes.php diff --git a/config/sql/db_acl.php b/app/config/sql/db_acl.php similarity index 100% rename from config/sql/db_acl.php rename to app/config/sql/db_acl.php diff --git a/config/sql/db_acl.sql b/app/config/sql/db_acl.sql similarity index 100% rename from config/sql/db_acl.sql rename to app/config/sql/db_acl.sql diff --git a/config/sql/i18n.php b/app/config/sql/i18n.php similarity index 100% rename from config/sql/i18n.php rename to app/config/sql/i18n.php diff --git a/config/sql/i18n.sql b/app/config/sql/i18n.sql similarity index 100% rename from config/sql/i18n.sql rename to app/config/sql/i18n.sql diff --git a/config/sql/sessions.php b/app/config/sql/sessions.php similarity index 100% rename from config/sql/sessions.php rename to app/config/sql/sessions.php diff --git a/config/sql/sessions.sql b/app/config/sql/sessions.sql similarity index 100% rename from config/sql/sessions.sql rename to app/config/sql/sessions.sql diff --git a/controllers/addresses_controller.php b/app/controllers/addresses_controller.php similarity index 100% rename from controllers/addresses_controller.php rename to app/controllers/addresses_controller.php diff --git a/controllers/app_controller.php b/app/controllers/app_controller.php similarity index 100% rename from controllers/app_controller.php rename to app/controllers/app_controller.php diff --git a/controllers/attachments_controller.php b/app/controllers/attachments_controller.php similarity index 100% rename from controllers/attachments_controller.php rename to app/controllers/attachments_controller.php diff --git a/controllers/boxes_controller.php b/app/controllers/boxes_controller.php similarity index 100% rename from controllers/boxes_controller.php rename to app/controllers/boxes_controller.php diff --git a/controllers/components/empty b/app/controllers/components/empty similarity index 100% rename from controllers/components/empty rename to app/controllers/components/empty diff --git a/controllers/components/image.php b/app/controllers/components/image.php similarity index 100% rename from controllers/components/image.php rename to app/controllers/components/image.php diff --git a/controllers/contact_categories_controller.php b/app/controllers/contact_categories_controller.php similarity index 100% rename from controllers/contact_categories_controller.php rename to app/controllers/contact_categories_controller.php diff --git a/controllers/costings_controller.php b/app/controllers/costings_controller.php similarity index 100% rename from controllers/costings_controller.php rename to app/controllers/costings_controller.php diff --git a/controllers/countries_controller.php b/app/controllers/countries_controller.php similarity index 100% rename from controllers/countries_controller.php rename to app/controllers/countries_controller.php diff --git a/controllers/currencies_controller.php b/app/controllers/currencies_controller.php similarity index 100% rename from controllers/currencies_controller.php rename to app/controllers/currencies_controller.php diff --git a/controllers/customer_categories_controller.php b/app/controllers/customer_categories_controller.php similarity index 100% rename from controllers/customer_categories_controller.php rename to app/controllers/customer_categories_controller.php diff --git a/controllers/customers_controller.php b/app/controllers/customers_controller.php similarity index 100% rename from controllers/customers_controller.php rename to app/controllers/customers_controller.php diff --git a/controllers/doc_pages_controller.php b/app/controllers/doc_pages_controller.php similarity index 100% rename from controllers/doc_pages_controller.php rename to app/controllers/doc_pages_controller.php diff --git a/controllers/documents_controller.php b/app/controllers/documents_controller.php similarity index 100% rename from controllers/documents_controller.php rename to app/controllers/documents_controller.php diff --git a/controllers/email_attachments_controller.php b/app/controllers/email_attachments_controller.php similarity index 100% rename from controllers/email_attachments_controller.php rename to app/controllers/email_attachments_controller.php diff --git a/controllers/email_recipients_controller.php b/app/controllers/email_recipients_controller.php similarity index 100% rename from controllers/email_recipients_controller.php rename to app/controllers/email_recipients_controller.php diff --git a/controllers/emails_controller.php b/app/controllers/emails_controller.php similarity index 100% rename from controllers/emails_controller.php rename to app/controllers/emails_controller.php diff --git a/controllers/enquiries_controller.php b/app/controllers/enquiries_controller.php similarity index 100% rename from controllers/enquiries_controller.php rename to app/controllers/enquiries_controller.php diff --git a/controllers/enquiry_files_controller.php b/app/controllers/enquiry_files_controller.php similarity index 100% rename from controllers/enquiry_files_controller.php rename to app/controllers/enquiry_files_controller.php diff --git a/controllers/freight_forwarders_controller.php b/app/controllers/freight_forwarders_controller.php similarity index 100% rename from controllers/freight_forwarders_controller.php rename to app/controllers/freight_forwarders_controller.php diff --git a/controllers/freight_services_controller.php b/app/controllers/freight_services_controller.php similarity index 100% rename from controllers/freight_services_controller.php rename to app/controllers/freight_services_controller.php diff --git a/controllers/groups_controller.php b/app/controllers/groups_controller.php similarity index 100% rename from controllers/groups_controller.php rename to app/controllers/groups_controller.php diff --git a/controllers/industries_controller.php b/app/controllers/industries_controller.php similarity index 100% rename from controllers/industries_controller.php rename to app/controllers/industries_controller.php diff --git a/controllers/invoices_controller.php b/app/controllers/invoices_controller.php similarity index 100% rename from controllers/invoices_controller.php rename to app/controllers/invoices_controller.php diff --git a/controllers/issue_actions_controller.php b/app/controllers/issue_actions_controller.php similarity index 100% rename from controllers/issue_actions_controller.php rename to app/controllers/issue_actions_controller.php diff --git a/controllers/issues_controller.php b/app/controllers/issues_controller.php similarity index 100% rename from controllers/issues_controller.php rename to app/controllers/issues_controller.php diff --git a/controllers/jobs_controller.php b/app/controllers/jobs_controller.php similarity index 100% rename from controllers/jobs_controller.php rename to app/controllers/jobs_controller.php diff --git a/controllers/line_items_controller.php b/app/controllers/line_items_controller.php similarity index 100% rename from controllers/line_items_controller.php rename to app/controllers/line_items_controller.php diff --git a/controllers/principle_addresses_controller.php b/app/controllers/principle_addresses_controller.php similarity index 100% rename from controllers/principle_addresses_controller.php rename to app/controllers/principle_addresses_controller.php diff --git a/controllers/principle_contacts_controller.php b/app/controllers/principle_contacts_controller.php similarity index 100% rename from controllers/principle_contacts_controller.php rename to app/controllers/principle_contacts_controller.php diff --git a/controllers/principles_controller.php b/app/controllers/principles_controller.php similarity index 100% rename from controllers/principles_controller.php rename to app/controllers/principles_controller.php diff --git a/controllers/product_attachments_controller.php b/app/controllers/product_attachments_controller.php similarity index 100% rename from controllers/product_attachments_controller.php rename to app/controllers/product_attachments_controller.php diff --git a/controllers/product_categories_controller.php b/app/controllers/product_categories_controller.php similarity index 100% rename from controllers/product_categories_controller.php rename to app/controllers/product_categories_controller.php diff --git a/controllers/product_options_categories_controller.php b/app/controllers/product_options_categories_controller.php similarity index 100% rename from controllers/product_options_categories_controller.php rename to app/controllers/product_options_categories_controller.php diff --git a/controllers/product_options_controller.php b/app/controllers/product_options_controller.php similarity index 100% rename from controllers/product_options_controller.php rename to app/controllers/product_options_controller.php diff --git a/controllers/products_controller.php b/app/controllers/products_controller.php similarity index 100% rename from controllers/products_controller.php rename to app/controllers/products_controller.php diff --git a/controllers/purchase_invoices_controller.php b/app/controllers/purchase_invoices_controller.php similarity index 100% rename from controllers/purchase_invoices_controller.php rename to app/controllers/purchase_invoices_controller.php diff --git a/controllers/purchase_orders_controller.php b/app/controllers/purchase_orders_controller.php similarity index 100% rename from controllers/purchase_orders_controller.php rename to app/controllers/purchase_orders_controller.php diff --git a/controllers/quote_products_controller.php b/app/controllers/quote_products_controller.php similarity index 100% rename from controllers/quote_products_controller.php rename to app/controllers/quote_products_controller.php diff --git a/controllers/quotes_controller.php b/app/controllers/quotes_controller.php similarity index 100% rename from controllers/quotes_controller.php rename to app/controllers/quotes_controller.php diff --git a/controllers/shipment_invoices_controller.php b/app/controllers/shipment_invoices_controller.php similarity index 100% rename from controllers/shipment_invoices_controller.php rename to app/controllers/shipment_invoices_controller.php diff --git a/controllers/shipments_controller.php b/app/controllers/shipments_controller.php similarity index 100% rename from controllers/shipments_controller.php rename to app/controllers/shipments_controller.php diff --git a/controllers/states_controller.php b/app/controllers/states_controller.php similarity index 100% rename from controllers/states_controller.php rename to app/controllers/states_controller.php diff --git a/controllers/statuses_controller.php b/app/controllers/statuses_controller.php similarity index 100% rename from controllers/statuses_controller.php rename to app/controllers/statuses_controller.php diff --git a/controllers/users_controller.php b/app/controllers/users_controller.php similarity index 100% rename from controllers/users_controller.php rename to app/controllers/users_controller.php diff --git a/emails/.eml b/app/emails/.eml similarity index 100% rename from emails/.eml rename to app/emails/.eml diff --git a/app/index.php b/app/index.php new file mode 100755 index 00000000..985b0cd0 --- /dev/null +++ b/app/index.php @@ -0,0 +1,26 @@ + + * Copyright 2005-2008, 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. + * + * @filesource + * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project + * @package cake + * @subpackage cake.app + * @since CakePHP(tm) v 0.10.0.1076 + * @version $Revision: 6311 $ + * @modifiedby $LastChangedBy: phpnut $ + * @lastmodified $Date: 2008-01-01 22:33:52 -0800 (Tue, 01 Jan 2008) $ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +require 'webroot' . DIRECTORY_SEPARATOR . 'index.php'; +?> \ No newline at end of file diff --git a/locale/eng/LC_MESSAGES/empty b/app/locale/eng/LC_MESSAGES/empty similarity index 100% rename from locale/eng/LC_MESSAGES/empty rename to app/locale/eng/LC_MESSAGES/empty diff --git a/models/address.php b/app/models/address.php similarity index 100% rename from models/address.php rename to app/models/address.php diff --git a/models/app_model.php b/app/models/app_model.php similarity index 100% rename from models/app_model.php rename to app/models/app_model.php diff --git a/models/attachment.php b/app/models/attachment.php similarity index 100% rename from models/attachment.php rename to app/models/attachment.php diff --git a/models/behaviors/empty b/app/models/behaviors/empty similarity index 100% rename from models/behaviors/empty rename to app/models/behaviors/empty diff --git a/models/box.php b/app/models/box.php similarity index 100% rename from models/box.php rename to app/models/box.php diff --git a/models/contact.php b/app/models/contact.php similarity index 100% rename from models/contact.php rename to app/models/contact.php diff --git a/models/contact_category.php b/app/models/contact_category.php similarity index 100% rename from models/contact_category.php rename to app/models/contact_category.php diff --git a/models/costing.php b/app/models/costing.php similarity index 100% rename from models/costing.php rename to app/models/costing.php diff --git a/models/country.php b/app/models/country.php similarity index 100% rename from models/country.php rename to app/models/country.php diff --git a/models/currency.php b/app/models/currency.php similarity index 100% rename from models/currency.php rename to app/models/currency.php diff --git a/models/customer.php b/app/models/customer.php similarity index 100% rename from models/customer.php rename to app/models/customer.php diff --git a/models/customer_category.php b/app/models/customer_category.php similarity index 100% rename from models/customer_category.php rename to app/models/customer_category.php diff --git a/models/datasources/empty b/app/models/datasources/empty similarity index 100% rename from models/datasources/empty rename to app/models/datasources/empty diff --git a/models/doc_page.php b/app/models/doc_page.php similarity index 100% rename from models/doc_page.php rename to app/models/doc_page.php diff --git a/models/document.php b/app/models/document.php similarity index 100% rename from models/document.php rename to app/models/document.php diff --git a/models/document_attachment.php b/app/models/document_attachment.php similarity index 100% rename from models/document_attachment.php rename to app/models/document_attachment.php diff --git a/models/email.php b/app/models/email.php similarity index 100% rename from models/email.php rename to app/models/email.php diff --git a/models/email_attachment.php b/app/models/email_attachment.php similarity index 100% rename from models/email_attachment.php rename to app/models/email_attachment.php diff --git a/models/email_recipient.php b/app/models/email_recipient.php similarity index 100% rename from models/email_recipient.php rename to app/models/email_recipient.php diff --git a/models/enquiry.php b/app/models/enquiry.php similarity index 100% rename from models/enquiry.php rename to app/models/enquiry.php diff --git a/models/enquiry_email_queue.php b/app/models/enquiry_email_queue.php similarity index 100% rename from models/enquiry_email_queue.php rename to app/models/enquiry_email_queue.php diff --git a/models/enquiry_file.php b/app/models/enquiry_file.php similarity index 100% rename from models/enquiry_file.php rename to app/models/enquiry_file.php diff --git a/models/freight_forwarder.php b/app/models/freight_forwarder.php similarity index 100% rename from models/freight_forwarder.php rename to app/models/freight_forwarder.php diff --git a/models/freight_service.php b/app/models/freight_service.php similarity index 100% rename from models/freight_service.php rename to app/models/freight_service.php diff --git a/models/group.php b/app/models/group.php similarity index 100% rename from models/group.php rename to app/models/group.php diff --git a/models/industry.php b/app/models/industry.php similarity index 100% rename from models/industry.php rename to app/models/industry.php diff --git a/models/invoice.php b/app/models/invoice.php similarity index 100% rename from models/invoice.php rename to app/models/invoice.php diff --git a/models/job.php b/app/models/job.php similarity index 100% rename from models/job.php rename to app/models/job.php diff --git a/models/line_item.php b/app/models/line_item.php similarity index 100% rename from models/line_item.php rename to app/models/line_item.php diff --git a/models/order_acknowledgement.php b/app/models/order_acknowledgement.php similarity index 100% rename from models/order_acknowledgement.php rename to app/models/order_acknowledgement.php diff --git a/models/principle.php b/app/models/principle.php similarity index 100% rename from models/principle.php rename to app/models/principle.php diff --git a/models/principle_address.php b/app/models/principle_address.php similarity index 100% rename from models/principle_address.php rename to app/models/principle_address.php diff --git a/models/principle_contact.php b/app/models/principle_contact.php similarity index 100% rename from models/principle_contact.php rename to app/models/principle_contact.php diff --git a/models/product.php b/app/models/product.php similarity index 100% rename from models/product.php rename to app/models/product.php diff --git a/models/product_attachment.php b/app/models/product_attachment.php similarity index 100% rename from models/product_attachment.php rename to app/models/product_attachment.php diff --git a/models/product_category.php b/app/models/product_category.php similarity index 100% rename from models/product_category.php rename to app/models/product_category.php diff --git a/models/product_option.php b/app/models/product_option.php similarity index 100% rename from models/product_option.php rename to app/models/product_option.php diff --git a/models/product_options_category.php b/app/models/product_options_category.php similarity index 100% rename from models/product_options_category.php rename to app/models/product_options_category.php diff --git a/models/purchase_order.php b/app/models/purchase_order.php similarity index 100% rename from models/purchase_order.php rename to app/models/purchase_order.php diff --git a/models/quote.php b/app/models/quote.php similarity index 100% rename from models/quote.php rename to app/models/quote.php diff --git a/models/shipment.php b/app/models/shipment.php similarity index 100% rename from models/shipment.php rename to app/models/shipment.php diff --git a/models/shipment_invoice.php b/app/models/shipment_invoice.php similarity index 100% rename from models/shipment_invoice.php rename to app/models/shipment_invoice.php diff --git a/models/state.php b/app/models/state.php similarity index 100% rename from models/state.php rename to app/models/state.php diff --git a/models/status.php b/app/models/status.php similarity index 100% rename from models/status.php rename to app/models/status.php diff --git a/models/user.php b/app/models/user.php similarity index 100% rename from models/user.php rename to app/models/user.php diff --git a/plugins/empty b/app/plugins/empty similarity index 100% rename from plugins/empty rename to app/plugins/empty diff --git a/tests/cases/behaviors/empty b/app/tests/cases/behaviors/empty similarity index 100% rename from tests/cases/behaviors/empty rename to app/tests/cases/behaviors/empty diff --git a/tests/cases/components/empty b/app/tests/cases/components/empty similarity index 100% rename from tests/cases/components/empty rename to app/tests/cases/components/empty diff --git a/tests/cases/controllers/empty b/app/tests/cases/controllers/empty similarity index 100% rename from tests/cases/controllers/empty rename to app/tests/cases/controllers/empty diff --git a/tests/cases/helpers/empty b/app/tests/cases/helpers/empty similarity index 100% rename from tests/cases/helpers/empty rename to app/tests/cases/helpers/empty diff --git a/tests/cases/models/currency.test.php b/app/tests/cases/models/currency.test.php similarity index 100% rename from tests/cases/models/currency.test.php rename to app/tests/cases/models/currency.test.php diff --git a/tests/cases/models/empty b/app/tests/cases/models/empty similarity index 100% rename from tests/cases/models/empty rename to app/tests/cases/models/empty diff --git a/tests/fixtures/currency_fixture.php b/app/tests/fixtures/currency_fixture.php similarity index 100% rename from tests/fixtures/currency_fixture.php rename to app/tests/fixtures/currency_fixture.php diff --git a/tests/fixtures/empty b/app/tests/fixtures/empty similarity index 100% rename from tests/fixtures/empty rename to app/tests/fixtures/empty diff --git a/tests/groups/empty b/app/tests/groups/empty similarity index 100% rename from tests/groups/empty rename to app/tests/groups/empty diff --git a/vendors/fpdi/filters/FilterASCII85.php b/app/vendors/fpdi/filters/FilterASCII85.php similarity index 100% rename from vendors/fpdi/filters/FilterASCII85.php rename to app/vendors/fpdi/filters/FilterASCII85.php diff --git a/vendors/fpdi/filters/FilterASCII85_FPDI.php b/app/vendors/fpdi/filters/FilterASCII85_FPDI.php similarity index 100% rename from vendors/fpdi/filters/FilterASCII85_FPDI.php rename to app/vendors/fpdi/filters/FilterASCII85_FPDI.php diff --git a/vendors/fpdi/filters/FilterLZW.php b/app/vendors/fpdi/filters/FilterLZW.php similarity index 100% rename from vendors/fpdi/filters/FilterLZW.php rename to app/vendors/fpdi/filters/FilterLZW.php diff --git a/vendors/fpdi/filters/FilterLZW_FPDI.php b/app/vendors/fpdi/filters/FilterLZW_FPDI.php similarity index 100% rename from vendors/fpdi/filters/FilterLZW_FPDI.php rename to app/vendors/fpdi/filters/FilterLZW_FPDI.php diff --git a/vendors/fpdi/fpdf_tpl.php b/app/vendors/fpdi/fpdf_tpl.php similarity index 100% rename from vendors/fpdi/fpdf_tpl.php rename to app/vendors/fpdi/fpdf_tpl.php diff --git a/vendors/fpdi/fpdi.php b/app/vendors/fpdi/fpdi.php similarity index 100% rename from vendors/fpdi/fpdi.php rename to app/vendors/fpdi/fpdi.php diff --git a/vendors/fpdi/fpdi2tcpdf_bridge.php b/app/vendors/fpdi/fpdi2tcpdf_bridge.php similarity index 100% rename from vendors/fpdi/fpdi2tcpdf_bridge.php rename to app/vendors/fpdi/fpdi2tcpdf_bridge.php diff --git a/vendors/fpdi/fpdi_pdf_parser.php b/app/vendors/fpdi/fpdi_pdf_parser.php similarity index 100% rename from vendors/fpdi/fpdi_pdf_parser.php rename to app/vendors/fpdi/fpdi_pdf_parser.php diff --git a/vendors/fpdi/pdf_context.php b/app/vendors/fpdi/pdf_context.php similarity index 100% rename from vendors/fpdi/pdf_context.php rename to app/vendors/fpdi/pdf_context.php diff --git a/vendors/fpdi/pdf_parser.php b/app/vendors/fpdi/pdf_parser.php similarity index 100% rename from vendors/fpdi/pdf_parser.php rename to app/vendors/fpdi/pdf_parser.php diff --git a/vendors/pdfdoc.php b/app/vendors/pdfdoc.php similarity index 100% rename from vendors/pdfdoc.php rename to app/vendors/pdfdoc.php diff --git a/vendors/shells/enquiry_email.php b/app/vendors/shells/enquiry_email.php similarity index 100% rename from vendors/shells/enquiry_email.php rename to app/vendors/shells/enquiry_email.php diff --git a/vendors/shells/firstpass.php b/app/vendors/shells/firstpass.php similarity index 100% rename from vendors/shells/firstpass.php rename to app/vendors/shells/firstpass.php diff --git a/views/elements/empty b/app/vendors/shells/tasks/empty similarity index 100% rename from views/elements/empty rename to app/vendors/shells/tasks/empty diff --git a/views/errors/empty b/app/vendors/shells/templates/empty similarity index 100% rename from views/errors/empty rename to app/vendors/shells/templates/empty diff --git a/vendors/shells/update_quote_revisions.php b/app/vendors/shells/update_quote_revisions.php similarity index 100% rename from vendors/shells/update_quote_revisions.php rename to app/vendors/shells/update_quote_revisions.php diff --git a/vendors/shells/users_migrate.php b/app/vendors/shells/users_migrate.php similarity index 100% rename from vendors/shells/users_migrate.php rename to app/vendors/shells/users_migrate.php diff --git a/vendors/shells/vault.php b/app/vendors/shells/vault.php similarity index 100% rename from vendors/shells/vault.php rename to app/vendors/shells/vault.php diff --git a/vendors/shells/vault_two.php b/app/vendors/shells/vault_two.php similarity index 100% rename from vendors/shells/vault_two.php rename to app/vendors/shells/vault_two.php diff --git a/vendors/tcpdf/2dbarcodes.php b/app/vendors/tcpdf/2dbarcodes.php similarity index 100% rename from vendors/tcpdf/2dbarcodes.php rename to app/vendors/tcpdf/2dbarcodes.php diff --git a/vendors/tcpdf/CHANGELOG.TXT b/app/vendors/tcpdf/CHANGELOG.TXT similarity index 100% rename from vendors/tcpdf/CHANGELOG.TXT rename to app/vendors/tcpdf/CHANGELOG.TXT diff --git a/vendors/tcpdf/LICENSE.TXT b/app/vendors/tcpdf/LICENSE.TXT similarity index 100% rename from vendors/tcpdf/LICENSE.TXT rename to app/vendors/tcpdf/LICENSE.TXT diff --git a/vendors/tcpdf/README.TXT b/app/vendors/tcpdf/README.TXT similarity index 100% rename from vendors/tcpdf/README.TXT rename to app/vendors/tcpdf/README.TXT diff --git a/vendors/tcpdf/barcodes.php b/app/vendors/tcpdf/barcodes.php similarity index 100% rename from vendors/tcpdf/barcodes.php rename to app/vendors/tcpdf/barcodes.php diff --git a/vendors/tcpdf/config/lang/afr.php b/app/vendors/tcpdf/config/lang/afr.php similarity index 100% rename from vendors/tcpdf/config/lang/afr.php rename to app/vendors/tcpdf/config/lang/afr.php diff --git a/vendors/tcpdf/config/lang/ara.php b/app/vendors/tcpdf/config/lang/ara.php similarity index 100% rename from vendors/tcpdf/config/lang/ara.php rename to app/vendors/tcpdf/config/lang/ara.php diff --git a/vendors/tcpdf/config/lang/aze.php b/app/vendors/tcpdf/config/lang/aze.php similarity index 100% rename from vendors/tcpdf/config/lang/aze.php rename to app/vendors/tcpdf/config/lang/aze.php diff --git a/vendors/tcpdf/config/lang/bel.php b/app/vendors/tcpdf/config/lang/bel.php similarity index 100% rename from vendors/tcpdf/config/lang/bel.php rename to app/vendors/tcpdf/config/lang/bel.php diff --git a/vendors/tcpdf/config/lang/bra.php b/app/vendors/tcpdf/config/lang/bra.php similarity index 100% rename from vendors/tcpdf/config/lang/bra.php rename to app/vendors/tcpdf/config/lang/bra.php diff --git a/vendors/tcpdf/config/lang/cat.php b/app/vendors/tcpdf/config/lang/cat.php similarity index 100% rename from vendors/tcpdf/config/lang/cat.php rename to app/vendors/tcpdf/config/lang/cat.php diff --git a/vendors/tcpdf/config/lang/ces.php b/app/vendors/tcpdf/config/lang/ces.php similarity index 100% rename from vendors/tcpdf/config/lang/ces.php rename to app/vendors/tcpdf/config/lang/ces.php diff --git a/vendors/tcpdf/config/lang/chi.php b/app/vendors/tcpdf/config/lang/chi.php similarity index 100% rename from vendors/tcpdf/config/lang/chi.php rename to app/vendors/tcpdf/config/lang/chi.php diff --git a/vendors/tcpdf/config/lang/cym.php b/app/vendors/tcpdf/config/lang/cym.php similarity index 100% rename from vendors/tcpdf/config/lang/cym.php rename to app/vendors/tcpdf/config/lang/cym.php diff --git a/vendors/tcpdf/config/lang/dan.php b/app/vendors/tcpdf/config/lang/dan.php similarity index 100% rename from vendors/tcpdf/config/lang/dan.php rename to app/vendors/tcpdf/config/lang/dan.php diff --git a/vendors/tcpdf/config/lang/eng.php b/app/vendors/tcpdf/config/lang/eng.php similarity index 100% rename from vendors/tcpdf/config/lang/eng.php rename to app/vendors/tcpdf/config/lang/eng.php diff --git a/vendors/tcpdf/config/lang/est.php b/app/vendors/tcpdf/config/lang/est.php similarity index 100% rename from vendors/tcpdf/config/lang/est.php rename to app/vendors/tcpdf/config/lang/est.php diff --git a/vendors/tcpdf/config/lang/eus.php b/app/vendors/tcpdf/config/lang/eus.php similarity index 100% rename from vendors/tcpdf/config/lang/eus.php rename to app/vendors/tcpdf/config/lang/eus.php diff --git a/vendors/tcpdf/config/lang/far.php b/app/vendors/tcpdf/config/lang/far.php similarity index 100% rename from vendors/tcpdf/config/lang/far.php rename to app/vendors/tcpdf/config/lang/far.php diff --git a/vendors/tcpdf/config/lang/fra.php b/app/vendors/tcpdf/config/lang/fra.php similarity index 100% rename from vendors/tcpdf/config/lang/fra.php rename to app/vendors/tcpdf/config/lang/fra.php diff --git a/vendors/tcpdf/config/lang/ger.php b/app/vendors/tcpdf/config/lang/ger.php similarity index 100% rename from vendors/tcpdf/config/lang/ger.php rename to app/vendors/tcpdf/config/lang/ger.php diff --git a/vendors/tcpdf/config/lang/gle.php b/app/vendors/tcpdf/config/lang/gle.php similarity index 100% rename from vendors/tcpdf/config/lang/gle.php rename to app/vendors/tcpdf/config/lang/gle.php diff --git a/vendors/tcpdf/config/lang/glg.php b/app/vendors/tcpdf/config/lang/glg.php similarity index 100% rename from vendors/tcpdf/config/lang/glg.php rename to app/vendors/tcpdf/config/lang/glg.php diff --git a/vendors/tcpdf/config/lang/hat.php b/app/vendors/tcpdf/config/lang/hat.php similarity index 100% rename from vendors/tcpdf/config/lang/hat.php rename to app/vendors/tcpdf/config/lang/hat.php diff --git a/vendors/tcpdf/config/lang/heb.php b/app/vendors/tcpdf/config/lang/heb.php similarity index 100% rename from vendors/tcpdf/config/lang/heb.php rename to app/vendors/tcpdf/config/lang/heb.php diff --git a/vendors/tcpdf/config/lang/hrv.php b/app/vendors/tcpdf/config/lang/hrv.php similarity index 100% rename from vendors/tcpdf/config/lang/hrv.php rename to app/vendors/tcpdf/config/lang/hrv.php diff --git a/vendors/tcpdf/config/lang/hun.php b/app/vendors/tcpdf/config/lang/hun.php similarity index 100% rename from vendors/tcpdf/config/lang/hun.php rename to app/vendors/tcpdf/config/lang/hun.php diff --git a/vendors/tcpdf/config/lang/hye.php b/app/vendors/tcpdf/config/lang/hye.php similarity index 100% rename from vendors/tcpdf/config/lang/hye.php rename to app/vendors/tcpdf/config/lang/hye.php diff --git a/vendors/tcpdf/config/lang/ind.php b/app/vendors/tcpdf/config/lang/ind.php similarity index 100% rename from vendors/tcpdf/config/lang/ind.php rename to app/vendors/tcpdf/config/lang/ind.php diff --git a/vendors/tcpdf/config/lang/ita.php b/app/vendors/tcpdf/config/lang/ita.php similarity index 100% rename from vendors/tcpdf/config/lang/ita.php rename to app/vendors/tcpdf/config/lang/ita.php diff --git a/vendors/tcpdf/config/lang/kat.php b/app/vendors/tcpdf/config/lang/kat.php similarity index 100% rename from vendors/tcpdf/config/lang/kat.php rename to app/vendors/tcpdf/config/lang/kat.php diff --git a/vendors/tcpdf/config/lang/kor.php b/app/vendors/tcpdf/config/lang/kor.php similarity index 100% rename from vendors/tcpdf/config/lang/kor.php rename to app/vendors/tcpdf/config/lang/kor.php diff --git a/vendors/tcpdf/config/lang/mkd.php b/app/vendors/tcpdf/config/lang/mkd.php similarity index 100% rename from vendors/tcpdf/config/lang/mkd.php rename to app/vendors/tcpdf/config/lang/mkd.php diff --git a/vendors/tcpdf/config/lang/mlt.php b/app/vendors/tcpdf/config/lang/mlt.php similarity index 100% rename from vendors/tcpdf/config/lang/mlt.php rename to app/vendors/tcpdf/config/lang/mlt.php diff --git a/vendors/tcpdf/config/lang/msa.php b/app/vendors/tcpdf/config/lang/msa.php similarity index 100% rename from vendors/tcpdf/config/lang/msa.php rename to app/vendors/tcpdf/config/lang/msa.php diff --git a/vendors/tcpdf/config/lang/nld.php b/app/vendors/tcpdf/config/lang/nld.php similarity index 100% rename from vendors/tcpdf/config/lang/nld.php rename to app/vendors/tcpdf/config/lang/nld.php diff --git a/vendors/tcpdf/config/lang/nob.php b/app/vendors/tcpdf/config/lang/nob.php similarity index 100% rename from vendors/tcpdf/config/lang/nob.php rename to app/vendors/tcpdf/config/lang/nob.php diff --git a/vendors/tcpdf/config/lang/pol.php b/app/vendors/tcpdf/config/lang/pol.php similarity index 100% rename from vendors/tcpdf/config/lang/pol.php rename to app/vendors/tcpdf/config/lang/pol.php diff --git a/vendors/tcpdf/config/lang/por.php b/app/vendors/tcpdf/config/lang/por.php similarity index 100% rename from vendors/tcpdf/config/lang/por.php rename to app/vendors/tcpdf/config/lang/por.php diff --git a/vendors/tcpdf/config/lang/ron.php b/app/vendors/tcpdf/config/lang/ron.php similarity index 100% rename from vendors/tcpdf/config/lang/ron.php rename to app/vendors/tcpdf/config/lang/ron.php diff --git a/vendors/tcpdf/config/lang/rus.php b/app/vendors/tcpdf/config/lang/rus.php similarity index 100% rename from vendors/tcpdf/config/lang/rus.php rename to app/vendors/tcpdf/config/lang/rus.php diff --git a/vendors/tcpdf/config/lang/slv.php b/app/vendors/tcpdf/config/lang/slv.php similarity index 100% rename from vendors/tcpdf/config/lang/slv.php rename to app/vendors/tcpdf/config/lang/slv.php diff --git a/vendors/tcpdf/config/lang/spa.php b/app/vendors/tcpdf/config/lang/spa.php similarity index 100% rename from vendors/tcpdf/config/lang/spa.php rename to app/vendors/tcpdf/config/lang/spa.php diff --git a/vendors/tcpdf/config/lang/sqi.php b/app/vendors/tcpdf/config/lang/sqi.php similarity index 100% rename from vendors/tcpdf/config/lang/sqi.php rename to app/vendors/tcpdf/config/lang/sqi.php diff --git a/vendors/tcpdf/config/lang/srp.php b/app/vendors/tcpdf/config/lang/srp.php similarity index 100% rename from vendors/tcpdf/config/lang/srp.php rename to app/vendors/tcpdf/config/lang/srp.php diff --git a/vendors/tcpdf/config/lang/swa.php b/app/vendors/tcpdf/config/lang/swa.php similarity index 100% rename from vendors/tcpdf/config/lang/swa.php rename to app/vendors/tcpdf/config/lang/swa.php diff --git a/vendors/tcpdf/config/lang/swe.php b/app/vendors/tcpdf/config/lang/swe.php similarity index 100% rename from vendors/tcpdf/config/lang/swe.php rename to app/vendors/tcpdf/config/lang/swe.php diff --git a/vendors/tcpdf/config/lang/urd.php b/app/vendors/tcpdf/config/lang/urd.php similarity index 100% rename from vendors/tcpdf/config/lang/urd.php rename to app/vendors/tcpdf/config/lang/urd.php diff --git a/vendors/tcpdf/config/lang/yid.php b/app/vendors/tcpdf/config/lang/yid.php similarity index 100% rename from vendors/tcpdf/config/lang/yid.php rename to app/vendors/tcpdf/config/lang/yid.php diff --git a/vendors/tcpdf/config/lang/zho.php b/app/vendors/tcpdf/config/lang/zho.php similarity index 100% rename from vendors/tcpdf/config/lang/zho.php rename to app/vendors/tcpdf/config/lang/zho.php diff --git a/vendors/tcpdf/config/tcpdf_config.php b/app/vendors/tcpdf/config/tcpdf_config.php similarity index 100% rename from vendors/tcpdf/config/tcpdf_config.php rename to app/vendors/tcpdf/config/tcpdf_config.php diff --git a/vendors/tcpdf/config/tcpdf_config_alt.php b/app/vendors/tcpdf/config/tcpdf_config_alt.php similarity index 100% rename from vendors/tcpdf/config/tcpdf_config_alt.php rename to app/vendors/tcpdf/config/tcpdf_config_alt.php diff --git a/vendors/tcpdf/doc/index.html b/app/vendors/tcpdf/doc/index.html similarity index 100% rename from vendors/tcpdf/doc/index.html rename to app/vendors/tcpdf/doc/index.html diff --git a/vendors/tcpdf/examples/example_001.php b/app/vendors/tcpdf/examples/example_001.php similarity index 100% rename from vendors/tcpdf/examples/example_001.php rename to app/vendors/tcpdf/examples/example_001.php diff --git a/vendors/tcpdf/examples/example_002.php b/app/vendors/tcpdf/examples/example_002.php similarity index 100% rename from vendors/tcpdf/examples/example_002.php rename to app/vendors/tcpdf/examples/example_002.php diff --git a/vendors/tcpdf/examples/example_003.php b/app/vendors/tcpdf/examples/example_003.php similarity index 100% rename from vendors/tcpdf/examples/example_003.php rename to app/vendors/tcpdf/examples/example_003.php diff --git a/vendors/tcpdf/examples/example_004.php b/app/vendors/tcpdf/examples/example_004.php similarity index 100% rename from vendors/tcpdf/examples/example_004.php rename to app/vendors/tcpdf/examples/example_004.php diff --git a/vendors/tcpdf/examples/example_005.php b/app/vendors/tcpdf/examples/example_005.php similarity index 100% rename from vendors/tcpdf/examples/example_005.php rename to app/vendors/tcpdf/examples/example_005.php diff --git a/vendors/tcpdf/examples/example_006.php b/app/vendors/tcpdf/examples/example_006.php similarity index 100% rename from vendors/tcpdf/examples/example_006.php rename to app/vendors/tcpdf/examples/example_006.php diff --git a/vendors/tcpdf/examples/example_007.php b/app/vendors/tcpdf/examples/example_007.php similarity index 100% rename from vendors/tcpdf/examples/example_007.php rename to app/vendors/tcpdf/examples/example_007.php diff --git a/vendors/tcpdf/examples/example_008.php b/app/vendors/tcpdf/examples/example_008.php similarity index 100% rename from vendors/tcpdf/examples/example_008.php rename to app/vendors/tcpdf/examples/example_008.php diff --git a/vendors/tcpdf/examples/example_009.php b/app/vendors/tcpdf/examples/example_009.php similarity index 100% rename from vendors/tcpdf/examples/example_009.php rename to app/vendors/tcpdf/examples/example_009.php diff --git a/vendors/tcpdf/examples/example_010.php b/app/vendors/tcpdf/examples/example_010.php similarity index 100% rename from vendors/tcpdf/examples/example_010.php rename to app/vendors/tcpdf/examples/example_010.php diff --git a/vendors/tcpdf/examples/example_011.php b/app/vendors/tcpdf/examples/example_011.php similarity index 100% rename from vendors/tcpdf/examples/example_011.php rename to app/vendors/tcpdf/examples/example_011.php diff --git a/vendors/tcpdf/examples/example_012.php b/app/vendors/tcpdf/examples/example_012.php similarity index 100% rename from vendors/tcpdf/examples/example_012.php rename to app/vendors/tcpdf/examples/example_012.php diff --git a/vendors/tcpdf/examples/example_013.php b/app/vendors/tcpdf/examples/example_013.php similarity index 100% rename from vendors/tcpdf/examples/example_013.php rename to app/vendors/tcpdf/examples/example_013.php diff --git a/vendors/tcpdf/examples/example_014.php b/app/vendors/tcpdf/examples/example_014.php similarity index 100% rename from vendors/tcpdf/examples/example_014.php rename to app/vendors/tcpdf/examples/example_014.php diff --git a/vendors/tcpdf/examples/example_015.php b/app/vendors/tcpdf/examples/example_015.php similarity index 100% rename from vendors/tcpdf/examples/example_015.php rename to app/vendors/tcpdf/examples/example_015.php diff --git a/vendors/tcpdf/examples/example_016.php b/app/vendors/tcpdf/examples/example_016.php similarity index 100% rename from vendors/tcpdf/examples/example_016.php rename to app/vendors/tcpdf/examples/example_016.php diff --git a/vendors/tcpdf/examples/example_017.php b/app/vendors/tcpdf/examples/example_017.php similarity index 100% rename from vendors/tcpdf/examples/example_017.php rename to app/vendors/tcpdf/examples/example_017.php diff --git a/vendors/tcpdf/examples/example_018.php b/app/vendors/tcpdf/examples/example_018.php similarity index 100% rename from vendors/tcpdf/examples/example_018.php rename to app/vendors/tcpdf/examples/example_018.php diff --git a/vendors/tcpdf/examples/example_019.php b/app/vendors/tcpdf/examples/example_019.php similarity index 100% rename from vendors/tcpdf/examples/example_019.php rename to app/vendors/tcpdf/examples/example_019.php diff --git a/vendors/tcpdf/examples/example_020.php b/app/vendors/tcpdf/examples/example_020.php similarity index 100% rename from vendors/tcpdf/examples/example_020.php rename to app/vendors/tcpdf/examples/example_020.php diff --git a/vendors/tcpdf/examples/example_021.php b/app/vendors/tcpdf/examples/example_021.php similarity index 100% rename from vendors/tcpdf/examples/example_021.php rename to app/vendors/tcpdf/examples/example_021.php diff --git a/vendors/tcpdf/examples/example_022.php b/app/vendors/tcpdf/examples/example_022.php similarity index 100% rename from vendors/tcpdf/examples/example_022.php rename to app/vendors/tcpdf/examples/example_022.php diff --git a/vendors/tcpdf/examples/example_023.php b/app/vendors/tcpdf/examples/example_023.php similarity index 100% rename from vendors/tcpdf/examples/example_023.php rename to app/vendors/tcpdf/examples/example_023.php diff --git a/vendors/tcpdf/examples/example_024.php b/app/vendors/tcpdf/examples/example_024.php similarity index 100% rename from vendors/tcpdf/examples/example_024.php rename to app/vendors/tcpdf/examples/example_024.php diff --git a/vendors/tcpdf/examples/example_025.php b/app/vendors/tcpdf/examples/example_025.php similarity index 100% rename from vendors/tcpdf/examples/example_025.php rename to app/vendors/tcpdf/examples/example_025.php diff --git a/vendors/tcpdf/examples/example_026.php b/app/vendors/tcpdf/examples/example_026.php similarity index 100% rename from vendors/tcpdf/examples/example_026.php rename to app/vendors/tcpdf/examples/example_026.php diff --git a/vendors/tcpdf/examples/example_027.php b/app/vendors/tcpdf/examples/example_027.php similarity index 100% rename from vendors/tcpdf/examples/example_027.php rename to app/vendors/tcpdf/examples/example_027.php diff --git a/vendors/tcpdf/examples/example_028.php b/app/vendors/tcpdf/examples/example_028.php similarity index 100% rename from vendors/tcpdf/examples/example_028.php rename to app/vendors/tcpdf/examples/example_028.php diff --git a/vendors/tcpdf/examples/example_029.php b/app/vendors/tcpdf/examples/example_029.php similarity index 100% rename from vendors/tcpdf/examples/example_029.php rename to app/vendors/tcpdf/examples/example_029.php diff --git a/vendors/tcpdf/examples/example_030.php b/app/vendors/tcpdf/examples/example_030.php similarity index 100% rename from vendors/tcpdf/examples/example_030.php rename to app/vendors/tcpdf/examples/example_030.php diff --git a/vendors/tcpdf/examples/example_031.php b/app/vendors/tcpdf/examples/example_031.php similarity index 100% rename from vendors/tcpdf/examples/example_031.php rename to app/vendors/tcpdf/examples/example_031.php diff --git a/vendors/tcpdf/examples/example_032.php b/app/vendors/tcpdf/examples/example_032.php similarity index 100% rename from vendors/tcpdf/examples/example_032.php rename to app/vendors/tcpdf/examples/example_032.php diff --git a/vendors/tcpdf/examples/example_033.php b/app/vendors/tcpdf/examples/example_033.php similarity index 100% rename from vendors/tcpdf/examples/example_033.php rename to app/vendors/tcpdf/examples/example_033.php diff --git a/vendors/tcpdf/examples/example_034.php b/app/vendors/tcpdf/examples/example_034.php similarity index 100% rename from vendors/tcpdf/examples/example_034.php rename to app/vendors/tcpdf/examples/example_034.php diff --git a/vendors/tcpdf/examples/example_035.php b/app/vendors/tcpdf/examples/example_035.php similarity index 100% rename from vendors/tcpdf/examples/example_035.php rename to app/vendors/tcpdf/examples/example_035.php diff --git a/vendors/tcpdf/examples/example_036.php b/app/vendors/tcpdf/examples/example_036.php similarity index 100% rename from vendors/tcpdf/examples/example_036.php rename to app/vendors/tcpdf/examples/example_036.php diff --git a/vendors/tcpdf/examples/example_037.php b/app/vendors/tcpdf/examples/example_037.php similarity index 100% rename from vendors/tcpdf/examples/example_037.php rename to app/vendors/tcpdf/examples/example_037.php diff --git a/vendors/tcpdf/examples/example_038.php b/app/vendors/tcpdf/examples/example_038.php similarity index 100% rename from vendors/tcpdf/examples/example_038.php rename to app/vendors/tcpdf/examples/example_038.php diff --git a/vendors/tcpdf/examples/example_039.php b/app/vendors/tcpdf/examples/example_039.php similarity index 100% rename from vendors/tcpdf/examples/example_039.php rename to app/vendors/tcpdf/examples/example_039.php diff --git a/vendors/tcpdf/examples/example_040.php b/app/vendors/tcpdf/examples/example_040.php similarity index 100% rename from vendors/tcpdf/examples/example_040.php rename to app/vendors/tcpdf/examples/example_040.php diff --git a/vendors/tcpdf/examples/example_041.php b/app/vendors/tcpdf/examples/example_041.php similarity index 100% rename from vendors/tcpdf/examples/example_041.php rename to app/vendors/tcpdf/examples/example_041.php diff --git a/vendors/tcpdf/examples/example_042.php b/app/vendors/tcpdf/examples/example_042.php similarity index 100% rename from vendors/tcpdf/examples/example_042.php rename to app/vendors/tcpdf/examples/example_042.php diff --git a/vendors/tcpdf/examples/example_043.php b/app/vendors/tcpdf/examples/example_043.php similarity index 100% rename from vendors/tcpdf/examples/example_043.php rename to app/vendors/tcpdf/examples/example_043.php diff --git a/vendors/tcpdf/examples/example_044.php b/app/vendors/tcpdf/examples/example_044.php similarity index 100% rename from vendors/tcpdf/examples/example_044.php rename to app/vendors/tcpdf/examples/example_044.php diff --git a/vendors/tcpdf/examples/example_045.php b/app/vendors/tcpdf/examples/example_045.php similarity index 100% rename from vendors/tcpdf/examples/example_045.php rename to app/vendors/tcpdf/examples/example_045.php diff --git a/vendors/tcpdf/examples/example_046.php b/app/vendors/tcpdf/examples/example_046.php similarity index 100% rename from vendors/tcpdf/examples/example_046.php rename to app/vendors/tcpdf/examples/example_046.php diff --git a/vendors/tcpdf/examples/example_047.php b/app/vendors/tcpdf/examples/example_047.php similarity index 100% rename from vendors/tcpdf/examples/example_047.php rename to app/vendors/tcpdf/examples/example_047.php diff --git a/vendors/tcpdf/examples/example_048.php b/app/vendors/tcpdf/examples/example_048.php similarity index 100% rename from vendors/tcpdf/examples/example_048.php rename to app/vendors/tcpdf/examples/example_048.php diff --git a/vendors/tcpdf/examples/example_049.php b/app/vendors/tcpdf/examples/example_049.php similarity index 100% rename from vendors/tcpdf/examples/example_049.php rename to app/vendors/tcpdf/examples/example_049.php diff --git a/vendors/tcpdf/examples/example_050.php b/app/vendors/tcpdf/examples/example_050.php similarity index 100% rename from vendors/tcpdf/examples/example_050.php rename to app/vendors/tcpdf/examples/example_050.php diff --git a/vendors/tcpdf/examples/example_051.php b/app/vendors/tcpdf/examples/example_051.php similarity index 100% rename from vendors/tcpdf/examples/example_051.php rename to app/vendors/tcpdf/examples/example_051.php diff --git a/vendors/tcpdf/examples/example_052.php b/app/vendors/tcpdf/examples/example_052.php similarity index 100% rename from vendors/tcpdf/examples/example_052.php rename to app/vendors/tcpdf/examples/example_052.php diff --git a/vendors/tcpdf/examples/example_053.php b/app/vendors/tcpdf/examples/example_053.php similarity index 100% rename from vendors/tcpdf/examples/example_053.php rename to app/vendors/tcpdf/examples/example_053.php diff --git a/vendors/tcpdf/examples/example_054.php b/app/vendors/tcpdf/examples/example_054.php similarity index 100% rename from vendors/tcpdf/examples/example_054.php rename to app/vendors/tcpdf/examples/example_054.php diff --git a/vendors/tcpdf/examples/example_055.php b/app/vendors/tcpdf/examples/example_055.php similarity index 100% rename from vendors/tcpdf/examples/example_055.php rename to app/vendors/tcpdf/examples/example_055.php diff --git a/vendors/tcpdf/examples/example_056.php b/app/vendors/tcpdf/examples/example_056.php similarity index 100% rename from vendors/tcpdf/examples/example_056.php rename to app/vendors/tcpdf/examples/example_056.php diff --git a/vendors/tcpdf/examples/example_057.php b/app/vendors/tcpdf/examples/example_057.php similarity index 100% rename from vendors/tcpdf/examples/example_057.php rename to app/vendors/tcpdf/examples/example_057.php diff --git a/vendors/tcpdf/examples/example_058.php b/app/vendors/tcpdf/examples/example_058.php similarity index 100% rename from vendors/tcpdf/examples/example_058.php rename to app/vendors/tcpdf/examples/example_058.php diff --git a/vendors/tcpdf/examples/example_059.php b/app/vendors/tcpdf/examples/example_059.php similarity index 100% rename from vendors/tcpdf/examples/example_059.php rename to app/vendors/tcpdf/examples/example_059.php diff --git a/vendors/tcpdf/examples/example_060.php b/app/vendors/tcpdf/examples/example_060.php similarity index 100% rename from vendors/tcpdf/examples/example_060.php rename to app/vendors/tcpdf/examples/example_060.php diff --git a/vendors/tcpdf/examples/example_061.php b/app/vendors/tcpdf/examples/example_061.php similarity index 100% rename from vendors/tcpdf/examples/example_061.php rename to app/vendors/tcpdf/examples/example_061.php diff --git a/vendors/tcpdf/examples/example_062.php b/app/vendors/tcpdf/examples/example_062.php similarity index 100% rename from vendors/tcpdf/examples/example_062.php rename to app/vendors/tcpdf/examples/example_062.php diff --git a/vendors/tcpdf/examples/example_063.php b/app/vendors/tcpdf/examples/example_063.php similarity index 100% rename from vendors/tcpdf/examples/example_063.php rename to app/vendors/tcpdf/examples/example_063.php diff --git a/vendors/tcpdf/examples/example_064.php b/app/vendors/tcpdf/examples/example_064.php similarity index 100% rename from vendors/tcpdf/examples/example_064.php rename to app/vendors/tcpdf/examples/example_064.php diff --git a/vendors/tcpdf/examples/index.php b/app/vendors/tcpdf/examples/index.php similarity index 100% rename from vendors/tcpdf/examples/index.php rename to app/vendors/tcpdf/examples/index.php diff --git a/vendors/tcpdf/fonts/README.TXT b/app/vendors/tcpdf/fonts/README.TXT similarity index 100% rename from vendors/tcpdf/fonts/README.TXT rename to app/vendors/tcpdf/fonts/README.TXT diff --git a/vendors/tcpdf/fonts/ZarBold.ctg.z b/app/vendors/tcpdf/fonts/ZarBold.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/ZarBold.ctg.z rename to app/vendors/tcpdf/fonts/ZarBold.ctg.z diff --git a/vendors/tcpdf/fonts/ZarBold.z b/app/vendors/tcpdf/fonts/ZarBold.z similarity index 100% rename from vendors/tcpdf/fonts/ZarBold.z rename to app/vendors/tcpdf/fonts/ZarBold.z diff --git a/vendors/tcpdf/fonts/almohanad.ctg.z b/app/vendors/tcpdf/fonts/almohanad.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/almohanad.ctg.z rename to app/vendors/tcpdf/fonts/almohanad.ctg.z diff --git a/vendors/tcpdf/fonts/almohanad.php b/app/vendors/tcpdf/fonts/almohanad.php similarity index 100% rename from vendors/tcpdf/fonts/almohanad.php rename to app/vendors/tcpdf/fonts/almohanad.php diff --git a/vendors/tcpdf/fonts/almohanad.z b/app/vendors/tcpdf/fonts/almohanad.z similarity index 100% rename from vendors/tcpdf/fonts/almohanad.z rename to app/vendors/tcpdf/fonts/almohanad.z diff --git a/vendors/tcpdf/fonts/arialunicid0.php b/app/vendors/tcpdf/fonts/arialunicid0.php similarity index 100% rename from vendors/tcpdf/fonts/arialunicid0.php rename to app/vendors/tcpdf/fonts/arialunicid0.php diff --git a/vendors/tcpdf/fonts/chinese.php b/app/vendors/tcpdf/fonts/chinese.php similarity index 100% rename from vendors/tcpdf/fonts/chinese.php rename to app/vendors/tcpdf/fonts/chinese.php diff --git a/vendors/tcpdf/fonts/courier.php b/app/vendors/tcpdf/fonts/courier.php similarity index 100% rename from vendors/tcpdf/fonts/courier.php rename to app/vendors/tcpdf/fonts/courier.php diff --git a/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/AUTHORS b/app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/AUTHORS similarity index 100% rename from vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/AUTHORS rename to app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/AUTHORS diff --git a/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/BUGS b/app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/BUGS similarity index 100% rename from vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/BUGS rename to app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/BUGS diff --git a/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE b/app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE similarity index 100% rename from vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE rename to app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/LICENSE diff --git a/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/NEWS b/app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/NEWS similarity index 100% rename from vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/NEWS rename to app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/NEWS diff --git a/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/README b/app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/README similarity index 100% rename from vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/README rename to app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/README diff --git a/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/langcover.txt b/app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/langcover.txt similarity index 100% rename from vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/langcover.txt rename to app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/langcover.txt diff --git a/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/status.txt b/app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/status.txt similarity index 100% rename from vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/status.txt rename to app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/status.txt diff --git a/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/unicover.txt b/app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/unicover.txt similarity index 100% rename from vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/unicover.txt rename to app/vendors/tcpdf/fonts/dejavu-fonts-ttf-2.33/unicover.txt diff --git a/vendors/tcpdf/fonts/dejavusans.ctg.z b/app/vendors/tcpdf/fonts/dejavusans.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusans.ctg.z rename to app/vendors/tcpdf/fonts/dejavusans.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusans.php b/app/vendors/tcpdf/fonts/dejavusans.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusans.php rename to app/vendors/tcpdf/fonts/dejavusans.php diff --git a/vendors/tcpdf/fonts/dejavusans.z b/app/vendors/tcpdf/fonts/dejavusans.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusans.z rename to app/vendors/tcpdf/fonts/dejavusans.z diff --git a/vendors/tcpdf/fonts/dejavusansb.ctg.z b/app/vendors/tcpdf/fonts/dejavusansb.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansb.ctg.z rename to app/vendors/tcpdf/fonts/dejavusansb.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusansb.php b/app/vendors/tcpdf/fonts/dejavusansb.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusansb.php rename to app/vendors/tcpdf/fonts/dejavusansb.php diff --git a/vendors/tcpdf/fonts/dejavusansb.z b/app/vendors/tcpdf/fonts/dejavusansb.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansb.z rename to app/vendors/tcpdf/fonts/dejavusansb.z diff --git a/vendors/tcpdf/fonts/dejavusansbi.ctg.z b/app/vendors/tcpdf/fonts/dejavusansbi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansbi.ctg.z rename to app/vendors/tcpdf/fonts/dejavusansbi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusansbi.php b/app/vendors/tcpdf/fonts/dejavusansbi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusansbi.php rename to app/vendors/tcpdf/fonts/dejavusansbi.php diff --git a/vendors/tcpdf/fonts/dejavusansbi.z b/app/vendors/tcpdf/fonts/dejavusansbi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansbi.z rename to app/vendors/tcpdf/fonts/dejavusansbi.z diff --git a/vendors/tcpdf/fonts/dejavusanscondensed.ctg.z b/app/vendors/tcpdf/fonts/dejavusanscondensed.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensed.ctg.z rename to app/vendors/tcpdf/fonts/dejavusanscondensed.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusanscondensed.php b/app/vendors/tcpdf/fonts/dejavusanscondensed.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensed.php rename to app/vendors/tcpdf/fonts/dejavusanscondensed.php diff --git a/vendors/tcpdf/fonts/dejavusanscondensed.z b/app/vendors/tcpdf/fonts/dejavusanscondensed.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensed.z rename to app/vendors/tcpdf/fonts/dejavusanscondensed.z diff --git a/vendors/tcpdf/fonts/dejavusanscondensedb.ctg.z b/app/vendors/tcpdf/fonts/dejavusanscondensedb.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedb.ctg.z rename to app/vendors/tcpdf/fonts/dejavusanscondensedb.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusanscondensedb.php b/app/vendors/tcpdf/fonts/dejavusanscondensedb.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedb.php rename to app/vendors/tcpdf/fonts/dejavusanscondensedb.php diff --git a/vendors/tcpdf/fonts/dejavusanscondensedb.z b/app/vendors/tcpdf/fonts/dejavusanscondensedb.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedb.z rename to app/vendors/tcpdf/fonts/dejavusanscondensedb.z diff --git a/vendors/tcpdf/fonts/dejavusanscondensedbi.ctg.z b/app/vendors/tcpdf/fonts/dejavusanscondensedbi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedbi.ctg.z rename to app/vendors/tcpdf/fonts/dejavusanscondensedbi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusanscondensedbi.php b/app/vendors/tcpdf/fonts/dejavusanscondensedbi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedbi.php rename to app/vendors/tcpdf/fonts/dejavusanscondensedbi.php diff --git a/vendors/tcpdf/fonts/dejavusanscondensedbi.z b/app/vendors/tcpdf/fonts/dejavusanscondensedbi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedbi.z rename to app/vendors/tcpdf/fonts/dejavusanscondensedbi.z diff --git a/vendors/tcpdf/fonts/dejavusanscondensedi.ctg.z b/app/vendors/tcpdf/fonts/dejavusanscondensedi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedi.ctg.z rename to app/vendors/tcpdf/fonts/dejavusanscondensedi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusanscondensedi.php b/app/vendors/tcpdf/fonts/dejavusanscondensedi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedi.php rename to app/vendors/tcpdf/fonts/dejavusanscondensedi.php diff --git a/vendors/tcpdf/fonts/dejavusanscondensedi.z b/app/vendors/tcpdf/fonts/dejavusanscondensedi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusanscondensedi.z rename to app/vendors/tcpdf/fonts/dejavusanscondensedi.z diff --git a/vendors/tcpdf/fonts/dejavusansextralight.ctg.z b/app/vendors/tcpdf/fonts/dejavusansextralight.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansextralight.ctg.z rename to app/vendors/tcpdf/fonts/dejavusansextralight.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusansextralight.php b/app/vendors/tcpdf/fonts/dejavusansextralight.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusansextralight.php rename to app/vendors/tcpdf/fonts/dejavusansextralight.php diff --git a/vendors/tcpdf/fonts/dejavusansextralight.z b/app/vendors/tcpdf/fonts/dejavusansextralight.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansextralight.z rename to app/vendors/tcpdf/fonts/dejavusansextralight.z diff --git a/vendors/tcpdf/fonts/dejavusansi.ctg.z b/app/vendors/tcpdf/fonts/dejavusansi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansi.ctg.z rename to app/vendors/tcpdf/fonts/dejavusansi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusansi.php b/app/vendors/tcpdf/fonts/dejavusansi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusansi.php rename to app/vendors/tcpdf/fonts/dejavusansi.php diff --git a/vendors/tcpdf/fonts/dejavusansi.z b/app/vendors/tcpdf/fonts/dejavusansi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansi.z rename to app/vendors/tcpdf/fonts/dejavusansi.z diff --git a/vendors/tcpdf/fonts/dejavusansmono.ctg.z b/app/vendors/tcpdf/fonts/dejavusansmono.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmono.ctg.z rename to app/vendors/tcpdf/fonts/dejavusansmono.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusansmono.php b/app/vendors/tcpdf/fonts/dejavusansmono.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmono.php rename to app/vendors/tcpdf/fonts/dejavusansmono.php diff --git a/vendors/tcpdf/fonts/dejavusansmono.z b/app/vendors/tcpdf/fonts/dejavusansmono.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmono.z rename to app/vendors/tcpdf/fonts/dejavusansmono.z diff --git a/vendors/tcpdf/fonts/dejavusansmonob.ctg.z b/app/vendors/tcpdf/fonts/dejavusansmonob.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonob.ctg.z rename to app/vendors/tcpdf/fonts/dejavusansmonob.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusansmonob.php b/app/vendors/tcpdf/fonts/dejavusansmonob.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonob.php rename to app/vendors/tcpdf/fonts/dejavusansmonob.php diff --git a/vendors/tcpdf/fonts/dejavusansmonob.z b/app/vendors/tcpdf/fonts/dejavusansmonob.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonob.z rename to app/vendors/tcpdf/fonts/dejavusansmonob.z diff --git a/vendors/tcpdf/fonts/dejavusansmonobi.ctg.z b/app/vendors/tcpdf/fonts/dejavusansmonobi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonobi.ctg.z rename to app/vendors/tcpdf/fonts/dejavusansmonobi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusansmonobi.php b/app/vendors/tcpdf/fonts/dejavusansmonobi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonobi.php rename to app/vendors/tcpdf/fonts/dejavusansmonobi.php diff --git a/vendors/tcpdf/fonts/dejavusansmonobi.z b/app/vendors/tcpdf/fonts/dejavusansmonobi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonobi.z rename to app/vendors/tcpdf/fonts/dejavusansmonobi.z diff --git a/vendors/tcpdf/fonts/dejavusansmonoi.ctg.z b/app/vendors/tcpdf/fonts/dejavusansmonoi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonoi.ctg.z rename to app/vendors/tcpdf/fonts/dejavusansmonoi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavusansmonoi.php b/app/vendors/tcpdf/fonts/dejavusansmonoi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonoi.php rename to app/vendors/tcpdf/fonts/dejavusansmonoi.php diff --git a/vendors/tcpdf/fonts/dejavusansmonoi.z b/app/vendors/tcpdf/fonts/dejavusansmonoi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavusansmonoi.z rename to app/vendors/tcpdf/fonts/dejavusansmonoi.z diff --git a/vendors/tcpdf/fonts/dejavuserif.ctg.z b/app/vendors/tcpdf/fonts/dejavuserif.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserif.ctg.z rename to app/vendors/tcpdf/fonts/dejavuserif.ctg.z diff --git a/vendors/tcpdf/fonts/dejavuserif.php b/app/vendors/tcpdf/fonts/dejavuserif.php similarity index 100% rename from vendors/tcpdf/fonts/dejavuserif.php rename to app/vendors/tcpdf/fonts/dejavuserif.php diff --git a/vendors/tcpdf/fonts/dejavuserif.z b/app/vendors/tcpdf/fonts/dejavuserif.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserif.z rename to app/vendors/tcpdf/fonts/dejavuserif.z diff --git a/vendors/tcpdf/fonts/dejavuserifb.ctg.z b/app/vendors/tcpdf/fonts/dejavuserifb.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifb.ctg.z rename to app/vendors/tcpdf/fonts/dejavuserifb.ctg.z diff --git a/vendors/tcpdf/fonts/dejavuserifb.php b/app/vendors/tcpdf/fonts/dejavuserifb.php similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifb.php rename to app/vendors/tcpdf/fonts/dejavuserifb.php diff --git a/vendors/tcpdf/fonts/dejavuserifb.z b/app/vendors/tcpdf/fonts/dejavuserifb.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifb.z rename to app/vendors/tcpdf/fonts/dejavuserifb.z diff --git a/vendors/tcpdf/fonts/dejavuserifbi.ctg.z b/app/vendors/tcpdf/fonts/dejavuserifbi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifbi.ctg.z rename to app/vendors/tcpdf/fonts/dejavuserifbi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavuserifbi.php b/app/vendors/tcpdf/fonts/dejavuserifbi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifbi.php rename to app/vendors/tcpdf/fonts/dejavuserifbi.php diff --git a/vendors/tcpdf/fonts/dejavuserifbi.z b/app/vendors/tcpdf/fonts/dejavuserifbi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifbi.z rename to app/vendors/tcpdf/fonts/dejavuserifbi.z diff --git a/vendors/tcpdf/fonts/dejavuserifcondensed.ctg.z b/app/vendors/tcpdf/fonts/dejavuserifcondensed.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensed.ctg.z rename to app/vendors/tcpdf/fonts/dejavuserifcondensed.ctg.z diff --git a/vendors/tcpdf/fonts/dejavuserifcondensed.php b/app/vendors/tcpdf/fonts/dejavuserifcondensed.php similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensed.php rename to app/vendors/tcpdf/fonts/dejavuserifcondensed.php diff --git a/vendors/tcpdf/fonts/dejavuserifcondensed.z b/app/vendors/tcpdf/fonts/dejavuserifcondensed.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensed.z rename to app/vendors/tcpdf/fonts/dejavuserifcondensed.z diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedb.ctg.z b/app/vendors/tcpdf/fonts/dejavuserifcondensedb.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedb.ctg.z rename to app/vendors/tcpdf/fonts/dejavuserifcondensedb.ctg.z diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedb.php b/app/vendors/tcpdf/fonts/dejavuserifcondensedb.php similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedb.php rename to app/vendors/tcpdf/fonts/dejavuserifcondensedb.php diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedb.z b/app/vendors/tcpdf/fonts/dejavuserifcondensedb.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedb.z rename to app/vendors/tcpdf/fonts/dejavuserifcondensedb.z diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedbi.ctg.z b/app/vendors/tcpdf/fonts/dejavuserifcondensedbi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedbi.ctg.z rename to app/vendors/tcpdf/fonts/dejavuserifcondensedbi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedbi.php b/app/vendors/tcpdf/fonts/dejavuserifcondensedbi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedbi.php rename to app/vendors/tcpdf/fonts/dejavuserifcondensedbi.php diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedbi.z b/app/vendors/tcpdf/fonts/dejavuserifcondensedbi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedbi.z rename to app/vendors/tcpdf/fonts/dejavuserifcondensedbi.z diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedi.ctg.z b/app/vendors/tcpdf/fonts/dejavuserifcondensedi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedi.ctg.z rename to app/vendors/tcpdf/fonts/dejavuserifcondensedi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedi.php b/app/vendors/tcpdf/fonts/dejavuserifcondensedi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedi.php rename to app/vendors/tcpdf/fonts/dejavuserifcondensedi.php diff --git a/vendors/tcpdf/fonts/dejavuserifcondensedi.z b/app/vendors/tcpdf/fonts/dejavuserifcondensedi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifcondensedi.z rename to app/vendors/tcpdf/fonts/dejavuserifcondensedi.z diff --git a/vendors/tcpdf/fonts/dejavuserifi.ctg.z b/app/vendors/tcpdf/fonts/dejavuserifi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifi.ctg.z rename to app/vendors/tcpdf/fonts/dejavuserifi.ctg.z diff --git a/vendors/tcpdf/fonts/dejavuserifi.php b/app/vendors/tcpdf/fonts/dejavuserifi.php similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifi.php rename to app/vendors/tcpdf/fonts/dejavuserifi.php diff --git a/vendors/tcpdf/fonts/dejavuserifi.z b/app/vendors/tcpdf/fonts/dejavuserifi.z similarity index 100% rename from vendors/tcpdf/fonts/dejavuserifi.z rename to app/vendors/tcpdf/fonts/dejavuserifi.z diff --git a/vendors/tcpdf/fonts/freefont-20100919/AUTHORS b/app/vendors/tcpdf/fonts/freefont-20100919/AUTHORS similarity index 100% rename from vendors/tcpdf/fonts/freefont-20100919/AUTHORS rename to app/vendors/tcpdf/fonts/freefont-20100919/AUTHORS diff --git a/vendors/tcpdf/fonts/freefont-20100919/COPYING b/app/vendors/tcpdf/fonts/freefont-20100919/COPYING similarity index 100% rename from vendors/tcpdf/fonts/freefont-20100919/COPYING rename to app/vendors/tcpdf/fonts/freefont-20100919/COPYING diff --git a/vendors/tcpdf/fonts/freefont-20100919/CREDITS b/app/vendors/tcpdf/fonts/freefont-20100919/CREDITS similarity index 100% rename from vendors/tcpdf/fonts/freefont-20100919/CREDITS rename to app/vendors/tcpdf/fonts/freefont-20100919/CREDITS diff --git a/vendors/tcpdf/fonts/freefont-20100919/ChangeLog b/app/vendors/tcpdf/fonts/freefont-20100919/ChangeLog similarity index 100% rename from vendors/tcpdf/fonts/freefont-20100919/ChangeLog rename to app/vendors/tcpdf/fonts/freefont-20100919/ChangeLog diff --git a/vendors/tcpdf/fonts/freefont-20100919/INSTALL b/app/vendors/tcpdf/fonts/freefont-20100919/INSTALL similarity index 100% rename from vendors/tcpdf/fonts/freefont-20100919/INSTALL rename to app/vendors/tcpdf/fonts/freefont-20100919/INSTALL diff --git a/vendors/tcpdf/fonts/freefont-20100919/README b/app/vendors/tcpdf/fonts/freefont-20100919/README similarity index 100% rename from vendors/tcpdf/fonts/freefont-20100919/README rename to app/vendors/tcpdf/fonts/freefont-20100919/README diff --git a/vendors/tcpdf/fonts/freemono.ctg.z b/app/vendors/tcpdf/fonts/freemono.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freemono.ctg.z rename to app/vendors/tcpdf/fonts/freemono.ctg.z diff --git a/vendors/tcpdf/fonts/freemono.php b/app/vendors/tcpdf/fonts/freemono.php similarity index 100% rename from vendors/tcpdf/fonts/freemono.php rename to app/vendors/tcpdf/fonts/freemono.php diff --git a/vendors/tcpdf/fonts/freemono.z b/app/vendors/tcpdf/fonts/freemono.z similarity index 100% rename from vendors/tcpdf/fonts/freemono.z rename to app/vendors/tcpdf/fonts/freemono.z diff --git a/vendors/tcpdf/fonts/freemonob.ctg.z b/app/vendors/tcpdf/fonts/freemonob.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freemonob.ctg.z rename to app/vendors/tcpdf/fonts/freemonob.ctg.z diff --git a/vendors/tcpdf/fonts/freemonob.php b/app/vendors/tcpdf/fonts/freemonob.php similarity index 100% rename from vendors/tcpdf/fonts/freemonob.php rename to app/vendors/tcpdf/fonts/freemonob.php diff --git a/vendors/tcpdf/fonts/freemonob.z b/app/vendors/tcpdf/fonts/freemonob.z similarity index 100% rename from vendors/tcpdf/fonts/freemonob.z rename to app/vendors/tcpdf/fonts/freemonob.z diff --git a/vendors/tcpdf/fonts/freemonobi.ctg.z b/app/vendors/tcpdf/fonts/freemonobi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freemonobi.ctg.z rename to app/vendors/tcpdf/fonts/freemonobi.ctg.z diff --git a/vendors/tcpdf/fonts/freemonobi.php b/app/vendors/tcpdf/fonts/freemonobi.php similarity index 100% rename from vendors/tcpdf/fonts/freemonobi.php rename to app/vendors/tcpdf/fonts/freemonobi.php diff --git a/vendors/tcpdf/fonts/freemonobi.z b/app/vendors/tcpdf/fonts/freemonobi.z similarity index 100% rename from vendors/tcpdf/fonts/freemonobi.z rename to app/vendors/tcpdf/fonts/freemonobi.z diff --git a/vendors/tcpdf/fonts/freemonoi.ctg.z b/app/vendors/tcpdf/fonts/freemonoi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freemonoi.ctg.z rename to app/vendors/tcpdf/fonts/freemonoi.ctg.z diff --git a/vendors/tcpdf/fonts/freemonoi.php b/app/vendors/tcpdf/fonts/freemonoi.php similarity index 100% rename from vendors/tcpdf/fonts/freemonoi.php rename to app/vendors/tcpdf/fonts/freemonoi.php diff --git a/vendors/tcpdf/fonts/freemonoi.z b/app/vendors/tcpdf/fonts/freemonoi.z similarity index 100% rename from vendors/tcpdf/fonts/freemonoi.z rename to app/vendors/tcpdf/fonts/freemonoi.z diff --git a/vendors/tcpdf/fonts/freesans.ctg.z b/app/vendors/tcpdf/fonts/freesans.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freesans.ctg.z rename to app/vendors/tcpdf/fonts/freesans.ctg.z diff --git a/vendors/tcpdf/fonts/freesans.php b/app/vendors/tcpdf/fonts/freesans.php similarity index 100% rename from vendors/tcpdf/fonts/freesans.php rename to app/vendors/tcpdf/fonts/freesans.php diff --git a/vendors/tcpdf/fonts/freesans.z b/app/vendors/tcpdf/fonts/freesans.z similarity index 100% rename from vendors/tcpdf/fonts/freesans.z rename to app/vendors/tcpdf/fonts/freesans.z diff --git a/vendors/tcpdf/fonts/freesansb.ctg.z b/app/vendors/tcpdf/fonts/freesansb.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freesansb.ctg.z rename to app/vendors/tcpdf/fonts/freesansb.ctg.z diff --git a/vendors/tcpdf/fonts/freesansb.php b/app/vendors/tcpdf/fonts/freesansb.php similarity index 100% rename from vendors/tcpdf/fonts/freesansb.php rename to app/vendors/tcpdf/fonts/freesansb.php diff --git a/vendors/tcpdf/fonts/freesansb.z b/app/vendors/tcpdf/fonts/freesansb.z similarity index 100% rename from vendors/tcpdf/fonts/freesansb.z rename to app/vendors/tcpdf/fonts/freesansb.z diff --git a/vendors/tcpdf/fonts/freesansbi.ctg.z b/app/vendors/tcpdf/fonts/freesansbi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freesansbi.ctg.z rename to app/vendors/tcpdf/fonts/freesansbi.ctg.z diff --git a/vendors/tcpdf/fonts/freesansbi.php b/app/vendors/tcpdf/fonts/freesansbi.php similarity index 100% rename from vendors/tcpdf/fonts/freesansbi.php rename to app/vendors/tcpdf/fonts/freesansbi.php diff --git a/vendors/tcpdf/fonts/freesansbi.z b/app/vendors/tcpdf/fonts/freesansbi.z similarity index 100% rename from vendors/tcpdf/fonts/freesansbi.z rename to app/vendors/tcpdf/fonts/freesansbi.z diff --git a/vendors/tcpdf/fonts/freesansi.ctg.z b/app/vendors/tcpdf/fonts/freesansi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freesansi.ctg.z rename to app/vendors/tcpdf/fonts/freesansi.ctg.z diff --git a/vendors/tcpdf/fonts/freesansi.php b/app/vendors/tcpdf/fonts/freesansi.php similarity index 100% rename from vendors/tcpdf/fonts/freesansi.php rename to app/vendors/tcpdf/fonts/freesansi.php diff --git a/vendors/tcpdf/fonts/freesansi.z b/app/vendors/tcpdf/fonts/freesansi.z similarity index 100% rename from vendors/tcpdf/fonts/freesansi.z rename to app/vendors/tcpdf/fonts/freesansi.z diff --git a/vendors/tcpdf/fonts/freeserif.ctg.z b/app/vendors/tcpdf/fonts/freeserif.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freeserif.ctg.z rename to app/vendors/tcpdf/fonts/freeserif.ctg.z diff --git a/vendors/tcpdf/fonts/freeserif.php b/app/vendors/tcpdf/fonts/freeserif.php similarity index 100% rename from vendors/tcpdf/fonts/freeserif.php rename to app/vendors/tcpdf/fonts/freeserif.php diff --git a/vendors/tcpdf/fonts/freeserif.z b/app/vendors/tcpdf/fonts/freeserif.z similarity index 100% rename from vendors/tcpdf/fonts/freeserif.z rename to app/vendors/tcpdf/fonts/freeserif.z diff --git a/vendors/tcpdf/fonts/freeserifb.ctg.z b/app/vendors/tcpdf/fonts/freeserifb.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freeserifb.ctg.z rename to app/vendors/tcpdf/fonts/freeserifb.ctg.z diff --git a/vendors/tcpdf/fonts/freeserifb.php b/app/vendors/tcpdf/fonts/freeserifb.php similarity index 100% rename from vendors/tcpdf/fonts/freeserifb.php rename to app/vendors/tcpdf/fonts/freeserifb.php diff --git a/vendors/tcpdf/fonts/freeserifb.z b/app/vendors/tcpdf/fonts/freeserifb.z similarity index 100% rename from vendors/tcpdf/fonts/freeserifb.z rename to app/vendors/tcpdf/fonts/freeserifb.z diff --git a/vendors/tcpdf/fonts/freeserifbi.ctg.z b/app/vendors/tcpdf/fonts/freeserifbi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freeserifbi.ctg.z rename to app/vendors/tcpdf/fonts/freeserifbi.ctg.z diff --git a/vendors/tcpdf/fonts/freeserifbi.php b/app/vendors/tcpdf/fonts/freeserifbi.php similarity index 100% rename from vendors/tcpdf/fonts/freeserifbi.php rename to app/vendors/tcpdf/fonts/freeserifbi.php diff --git a/vendors/tcpdf/fonts/freeserifbi.z b/app/vendors/tcpdf/fonts/freeserifbi.z similarity index 100% rename from vendors/tcpdf/fonts/freeserifbi.z rename to app/vendors/tcpdf/fonts/freeserifbi.z diff --git a/vendors/tcpdf/fonts/freeserifi.ctg.z b/app/vendors/tcpdf/fonts/freeserifi.ctg.z similarity index 100% rename from vendors/tcpdf/fonts/freeserifi.ctg.z rename to app/vendors/tcpdf/fonts/freeserifi.ctg.z diff --git a/vendors/tcpdf/fonts/freeserifi.php b/app/vendors/tcpdf/fonts/freeserifi.php similarity index 100% rename from vendors/tcpdf/fonts/freeserifi.php rename to app/vendors/tcpdf/fonts/freeserifi.php diff --git a/vendors/tcpdf/fonts/freeserifi.z b/app/vendors/tcpdf/fonts/freeserifi.z similarity index 100% rename from vendors/tcpdf/fonts/freeserifi.z rename to app/vendors/tcpdf/fonts/freeserifi.z diff --git a/vendors/tcpdf/fonts/helvetica.php b/app/vendors/tcpdf/fonts/helvetica.php similarity index 100% rename from vendors/tcpdf/fonts/helvetica.php rename to app/vendors/tcpdf/fonts/helvetica.php diff --git a/vendors/tcpdf/fonts/helveticab.php b/app/vendors/tcpdf/fonts/helveticab.php similarity index 100% rename from vendors/tcpdf/fonts/helveticab.php rename to app/vendors/tcpdf/fonts/helveticab.php diff --git a/vendors/tcpdf/fonts/helveticabi.php b/app/vendors/tcpdf/fonts/helveticabi.php similarity index 100% rename from vendors/tcpdf/fonts/helveticabi.php rename to app/vendors/tcpdf/fonts/helveticabi.php diff --git a/vendors/tcpdf/fonts/helveticai.php b/app/vendors/tcpdf/fonts/helveticai.php similarity index 100% rename from vendors/tcpdf/fonts/helveticai.php rename to app/vendors/tcpdf/fonts/helveticai.php diff --git a/vendors/tcpdf/fonts/hysmyeongjostdmedium.php b/app/vendors/tcpdf/fonts/hysmyeongjostdmedium.php similarity index 100% rename from vendors/tcpdf/fonts/hysmyeongjostdmedium.php rename to app/vendors/tcpdf/fonts/hysmyeongjostdmedium.php diff --git a/vendors/tcpdf/fonts/kozgopromedium.php b/app/vendors/tcpdf/fonts/kozgopromedium.php similarity index 100% rename from vendors/tcpdf/fonts/kozgopromedium.php rename to app/vendors/tcpdf/fonts/kozgopromedium.php diff --git a/vendors/tcpdf/fonts/kozminproregular.php b/app/vendors/tcpdf/fonts/kozminproregular.php similarity index 100% rename from vendors/tcpdf/fonts/kozminproregular.php rename to app/vendors/tcpdf/fonts/kozminproregular.php diff --git a/vendors/tcpdf/fonts/msungstdlight.php b/app/vendors/tcpdf/fonts/msungstdlight.php similarity index 100% rename from vendors/tcpdf/fonts/msungstdlight.php rename to app/vendors/tcpdf/fonts/msungstdlight.php diff --git a/vendors/tcpdf/fonts/stsongstdlight.php b/app/vendors/tcpdf/fonts/stsongstdlight.php similarity index 100% rename from vendors/tcpdf/fonts/stsongstdlight.php rename to app/vendors/tcpdf/fonts/stsongstdlight.php diff --git a/vendors/tcpdf/fonts/symbol.php b/app/vendors/tcpdf/fonts/symbol.php similarity index 100% rename from vendors/tcpdf/fonts/symbol.php rename to app/vendors/tcpdf/fonts/symbol.php diff --git a/vendors/tcpdf/fonts/times.php b/app/vendors/tcpdf/fonts/times.php similarity index 100% rename from vendors/tcpdf/fonts/times.php rename to app/vendors/tcpdf/fonts/times.php diff --git a/vendors/tcpdf/fonts/timesb.php b/app/vendors/tcpdf/fonts/timesb.php similarity index 100% rename from vendors/tcpdf/fonts/timesb.php rename to app/vendors/tcpdf/fonts/timesb.php diff --git a/vendors/tcpdf/fonts/timesbi.php b/app/vendors/tcpdf/fonts/timesbi.php similarity index 100% rename from vendors/tcpdf/fonts/timesbi.php rename to app/vendors/tcpdf/fonts/timesbi.php diff --git a/vendors/tcpdf/fonts/timesi.php b/app/vendors/tcpdf/fonts/timesi.php similarity index 100% rename from vendors/tcpdf/fonts/timesi.php rename to app/vendors/tcpdf/fonts/timesi.php diff --git a/vendors/tcpdf/fonts/uni2cid_ac15.php b/app/vendors/tcpdf/fonts/uni2cid_ac15.php similarity index 100% rename from vendors/tcpdf/fonts/uni2cid_ac15.php rename to app/vendors/tcpdf/fonts/uni2cid_ac15.php diff --git a/vendors/tcpdf/fonts/uni2cid_ag15.php b/app/vendors/tcpdf/fonts/uni2cid_ag15.php similarity index 100% rename from vendors/tcpdf/fonts/uni2cid_ag15.php rename to app/vendors/tcpdf/fonts/uni2cid_ag15.php diff --git a/vendors/tcpdf/fonts/uni2cid_aj16.php b/app/vendors/tcpdf/fonts/uni2cid_aj16.php similarity index 100% rename from vendors/tcpdf/fonts/uni2cid_aj16.php rename to app/vendors/tcpdf/fonts/uni2cid_aj16.php diff --git a/vendors/tcpdf/fonts/uni2cid_ak12.php b/app/vendors/tcpdf/fonts/uni2cid_ak12.php similarity index 100% rename from vendors/tcpdf/fonts/uni2cid_ak12.php rename to app/vendors/tcpdf/fonts/uni2cid_ak12.php diff --git a/vendors/tcpdf/fonts/utils/README.TXT b/app/vendors/tcpdf/fonts/utils/README.TXT similarity index 100% rename from vendors/tcpdf/fonts/utils/README.TXT rename to app/vendors/tcpdf/fonts/utils/README.TXT diff --git a/vendors/tcpdf/fonts/utils/enc/cp1250.map b/app/vendors/tcpdf/fonts/utils/enc/cp1250.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp1250.map rename to app/vendors/tcpdf/fonts/utils/enc/cp1250.map diff --git a/vendors/tcpdf/fonts/utils/enc/cp1251.map b/app/vendors/tcpdf/fonts/utils/enc/cp1251.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp1251.map rename to app/vendors/tcpdf/fonts/utils/enc/cp1251.map diff --git a/vendors/tcpdf/fonts/utils/enc/cp1252.map b/app/vendors/tcpdf/fonts/utils/enc/cp1252.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp1252.map rename to app/vendors/tcpdf/fonts/utils/enc/cp1252.map diff --git a/vendors/tcpdf/fonts/utils/enc/cp1253.map b/app/vendors/tcpdf/fonts/utils/enc/cp1253.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp1253.map rename to app/vendors/tcpdf/fonts/utils/enc/cp1253.map diff --git a/vendors/tcpdf/fonts/utils/enc/cp1254.map b/app/vendors/tcpdf/fonts/utils/enc/cp1254.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp1254.map rename to app/vendors/tcpdf/fonts/utils/enc/cp1254.map diff --git a/vendors/tcpdf/fonts/utils/enc/cp1255.map b/app/vendors/tcpdf/fonts/utils/enc/cp1255.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp1255.map rename to app/vendors/tcpdf/fonts/utils/enc/cp1255.map diff --git a/vendors/tcpdf/fonts/utils/enc/cp1257.map b/app/vendors/tcpdf/fonts/utils/enc/cp1257.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp1257.map rename to app/vendors/tcpdf/fonts/utils/enc/cp1257.map diff --git a/vendors/tcpdf/fonts/utils/enc/cp1258.map b/app/vendors/tcpdf/fonts/utils/enc/cp1258.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp1258.map rename to app/vendors/tcpdf/fonts/utils/enc/cp1258.map diff --git a/vendors/tcpdf/fonts/utils/enc/cp874.map b/app/vendors/tcpdf/fonts/utils/enc/cp874.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/cp874.map rename to app/vendors/tcpdf/fonts/utils/enc/cp874.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-1.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-1.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-1.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-1.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-11.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-11.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-11.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-11.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-15.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-15.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-15.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-15.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-16.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-16.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-16.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-16.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-2.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-2.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-2.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-2.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-4.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-4.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-4.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-4.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-5.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-5.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-5.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-5.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-7.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-7.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-7.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-7.map diff --git a/vendors/tcpdf/fonts/utils/enc/iso-8859-9.map b/app/vendors/tcpdf/fonts/utils/enc/iso-8859-9.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/iso-8859-9.map rename to app/vendors/tcpdf/fonts/utils/enc/iso-8859-9.map diff --git a/vendors/tcpdf/fonts/utils/enc/koi8-r.map b/app/vendors/tcpdf/fonts/utils/enc/koi8-r.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/koi8-r.map rename to app/vendors/tcpdf/fonts/utils/enc/koi8-r.map diff --git a/vendors/tcpdf/fonts/utils/enc/koi8-u.map b/app/vendors/tcpdf/fonts/utils/enc/koi8-u.map similarity index 100% rename from vendors/tcpdf/fonts/utils/enc/koi8-u.map rename to app/vendors/tcpdf/fonts/utils/enc/koi8-u.map diff --git a/vendors/tcpdf/fonts/utils/freetype6.dll b/app/vendors/tcpdf/fonts/utils/freetype6.dll similarity index 100% rename from vendors/tcpdf/fonts/utils/freetype6.dll rename to app/vendors/tcpdf/fonts/utils/freetype6.dll diff --git a/vendors/tcpdf/fonts/utils/makeallotfufm.php b/app/vendors/tcpdf/fonts/utils/makeallotfufm.php similarity index 100% rename from vendors/tcpdf/fonts/utils/makeallotfufm.php rename to app/vendors/tcpdf/fonts/utils/makeallotfufm.php diff --git a/vendors/tcpdf/fonts/utils/makeallttffonts.php b/app/vendors/tcpdf/fonts/utils/makeallttffonts.php similarity index 100% rename from vendors/tcpdf/fonts/utils/makeallttffonts.php rename to app/vendors/tcpdf/fonts/utils/makeallttffonts.php diff --git a/vendors/tcpdf/fonts/utils/makefont.php b/app/vendors/tcpdf/fonts/utils/makefont.php similarity index 100% rename from vendors/tcpdf/fonts/utils/makefont.php rename to app/vendors/tcpdf/fonts/utils/makefont.php diff --git a/vendors/tcpdf/fonts/utils/pfm2afm b/app/vendors/tcpdf/fonts/utils/pfm2afm similarity index 100% rename from vendors/tcpdf/fonts/utils/pfm2afm rename to app/vendors/tcpdf/fonts/utils/pfm2afm diff --git a/vendors/tcpdf/fonts/utils/pfm2afm.exe b/app/vendors/tcpdf/fonts/utils/pfm2afm.exe similarity index 100% rename from vendors/tcpdf/fonts/utils/pfm2afm.exe rename to app/vendors/tcpdf/fonts/utils/pfm2afm.exe diff --git a/vendors/tcpdf/fonts/utils/src/readme.txt b/app/vendors/tcpdf/fonts/utils/src/readme.txt similarity index 100% rename from vendors/tcpdf/fonts/utils/src/readme.txt rename to app/vendors/tcpdf/fonts/utils/src/readme.txt diff --git a/vendors/tcpdf/fonts/utils/ttf2ufm b/app/vendors/tcpdf/fonts/utils/ttf2ufm similarity index 100% rename from vendors/tcpdf/fonts/utils/ttf2ufm rename to app/vendors/tcpdf/fonts/utils/ttf2ufm diff --git a/vendors/tcpdf/fonts/utils/ttf2ufm.exe b/app/vendors/tcpdf/fonts/utils/ttf2ufm.exe similarity index 100% rename from vendors/tcpdf/fonts/utils/ttf2ufm.exe rename to app/vendors/tcpdf/fonts/utils/ttf2ufm.exe diff --git a/vendors/tcpdf/fonts/utils/zlib1.dll b/app/vendors/tcpdf/fonts/utils/zlib1.dll similarity index 100% rename from vendors/tcpdf/fonts/utils/zlib1.dll rename to app/vendors/tcpdf/fonts/utils/zlib1.dll diff --git a/vendors/tcpdf/fonts/zapfdingbats.php b/app/vendors/tcpdf/fonts/zapfdingbats.php similarity index 100% rename from vendors/tcpdf/fonts/zapfdingbats.php rename to app/vendors/tcpdf/fonts/zapfdingbats.php diff --git a/vendors/tcpdf/fonts/zarbold.php b/app/vendors/tcpdf/fonts/zarbold.php similarity index 100% rename from vendors/tcpdf/fonts/zarbold.php rename to app/vendors/tcpdf/fonts/zarbold.php diff --git a/vendors/tcpdf/htmlcolors.php b/app/vendors/tcpdf/htmlcolors.php similarity index 100% rename from vendors/tcpdf/htmlcolors.php rename to app/vendors/tcpdf/htmlcolors.php diff --git a/vendors/tcpdf/images/_blank.png b/app/vendors/tcpdf/images/_blank.png similarity index 100% rename from vendors/tcpdf/images/_blank.png rename to app/vendors/tcpdf/images/_blank.png diff --git a/vendors/tcpdf/images/alpha.png b/app/vendors/tcpdf/images/alpha.png similarity index 100% rename from vendors/tcpdf/images/alpha.png rename to app/vendors/tcpdf/images/alpha.png diff --git a/vendors/tcpdf/images/bug.eps b/app/vendors/tcpdf/images/bug.eps similarity index 100% rename from vendors/tcpdf/images/bug.eps rename to app/vendors/tcpdf/images/bug.eps diff --git a/vendors/tcpdf/images/cmcfooter.jpg b/app/vendors/tcpdf/images/cmcfooter.jpg similarity index 100% rename from vendors/tcpdf/images/cmcfooter.jpg rename to app/vendors/tcpdf/images/cmcfooter.jpg diff --git a/vendors/tcpdf/images/cmcfooter.png b/app/vendors/tcpdf/images/cmcfooter.png similarity index 100% rename from vendors/tcpdf/images/cmcfooter.png rename to app/vendors/tcpdf/images/cmcfooter.png diff --git a/vendors/tcpdf/images/cmcheader.jpg b/app/vendors/tcpdf/images/cmcheader.jpg similarity index 100% rename from vendors/tcpdf/images/cmcheader.jpg rename to app/vendors/tcpdf/images/cmcheader.jpg diff --git a/vendors/tcpdf/images/cmclogo.jpg b/app/vendors/tcpdf/images/cmclogo.jpg similarity index 100% rename from vendors/tcpdf/images/cmclogo.jpg rename to app/vendors/tcpdf/images/cmclogo.jpg diff --git a/vendors/tcpdf/images/cmclogosmall.jpg b/app/vendors/tcpdf/images/cmclogosmall.jpg similarity index 100% rename from vendors/tcpdf/images/cmclogosmall.jpg rename to app/vendors/tcpdf/images/cmclogosmall.jpg diff --git a/vendors/tcpdf/images/image_demo.jpg b/app/vendors/tcpdf/images/image_demo.jpg similarity index 100% rename from vendors/tcpdf/images/image_demo.jpg rename to app/vendors/tcpdf/images/image_demo.jpg diff --git a/vendors/tcpdf/images/image_with_alpha.png b/app/vendors/tcpdf/images/image_with_alpha.png similarity index 100% rename from vendors/tcpdf/images/image_with_alpha.png rename to app/vendors/tcpdf/images/image_with_alpha.png diff --git a/vendors/tcpdf/images/img.png b/app/vendors/tcpdf/images/img.png similarity index 100% rename from vendors/tcpdf/images/img.png rename to app/vendors/tcpdf/images/img.png diff --git a/vendors/tcpdf/images/logo_example.gif b/app/vendors/tcpdf/images/logo_example.gif similarity index 100% rename from vendors/tcpdf/images/logo_example.gif rename to app/vendors/tcpdf/images/logo_example.gif diff --git a/vendors/tcpdf/images/logo_example.jpg b/app/vendors/tcpdf/images/logo_example.jpg similarity index 100% rename from vendors/tcpdf/images/logo_example.jpg rename to app/vendors/tcpdf/images/logo_example.jpg diff --git a/vendors/tcpdf/images/logo_example.png b/app/vendors/tcpdf/images/logo_example.png similarity index 100% rename from vendors/tcpdf/images/logo_example.png rename to app/vendors/tcpdf/images/logo_example.png diff --git a/vendors/tcpdf/images/pelican.ai b/app/vendors/tcpdf/images/pelican.ai similarity index 100% rename from vendors/tcpdf/images/pelican.ai rename to app/vendors/tcpdf/images/pelican.ai diff --git a/vendors/tcpdf/images/tcpdf_cell.png b/app/vendors/tcpdf/images/tcpdf_cell.png similarity index 100% rename from vendors/tcpdf/images/tcpdf_cell.png rename to app/vendors/tcpdf/images/tcpdf_cell.png diff --git a/vendors/tcpdf/images/tcpdf_logo.jpg b/app/vendors/tcpdf/images/tcpdf_logo.jpg similarity index 100% rename from vendors/tcpdf/images/tcpdf_logo.jpg rename to app/vendors/tcpdf/images/tcpdf_logo.jpg diff --git a/vendors/tcpdf/images/tcpdf_signature.png b/app/vendors/tcpdf/images/tcpdf_signature.png similarity index 100% rename from vendors/tcpdf/images/tcpdf_signature.png rename to app/vendors/tcpdf/images/tcpdf_signature.png diff --git a/vendors/tcpdf/images/testsvg.svg b/app/vendors/tcpdf/images/testsvg.svg similarity index 100% rename from vendors/tcpdf/images/testsvg.svg rename to app/vendors/tcpdf/images/testsvg.svg diff --git a/vendors/tcpdf/images/tiger.ai b/app/vendors/tcpdf/images/tiger.ai similarity index 100% rename from vendors/tcpdf/images/tiger.ai rename to app/vendors/tcpdf/images/tiger.ai diff --git a/vendors/tcpdf/images/tux.svg b/app/vendors/tcpdf/images/tux.svg similarity index 100% rename from vendors/tcpdf/images/tux.svg rename to app/vendors/tcpdf/images/tux.svg diff --git a/vendors/tcpdf/pdf417.php b/app/vendors/tcpdf/pdf417.php similarity index 100% rename from vendors/tcpdf/pdf417.php rename to app/vendors/tcpdf/pdf417.php diff --git a/vendors/tcpdf/qrcode.php b/app/vendors/tcpdf/qrcode.php similarity index 100% rename from vendors/tcpdf/qrcode.php rename to app/vendors/tcpdf/qrcode.php diff --git a/vendors/tcpdf/spotcolors.php b/app/vendors/tcpdf/spotcolors.php similarity index 100% rename from vendors/tcpdf/spotcolors.php rename to app/vendors/tcpdf/spotcolors.php diff --git a/vendors/tcpdf/tcpdf.crt b/app/vendors/tcpdf/tcpdf.crt similarity index 100% rename from vendors/tcpdf/tcpdf.crt rename to app/vendors/tcpdf/tcpdf.crt diff --git a/vendors/tcpdf/tcpdf.fdf b/app/vendors/tcpdf/tcpdf.fdf similarity index 100% rename from vendors/tcpdf/tcpdf.fdf rename to app/vendors/tcpdf/tcpdf.fdf diff --git a/vendors/tcpdf/tcpdf.p12 b/app/vendors/tcpdf/tcpdf.p12 similarity index 100% rename from vendors/tcpdf/tcpdf.p12 rename to app/vendors/tcpdf/tcpdf.p12 diff --git a/vendors/tcpdf/tcpdf.php b/app/vendors/tcpdf/tcpdf.php similarity index 100% rename from vendors/tcpdf/tcpdf.php rename to app/vendors/tcpdf/tcpdf.php diff --git a/vendors/tcpdf/unicode_data.php b/app/vendors/tcpdf/unicode_data.php similarity index 100% rename from vendors/tcpdf/unicode_data.php rename to app/vendors/tcpdf/unicode_data.php diff --git a/vendors/xfpdi.php b/app/vendors/xfpdi.php similarity index 100% rename from vendors/xfpdi.php rename to app/vendors/xfpdi.php diff --git a/vendors/xtcpdf.php b/app/vendors/xtcpdf.php similarity index 100% rename from vendors/xtcpdf.php rename to app/vendors/xtcpdf.php diff --git a/views/addresses/add.ctp b/app/views/addresses/add.ctp similarity index 100% rename from views/addresses/add.ctp rename to app/views/addresses/add.ctp diff --git a/views/addresses/add_another.ctp b/app/views/addresses/add_another.ctp similarity index 100% rename from views/addresses/add_another.ctp rename to app/views/addresses/add_another.ctp diff --git a/views/addresses/customer_addresses.ctp b/app/views/addresses/customer_addresses.ctp similarity index 100% rename from views/addresses/customer_addresses.ctp rename to app/views/addresses/customer_addresses.ctp diff --git a/views/addresses/edit.ctp b/app/views/addresses/edit.ctp similarity index 100% rename from views/addresses/edit.ctp rename to app/views/addresses/edit.ctp diff --git a/views/addresses/index.ctp b/app/views/addresses/index.ctp similarity index 100% rename from views/addresses/index.ctp rename to app/views/addresses/index.ctp diff --git a/views/addresses/remove_another.ctp b/app/views/addresses/remove_another.ctp similarity index 100% rename from views/addresses/remove_another.ctp rename to app/views/addresses/remove_another.ctp diff --git a/views/addresses/view.ctp b/app/views/addresses/view.ctp similarity index 100% rename from views/addresses/view.ctp rename to app/views/addresses/view.ctp diff --git a/views/attachments/add.ctp b/app/views/attachments/add.ctp similarity index 100% rename from views/attachments/add.ctp rename to app/views/attachments/add.ctp diff --git a/views/attachments/edit.ctp b/app/views/attachments/edit.ctp similarity index 100% rename from views/attachments/edit.ctp rename to app/views/attachments/edit.ctp diff --git a/views/attachments/index.ctp b/app/views/attachments/index.ctp similarity index 100% rename from views/attachments/index.ctp rename to app/views/attachments/index.ctp diff --git a/views/attachments/view.ctp b/app/views/attachments/view.ctp similarity index 100% rename from views/attachments/view.ctp rename to app/views/attachments/view.ctp diff --git a/views/boxes/add.ctp b/app/views/boxes/add.ctp similarity index 100% rename from views/boxes/add.ctp rename to app/views/boxes/add.ctp diff --git a/views/boxes/edit.ctp b/app/views/boxes/edit.ctp similarity index 100% rename from views/boxes/edit.ctp rename to app/views/boxes/edit.ctp diff --git a/views/boxes/index.ctp b/app/views/boxes/index.ctp similarity index 100% rename from views/boxes/index.ctp rename to app/views/boxes/index.ctp diff --git a/views/boxes/view.ctp b/app/views/boxes/view.ctp similarity index 100% rename from views/boxes/view.ctp rename to app/views/boxes/view.ctp diff --git a/views/contact_categories/add.ctp b/app/views/contact_categories/add.ctp similarity index 100% rename from views/contact_categories/add.ctp rename to app/views/contact_categories/add.ctp diff --git a/views/contact_categories/edit.ctp b/app/views/contact_categories/edit.ctp similarity index 100% rename from views/contact_categories/edit.ctp rename to app/views/contact_categories/edit.ctp diff --git a/views/contact_categories/index.ctp b/app/views/contact_categories/index.ctp similarity index 100% rename from views/contact_categories/index.ctp rename to app/views/contact_categories/index.ctp diff --git a/views/contact_categories/view.ctp b/app/views/contact_categories/view.ctp similarity index 100% rename from views/contact_categories/view.ctp rename to app/views/contact_categories/view.ctp diff --git a/views/costings/add.ctp b/app/views/costings/add.ctp similarity index 100% rename from views/costings/add.ctp rename to app/views/costings/add.ctp diff --git a/views/costings/edit.ctp b/app/views/costings/edit.ctp similarity index 100% rename from views/costings/edit.ctp rename to app/views/costings/edit.ctp diff --git a/views/costings/index.ctp b/app/views/costings/index.ctp similarity index 100% rename from views/costings/index.ctp rename to app/views/costings/index.ctp diff --git a/views/costings/view.ctp b/app/views/costings/view.ctp similarity index 100% rename from views/costings/view.ctp rename to app/views/costings/view.ctp diff --git a/views/countries/add.ctp b/app/views/countries/add.ctp similarity index 100% rename from views/countries/add.ctp rename to app/views/countries/add.ctp diff --git a/views/countries/complete_country.ctp b/app/views/countries/complete_country.ctp similarity index 100% rename from views/countries/complete_country.ctp rename to app/views/countries/complete_country.ctp diff --git a/views/countries/edit.ctp b/app/views/countries/edit.ctp similarity index 100% rename from views/countries/edit.ctp rename to app/views/countries/edit.ctp diff --git a/views/countries/index.ctp b/app/views/countries/index.ctp similarity index 100% rename from views/countries/index.ctp rename to app/views/countries/index.ctp diff --git a/views/countries/view.ctp b/app/views/countries/view.ctp similarity index 100% rename from views/countries/view.ctp rename to app/views/countries/view.ctp diff --git a/views/currencies/add.ctp b/app/views/currencies/add.ctp similarity index 100% rename from views/currencies/add.ctp rename to app/views/currencies/add.ctp diff --git a/views/currencies/edit.ctp b/app/views/currencies/edit.ctp similarity index 100% rename from views/currencies/edit.ctp rename to app/views/currencies/edit.ctp diff --git a/views/currencies/index.ctp b/app/views/currencies/index.ctp similarity index 100% rename from views/currencies/index.ctp rename to app/views/currencies/index.ctp diff --git a/views/currencies/jsonlist.ctp b/app/views/currencies/jsonlist.ctp similarity index 100% rename from views/currencies/jsonlist.ctp rename to app/views/currencies/jsonlist.ctp diff --git a/views/currencies/view.ctp b/app/views/currencies/view.ctp similarity index 100% rename from views/currencies/view.ctp rename to app/views/currencies/view.ctp diff --git a/views/customer_categories/add.ctp b/app/views/customer_categories/add.ctp similarity index 100% rename from views/customer_categories/add.ctp rename to app/views/customer_categories/add.ctp diff --git a/views/customer_categories/edit.ctp b/app/views/customer_categories/edit.ctp similarity index 100% rename from views/customer_categories/edit.ctp rename to app/views/customer_categories/edit.ctp diff --git a/views/customer_categories/index.ctp b/app/views/customer_categories/index.ctp similarity index 100% rename from views/customer_categories/index.ctp rename to app/views/customer_categories/index.ctp diff --git a/views/customer_categories/view.ctp b/app/views/customer_categories/view.ctp similarity index 100% rename from views/customer_categories/view.ctp rename to app/views/customer_categories/view.ctp diff --git a/views/customers/add.ctp b/app/views/customers/add.ctp similarity index 100% rename from views/customers/add.ctp rename to app/views/customers/add.ctp diff --git a/views/customers/complete_customer.ctp b/app/views/customers/complete_customer.ctp similarity index 100% rename from views/customers/complete_customer.ctp rename to app/views/customers/complete_customer.ctp diff --git a/views/customers/csv.ctp b/app/views/customers/csv.ctp similarity index 100% rename from views/customers/csv.ctp rename to app/views/customers/csv.ctp diff --git a/views/customers/customer_contacts.ctp b/app/views/customers/customer_contacts.ctp similarity index 100% rename from views/customers/customer_contacts.ctp rename to app/views/customers/customer_contacts.ctp diff --git a/views/customers/edit.ctp b/app/views/customers/edit.ctp similarity index 100% rename from views/customers/edit.ctp rename to app/views/customers/edit.ctp diff --git a/views/customers/index.ctp b/app/views/customers/index.ctp similarity index 100% rename from views/customers/index.ctp rename to app/views/customers/index.ctp diff --git a/views/customers/similar_customers.ctp b/app/views/customers/similar_customers.ctp similarity index 100% rename from views/customers/similar_customers.ctp rename to app/views/customers/similar_customers.ctp diff --git a/views/customers/tagindustries.ctp b/app/views/customers/tagindustries.ctp similarity index 100% rename from views/customers/tagindustries.ctp rename to app/views/customers/tagindustries.ctp diff --git a/views/customers/view.ctp b/app/views/customers/view.ctp similarity index 100% rename from views/customers/view.ctp rename to app/views/customers/view.ctp diff --git a/views/documents/add.ctp b/app/views/documents/add.ctp similarity index 100% rename from views/documents/add.ctp rename to app/views/documents/add.ctp diff --git a/views/documents/ajax_edit.ctp b/app/views/documents/ajax_edit.ctp similarity index 100% rename from views/documents/ajax_edit.ctp rename to app/views/documents/ajax_edit.ctp diff --git a/views/documents/convert_to_oa.ctp b/app/views/documents/convert_to_oa.ctp similarity index 100% rename from views/documents/convert_to_oa.ctp rename to app/views/documents/convert_to_oa.ctp diff --git a/views/documents/email_pdf.ctp b/app/views/documents/email_pdf.ctp similarity index 100% rename from views/documents/email_pdf.ctp rename to app/views/documents/email_pdf.ctp diff --git a/views/documents/generate_first_page.ctp b/app/views/documents/generate_first_page.ctp similarity index 100% rename from views/documents/generate_first_page.ctp rename to app/views/documents/generate_first_page.ctp diff --git a/views/documents/get_attachments.ctp b/app/views/documents/get_attachments.ctp similarity index 100% rename from views/documents/get_attachments.ctp rename to app/views/documents/get_attachments.ctp diff --git a/views/documents/get_attachments_by_principle.ctp b/app/views/documents/get_attachments_by_principle.ctp similarity index 100% rename from views/documents/get_attachments_by_principle.ctp rename to app/views/documents/get_attachments_by_principle.ctp diff --git a/views/documents/get_product_details.ctp b/app/views/documents/get_product_details.ctp similarity index 100% rename from views/documents/get_product_details.ctp rename to app/views/documents/get_product_details.ctp diff --git a/views/documents/get_products.ctp b/app/views/documents/get_products.ctp similarity index 100% rename from views/documents/get_products.ctp rename to app/views/documents/get_products.ctp diff --git a/views/documents/index.ctp b/app/views/documents/index.ctp similarity index 100% rename from views/documents/index.ctp rename to app/views/documents/index.ctp diff --git a/views/documents/pdf_invoice.ctp b/app/views/documents/pdf_invoice.ctp similarity index 100% rename from views/documents/pdf_invoice.ctp rename to app/views/documents/pdf_invoice.ctp diff --git a/views/documents/pdf_orderack.ctp b/app/views/documents/pdf_orderack.ctp similarity index 100% rename from views/documents/pdf_orderack.ctp rename to app/views/documents/pdf_orderack.ctp diff --git a/views/documents/pdf_quote.ctp b/app/views/documents/pdf_quote.ctp similarity index 100% rename from views/documents/pdf_quote.ctp rename to app/views/documents/pdf_quote.ctp diff --git a/views/documents/remove_attachments.ctp b/app/views/documents/remove_attachments.ctp similarity index 100% rename from views/documents/remove_attachments.ctp rename to app/views/documents/remove_attachments.ctp diff --git a/views/documents/revise.ctp b/app/views/documents/revise.ctp similarity index 100% rename from views/documents/revise.ctp rename to app/views/documents/revise.ctp diff --git a/views/documents/save_attachments.ctp b/app/views/documents/save_attachments.ctp similarity index 100% rename from views/documents/save_attachments.ctp rename to app/views/documents/save_attachments.ctp diff --git a/views/documents/view.ctp b/app/views/documents/view.ctp similarity index 100% rename from views/documents/view.ctp rename to app/views/documents/view.ctp diff --git a/views/documents/viewDEFAULT.ctp b/app/views/documents/viewDEFAULT.ctp similarity index 100% rename from views/documents/viewDEFAULT.ctp rename to app/views/documents/viewDEFAULT.ctp diff --git a/views/elements/abn.ctp b/app/views/elements/abn.ctp similarity index 100% rename from views/elements/abn.ctp rename to app/views/elements/abn.ctp diff --git a/views/elements/add_attachment.ctp b/app/views/elements/add_attachment.ctp similarity index 100% rename from views/elements/add_attachment.ctp rename to app/views/elements/add_attachment.ctp diff --git a/views/elements/add_edit_line_item.ctp b/app/views/elements/add_edit_line_item.ctp similarity index 100% rename from views/elements/add_edit_line_item.ctp rename to app/views/elements/add_edit_line_item.ctp diff --git a/views/elements/add_edit_shipment.ctp b/app/views/elements/add_edit_shipment.ctp similarity index 100% rename from views/elements/add_edit_shipment.ctp rename to app/views/elements/add_edit_shipment.ctp diff --git a/views/elements/address_table.ctp b/app/views/elements/address_table.ctp similarity index 100% rename from views/elements/address_table.ctp rename to app/views/elements/address_table.ctp diff --git a/views/elements/booleanTick.ctp b/app/views/elements/booleanTick.ctp similarity index 100% rename from views/elements/booleanTick.ctp rename to app/views/elements/booleanTick.ctp diff --git a/views/elements/document_invoice_view.ctp b/app/views/elements/document_invoice_view.ctp similarity index 100% rename from views/elements/document_invoice_view.ctp rename to app/views/elements/document_invoice_view.ctp diff --git a/views/elements/document_orderack_view.ctp b/app/views/elements/document_orderack_view.ctp similarity index 100% rename from views/elements/document_orderack_view.ctp rename to app/views/elements/document_orderack_view.ctp diff --git a/views/elements/document_quote_view.ctp b/app/views/elements/document_quote_view.ctp similarity index 100% rename from views/elements/document_quote_view.ctp rename to app/views/elements/document_quote_view.ctp diff --git a/views/elements/email/html/email_pdf.ctp b/app/views/elements/email/html/email_pdf.ctp similarity index 100% rename from views/elements/email/html/email_pdf.ctp rename to app/views/elements/email/html/email_pdf.ctp diff --git a/views/elements/email/html/enquiry_added.ctp b/app/views/elements/email/html/enquiry_added.ctp similarity index 100% rename from views/elements/email/html/enquiry_added.ctp rename to app/views/elements/email/html/enquiry_added.ctp diff --git a/views/elements/email/html/invoice_email.ctp b/app/views/elements/email/html/invoice_email.ctp similarity index 100% rename from views/elements/email/html/invoice_email.ctp rename to app/views/elements/email/html/invoice_email.ctp diff --git a/views/elements/email/html/orderAck_email.ctp b/app/views/elements/email/html/orderAck_email.ctp similarity index 100% rename from views/elements/email/html/orderAck_email.ctp rename to app/views/elements/email/html/orderAck_email.ctp diff --git a/views/elements/email/html/purchaseOrder_email.ctp b/app/views/elements/email/html/purchaseOrder_email.ctp similarity index 100% rename from views/elements/email/html/purchaseOrder_email.ctp rename to app/views/elements/email/html/purchaseOrder_email.ctp diff --git a/views/elements/email/html/quote_email.ctp b/app/views/elements/email/html/quote_email.ctp similarity index 100% rename from views/elements/email/html/quote_email.ctp rename to app/views/elements/email/html/quote_email.ctp diff --git a/views/elements/email/html/signature.ctp b/app/views/elements/email/html/signature.ctp similarity index 100% rename from views/elements/email/html/signature.ctp rename to app/views/elements/email/html/signature.ctp diff --git a/views/elements/email/text/enquiry_added.ctp b/app/views/elements/email/text/enquiry_added.ctp similarity index 100% rename from views/elements/email/text/enquiry_added.ctp rename to app/views/elements/email/text/enquiry_added.ctp diff --git a/views/elements/email/text/invoice_email.ctp b/app/views/elements/email/text/invoice_email.ctp similarity index 100% rename from views/elements/email/text/invoice_email.ctp rename to app/views/elements/email/text/invoice_email.ctp diff --git a/views/elements/email/text/orderAck_email.ctp b/app/views/elements/email/text/orderAck_email.ctp similarity index 100% rename from views/elements/email/text/orderAck_email.ctp rename to app/views/elements/email/text/orderAck_email.ctp diff --git a/views/elements/email/text/purchaseOrder_email.ctp b/app/views/elements/email/text/purchaseOrder_email.ctp similarity index 100% rename from views/elements/email/text/purchaseOrder_email.ctp rename to app/views/elements/email/text/purchaseOrder_email.ctp diff --git a/views/elements/email/text/quote_email.ctp b/app/views/elements/email/text/quote_email.ctp similarity index 100% rename from views/elements/email/text/quote_email.ctp rename to app/views/elements/email/text/quote_email.ctp diff --git a/views/elements/email_attachments.ctp b/app/views/elements/email_attachments.ctp similarity index 100% rename from views/elements/email_attachments.ctp rename to app/views/elements/email_attachments.ctp diff --git a/views/elements/email_table.ctp b/app/views/elements/email_table.ctp similarity index 100% rename from views/elements/email_table.ctp rename to app/views/elements/email_table.ctp diff --git a/views/elements/email_table_ajax.ctp b/app/views/elements/email_table_ajax.ctp similarity index 100% rename from views/elements/email_table_ajax.ctp rename to app/views/elements/email_table_ajax.ctp diff --git a/views/helpers/empty b/app/views/elements/empty similarity index 100% rename from views/helpers/empty rename to app/views/elements/empty diff --git a/views/elements/enquiry_file_table.ctp b/app/views/elements/enquiry_file_table.ctp similarity index 100% rename from views/elements/enquiry_file_table.ctp rename to app/views/elements/enquiry_file_table.ctp diff --git a/views/elements/enquiry_table.ctp b/app/views/elements/enquiry_table.ctp similarity index 100% rename from views/elements/enquiry_table.ctp rename to app/views/elements/enquiry_table.ctp diff --git a/views/elements/enquiry_table.ctp~ b/app/views/elements/enquiry_table.ctp~ similarity index 100% rename from views/elements/enquiry_table.ctp~ rename to app/views/elements/enquiry_table.ctp~ diff --git a/views/elements/enquiry_tableworkin.ctp b/app/views/elements/enquiry_tableworkin.ctp similarity index 100% rename from views/elements/enquiry_tableworkin.ctp rename to app/views/elements/enquiry_tableworkin.ctp diff --git a/views/elements/format_address.ctp b/app/views/elements/format_address.ctp similarity index 100% rename from views/elements/format_address.ctp rename to app/views/elements/format_address.ctp diff --git a/views/elements/info_table.ctp b/app/views/elements/info_table.ctp similarity index 100% rename from views/elements/info_table.ctp rename to app/views/elements/info_table.ctp diff --git a/views/elements/invoice_table.ctp b/app/views/elements/invoice_table.ctp similarity index 100% rename from views/elements/invoice_table.ctp rename to app/views/elements/invoice_table.ctp diff --git a/views/elements/isEmptyDate.ctp b/app/views/elements/isEmptyDate.ctp similarity index 100% rename from views/elements/isEmptyDate.ctp rename to app/views/elements/isEmptyDate.ctp diff --git a/views/elements/issue_priority_select.ctp b/app/views/elements/issue_priority_select.ctp similarity index 100% rename from views/elements/issue_priority_select.ctp rename to app/views/elements/issue_priority_select.ctp diff --git a/views/elements/job_table.ctp b/app/views/elements/job_table.ctp similarity index 100% rename from views/elements/job_table.ctp rename to app/views/elements/job_table.ctp diff --git a/views/elements/line_items_table.ctp b/app/views/elements/line_items_table.ctp similarity index 100% rename from views/elements/line_items_table.ctp rename to app/views/elements/line_items_table.ctp diff --git a/views/elements/line_items_table_with_shipping.ctp b/app/views/elements/line_items_table_with_shipping.ctp similarity index 100% rename from views/elements/line_items_table_with_shipping.ctp rename to app/views/elements/line_items_table_with_shipping.ctp diff --git a/views/elements/payment_terms_box.ctp b/app/views/elements/payment_terms_box.ctp similarity index 100% rename from views/elements/payment_terms_box.ctp rename to app/views/elements/payment_terms_box.ctp diff --git a/views/elements/pdf_created_message.ctp b/app/views/elements/pdf_created_message.ctp similarity index 100% rename from views/elements/pdf_created_message.ctp rename to app/views/elements/pdf_created_message.ctp diff --git a/views/elements/pdf_document_data_payment.ctp b/app/views/elements/pdf_document_data_payment.ctp similarity index 100% rename from views/elements/pdf_document_data_payment.ctp rename to app/views/elements/pdf_document_data_payment.ctp diff --git a/views/elements/pdf_output.ctp b/app/views/elements/pdf_output.ctp similarity index 100% rename from views/elements/pdf_output.ctp rename to app/views/elements/pdf_output.ctp diff --git a/views/elements/pdf_shipping_billing_box.ctp b/app/views/elements/pdf_shipping_billing_box.ctp similarity index 100% rename from views/elements/pdf_shipping_billing_box.ctp rename to app/views/elements/pdf_shipping_billing_box.ctp diff --git a/views/elements/pdf_shipping_details.ctp b/app/views/elements/pdf_shipping_details.ctp similarity index 100% rename from views/elements/pdf_shipping_details.ctp rename to app/views/elements/pdf_shipping_details.ctp diff --git a/views/elements/principle_address_table.ctp b/app/views/elements/principle_address_table.ctp similarity index 100% rename from views/elements/principle_address_table.ctp rename to app/views/elements/principle_address_table.ctp diff --git a/views/elements/principle_contacts.ctp b/app/views/elements/principle_contacts.ctp similarity index 100% rename from views/elements/principle_contacts.ctp rename to app/views/elements/principle_contacts.ctp diff --git a/views/elements/principle_contacts_email_box.ctp b/app/views/elements/principle_contacts_email_box.ctp similarity index 100% rename from views/elements/principle_contacts_email_box.ctp rename to app/views/elements/principle_contacts_email_box.ctp diff --git a/views/elements/product_attachment_table.ctp b/app/views/elements/product_attachment_table.ctp similarity index 100% rename from views/elements/product_attachment_table.ctp rename to app/views/elements/product_attachment_table.ctp diff --git a/views/elements/product_costing.ctp b/app/views/elements/product_costing.ctp similarity index 100% rename from views/elements/product_costing.ctp rename to app/views/elements/product_costing.ctp diff --git a/views/elements/product_list.ctp b/app/views/elements/product_list.ctp similarity index 100% rename from views/elements/product_list.ctp rename to app/views/elements/product_list.ctp diff --git a/views/elements/product_table.ctp b/app/views/elements/product_table.ctp similarity index 100% rename from views/elements/product_table.ctp rename to app/views/elements/product_table.ctp diff --git a/views/elements/quote_table.ctp b/app/views/elements/quote_table.ctp similarity index 100% rename from views/elements/quote_table.ctp rename to app/views/elements/quote_table.ctp diff --git a/views/elements/shipment_index_all.ctp b/app/views/elements/shipment_index_all.ctp similarity index 100% rename from views/elements/shipment_index_all.ctp rename to app/views/elements/shipment_index_all.ctp diff --git a/views/elements/shipment_index_direct.ctp b/app/views/elements/shipment_index_direct.ctp similarity index 100% rename from views/elements/shipment_index_direct.ctp rename to app/views/elements/shipment_index_direct.ctp diff --git a/views/elements/shipment_index_export.ctp b/app/views/elements/shipment_index_export.ctp similarity index 100% rename from views/elements/shipment_index_export.ctp rename to app/views/elements/shipment_index_export.ctp diff --git a/views/elements/shipment_index_import.ctp b/app/views/elements/shipment_index_import.ctp similarity index 100% rename from views/elements/shipment_index_import.ctp rename to app/views/elements/shipment_index_import.ctp diff --git a/views/elements/shipment_index_local.ctp b/app/views/elements/shipment_index_local.ctp similarity index 100% rename from views/elements/shipment_index_local.ctp rename to app/views/elements/shipment_index_local.ctp diff --git a/views/elements/short_enquiry_number.ctp b/app/views/elements/short_enquiry_number.ctp similarity index 100% rename from views/elements/short_enquiry_number.ctp rename to app/views/elements/short_enquiry_number.ctp diff --git a/views/elements/tipbox.ctp b/app/views/elements/tipbox.ctp similarity index 100% rename from views/elements/tipbox.ctp rename to app/views/elements/tipbox.ctp diff --git a/views/email_attachments/view.ctp b/app/views/email_attachments/view.ctp similarity index 100% rename from views/email_attachments/view.ctp rename to app/views/email_attachments/view.ctp diff --git a/views/emails/add.ctp b/app/views/emails/add.ctp similarity index 100% rename from views/emails/add.ctp rename to app/views/emails/add.ctp diff --git a/views/emails/frame.ctp b/app/views/emails/frame.ctp similarity index 100% rename from views/emails/frame.ctp rename to app/views/emails/frame.ctp diff --git a/views/emails/index.ctp b/app/views/emails/index.ctp similarity index 100% rename from views/emails/index.ctp rename to app/views/emails/index.ctp diff --git a/views/emails/print.ctp b/app/views/emails/print.ctp similarity index 100% rename from views/emails/print.ctp rename to app/views/emails/print.ctp diff --git a/views/emails/print_frame.ctp b/app/views/emails/print_frame.ctp similarity index 100% rename from views/emails/print_frame.ctp rename to app/views/emails/print_frame.ctp diff --git a/views/emails/printview.ctp b/app/views/emails/printview.ctp similarity index 100% rename from views/emails/printview.ctp rename to app/views/emails/printview.ctp diff --git a/views/emails/show.ctp b/app/views/emails/show.ctp similarity index 100% rename from views/emails/show.ctp rename to app/views/emails/show.ctp diff --git a/views/emails/view.ctp b/app/views/emails/view.ctp similarity index 100% rename from views/emails/view.ctp rename to app/views/emails/view.ctp diff --git a/views/emails/view_user_emails.ctp b/app/views/emails/view_user_emails.ctp similarity index 100% rename from views/emails/view_user_emails.ctp rename to app/views/emails/view_user_emails.ctp diff --git a/views/enquiries/add-original.ctp b/app/views/enquiries/add-original.ctp similarity index 100% rename from views/enquiries/add-original.ctp rename to app/views/enquiries/add-original.ctp diff --git a/views/enquiries/add.ctp b/app/views/enquiries/add.ctp similarity index 100% rename from views/enquiries/add.ctp rename to app/views/enquiries/add.ctp diff --git a/views/enquiries/addold.ctp b/app/views/enquiries/addold.ctp similarity index 100% rename from views/enquiries/addold.ctp rename to app/views/enquiries/addold.ctp diff --git a/views/enquiries/complete_customer.ctp b/app/views/enquiries/complete_customer.ctp similarity index 100% rename from views/enquiries/complete_customer.ctp rename to app/views/enquiries/complete_customer.ctp diff --git a/views/enquiries/do_search.ctp b/app/views/enquiries/do_search.ctp similarity index 100% rename from views/enquiries/do_search.ctp rename to app/views/enquiries/do_search.ctp diff --git a/views/enquiries/edit.ctp b/app/views/enquiries/edit.ctp similarity index 100% rename from views/enquiries/edit.ctp rename to app/views/enquiries/edit.ctp diff --git a/views/enquiries/index.ctp b/app/views/enquiries/index.ctp similarity index 100% rename from views/enquiries/index.ctp rename to app/views/enquiries/index.ctp diff --git a/views/enquiries/search.ctp b/app/views/enquiries/search.ctp similarity index 100% rename from views/enquiries/search.ctp rename to app/views/enquiries/search.ctp diff --git a/views/enquiries/update_status.ctp b/app/views/enquiries/update_status.ctp similarity index 100% rename from views/enquiries/update_status.ctp rename to app/views/enquiries/update_status.ctp diff --git a/views/enquiries/view.ctp b/app/views/enquiries/view.ctp similarity index 100% rename from views/enquiries/view.ctp rename to app/views/enquiries/view.ctp diff --git a/views/enquiries/view_user_enquiries.ctp b/app/views/enquiries/view_user_enquiries.ctp similarity index 100% rename from views/enquiries/view_user_enquiries.ctp rename to app/views/enquiries/view_user_enquiries.ctp diff --git a/views/enquiry_files/add.ctp b/app/views/enquiry_files/add.ctp similarity index 100% rename from views/enquiry_files/add.ctp rename to app/views/enquiry_files/add.ctp diff --git a/views/layouts/js/empty b/app/views/errors/empty similarity index 100% rename from views/layouts/js/empty rename to app/views/errors/empty diff --git a/views/freight_forwarders/add.ctp b/app/views/freight_forwarders/add.ctp similarity index 100% rename from views/freight_forwarders/add.ctp rename to app/views/freight_forwarders/add.ctp diff --git a/views/freight_forwarders/ajax_add.ctp b/app/views/freight_forwarders/ajax_add.ctp similarity index 100% rename from views/freight_forwarders/ajax_add.ctp rename to app/views/freight_forwarders/ajax_add.ctp diff --git a/views/freight_forwarders/build_options.ctp b/app/views/freight_forwarders/build_options.ctp similarity index 100% rename from views/freight_forwarders/build_options.ctp rename to app/views/freight_forwarders/build_options.ctp diff --git a/views/freight_forwarders/edit.ctp b/app/views/freight_forwarders/edit.ctp similarity index 100% rename from views/freight_forwarders/edit.ctp rename to app/views/freight_forwarders/edit.ctp diff --git a/views/freight_forwarders/index.ctp b/app/views/freight_forwarders/index.ctp similarity index 100% rename from views/freight_forwarders/index.ctp rename to app/views/freight_forwarders/index.ctp diff --git a/views/freight_forwarders/view.ctp b/app/views/freight_forwarders/view.ctp similarity index 100% rename from views/freight_forwarders/view.ctp rename to app/views/freight_forwarders/view.ctp diff --git a/views/freight_services/add.ctp b/app/views/freight_services/add.ctp similarity index 100% rename from views/freight_services/add.ctp rename to app/views/freight_services/add.ctp diff --git a/views/freight_services/edit.ctp b/app/views/freight_services/edit.ctp similarity index 100% rename from views/freight_services/edit.ctp rename to app/views/freight_services/edit.ctp diff --git a/views/freight_services/index.ctp b/app/views/freight_services/index.ctp similarity index 100% rename from views/freight_services/index.ctp rename to app/views/freight_services/index.ctp diff --git a/views/freight_services/view.ctp b/app/views/freight_services/view.ctp similarity index 100% rename from views/freight_services/view.ctp rename to app/views/freight_services/view.ctp diff --git a/views/groups/add.ctp b/app/views/groups/add.ctp similarity index 100% rename from views/groups/add.ctp rename to app/views/groups/add.ctp diff --git a/views/groups/edit.ctp b/app/views/groups/edit.ctp similarity index 100% rename from views/groups/edit.ctp rename to app/views/groups/edit.ctp diff --git a/views/groups/index.ctp b/app/views/groups/index.ctp similarity index 100% rename from views/groups/index.ctp rename to app/views/groups/index.ctp diff --git a/views/groups/view.ctp b/app/views/groups/view.ctp similarity index 100% rename from views/groups/view.ctp rename to app/views/groups/view.ctp diff --git a/views/helpers/decimal.php b/app/views/helpers/decimal.php similarity index 100% rename from views/helpers/decimal.php rename to app/views/helpers/decimal.php diff --git a/views/layouts/rss/empty b/app/views/helpers/empty similarity index 100% rename from views/layouts/rss/empty rename to app/views/helpers/empty diff --git a/views/helpers/fck.php b/app/views/helpers/fck.php similarity index 100% rename from views/helpers/fck.php rename to app/views/helpers/fck.php diff --git a/views/helpers/link.php b/app/views/helpers/link.php similarity index 100% rename from views/helpers/link.php rename to app/views/helpers/link.php diff --git a/views/industries/add.ctp b/app/views/industries/add.ctp similarity index 100% rename from views/industries/add.ctp rename to app/views/industries/add.ctp diff --git a/views/industries/edit.ctp b/app/views/industries/edit.ctp similarity index 100% rename from views/industries/edit.ctp rename to app/views/industries/edit.ctp diff --git a/views/industries/index.ctp b/app/views/industries/index.ctp similarity index 100% rename from views/industries/index.ctp rename to app/views/industries/index.ctp diff --git a/views/industries/view.ctp b/app/views/industries/view.ctp similarity index 100% rename from views/industries/view.ctp rename to app/views/industries/view.ctp diff --git a/views/invoices/add.ctp b/app/views/invoices/add.ctp similarity index 100% rename from views/invoices/add.ctp rename to app/views/invoices/add.ctp diff --git a/views/invoices/edit.ctp b/app/views/invoices/edit.ctp similarity index 100% rename from views/invoices/edit.ctp rename to app/views/invoices/edit.ctp diff --git a/views/invoices/index.ctp b/app/views/invoices/index.ctp similarity index 100% rename from views/invoices/index.ctp rename to app/views/invoices/index.ctp diff --git a/views/invoices/print_view.ctp b/app/views/invoices/print_view.ctp similarity index 100% rename from views/invoices/print_view.ctp rename to app/views/invoices/print_view.ctp diff --git a/views/invoices/view.ctp b/app/views/invoices/view.ctp similarity index 100% rename from views/invoices/view.ctp rename to app/views/invoices/view.ctp diff --git a/views/jobs/add.ctp b/app/views/jobs/add.ctp similarity index 100% rename from views/jobs/add.ctp rename to app/views/jobs/add.ctp diff --git a/views/jobs/ajax_add.ctp b/app/views/jobs/ajax_add.ctp similarity index 100% rename from views/jobs/ajax_add.ctp rename to app/views/jobs/ajax_add.ctp diff --git a/views/jobs/ajax_edit.ctp b/app/views/jobs/ajax_edit.ctp similarity index 100% rename from views/jobs/ajax_edit.ctp rename to app/views/jobs/ajax_edit.ctp diff --git a/views/jobs/autocomplete.ctp b/app/views/jobs/autocomplete.ctp similarity index 100% rename from views/jobs/autocomplete.ctp rename to app/views/jobs/autocomplete.ctp diff --git a/views/jobs/build_jobs_json.ctp b/app/views/jobs/build_jobs_json.ctp similarity index 100% rename from views/jobs/build_jobs_json.ctp rename to app/views/jobs/build_jobs_json.ctp diff --git a/views/jobs/edit.ctp b/app/views/jobs/edit.ctp similarity index 100% rename from views/jobs/edit.ctp rename to app/views/jobs/edit.ctp diff --git a/views/jobs/fix_jobs.ctp b/app/views/jobs/fix_jobs.ctp similarity index 100% rename from views/jobs/fix_jobs.ctp rename to app/views/jobs/fix_jobs.ctp diff --git a/views/jobs/get_editable_row.ctp b/app/views/jobs/get_editable_row.ctp similarity index 100% rename from views/jobs/get_editable_row.ctp rename to app/views/jobs/get_editable_row.ctp diff --git a/views/jobs/get_viewable_row.ctp b/app/views/jobs/get_viewable_row.ctp similarity index 100% rename from views/jobs/get_viewable_row.ctp rename to app/views/jobs/get_viewable_row.ctp diff --git a/views/jobs/getdata.ctp b/app/views/jobs/getdata.ctp similarity index 100% rename from views/jobs/getdata.ctp rename to app/views/jobs/getdata.ctp diff --git a/views/jobs/index-working.ctp b/app/views/jobs/index-working.ctp similarity index 100% rename from views/jobs/index-working.ctp rename to app/views/jobs/index-working.ctp diff --git a/views/jobs/index.ctp b/app/views/jobs/index.ctp similarity index 100% rename from views/jobs/index.ctp rename to app/views/jobs/index.ctp diff --git a/views/jobs/index_datatable.ctp b/app/views/jobs/index_datatable.ctp similarity index 100% rename from views/jobs/index_datatable.ctp rename to app/views/jobs/index_datatable.ctp diff --git a/views/jobs/index_grid.ctp b/app/views/jobs/index_grid.ctp similarity index 100% rename from views/jobs/index_grid.ctp rename to app/views/jobs/index_grid.ctp diff --git a/views/jobs/index_table.ctp b/app/views/jobs/index_table.ctp similarity index 100% rename from views/jobs/index_table.ctp rename to app/views/jobs/index_table.ctp diff --git a/views/jobs/mark_deleted.ctp b/app/views/jobs/mark_deleted.ctp similarity index 100% rename from views/jobs/mark_deleted.ctp rename to app/views/jobs/mark_deleted.ctp diff --git a/views/jobs/mark_undeleted.ctp b/app/views/jobs/mark_undeleted.ctp similarity index 100% rename from views/jobs/mark_undeleted.ctp rename to app/views/jobs/mark_undeleted.ctp diff --git a/views/jobs/reports.ctp b/app/views/jobs/reports.ctp similarity index 100% rename from views/jobs/reports.ctp rename to app/views/jobs/reports.ctp diff --git a/views/jobs/view.ctp b/app/views/jobs/view.ctp similarity index 100% rename from views/jobs/view.ctp rename to app/views/jobs/view.ctp diff --git a/views/jobs/view_report.ctp b/app/views/jobs/view_report.ctp similarity index 100% rename from views/jobs/view_report.ctp rename to app/views/jobs/view_report.ctp diff --git a/views/layouts/csv.ctp b/app/views/layouts/csv.ctp similarity index 100% rename from views/layouts/csv.ctp rename to app/views/layouts/csv.ctp diff --git a/views/layouts/default.ctp b/app/views/layouts/default.ctp similarity index 100% rename from views/layouts/default.ctp rename to app/views/layouts/default.ctp diff --git a/views/layouts/email/html/default.ctp b/app/views/layouts/email/html/default.ctp similarity index 100% rename from views/layouts/email/html/default.ctp rename to app/views/layouts/email/html/default.ctp diff --git a/views/layouts/email/text/default.ctp b/app/views/layouts/email/text/default.ctp similarity index 100% rename from views/layouts/email/text/default.ctp rename to app/views/layouts/email/text/default.ctp diff --git a/views/layouts/email/text/default.ctp~ b/app/views/layouts/email/text/default.ctp~ similarity index 100% rename from views/layouts/email/text/default.ctp~ rename to app/views/layouts/email/text/default.ctp~ diff --git a/views/layouts/jqgrid.ctp b/app/views/layouts/jqgrid.ctp similarity index 100% rename from views/layouts/jqgrid.ctp rename to app/views/layouts/jqgrid.ctp diff --git a/views/layouts/xml/empty b/app/views/layouts/js/empty similarity index 100% rename from views/layouts/xml/empty rename to app/views/layouts/js/empty diff --git a/views/layouts/minimal.ctp b/app/views/layouts/minimal.ctp similarity index 100% rename from views/layouts/minimal.ctp rename to app/views/layouts/minimal.ctp diff --git a/views/layouts/pdf.ctp b/app/views/layouts/pdf.ctp similarity index 100% rename from views/layouts/pdf.ctp rename to app/views/layouts/pdf.ctp diff --git a/views/scaffolds/empty b/app/views/layouts/rss/empty similarity index 100% rename from views/scaffolds/empty rename to app/views/layouts/rss/empty diff --git a/views/line_items/ajax_add.ctp b/app/views/layouts/xml/empty similarity index 100% rename from views/line_items/ajax_add.ctp rename to app/views/layouts/xml/empty diff --git a/views/line_items/add.ctp b/app/views/line_items/add.ctp similarity index 100% rename from views/line_items/add.ctp rename to app/views/line_items/add.ctp diff --git a/views/shipments/ajax_add.ctp b/app/views/line_items/ajax_add.ctp similarity index 100% rename from views/shipments/ajax_add.ctp rename to app/views/line_items/ajax_add.ctp diff --git a/views/line_items/ajax_delete.ctp b/app/views/line_items/ajax_delete.ctp similarity index 100% rename from views/line_items/ajax_delete.ctp rename to app/views/line_items/ajax_delete.ctp diff --git a/views/line_items/ajax_edit.ctp b/app/views/line_items/ajax_edit.ctp similarity index 100% rename from views/line_items/ajax_edit.ctp rename to app/views/line_items/ajax_edit.ctp diff --git a/views/line_items/edit.ctp b/app/views/line_items/edit.ctp similarity index 100% rename from views/line_items/edit.ctp rename to app/views/line_items/edit.ctp diff --git a/views/line_items/get_table.ctp b/app/views/line_items/get_table.ctp similarity index 100% rename from views/line_items/get_table.ctp rename to app/views/line_items/get_table.ctp diff --git a/views/pages/about.ctp b/app/views/pages/about.ctp similarity index 100% rename from views/pages/about.ctp rename to app/views/pages/about.ctp diff --git a/views/pages/add.ctp b/app/views/pages/add.ctp similarity index 100% rename from views/pages/add.ctp rename to app/views/pages/add.ctp diff --git a/views/pages/admin.ctp b/app/views/pages/admin.ctp similarity index 100% rename from views/pages/admin.ctp rename to app/views/pages/admin.ctp diff --git a/views/pages/edit.ctp b/app/views/pages/edit.ctp similarity index 100% rename from views/pages/edit.ctp rename to app/views/pages/edit.ctp diff --git a/views/pages/help.ctp b/app/views/pages/help.ctp similarity index 100% rename from views/pages/help.ctp rename to app/views/pages/help.ctp diff --git a/views/pages/index.ctp b/app/views/pages/index.ctp similarity index 100% rename from views/pages/index.ctp rename to app/views/pages/index.ctp diff --git a/views/pages/statistics.ctp b/app/views/pages/statistics.ctp similarity index 100% rename from views/pages/statistics.ctp rename to app/views/pages/statistics.ctp diff --git a/views/pages/view.ctp b/app/views/pages/view.ctp similarity index 100% rename from views/pages/view.ctp rename to app/views/pages/view.ctp diff --git a/views/principle_addresses/add.ctp b/app/views/principle_addresses/add.ctp similarity index 100% rename from views/principle_addresses/add.ctp rename to app/views/principle_addresses/add.ctp diff --git a/views/principle_addresses/edit.ctp b/app/views/principle_addresses/edit.ctp similarity index 100% rename from views/principle_addresses/edit.ctp rename to app/views/principle_addresses/edit.ctp diff --git a/views/principle_addresses/index.ctp b/app/views/principle_addresses/index.ctp similarity index 100% rename from views/principle_addresses/index.ctp rename to app/views/principle_addresses/index.ctp diff --git a/views/principle_addresses/view.ctp b/app/views/principle_addresses/view.ctp similarity index 100% rename from views/principle_addresses/view.ctp rename to app/views/principle_addresses/view.ctp diff --git a/views/principle_contacts/add.ctp b/app/views/principle_contacts/add.ctp similarity index 100% rename from views/principle_contacts/add.ctp rename to app/views/principle_contacts/add.ctp diff --git a/views/principle_contacts/edit.ctp b/app/views/principle_contacts/edit.ctp similarity index 100% rename from views/principle_contacts/edit.ctp rename to app/views/principle_contacts/edit.ctp diff --git a/views/principle_contacts/index.ctp b/app/views/principle_contacts/index.ctp similarity index 100% rename from views/principle_contacts/index.ctp rename to app/views/principle_contacts/index.ctp diff --git a/views/principle_contacts/view.ctp b/app/views/principle_contacts/view.ctp similarity index 100% rename from views/principle_contacts/view.ctp rename to app/views/principle_contacts/view.ctp diff --git a/views/principles/add.ctp b/app/views/principles/add.ctp similarity index 100% rename from views/principles/add.ctp rename to app/views/principles/add.ctp diff --git a/views/principles/edit.ctp b/app/views/principles/edit.ctp similarity index 100% rename from views/principles/edit.ctp rename to app/views/principles/edit.ctp diff --git a/views/principles/index.ctp b/app/views/principles/index.ctp similarity index 100% rename from views/principles/index.ctp rename to app/views/principles/index.ctp diff --git a/views/principles/view.ctp b/app/views/principles/view.ctp similarity index 100% rename from views/principles/view.ctp rename to app/views/principles/view.ctp diff --git a/views/product_attachments/add.ctp b/app/views/product_attachments/add.ctp similarity index 100% rename from views/product_attachments/add.ctp rename to app/views/product_attachments/add.ctp diff --git a/views/product_categories/add.ctp b/app/views/product_categories/add.ctp similarity index 100% rename from views/product_categories/add.ctp rename to app/views/product_categories/add.ctp diff --git a/views/product_categories/edit.ctp b/app/views/product_categories/edit.ctp similarity index 100% rename from views/product_categories/edit.ctp rename to app/views/product_categories/edit.ctp diff --git a/views/product_categories/index.ctp b/app/views/product_categories/index.ctp similarity index 100% rename from views/product_categories/index.ctp rename to app/views/product_categories/index.ctp diff --git a/views/product_categories/view.ctp b/app/views/product_categories/view.ctp similarity index 100% rename from views/product_categories/view.ctp rename to app/views/product_categories/view.ctp diff --git a/views/product_options/add.ctp b/app/views/product_options/add.ctp similarity index 100% rename from views/product_options/add.ctp rename to app/views/product_options/add.ctp diff --git a/views/product_options/edit.ctp b/app/views/product_options/edit.ctp similarity index 100% rename from views/product_options/edit.ctp rename to app/views/product_options/edit.ctp diff --git a/views/product_options/index.ctp b/app/views/product_options/index.ctp similarity index 100% rename from views/product_options/index.ctp rename to app/views/product_options/index.ctp diff --git a/views/product_options/view.ctp b/app/views/product_options/view.ctp similarity index 100% rename from views/product_options/view.ctp rename to app/views/product_options/view.ctp diff --git a/views/product_options_categories/add.ctp b/app/views/product_options_categories/add.ctp similarity index 100% rename from views/product_options_categories/add.ctp rename to app/views/product_options_categories/add.ctp diff --git a/views/product_options_categories/edit.ctp b/app/views/product_options_categories/edit.ctp similarity index 100% rename from views/product_options_categories/edit.ctp rename to app/views/product_options_categories/edit.ctp diff --git a/views/product_options_categories/index.ctp b/app/views/product_options_categories/index.ctp similarity index 100% rename from views/product_options_categories/index.ctp rename to app/views/product_options_categories/index.ctp diff --git a/views/product_options_categories/view.ctp b/app/views/product_options_categories/view.ctp similarity index 100% rename from views/product_options_categories/view.ctp rename to app/views/product_options_categories/view.ctp diff --git a/views/products/.LCKindex.ctp~ b/app/views/products/.LCKindex.ctp~ similarity index 100% rename from views/products/.LCKindex.ctp~ rename to app/views/products/.LCKindex.ctp~ diff --git a/views/products/add.ctp b/app/views/products/add.ctp similarity index 100% rename from views/products/add.ctp rename to app/views/products/add.ctp diff --git a/views/products/clone_product.ctp b/app/views/products/clone_product.ctp similarity index 100% rename from views/products/clone_product.ctp rename to app/views/products/clone_product.ctp diff --git a/views/products/edit.ctp b/app/views/products/edit.ctp similarity index 100% rename from views/products/edit.ctp rename to app/views/products/edit.ctp diff --git a/views/products/get_principle_products.ctp b/app/views/products/get_principle_products.ctp similarity index 100% rename from views/products/get_principle_products.ctp rename to app/views/products/get_principle_products.ctp diff --git a/views/products/get_product_options.ctp b/app/views/products/get_product_options.ctp similarity index 100% rename from views/products/get_product_options.ctp rename to app/views/products/get_product_options.ctp diff --git a/views/products/index.ctp b/app/views/products/index.ctp similarity index 100% rename from views/products/index.ctp rename to app/views/products/index.ctp diff --git a/views/products/view.ctp b/app/views/products/view.ctp similarity index 100% rename from views/products/view.ctp rename to app/views/products/view.ctp diff --git a/views/products/view_principle.ctp b/app/views/products/view_principle.ctp similarity index 100% rename from views/products/view_principle.ctp rename to app/views/products/view_principle.ctp diff --git a/views/purchase_orders/add.ctp b/app/views/purchase_orders/add.ctp similarity index 100% rename from views/purchase_orders/add.ctp rename to app/views/purchase_orders/add.ctp diff --git a/views/purchase_orders/autocomplete.ctp b/app/views/purchase_orders/autocomplete.ctp similarity index 100% rename from views/purchase_orders/autocomplete.ctp rename to app/views/purchase_orders/autocomplete.ctp diff --git a/views/purchase_orders/edit.ctp b/app/views/purchase_orders/edit.ctp similarity index 100% rename from views/purchase_orders/edit.ctp rename to app/views/purchase_orders/edit.ctp diff --git a/views/purchase_orders/getdata.ctp b/app/views/purchase_orders/getdata.ctp similarity index 100% rename from views/purchase_orders/getdata.ctp rename to app/views/purchase_orders/getdata.ctp diff --git a/views/purchase_orders/index.ctp b/app/views/purchase_orders/index.ctp similarity index 100% rename from views/purchase_orders/index.ctp rename to app/views/purchase_orders/index.ctp diff --git a/views/purchase_orders/view.ctp b/app/views/purchase_orders/view.ctp similarity index 100% rename from views/purchase_orders/view.ctp rename to app/views/purchase_orders/view.ctp diff --git a/views/quotes/ajax_edit.ctp b/app/views/quotes/ajax_edit.ctp similarity index 100% rename from views/quotes/ajax_edit.ctp rename to app/views/quotes/ajax_edit.ctp diff --git a/views/quotes/ajaxpdf.ctp b/app/views/quotes/ajaxpdf.ctp similarity index 100% rename from views/quotes/ajaxpdf.ctp rename to app/views/quotes/ajaxpdf.ctp diff --git a/views/quotes/edit.ctp b/app/views/quotes/edit.ctp similarity index 100% rename from views/quotes/edit.ctp rename to app/views/quotes/edit.ctp diff --git a/views/quotes/frame.ctp b/app/views/quotes/frame.ctp similarity index 100% rename from views/quotes/frame.ctp rename to app/views/quotes/frame.ctp diff --git a/views/quotes/index.ctp b/app/views/quotes/index.ctp similarity index 100% rename from views/quotes/index.ctp rename to app/views/quotes/index.ctp diff --git a/views/quotes/pdf.ctp b/app/views/quotes/pdf.ctp similarity index 100% rename from views/quotes/pdf.ctp rename to app/views/quotes/pdf.ctp diff --git a/views/statuses/status_list.ctp b/app/views/scaffolds/empty similarity index 100% rename from views/statuses/status_list.ctp rename to app/views/scaffolds/empty diff --git a/views/shipment_invoices/add.ctp b/app/views/shipment_invoices/add.ctp similarity index 100% rename from views/shipment_invoices/add.ctp rename to app/views/shipment_invoices/add.ctp diff --git a/views/shipment_invoices/edit.ctp b/app/views/shipment_invoices/edit.ctp similarity index 100% rename from views/shipment_invoices/edit.ctp rename to app/views/shipment_invoices/edit.ctp diff --git a/views/shipment_invoices/index.ctp b/app/views/shipment_invoices/index.ctp similarity index 100% rename from views/shipment_invoices/index.ctp rename to app/views/shipment_invoices/index.ctp diff --git a/views/shipment_invoices/view.ctp b/app/views/shipment_invoices/view.ctp similarity index 100% rename from views/shipment_invoices/view.ctp rename to app/views/shipment_invoices/view.ctp diff --git a/views/shipments/add.ctp b/app/views/shipments/add.ctp similarity index 100% rename from views/shipments/add.ctp rename to app/views/shipments/add.ctp diff --git a/app/views/shipments/ajax_add.ctp b/app/views/shipments/ajax_add.ctp new file mode 100755 index 00000000..e69de29b diff --git a/views/shipments/ajax_edit.ctp b/app/views/shipments/ajax_edit.ctp similarity index 100% rename from views/shipments/ajax_edit.ctp rename to app/views/shipments/ajax_edit.ctp diff --git a/views/shipments/edit.ctp b/app/views/shipments/edit.ctp similarity index 100% rename from views/shipments/edit.ctp rename to app/views/shipments/edit.ctp diff --git a/views/shipments/index.ctp b/app/views/shipments/index.ctp similarity index 100% rename from views/shipments/index.ctp rename to app/views/shipments/index.ctp diff --git a/views/shipments/reports.ctp b/app/views/shipments/reports.ctp similarity index 100% rename from views/shipments/reports.ctp rename to app/views/shipments/reports.ctp diff --git a/views/shipments/view.ctp b/app/views/shipments/view.ctp similarity index 100% rename from views/shipments/view.ctp rename to app/views/shipments/view.ctp diff --git a/views/shipments/view_report.ctp b/app/views/shipments/view_report.ctp similarity index 100% rename from views/shipments/view_report.ctp rename to app/views/shipments/view_report.ctp diff --git a/views/states/add.ctp b/app/views/states/add.ctp similarity index 100% rename from views/states/add.ctp rename to app/views/states/add.ctp diff --git a/views/states/edit.ctp b/app/views/states/edit.ctp similarity index 100% rename from views/states/edit.ctp rename to app/views/states/edit.ctp diff --git a/views/states/index.ctp b/app/views/states/index.ctp similarity index 100% rename from views/states/index.ctp rename to app/views/states/index.ctp diff --git a/views/states/view.ctp b/app/views/states/view.ctp similarity index 100% rename from views/states/view.ctp rename to app/views/states/view.ctp diff --git a/views/statuses/add.ctp b/app/views/statuses/add.ctp similarity index 100% rename from views/statuses/add.ctp rename to app/views/statuses/add.ctp diff --git a/views/statuses/edit.ctp b/app/views/statuses/edit.ctp similarity index 100% rename from views/statuses/edit.ctp rename to app/views/statuses/edit.ctp diff --git a/views/statuses/index.ctp b/app/views/statuses/index.ctp similarity index 100% rename from views/statuses/index.ctp rename to app/views/statuses/index.ctp diff --git a/app/views/statuses/status_list.ctp b/app/views/statuses/status_list.ctp new file mode 100755 index 00000000..e69de29b diff --git a/views/statuses/view.ctp b/app/views/statuses/view.ctp similarity index 100% rename from views/statuses/view.ctp rename to app/views/statuses/view.ctp diff --git a/views/users/add.ctp b/app/views/users/add.ctp similarity index 100% rename from views/users/add.ctp rename to app/views/users/add.ctp diff --git a/views/users/add_edit.ctp b/app/views/users/add_edit.ctp similarity index 100% rename from views/users/add_edit.ctp rename to app/views/users/add_edit.ctp diff --git a/views/users/add_user.ctp b/app/views/users/add_user.ctp similarity index 100% rename from views/users/add_user.ctp rename to app/views/users/add_user.ctp diff --git a/views/users/edit.ctp b/app/views/users/edit.ctp similarity index 100% rename from views/users/edit.ctp rename to app/views/users/edit.ctp diff --git a/views/users/index.ctp b/app/views/users/index.ctp similarity index 100% rename from views/users/index.ctp rename to app/views/users/index.ctp diff --git a/views/users/login.ctp b/app/views/users/login.ctp similarity index 100% rename from views/users/login.ctp rename to app/views/users/login.ctp diff --git a/views/users/view.ctp b/app/views/users/view.ctp similarity index 100% rename from views/users/view.ctp rename to app/views/users/view.ctp diff --git a/views/users/view_contact.ctp b/app/views/users/view_contact.ctp similarity index 100% rename from views/users/view_contact.ctp rename to app/views/users/view_contact.ctp diff --git a/views/users/view_principle.ctp b/app/views/users/view_principle.ctp similarity index 100% rename from views/users/view_principle.ctp rename to app/views/users/view_principle.ctp diff --git a/views/users/view_user.ctp b/app/views/users/view_user.ctp similarity index 100% rename from views/users/view_user.ctp rename to app/views/users/view_user.ctp diff --git a/webroot/.htaccess b/app/webroot/.htaccess similarity index 100% rename from webroot/.htaccess rename to app/webroot/.htaccess diff --git a/webroot/css.php b/app/webroot/css.php similarity index 100% rename from webroot/css.php rename to app/webroot/css.php diff --git a/webroot/css/cake.generic.css b/app/webroot/css/cake.generic.css similarity index 100% rename from webroot/css/cake.generic.css rename to app/webroot/css/cake.generic.css diff --git a/webroot/css/email.css b/app/webroot/css/email.css similarity index 100% rename from webroot/css/email.css rename to app/webroot/css/email.css diff --git a/webroot/css/grid.css b/app/webroot/css/grid.css similarity index 100% rename from webroot/css/grid.css rename to app/webroot/css/grid.css diff --git a/webroot/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/app/webroot/css/images/ui-bg_flat_0_aaaaaa_40x100.png similarity index 100% rename from webroot/css/images/ui-bg_flat_0_aaaaaa_40x100.png rename to app/webroot/css/images/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/webroot/css/images/ui-bg_flat_55_fbec88_40x100.png b/app/webroot/css/images/ui-bg_flat_55_fbec88_40x100.png similarity index 100% rename from webroot/css/images/ui-bg_flat_55_fbec88_40x100.png rename to app/webroot/css/images/ui-bg_flat_55_fbec88_40x100.png diff --git a/webroot/css/images/ui-bg_glass_75_d0e5f5_1x400.png b/app/webroot/css/images/ui-bg_glass_75_d0e5f5_1x400.png similarity index 100% rename from webroot/css/images/ui-bg_glass_75_d0e5f5_1x400.png rename to app/webroot/css/images/ui-bg_glass_75_d0e5f5_1x400.png diff --git a/webroot/css/images/ui-bg_glass_85_dfeffc_1x400.png b/app/webroot/css/images/ui-bg_glass_85_dfeffc_1x400.png similarity index 100% rename from webroot/css/images/ui-bg_glass_85_dfeffc_1x400.png rename to app/webroot/css/images/ui-bg_glass_85_dfeffc_1x400.png diff --git a/webroot/css/images/ui-bg_glass_95_fef1ec_1x400.png b/app/webroot/css/images/ui-bg_glass_95_fef1ec_1x400.png similarity index 100% rename from webroot/css/images/ui-bg_glass_95_fef1ec_1x400.png rename to app/webroot/css/images/ui-bg_glass_95_fef1ec_1x400.png diff --git a/webroot/css/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png b/app/webroot/css/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png similarity index 100% rename from webroot/css/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png rename to app/webroot/css/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png diff --git a/webroot/css/images/ui-bg_inset-hard_100_f5f8f9_1x100.png b/app/webroot/css/images/ui-bg_inset-hard_100_f5f8f9_1x100.png similarity index 100% rename from webroot/css/images/ui-bg_inset-hard_100_f5f8f9_1x100.png rename to app/webroot/css/images/ui-bg_inset-hard_100_f5f8f9_1x100.png diff --git a/webroot/css/images/ui-bg_inset-hard_100_fcfdfd_1x100.png b/app/webroot/css/images/ui-bg_inset-hard_100_fcfdfd_1x100.png similarity index 100% rename from webroot/css/images/ui-bg_inset-hard_100_fcfdfd_1x100.png rename to app/webroot/css/images/ui-bg_inset-hard_100_fcfdfd_1x100.png diff --git a/webroot/css/images/ui-icons_217bc0_256x240.png b/app/webroot/css/images/ui-icons_217bc0_256x240.png similarity index 100% rename from webroot/css/images/ui-icons_217bc0_256x240.png rename to app/webroot/css/images/ui-icons_217bc0_256x240.png diff --git a/webroot/css/images/ui-icons_2e83ff_256x240.png b/app/webroot/css/images/ui-icons_2e83ff_256x240.png similarity index 100% rename from webroot/css/images/ui-icons_2e83ff_256x240.png rename to app/webroot/css/images/ui-icons_2e83ff_256x240.png diff --git a/webroot/css/images/ui-icons_469bdd_256x240.png b/app/webroot/css/images/ui-icons_469bdd_256x240.png similarity index 100% rename from webroot/css/images/ui-icons_469bdd_256x240.png rename to app/webroot/css/images/ui-icons_469bdd_256x240.png diff --git a/webroot/css/images/ui-icons_6da8d5_256x240.png b/app/webroot/css/images/ui-icons_6da8d5_256x240.png similarity index 100% rename from webroot/css/images/ui-icons_6da8d5_256x240.png rename to app/webroot/css/images/ui-icons_6da8d5_256x240.png diff --git a/webroot/css/images/ui-icons_cd0a0a_256x240.png b/app/webroot/css/images/ui-icons_cd0a0a_256x240.png similarity index 100% rename from webroot/css/images/ui-icons_cd0a0a_256x240.png rename to app/webroot/css/images/ui-icons_cd0a0a_256x240.png diff --git a/webroot/css/images/ui-icons_d8e7f3_256x240.png b/app/webroot/css/images/ui-icons_d8e7f3_256x240.png similarity index 100% rename from webroot/css/images/ui-icons_d8e7f3_256x240.png rename to app/webroot/css/images/ui-icons_d8e7f3_256x240.png diff --git a/webroot/css/images/ui-icons_f9bd01_256x240.png b/app/webroot/css/images/ui-icons_f9bd01_256x240.png similarity index 100% rename from webroot/css/images/ui-icons_f9bd01_256x240.png rename to app/webroot/css/images/ui-icons_f9bd01_256x240.png diff --git a/webroot/css/imagesOld/ui-bg_diagonals-thick_90_eeeeee_40x40.png b/app/webroot/css/imagesOld/ui-bg_diagonals-thick_90_eeeeee_40x40.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_diagonals-thick_90_eeeeee_40x40.png rename to app/webroot/css/imagesOld/ui-bg_diagonals-thick_90_eeeeee_40x40.png diff --git a/webroot/css/imagesOld/ui-bg_flat_0_aaaaaa_40x100.png b/app/webroot/css/imagesOld/ui-bg_flat_0_aaaaaa_40x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_flat_0_aaaaaa_40x100.png rename to app/webroot/css/imagesOld/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/webroot/css/imagesOld/ui-bg_flat_15_cd0a0a_40x100.png b/app/webroot/css/imagesOld/ui-bg_flat_15_cd0a0a_40x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_flat_15_cd0a0a_40x100.png rename to app/webroot/css/imagesOld/ui-bg_flat_15_cd0a0a_40x100.png diff --git a/webroot/css/imagesOld/ui-bg_flat_55_fbec88_40x100.png b/app/webroot/css/imagesOld/ui-bg_flat_55_fbec88_40x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_flat_55_fbec88_40x100.png rename to app/webroot/css/imagesOld/ui-bg_flat_55_fbec88_40x100.png diff --git a/webroot/css/imagesOld/ui-bg_glass_100_e4f1fb_1x400.png b/app/webroot/css/imagesOld/ui-bg_glass_100_e4f1fb_1x400.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_glass_100_e4f1fb_1x400.png rename to app/webroot/css/imagesOld/ui-bg_glass_100_e4f1fb_1x400.png diff --git a/webroot/css/imagesOld/ui-bg_glass_50_3baae3_1x400.png b/app/webroot/css/imagesOld/ui-bg_glass_50_3baae3_1x400.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_glass_50_3baae3_1x400.png rename to app/webroot/css/imagesOld/ui-bg_glass_50_3baae3_1x400.png diff --git a/webroot/css/imagesOld/ui-bg_glass_75_d0e5f5_1x400.png b/app/webroot/css/imagesOld/ui-bg_glass_75_d0e5f5_1x400.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_glass_75_d0e5f5_1x400.png rename to app/webroot/css/imagesOld/ui-bg_glass_75_d0e5f5_1x400.png diff --git a/webroot/css/imagesOld/ui-bg_glass_80_d7ebf9_1x400.png b/app/webroot/css/imagesOld/ui-bg_glass_80_d7ebf9_1x400.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_glass_80_d7ebf9_1x400.png rename to app/webroot/css/imagesOld/ui-bg_glass_80_d7ebf9_1x400.png diff --git a/webroot/css/imagesOld/ui-bg_glass_85_dfeffc_1x400.png b/app/webroot/css/imagesOld/ui-bg_glass_85_dfeffc_1x400.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_glass_85_dfeffc_1x400.png rename to app/webroot/css/imagesOld/ui-bg_glass_85_dfeffc_1x400.png diff --git a/webroot/css/imagesOld/ui-bg_glass_95_fef1ec_1x400.png b/app/webroot/css/imagesOld/ui-bg_glass_95_fef1ec_1x400.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_glass_95_fef1ec_1x400.png rename to app/webroot/css/imagesOld/ui-bg_glass_95_fef1ec_1x400.png diff --git a/webroot/css/imagesOld/ui-bg_gloss-wave_55_5c9ccc_500x100.png b/app/webroot/css/imagesOld/ui-bg_gloss-wave_55_5c9ccc_500x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_gloss-wave_55_5c9ccc_500x100.png rename to app/webroot/css/imagesOld/ui-bg_gloss-wave_55_5c9ccc_500x100.png diff --git a/webroot/css/imagesOld/ui-bg_highlight-hard_100_f2f5f7_1x100.png b/app/webroot/css/imagesOld/ui-bg_highlight-hard_100_f2f5f7_1x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_highlight-hard_100_f2f5f7_1x100.png rename to app/webroot/css/imagesOld/ui-bg_highlight-hard_100_f2f5f7_1x100.png diff --git a/webroot/css/imagesOld/ui-bg_highlight-hard_70_000000_1x100.png b/app/webroot/css/imagesOld/ui-bg_highlight-hard_70_000000_1x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_highlight-hard_70_000000_1x100.png rename to app/webroot/css/imagesOld/ui-bg_highlight-hard_70_000000_1x100.png diff --git a/webroot/css/imagesOld/ui-bg_highlight-soft_100_deedf7_1x100.png b/app/webroot/css/imagesOld/ui-bg_highlight-soft_100_deedf7_1x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_highlight-soft_100_deedf7_1x100.png rename to app/webroot/css/imagesOld/ui-bg_highlight-soft_100_deedf7_1x100.png diff --git a/webroot/css/imagesOld/ui-bg_highlight-soft_25_ffef8f_1x100.png b/app/webroot/css/imagesOld/ui-bg_highlight-soft_25_ffef8f_1x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_highlight-soft_25_ffef8f_1x100.png rename to app/webroot/css/imagesOld/ui-bg_highlight-soft_25_ffef8f_1x100.png diff --git a/webroot/css/imagesOld/ui-bg_inset-hard_100_f5f8f9_1x100.png b/app/webroot/css/imagesOld/ui-bg_inset-hard_100_f5f8f9_1x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_inset-hard_100_f5f8f9_1x100.png rename to app/webroot/css/imagesOld/ui-bg_inset-hard_100_f5f8f9_1x100.png diff --git a/webroot/css/imagesOld/ui-bg_inset-hard_100_fcfdfd_1x100.png b/app/webroot/css/imagesOld/ui-bg_inset-hard_100_fcfdfd_1x100.png similarity index 100% rename from webroot/css/imagesOld/ui-bg_inset-hard_100_fcfdfd_1x100.png rename to app/webroot/css/imagesOld/ui-bg_inset-hard_100_fcfdfd_1x100.png diff --git a/webroot/css/imagesOld/ui-icons_217bc0_256x240.png b/app/webroot/css/imagesOld/ui-icons_217bc0_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_217bc0_256x240.png rename to app/webroot/css/imagesOld/ui-icons_217bc0_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_2694e8_256x240.png b/app/webroot/css/imagesOld/ui-icons_2694e8_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_2694e8_256x240.png rename to app/webroot/css/imagesOld/ui-icons_2694e8_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_2e83ff_256x240.png b/app/webroot/css/imagesOld/ui-icons_2e83ff_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_2e83ff_256x240.png rename to app/webroot/css/imagesOld/ui-icons_2e83ff_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_3d80b3_256x240.png b/app/webroot/css/imagesOld/ui-icons_3d80b3_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_3d80b3_256x240.png rename to app/webroot/css/imagesOld/ui-icons_3d80b3_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_469bdd_256x240.png b/app/webroot/css/imagesOld/ui-icons_469bdd_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_469bdd_256x240.png rename to app/webroot/css/imagesOld/ui-icons_469bdd_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_6da8d5_256x240.png b/app/webroot/css/imagesOld/ui-icons_6da8d5_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_6da8d5_256x240.png rename to app/webroot/css/imagesOld/ui-icons_6da8d5_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_72a7cf_256x240.png b/app/webroot/css/imagesOld/ui-icons_72a7cf_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_72a7cf_256x240.png rename to app/webroot/css/imagesOld/ui-icons_72a7cf_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_cd0a0a_256x240.png b/app/webroot/css/imagesOld/ui-icons_cd0a0a_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_cd0a0a_256x240.png rename to app/webroot/css/imagesOld/ui-icons_cd0a0a_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_d8e7f3_256x240.png b/app/webroot/css/imagesOld/ui-icons_d8e7f3_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_d8e7f3_256x240.png rename to app/webroot/css/imagesOld/ui-icons_d8e7f3_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_f9bd01_256x240.png b/app/webroot/css/imagesOld/ui-icons_f9bd01_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_f9bd01_256x240.png rename to app/webroot/css/imagesOld/ui-icons_f9bd01_256x240.png diff --git a/webroot/css/imagesOld/ui-icons_ffffff_256x240.png b/app/webroot/css/imagesOld/ui-icons_ffffff_256x240.png similarity index 100% rename from webroot/css/imagesOld/ui-icons_ffffff_256x240.png rename to app/webroot/css/imagesOld/ui-icons_ffffff_256x240.png diff --git a/webroot/css/jquery-ui.custom-old.css b/app/webroot/css/jquery-ui.custom-old.css similarity index 100% rename from webroot/css/jquery-ui.custom-old.css rename to app/webroot/css/jquery-ui.custom-old.css diff --git a/webroot/css/jquery-ui.custom.css b/app/webroot/css/jquery-ui.custom.css similarity index 100% rename from webroot/css/jquery-ui.custom.css rename to app/webroot/css/jquery-ui.custom.css diff --git a/webroot/css/quotenik.css b/app/webroot/css/quotenik.css similarity index 100% rename from webroot/css/quotenik.css rename to app/webroot/css/quotenik.css diff --git a/webroot/css/ui.jqgrid.css b/app/webroot/css/ui.jqgrid.css similarity index 100% rename from webroot/css/ui.jqgrid.css rename to app/webroot/css/ui.jqgrid.css diff --git a/webroot/favicon.ico b/app/webroot/favicon.ico similarity index 100% rename from webroot/favicon.ico rename to app/webroot/favicon.ico diff --git a/webroot/img/address-book-new.png b/app/webroot/img/address-book-new.png similarity index 100% rename from webroot/img/address-book-new.png rename to app/webroot/img/address-book-new.png diff --git a/webroot/img/ajax-loader.gif b/app/webroot/img/ajax-loader.gif similarity index 100% rename from webroot/img/ajax-loader.gif rename to app/webroot/img/ajax-loader.gif diff --git a/webroot/img/attach.png b/app/webroot/img/attach.png similarity index 100% rename from webroot/img/attach.png rename to app/webroot/img/attach.png diff --git a/webroot/img/cake.power.gif b/app/webroot/img/cake.power.gif similarity index 100% rename from webroot/img/cake.power.gif rename to app/webroot/img/cake.power.gif diff --git a/webroot/img/calculator.png b/app/webroot/img/calculator.png similarity index 100% rename from webroot/img/calculator.png rename to app/webroot/img/calculator.png diff --git a/webroot/img/cmcheader.jpg b/app/webroot/img/cmcheader.jpg similarity index 100% rename from webroot/img/cmcheader.jpg rename to app/webroot/img/cmcheader.jpg diff --git a/webroot/img/cmclogo.png b/app/webroot/img/cmclogo.png similarity index 100% rename from webroot/img/cmclogo.png rename to app/webroot/img/cmclogo.png diff --git a/webroot/img/cmclogosmall.png b/app/webroot/img/cmclogosmall.png similarity index 100% rename from webroot/img/cmclogosmall.png rename to app/webroot/img/cmclogosmall.png diff --git a/webroot/img/cmclogowhite.png b/app/webroot/img/cmclogowhite.png similarity index 100% rename from webroot/img/cmclogowhite.png rename to app/webroot/img/cmclogowhite.png diff --git a/webroot/img/cmclogowhite2.png b/app/webroot/img/cmclogowhite2.png similarity index 100% rename from webroot/img/cmclogowhite2.png rename to app/webroot/img/cmclogowhite2.png diff --git a/webroot/img/contact-new.png b/app/webroot/img/contact-new.png similarity index 100% rename from webroot/img/contact-new.png rename to app/webroot/img/contact-new.png diff --git a/webroot/img/core.png b/app/webroot/img/core.png similarity index 100% rename from webroot/img/core.png rename to app/webroot/img/core.png diff --git a/webroot/img/document.png b/app/webroot/img/document.png similarity index 100% rename from webroot/img/document.png rename to app/webroot/img/document.png diff --git a/webroot/img/document_type.png b/app/webroot/img/document_type.png similarity index 100% rename from webroot/img/document_type.png rename to app/webroot/img/document_type.png diff --git a/webroot/img/edit-undo.png b/app/webroot/img/edit-undo.png similarity index 100% rename from webroot/img/edit-undo.png rename to app/webroot/img/edit-undo.png diff --git a/webroot/img/edit_add.png b/app/webroot/img/edit_add.png similarity index 100% rename from webroot/img/edit_add.png rename to app/webroot/img/edit_add.png diff --git a/webroot/img/email-small.png b/app/webroot/img/email-small.png similarity index 100% rename from webroot/img/email-small.png rename to app/webroot/img/email-small.png diff --git a/webroot/img/email.png b/app/webroot/img/email.png similarity index 100% rename from webroot/img/email.png rename to app/webroot/img/email.png diff --git a/webroot/img/favicon.ico b/app/webroot/img/favicon.ico similarity index 100% rename from webroot/img/favicon.ico rename to app/webroot/img/favicon.ico diff --git a/webroot/img/folder-new.png b/app/webroot/img/folder-new.png similarity index 100% rename from webroot/img/folder-new.png rename to app/webroot/img/folder-new.png diff --git a/webroot/img/folder.png b/app/webroot/img/folder.png similarity index 100% rename from webroot/img/folder.png rename to app/webroot/img/folder.png diff --git a/webroot/img/image_type.png b/app/webroot/img/image_type.png similarity index 100% rename from webroot/img/image_type.png rename to app/webroot/img/image_type.png diff --git a/webroot/img/internet-mail.png b/app/webroot/img/internet-mail.png similarity index 100% rename from webroot/img/internet-mail.png rename to app/webroot/img/internet-mail.png diff --git a/webroot/img/kaddressbook.png b/app/webroot/img/kaddressbook.png similarity index 100% rename from webroot/img/kaddressbook.png rename to app/webroot/img/kaddressbook.png diff --git a/webroot/img/message_type.png b/app/webroot/img/message_type.png similarity index 100% rename from webroot/img/message_type.png rename to app/webroot/img/message_type.png diff --git a/webroot/img/pdf.png b/app/webroot/img/pdf.png similarity index 100% rename from webroot/img/pdf.png rename to app/webroot/img/pdf.png diff --git a/webroot/img/pdf_type.png b/app/webroot/img/pdf_type.png similarity index 100% rename from webroot/img/pdf_type.png rename to app/webroot/img/pdf_type.png diff --git a/webroot/img/spreadsheet_document_type.png b/app/webroot/img/spreadsheet_document_type.png similarity index 100% rename from webroot/img/spreadsheet_document_type.png rename to app/webroot/img/spreadsheet_document_type.png diff --git a/webroot/img/system-run-3.png b/app/webroot/img/system-run-3.png similarity index 100% rename from webroot/img/system-run-3.png rename to app/webroot/img/system-run-3.png diff --git a/webroot/img/system-search.png b/app/webroot/img/system-search.png similarity index 100% rename from webroot/img/system-search.png rename to app/webroot/img/system-search.png diff --git a/webroot/img/system-users.png b/app/webroot/img/system-users.png similarity index 100% rename from webroot/img/system-users.png rename to app/webroot/img/system-users.png diff --git a/webroot/img/text-x-generic.png b/app/webroot/img/text-x-generic.png similarity index 100% rename from webroot/img/text-x-generic.png rename to app/webroot/img/text-x-generic.png diff --git a/webroot/img/tick.png b/app/webroot/img/tick.png similarity index 100% rename from webroot/img/tick.png rename to app/webroot/img/tick.png diff --git a/webroot/img/tip.png b/app/webroot/img/tip.png similarity index 100% rename from webroot/img/tip.png rename to app/webroot/img/tip.png diff --git a/webroot/img/top-gradient.png b/app/webroot/img/top-gradient.png similarity index 100% rename from webroot/img/top-gradient.png rename to app/webroot/img/top-gradient.png diff --git a/webroot/img/top-gradient2.png b/app/webroot/img/top-gradient2.png similarity index 100% rename from webroot/img/top-gradient2.png rename to app/webroot/img/top-gradient2.png diff --git a/webroot/img/txt_type.png b/app/webroot/img/txt_type.png similarity index 100% rename from webroot/img/txt_type.png rename to app/webroot/img/txt_type.png diff --git a/webroot/img/unknown_type.png b/app/webroot/img/unknown_type.png similarity index 100% rename from webroot/img/unknown_type.png rename to app/webroot/img/unknown_type.png diff --git a/webroot/img/x-office-address-book.png b/app/webroot/img/x-office-address-book.png similarity index 100% rename from webroot/img/x-office-address-book.png rename to app/webroot/img/x-office-address-book.png diff --git a/webroot/index.php b/app/webroot/index.php similarity index 100% rename from webroot/index.php rename to app/webroot/index.php diff --git a/webroot/js/addLineItem.js b/app/webroot/js/addLineItem.js similarity index 100% rename from webroot/js/addLineItem.js rename to app/webroot/js/addLineItem.js diff --git a/webroot/js/add_costing.js b/app/webroot/js/add_costing.js similarity index 100% rename from webroot/js/add_costing.js rename to app/webroot/js/add_costing.js diff --git a/webroot/js/add_edit_shipment.js b/app/webroot/js/add_edit_shipment.js similarity index 100% rename from webroot/js/add_edit_shipment.js rename to app/webroot/js/add_edit_shipment.js diff --git a/webroot/js/add_edit_user.js b/app/webroot/js/add_edit_user.js similarity index 100% rename from webroot/js/add_edit_user.js rename to app/webroot/js/add_edit_user.js diff --git a/webroot/js/add_edit_user_ajax.js b/app/webroot/js/add_edit_user_ajax.js similarity index 100% rename from webroot/js/add_edit_user_ajax.js rename to app/webroot/js/add_edit_user_ajax.js diff --git a/webroot/js/add_enquiry.js b/app/webroot/js/add_enquiry.js similarity index 100% rename from webroot/js/add_enquiry.js rename to app/webroot/js/add_enquiry.js diff --git a/webroot/js/addpurchaseorder.js b/app/webroot/js/addpurchaseorder.js similarity index 100% rename from webroot/js/addpurchaseorder.js rename to app/webroot/js/addpurchaseorder.js diff --git a/webroot/js/ajax_pagination_email.js b/app/webroot/js/ajax_pagination_email.js similarity index 100% rename from webroot/js/ajax_pagination_email.js rename to app/webroot/js/ajax_pagination_email.js diff --git a/webroot/js/ajax_pagination_enquiry.js b/app/webroot/js/ajax_pagination_enquiry.js similarity index 100% rename from webroot/js/ajax_pagination_enquiry.js rename to app/webroot/js/ajax_pagination_enquiry.js diff --git a/webroot/js/ckeditor/.htaccess b/app/webroot/js/ckeditor/.htaccess similarity index 100% rename from webroot/js/ckeditor/.htaccess rename to app/webroot/js/ckeditor/.htaccess diff --git a/webroot/js/ckeditor/CHANGES.html b/app/webroot/js/ckeditor/CHANGES.html similarity index 100% rename from webroot/js/ckeditor/CHANGES.html rename to app/webroot/js/ckeditor/CHANGES.html diff --git a/webroot/js/ckeditor/INSTALL.html b/app/webroot/js/ckeditor/INSTALL.html similarity index 100% rename from webroot/js/ckeditor/INSTALL.html rename to app/webroot/js/ckeditor/INSTALL.html diff --git a/webroot/js/ckeditor/LICENSE.html b/app/webroot/js/ckeditor/LICENSE.html similarity index 100% rename from webroot/js/ckeditor/LICENSE.html rename to app/webroot/js/ckeditor/LICENSE.html diff --git a/webroot/js/ckeditor/_samples/adobeair/application.xml b/app/webroot/js/ckeditor/_samples/adobeair/application.xml similarity index 100% rename from webroot/js/ckeditor/_samples/adobeair/application.xml rename to app/webroot/js/ckeditor/_samples/adobeair/application.xml diff --git a/webroot/js/ckeditor/_samples/adobeair/run.bat b/app/webroot/js/ckeditor/_samples/adobeair/run.bat similarity index 100% rename from webroot/js/ckeditor/_samples/adobeair/run.bat rename to app/webroot/js/ckeditor/_samples/adobeair/run.bat diff --git a/webroot/js/ckeditor/_samples/adobeair/run.sh b/app/webroot/js/ckeditor/_samples/adobeair/run.sh similarity index 100% rename from webroot/js/ckeditor/_samples/adobeair/run.sh rename to app/webroot/js/ckeditor/_samples/adobeair/run.sh diff --git a/webroot/js/ckeditor/_samples/adobeair/sample.html b/app/webroot/js/ckeditor/_samples/adobeair/sample.html similarity index 100% rename from webroot/js/ckeditor/_samples/adobeair/sample.html rename to app/webroot/js/ckeditor/_samples/adobeair/sample.html diff --git a/webroot/js/ckeditor/_samples/ajax.html b/app/webroot/js/ckeditor/_samples/ajax.html similarity index 100% rename from webroot/js/ckeditor/_samples/ajax.html rename to app/webroot/js/ckeditor/_samples/ajax.html diff --git a/webroot/js/ckeditor/_samples/api.html b/app/webroot/js/ckeditor/_samples/api.html similarity index 100% rename from webroot/js/ckeditor/_samples/api.html rename to app/webroot/js/ckeditor/_samples/api.html diff --git a/webroot/js/ckeditor/_samples/api_dialog.html b/app/webroot/js/ckeditor/_samples/api_dialog.html similarity index 100% rename from webroot/js/ckeditor/_samples/api_dialog.html rename to app/webroot/js/ckeditor/_samples/api_dialog.html diff --git a/webroot/js/ckeditor/_samples/api_dialog/my_dialog.js b/app/webroot/js/ckeditor/_samples/api_dialog/my_dialog.js similarity index 100% rename from webroot/js/ckeditor/_samples/api_dialog/my_dialog.js rename to app/webroot/js/ckeditor/_samples/api_dialog/my_dialog.js diff --git a/webroot/js/ckeditor/_samples/asp/advanced.asp b/app/webroot/js/ckeditor/_samples/asp/advanced.asp similarity index 100% rename from webroot/js/ckeditor/_samples/asp/advanced.asp rename to app/webroot/js/ckeditor/_samples/asp/advanced.asp diff --git a/webroot/js/ckeditor/_samples/asp/events.asp b/app/webroot/js/ckeditor/_samples/asp/events.asp similarity index 100% rename from webroot/js/ckeditor/_samples/asp/events.asp rename to app/webroot/js/ckeditor/_samples/asp/events.asp diff --git a/webroot/js/ckeditor/_samples/asp/index.html b/app/webroot/js/ckeditor/_samples/asp/index.html similarity index 100% rename from webroot/js/ckeditor/_samples/asp/index.html rename to app/webroot/js/ckeditor/_samples/asp/index.html diff --git a/webroot/js/ckeditor/_samples/asp/replace.asp b/app/webroot/js/ckeditor/_samples/asp/replace.asp similarity index 100% rename from webroot/js/ckeditor/_samples/asp/replace.asp rename to app/webroot/js/ckeditor/_samples/asp/replace.asp diff --git a/webroot/js/ckeditor/_samples/asp/replaceall.asp b/app/webroot/js/ckeditor/_samples/asp/replaceall.asp similarity index 100% rename from webroot/js/ckeditor/_samples/asp/replaceall.asp rename to app/webroot/js/ckeditor/_samples/asp/replaceall.asp diff --git a/webroot/js/ckeditor/_samples/asp/sample_posteddata.asp b/app/webroot/js/ckeditor/_samples/asp/sample_posteddata.asp similarity index 100% rename from webroot/js/ckeditor/_samples/asp/sample_posteddata.asp rename to app/webroot/js/ckeditor/_samples/asp/sample_posteddata.asp diff --git a/webroot/js/ckeditor/_samples/asp/standalone.asp b/app/webroot/js/ckeditor/_samples/asp/standalone.asp similarity index 100% rename from webroot/js/ckeditor/_samples/asp/standalone.asp rename to app/webroot/js/ckeditor/_samples/asp/standalone.asp diff --git a/webroot/js/ckeditor/_samples/assets/_posteddata.php b/app/webroot/js/ckeditor/_samples/assets/_posteddata.php similarity index 100% rename from webroot/js/ckeditor/_samples/assets/_posteddata.php rename to app/webroot/js/ckeditor/_samples/assets/_posteddata.php diff --git a/webroot/js/ckeditor/_samples/assets/output_for_flash.fla b/app/webroot/js/ckeditor/_samples/assets/output_for_flash.fla similarity index 100% rename from webroot/js/ckeditor/_samples/assets/output_for_flash.fla rename to app/webroot/js/ckeditor/_samples/assets/output_for_flash.fla diff --git a/webroot/js/ckeditor/_samples/assets/output_for_flash.swf b/app/webroot/js/ckeditor/_samples/assets/output_for_flash.swf similarity index 100% rename from webroot/js/ckeditor/_samples/assets/output_for_flash.swf rename to app/webroot/js/ckeditor/_samples/assets/output_for_flash.swf diff --git a/webroot/js/ckeditor/_samples/assets/output_xhtml.css b/app/webroot/js/ckeditor/_samples/assets/output_xhtml.css similarity index 100% rename from webroot/js/ckeditor/_samples/assets/output_xhtml.css rename to app/webroot/js/ckeditor/_samples/assets/output_xhtml.css diff --git a/webroot/js/ckeditor/_samples/assets/parsesample.css b/app/webroot/js/ckeditor/_samples/assets/parsesample.css similarity index 100% rename from webroot/js/ckeditor/_samples/assets/parsesample.css rename to app/webroot/js/ckeditor/_samples/assets/parsesample.css diff --git a/webroot/js/ckeditor/_samples/assets/swfobject.js b/app/webroot/js/ckeditor/_samples/assets/swfobject.js similarity index 100% rename from webroot/js/ckeditor/_samples/assets/swfobject.js rename to app/webroot/js/ckeditor/_samples/assets/swfobject.js diff --git a/webroot/js/ckeditor/_samples/autogrow.html b/app/webroot/js/ckeditor/_samples/autogrow.html similarity index 100% rename from webroot/js/ckeditor/_samples/autogrow.html rename to app/webroot/js/ckeditor/_samples/autogrow.html diff --git a/webroot/js/ckeditor/_samples/bbcode.html b/app/webroot/js/ckeditor/_samples/bbcode.html similarity index 100% rename from webroot/js/ckeditor/_samples/bbcode.html rename to app/webroot/js/ckeditor/_samples/bbcode.html diff --git a/webroot/js/ckeditor/_samples/devtools.html b/app/webroot/js/ckeditor/_samples/devtools.html similarity index 100% rename from webroot/js/ckeditor/_samples/devtools.html rename to app/webroot/js/ckeditor/_samples/devtools.html diff --git a/webroot/js/ckeditor/_samples/divreplace.html b/app/webroot/js/ckeditor/_samples/divreplace.html similarity index 100% rename from webroot/js/ckeditor/_samples/divreplace.html rename to app/webroot/js/ckeditor/_samples/divreplace.html diff --git a/webroot/js/ckeditor/_samples/enterkey.html b/app/webroot/js/ckeditor/_samples/enterkey.html similarity index 100% rename from webroot/js/ckeditor/_samples/enterkey.html rename to app/webroot/js/ckeditor/_samples/enterkey.html diff --git a/webroot/js/ckeditor/_samples/fullpage.html b/app/webroot/js/ckeditor/_samples/fullpage.html similarity index 100% rename from webroot/js/ckeditor/_samples/fullpage.html rename to app/webroot/js/ckeditor/_samples/fullpage.html diff --git a/webroot/js/ckeditor/_samples/index.html b/app/webroot/js/ckeditor/_samples/index.html similarity index 100% rename from webroot/js/ckeditor/_samples/index.html rename to app/webroot/js/ckeditor/_samples/index.html diff --git a/webroot/js/ckeditor/_samples/jqueryadapter.html b/app/webroot/js/ckeditor/_samples/jqueryadapter.html similarity index 100% rename from webroot/js/ckeditor/_samples/jqueryadapter.html rename to app/webroot/js/ckeditor/_samples/jqueryadapter.html diff --git a/webroot/js/ckeditor/_samples/output_for_flash.html b/app/webroot/js/ckeditor/_samples/output_for_flash.html similarity index 100% rename from webroot/js/ckeditor/_samples/output_for_flash.html rename to app/webroot/js/ckeditor/_samples/output_for_flash.html diff --git a/webroot/js/ckeditor/_samples/output_html.html b/app/webroot/js/ckeditor/_samples/output_html.html similarity index 100% rename from webroot/js/ckeditor/_samples/output_html.html rename to app/webroot/js/ckeditor/_samples/output_html.html diff --git a/webroot/js/ckeditor/_samples/output_xhtml.html b/app/webroot/js/ckeditor/_samples/output_xhtml.html similarity index 100% rename from webroot/js/ckeditor/_samples/output_xhtml.html rename to app/webroot/js/ckeditor/_samples/output_xhtml.html diff --git a/webroot/js/ckeditor/_samples/php/advanced.php b/app/webroot/js/ckeditor/_samples/php/advanced.php similarity index 100% rename from webroot/js/ckeditor/_samples/php/advanced.php rename to app/webroot/js/ckeditor/_samples/php/advanced.php diff --git a/webroot/js/ckeditor/_samples/php/events.php b/app/webroot/js/ckeditor/_samples/php/events.php similarity index 100% rename from webroot/js/ckeditor/_samples/php/events.php rename to app/webroot/js/ckeditor/_samples/php/events.php diff --git a/webroot/js/ckeditor/_samples/php/index.html b/app/webroot/js/ckeditor/_samples/php/index.html similarity index 100% rename from webroot/js/ckeditor/_samples/php/index.html rename to app/webroot/js/ckeditor/_samples/php/index.html diff --git a/webroot/js/ckeditor/_samples/php/replace.php b/app/webroot/js/ckeditor/_samples/php/replace.php similarity index 100% rename from webroot/js/ckeditor/_samples/php/replace.php rename to app/webroot/js/ckeditor/_samples/php/replace.php diff --git a/webroot/js/ckeditor/_samples/php/replaceall.php b/app/webroot/js/ckeditor/_samples/php/replaceall.php similarity index 100% rename from webroot/js/ckeditor/_samples/php/replaceall.php rename to app/webroot/js/ckeditor/_samples/php/replaceall.php diff --git a/webroot/js/ckeditor/_samples/php/standalone.php b/app/webroot/js/ckeditor/_samples/php/standalone.php similarity index 100% rename from webroot/js/ckeditor/_samples/php/standalone.php rename to app/webroot/js/ckeditor/_samples/php/standalone.php diff --git a/webroot/js/ckeditor/_samples/placeholder.html b/app/webroot/js/ckeditor/_samples/placeholder.html similarity index 100% rename from webroot/js/ckeditor/_samples/placeholder.html rename to app/webroot/js/ckeditor/_samples/placeholder.html diff --git a/webroot/js/ckeditor/_samples/readonly.html b/app/webroot/js/ckeditor/_samples/readonly.html similarity index 100% rename from webroot/js/ckeditor/_samples/readonly.html rename to app/webroot/js/ckeditor/_samples/readonly.html diff --git a/webroot/js/ckeditor/_samples/replacebyclass.html b/app/webroot/js/ckeditor/_samples/replacebyclass.html similarity index 100% rename from webroot/js/ckeditor/_samples/replacebyclass.html rename to app/webroot/js/ckeditor/_samples/replacebyclass.html diff --git a/webroot/js/ckeditor/_samples/replacebycode.html b/app/webroot/js/ckeditor/_samples/replacebycode.html similarity index 100% rename from webroot/js/ckeditor/_samples/replacebycode.html rename to app/webroot/js/ckeditor/_samples/replacebycode.html diff --git a/webroot/js/ckeditor/_samples/sample.css b/app/webroot/js/ckeditor/_samples/sample.css similarity index 100% rename from webroot/js/ckeditor/_samples/sample.css rename to app/webroot/js/ckeditor/_samples/sample.css diff --git a/webroot/js/ckeditor/_samples/sample.js b/app/webroot/js/ckeditor/_samples/sample.js similarity index 100% rename from webroot/js/ckeditor/_samples/sample.js rename to app/webroot/js/ckeditor/_samples/sample.js diff --git a/webroot/js/ckeditor/_samples/sample_posteddata.php b/app/webroot/js/ckeditor/_samples/sample_posteddata.php similarity index 100% rename from webroot/js/ckeditor/_samples/sample_posteddata.php rename to app/webroot/js/ckeditor/_samples/sample_posteddata.php diff --git a/webroot/js/ckeditor/_samples/sharedspaces.html b/app/webroot/js/ckeditor/_samples/sharedspaces.html similarity index 100% rename from webroot/js/ckeditor/_samples/sharedspaces.html rename to app/webroot/js/ckeditor/_samples/sharedspaces.html diff --git a/webroot/js/ckeditor/_samples/skins.html b/app/webroot/js/ckeditor/_samples/skins.html similarity index 100% rename from webroot/js/ckeditor/_samples/skins.html rename to app/webroot/js/ckeditor/_samples/skins.html diff --git a/webroot/js/ckeditor/_samples/stylesheetparser.html b/app/webroot/js/ckeditor/_samples/stylesheetparser.html similarity index 100% rename from webroot/js/ckeditor/_samples/stylesheetparser.html rename to app/webroot/js/ckeditor/_samples/stylesheetparser.html diff --git a/webroot/js/ckeditor/_samples/tableresize.html b/app/webroot/js/ckeditor/_samples/tableresize.html similarity index 100% rename from webroot/js/ckeditor/_samples/tableresize.html rename to app/webroot/js/ckeditor/_samples/tableresize.html diff --git a/webroot/js/ckeditor/_samples/ui_color.html b/app/webroot/js/ckeditor/_samples/ui_color.html similarity index 100% rename from webroot/js/ckeditor/_samples/ui_color.html rename to app/webroot/js/ckeditor/_samples/ui_color.html diff --git a/webroot/js/ckeditor/_samples/ui_languages.html b/app/webroot/js/ckeditor/_samples/ui_languages.html similarity index 100% rename from webroot/js/ckeditor/_samples/ui_languages.html rename to app/webroot/js/ckeditor/_samples/ui_languages.html diff --git a/webroot/js/ckeditor/_source/adapters/jquery.js b/app/webroot/js/ckeditor/_source/adapters/jquery.js similarity index 100% rename from webroot/js/ckeditor/_source/adapters/jquery.js rename to app/webroot/js/ckeditor/_source/adapters/jquery.js diff --git a/webroot/js/ckeditor/_source/core/_bootstrap.js b/app/webroot/js/ckeditor/_source/core/_bootstrap.js similarity index 100% rename from webroot/js/ckeditor/_source/core/_bootstrap.js rename to app/webroot/js/ckeditor/_source/core/_bootstrap.js diff --git a/webroot/js/ckeditor/_source/core/ckeditor.js b/app/webroot/js/ckeditor/_source/core/ckeditor.js similarity index 100% rename from webroot/js/ckeditor/_source/core/ckeditor.js rename to app/webroot/js/ckeditor/_source/core/ckeditor.js diff --git a/webroot/js/ckeditor/_source/core/ckeditor_base.js b/app/webroot/js/ckeditor/_source/core/ckeditor_base.js similarity index 100% rename from webroot/js/ckeditor/_source/core/ckeditor_base.js rename to app/webroot/js/ckeditor/_source/core/ckeditor_base.js diff --git a/webroot/js/ckeditor/_source/core/ckeditor_basic.js b/app/webroot/js/ckeditor/_source/core/ckeditor_basic.js similarity index 100% rename from webroot/js/ckeditor/_source/core/ckeditor_basic.js rename to app/webroot/js/ckeditor/_source/core/ckeditor_basic.js diff --git a/webroot/js/ckeditor/_source/core/command.js b/app/webroot/js/ckeditor/_source/core/command.js similarity index 100% rename from webroot/js/ckeditor/_source/core/command.js rename to app/webroot/js/ckeditor/_source/core/command.js diff --git a/webroot/js/ckeditor/_source/core/commanddefinition.js b/app/webroot/js/ckeditor/_source/core/commanddefinition.js similarity index 100% rename from webroot/js/ckeditor/_source/core/commanddefinition.js rename to app/webroot/js/ckeditor/_source/core/commanddefinition.js diff --git a/webroot/js/ckeditor/_source/core/config.js b/app/webroot/js/ckeditor/_source/core/config.js similarity index 100% rename from webroot/js/ckeditor/_source/core/config.js rename to app/webroot/js/ckeditor/_source/core/config.js diff --git a/webroot/js/ckeditor/_source/core/dataprocessor.js b/app/webroot/js/ckeditor/_source/core/dataprocessor.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dataprocessor.js rename to app/webroot/js/ckeditor/_source/core/dataprocessor.js diff --git a/webroot/js/ckeditor/_source/core/dom.js b/app/webroot/js/ckeditor/_source/core/dom.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom.js rename to app/webroot/js/ckeditor/_source/core/dom.js diff --git a/webroot/js/ckeditor/_source/core/dom/comment.js b/app/webroot/js/ckeditor/_source/core/dom/comment.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/comment.js rename to app/webroot/js/ckeditor/_source/core/dom/comment.js diff --git a/webroot/js/ckeditor/_source/core/dom/document.js b/app/webroot/js/ckeditor/_source/core/dom/document.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/document.js rename to app/webroot/js/ckeditor/_source/core/dom/document.js diff --git a/webroot/js/ckeditor/_source/core/dom/documentfragment.js b/app/webroot/js/ckeditor/_source/core/dom/documentfragment.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/documentfragment.js rename to app/webroot/js/ckeditor/_source/core/dom/documentfragment.js diff --git a/webroot/js/ckeditor/_source/core/dom/domobject.js b/app/webroot/js/ckeditor/_source/core/dom/domobject.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/domobject.js rename to app/webroot/js/ckeditor/_source/core/dom/domobject.js diff --git a/webroot/js/ckeditor/_source/core/dom/element.js b/app/webroot/js/ckeditor/_source/core/dom/element.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/element.js rename to app/webroot/js/ckeditor/_source/core/dom/element.js diff --git a/webroot/js/ckeditor/_source/core/dom/elementpath.js b/app/webroot/js/ckeditor/_source/core/dom/elementpath.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/elementpath.js rename to app/webroot/js/ckeditor/_source/core/dom/elementpath.js diff --git a/webroot/js/ckeditor/_source/core/dom/event.js b/app/webroot/js/ckeditor/_source/core/dom/event.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/event.js rename to app/webroot/js/ckeditor/_source/core/dom/event.js diff --git a/webroot/js/ckeditor/_source/core/dom/node.js b/app/webroot/js/ckeditor/_source/core/dom/node.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/node.js rename to app/webroot/js/ckeditor/_source/core/dom/node.js diff --git a/webroot/js/ckeditor/_source/core/dom/nodelist.js b/app/webroot/js/ckeditor/_source/core/dom/nodelist.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/nodelist.js rename to app/webroot/js/ckeditor/_source/core/dom/nodelist.js diff --git a/webroot/js/ckeditor/_source/core/dom/range.js b/app/webroot/js/ckeditor/_source/core/dom/range.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/range.js rename to app/webroot/js/ckeditor/_source/core/dom/range.js diff --git a/webroot/js/ckeditor/_source/core/dom/rangelist.js b/app/webroot/js/ckeditor/_source/core/dom/rangelist.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/rangelist.js rename to app/webroot/js/ckeditor/_source/core/dom/rangelist.js diff --git a/webroot/js/ckeditor/_source/core/dom/text.js b/app/webroot/js/ckeditor/_source/core/dom/text.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/text.js rename to app/webroot/js/ckeditor/_source/core/dom/text.js diff --git a/webroot/js/ckeditor/_source/core/dom/walker.js b/app/webroot/js/ckeditor/_source/core/dom/walker.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/walker.js rename to app/webroot/js/ckeditor/_source/core/dom/walker.js diff --git a/webroot/js/ckeditor/_source/core/dom/window.js b/app/webroot/js/ckeditor/_source/core/dom/window.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dom/window.js rename to app/webroot/js/ckeditor/_source/core/dom/window.js diff --git a/webroot/js/ckeditor/_source/core/dtd.js b/app/webroot/js/ckeditor/_source/core/dtd.js similarity index 100% rename from webroot/js/ckeditor/_source/core/dtd.js rename to app/webroot/js/ckeditor/_source/core/dtd.js diff --git a/webroot/js/ckeditor/_source/core/editor.js b/app/webroot/js/ckeditor/_source/core/editor.js similarity index 100% rename from webroot/js/ckeditor/_source/core/editor.js rename to app/webroot/js/ckeditor/_source/core/editor.js diff --git a/webroot/js/ckeditor/_source/core/editor_basic.js b/app/webroot/js/ckeditor/_source/core/editor_basic.js similarity index 100% rename from webroot/js/ckeditor/_source/core/editor_basic.js rename to app/webroot/js/ckeditor/_source/core/editor_basic.js diff --git a/webroot/js/ckeditor/_source/core/env.js b/app/webroot/js/ckeditor/_source/core/env.js similarity index 100% rename from webroot/js/ckeditor/_source/core/env.js rename to app/webroot/js/ckeditor/_source/core/env.js diff --git a/webroot/js/ckeditor/_source/core/event.js b/app/webroot/js/ckeditor/_source/core/event.js similarity index 100% rename from webroot/js/ckeditor/_source/core/event.js rename to app/webroot/js/ckeditor/_source/core/event.js diff --git a/webroot/js/ckeditor/_source/core/eventInfo.js b/app/webroot/js/ckeditor/_source/core/eventInfo.js similarity index 100% rename from webroot/js/ckeditor/_source/core/eventInfo.js rename to app/webroot/js/ckeditor/_source/core/eventInfo.js diff --git a/webroot/js/ckeditor/_source/core/focusmanager.js b/app/webroot/js/ckeditor/_source/core/focusmanager.js similarity index 100% rename from webroot/js/ckeditor/_source/core/focusmanager.js rename to app/webroot/js/ckeditor/_source/core/focusmanager.js diff --git a/webroot/js/ckeditor/_source/core/htmlparser.js b/app/webroot/js/ckeditor/_source/core/htmlparser.js similarity index 100% rename from webroot/js/ckeditor/_source/core/htmlparser.js rename to app/webroot/js/ckeditor/_source/core/htmlparser.js diff --git a/webroot/js/ckeditor/_source/core/htmlparser/basicwriter.js b/app/webroot/js/ckeditor/_source/core/htmlparser/basicwriter.js similarity index 100% rename from webroot/js/ckeditor/_source/core/htmlparser/basicwriter.js rename to app/webroot/js/ckeditor/_source/core/htmlparser/basicwriter.js diff --git a/webroot/js/ckeditor/_source/core/htmlparser/cdata.js b/app/webroot/js/ckeditor/_source/core/htmlparser/cdata.js similarity index 100% rename from webroot/js/ckeditor/_source/core/htmlparser/cdata.js rename to app/webroot/js/ckeditor/_source/core/htmlparser/cdata.js diff --git a/webroot/js/ckeditor/_source/core/htmlparser/comment.js b/app/webroot/js/ckeditor/_source/core/htmlparser/comment.js similarity index 100% rename from webroot/js/ckeditor/_source/core/htmlparser/comment.js rename to app/webroot/js/ckeditor/_source/core/htmlparser/comment.js diff --git a/webroot/js/ckeditor/_source/core/htmlparser/element.js b/app/webroot/js/ckeditor/_source/core/htmlparser/element.js similarity index 100% rename from webroot/js/ckeditor/_source/core/htmlparser/element.js rename to app/webroot/js/ckeditor/_source/core/htmlparser/element.js diff --git a/webroot/js/ckeditor/_source/core/htmlparser/filter.js b/app/webroot/js/ckeditor/_source/core/htmlparser/filter.js similarity index 100% rename from webroot/js/ckeditor/_source/core/htmlparser/filter.js rename to app/webroot/js/ckeditor/_source/core/htmlparser/filter.js diff --git a/webroot/js/ckeditor/_source/core/htmlparser/fragment.js b/app/webroot/js/ckeditor/_source/core/htmlparser/fragment.js similarity index 100% rename from webroot/js/ckeditor/_source/core/htmlparser/fragment.js rename to app/webroot/js/ckeditor/_source/core/htmlparser/fragment.js diff --git a/webroot/js/ckeditor/_source/core/htmlparser/text.js b/app/webroot/js/ckeditor/_source/core/htmlparser/text.js similarity index 100% rename from webroot/js/ckeditor/_source/core/htmlparser/text.js rename to app/webroot/js/ckeditor/_source/core/htmlparser/text.js diff --git a/webroot/js/ckeditor/_source/core/lang.js b/app/webroot/js/ckeditor/_source/core/lang.js similarity index 100% rename from webroot/js/ckeditor/_source/core/lang.js rename to app/webroot/js/ckeditor/_source/core/lang.js diff --git a/webroot/js/ckeditor/_source/core/loader.js b/app/webroot/js/ckeditor/_source/core/loader.js similarity index 100% rename from webroot/js/ckeditor/_source/core/loader.js rename to app/webroot/js/ckeditor/_source/core/loader.js diff --git a/webroot/js/ckeditor/_source/core/plugindefinition.js b/app/webroot/js/ckeditor/_source/core/plugindefinition.js similarity index 100% rename from webroot/js/ckeditor/_source/core/plugindefinition.js rename to app/webroot/js/ckeditor/_source/core/plugindefinition.js diff --git a/webroot/js/ckeditor/_source/core/plugins.js b/app/webroot/js/ckeditor/_source/core/plugins.js similarity index 100% rename from webroot/js/ckeditor/_source/core/plugins.js rename to app/webroot/js/ckeditor/_source/core/plugins.js diff --git a/webroot/js/ckeditor/_source/core/resourcemanager.js b/app/webroot/js/ckeditor/_source/core/resourcemanager.js similarity index 100% rename from webroot/js/ckeditor/_source/core/resourcemanager.js rename to app/webroot/js/ckeditor/_source/core/resourcemanager.js diff --git a/webroot/js/ckeditor/_source/core/scriptloader.js b/app/webroot/js/ckeditor/_source/core/scriptloader.js similarity index 100% rename from webroot/js/ckeditor/_source/core/scriptloader.js rename to app/webroot/js/ckeditor/_source/core/scriptloader.js diff --git a/webroot/js/ckeditor/_source/core/skins.js b/app/webroot/js/ckeditor/_source/core/skins.js similarity index 100% rename from webroot/js/ckeditor/_source/core/skins.js rename to app/webroot/js/ckeditor/_source/core/skins.js diff --git a/webroot/js/ckeditor/_source/core/themes.js b/app/webroot/js/ckeditor/_source/core/themes.js similarity index 100% rename from webroot/js/ckeditor/_source/core/themes.js rename to app/webroot/js/ckeditor/_source/core/themes.js diff --git a/webroot/js/ckeditor/_source/core/tools.js b/app/webroot/js/ckeditor/_source/core/tools.js similarity index 100% rename from webroot/js/ckeditor/_source/core/tools.js rename to app/webroot/js/ckeditor/_source/core/tools.js diff --git a/webroot/js/ckeditor/_source/core/ui.js b/app/webroot/js/ckeditor/_source/core/ui.js similarity index 100% rename from webroot/js/ckeditor/_source/core/ui.js rename to app/webroot/js/ckeditor/_source/core/ui.js diff --git a/webroot/js/ckeditor/_source/lang/_languages.js b/app/webroot/js/ckeditor/_source/lang/_languages.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/_languages.js rename to app/webroot/js/ckeditor/_source/lang/_languages.js diff --git a/webroot/js/ckeditor/_source/lang/_translationstatus.txt b/app/webroot/js/ckeditor/_source/lang/_translationstatus.txt similarity index 100% rename from webroot/js/ckeditor/_source/lang/_translationstatus.txt rename to app/webroot/js/ckeditor/_source/lang/_translationstatus.txt diff --git a/webroot/js/ckeditor/_source/lang/af.js b/app/webroot/js/ckeditor/_source/lang/af.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/af.js rename to app/webroot/js/ckeditor/_source/lang/af.js diff --git a/webroot/js/ckeditor/_source/lang/ar.js b/app/webroot/js/ckeditor/_source/lang/ar.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/ar.js rename to app/webroot/js/ckeditor/_source/lang/ar.js diff --git a/webroot/js/ckeditor/_source/lang/bg.js b/app/webroot/js/ckeditor/_source/lang/bg.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/bg.js rename to app/webroot/js/ckeditor/_source/lang/bg.js diff --git a/webroot/js/ckeditor/_source/lang/bn.js b/app/webroot/js/ckeditor/_source/lang/bn.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/bn.js rename to app/webroot/js/ckeditor/_source/lang/bn.js diff --git a/webroot/js/ckeditor/_source/lang/bs.js b/app/webroot/js/ckeditor/_source/lang/bs.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/bs.js rename to app/webroot/js/ckeditor/_source/lang/bs.js diff --git a/webroot/js/ckeditor/_source/lang/ca.js b/app/webroot/js/ckeditor/_source/lang/ca.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/ca.js rename to app/webroot/js/ckeditor/_source/lang/ca.js diff --git a/webroot/js/ckeditor/_source/lang/cs.js b/app/webroot/js/ckeditor/_source/lang/cs.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/cs.js rename to app/webroot/js/ckeditor/_source/lang/cs.js diff --git a/webroot/js/ckeditor/_source/lang/cy.js b/app/webroot/js/ckeditor/_source/lang/cy.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/cy.js rename to app/webroot/js/ckeditor/_source/lang/cy.js diff --git a/webroot/js/ckeditor/_source/lang/da.js b/app/webroot/js/ckeditor/_source/lang/da.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/da.js rename to app/webroot/js/ckeditor/_source/lang/da.js diff --git a/webroot/js/ckeditor/_source/lang/de.js b/app/webroot/js/ckeditor/_source/lang/de.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/de.js rename to app/webroot/js/ckeditor/_source/lang/de.js diff --git a/webroot/js/ckeditor/_source/lang/el.js b/app/webroot/js/ckeditor/_source/lang/el.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/el.js rename to app/webroot/js/ckeditor/_source/lang/el.js diff --git a/webroot/js/ckeditor/_source/lang/en-au.js b/app/webroot/js/ckeditor/_source/lang/en-au.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/en-au.js rename to app/webroot/js/ckeditor/_source/lang/en-au.js diff --git a/webroot/js/ckeditor/_source/lang/en-ca.js b/app/webroot/js/ckeditor/_source/lang/en-ca.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/en-ca.js rename to app/webroot/js/ckeditor/_source/lang/en-ca.js diff --git a/webroot/js/ckeditor/_source/lang/en-gb.js b/app/webroot/js/ckeditor/_source/lang/en-gb.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/en-gb.js rename to app/webroot/js/ckeditor/_source/lang/en-gb.js diff --git a/webroot/js/ckeditor/_source/lang/en.js b/app/webroot/js/ckeditor/_source/lang/en.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/en.js rename to app/webroot/js/ckeditor/_source/lang/en.js diff --git a/webroot/js/ckeditor/_source/lang/eo.js b/app/webroot/js/ckeditor/_source/lang/eo.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/eo.js rename to app/webroot/js/ckeditor/_source/lang/eo.js diff --git a/webroot/js/ckeditor/_source/lang/es.js b/app/webroot/js/ckeditor/_source/lang/es.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/es.js rename to app/webroot/js/ckeditor/_source/lang/es.js diff --git a/webroot/js/ckeditor/_source/lang/et.js b/app/webroot/js/ckeditor/_source/lang/et.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/et.js rename to app/webroot/js/ckeditor/_source/lang/et.js diff --git a/webroot/js/ckeditor/_source/lang/eu.js b/app/webroot/js/ckeditor/_source/lang/eu.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/eu.js rename to app/webroot/js/ckeditor/_source/lang/eu.js diff --git a/webroot/js/ckeditor/_source/lang/fa.js b/app/webroot/js/ckeditor/_source/lang/fa.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/fa.js rename to app/webroot/js/ckeditor/_source/lang/fa.js diff --git a/webroot/js/ckeditor/_source/lang/fi.js b/app/webroot/js/ckeditor/_source/lang/fi.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/fi.js rename to app/webroot/js/ckeditor/_source/lang/fi.js diff --git a/webroot/js/ckeditor/_source/lang/fo.js b/app/webroot/js/ckeditor/_source/lang/fo.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/fo.js rename to app/webroot/js/ckeditor/_source/lang/fo.js diff --git a/webroot/js/ckeditor/_source/lang/fr-ca.js b/app/webroot/js/ckeditor/_source/lang/fr-ca.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/fr-ca.js rename to app/webroot/js/ckeditor/_source/lang/fr-ca.js diff --git a/webroot/js/ckeditor/_source/lang/fr.js b/app/webroot/js/ckeditor/_source/lang/fr.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/fr.js rename to app/webroot/js/ckeditor/_source/lang/fr.js diff --git a/webroot/js/ckeditor/_source/lang/gl.js b/app/webroot/js/ckeditor/_source/lang/gl.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/gl.js rename to app/webroot/js/ckeditor/_source/lang/gl.js diff --git a/webroot/js/ckeditor/_source/lang/gu.js b/app/webroot/js/ckeditor/_source/lang/gu.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/gu.js rename to app/webroot/js/ckeditor/_source/lang/gu.js diff --git a/webroot/js/ckeditor/_source/lang/he.js b/app/webroot/js/ckeditor/_source/lang/he.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/he.js rename to app/webroot/js/ckeditor/_source/lang/he.js diff --git a/webroot/js/ckeditor/_source/lang/hi.js b/app/webroot/js/ckeditor/_source/lang/hi.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/hi.js rename to app/webroot/js/ckeditor/_source/lang/hi.js diff --git a/webroot/js/ckeditor/_source/lang/hr.js b/app/webroot/js/ckeditor/_source/lang/hr.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/hr.js rename to app/webroot/js/ckeditor/_source/lang/hr.js diff --git a/webroot/js/ckeditor/_source/lang/hu.js b/app/webroot/js/ckeditor/_source/lang/hu.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/hu.js rename to app/webroot/js/ckeditor/_source/lang/hu.js diff --git a/webroot/js/ckeditor/_source/lang/is.js b/app/webroot/js/ckeditor/_source/lang/is.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/is.js rename to app/webroot/js/ckeditor/_source/lang/is.js diff --git a/webroot/js/ckeditor/_source/lang/it.js b/app/webroot/js/ckeditor/_source/lang/it.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/it.js rename to app/webroot/js/ckeditor/_source/lang/it.js diff --git a/webroot/js/ckeditor/_source/lang/ja.js b/app/webroot/js/ckeditor/_source/lang/ja.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/ja.js rename to app/webroot/js/ckeditor/_source/lang/ja.js diff --git a/webroot/js/ckeditor/_source/lang/ka.js b/app/webroot/js/ckeditor/_source/lang/ka.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/ka.js rename to app/webroot/js/ckeditor/_source/lang/ka.js diff --git a/webroot/js/ckeditor/_source/lang/km.js b/app/webroot/js/ckeditor/_source/lang/km.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/km.js rename to app/webroot/js/ckeditor/_source/lang/km.js diff --git a/webroot/js/ckeditor/_source/lang/ko.js b/app/webroot/js/ckeditor/_source/lang/ko.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/ko.js rename to app/webroot/js/ckeditor/_source/lang/ko.js diff --git a/webroot/js/ckeditor/_source/lang/lt.js b/app/webroot/js/ckeditor/_source/lang/lt.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/lt.js rename to app/webroot/js/ckeditor/_source/lang/lt.js diff --git a/webroot/js/ckeditor/_source/lang/lv.js b/app/webroot/js/ckeditor/_source/lang/lv.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/lv.js rename to app/webroot/js/ckeditor/_source/lang/lv.js diff --git a/webroot/js/ckeditor/_source/lang/mn.js b/app/webroot/js/ckeditor/_source/lang/mn.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/mn.js rename to app/webroot/js/ckeditor/_source/lang/mn.js diff --git a/webroot/js/ckeditor/_source/lang/ms.js b/app/webroot/js/ckeditor/_source/lang/ms.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/ms.js rename to app/webroot/js/ckeditor/_source/lang/ms.js diff --git a/webroot/js/ckeditor/_source/lang/nb.js b/app/webroot/js/ckeditor/_source/lang/nb.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/nb.js rename to app/webroot/js/ckeditor/_source/lang/nb.js diff --git a/webroot/js/ckeditor/_source/lang/nl.js b/app/webroot/js/ckeditor/_source/lang/nl.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/nl.js rename to app/webroot/js/ckeditor/_source/lang/nl.js diff --git a/webroot/js/ckeditor/_source/lang/no.js b/app/webroot/js/ckeditor/_source/lang/no.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/no.js rename to app/webroot/js/ckeditor/_source/lang/no.js diff --git a/webroot/js/ckeditor/_source/lang/pl.js b/app/webroot/js/ckeditor/_source/lang/pl.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/pl.js rename to app/webroot/js/ckeditor/_source/lang/pl.js diff --git a/webroot/js/ckeditor/_source/lang/pt-br.js b/app/webroot/js/ckeditor/_source/lang/pt-br.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/pt-br.js rename to app/webroot/js/ckeditor/_source/lang/pt-br.js diff --git a/webroot/js/ckeditor/_source/lang/pt.js b/app/webroot/js/ckeditor/_source/lang/pt.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/pt.js rename to app/webroot/js/ckeditor/_source/lang/pt.js diff --git a/webroot/js/ckeditor/_source/lang/ro.js b/app/webroot/js/ckeditor/_source/lang/ro.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/ro.js rename to app/webroot/js/ckeditor/_source/lang/ro.js diff --git a/webroot/js/ckeditor/_source/lang/ru.js b/app/webroot/js/ckeditor/_source/lang/ru.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/ru.js rename to app/webroot/js/ckeditor/_source/lang/ru.js diff --git a/webroot/js/ckeditor/_source/lang/sk.js b/app/webroot/js/ckeditor/_source/lang/sk.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/sk.js rename to app/webroot/js/ckeditor/_source/lang/sk.js diff --git a/webroot/js/ckeditor/_source/lang/sl.js b/app/webroot/js/ckeditor/_source/lang/sl.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/sl.js rename to app/webroot/js/ckeditor/_source/lang/sl.js diff --git a/webroot/js/ckeditor/_source/lang/sr-latn.js b/app/webroot/js/ckeditor/_source/lang/sr-latn.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/sr-latn.js rename to app/webroot/js/ckeditor/_source/lang/sr-latn.js diff --git a/webroot/js/ckeditor/_source/lang/sr.js b/app/webroot/js/ckeditor/_source/lang/sr.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/sr.js rename to app/webroot/js/ckeditor/_source/lang/sr.js diff --git a/webroot/js/ckeditor/_source/lang/sv.js b/app/webroot/js/ckeditor/_source/lang/sv.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/sv.js rename to app/webroot/js/ckeditor/_source/lang/sv.js diff --git a/webroot/js/ckeditor/_source/lang/th.js b/app/webroot/js/ckeditor/_source/lang/th.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/th.js rename to app/webroot/js/ckeditor/_source/lang/th.js diff --git a/webroot/js/ckeditor/_source/lang/tr.js b/app/webroot/js/ckeditor/_source/lang/tr.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/tr.js rename to app/webroot/js/ckeditor/_source/lang/tr.js diff --git a/webroot/js/ckeditor/_source/lang/uk.js b/app/webroot/js/ckeditor/_source/lang/uk.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/uk.js rename to app/webroot/js/ckeditor/_source/lang/uk.js diff --git a/webroot/js/ckeditor/_source/lang/vi.js b/app/webroot/js/ckeditor/_source/lang/vi.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/vi.js rename to app/webroot/js/ckeditor/_source/lang/vi.js diff --git a/webroot/js/ckeditor/_source/lang/zh-cn.js b/app/webroot/js/ckeditor/_source/lang/zh-cn.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/zh-cn.js rename to app/webroot/js/ckeditor/_source/lang/zh-cn.js diff --git a/webroot/js/ckeditor/_source/lang/zh.js b/app/webroot/js/ckeditor/_source/lang/zh.js similarity index 100% rename from webroot/js/ckeditor/_source/lang/zh.js rename to app/webroot/js/ckeditor/_source/lang/zh.js diff --git a/webroot/js/ckeditor/_source/plugins/a11yhelp/dialogs/a11yhelp.js b/app/webroot/js/ckeditor/_source/plugins/a11yhelp/dialogs/a11yhelp.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/a11yhelp/dialogs/a11yhelp.js rename to app/webroot/js/ckeditor/_source/plugins/a11yhelp/dialogs/a11yhelp.js diff --git a/webroot/js/ckeditor/_source/plugins/a11yhelp/lang/en.js b/app/webroot/js/ckeditor/_source/plugins/a11yhelp/lang/en.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/a11yhelp/lang/en.js rename to app/webroot/js/ckeditor/_source/plugins/a11yhelp/lang/en.js diff --git a/webroot/js/ckeditor/_source/plugins/a11yhelp/lang/he.js b/app/webroot/js/ckeditor/_source/plugins/a11yhelp/lang/he.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/a11yhelp/lang/he.js rename to app/webroot/js/ckeditor/_source/plugins/a11yhelp/lang/he.js diff --git a/webroot/js/ckeditor/_source/plugins/a11yhelp/plugin.js b/app/webroot/js/ckeditor/_source/plugins/a11yhelp/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/a11yhelp/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/a11yhelp/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/about/dialogs/about.js b/app/webroot/js/ckeditor/_source/plugins/about/dialogs/about.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/about/dialogs/about.js rename to app/webroot/js/ckeditor/_source/plugins/about/dialogs/about.js diff --git a/webroot/js/ckeditor/_source/plugins/about/dialogs/logo_ckeditor.png b/app/webroot/js/ckeditor/_source/plugins/about/dialogs/logo_ckeditor.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/about/dialogs/logo_ckeditor.png rename to app/webroot/js/ckeditor/_source/plugins/about/dialogs/logo_ckeditor.png diff --git a/webroot/js/ckeditor/_source/plugins/about/plugin.js b/app/webroot/js/ckeditor/_source/plugins/about/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/about/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/about/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/adobeair/plugin.js b/app/webroot/js/ckeditor/_source/plugins/adobeair/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/adobeair/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/adobeair/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/ajax/plugin.js b/app/webroot/js/ckeditor/_source/plugins/ajax/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/ajax/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/ajax/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/autogrow/plugin.js b/app/webroot/js/ckeditor/_source/plugins/autogrow/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/autogrow/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/autogrow/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/basicstyles/plugin.js b/app/webroot/js/ckeditor/_source/plugins/basicstyles/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/basicstyles/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/basicstyles/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/bbcode/plugin.js b/app/webroot/js/ckeditor/_source/plugins/bbcode/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/bbcode/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/bbcode/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/bidi/plugin.js b/app/webroot/js/ckeditor/_source/plugins/bidi/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/bidi/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/bidi/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/blockquote/plugin.js b/app/webroot/js/ckeditor/_source/plugins/blockquote/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/blockquote/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/blockquote/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/button/plugin.js b/app/webroot/js/ckeditor/_source/plugins/button/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/button/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/button/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/clipboard/dialogs/paste.js b/app/webroot/js/ckeditor/_source/plugins/clipboard/dialogs/paste.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/clipboard/dialogs/paste.js rename to app/webroot/js/ckeditor/_source/plugins/clipboard/dialogs/paste.js diff --git a/webroot/js/ckeditor/_source/plugins/clipboard/plugin.js b/app/webroot/js/ckeditor/_source/plugins/clipboard/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/clipboard/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/clipboard/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/colorbutton/plugin.js b/app/webroot/js/ckeditor/_source/plugins/colorbutton/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/colorbutton/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/colorbutton/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/colordialog/dialogs/colordialog.js b/app/webroot/js/ckeditor/_source/plugins/colordialog/dialogs/colordialog.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/colordialog/dialogs/colordialog.js rename to app/webroot/js/ckeditor/_source/plugins/colordialog/dialogs/colordialog.js diff --git a/webroot/js/ckeditor/_source/plugins/colordialog/plugin.js b/app/webroot/js/ckeditor/_source/plugins/colordialog/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/colordialog/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/colordialog/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/contextmenu/plugin.js b/app/webroot/js/ckeditor/_source/plugins/contextmenu/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/contextmenu/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/contextmenu/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/devtools/lang/en.js b/app/webroot/js/ckeditor/_source/plugins/devtools/lang/en.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/devtools/lang/en.js rename to app/webroot/js/ckeditor/_source/plugins/devtools/lang/en.js diff --git a/webroot/js/ckeditor/_source/plugins/devtools/plugin.js b/app/webroot/js/ckeditor/_source/plugins/devtools/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/devtools/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/devtools/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/dialog/dialogDefinition.js b/app/webroot/js/ckeditor/_source/plugins/dialog/dialogDefinition.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/dialog/dialogDefinition.js rename to app/webroot/js/ckeditor/_source/plugins/dialog/dialogDefinition.js diff --git a/webroot/js/ckeditor/_source/plugins/dialog/plugin.js b/app/webroot/js/ckeditor/_source/plugins/dialog/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/dialog/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/dialog/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/dialogadvtab/plugin.js b/app/webroot/js/ckeditor/_source/plugins/dialogadvtab/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/dialogadvtab/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/dialogadvtab/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/dialogui/plugin.js b/app/webroot/js/ckeditor/_source/plugins/dialogui/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/dialogui/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/dialogui/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/div/dialogs/div.js b/app/webroot/js/ckeditor/_source/plugins/div/dialogs/div.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/div/dialogs/div.js rename to app/webroot/js/ckeditor/_source/plugins/div/dialogs/div.js diff --git a/webroot/js/ckeditor/_source/plugins/div/plugin.js b/app/webroot/js/ckeditor/_source/plugins/div/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/div/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/div/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/docprops/dialogs/docprops.js b/app/webroot/js/ckeditor/_source/plugins/docprops/dialogs/docprops.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/docprops/dialogs/docprops.js rename to app/webroot/js/ckeditor/_source/plugins/docprops/dialogs/docprops.js diff --git a/webroot/js/ckeditor/_source/plugins/docprops/plugin.js b/app/webroot/js/ckeditor/_source/plugins/docprops/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/docprops/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/docprops/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/domiterator/plugin.js b/app/webroot/js/ckeditor/_source/plugins/domiterator/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/domiterator/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/domiterator/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/editingblock/plugin.js b/app/webroot/js/ckeditor/_source/plugins/editingblock/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/editingblock/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/editingblock/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/elementspath/plugin.js b/app/webroot/js/ckeditor/_source/plugins/elementspath/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/elementspath/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/elementspath/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/enterkey/plugin.js b/app/webroot/js/ckeditor/_source/plugins/enterkey/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/enterkey/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/enterkey/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/entities/plugin.js b/app/webroot/js/ckeditor/_source/plugins/entities/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/entities/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/entities/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/fakeobjects/plugin.js b/app/webroot/js/ckeditor/_source/plugins/fakeobjects/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/fakeobjects/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/fakeobjects/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/filebrowser/plugin.js b/app/webroot/js/ckeditor/_source/plugins/filebrowser/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/filebrowser/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/filebrowser/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/find/dialogs/find.js b/app/webroot/js/ckeditor/_source/plugins/find/dialogs/find.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/find/dialogs/find.js rename to app/webroot/js/ckeditor/_source/plugins/find/dialogs/find.js diff --git a/webroot/js/ckeditor/_source/plugins/find/plugin.js b/app/webroot/js/ckeditor/_source/plugins/find/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/find/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/find/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/flash/dialogs/flash.js b/app/webroot/js/ckeditor/_source/plugins/flash/dialogs/flash.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/flash/dialogs/flash.js rename to app/webroot/js/ckeditor/_source/plugins/flash/dialogs/flash.js diff --git a/webroot/js/ckeditor/_source/plugins/flash/images/placeholder.png b/app/webroot/js/ckeditor/_source/plugins/flash/images/placeholder.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/flash/images/placeholder.png rename to app/webroot/js/ckeditor/_source/plugins/flash/images/placeholder.png diff --git a/webroot/js/ckeditor/_source/plugins/flash/plugin.js b/app/webroot/js/ckeditor/_source/plugins/flash/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/flash/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/flash/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/floatpanel/plugin.js b/app/webroot/js/ckeditor/_source/plugins/floatpanel/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/floatpanel/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/floatpanel/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/font/plugin.js b/app/webroot/js/ckeditor/_source/plugins/font/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/font/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/font/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/format/plugin.js b/app/webroot/js/ckeditor/_source/plugins/format/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/format/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/format/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/dialogs/button.js b/app/webroot/js/ckeditor/_source/plugins/forms/dialogs/button.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/dialogs/button.js rename to app/webroot/js/ckeditor/_source/plugins/forms/dialogs/button.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/dialogs/checkbox.js b/app/webroot/js/ckeditor/_source/plugins/forms/dialogs/checkbox.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/dialogs/checkbox.js rename to app/webroot/js/ckeditor/_source/plugins/forms/dialogs/checkbox.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/dialogs/form.js b/app/webroot/js/ckeditor/_source/plugins/forms/dialogs/form.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/dialogs/form.js rename to app/webroot/js/ckeditor/_source/plugins/forms/dialogs/form.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/dialogs/hiddenfield.js b/app/webroot/js/ckeditor/_source/plugins/forms/dialogs/hiddenfield.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/dialogs/hiddenfield.js rename to app/webroot/js/ckeditor/_source/plugins/forms/dialogs/hiddenfield.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/dialogs/radio.js b/app/webroot/js/ckeditor/_source/plugins/forms/dialogs/radio.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/dialogs/radio.js rename to app/webroot/js/ckeditor/_source/plugins/forms/dialogs/radio.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/dialogs/select.js b/app/webroot/js/ckeditor/_source/plugins/forms/dialogs/select.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/dialogs/select.js rename to app/webroot/js/ckeditor/_source/plugins/forms/dialogs/select.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/dialogs/textarea.js b/app/webroot/js/ckeditor/_source/plugins/forms/dialogs/textarea.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/dialogs/textarea.js rename to app/webroot/js/ckeditor/_source/plugins/forms/dialogs/textarea.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/dialogs/textfield.js b/app/webroot/js/ckeditor/_source/plugins/forms/dialogs/textfield.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/dialogs/textfield.js rename to app/webroot/js/ckeditor/_source/plugins/forms/dialogs/textfield.js diff --git a/webroot/js/ckeditor/_source/plugins/forms/images/hiddenfield.gif b/app/webroot/js/ckeditor/_source/plugins/forms/images/hiddenfield.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/images/hiddenfield.gif rename to app/webroot/js/ckeditor/_source/plugins/forms/images/hiddenfield.gif diff --git a/webroot/js/ckeditor/_source/plugins/forms/plugin.js b/app/webroot/js/ckeditor/_source/plugins/forms/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/forms/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/forms/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/horizontalrule/plugin.js b/app/webroot/js/ckeditor/_source/plugins/horizontalrule/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/horizontalrule/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/horizontalrule/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/htmldataprocessor/plugin.js b/app/webroot/js/ckeditor/_source/plugins/htmldataprocessor/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/htmldataprocessor/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/htmldataprocessor/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/htmlwriter/plugin.js b/app/webroot/js/ckeditor/_source/plugins/htmlwriter/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/htmlwriter/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/htmlwriter/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/iframe/dialogs/iframe.js b/app/webroot/js/ckeditor/_source/plugins/iframe/dialogs/iframe.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/iframe/dialogs/iframe.js rename to app/webroot/js/ckeditor/_source/plugins/iframe/dialogs/iframe.js diff --git a/webroot/js/ckeditor/_source/plugins/iframe/images/placeholder.png b/app/webroot/js/ckeditor/_source/plugins/iframe/images/placeholder.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/iframe/images/placeholder.png rename to app/webroot/js/ckeditor/_source/plugins/iframe/images/placeholder.png diff --git a/webroot/js/ckeditor/_source/plugins/iframe/plugin.js b/app/webroot/js/ckeditor/_source/plugins/iframe/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/iframe/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/iframe/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/iframedialog/plugin.js b/app/webroot/js/ckeditor/_source/plugins/iframedialog/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/iframedialog/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/iframedialog/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/image/dialogs/image.js b/app/webroot/js/ckeditor/_source/plugins/image/dialogs/image.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/image/dialogs/image.js rename to app/webroot/js/ckeditor/_source/plugins/image/dialogs/image.js diff --git a/webroot/js/ckeditor/_source/plugins/image/plugin.js b/app/webroot/js/ckeditor/_source/plugins/image/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/image/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/image/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/indent/plugin.js b/app/webroot/js/ckeditor/_source/plugins/indent/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/indent/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/indent/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/justify/plugin.js b/app/webroot/js/ckeditor/_source/plugins/justify/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/justify/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/justify/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/keystrokes/plugin.js b/app/webroot/js/ckeditor/_source/plugins/keystrokes/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/keystrokes/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/keystrokes/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/link/dialogs/anchor.js b/app/webroot/js/ckeditor/_source/plugins/link/dialogs/anchor.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/link/dialogs/anchor.js rename to app/webroot/js/ckeditor/_source/plugins/link/dialogs/anchor.js diff --git a/webroot/js/ckeditor/_source/plugins/link/dialogs/link.js b/app/webroot/js/ckeditor/_source/plugins/link/dialogs/link.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/link/dialogs/link.js rename to app/webroot/js/ckeditor/_source/plugins/link/dialogs/link.js diff --git a/webroot/js/ckeditor/_source/plugins/link/images/anchor.gif b/app/webroot/js/ckeditor/_source/plugins/link/images/anchor.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/link/images/anchor.gif rename to app/webroot/js/ckeditor/_source/plugins/link/images/anchor.gif diff --git a/webroot/js/ckeditor/_source/plugins/link/plugin.js b/app/webroot/js/ckeditor/_source/plugins/link/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/link/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/link/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/list/plugin.js b/app/webroot/js/ckeditor/_source/plugins/list/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/list/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/list/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/listblock/plugin.js b/app/webroot/js/ckeditor/_source/plugins/listblock/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/listblock/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/listblock/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/liststyle/dialogs/liststyle.js b/app/webroot/js/ckeditor/_source/plugins/liststyle/dialogs/liststyle.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/liststyle/dialogs/liststyle.js rename to app/webroot/js/ckeditor/_source/plugins/liststyle/dialogs/liststyle.js diff --git a/webroot/js/ckeditor/_source/plugins/liststyle/plugin.js b/app/webroot/js/ckeditor/_source/plugins/liststyle/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/liststyle/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/liststyle/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/maximize/plugin.js b/app/webroot/js/ckeditor/_source/plugins/maximize/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/maximize/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/maximize/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/menu/plugin.js b/app/webroot/js/ckeditor/_source/plugins/menu/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/menu/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/menu/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/menubutton/plugin.js b/app/webroot/js/ckeditor/_source/plugins/menubutton/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/menubutton/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/menubutton/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/newpage/plugin.js b/app/webroot/js/ckeditor/_source/plugins/newpage/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/newpage/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/newpage/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/pagebreak/images/pagebreak.gif b/app/webroot/js/ckeditor/_source/plugins/pagebreak/images/pagebreak.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/pagebreak/images/pagebreak.gif rename to app/webroot/js/ckeditor/_source/plugins/pagebreak/images/pagebreak.gif diff --git a/webroot/js/ckeditor/_source/plugins/pagebreak/plugin.js b/app/webroot/js/ckeditor/_source/plugins/pagebreak/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/pagebreak/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/pagebreak/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/panel/plugin.js b/app/webroot/js/ckeditor/_source/plugins/panel/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/panel/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/panel/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/panelbutton/plugin.js b/app/webroot/js/ckeditor/_source/plugins/panelbutton/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/panelbutton/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/panelbutton/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/pastefromword/filter/default.js b/app/webroot/js/ckeditor/_source/plugins/pastefromword/filter/default.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/pastefromword/filter/default.js rename to app/webroot/js/ckeditor/_source/plugins/pastefromword/filter/default.js diff --git a/webroot/js/ckeditor/_source/plugins/pastefromword/plugin.js b/app/webroot/js/ckeditor/_source/plugins/pastefromword/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/pastefromword/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/pastefromword/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/pastetext/dialogs/pastetext.js b/app/webroot/js/ckeditor/_source/plugins/pastetext/dialogs/pastetext.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/pastetext/dialogs/pastetext.js rename to app/webroot/js/ckeditor/_source/plugins/pastetext/dialogs/pastetext.js diff --git a/webroot/js/ckeditor/_source/plugins/pastetext/plugin.js b/app/webroot/js/ckeditor/_source/plugins/pastetext/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/pastetext/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/pastetext/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/placeholder/dialogs/placeholder.js b/app/webroot/js/ckeditor/_source/plugins/placeholder/dialogs/placeholder.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/placeholder/dialogs/placeholder.js rename to app/webroot/js/ckeditor/_source/plugins/placeholder/dialogs/placeholder.js diff --git a/webroot/js/ckeditor/_source/plugins/placeholder/lang/en.js b/app/webroot/js/ckeditor/_source/plugins/placeholder/lang/en.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/placeholder/lang/en.js rename to app/webroot/js/ckeditor/_source/plugins/placeholder/lang/en.js diff --git a/webroot/js/ckeditor/_source/plugins/placeholder/lang/he.js b/app/webroot/js/ckeditor/_source/plugins/placeholder/lang/he.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/placeholder/lang/he.js rename to app/webroot/js/ckeditor/_source/plugins/placeholder/lang/he.js diff --git a/webroot/js/ckeditor/_source/plugins/placeholder/placeholder.gif b/app/webroot/js/ckeditor/_source/plugins/placeholder/placeholder.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/placeholder/placeholder.gif rename to app/webroot/js/ckeditor/_source/plugins/placeholder/placeholder.gif diff --git a/webroot/js/ckeditor/_source/plugins/placeholder/plugin.js b/app/webroot/js/ckeditor/_source/plugins/placeholder/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/placeholder/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/placeholder/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/popup/plugin.js b/app/webroot/js/ckeditor/_source/plugins/popup/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/popup/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/popup/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/preview/plugin.js b/app/webroot/js/ckeditor/_source/plugins/preview/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/preview/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/preview/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/print/plugin.js b/app/webroot/js/ckeditor/_source/plugins/print/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/print/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/print/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/removeformat/plugin.js b/app/webroot/js/ckeditor/_source/plugins/removeformat/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/removeformat/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/removeformat/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/resize/plugin.js b/app/webroot/js/ckeditor/_source/plugins/resize/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/resize/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/resize/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/richcombo/plugin.js b/app/webroot/js/ckeditor/_source/plugins/richcombo/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/richcombo/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/richcombo/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/save/plugin.js b/app/webroot/js/ckeditor/_source/plugins/save/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/save/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/save/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/scayt/dialogs/options.js b/app/webroot/js/ckeditor/_source/plugins/scayt/dialogs/options.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/scayt/dialogs/options.js rename to app/webroot/js/ckeditor/_source/plugins/scayt/dialogs/options.js diff --git a/webroot/js/ckeditor/_source/plugins/scayt/dialogs/toolbar.css b/app/webroot/js/ckeditor/_source/plugins/scayt/dialogs/toolbar.css similarity index 100% rename from webroot/js/ckeditor/_source/plugins/scayt/dialogs/toolbar.css rename to app/webroot/js/ckeditor/_source/plugins/scayt/dialogs/toolbar.css diff --git a/webroot/js/ckeditor/_source/plugins/scayt/plugin.js b/app/webroot/js/ckeditor/_source/plugins/scayt/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/scayt/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/scayt/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/selection/plugin.js b/app/webroot/js/ckeditor/_source/plugins/selection/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/selection/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/selection/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_address.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_address.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_address.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_address.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_blockquote.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_blockquote.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_blockquote.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_blockquote.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_div.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_div.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_div.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_div.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h1.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h1.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_h1.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h1.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h2.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h2.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_h2.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h2.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h3.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h3.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_h3.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h3.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h4.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h4.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_h4.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h4.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h5.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h5.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_h5.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h5.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h6.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h6.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_h6.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_h6.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_p.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_p.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_p.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_p.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/images/block_pre.png b/app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_pre.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/images/block_pre.png rename to app/webroot/js/ckeditor/_source/plugins/showblocks/images/block_pre.png diff --git a/webroot/js/ckeditor/_source/plugins/showblocks/plugin.js b/app/webroot/js/ckeditor/_source/plugins/showblocks/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showblocks/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/showblocks/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/showborders/plugin.js b/app/webroot/js/ckeditor/_source/plugins/showborders/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/showborders/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/showborders/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/smiley/dialogs/smiley.js b/app/webroot/js/ckeditor/_source/plugins/smiley/dialogs/smiley.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/dialogs/smiley.js rename to app/webroot/js/ckeditor/_source/plugins/smiley/dialogs/smiley.js diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/angel_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/angel_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/angel_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/angel_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/angry_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/angry_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/angry_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/angry_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/broken_heart.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/broken_heart.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/broken_heart.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/broken_heart.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/confused_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/confused_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/confused_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/confused_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/cry_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/cry_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/cry_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/cry_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/devil_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/devil_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/devil_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/devil_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/embaressed_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/embaressed_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/embaressed_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/embaressed_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/envelope.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/envelope.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/envelope.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/envelope.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/heart.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/heart.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/heart.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/heart.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/kiss.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/kiss.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/kiss.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/kiss.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/lightbulb.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/lightbulb.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/lightbulb.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/lightbulb.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/omg_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/omg_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/omg_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/omg_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/regular_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/regular_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/regular_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/regular_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/sad_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/sad_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/sad_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/sad_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/shades_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/shades_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/shades_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/shades_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/teeth_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/teeth_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/teeth_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/teeth_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/thumbs_down.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/thumbs_down.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/thumbs_down.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/thumbs_down.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/thumbs_up.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/thumbs_up.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/thumbs_up.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/thumbs_up.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/tounge_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/tounge_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/tounge_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/tounge_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/whatchutalkingabout_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/whatchutalkingabout_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/whatchutalkingabout_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/whatchutalkingabout_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/images/wink_smile.gif b/app/webroot/js/ckeditor/_source/plugins/smiley/images/wink_smile.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/images/wink_smile.gif rename to app/webroot/js/ckeditor/_source/plugins/smiley/images/wink_smile.gif diff --git a/webroot/js/ckeditor/_source/plugins/smiley/plugin.js b/app/webroot/js/ckeditor/_source/plugins/smiley/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/smiley/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/smiley/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/sourcearea/plugin.js b/app/webroot/js/ckeditor/_source/plugins/sourcearea/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/sourcearea/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/sourcearea/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/specialchar/dialogs/specialchar.js b/app/webroot/js/ckeditor/_source/plugins/specialchar/dialogs/specialchar.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/specialchar/dialogs/specialchar.js rename to app/webroot/js/ckeditor/_source/plugins/specialchar/dialogs/specialchar.js diff --git a/webroot/js/ckeditor/_source/plugins/specialchar/lang/en.js b/app/webroot/js/ckeditor/_source/plugins/specialchar/lang/en.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/specialchar/lang/en.js rename to app/webroot/js/ckeditor/_source/plugins/specialchar/lang/en.js diff --git a/webroot/js/ckeditor/_source/plugins/specialchar/plugin.js b/app/webroot/js/ckeditor/_source/plugins/specialchar/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/specialchar/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/specialchar/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/styles/plugin.js b/app/webroot/js/ckeditor/_source/plugins/styles/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/styles/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/styles/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/styles/styles/default.js b/app/webroot/js/ckeditor/_source/plugins/styles/styles/default.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/styles/styles/default.js rename to app/webroot/js/ckeditor/_source/plugins/styles/styles/default.js diff --git a/webroot/js/ckeditor/_source/plugins/stylescombo/plugin.js b/app/webroot/js/ckeditor/_source/plugins/stylescombo/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/stylescombo/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/stylescombo/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/stylesheetparser/plugin.js b/app/webroot/js/ckeditor/_source/plugins/stylesheetparser/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/stylesheetparser/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/stylesheetparser/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/tab/plugin.js b/app/webroot/js/ckeditor/_source/plugins/tab/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/tab/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/tab/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/table/dialogs/table.js b/app/webroot/js/ckeditor/_source/plugins/table/dialogs/table.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/table/dialogs/table.js rename to app/webroot/js/ckeditor/_source/plugins/table/dialogs/table.js diff --git a/webroot/js/ckeditor/_source/plugins/table/plugin.js b/app/webroot/js/ckeditor/_source/plugins/table/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/table/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/table/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/tableresize/plugin.js b/app/webroot/js/ckeditor/_source/plugins/tableresize/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/tableresize/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/tableresize/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/tabletools/dialogs/tableCell.js b/app/webroot/js/ckeditor/_source/plugins/tabletools/dialogs/tableCell.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/tabletools/dialogs/tableCell.js rename to app/webroot/js/ckeditor/_source/plugins/tabletools/dialogs/tableCell.js diff --git a/webroot/js/ckeditor/_source/plugins/tabletools/plugin.js b/app/webroot/js/ckeditor/_source/plugins/tabletools/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/tabletools/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/tabletools/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/templates/dialogs/templates.js b/app/webroot/js/ckeditor/_source/plugins/templates/dialogs/templates.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/templates/dialogs/templates.js rename to app/webroot/js/ckeditor/_source/plugins/templates/dialogs/templates.js diff --git a/webroot/js/ckeditor/_source/plugins/templates/plugin.js b/app/webroot/js/ckeditor/_source/plugins/templates/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/templates/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/templates/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/templates/templates/default.js b/app/webroot/js/ckeditor/_source/plugins/templates/templates/default.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/templates/templates/default.js rename to app/webroot/js/ckeditor/_source/plugins/templates/templates/default.js diff --git a/webroot/js/ckeditor/_source/plugins/templates/templates/images/template1.gif b/app/webroot/js/ckeditor/_source/plugins/templates/templates/images/template1.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/templates/templates/images/template1.gif rename to app/webroot/js/ckeditor/_source/plugins/templates/templates/images/template1.gif diff --git a/webroot/js/ckeditor/_source/plugins/templates/templates/images/template2.gif b/app/webroot/js/ckeditor/_source/plugins/templates/templates/images/template2.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/templates/templates/images/template2.gif rename to app/webroot/js/ckeditor/_source/plugins/templates/templates/images/template2.gif diff --git a/webroot/js/ckeditor/_source/plugins/templates/templates/images/template3.gif b/app/webroot/js/ckeditor/_source/plugins/templates/templates/images/template3.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/templates/templates/images/template3.gif rename to app/webroot/js/ckeditor/_source/plugins/templates/templates/images/template3.gif diff --git a/webroot/js/ckeditor/_source/plugins/toolbar/plugin.js b/app/webroot/js/ckeditor/_source/plugins/toolbar/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/toolbar/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/toolbar/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/dialogs/uicolor.js b/app/webroot/js/ckeditor/_source/plugins/uicolor/dialogs/uicolor.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/dialogs/uicolor.js rename to app/webroot/js/ckeditor/_source/plugins/uicolor/dialogs/uicolor.js diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/lang/en.js b/app/webroot/js/ckeditor/_source/plugins/uicolor/lang/en.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/lang/en.js rename to app/webroot/js/ckeditor/_source/plugins/uicolor/lang/en.js diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/lang/he.js b/app/webroot/js/ckeditor/_source/plugins/uicolor/lang/he.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/lang/he.js rename to app/webroot/js/ckeditor/_source/plugins/uicolor/lang/he.js diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/plugin.js b/app/webroot/js/ckeditor/_source/plugins/uicolor/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/uicolor/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/uicolor.gif b/app/webroot/js/ckeditor/_source/plugins/uicolor/uicolor.gif similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/uicolor.gif rename to app/webroot/js/ckeditor/_source/plugins/uicolor/uicolor.gif diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/hue_bg.png b/app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/hue_bg.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/hue_bg.png rename to app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/hue_bg.png diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/hue_thumb.png b/app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/hue_thumb.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/hue_thumb.png rename to app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/hue_thumb.png diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/picker_mask.png b/app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/picker_mask.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/picker_mask.png rename to app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/picker_mask.png diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/picker_thumb.png b/app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/picker_thumb.png similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/picker_thumb.png rename to app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/picker_thumb.png diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/yui.css b/app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/yui.css similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/yui.css rename to app/webroot/js/ckeditor/_source/plugins/uicolor/yui/assets/yui.css diff --git a/webroot/js/ckeditor/_source/plugins/uicolor/yui/yui.js b/app/webroot/js/ckeditor/_source/plugins/uicolor/yui/yui.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/uicolor/yui/yui.js rename to app/webroot/js/ckeditor/_source/plugins/uicolor/yui/yui.js diff --git a/webroot/js/ckeditor/_source/plugins/undo/plugin.js b/app/webroot/js/ckeditor/_source/plugins/undo/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/undo/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/undo/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/wsc/dialogs/ciframe.html b/app/webroot/js/ckeditor/_source/plugins/wsc/dialogs/ciframe.html similarity index 100% rename from webroot/js/ckeditor/_source/plugins/wsc/dialogs/ciframe.html rename to app/webroot/js/ckeditor/_source/plugins/wsc/dialogs/ciframe.html diff --git a/webroot/js/ckeditor/_source/plugins/wsc/dialogs/tmpFrameset.html b/app/webroot/js/ckeditor/_source/plugins/wsc/dialogs/tmpFrameset.html similarity index 100% rename from webroot/js/ckeditor/_source/plugins/wsc/dialogs/tmpFrameset.html rename to app/webroot/js/ckeditor/_source/plugins/wsc/dialogs/tmpFrameset.html diff --git a/webroot/js/ckeditor/_source/plugins/wsc/dialogs/wsc.css b/app/webroot/js/ckeditor/_source/plugins/wsc/dialogs/wsc.css similarity index 100% rename from webroot/js/ckeditor/_source/plugins/wsc/dialogs/wsc.css rename to app/webroot/js/ckeditor/_source/plugins/wsc/dialogs/wsc.css diff --git a/webroot/js/ckeditor/_source/plugins/wsc/dialogs/wsc.js b/app/webroot/js/ckeditor/_source/plugins/wsc/dialogs/wsc.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/wsc/dialogs/wsc.js rename to app/webroot/js/ckeditor/_source/plugins/wsc/dialogs/wsc.js diff --git a/webroot/js/ckeditor/_source/plugins/wsc/plugin.js b/app/webroot/js/ckeditor/_source/plugins/wsc/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/wsc/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/wsc/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/wysiwygarea/plugin.js b/app/webroot/js/ckeditor/_source/plugins/wysiwygarea/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/wysiwygarea/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/wysiwygarea/plugin.js diff --git a/webroot/js/ckeditor/_source/plugins/xml/plugin.js b/app/webroot/js/ckeditor/_source/plugins/xml/plugin.js similarity index 100% rename from webroot/js/ckeditor/_source/plugins/xml/plugin.js rename to app/webroot/js/ckeditor/_source/plugins/xml/plugin.js diff --git a/webroot/js/ckeditor/_source/skins/kama/dialog.css b/app/webroot/js/ckeditor/_source/skins/kama/dialog.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/dialog.css rename to app/webroot/js/ckeditor/_source/skins/kama/dialog.css diff --git a/webroot/js/ckeditor/_source/skins/kama/editor.css b/app/webroot/js/ckeditor/_source/skins/kama/editor.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/editor.css rename to app/webroot/js/ckeditor/_source/skins/kama/editor.css diff --git a/webroot/js/ckeditor/_source/skins/kama/elementspath.css b/app/webroot/js/ckeditor/_source/skins/kama/elementspath.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/elementspath.css rename to app/webroot/js/ckeditor/_source/skins/kama/elementspath.css diff --git a/webroot/js/ckeditor/_source/skins/kama/icons.css b/app/webroot/js/ckeditor/_source/skins/kama/icons.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/icons.css rename to app/webroot/js/ckeditor/_source/skins/kama/icons.css diff --git a/webroot/js/ckeditor/_source/skins/kama/icons.png b/app/webroot/js/ckeditor/_source/skins/kama/icons.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/icons.png rename to app/webroot/js/ckeditor/_source/skins/kama/icons.png diff --git a/webroot/js/ckeditor/_source/skins/kama/icons_rtl.png b/app/webroot/js/ckeditor/_source/skins/kama/icons_rtl.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/icons_rtl.png rename to app/webroot/js/ckeditor/_source/skins/kama/icons_rtl.png diff --git a/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides.gif b/app/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides.gif similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/images/dialog_sides.gif rename to app/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides.gif diff --git a/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides.png b/app/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/images/dialog_sides.png rename to app/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides.png diff --git a/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides_rtl.png b/app/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides_rtl.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/images/dialog_sides_rtl.png rename to app/webroot/js/ckeditor/_source/skins/kama/images/dialog_sides_rtl.png diff --git a/webroot/js/ckeditor/_source/skins/kama/images/mini.gif b/app/webroot/js/ckeditor/_source/skins/kama/images/mini.gif similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/images/mini.gif rename to app/webroot/js/ckeditor/_source/skins/kama/images/mini.gif diff --git a/webroot/js/ckeditor/_source/skins/kama/images/noimage.png b/app/webroot/js/ckeditor/_source/skins/kama/images/noimage.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/images/noimage.png rename to app/webroot/js/ckeditor/_source/skins/kama/images/noimage.png diff --git a/webroot/js/ckeditor/_source/skins/kama/images/sprites.png b/app/webroot/js/ckeditor/_source/skins/kama/images/sprites.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/images/sprites.png rename to app/webroot/js/ckeditor/_source/skins/kama/images/sprites.png diff --git a/webroot/js/ckeditor/_source/skins/kama/images/sprites_ie6.png b/app/webroot/js/ckeditor/_source/skins/kama/images/sprites_ie6.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/images/sprites_ie6.png rename to app/webroot/js/ckeditor/_source/skins/kama/images/sprites_ie6.png diff --git a/webroot/js/ckeditor/_source/skins/kama/images/toolbar_start.gif b/app/webroot/js/ckeditor/_source/skins/kama/images/toolbar_start.gif similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/images/toolbar_start.gif rename to app/webroot/js/ckeditor/_source/skins/kama/images/toolbar_start.gif diff --git a/webroot/js/ckeditor/_source/skins/kama/mainui.css b/app/webroot/js/ckeditor/_source/skins/kama/mainui.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/mainui.css rename to app/webroot/js/ckeditor/_source/skins/kama/mainui.css diff --git a/webroot/js/ckeditor/_source/skins/kama/menu.css b/app/webroot/js/ckeditor/_source/skins/kama/menu.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/menu.css rename to app/webroot/js/ckeditor/_source/skins/kama/menu.css diff --git a/webroot/js/ckeditor/_source/skins/kama/panel.css b/app/webroot/js/ckeditor/_source/skins/kama/panel.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/panel.css rename to app/webroot/js/ckeditor/_source/skins/kama/panel.css diff --git a/webroot/js/ckeditor/_source/skins/kama/presets.css b/app/webroot/js/ckeditor/_source/skins/kama/presets.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/presets.css rename to app/webroot/js/ckeditor/_source/skins/kama/presets.css diff --git a/webroot/js/ckeditor/_source/skins/kama/reset.css b/app/webroot/js/ckeditor/_source/skins/kama/reset.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/reset.css rename to app/webroot/js/ckeditor/_source/skins/kama/reset.css diff --git a/webroot/js/ckeditor/_source/skins/kama/richcombo.css b/app/webroot/js/ckeditor/_source/skins/kama/richcombo.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/richcombo.css rename to app/webroot/js/ckeditor/_source/skins/kama/richcombo.css diff --git a/webroot/js/ckeditor/_source/skins/kama/skin.js b/app/webroot/js/ckeditor/_source/skins/kama/skin.js similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/skin.js rename to app/webroot/js/ckeditor/_source/skins/kama/skin.js diff --git a/webroot/js/ckeditor/_source/skins/kama/templates.css b/app/webroot/js/ckeditor/_source/skins/kama/templates.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/templates.css rename to app/webroot/js/ckeditor/_source/skins/kama/templates.css diff --git a/webroot/js/ckeditor/_source/skins/kama/toolbar.css b/app/webroot/js/ckeditor/_source/skins/kama/toolbar.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/kama/toolbar.css rename to app/webroot/js/ckeditor/_source/skins/kama/toolbar.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/dialog.css b/app/webroot/js/ckeditor/_source/skins/office2003/dialog.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/dialog.css rename to app/webroot/js/ckeditor/_source/skins/office2003/dialog.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/editor.css b/app/webroot/js/ckeditor/_source/skins/office2003/editor.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/editor.css rename to app/webroot/js/ckeditor/_source/skins/office2003/editor.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/elementspath.css b/app/webroot/js/ckeditor/_source/skins/office2003/elementspath.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/elementspath.css rename to app/webroot/js/ckeditor/_source/skins/office2003/elementspath.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/icons.css b/app/webroot/js/ckeditor/_source/skins/office2003/icons.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/icons.css rename to app/webroot/js/ckeditor/_source/skins/office2003/icons.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/icons.png b/app/webroot/js/ckeditor/_source/skins/office2003/icons.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/icons.png rename to app/webroot/js/ckeditor/_source/skins/office2003/icons.png diff --git a/webroot/js/ckeditor/_source/skins/office2003/icons_rtl.png b/app/webroot/js/ckeditor/_source/skins/office2003/icons_rtl.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/icons_rtl.png rename to app/webroot/js/ckeditor/_source/skins/office2003/icons_rtl.png diff --git a/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides.gif b/app/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides.gif similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides.gif rename to app/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides.gif diff --git a/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides.png b/app/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides.png rename to app/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides.png diff --git a/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides_rtl.png b/app/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides_rtl.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides_rtl.png rename to app/webroot/js/ckeditor/_source/skins/office2003/images/dialog_sides_rtl.png diff --git a/webroot/js/ckeditor/_source/skins/office2003/images/mini.gif b/app/webroot/js/ckeditor/_source/skins/office2003/images/mini.gif similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/images/mini.gif rename to app/webroot/js/ckeditor/_source/skins/office2003/images/mini.gif diff --git a/webroot/js/ckeditor/_source/skins/office2003/images/noimage.png b/app/webroot/js/ckeditor/_source/skins/office2003/images/noimage.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/images/noimage.png rename to app/webroot/js/ckeditor/_source/skins/office2003/images/noimage.png diff --git a/webroot/js/ckeditor/_source/skins/office2003/images/sprites.png b/app/webroot/js/ckeditor/_source/skins/office2003/images/sprites.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/images/sprites.png rename to app/webroot/js/ckeditor/_source/skins/office2003/images/sprites.png diff --git a/webroot/js/ckeditor/_source/skins/office2003/images/sprites_ie6.png b/app/webroot/js/ckeditor/_source/skins/office2003/images/sprites_ie6.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/images/sprites_ie6.png rename to app/webroot/js/ckeditor/_source/skins/office2003/images/sprites_ie6.png diff --git a/webroot/js/ckeditor/_source/skins/office2003/mainui.css b/app/webroot/js/ckeditor/_source/skins/office2003/mainui.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/mainui.css rename to app/webroot/js/ckeditor/_source/skins/office2003/mainui.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/menu.css b/app/webroot/js/ckeditor/_source/skins/office2003/menu.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/menu.css rename to app/webroot/js/ckeditor/_source/skins/office2003/menu.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/panel.css b/app/webroot/js/ckeditor/_source/skins/office2003/panel.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/panel.css rename to app/webroot/js/ckeditor/_source/skins/office2003/panel.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/presets.css b/app/webroot/js/ckeditor/_source/skins/office2003/presets.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/presets.css rename to app/webroot/js/ckeditor/_source/skins/office2003/presets.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/reset.css b/app/webroot/js/ckeditor/_source/skins/office2003/reset.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/reset.css rename to app/webroot/js/ckeditor/_source/skins/office2003/reset.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/richcombo.css b/app/webroot/js/ckeditor/_source/skins/office2003/richcombo.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/richcombo.css rename to app/webroot/js/ckeditor/_source/skins/office2003/richcombo.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/skin.js b/app/webroot/js/ckeditor/_source/skins/office2003/skin.js similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/skin.js rename to app/webroot/js/ckeditor/_source/skins/office2003/skin.js diff --git a/webroot/js/ckeditor/_source/skins/office2003/templates.css b/app/webroot/js/ckeditor/_source/skins/office2003/templates.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/templates.css rename to app/webroot/js/ckeditor/_source/skins/office2003/templates.css diff --git a/webroot/js/ckeditor/_source/skins/office2003/toolbar.css b/app/webroot/js/ckeditor/_source/skins/office2003/toolbar.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/office2003/toolbar.css rename to app/webroot/js/ckeditor/_source/skins/office2003/toolbar.css diff --git a/webroot/js/ckeditor/_source/skins/v2/dialog.css b/app/webroot/js/ckeditor/_source/skins/v2/dialog.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/dialog.css rename to app/webroot/js/ckeditor/_source/skins/v2/dialog.css diff --git a/webroot/js/ckeditor/_source/skins/v2/editor.css b/app/webroot/js/ckeditor/_source/skins/v2/editor.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/editor.css rename to app/webroot/js/ckeditor/_source/skins/v2/editor.css diff --git a/webroot/js/ckeditor/_source/skins/v2/elementspath.css b/app/webroot/js/ckeditor/_source/skins/v2/elementspath.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/elementspath.css rename to app/webroot/js/ckeditor/_source/skins/v2/elementspath.css diff --git a/webroot/js/ckeditor/_source/skins/v2/icons.css b/app/webroot/js/ckeditor/_source/skins/v2/icons.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/icons.css rename to app/webroot/js/ckeditor/_source/skins/v2/icons.css diff --git a/webroot/js/ckeditor/_source/skins/v2/icons.png b/app/webroot/js/ckeditor/_source/skins/v2/icons.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/icons.png rename to app/webroot/js/ckeditor/_source/skins/v2/icons.png diff --git a/webroot/js/ckeditor/_source/skins/v2/icons_rtl.png b/app/webroot/js/ckeditor/_source/skins/v2/icons_rtl.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/icons_rtl.png rename to app/webroot/js/ckeditor/_source/skins/v2/icons_rtl.png diff --git a/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides.gif b/app/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides.gif similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/images/dialog_sides.gif rename to app/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides.gif diff --git a/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides.png b/app/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/images/dialog_sides.png rename to app/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides.png diff --git a/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides_rtl.png b/app/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides_rtl.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/images/dialog_sides_rtl.png rename to app/webroot/js/ckeditor/_source/skins/v2/images/dialog_sides_rtl.png diff --git a/webroot/js/ckeditor/_source/skins/v2/images/mini.gif b/app/webroot/js/ckeditor/_source/skins/v2/images/mini.gif similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/images/mini.gif rename to app/webroot/js/ckeditor/_source/skins/v2/images/mini.gif diff --git a/webroot/js/ckeditor/_source/skins/v2/images/noimage.png b/app/webroot/js/ckeditor/_source/skins/v2/images/noimage.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/images/noimage.png rename to app/webroot/js/ckeditor/_source/skins/v2/images/noimage.png diff --git a/webroot/js/ckeditor/_source/skins/v2/images/sprites.png b/app/webroot/js/ckeditor/_source/skins/v2/images/sprites.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/images/sprites.png rename to app/webroot/js/ckeditor/_source/skins/v2/images/sprites.png diff --git a/webroot/js/ckeditor/_source/skins/v2/images/sprites_ie6.png b/app/webroot/js/ckeditor/_source/skins/v2/images/sprites_ie6.png similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/images/sprites_ie6.png rename to app/webroot/js/ckeditor/_source/skins/v2/images/sprites_ie6.png diff --git a/webroot/js/ckeditor/_source/skins/v2/images/toolbar_start.gif b/app/webroot/js/ckeditor/_source/skins/v2/images/toolbar_start.gif similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/images/toolbar_start.gif rename to app/webroot/js/ckeditor/_source/skins/v2/images/toolbar_start.gif diff --git a/webroot/js/ckeditor/_source/skins/v2/mainui.css b/app/webroot/js/ckeditor/_source/skins/v2/mainui.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/mainui.css rename to app/webroot/js/ckeditor/_source/skins/v2/mainui.css diff --git a/webroot/js/ckeditor/_source/skins/v2/menu.css b/app/webroot/js/ckeditor/_source/skins/v2/menu.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/menu.css rename to app/webroot/js/ckeditor/_source/skins/v2/menu.css diff --git a/webroot/js/ckeditor/_source/skins/v2/panel.css b/app/webroot/js/ckeditor/_source/skins/v2/panel.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/panel.css rename to app/webroot/js/ckeditor/_source/skins/v2/panel.css diff --git a/webroot/js/ckeditor/_source/skins/v2/presets.css b/app/webroot/js/ckeditor/_source/skins/v2/presets.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/presets.css rename to app/webroot/js/ckeditor/_source/skins/v2/presets.css diff --git a/webroot/js/ckeditor/_source/skins/v2/reset.css b/app/webroot/js/ckeditor/_source/skins/v2/reset.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/reset.css rename to app/webroot/js/ckeditor/_source/skins/v2/reset.css diff --git a/webroot/js/ckeditor/_source/skins/v2/richcombo.css b/app/webroot/js/ckeditor/_source/skins/v2/richcombo.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/richcombo.css rename to app/webroot/js/ckeditor/_source/skins/v2/richcombo.css diff --git a/webroot/js/ckeditor/_source/skins/v2/skin.js b/app/webroot/js/ckeditor/_source/skins/v2/skin.js similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/skin.js rename to app/webroot/js/ckeditor/_source/skins/v2/skin.js diff --git a/webroot/js/ckeditor/_source/skins/v2/templates.css b/app/webroot/js/ckeditor/_source/skins/v2/templates.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/templates.css rename to app/webroot/js/ckeditor/_source/skins/v2/templates.css diff --git a/webroot/js/ckeditor/_source/skins/v2/toolbar.css b/app/webroot/js/ckeditor/_source/skins/v2/toolbar.css similarity index 100% rename from webroot/js/ckeditor/_source/skins/v2/toolbar.css rename to app/webroot/js/ckeditor/_source/skins/v2/toolbar.css diff --git a/webroot/js/ckeditor/_source/themes/default/theme.js b/app/webroot/js/ckeditor/_source/themes/default/theme.js similarity index 100% rename from webroot/js/ckeditor/_source/themes/default/theme.js rename to app/webroot/js/ckeditor/_source/themes/default/theme.js diff --git a/webroot/js/ckeditor/adapters/jquery.js b/app/webroot/js/ckeditor/adapters/jquery.js similarity index 100% rename from webroot/js/ckeditor/adapters/jquery.js rename to app/webroot/js/ckeditor/adapters/jquery.js diff --git a/webroot/js/ckeditor/ckeditor.asp b/app/webroot/js/ckeditor/ckeditor.asp similarity index 100% rename from webroot/js/ckeditor/ckeditor.asp rename to app/webroot/js/ckeditor/ckeditor.asp diff --git a/webroot/js/ckeditor/ckeditor.js b/app/webroot/js/ckeditor/ckeditor.js similarity index 100% rename from webroot/js/ckeditor/ckeditor.js rename to app/webroot/js/ckeditor/ckeditor.js diff --git a/webroot/js/ckeditor/ckeditor.pack b/app/webroot/js/ckeditor/ckeditor.pack similarity index 100% rename from webroot/js/ckeditor/ckeditor.pack rename to app/webroot/js/ckeditor/ckeditor.pack diff --git a/webroot/js/ckeditor/ckeditor.php b/app/webroot/js/ckeditor/ckeditor.php similarity index 100% rename from webroot/js/ckeditor/ckeditor.php rename to app/webroot/js/ckeditor/ckeditor.php diff --git a/webroot/js/ckeditor/ckeditor_basic.js b/app/webroot/js/ckeditor/ckeditor_basic.js similarity index 100% rename from webroot/js/ckeditor/ckeditor_basic.js rename to app/webroot/js/ckeditor/ckeditor_basic.js diff --git a/webroot/js/ckeditor/ckeditor_basic_source.js b/app/webroot/js/ckeditor/ckeditor_basic_source.js similarity index 100% rename from webroot/js/ckeditor/ckeditor_basic_source.js rename to app/webroot/js/ckeditor/ckeditor_basic_source.js diff --git a/webroot/js/ckeditor/ckeditor_php4.php b/app/webroot/js/ckeditor/ckeditor_php4.php similarity index 100% rename from webroot/js/ckeditor/ckeditor_php4.php rename to app/webroot/js/ckeditor/ckeditor_php4.php diff --git a/webroot/js/ckeditor/ckeditor_php5.php b/app/webroot/js/ckeditor/ckeditor_php5.php similarity index 100% rename from webroot/js/ckeditor/ckeditor_php5.php rename to app/webroot/js/ckeditor/ckeditor_php5.php diff --git a/webroot/js/ckeditor/ckeditor_source.js b/app/webroot/js/ckeditor/ckeditor_source.js similarity index 100% rename from webroot/js/ckeditor/ckeditor_source.js rename to app/webroot/js/ckeditor/ckeditor_source.js diff --git a/webroot/js/ckeditor/config.js b/app/webroot/js/ckeditor/config.js similarity index 100% rename from webroot/js/ckeditor/config.js rename to app/webroot/js/ckeditor/config.js diff --git a/webroot/js/ckeditor/contents.css b/app/webroot/js/ckeditor/contents.css similarity index 100% rename from webroot/js/ckeditor/contents.css rename to app/webroot/js/ckeditor/contents.css diff --git a/webroot/js/ckeditor/images/spacer.gif b/app/webroot/js/ckeditor/images/spacer.gif similarity index 100% rename from webroot/js/ckeditor/images/spacer.gif rename to app/webroot/js/ckeditor/images/spacer.gif diff --git a/webroot/js/ckeditor/lang/_languages.js b/app/webroot/js/ckeditor/lang/_languages.js similarity index 100% rename from webroot/js/ckeditor/lang/_languages.js rename to app/webroot/js/ckeditor/lang/_languages.js diff --git a/webroot/js/ckeditor/lang/_translationstatus.txt b/app/webroot/js/ckeditor/lang/_translationstatus.txt similarity index 100% rename from webroot/js/ckeditor/lang/_translationstatus.txt rename to app/webroot/js/ckeditor/lang/_translationstatus.txt diff --git a/webroot/js/ckeditor/lang/af.js b/app/webroot/js/ckeditor/lang/af.js similarity index 100% rename from webroot/js/ckeditor/lang/af.js rename to app/webroot/js/ckeditor/lang/af.js diff --git a/webroot/js/ckeditor/lang/ar.js b/app/webroot/js/ckeditor/lang/ar.js similarity index 100% rename from webroot/js/ckeditor/lang/ar.js rename to app/webroot/js/ckeditor/lang/ar.js diff --git a/webroot/js/ckeditor/lang/bg.js b/app/webroot/js/ckeditor/lang/bg.js similarity index 100% rename from webroot/js/ckeditor/lang/bg.js rename to app/webroot/js/ckeditor/lang/bg.js diff --git a/webroot/js/ckeditor/lang/bn.js b/app/webroot/js/ckeditor/lang/bn.js similarity index 100% rename from webroot/js/ckeditor/lang/bn.js rename to app/webroot/js/ckeditor/lang/bn.js diff --git a/webroot/js/ckeditor/lang/bs.js b/app/webroot/js/ckeditor/lang/bs.js similarity index 100% rename from webroot/js/ckeditor/lang/bs.js rename to app/webroot/js/ckeditor/lang/bs.js diff --git a/webroot/js/ckeditor/lang/ca.js b/app/webroot/js/ckeditor/lang/ca.js similarity index 100% rename from webroot/js/ckeditor/lang/ca.js rename to app/webroot/js/ckeditor/lang/ca.js diff --git a/webroot/js/ckeditor/lang/cs.js b/app/webroot/js/ckeditor/lang/cs.js similarity index 100% rename from webroot/js/ckeditor/lang/cs.js rename to app/webroot/js/ckeditor/lang/cs.js diff --git a/webroot/js/ckeditor/lang/cy.js b/app/webroot/js/ckeditor/lang/cy.js similarity index 100% rename from webroot/js/ckeditor/lang/cy.js rename to app/webroot/js/ckeditor/lang/cy.js diff --git a/webroot/js/ckeditor/lang/da.js b/app/webroot/js/ckeditor/lang/da.js similarity index 100% rename from webroot/js/ckeditor/lang/da.js rename to app/webroot/js/ckeditor/lang/da.js diff --git a/webroot/js/ckeditor/lang/de.js b/app/webroot/js/ckeditor/lang/de.js similarity index 100% rename from webroot/js/ckeditor/lang/de.js rename to app/webroot/js/ckeditor/lang/de.js diff --git a/webroot/js/ckeditor/lang/el.js b/app/webroot/js/ckeditor/lang/el.js similarity index 100% rename from webroot/js/ckeditor/lang/el.js rename to app/webroot/js/ckeditor/lang/el.js diff --git a/webroot/js/ckeditor/lang/en-au.js b/app/webroot/js/ckeditor/lang/en-au.js similarity index 100% rename from webroot/js/ckeditor/lang/en-au.js rename to app/webroot/js/ckeditor/lang/en-au.js diff --git a/webroot/js/ckeditor/lang/en-ca.js b/app/webroot/js/ckeditor/lang/en-ca.js similarity index 100% rename from webroot/js/ckeditor/lang/en-ca.js rename to app/webroot/js/ckeditor/lang/en-ca.js diff --git a/webroot/js/ckeditor/lang/en-gb.js b/app/webroot/js/ckeditor/lang/en-gb.js similarity index 100% rename from webroot/js/ckeditor/lang/en-gb.js rename to app/webroot/js/ckeditor/lang/en-gb.js diff --git a/webroot/js/ckeditor/lang/en.js b/app/webroot/js/ckeditor/lang/en.js similarity index 100% rename from webroot/js/ckeditor/lang/en.js rename to app/webroot/js/ckeditor/lang/en.js diff --git a/webroot/js/ckeditor/lang/eo.js b/app/webroot/js/ckeditor/lang/eo.js similarity index 100% rename from webroot/js/ckeditor/lang/eo.js rename to app/webroot/js/ckeditor/lang/eo.js diff --git a/webroot/js/ckeditor/lang/es.js b/app/webroot/js/ckeditor/lang/es.js similarity index 100% rename from webroot/js/ckeditor/lang/es.js rename to app/webroot/js/ckeditor/lang/es.js diff --git a/webroot/js/ckeditor/lang/et.js b/app/webroot/js/ckeditor/lang/et.js similarity index 100% rename from webroot/js/ckeditor/lang/et.js rename to app/webroot/js/ckeditor/lang/et.js diff --git a/webroot/js/ckeditor/lang/eu.js b/app/webroot/js/ckeditor/lang/eu.js similarity index 100% rename from webroot/js/ckeditor/lang/eu.js rename to app/webroot/js/ckeditor/lang/eu.js diff --git a/webroot/js/ckeditor/lang/fa.js b/app/webroot/js/ckeditor/lang/fa.js similarity index 100% rename from webroot/js/ckeditor/lang/fa.js rename to app/webroot/js/ckeditor/lang/fa.js diff --git a/webroot/js/ckeditor/lang/fi.js b/app/webroot/js/ckeditor/lang/fi.js similarity index 100% rename from webroot/js/ckeditor/lang/fi.js rename to app/webroot/js/ckeditor/lang/fi.js diff --git a/webroot/js/ckeditor/lang/fo.js b/app/webroot/js/ckeditor/lang/fo.js similarity index 100% rename from webroot/js/ckeditor/lang/fo.js rename to app/webroot/js/ckeditor/lang/fo.js diff --git a/webroot/js/ckeditor/lang/fr-ca.js b/app/webroot/js/ckeditor/lang/fr-ca.js similarity index 100% rename from webroot/js/ckeditor/lang/fr-ca.js rename to app/webroot/js/ckeditor/lang/fr-ca.js diff --git a/webroot/js/ckeditor/lang/fr.js b/app/webroot/js/ckeditor/lang/fr.js similarity index 100% rename from webroot/js/ckeditor/lang/fr.js rename to app/webroot/js/ckeditor/lang/fr.js diff --git a/webroot/js/ckeditor/lang/gl.js b/app/webroot/js/ckeditor/lang/gl.js similarity index 100% rename from webroot/js/ckeditor/lang/gl.js rename to app/webroot/js/ckeditor/lang/gl.js diff --git a/webroot/js/ckeditor/lang/gu.js b/app/webroot/js/ckeditor/lang/gu.js similarity index 100% rename from webroot/js/ckeditor/lang/gu.js rename to app/webroot/js/ckeditor/lang/gu.js diff --git a/webroot/js/ckeditor/lang/he.js b/app/webroot/js/ckeditor/lang/he.js similarity index 100% rename from webroot/js/ckeditor/lang/he.js rename to app/webroot/js/ckeditor/lang/he.js diff --git a/webroot/js/ckeditor/lang/hi.js b/app/webroot/js/ckeditor/lang/hi.js similarity index 100% rename from webroot/js/ckeditor/lang/hi.js rename to app/webroot/js/ckeditor/lang/hi.js diff --git a/webroot/js/ckeditor/lang/hr.js b/app/webroot/js/ckeditor/lang/hr.js similarity index 100% rename from webroot/js/ckeditor/lang/hr.js rename to app/webroot/js/ckeditor/lang/hr.js diff --git a/webroot/js/ckeditor/lang/hu.js b/app/webroot/js/ckeditor/lang/hu.js similarity index 100% rename from webroot/js/ckeditor/lang/hu.js rename to app/webroot/js/ckeditor/lang/hu.js diff --git a/webroot/js/ckeditor/lang/is.js b/app/webroot/js/ckeditor/lang/is.js similarity index 100% rename from webroot/js/ckeditor/lang/is.js rename to app/webroot/js/ckeditor/lang/is.js diff --git a/webroot/js/ckeditor/lang/it.js b/app/webroot/js/ckeditor/lang/it.js similarity index 100% rename from webroot/js/ckeditor/lang/it.js rename to app/webroot/js/ckeditor/lang/it.js diff --git a/webroot/js/ckeditor/lang/ja.js b/app/webroot/js/ckeditor/lang/ja.js similarity index 100% rename from webroot/js/ckeditor/lang/ja.js rename to app/webroot/js/ckeditor/lang/ja.js diff --git a/webroot/js/ckeditor/lang/ka.js b/app/webroot/js/ckeditor/lang/ka.js similarity index 100% rename from webroot/js/ckeditor/lang/ka.js rename to app/webroot/js/ckeditor/lang/ka.js diff --git a/webroot/js/ckeditor/lang/km.js b/app/webroot/js/ckeditor/lang/km.js similarity index 100% rename from webroot/js/ckeditor/lang/km.js rename to app/webroot/js/ckeditor/lang/km.js diff --git a/webroot/js/ckeditor/lang/ko.js b/app/webroot/js/ckeditor/lang/ko.js similarity index 100% rename from webroot/js/ckeditor/lang/ko.js rename to app/webroot/js/ckeditor/lang/ko.js diff --git a/webroot/js/ckeditor/lang/lt.js b/app/webroot/js/ckeditor/lang/lt.js similarity index 100% rename from webroot/js/ckeditor/lang/lt.js rename to app/webroot/js/ckeditor/lang/lt.js diff --git a/webroot/js/ckeditor/lang/lv.js b/app/webroot/js/ckeditor/lang/lv.js similarity index 100% rename from webroot/js/ckeditor/lang/lv.js rename to app/webroot/js/ckeditor/lang/lv.js diff --git a/webroot/js/ckeditor/lang/mn.js b/app/webroot/js/ckeditor/lang/mn.js similarity index 100% rename from webroot/js/ckeditor/lang/mn.js rename to app/webroot/js/ckeditor/lang/mn.js diff --git a/webroot/js/ckeditor/lang/ms.js b/app/webroot/js/ckeditor/lang/ms.js similarity index 100% rename from webroot/js/ckeditor/lang/ms.js rename to app/webroot/js/ckeditor/lang/ms.js diff --git a/webroot/js/ckeditor/lang/nb.js b/app/webroot/js/ckeditor/lang/nb.js similarity index 100% rename from webroot/js/ckeditor/lang/nb.js rename to app/webroot/js/ckeditor/lang/nb.js diff --git a/webroot/js/ckeditor/lang/nl.js b/app/webroot/js/ckeditor/lang/nl.js similarity index 100% rename from webroot/js/ckeditor/lang/nl.js rename to app/webroot/js/ckeditor/lang/nl.js diff --git a/webroot/js/ckeditor/lang/no.js b/app/webroot/js/ckeditor/lang/no.js similarity index 100% rename from webroot/js/ckeditor/lang/no.js rename to app/webroot/js/ckeditor/lang/no.js diff --git a/webroot/js/ckeditor/lang/pl.js b/app/webroot/js/ckeditor/lang/pl.js similarity index 100% rename from webroot/js/ckeditor/lang/pl.js rename to app/webroot/js/ckeditor/lang/pl.js diff --git a/webroot/js/ckeditor/lang/pt-br.js b/app/webroot/js/ckeditor/lang/pt-br.js similarity index 100% rename from webroot/js/ckeditor/lang/pt-br.js rename to app/webroot/js/ckeditor/lang/pt-br.js diff --git a/webroot/js/ckeditor/lang/pt.js b/app/webroot/js/ckeditor/lang/pt.js similarity index 100% rename from webroot/js/ckeditor/lang/pt.js rename to app/webroot/js/ckeditor/lang/pt.js diff --git a/webroot/js/ckeditor/lang/ro.js b/app/webroot/js/ckeditor/lang/ro.js similarity index 100% rename from webroot/js/ckeditor/lang/ro.js rename to app/webroot/js/ckeditor/lang/ro.js diff --git a/webroot/js/ckeditor/lang/ru.js b/app/webroot/js/ckeditor/lang/ru.js similarity index 100% rename from webroot/js/ckeditor/lang/ru.js rename to app/webroot/js/ckeditor/lang/ru.js diff --git a/webroot/js/ckeditor/lang/sk.js b/app/webroot/js/ckeditor/lang/sk.js similarity index 100% rename from webroot/js/ckeditor/lang/sk.js rename to app/webroot/js/ckeditor/lang/sk.js diff --git a/webroot/js/ckeditor/lang/sl.js b/app/webroot/js/ckeditor/lang/sl.js similarity index 100% rename from webroot/js/ckeditor/lang/sl.js rename to app/webroot/js/ckeditor/lang/sl.js diff --git a/webroot/js/ckeditor/lang/sr-latn.js b/app/webroot/js/ckeditor/lang/sr-latn.js similarity index 100% rename from webroot/js/ckeditor/lang/sr-latn.js rename to app/webroot/js/ckeditor/lang/sr-latn.js diff --git a/webroot/js/ckeditor/lang/sr.js b/app/webroot/js/ckeditor/lang/sr.js similarity index 100% rename from webroot/js/ckeditor/lang/sr.js rename to app/webroot/js/ckeditor/lang/sr.js diff --git a/webroot/js/ckeditor/lang/sv.js b/app/webroot/js/ckeditor/lang/sv.js similarity index 100% rename from webroot/js/ckeditor/lang/sv.js rename to app/webroot/js/ckeditor/lang/sv.js diff --git a/webroot/js/ckeditor/lang/th.js b/app/webroot/js/ckeditor/lang/th.js similarity index 100% rename from webroot/js/ckeditor/lang/th.js rename to app/webroot/js/ckeditor/lang/th.js diff --git a/webroot/js/ckeditor/lang/tr.js b/app/webroot/js/ckeditor/lang/tr.js similarity index 100% rename from webroot/js/ckeditor/lang/tr.js rename to app/webroot/js/ckeditor/lang/tr.js diff --git a/webroot/js/ckeditor/lang/uk.js b/app/webroot/js/ckeditor/lang/uk.js similarity index 100% rename from webroot/js/ckeditor/lang/uk.js rename to app/webroot/js/ckeditor/lang/uk.js diff --git a/webroot/js/ckeditor/lang/vi.js b/app/webroot/js/ckeditor/lang/vi.js similarity index 100% rename from webroot/js/ckeditor/lang/vi.js rename to app/webroot/js/ckeditor/lang/vi.js diff --git a/webroot/js/ckeditor/lang/zh-cn.js b/app/webroot/js/ckeditor/lang/zh-cn.js similarity index 100% rename from webroot/js/ckeditor/lang/zh-cn.js rename to app/webroot/js/ckeditor/lang/zh-cn.js diff --git a/webroot/js/ckeditor/lang/zh.js b/app/webroot/js/ckeditor/lang/zh.js similarity index 100% rename from webroot/js/ckeditor/lang/zh.js rename to app/webroot/js/ckeditor/lang/zh.js diff --git a/webroot/js/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js b/app/webroot/js/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js similarity index 100% rename from webroot/js/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js rename to app/webroot/js/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js diff --git a/webroot/js/ckeditor/plugins/a11yhelp/lang/en.js b/app/webroot/js/ckeditor/plugins/a11yhelp/lang/en.js similarity index 100% rename from webroot/js/ckeditor/plugins/a11yhelp/lang/en.js rename to app/webroot/js/ckeditor/plugins/a11yhelp/lang/en.js diff --git a/webroot/js/ckeditor/plugins/a11yhelp/lang/he.js b/app/webroot/js/ckeditor/plugins/a11yhelp/lang/he.js similarity index 100% rename from webroot/js/ckeditor/plugins/a11yhelp/lang/he.js rename to app/webroot/js/ckeditor/plugins/a11yhelp/lang/he.js diff --git a/webroot/js/ckeditor/plugins/about/dialogs/about.js b/app/webroot/js/ckeditor/plugins/about/dialogs/about.js similarity index 100% rename from webroot/js/ckeditor/plugins/about/dialogs/about.js rename to app/webroot/js/ckeditor/plugins/about/dialogs/about.js diff --git a/webroot/js/ckeditor/plugins/about/dialogs/logo_ckeditor.png b/app/webroot/js/ckeditor/plugins/about/dialogs/logo_ckeditor.png similarity index 100% rename from webroot/js/ckeditor/plugins/about/dialogs/logo_ckeditor.png rename to app/webroot/js/ckeditor/plugins/about/dialogs/logo_ckeditor.png diff --git a/webroot/js/ckeditor/plugins/adobeair/plugin.js b/app/webroot/js/ckeditor/plugins/adobeair/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/adobeair/plugin.js rename to app/webroot/js/ckeditor/plugins/adobeair/plugin.js diff --git a/webroot/js/ckeditor/plugins/ajax/plugin.js b/app/webroot/js/ckeditor/plugins/ajax/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/ajax/plugin.js rename to app/webroot/js/ckeditor/plugins/ajax/plugin.js diff --git a/webroot/js/ckeditor/plugins/autogrow/plugin.js b/app/webroot/js/ckeditor/plugins/autogrow/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/autogrow/plugin.js rename to app/webroot/js/ckeditor/plugins/autogrow/plugin.js diff --git a/webroot/js/ckeditor/plugins/bbcode/plugin.js b/app/webroot/js/ckeditor/plugins/bbcode/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/bbcode/plugin.js rename to app/webroot/js/ckeditor/plugins/bbcode/plugin.js diff --git a/webroot/js/ckeditor/plugins/clipboard/dialogs/paste.js b/app/webroot/js/ckeditor/plugins/clipboard/dialogs/paste.js similarity index 100% rename from webroot/js/ckeditor/plugins/clipboard/dialogs/paste.js rename to app/webroot/js/ckeditor/plugins/clipboard/dialogs/paste.js diff --git a/webroot/js/ckeditor/plugins/colordialog/dialogs/colordialog.js b/app/webroot/js/ckeditor/plugins/colordialog/dialogs/colordialog.js similarity index 100% rename from webroot/js/ckeditor/plugins/colordialog/dialogs/colordialog.js rename to app/webroot/js/ckeditor/plugins/colordialog/dialogs/colordialog.js diff --git a/webroot/js/ckeditor/plugins/devtools/lang/en.js b/app/webroot/js/ckeditor/plugins/devtools/lang/en.js similarity index 100% rename from webroot/js/ckeditor/plugins/devtools/lang/en.js rename to app/webroot/js/ckeditor/plugins/devtools/lang/en.js diff --git a/webroot/js/ckeditor/plugins/devtools/plugin.js b/app/webroot/js/ckeditor/plugins/devtools/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/devtools/plugin.js rename to app/webroot/js/ckeditor/plugins/devtools/plugin.js diff --git a/webroot/js/ckeditor/plugins/dialog/dialogDefinition.js b/app/webroot/js/ckeditor/plugins/dialog/dialogDefinition.js similarity index 100% rename from webroot/js/ckeditor/plugins/dialog/dialogDefinition.js rename to app/webroot/js/ckeditor/plugins/dialog/dialogDefinition.js diff --git a/webroot/js/ckeditor/plugins/div/dialogs/div.js b/app/webroot/js/ckeditor/plugins/div/dialogs/div.js similarity index 100% rename from webroot/js/ckeditor/plugins/div/dialogs/div.js rename to app/webroot/js/ckeditor/plugins/div/dialogs/div.js diff --git a/webroot/js/ckeditor/plugins/docprops/dialogs/docprops.js b/app/webroot/js/ckeditor/plugins/docprops/dialogs/docprops.js similarity index 100% rename from webroot/js/ckeditor/plugins/docprops/dialogs/docprops.js rename to app/webroot/js/ckeditor/plugins/docprops/dialogs/docprops.js diff --git a/webroot/js/ckeditor/plugins/docprops/plugin.js b/app/webroot/js/ckeditor/plugins/docprops/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/docprops/plugin.js rename to app/webroot/js/ckeditor/plugins/docprops/plugin.js diff --git a/webroot/js/ckeditor/plugins/find/dialogs/find.js b/app/webroot/js/ckeditor/plugins/find/dialogs/find.js similarity index 100% rename from webroot/js/ckeditor/plugins/find/dialogs/find.js rename to app/webroot/js/ckeditor/plugins/find/dialogs/find.js diff --git a/webroot/js/ckeditor/plugins/flash/dialogs/flash.js b/app/webroot/js/ckeditor/plugins/flash/dialogs/flash.js similarity index 100% rename from webroot/js/ckeditor/plugins/flash/dialogs/flash.js rename to app/webroot/js/ckeditor/plugins/flash/dialogs/flash.js diff --git a/webroot/js/ckeditor/plugins/flash/images/placeholder.png b/app/webroot/js/ckeditor/plugins/flash/images/placeholder.png similarity index 100% rename from webroot/js/ckeditor/plugins/flash/images/placeholder.png rename to app/webroot/js/ckeditor/plugins/flash/images/placeholder.png diff --git a/webroot/js/ckeditor/plugins/forms/dialogs/button.js b/app/webroot/js/ckeditor/plugins/forms/dialogs/button.js similarity index 100% rename from webroot/js/ckeditor/plugins/forms/dialogs/button.js rename to app/webroot/js/ckeditor/plugins/forms/dialogs/button.js diff --git a/webroot/js/ckeditor/plugins/forms/dialogs/checkbox.js b/app/webroot/js/ckeditor/plugins/forms/dialogs/checkbox.js similarity index 100% rename from webroot/js/ckeditor/plugins/forms/dialogs/checkbox.js rename to app/webroot/js/ckeditor/plugins/forms/dialogs/checkbox.js diff --git a/webroot/js/ckeditor/plugins/forms/dialogs/form.js b/app/webroot/js/ckeditor/plugins/forms/dialogs/form.js similarity index 100% rename from webroot/js/ckeditor/plugins/forms/dialogs/form.js rename to app/webroot/js/ckeditor/plugins/forms/dialogs/form.js diff --git a/webroot/js/ckeditor/plugins/forms/dialogs/hiddenfield.js b/app/webroot/js/ckeditor/plugins/forms/dialogs/hiddenfield.js similarity index 100% rename from webroot/js/ckeditor/plugins/forms/dialogs/hiddenfield.js rename to app/webroot/js/ckeditor/plugins/forms/dialogs/hiddenfield.js diff --git a/webroot/js/ckeditor/plugins/forms/dialogs/radio.js b/app/webroot/js/ckeditor/plugins/forms/dialogs/radio.js similarity index 100% rename from webroot/js/ckeditor/plugins/forms/dialogs/radio.js rename to app/webroot/js/ckeditor/plugins/forms/dialogs/radio.js diff --git a/webroot/js/ckeditor/plugins/forms/dialogs/select.js b/app/webroot/js/ckeditor/plugins/forms/dialogs/select.js similarity index 100% rename from webroot/js/ckeditor/plugins/forms/dialogs/select.js rename to app/webroot/js/ckeditor/plugins/forms/dialogs/select.js diff --git a/webroot/js/ckeditor/plugins/forms/dialogs/textarea.js b/app/webroot/js/ckeditor/plugins/forms/dialogs/textarea.js similarity index 100% rename from webroot/js/ckeditor/plugins/forms/dialogs/textarea.js rename to app/webroot/js/ckeditor/plugins/forms/dialogs/textarea.js diff --git a/webroot/js/ckeditor/plugins/forms/dialogs/textfield.js b/app/webroot/js/ckeditor/plugins/forms/dialogs/textfield.js similarity index 100% rename from webroot/js/ckeditor/plugins/forms/dialogs/textfield.js rename to app/webroot/js/ckeditor/plugins/forms/dialogs/textfield.js diff --git a/webroot/js/ckeditor/plugins/forms/images/hiddenfield.gif b/app/webroot/js/ckeditor/plugins/forms/images/hiddenfield.gif similarity index 100% rename from webroot/js/ckeditor/plugins/forms/images/hiddenfield.gif rename to app/webroot/js/ckeditor/plugins/forms/images/hiddenfield.gif diff --git a/webroot/js/ckeditor/plugins/iframe/dialogs/iframe.js b/app/webroot/js/ckeditor/plugins/iframe/dialogs/iframe.js similarity index 100% rename from webroot/js/ckeditor/plugins/iframe/dialogs/iframe.js rename to app/webroot/js/ckeditor/plugins/iframe/dialogs/iframe.js diff --git a/webroot/js/ckeditor/plugins/iframe/images/placeholder.png b/app/webroot/js/ckeditor/plugins/iframe/images/placeholder.png similarity index 100% rename from webroot/js/ckeditor/plugins/iframe/images/placeholder.png rename to app/webroot/js/ckeditor/plugins/iframe/images/placeholder.png diff --git a/webroot/js/ckeditor/plugins/iframedialog/plugin.js b/app/webroot/js/ckeditor/plugins/iframedialog/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/iframedialog/plugin.js rename to app/webroot/js/ckeditor/plugins/iframedialog/plugin.js diff --git a/webroot/js/ckeditor/plugins/image/dialogs/image.js b/app/webroot/js/ckeditor/plugins/image/dialogs/image.js similarity index 100% rename from webroot/js/ckeditor/plugins/image/dialogs/image.js rename to app/webroot/js/ckeditor/plugins/image/dialogs/image.js diff --git a/webroot/js/ckeditor/plugins/link/dialogs/anchor.js b/app/webroot/js/ckeditor/plugins/link/dialogs/anchor.js similarity index 100% rename from webroot/js/ckeditor/plugins/link/dialogs/anchor.js rename to app/webroot/js/ckeditor/plugins/link/dialogs/anchor.js diff --git a/webroot/js/ckeditor/plugins/link/dialogs/link.js b/app/webroot/js/ckeditor/plugins/link/dialogs/link.js similarity index 100% rename from webroot/js/ckeditor/plugins/link/dialogs/link.js rename to app/webroot/js/ckeditor/plugins/link/dialogs/link.js diff --git a/webroot/js/ckeditor/plugins/link/images/anchor.gif b/app/webroot/js/ckeditor/plugins/link/images/anchor.gif similarity index 100% rename from webroot/js/ckeditor/plugins/link/images/anchor.gif rename to app/webroot/js/ckeditor/plugins/link/images/anchor.gif diff --git a/webroot/js/ckeditor/plugins/liststyle/dialogs/liststyle.js b/app/webroot/js/ckeditor/plugins/liststyle/dialogs/liststyle.js similarity index 100% rename from webroot/js/ckeditor/plugins/liststyle/dialogs/liststyle.js rename to app/webroot/js/ckeditor/plugins/liststyle/dialogs/liststyle.js diff --git a/webroot/js/ckeditor/plugins/pagebreak/images/pagebreak.gif b/app/webroot/js/ckeditor/plugins/pagebreak/images/pagebreak.gif similarity index 100% rename from webroot/js/ckeditor/plugins/pagebreak/images/pagebreak.gif rename to app/webroot/js/ckeditor/plugins/pagebreak/images/pagebreak.gif diff --git a/webroot/js/ckeditor/plugins/pastefromword/filter/default.js b/app/webroot/js/ckeditor/plugins/pastefromword/filter/default.js similarity index 100% rename from webroot/js/ckeditor/plugins/pastefromword/filter/default.js rename to app/webroot/js/ckeditor/plugins/pastefromword/filter/default.js diff --git a/webroot/js/ckeditor/plugins/pastetext/dialogs/pastetext.js b/app/webroot/js/ckeditor/plugins/pastetext/dialogs/pastetext.js similarity index 100% rename from webroot/js/ckeditor/plugins/pastetext/dialogs/pastetext.js rename to app/webroot/js/ckeditor/plugins/pastetext/dialogs/pastetext.js diff --git a/webroot/js/ckeditor/plugins/placeholder/dialogs/placeholder.js b/app/webroot/js/ckeditor/plugins/placeholder/dialogs/placeholder.js similarity index 100% rename from webroot/js/ckeditor/plugins/placeholder/dialogs/placeholder.js rename to app/webroot/js/ckeditor/plugins/placeholder/dialogs/placeholder.js diff --git a/webroot/js/ckeditor/plugins/placeholder/lang/en.js b/app/webroot/js/ckeditor/plugins/placeholder/lang/en.js similarity index 100% rename from webroot/js/ckeditor/plugins/placeholder/lang/en.js rename to app/webroot/js/ckeditor/plugins/placeholder/lang/en.js diff --git a/webroot/js/ckeditor/plugins/placeholder/lang/he.js b/app/webroot/js/ckeditor/plugins/placeholder/lang/he.js similarity index 100% rename from webroot/js/ckeditor/plugins/placeholder/lang/he.js rename to app/webroot/js/ckeditor/plugins/placeholder/lang/he.js diff --git a/webroot/js/ckeditor/plugins/placeholder/placeholder.gif b/app/webroot/js/ckeditor/plugins/placeholder/placeholder.gif similarity index 100% rename from webroot/js/ckeditor/plugins/placeholder/placeholder.gif rename to app/webroot/js/ckeditor/plugins/placeholder/placeholder.gif diff --git a/webroot/js/ckeditor/plugins/placeholder/plugin.js b/app/webroot/js/ckeditor/plugins/placeholder/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/placeholder/plugin.js rename to app/webroot/js/ckeditor/plugins/placeholder/plugin.js diff --git a/webroot/js/ckeditor/plugins/scayt/dialogs/options.js b/app/webroot/js/ckeditor/plugins/scayt/dialogs/options.js similarity index 100% rename from webroot/js/ckeditor/plugins/scayt/dialogs/options.js rename to app/webroot/js/ckeditor/plugins/scayt/dialogs/options.js diff --git a/webroot/js/ckeditor/plugins/scayt/dialogs/toolbar.css b/app/webroot/js/ckeditor/plugins/scayt/dialogs/toolbar.css similarity index 100% rename from webroot/js/ckeditor/plugins/scayt/dialogs/toolbar.css rename to app/webroot/js/ckeditor/plugins/scayt/dialogs/toolbar.css diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_address.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_address.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_address.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_address.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_blockquote.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_blockquote.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_blockquote.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_blockquote.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_div.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_div.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_div.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_div.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_h1.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_h1.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_h1.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_h1.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_h2.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_h2.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_h2.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_h2.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_h3.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_h3.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_h3.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_h3.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_h4.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_h4.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_h4.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_h4.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_h5.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_h5.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_h5.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_h5.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_h6.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_h6.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_h6.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_h6.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_p.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_p.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_p.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_p.png diff --git a/webroot/js/ckeditor/plugins/showblocks/images/block_pre.png b/app/webroot/js/ckeditor/plugins/showblocks/images/block_pre.png similarity index 100% rename from webroot/js/ckeditor/plugins/showblocks/images/block_pre.png rename to app/webroot/js/ckeditor/plugins/showblocks/images/block_pre.png diff --git a/webroot/js/ckeditor/plugins/smiley/dialogs/smiley.js b/app/webroot/js/ckeditor/plugins/smiley/dialogs/smiley.js similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/dialogs/smiley.js rename to app/webroot/js/ckeditor/plugins/smiley/dialogs/smiley.js diff --git a/webroot/js/ckeditor/plugins/smiley/images/angel_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/angel_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/angel_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/angel_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/angry_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/angry_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/angry_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/angry_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/broken_heart.gif b/app/webroot/js/ckeditor/plugins/smiley/images/broken_heart.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/broken_heart.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/broken_heart.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/confused_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/confused_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/confused_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/confused_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/cry_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/cry_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/cry_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/cry_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/devil_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/devil_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/devil_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/devil_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/embaressed_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/embaressed_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/embaressed_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/embaressed_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/envelope.gif b/app/webroot/js/ckeditor/plugins/smiley/images/envelope.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/envelope.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/envelope.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/heart.gif b/app/webroot/js/ckeditor/plugins/smiley/images/heart.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/heart.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/heart.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/kiss.gif b/app/webroot/js/ckeditor/plugins/smiley/images/kiss.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/kiss.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/kiss.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/lightbulb.gif b/app/webroot/js/ckeditor/plugins/smiley/images/lightbulb.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/lightbulb.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/lightbulb.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/omg_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/omg_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/omg_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/omg_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/regular_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/regular_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/regular_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/regular_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/sad_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/sad_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/sad_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/sad_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/shades_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/shades_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/shades_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/shades_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/teeth_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/teeth_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/teeth_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/teeth_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/thumbs_down.gif b/app/webroot/js/ckeditor/plugins/smiley/images/thumbs_down.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/thumbs_down.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/thumbs_down.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/thumbs_up.gif b/app/webroot/js/ckeditor/plugins/smiley/images/thumbs_up.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/thumbs_up.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/thumbs_up.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/tounge_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/tounge_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/tounge_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/tounge_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/whatchutalkingabout_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/whatchutalkingabout_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/whatchutalkingabout_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/whatchutalkingabout_smile.gif diff --git a/webroot/js/ckeditor/plugins/smiley/images/wink_smile.gif b/app/webroot/js/ckeditor/plugins/smiley/images/wink_smile.gif similarity index 100% rename from webroot/js/ckeditor/plugins/smiley/images/wink_smile.gif rename to app/webroot/js/ckeditor/plugins/smiley/images/wink_smile.gif diff --git a/webroot/js/ckeditor/plugins/specialchar/dialogs/specialchar.js b/app/webroot/js/ckeditor/plugins/specialchar/dialogs/specialchar.js similarity index 100% rename from webroot/js/ckeditor/plugins/specialchar/dialogs/specialchar.js rename to app/webroot/js/ckeditor/plugins/specialchar/dialogs/specialchar.js diff --git a/webroot/js/ckeditor/plugins/specialchar/lang/en.js b/app/webroot/js/ckeditor/plugins/specialchar/lang/en.js similarity index 100% rename from webroot/js/ckeditor/plugins/specialchar/lang/en.js rename to app/webroot/js/ckeditor/plugins/specialchar/lang/en.js diff --git a/webroot/js/ckeditor/plugins/styles/styles/default.js b/app/webroot/js/ckeditor/plugins/styles/styles/default.js similarity index 100% rename from webroot/js/ckeditor/plugins/styles/styles/default.js rename to app/webroot/js/ckeditor/plugins/styles/styles/default.js diff --git a/webroot/js/ckeditor/plugins/stylesheetparser/plugin.js b/app/webroot/js/ckeditor/plugins/stylesheetparser/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/stylesheetparser/plugin.js rename to app/webroot/js/ckeditor/plugins/stylesheetparser/plugin.js diff --git a/webroot/js/ckeditor/plugins/table/dialogs/table.js b/app/webroot/js/ckeditor/plugins/table/dialogs/table.js similarity index 100% rename from webroot/js/ckeditor/plugins/table/dialogs/table.js rename to app/webroot/js/ckeditor/plugins/table/dialogs/table.js diff --git a/webroot/js/ckeditor/plugins/tableresize/plugin.js b/app/webroot/js/ckeditor/plugins/tableresize/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/tableresize/plugin.js rename to app/webroot/js/ckeditor/plugins/tableresize/plugin.js diff --git a/webroot/js/ckeditor/plugins/tabletools/dialogs/tableCell.js b/app/webroot/js/ckeditor/plugins/tabletools/dialogs/tableCell.js similarity index 100% rename from webroot/js/ckeditor/plugins/tabletools/dialogs/tableCell.js rename to app/webroot/js/ckeditor/plugins/tabletools/dialogs/tableCell.js diff --git a/webroot/js/ckeditor/plugins/templates/dialogs/templates.js b/app/webroot/js/ckeditor/plugins/templates/dialogs/templates.js similarity index 100% rename from webroot/js/ckeditor/plugins/templates/dialogs/templates.js rename to app/webroot/js/ckeditor/plugins/templates/dialogs/templates.js diff --git a/webroot/js/ckeditor/plugins/templates/templates/default.js b/app/webroot/js/ckeditor/plugins/templates/templates/default.js similarity index 100% rename from webroot/js/ckeditor/plugins/templates/templates/default.js rename to app/webroot/js/ckeditor/plugins/templates/templates/default.js diff --git a/webroot/js/ckeditor/plugins/templates/templates/images/template1.gif b/app/webroot/js/ckeditor/plugins/templates/templates/images/template1.gif similarity index 100% rename from webroot/js/ckeditor/plugins/templates/templates/images/template1.gif rename to app/webroot/js/ckeditor/plugins/templates/templates/images/template1.gif diff --git a/webroot/js/ckeditor/plugins/templates/templates/images/template2.gif b/app/webroot/js/ckeditor/plugins/templates/templates/images/template2.gif similarity index 100% rename from webroot/js/ckeditor/plugins/templates/templates/images/template2.gif rename to app/webroot/js/ckeditor/plugins/templates/templates/images/template2.gif diff --git a/webroot/js/ckeditor/plugins/templates/templates/images/template3.gif b/app/webroot/js/ckeditor/plugins/templates/templates/images/template3.gif similarity index 100% rename from webroot/js/ckeditor/plugins/templates/templates/images/template3.gif rename to app/webroot/js/ckeditor/plugins/templates/templates/images/template3.gif diff --git a/webroot/js/ckeditor/plugins/uicolor/dialogs/uicolor.js b/app/webroot/js/ckeditor/plugins/uicolor/dialogs/uicolor.js similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/dialogs/uicolor.js rename to app/webroot/js/ckeditor/plugins/uicolor/dialogs/uicolor.js diff --git a/webroot/js/ckeditor/plugins/uicolor/lang/en.js b/app/webroot/js/ckeditor/plugins/uicolor/lang/en.js similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/lang/en.js rename to app/webroot/js/ckeditor/plugins/uicolor/lang/en.js diff --git a/webroot/js/ckeditor/plugins/uicolor/lang/he.js b/app/webroot/js/ckeditor/plugins/uicolor/lang/he.js similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/lang/he.js rename to app/webroot/js/ckeditor/plugins/uicolor/lang/he.js diff --git a/webroot/js/ckeditor/plugins/uicolor/plugin.js b/app/webroot/js/ckeditor/plugins/uicolor/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/plugin.js rename to app/webroot/js/ckeditor/plugins/uicolor/plugin.js diff --git a/webroot/js/ckeditor/plugins/uicolor/uicolor.gif b/app/webroot/js/ckeditor/plugins/uicolor/uicolor.gif similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/uicolor.gif rename to app/webroot/js/ckeditor/plugins/uicolor/uicolor.gif diff --git a/webroot/js/ckeditor/plugins/uicolor/yui/assets/hue_bg.png b/app/webroot/js/ckeditor/plugins/uicolor/yui/assets/hue_bg.png similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/yui/assets/hue_bg.png rename to app/webroot/js/ckeditor/plugins/uicolor/yui/assets/hue_bg.png diff --git a/webroot/js/ckeditor/plugins/uicolor/yui/assets/hue_thumb.png b/app/webroot/js/ckeditor/plugins/uicolor/yui/assets/hue_thumb.png similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/yui/assets/hue_thumb.png rename to app/webroot/js/ckeditor/plugins/uicolor/yui/assets/hue_thumb.png diff --git a/webroot/js/ckeditor/plugins/uicolor/yui/assets/picker_mask.png b/app/webroot/js/ckeditor/plugins/uicolor/yui/assets/picker_mask.png similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/yui/assets/picker_mask.png rename to app/webroot/js/ckeditor/plugins/uicolor/yui/assets/picker_mask.png diff --git a/webroot/js/ckeditor/plugins/uicolor/yui/assets/picker_thumb.png b/app/webroot/js/ckeditor/plugins/uicolor/yui/assets/picker_thumb.png similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/yui/assets/picker_thumb.png rename to app/webroot/js/ckeditor/plugins/uicolor/yui/assets/picker_thumb.png diff --git a/webroot/js/ckeditor/plugins/uicolor/yui/assets/yui.css b/app/webroot/js/ckeditor/plugins/uicolor/yui/assets/yui.css similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/yui/assets/yui.css rename to app/webroot/js/ckeditor/plugins/uicolor/yui/assets/yui.css diff --git a/webroot/js/ckeditor/plugins/uicolor/yui/yui.js b/app/webroot/js/ckeditor/plugins/uicolor/yui/yui.js similarity index 100% rename from webroot/js/ckeditor/plugins/uicolor/yui/yui.js rename to app/webroot/js/ckeditor/plugins/uicolor/yui/yui.js diff --git a/webroot/js/ckeditor/plugins/wsc/dialogs/ciframe.html b/app/webroot/js/ckeditor/plugins/wsc/dialogs/ciframe.html similarity index 100% rename from webroot/js/ckeditor/plugins/wsc/dialogs/ciframe.html rename to app/webroot/js/ckeditor/plugins/wsc/dialogs/ciframe.html diff --git a/webroot/js/ckeditor/plugins/wsc/dialogs/tmpFrameset.html b/app/webroot/js/ckeditor/plugins/wsc/dialogs/tmpFrameset.html similarity index 100% rename from webroot/js/ckeditor/plugins/wsc/dialogs/tmpFrameset.html rename to app/webroot/js/ckeditor/plugins/wsc/dialogs/tmpFrameset.html diff --git a/webroot/js/ckeditor/plugins/wsc/dialogs/wsc.css b/app/webroot/js/ckeditor/plugins/wsc/dialogs/wsc.css similarity index 100% rename from webroot/js/ckeditor/plugins/wsc/dialogs/wsc.css rename to app/webroot/js/ckeditor/plugins/wsc/dialogs/wsc.css diff --git a/webroot/js/ckeditor/plugins/wsc/dialogs/wsc.js b/app/webroot/js/ckeditor/plugins/wsc/dialogs/wsc.js similarity index 100% rename from webroot/js/ckeditor/plugins/wsc/dialogs/wsc.js rename to app/webroot/js/ckeditor/plugins/wsc/dialogs/wsc.js diff --git a/webroot/js/ckeditor/plugins/xml/plugin.js b/app/webroot/js/ckeditor/plugins/xml/plugin.js similarity index 100% rename from webroot/js/ckeditor/plugins/xml/plugin.js rename to app/webroot/js/ckeditor/plugins/xml/plugin.js diff --git a/webroot/js/ckeditor/skins/kama/dialog.css b/app/webroot/js/ckeditor/skins/kama/dialog.css similarity index 100% rename from webroot/js/ckeditor/skins/kama/dialog.css rename to app/webroot/js/ckeditor/skins/kama/dialog.css diff --git a/webroot/js/ckeditor/skins/kama/editor.css b/app/webroot/js/ckeditor/skins/kama/editor.css similarity index 100% rename from webroot/js/ckeditor/skins/kama/editor.css rename to app/webroot/js/ckeditor/skins/kama/editor.css diff --git a/webroot/js/ckeditor/skins/kama/icons.png b/app/webroot/js/ckeditor/skins/kama/icons.png similarity index 100% rename from webroot/js/ckeditor/skins/kama/icons.png rename to app/webroot/js/ckeditor/skins/kama/icons.png diff --git a/webroot/js/ckeditor/skins/kama/icons_rtl.png b/app/webroot/js/ckeditor/skins/kama/icons_rtl.png similarity index 100% rename from webroot/js/ckeditor/skins/kama/icons_rtl.png rename to app/webroot/js/ckeditor/skins/kama/icons_rtl.png diff --git a/webroot/js/ckeditor/skins/kama/images/dialog_sides.gif b/app/webroot/js/ckeditor/skins/kama/images/dialog_sides.gif similarity index 100% rename from webroot/js/ckeditor/skins/kama/images/dialog_sides.gif rename to app/webroot/js/ckeditor/skins/kama/images/dialog_sides.gif diff --git a/webroot/js/ckeditor/skins/kama/images/dialog_sides.png b/app/webroot/js/ckeditor/skins/kama/images/dialog_sides.png similarity index 100% rename from webroot/js/ckeditor/skins/kama/images/dialog_sides.png rename to app/webroot/js/ckeditor/skins/kama/images/dialog_sides.png diff --git a/webroot/js/ckeditor/skins/kama/images/dialog_sides_rtl.png b/app/webroot/js/ckeditor/skins/kama/images/dialog_sides_rtl.png similarity index 100% rename from webroot/js/ckeditor/skins/kama/images/dialog_sides_rtl.png rename to app/webroot/js/ckeditor/skins/kama/images/dialog_sides_rtl.png diff --git a/webroot/js/ckeditor/skins/kama/images/mini.gif b/app/webroot/js/ckeditor/skins/kama/images/mini.gif similarity index 100% rename from webroot/js/ckeditor/skins/kama/images/mini.gif rename to app/webroot/js/ckeditor/skins/kama/images/mini.gif diff --git a/webroot/js/ckeditor/skins/kama/images/noimage.png b/app/webroot/js/ckeditor/skins/kama/images/noimage.png similarity index 100% rename from webroot/js/ckeditor/skins/kama/images/noimage.png rename to app/webroot/js/ckeditor/skins/kama/images/noimage.png diff --git a/webroot/js/ckeditor/skins/kama/images/sprites.png b/app/webroot/js/ckeditor/skins/kama/images/sprites.png similarity index 100% rename from webroot/js/ckeditor/skins/kama/images/sprites.png rename to app/webroot/js/ckeditor/skins/kama/images/sprites.png diff --git a/webroot/js/ckeditor/skins/kama/images/sprites_ie6.png b/app/webroot/js/ckeditor/skins/kama/images/sprites_ie6.png similarity index 100% rename from webroot/js/ckeditor/skins/kama/images/sprites_ie6.png rename to app/webroot/js/ckeditor/skins/kama/images/sprites_ie6.png diff --git a/webroot/js/ckeditor/skins/kama/images/toolbar_start.gif b/app/webroot/js/ckeditor/skins/kama/images/toolbar_start.gif similarity index 100% rename from webroot/js/ckeditor/skins/kama/images/toolbar_start.gif rename to app/webroot/js/ckeditor/skins/kama/images/toolbar_start.gif diff --git a/webroot/js/ckeditor/skins/kama/skin.js b/app/webroot/js/ckeditor/skins/kama/skin.js similarity index 100% rename from webroot/js/ckeditor/skins/kama/skin.js rename to app/webroot/js/ckeditor/skins/kama/skin.js diff --git a/webroot/js/ckeditor/skins/kama/templates.css b/app/webroot/js/ckeditor/skins/kama/templates.css similarity index 100% rename from webroot/js/ckeditor/skins/kama/templates.css rename to app/webroot/js/ckeditor/skins/kama/templates.css diff --git a/webroot/js/ckeditor/skins/office2003/dialog.css b/app/webroot/js/ckeditor/skins/office2003/dialog.css similarity index 100% rename from webroot/js/ckeditor/skins/office2003/dialog.css rename to app/webroot/js/ckeditor/skins/office2003/dialog.css diff --git a/webroot/js/ckeditor/skins/office2003/editor.css b/app/webroot/js/ckeditor/skins/office2003/editor.css similarity index 100% rename from webroot/js/ckeditor/skins/office2003/editor.css rename to app/webroot/js/ckeditor/skins/office2003/editor.css diff --git a/webroot/js/ckeditor/skins/office2003/icons.png b/app/webroot/js/ckeditor/skins/office2003/icons.png similarity index 100% rename from webroot/js/ckeditor/skins/office2003/icons.png rename to app/webroot/js/ckeditor/skins/office2003/icons.png diff --git a/webroot/js/ckeditor/skins/office2003/icons_rtl.png b/app/webroot/js/ckeditor/skins/office2003/icons_rtl.png similarity index 100% rename from webroot/js/ckeditor/skins/office2003/icons_rtl.png rename to app/webroot/js/ckeditor/skins/office2003/icons_rtl.png diff --git a/webroot/js/ckeditor/skins/office2003/images/dialog_sides.gif b/app/webroot/js/ckeditor/skins/office2003/images/dialog_sides.gif similarity index 100% rename from webroot/js/ckeditor/skins/office2003/images/dialog_sides.gif rename to app/webroot/js/ckeditor/skins/office2003/images/dialog_sides.gif diff --git a/webroot/js/ckeditor/skins/office2003/images/dialog_sides.png b/app/webroot/js/ckeditor/skins/office2003/images/dialog_sides.png similarity index 100% rename from webroot/js/ckeditor/skins/office2003/images/dialog_sides.png rename to app/webroot/js/ckeditor/skins/office2003/images/dialog_sides.png diff --git a/webroot/js/ckeditor/skins/office2003/images/dialog_sides_rtl.png b/app/webroot/js/ckeditor/skins/office2003/images/dialog_sides_rtl.png similarity index 100% rename from webroot/js/ckeditor/skins/office2003/images/dialog_sides_rtl.png rename to app/webroot/js/ckeditor/skins/office2003/images/dialog_sides_rtl.png diff --git a/webroot/js/ckeditor/skins/office2003/images/mini.gif b/app/webroot/js/ckeditor/skins/office2003/images/mini.gif similarity index 100% rename from webroot/js/ckeditor/skins/office2003/images/mini.gif rename to app/webroot/js/ckeditor/skins/office2003/images/mini.gif diff --git a/webroot/js/ckeditor/skins/office2003/images/noimage.png b/app/webroot/js/ckeditor/skins/office2003/images/noimage.png similarity index 100% rename from webroot/js/ckeditor/skins/office2003/images/noimage.png rename to app/webroot/js/ckeditor/skins/office2003/images/noimage.png diff --git a/webroot/js/ckeditor/skins/office2003/images/sprites.png b/app/webroot/js/ckeditor/skins/office2003/images/sprites.png similarity index 100% rename from webroot/js/ckeditor/skins/office2003/images/sprites.png rename to app/webroot/js/ckeditor/skins/office2003/images/sprites.png diff --git a/webroot/js/ckeditor/skins/office2003/images/sprites_ie6.png b/app/webroot/js/ckeditor/skins/office2003/images/sprites_ie6.png similarity index 100% rename from webroot/js/ckeditor/skins/office2003/images/sprites_ie6.png rename to app/webroot/js/ckeditor/skins/office2003/images/sprites_ie6.png diff --git a/webroot/js/ckeditor/skins/office2003/skin.js b/app/webroot/js/ckeditor/skins/office2003/skin.js similarity index 100% rename from webroot/js/ckeditor/skins/office2003/skin.js rename to app/webroot/js/ckeditor/skins/office2003/skin.js diff --git a/webroot/js/ckeditor/skins/office2003/templates.css b/app/webroot/js/ckeditor/skins/office2003/templates.css similarity index 100% rename from webroot/js/ckeditor/skins/office2003/templates.css rename to app/webroot/js/ckeditor/skins/office2003/templates.css diff --git a/webroot/js/ckeditor/skins/v2/dialog.css b/app/webroot/js/ckeditor/skins/v2/dialog.css similarity index 100% rename from webroot/js/ckeditor/skins/v2/dialog.css rename to app/webroot/js/ckeditor/skins/v2/dialog.css diff --git a/webroot/js/ckeditor/skins/v2/editor.css b/app/webroot/js/ckeditor/skins/v2/editor.css similarity index 100% rename from webroot/js/ckeditor/skins/v2/editor.css rename to app/webroot/js/ckeditor/skins/v2/editor.css diff --git a/webroot/js/ckeditor/skins/v2/icons.png b/app/webroot/js/ckeditor/skins/v2/icons.png similarity index 100% rename from webroot/js/ckeditor/skins/v2/icons.png rename to app/webroot/js/ckeditor/skins/v2/icons.png diff --git a/webroot/js/ckeditor/skins/v2/icons_rtl.png b/app/webroot/js/ckeditor/skins/v2/icons_rtl.png similarity index 100% rename from webroot/js/ckeditor/skins/v2/icons_rtl.png rename to app/webroot/js/ckeditor/skins/v2/icons_rtl.png diff --git a/webroot/js/ckeditor/skins/v2/images/dialog_sides.gif b/app/webroot/js/ckeditor/skins/v2/images/dialog_sides.gif similarity index 100% rename from webroot/js/ckeditor/skins/v2/images/dialog_sides.gif rename to app/webroot/js/ckeditor/skins/v2/images/dialog_sides.gif diff --git a/webroot/js/ckeditor/skins/v2/images/dialog_sides.png b/app/webroot/js/ckeditor/skins/v2/images/dialog_sides.png similarity index 100% rename from webroot/js/ckeditor/skins/v2/images/dialog_sides.png rename to app/webroot/js/ckeditor/skins/v2/images/dialog_sides.png diff --git a/webroot/js/ckeditor/skins/v2/images/dialog_sides_rtl.png b/app/webroot/js/ckeditor/skins/v2/images/dialog_sides_rtl.png similarity index 100% rename from webroot/js/ckeditor/skins/v2/images/dialog_sides_rtl.png rename to app/webroot/js/ckeditor/skins/v2/images/dialog_sides_rtl.png diff --git a/webroot/js/ckeditor/skins/v2/images/mini.gif b/app/webroot/js/ckeditor/skins/v2/images/mini.gif similarity index 100% rename from webroot/js/ckeditor/skins/v2/images/mini.gif rename to app/webroot/js/ckeditor/skins/v2/images/mini.gif diff --git a/webroot/js/ckeditor/skins/v2/images/noimage.png b/app/webroot/js/ckeditor/skins/v2/images/noimage.png similarity index 100% rename from webroot/js/ckeditor/skins/v2/images/noimage.png rename to app/webroot/js/ckeditor/skins/v2/images/noimage.png diff --git a/webroot/js/ckeditor/skins/v2/images/sprites.png b/app/webroot/js/ckeditor/skins/v2/images/sprites.png similarity index 100% rename from webroot/js/ckeditor/skins/v2/images/sprites.png rename to app/webroot/js/ckeditor/skins/v2/images/sprites.png diff --git a/webroot/js/ckeditor/skins/v2/images/sprites_ie6.png b/app/webroot/js/ckeditor/skins/v2/images/sprites_ie6.png similarity index 100% rename from webroot/js/ckeditor/skins/v2/images/sprites_ie6.png rename to app/webroot/js/ckeditor/skins/v2/images/sprites_ie6.png diff --git a/webroot/js/ckeditor/skins/v2/images/toolbar_start.gif b/app/webroot/js/ckeditor/skins/v2/images/toolbar_start.gif similarity index 100% rename from webroot/js/ckeditor/skins/v2/images/toolbar_start.gif rename to app/webroot/js/ckeditor/skins/v2/images/toolbar_start.gif diff --git a/webroot/js/ckeditor/skins/v2/skin.js b/app/webroot/js/ckeditor/skins/v2/skin.js similarity index 100% rename from webroot/js/ckeditor/skins/v2/skin.js rename to app/webroot/js/ckeditor/skins/v2/skin.js diff --git a/webroot/js/ckeditor/skins/v2/templates.css b/app/webroot/js/ckeditor/skins/v2/templates.css similarity index 100% rename from webroot/js/ckeditor/skins/v2/templates.css rename to app/webroot/js/ckeditor/skins/v2/templates.css diff --git a/webroot/js/ckeditor/themes/default/theme.js b/app/webroot/js/ckeditor/themes/default/theme.js similarity index 100% rename from webroot/js/ckeditor/themes/default/theme.js rename to app/webroot/js/ckeditor/themes/default/theme.js diff --git a/webroot/js/costing_dialog.js b/app/webroot/js/costing_dialog.js similarity index 100% rename from webroot/js/costing_dialog.js rename to app/webroot/js/costing_dialog.js diff --git a/webroot/js/document_add_edit.js b/app/webroot/js/document_add_edit.js similarity index 100% rename from webroot/js/document_add_edit.js rename to app/webroot/js/document_add_edit.js diff --git a/webroot/js/document_attachments.js b/app/webroot/js/document_attachments.js similarity index 100% rename from webroot/js/document_attachments.js rename to app/webroot/js/document_attachments.js diff --git a/webroot/js/editLineItem.js b/app/webroot/js/editLineItem.js similarity index 100% rename from webroot/js/editLineItem.js rename to app/webroot/js/editLineItem.js diff --git a/webroot/js/edit_product.js b/app/webroot/js/edit_product.js similarity index 100% rename from webroot/js/edit_product.js rename to app/webroot/js/edit_product.js diff --git a/webroot/js/email_frame.js b/app/webroot/js/email_frame.js similarity index 100% rename from webroot/js/email_frame.js rename to app/webroot/js/email_frame.js diff --git a/webroot/js/email_table.js b/app/webroot/js/email_table.js similarity index 100% rename from webroot/js/email_table.js rename to app/webroot/js/email_table.js diff --git a/webroot/js/enquiry_table.js b/app/webroot/js/enquiry_table.js similarity index 100% rename from webroot/js/enquiry_table.js rename to app/webroot/js/enquiry_table.js diff --git a/webroot/js/findcustomer.js b/app/webroot/js/findcustomer.js similarity index 100% rename from webroot/js/findcustomer.js rename to app/webroot/js/findcustomer.js diff --git a/webroot/js/global.js b/app/webroot/js/global.js similarity index 100% rename from webroot/js/global.js rename to app/webroot/js/global.js diff --git a/webroot/js/globalsearch.js b/app/webroot/js/globalsearch.js similarity index 100% rename from webroot/js/globalsearch.js rename to app/webroot/js/globalsearch.js diff --git a/webroot/js/job_index.js b/app/webroot/js/job_index.js similarity index 100% rename from webroot/js/job_index.js rename to app/webroot/js/job_index.js diff --git a/webroot/js/jquery-ui.js b/app/webroot/js/jquery-ui.js similarity index 100% rename from webroot/js/jquery-ui.js rename to app/webroot/js/jquery-ui.js diff --git a/webroot/js/jquery.form.js b/app/webroot/js/jquery.form.js similarity index 100% rename from webroot/js/jquery.form.js rename to app/webroot/js/jquery.form.js diff --git a/webroot/js/jquery.jeditable.mini.js b/app/webroot/js/jquery.jeditable.mini.js similarity index 100% rename from webroot/js/jquery.jeditable.mini.js rename to app/webroot/js/jquery.jeditable.mini.js diff --git a/webroot/js/jquery.js b/app/webroot/js/jquery.js similarity index 100% rename from webroot/js/jquery.js rename to app/webroot/js/jquery.js diff --git a/webroot/js/jquery.validate.js b/app/webroot/js/jquery.validate.js similarity index 100% rename from webroot/js/jquery.validate.js rename to app/webroot/js/jquery.validate.js diff --git a/webroot/js/lineItemPriceNoCosting.js b/app/webroot/js/lineItemPriceNoCosting.js similarity index 100% rename from webroot/js/lineItemPriceNoCosting.js rename to app/webroot/js/lineItemPriceNoCosting.js diff --git a/webroot/js/menu.js b/app/webroot/js/menu.js similarity index 100% rename from webroot/js/menu.js rename to app/webroot/js/menu.js diff --git a/webroot/js/product-model-number-builder.js b/app/webroot/js/product-model-number-builder.js similarity index 100% rename from webroot/js/product-model-number-builder.js rename to app/webroot/js/product-model-number-builder.js diff --git a/webroot/js/quotenik/add_costing.js b/app/webroot/js/quotenik/add_costing.js similarity index 100% rename from webroot/js/quotenik/add_costing.js rename to app/webroot/js/quotenik/add_costing.js diff --git a/webroot/js/quotenik/add_datetime.js b/app/webroot/js/quotenik/add_datetime.js similarity index 100% rename from webroot/js/quotenik/add_datetime.js rename to app/webroot/js/quotenik/add_datetime.js diff --git a/webroot/js/quotenik/product_buildup.js b/app/webroot/js/quotenik/product_buildup.js similarity index 100% rename from webroot/js/quotenik/product_buildup.js rename to app/webroot/js/quotenik/product_buildup.js diff --git a/webroot/js/reports.js b/app/webroot/js/reports.js similarity index 100% rename from webroot/js/reports.js rename to app/webroot/js/reports.js diff --git a/webroot/js/search.js b/app/webroot/js/search.js similarity index 100% rename from webroot/js/search.js rename to app/webroot/js/search.js diff --git a/webroot/js/shipment_add.js b/app/webroot/js/shipment_add.js similarity index 100% rename from webroot/js/shipment_add.js rename to app/webroot/js/shipment_add.js diff --git a/webroot/js/shipment_index.js b/app/webroot/js/shipment_index.js similarity index 100% rename from webroot/js/shipment_index.js rename to app/webroot/js/shipment_index.js diff --git a/webroot/js/similar-customer.js b/app/webroot/js/similar-customer.js similarity index 100% rename from webroot/js/similar-customer.js rename to app/webroot/js/similar-customer.js diff --git a/webroot/js/vendors.php b/app/webroot/js/vendors.php similarity index 100% rename from webroot/js/vendors.php rename to app/webroot/js/vendors.php diff --git a/webroot/js/view_enquiry.js b/app/webroot/js/view_enquiry.js similarity index 100% rename from webroot/js/view_enquiry.js rename to app/webroot/js/view_enquiry.js diff --git a/webroot/js/view_user.js b/app/webroot/js/view_user.js similarity index 100% rename from webroot/js/view_user.js rename to app/webroot/js/view_user.js diff --git a/webroot/js/view_user_email.js b/app/webroot/js/view_user_email.js similarity index 100% rename from webroot/js/view_user_email.js rename to app/webroot/js/view_user_email.js diff --git a/webroot/q.ico b/app/webroot/q.ico similarity index 100% rename from webroot/q.ico rename to app/webroot/q.ico diff --git a/webroot/test.php b/app/webroot/test.php similarity index 100% rename from webroot/test.php rename to app/webroot/test.php diff --git a/cake/.DS_Store b/cake/.DS_Store new file mode 100755 index 00000000..28432d55 Binary files /dev/null and b/cake/.DS_Store differ diff --git a/cake/LICENSE.txt b/cake/LICENSE.txt new file mode 100755 index 00000000..e54a5572 --- /dev/null +++ b/cake/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License + +CakePHP(tm) : The Rapid Development PHP Framework (http://www.cakephp.org) +Copyright 2005-2007, Cake Software Foundation, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/cake/VERSION.txt b/cake/VERSION.txt new file mode 100755 index 00000000..3a1f10ea --- /dev/null +++ b/cake/VERSION.txt @@ -0,0 +1 @@ +1.2.5 \ No newline at end of file diff --git a/cake/basics.php b/cake/basics.php new file mode 100755 index 00000000..5e387c55 --- /dev/null +++ b/cake/basics.php @@ -0,0 +1,940 @@ + 0) { + if ($showFrom) { + $calledFrom = debug_backtrace(); + echo '' . substr(str_replace(ROOT, '', $calledFrom[0]['file']), 1) . ''; + echo ' (line ' . $calledFrom[0]['line'] . ')'; + } + echo "\n
\n";
+
+ $var = print_r($var, true);
+ if ($showHtml) {
+ $var = str_replace('<', '<', str_replace('>', '>', $var));
+ }
+ echo $var . "\n\n";
+ }
+ }
+if (!function_exists('getMicrotime')) {
+/**
+ * Returns microtime for execution time checking
+ *
+ * @return float Microtime
+ */
+ function getMicrotime() {
+ list($usec, $sec) = explode(' ', microtime());
+ return ((float)$usec + (float)$sec);
+ }
+}
+if (!function_exists('sortByKey')) {
+/**
+ * Sorts given $array by key $sortby.
+ *
+ * @param array $array Array to sort
+ * @param string $sortby Sort by this key
+ * @param string $order Sort order asc/desc (ascending or descending).
+ * @param integer $type Type of sorting to perform
+ * @return mixed Sorted array
+ */
+ function sortByKey(&$array, $sortby, $order = 'asc', $type = SORT_NUMERIC) {
+ if (!is_array($array)) {
+ return null;
+ }
+
+ foreach ($array as $key => $val) {
+ $sa[$key] = $val[$sortby];
+ }
+
+ if ($order == 'asc') {
+ asort($sa, $type);
+ } else {
+ arsort($sa, $type);
+ }
+
+ foreach ($sa as $key => $val) {
+ $out[] = $array[$key];
+ }
+ return $out;
+ }
+}
+if (!function_exists('array_combine')) {
+/**
+ * Combines given identical arrays by using the first array's values as keys,
+ * and the second one's values as values. (Implemented for backwards compatibility with PHP4)
+ *
+ * @param array $a1 Array to use for keys
+ * @param array $a2 Array to use for values
+ * @return mixed Outputs either combined array or false.
+ */
+ function array_combine($a1, $a2) {
+ $a1 = array_values($a1);
+ $a2 = array_values($a2);
+ $c1 = count($a1);
+ $c2 = count($a2);
+
+ if ($c1 != $c2) {
+ return false;
+ }
+ if ($c1 <= 0) {
+ return false;
+ }
+ $output = array();
+
+ for ($i = 0; $i < $c1; $i++) {
+ $output[$a1[$i]] = $a2[$i];
+ }
+ return $output;
+ }
+}
+/**
+ * Convenience method for htmlspecialchars.
+ *
+ * @param string $text Text to wrap through htmlspecialchars
+ * @param string $charset Character set to use when escaping. Defaults to config value in 'App.encoding' or 'UTF-8'
+ * @return string Wrapped text
+ * @link http://book.cakephp.org/view/703/h
+ */
+ function h($text, $charset = null) {
+ if (is_array($text)) {
+ return array_map('h', $text);
+ }
+ if (empty($charset)) {
+ $charset = Configure::read('App.encoding');
+ }
+ if (empty($charset)) {
+ $charset = 'UTF-8';
+ }
+ return htmlspecialchars($text, ENT_QUOTES, $charset);
+ }
+/**
+ * Returns an array of all the given parameters.
+ *
+ * Example:
+ *
+ * `a('a', 'b')`
+ *
+ * Would return:
+ *
+ * `array('a', 'b')`
+ *
+ * @return array Array of given parameters
+ * @link http://book.cakephp.org/view/694/a
+ */
+ function a() {
+ $args = func_get_args();
+ return $args;
+ }
+/**
+ * Constructs associative array from pairs of arguments.
+ *
+ * Example:
+ *
+ * `aa('a','b')`
+ *
+ * Would return:
+ *
+ * `array('a'=>'b')`
+ *
+ * @return array Associative array
+ * @link http://book.cakephp.org/view/695/aa
+ */
+ function aa() {
+ $args = func_get_args();
+ $argc = count($args);
+ for ($i = 0; $i < $argc; $i++) {
+ if ($i + 1 < $argc) {
+ $a[$args[$i]] = $args[$i + 1];
+ } else {
+ $a[$args[$i]] = null;
+ }
+ $i++;
+ }
+ return $a;
+ }
+/**
+ * Convenience method for echo().
+ *
+ * @param string $text String to echo
+ * @link http://book.cakephp.org/view/700/e
+ */
+ function e($text) {
+ echo $text;
+ }
+/**
+ * Convenience method for strtolower().
+ *
+ * @param string $str String to lowercase
+ * @return string Lowercased string
+ * @link http://book.cakephp.org/view/705/low
+ */
+ function low($str) {
+ return strtolower($str);
+ }
+/**
+ * Convenience method for strtoupper().
+ *
+ * @param string $str String to uppercase
+ * @return string Uppercased string
+ * @link http://book.cakephp.org/view/710/up
+ */
+ function up($str) {
+ return strtoupper($str);
+ }
+/**
+ * Convenience method for str_replace().
+ *
+ * @param string $search String to be replaced
+ * @param string $replace String to insert
+ * @param string $subject String to search
+ * @return string Replaced string
+ * @link http://book.cakephp.org/view/708/r
+ */
+ function r($search, $replace, $subject) {
+ return str_replace($search, $replace, $subject);
+ }
+/**
+ * Print_r convenience function, which prints out tags around
+ * the output of given array. Similar to debug().
+ *
+ * @see debug()
+ * @param array $var Variable to print out
+ * @param boolean $showFrom If set to true, the method prints from where the function was called
+ * @link http://book.cakephp.org/view/707/pr
+ */
+ function pr($var) {
+ if (Configure::read() > 0) {
+ echo '';
+ print_r($var);
+ echo '
';
+ }
+ }
+/**
+ * Display parameters.
+ *
+ * @param mixed $p Parameter as string or array
+ * @return string
+ */
+ function params($p) {
+ if (!is_array($p) || count($p) == 0) {
+ return null;
+ }
+ if (is_array($p[0]) && count($p) == 1) {
+ return $p[0];
+ }
+ return $p;
+ }
+/**
+ * Merge a group of arrays
+ *
+ * @param array First array
+ * @param array Second array
+ * @param array Third array
+ * @param array Etc...
+ * @return array All array parameters merged into one
+ * @link http://book.cakephp.org/view/696/am
+ */
+ function am() {
+ $r = array();
+ $args = func_get_args();
+ foreach ($args as $a) {
+ if (!is_array($a)) {
+ $a = array($a);
+ }
+ $r = array_merge($r, $a);
+ }
+ return $r;
+ }
+/**
+ * Gets an environment variable from available sources, and provides emulation
+ * for unsupported or inconsistent environment variables (i.e. DOCUMENT_ROOT on
+ * IIS, or SCRIPT_NAME in CGI mode). Also exposes some additional custom
+ * environment information.
+ *
+ * @param string $key Environment variable name.
+ * @return string Environment variable setting.
+ * @link http://book.cakephp.org/view/701/env
+ */
+ function env($key) {
+ if ($key == 'HTTPS') {
+ if (isset($_SERVER['HTTPS'])) {
+ return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
+ }
+ return (strpos(env('SCRIPT_URI'), 'https://') === 0);
+ }
+
+ if ($key == 'SCRIPT_NAME') {
+ if (env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) {
+ $key = 'SCRIPT_URL';
+ }
+ }
+
+ $val = null;
+ if (isset($_SERVER[$key])) {
+ $val = $_SERVER[$key];
+ } elseif (isset($_ENV[$key])) {
+ $val = $_ENV[$key];
+ } elseif (getenv($key) !== false) {
+ $val = getenv($key);
+ }
+
+ if ($key === 'REMOTE_ADDR' && $val === env('SERVER_ADDR')) {
+ $addr = env('HTTP_PC_REMOTE_ADDR');
+ if ($addr !== null) {
+ $val = $addr;
+ }
+ }
+
+ if ($val !== null) {
+ return $val;
+ }
+
+ switch ($key) {
+ case 'SCRIPT_FILENAME':
+ if (defined('SERVER_IIS') && SERVER_IIS === true) {
+ return str_replace('\\\\', '\\', env('PATH_TRANSLATED'));
+ }
+ break;
+ case 'DOCUMENT_ROOT':
+ $name = env('SCRIPT_NAME');
+ $filename = env('SCRIPT_FILENAME');
+ $offset = 0;
+ if (!strpos($name, '.php')) {
+ $offset = 4;
+ }
+ return substr($filename, 0, strlen($filename) - (strlen($name) + $offset));
+ break;
+ case 'PHP_SELF':
+ return str_replace(env('DOCUMENT_ROOT'), '', env('SCRIPT_FILENAME'));
+ break;
+ case 'CGI_MODE':
+ return (PHP_SAPI === 'cgi');
+ break;
+ case 'HTTP_BASE':
+ $host = env('HTTP_HOST');
+ if (substr_count($host, '.') !== 1) {
+ return preg_replace('/^([^.])*/i', null, env('HTTP_HOST'));
+ }
+ return '.' . $host;
+ break;
+ }
+ return null;
+ }
+if (!function_exists('file_put_contents')) {
+/**
+ * Writes data into file.
+ *
+ * If file exists, it will be overwritten. If data is an array, it will be join()ed with an empty string.
+ *
+ * @param string $fileName File name.
+ * @param mixed $data String or array.
+ * @return boolean Success
+ */
+ function file_put_contents($fileName, $data) {
+ if (is_array($data)) {
+ $data = join('', $data);
+ }
+ $res = @fopen($fileName, 'w+b');
+
+ if ($res) {
+ $write = @fwrite($res, $data);
+ if ($write === false) {
+ return false;
+ } else {
+ @fclose($res);
+ return $write;
+ }
+ }
+ return false;
+ }
+}
+/**
+ * Reads/writes temporary data to cache files or session.
+ *
+ * @param string $path File path within /tmp to save the file.
+ * @param mixed $data The data to save to the temporary file.
+ * @param mixed $expires A valid strtotime string when the data expires.
+ * @param string $target The target of the cached data; either 'cache' or 'public'.
+ * @return mixed The contents of the temporary file.
+ * @deprecated Please use Cache::write() instead
+ */
+ function cache($path, $data = null, $expires = '+1 day', $target = 'cache') {
+ if (Configure::read('Cache.disable')) {
+ return null;
+ }
+ $now = time();
+
+ if (!is_numeric($expires)) {
+ $expires = strtotime($expires, $now);
+ }
+
+ switch (low($target)) {
+ case 'cache':
+ $filename = CACHE . $path;
+ break;
+ case 'public':
+ $filename = WWW_ROOT . $path;
+ break;
+ case 'tmp':
+ $filename = TMP . $path;
+ break;
+ }
+ $timediff = $expires - $now;
+ $filetime = false;
+
+ if (file_exists($filename)) {
+ $filetime = @filemtime($filename);
+ }
+
+ if ($data === null) {
+ if (file_exists($filename) && $filetime !== false) {
+ if ($filetime + $timediff < $now) {
+ @unlink($filename);
+ } else {
+ $data = @file_get_contents($filename);
+ }
+ }
+ } elseif (is_writable(dirname($filename))) {
+ @file_put_contents($filename, $data);
+ }
+ return $data;
+ }
+/**
+ * Used to delete files in the cache directories, or clear contents of cache directories
+ *
+ * @param mixed $params As String name to be searched for deletion, if name is a directory all files in
+ * directory will be deleted. If array, names to be searched for deletion. If clearCache() without params,
+ * all files in app/tmp/cache/views will be deleted
+ * @param string $type Directory in tmp/cache defaults to view directory
+ * @param string $ext The file extension you are deleting
+ * @return true if files found and deleted false otherwise
+ */
+ function clearCache($params = null, $type = 'views', $ext = '.php') {
+ if (is_string($params) || $params === null) {
+ $params = preg_replace('/\/\//', '/', $params);
+ $cache = CACHE . $type . DS . $params;
+
+ if (is_file($cache . $ext)) {
+ @unlink($cache . $ext);
+ return true;
+ } elseif (is_dir($cache)) {
+ $files = glob($cache . '*');
+
+ if ($files === false) {
+ return false;
+ }
+
+ foreach ($files as $file) {
+ if (is_file($file)) {
+ @unlink($file);
+ }
+ }
+ return true;
+ } else {
+ $cache = array(
+ CACHE . $type . DS . '*' . $params . $ext,
+ CACHE . $type . DS . '*' . $params . '_*' . $ext
+ );
+ $files = array();
+ while ($search = array_shift($cache)) {
+ $results = glob($search);
+ if ($results !== false) {
+ $files = array_merge($files, $results);
+ }
+ }
+ if (empty($files)) {
+ return false;
+ }
+ foreach ($files as $file) {
+ if (is_file($file)) {
+ @unlink($file);
+ }
+ }
+ return true;
+ }
+ } elseif (is_array($params)) {
+ foreach ($params as $file) {
+ clearCache($file, $type, $ext);
+ }
+ return true;
+ }
+ return false;
+ }
+/**
+ * Recursively strips slashes from all values in an array
+ *
+ * @param array $values Array of values to strip slashes
+ * @return mixed What is returned from calling stripslashes
+ * @link http://book.cakephp.org/view/709/stripslashes_deep
+ */
+ function stripslashes_deep($values) {
+ if (is_array($values)) {
+ foreach ($values as $key => $value) {
+ $values[$key] = stripslashes_deep($value);
+ }
+ } else {
+ $values = stripslashes($values);
+ }
+ return $values;
+ }
+/**
+ * Returns a translated string if one is found; Otherwise, the submitted message.
+ *
+ * @param string $singular Text to translate
+ * @param boolean $return Set to true to return translated string, or false to echo
+ * @return mixed translated string if $return is false string will be echoed
+ * @link http://book.cakephp.org/view/693/__
+ */
+ function __($singular, $return = false) {
+ if (!$singular) {
+ return;
+ }
+ if (!class_exists('I18n')) {
+ App::import('Core', 'i18n');
+ }
+
+ if ($return === false) {
+ echo I18n::translate($singular);
+ } else {
+ return I18n::translate($singular);
+ }
+ }
+/**
+ * Returns correct plural form of message identified by $singular and $plural for count $count.
+ * Some languages have more than one form for plural messages dependent on the count.
+ *
+ * @param string $singular Singular text to translate
+ * @param string $plural Plural text
+ * @param integer $count Count
+ * @param boolean $return true to return, false to echo
+ * @return mixed plural form of translated string if $return is false string will be echoed
+ */
+ function __n($singular, $plural, $count, $return = false) {
+ if (!$singular) {
+ return;
+ }
+ if (!class_exists('I18n')) {
+ App::import('Core', 'i18n');
+ }
+
+ if ($return === false) {
+ echo I18n::translate($singular, $plural, null, 6, $count);
+ } else {
+ return I18n::translate($singular, $plural, null, 6, $count);
+ }
+ }
+/**
+ * Allows you to override the current domain for a single message lookup.
+ *
+ * @param string $domain Domain
+ * @param string $msg String to translate
+ * @param string $return true to return, false to echo
+ * @return translated string if $return is false string will be echoed
+ */
+ function __d($domain, $msg, $return = false) {
+ if (!$msg) {
+ return;
+ }
+ if (!class_exists('I18n')) {
+ App::import('Core', 'i18n');
+ }
+
+ if ($return === false) {
+ echo I18n::translate($msg, null, $domain);
+ } else {
+ return I18n::translate($msg, null, $domain);
+ }
+ }
+/**
+ * Allows you to override the current domain for a single plural message lookup.
+ * Returns correct plural form of message identified by $singular and $plural for count $count
+ * from domain $domain.
+ *
+ * @param string $domain Domain
+ * @param string $singular Singular string to translate
+ * @param string $plural Plural
+ * @param integer $count Count
+ * @param boolean $return true to return, false to echo
+ * @return plural form of translated string if $return is false string will be echoed
+ */
+ function __dn($domain, $singular, $plural, $count, $return = false) {
+ if (!$singular) {
+ return;
+ }
+ if (!class_exists('I18n')) {
+ App::import('Core', 'i18n');
+ }
+
+ if ($return === false) {
+ echo I18n::translate($singular, $plural, $domain, 6, $count);
+ } else {
+ return I18n::translate($singular, $plural, $domain, 6, $count);
+ }
+ }
+/**
+ * Allows you to override the current domain for a single message lookup.
+ * It also allows you to specify a category.
+ *
+ * The category argument allows a specific category of the locale settings to be used for fetching a message.
+ * Valid categories are: LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES and LC_ALL.
+ *
+ * Note that the category must be specified with a numeric value, instead of the constant name. The values are:
+ *
+ * - LC_ALL 0
+ * - LC_COLLATE 1
+ * - LC_CTYPE 2
+ * - LC_MONETARY 3
+ * - LC_NUMERIC 4
+ * - LC_TIME 5
+ * - LC_MESSAGES 6
+ *
+ * @param string $domain Domain
+ * @param string $msg Message to translate
+ * @param integer $category Category
+ * @param boolean $return true to return, false to echo
+ * @return translated string if $return is false string will be echoed
+ */
+ function __dc($domain, $msg, $category, $return = false) {
+ if (!$msg) {
+ return;
+ }
+ if (!class_exists('I18n')) {
+ App::import('Core', 'i18n');
+ }
+
+ if ($return === false) {
+ echo I18n::translate($msg, null, $domain, $category);
+ } else {
+ return I18n::translate($msg, null, $domain, $category);
+ }
+ }
+/**
+ * Allows you to override the current domain for a single plural message lookup.
+ * It also allows you to specify a category.
+ * Returns correct plural form of message identified by $singular and $plural for count $count
+ * from domain $domain.
+ *
+ * The category argument allows a specific category of the locale settings to be used for fetching a message.
+ * Valid categories are: LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES and LC_ALL.
+ *
+ * Note that the category must be specified with a numeric value, instead of the constant name. The values are:
+ *
+ * - LC_ALL 0
+ * - LC_COLLATE 1
+ * - LC_CTYPE 2
+ * - LC_MONETARY 3
+ * - LC_NUMERIC 4
+ * - LC_TIME 5
+ * - LC_MESSAGES 6
+ *
+ * @param string $domain Domain
+ * @param string $singular Singular string to translate
+ * @param string $plural Plural
+ * @param integer $count Count
+ * @param integer $category Category
+ * @param boolean $return true to return, false to echo
+ * @return plural form of translated string if $return is false string will be echoed
+ */
+ function __dcn($domain, $singular, $plural, $count, $category, $return = false) {
+ if (!$singular) {
+ return;
+ }
+ if (!class_exists('I18n')) {
+ App::import('Core', 'i18n');
+ }
+
+ if ($return === false) {
+ echo I18n::translate($singular, $plural, $domain, $category, $count);
+ } else {
+ return I18n::translate($singular, $plural, $domain, $category, $count);
+ }
+ }
+/**
+ * The category argument allows a specific category of the locale settings to be used for fetching a message.
+ * Valid categories are: LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES and LC_ALL.
+ *
+ * Note that the category must be specified with a numeric value, instead of the constant name. The values are:
+ *
+ * - LC_ALL 0
+ * - LC_COLLATE 1
+ * - LC_CTYPE 2
+ * - LC_MONETARY 3
+ * - LC_NUMERIC 4
+ * - LC_TIME 5
+ * - LC_MESSAGES 6
+ *
+ * @param string $msg String to translate
+ * @param integer $category Category
+ * @param string $return true to return, false to echo
+ * @return translated string if $return is false string will be echoed
+ */
+ function __c($msg, $category, $return = false) {
+ if (!$msg) {
+ return;
+ }
+ if (!class_exists('I18n')) {
+ App::import('Core', 'i18n');
+ }
+
+ if ($return === false) {
+ echo I18n::translate($msg, null, null, $category);
+ } else {
+ return I18n::translate($msg, null, null, $category);
+ }
+ }
+/**
+ * Computes the difference of arrays using keys for comparison.
+ *
+ * @param array First array
+ * @param array Second array
+ * @return array Array with different keys
+ */
+ if (!function_exists('array_diff_key')) {
+ function array_diff_key() {
+ $valuesDiff = array();
+
+ $argc = func_num_args();
+ if ($argc < 2) {
+ return false;
+ }
+
+ $args = func_get_args();
+ foreach ($args as $param) {
+ if (!is_array($param)) {
+ return false;
+ }
+ }
+
+ foreach ($args[0] as $valueKey => $valueData) {
+ for ($i = 1; $i < $argc; $i++) {
+ if (array_key_exists($valueKey, $args[$i])) {
+ continue 2;
+ }
+ }
+ $valuesDiff[$valueKey] = $valueData;
+ }
+ return $valuesDiff;
+ }
+ }
+/**
+ * Computes the intersection of arrays using keys for comparison
+ *
+ * @param array First array
+ * @param array Second array
+ * @return array Array with interesected keys
+ */
+ if (!function_exists('array_intersect_key')) {
+ function array_intersect_key($arr1, $arr2) {
+ $res = array();
+ foreach ($arr1 as $key => $value) {
+ if (array_key_exists($key, $arr2)) {
+ $res[$key] = $arr1[$key];
+ }
+ }
+ return $res;
+ }
+ }
+/**
+ * Shortcut to Log::write.
+ *
+ * @param string $message Message to write to log
+ */
+ function LogError($message) {
+ if (!class_exists('CakeLog')) {
+ App::import('Core', 'CakeLog');
+ }
+ $bad = array("\n", "\r", "\t");
+ $good = ' ';
+ CakeLog::write('error', str_replace($bad, $good, $message));
+ }
+/**
+ * Searches include path for files.
+ *
+ * @param string $file File to look for
+ * @return Full path to file if exists, otherwise false
+ * @link http://book.cakephp.org/view/702/fileExistsInPath
+ */
+ function fileExistsInPath($file) {
+ $paths = explode(PATH_SEPARATOR, ini_get('include_path'));
+ foreach ($paths as $path) {
+ $fullPath = $path . DS . $file;
+
+ if (file_exists($fullPath)) {
+ return $fullPath;
+ } elseif (file_exists($file)) {
+ return $file;
+ }
+ }
+ return false;
+ }
+/**
+ * Convert forward slashes to underscores and removes first and last underscores in a string
+ *
+ * @param string String to convert
+ * @return string with underscore remove from start and end of string
+ * @link http://book.cakephp.org/view/697/convertSlash
+ */
+ function convertSlash($string) {
+ $string = trim($string, '/');
+ $string = preg_replace('/\/\//', '/', $string);
+ $string = str_replace('/', '_', $string);
+ return $string;
+ }
+/**
+ * Implements http_build_query for PHP4.
+ *
+ * @param string $data Data to set in query string
+ * @param string $prefix If numeric indices, prepend this to index for elements in base array.
+ * @param string $argSep String used to separate arguments
+ * @param string $baseKey Base key
+ * @return string URL encoded query string
+ * @see http://php.net/http_build_query
+ */
+ if (!function_exists('http_build_query')) {
+ function http_build_query($data, $prefix = null, $argSep = null, $baseKey = null) {
+ if (empty($argSep)) {
+ $argSep = ini_get('arg_separator.output');
+ }
+ if (is_object($data)) {
+ $data = get_object_vars($data);
+ }
+ $out = array();
+
+ foreach ((array)$data as $key => $v) {
+ if (is_numeric($key) && !empty($prefix)) {
+ $key = $prefix . $key;
+ }
+ $key = urlencode($key);
+
+ if (!empty($baseKey)) {
+ $key = $baseKey . '[' . $key . ']';
+ }
+
+ if (is_array($v) || is_object($v)) {
+ $out[] = http_build_query($v, $prefix, $argSep, $key);
+ } else {
+ $out[] = $key . '=' . urlencode($v);
+ }
+ }
+ return implode($argSep, $out);
+ }
+ }
+/**
+ * Wraps ternary operations. If $condition is a non-empty value, $val1 is returned, otherwise $val2.
+ * Don't use for isset() conditions, or wrap your variable with @ operator:
+ * Example:
+ *
+ * `ife(isset($variable), @$variable, 'default');`
+ *
+ * @param mixed $condition Conditional expression
+ * @param mixed $val1 Value to return in case condition matches
+ * @param mixed $val2 Value to return if condition doesn't match
+ * @return mixed $val1 or $val2, depending on whether $condition evaluates to a non-empty expression.
+ * @link http://book.cakephp.org/view/704/ife
+ */
+ function ife($condition, $val1 = null, $val2 = null) {
+ if (!empty($condition)) {
+ return $val1;
+ }
+ return $val2;
+ }
+?>
\ No newline at end of file
diff --git a/cake/bootstrap.php b/cake/bootstrap.php
new file mode 100755
index 00000000..0a28e555
--- /dev/null
+++ b/cake/bootstrap.php
@@ -0,0 +1,52 @@
+= 5));
+}
+if (!defined('E_DEPRECATED')) {
+ define('E_DEPRECATED', 8192);
+}
+error_reporting(E_ALL & ~E_DEPRECATED);
+/**
+ * Configuration, directory layout and standard libraries
+ */
+ if (!isset($bootstrap)) {
+ require CORE_PATH . 'cake' . DS . 'basics.php';
+ $TIME_START = getMicrotime();
+ require CORE_PATH . 'cake' . DS . 'config' . DS . 'paths.php';
+ require LIBS . 'object.php';
+ require LIBS . 'inflector.php';
+ require LIBS . 'configure.php';
+ }
+ require LIBS . 'cache.php';
+
+ Configure::getInstance();
+
+ $url = null;
+
+ App::import('Core', array('Dispatcher'));
+?>
\ No newline at end of file
diff --git a/cake/config/config.php b/cake/config/config.php
new file mode 100755
index 00000000..24718485
--- /dev/null
+++ b/cake/config/config.php
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/cake/config/paths.php b/cake/config/paths.php
new file mode 100755
index 00000000..6a6449f0
--- /dev/null
+++ b/cake/config/paths.php
@@ -0,0 +1,210 @@
+
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/0080_00ff.php b/cake/config/unicode/casefolding/0080_00ff.php
new file mode 100755
index 00000000..361b9f4c
--- /dev/null
+++ b/cake/config/unicode/casefolding/0080_00ff.php
@@ -0,0 +1,79 @@
+ 181, 'status' => 'C', 'lower' => array(956));
+$config['0080_00ff'][] = array('upper' => 924, 'status' => 'C', 'lower' => array(181));
+$config['0080_00ff'][] = array('upper' => 192, 'status' => 'C', 'lower' => array(224)); /* LATIN CAPITAL LETTER A WITH GRAVE */
+$config['0080_00ff'][] = array('upper' => 193, 'status' => 'C', 'lower' => array(225)); /* LATIN CAPITAL LETTER A WITH ACUTE */
+$config['0080_00ff'][] = array('upper' => 194, 'status' => 'C', 'lower' => array(226)); /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
+$config['0080_00ff'][] = array('upper' => 195, 'status' => 'C', 'lower' => array(227)); /* LATIN CAPITAL LETTER A WITH TILDE */
+$config['0080_00ff'][] = array('upper' => 196, 'status' => 'C', 'lower' => array(228)); /* LATIN CAPITAL LETTER A WITH DIAERESIS */
+$config['0080_00ff'][] = array('upper' => 197, 'status' => 'C', 'lower' => array(229)); /* LATIN CAPITAL LETTER A WITH RING ABOVE */
+$config['0080_00ff'][] = array('upper' => 198, 'status' => 'C', 'lower' => array(230)); /* LATIN CAPITAL LETTER AE */
+$config['0080_00ff'][] = array('upper' => 199, 'status' => 'C', 'lower' => array(231)); /* LATIN CAPITAL LETTER C WITH CEDILLA */
+$config['0080_00ff'][] = array('upper' => 200, 'status' => 'C', 'lower' => array(232)); /* LATIN CAPITAL LETTER E WITH GRAVE */
+$config['0080_00ff'][] = array('upper' => 201, 'status' => 'C', 'lower' => array(233)); /* LATIN CAPITAL LETTER E WITH ACUTE */
+$config['0080_00ff'][] = array('upper' => 202, 'status' => 'C', 'lower' => array(234)); /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
+$config['0080_00ff'][] = array('upper' => 203, 'status' => 'C', 'lower' => array(235)); /* LATIN CAPITAL LETTER E WITH DIAERESIS */
+$config['0080_00ff'][] = array('upper' => 204, 'status' => 'C', 'lower' => array(236)); /* LATIN CAPITAL LETTER I WITH GRAVE */
+$config['0080_00ff'][] = array('upper' => 205, 'status' => 'C', 'lower' => array(237)); /* LATIN CAPITAL LETTER I WITH ACUTE */
+$config['0080_00ff'][] = array('upper' => 206, 'status' => 'C', 'lower' => array(238)); /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
+$config['0080_00ff'][] = array('upper' => 207, 'status' => 'C', 'lower' => array(239)); /* LATIN CAPITAL LETTER I WITH DIAERESIS */
+$config['0080_00ff'][] = array('upper' => 208, 'status' => 'C', 'lower' => array(240)); /* LATIN CAPITAL LETTER ETH */
+$config['0080_00ff'][] = array('upper' => 209, 'status' => 'C', 'lower' => array(241)); /* LATIN CAPITAL LETTER N WITH TILDE */
+$config['0080_00ff'][] = array('upper' => 210, 'status' => 'C', 'lower' => array(242)); /* LATIN CAPITAL LETTER O WITH GRAVE */
+$config['0080_00ff'][] = array('upper' => 211, 'status' => 'C', 'lower' => array(243)); /* LATIN CAPITAL LETTER O WITH ACUTE */
+$config['0080_00ff'][] = array('upper' => 212, 'status' => 'C', 'lower' => array(244)); /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
+$config['0080_00ff'][] = array('upper' => 213, 'status' => 'C', 'lower' => array(245)); /* LATIN CAPITAL LETTER O WITH TILDE */
+$config['0080_00ff'][] = array('upper' => 214, 'status' => 'C', 'lower' => array(246)); /* LATIN CAPITAL LETTER O WITH DIAERESIS */
+$config['0080_00ff'][] = array('upper' => 216, 'status' => 'C', 'lower' => array(248)); /* LATIN CAPITAL LETTER O WITH STROKE */
+$config['0080_00ff'][] = array('upper' => 217, 'status' => 'C', 'lower' => array(249)); /* LATIN CAPITAL LETTER U WITH GRAVE */
+$config['0080_00ff'][] = array('upper' => 218, 'status' => 'C', 'lower' => array(250)); /* LATIN CAPITAL LETTER U WITH ACUTE */
+$config['0080_00ff'][] = array('upper' => 219, 'status' => 'C', 'lower' => array(251)); /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
+$config['0080_00ff'][] = array('upper' => 220, 'status' => 'C', 'lower' => array(252)); /* LATIN CAPITAL LETTER U WITH DIAERESIS */
+$config['0080_00ff'][] = array('upper' => 221, 'status' => 'C', 'lower' => array(253)); /* LATIN CAPITAL LETTER Y WITH ACUTE */
+$config['0080_00ff'][] = array('upper' => 222, 'status' => 'C', 'lower' => array(254)); /* LATIN CAPITAL LETTER THORN */
+$config['0080_00ff'][] = array('upper' => 223, 'status' => 'F', 'lower' => array(115, 115)); /* LATIN SMALL LETTER SHARP S */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/0100_017f.php b/cake/config/unicode/casefolding/0100_017f.php
new file mode 100755
index 00000000..4f27b2b0
--- /dev/null
+++ b/cake/config/unicode/casefolding/0100_017f.php
@@ -0,0 +1,112 @@
+ 256, 'status' => 'C', 'lower' => array(257)); /* LATIN CAPITAL LETTER A WITH MACRON */
+$config['0100_017f'][] = array('upper' => 258, 'status' => 'C', 'lower' => array(259)); /* LATIN CAPITAL LETTER A WITH BREVE */
+$config['0100_017f'][] = array('upper' => 260, 'status' => 'C', 'lower' => array(261)); /* LATIN CAPITAL LETTER A WITH OGONEK */
+$config['0100_017f'][] = array('upper' => 262, 'status' => 'C', 'lower' => array(263)); /* LATIN CAPITAL LETTER C WITH ACUTE */
+$config['0100_017f'][] = array('upper' => 264, 'status' => 'C', 'lower' => array(265)); /* LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
+$config['0100_017f'][] = array('upper' => 266, 'status' => 'C', 'lower' => array(267)); /* LATIN CAPITAL LETTER C WITH DOT ABOVE */
+$config['0100_017f'][] = array('upper' => 268, 'status' => 'C', 'lower' => array(269)); /* LATIN CAPITAL LETTER C WITH CARON */
+$config['0100_017f'][] = array('upper' => 270, 'status' => 'C', 'lower' => array(271)); /* LATIN CAPITAL LETTER D WITH CARON */
+$config['0100_017f'][] = array('upper' => 272, 'status' => 'C', 'lower' => array(273)); /* LATIN CAPITAL LETTER D WITH STROKE */
+$config['0100_017f'][] = array('upper' => 274, 'status' => 'C', 'lower' => array(275)); /* LATIN CAPITAL LETTER E WITH MACRON */
+$config['0100_017f'][] = array('upper' => 276, 'status' => 'C', 'lower' => array(277)); /* LATIN CAPITAL LETTER E WITH BREVE */
+$config['0100_017f'][] = array('upper' => 278, 'status' => 'C', 'lower' => array(279)); /* LATIN CAPITAL LETTER E WITH DOT ABOVE */
+$config['0100_017f'][] = array('upper' => 280, 'status' => 'C', 'lower' => array(281)); /* LATIN CAPITAL LETTER E WITH OGONEK */
+$config['0100_017f'][] = array('upper' => 282, 'status' => 'C', 'lower' => array(283)); /* LATIN CAPITAL LETTER E WITH CARON */
+$config['0100_017f'][] = array('upper' => 284, 'status' => 'C', 'lower' => array(285)); /* LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
+$config['0100_017f'][] = array('upper' => 286, 'status' => 'C', 'lower' => array(287)); /* LATIN CAPITAL LETTER G WITH BREVE */
+$config['0100_017f'][] = array('upper' => 288, 'status' => 'C', 'lower' => array(289)); /* LATIN CAPITAL LETTER G WITH DOT ABOVE */
+$config['0100_017f'][] = array('upper' => 290, 'status' => 'C', 'lower' => array(291)); /* LATIN CAPITAL LETTER G WITH CEDILLA */
+$config['0100_017f'][] = array('upper' => 292, 'status' => 'C', 'lower' => array(293)); /* LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
+$config['0100_017f'][] = array('upper' => 294, 'status' => 'C', 'lower' => array(295)); /* LATIN CAPITAL LETTER H WITH STROKE */
+$config['0100_017f'][] = array('upper' => 296, 'status' => 'C', 'lower' => array(297)); /* LATIN CAPITAL LETTER I WITH TILDE */
+$config['0100_017f'][] = array('upper' => 298, 'status' => 'C', 'lower' => array(299)); /* LATIN CAPITAL LETTER I WITH MACRON */
+$config['0100_017f'][] = array('upper' => 300, 'status' => 'C', 'lower' => array(301)); /* LATIN CAPITAL LETTER I WITH BREVE */
+$config['0100_017f'][] = array('upper' => 302, 'status' => 'C', 'lower' => array(303)); /* LATIN CAPITAL LETTER I WITH OGONEK */
+$config['0100_017f'][] = array('upper' => 304, 'status' => 'F', 'lower' => array(105, 775)); /* LATIN CAPITAL LETTER I WITH DOT ABOVE */
+$config['0100_017f'][] = array('upper' => 304, 'status' => 'T', 'lower' => array(105)); /* LATIN CAPITAL LETTER I WITH DOT ABOVE */
+$config['0100_017f'][] = array('upper' => 306, 'status' => 'C', 'lower' => array(307)); /* LATIN CAPITAL LIGATURE IJ */
+$config['0100_017f'][] = array('upper' => 308, 'status' => 'C', 'lower' => array(309)); /* LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
+$config['0100_017f'][] = array('upper' => 310, 'status' => 'C', 'lower' => array(311)); /* LATIN CAPITAL LETTER K WITH CEDILLA */
+$config['0100_017f'][] = array('upper' => 313, 'status' => 'C', 'lower' => array(314)); /* LATIN CAPITAL LETTER L WITH ACUTE */
+$config['0100_017f'][] = array('upper' => 315, 'status' => 'C', 'lower' => array(316)); /* LATIN CAPITAL LETTER L WITH CEDILLA */
+$config['0100_017f'][] = array('upper' => 317, 'status' => 'C', 'lower' => array(318)); /* LATIN CAPITAL LETTER L WITH CARON */
+$config['0100_017f'][] = array('upper' => 319, 'status' => 'C', 'lower' => array(320)); /* LATIN CAPITAL LETTER L WITH MIDDLE DOT */
+$config['0100_017f'][] = array('upper' => 321, 'status' => 'C', 'lower' => array(322)); /* LATIN CAPITAL LETTER L WITH STROKE */
+$config['0100_017f'][] = array('upper' => 323, 'status' => 'C', 'lower' => array(324)); /* LATIN CAPITAL LETTER N WITH ACUTE */
+$config['0100_017f'][] = array('upper' => 325, 'status' => 'C', 'lower' => array(326)); /* LATIN CAPITAL LETTER N WITH CEDILLA */
+$config['0100_017f'][] = array('upper' => 327, 'status' => 'C', 'lower' => array(328)); /* LATIN CAPITAL LETTER N WITH CARON */
+$config['0100_017f'][] = array('upper' => 329, 'status' => 'F', 'lower' => array(700, 110)); /* LATIN SMALL LETTER N PRECEDED BY APOSTROPHE */
+$config['0100_017f'][] = array('upper' => 330, 'status' => 'C', 'lower' => array(331)); /* LATIN CAPITAL LETTER ENG */
+$config['0100_017f'][] = array('upper' => 332, 'status' => 'C', 'lower' => array(333)); /* LATIN CAPITAL LETTER O WITH MACRON */
+$config['0100_017f'][] = array('upper' => 334, 'status' => 'C', 'lower' => array(335)); /* LATIN CAPITAL LETTER O WITH BREVE */
+$config['0100_017f'][] = array('upper' => 336, 'status' => 'C', 'lower' => array(337)); /* LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
+$config['0100_017f'][] = array('upper' => 338, 'status' => 'C', 'lower' => array(339)); /* LATIN CAPITAL LIGATURE OE */
+$config['0100_017f'][] = array('upper' => 340, 'status' => 'C', 'lower' => array(341)); /* LATIN CAPITAL LETTER R WITH ACUTE */
+$config['0100_017f'][] = array('upper' => 342, 'status' => 'C', 'lower' => array(343)); /* LATIN CAPITAL LETTER R WITH CEDILLA */
+$config['0100_017f'][] = array('upper' => 344, 'status' => 'C', 'lower' => array(345)); /* LATIN CAPITAL LETTER R WITH CARON */
+$config['0100_017f'][] = array('upper' => 346, 'status' => 'C', 'lower' => array(347)); /* LATIN CAPITAL LETTER S WITH ACUTE */
+$config['0100_017f'][] = array('upper' => 348, 'status' => 'C', 'lower' => array(349)); /* LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
+$config['0100_017f'][] = array('upper' => 350, 'status' => 'C', 'lower' => array(351)); /* LATIN CAPITAL LETTER S WITH CEDILLA */
+$config['0100_017f'][] = array('upper' => 352, 'status' => 'C', 'lower' => array(353)); /* LATIN CAPITAL LETTER S WITH CARON */
+$config['0100_017f'][] = array('upper' => 354, 'status' => 'C', 'lower' => array(355)); /* LATIN CAPITAL LETTER T WITH CEDILLA */
+$config['0100_017f'][] = array('upper' => 356, 'status' => 'C', 'lower' => array(357)); /* LATIN CAPITAL LETTER T WITH CARON */
+$config['0100_017f'][] = array('upper' => 358, 'status' => 'C', 'lower' => array(359)); /* LATIN CAPITAL LETTER T WITH STROKE */
+$config['0100_017f'][] = array('upper' => 360, 'status' => 'C', 'lower' => array(361)); /* LATIN CAPITAL LETTER U WITH TILDE */
+$config['0100_017f'][] = array('upper' => 362, 'status' => 'C', 'lower' => array(363)); /* LATIN CAPITAL LETTER U WITH MACRON */
+$config['0100_017f'][] = array('upper' => 364, 'status' => 'C', 'lower' => array(365)); /* LATIN CAPITAL LETTER U WITH BREVE */
+$config['0100_017f'][] = array('upper' => 366, 'status' => 'C', 'lower' => array(367)); /* LATIN CAPITAL LETTER U WITH RING ABOVE */
+$config['0100_017f'][] = array('upper' => 368, 'status' => 'C', 'lower' => array(369)); /* LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
+$config['0100_017f'][] = array('upper' => 370, 'status' => 'C', 'lower' => array(371)); /* LATIN CAPITAL LETTER U WITH OGONEK */
+$config['0100_017f'][] = array('upper' => 372, 'status' => 'C', 'lower' => array(373)); /* LATIN CAPITAL LETTER W WITH CIRCUMFLEX */
+$config['0100_017f'][] = array('upper' => 374, 'status' => 'C', 'lower' => array(375)); /* LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */
+$config['0100_017f'][] = array('upper' => 376, 'status' => 'C', 'lower' => array(255)); /* LATIN CAPITAL LETTER Y WITH DIAERESIS */
+$config['0100_017f'][] = array('upper' => 377, 'status' => 'C', 'lower' => array(378)); /* LATIN CAPITAL LETTER Z WITH ACUTE */
+$config['0100_017f'][] = array('upper' => 379, 'status' => 'C', 'lower' => array(380)); /* LATIN CAPITAL LETTER Z WITH DOT ABOVE */
+$config['0100_017f'][] = array('upper' => 381, 'status' => 'C', 'lower' => array(382)); /* LATIN CAPITAL LETTER Z WITH CARON */
+$config['0100_017f'][] = array('upper' => 383, 'status' => 'C', 'lower' => array(115)); /* LATIN SMALL LETTER LONG S */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/0180_024F.php b/cake/config/unicode/casefolding/0180_024F.php
new file mode 100755
index 00000000..c3d60326
--- /dev/null
+++ b/cake/config/unicode/casefolding/0180_024F.php
@@ -0,0 +1,154 @@
+ 385, 'status' => 'C', 'lower' => array(595)); /* LATIN CAPITAL LETTER B WITH HOOK */
+$config['0180_024F'][] = array('upper' => 386, 'status' => 'C', 'lower' => array(387)); /* LATIN CAPITAL LETTER B WITH TOPBAR */
+$config['0180_024F'][] = array('upper' => 388, 'status' => 'C', 'lower' => array(389)); /* LATIN CAPITAL LETTER TONE SIX */
+$config['0180_024F'][] = array('upper' => 390, 'status' => 'C', 'lower' => array(596)); /* LATIN CAPITAL LETTER OPEN O */
+$config['0180_024F'][] = array('upper' => 391, 'status' => 'C', 'lower' => array(392)); /* LATIN CAPITAL LETTER C WITH HOOK */
+$config['0180_024F'][] = array('upper' => 393, 'status' => 'C', 'lower' => array(598)); /* LATIN CAPITAL LETTER AFRICAN D */
+$config['0180_024F'][] = array('upper' => 394, 'status' => 'C', 'lower' => array(599)); /* LATIN CAPITAL LETTER D WITH HOOK */
+$config['0180_024F'][] = array('upper' => 395, 'status' => 'C', 'lower' => array(396)); /* LATIN CAPITAL LETTER D WITH TOPBAR */
+$config['0180_024F'][] = array('upper' => 398, 'status' => 'C', 'lower' => array(477)); /* LATIN CAPITAL LETTER REVERSED E */
+$config['0180_024F'][] = array('upper' => 399, 'status' => 'C', 'lower' => array(601)); /* LATIN CAPITAL LETTER SCHWA */
+$config['0180_024F'][] = array('upper' => 400, 'status' => 'C', 'lower' => array(603)); /* LATIN CAPITAL LETTER OPEN E */
+$config['0180_024F'][] = array('upper' => 401, 'status' => 'C', 'lower' => array(402)); /* LATIN CAPITAL LETTER F WITH HOOK */
+$config['0180_024F'][] = array('upper' => 403, 'status' => 'C', 'lower' => array(608)); /* LATIN CAPITAL LETTER G WITH HOOK */
+$config['0180_024F'][] = array('upper' => 404, 'status' => 'C', 'lower' => array(611)); /* LATIN CAPITAL LETTER GAMMA */
+$config['0180_024F'][] = array('upper' => 406, 'status' => 'C', 'lower' => array(617)); /* LATIN CAPITAL LETTER IOTA */
+$config['0180_024F'][] = array('upper' => 407, 'status' => 'C', 'lower' => array(616)); /* LATIN CAPITAL LETTER I WITH STROKE */
+$config['0180_024F'][] = array('upper' => 408, 'status' => 'C', 'lower' => array(409)); /* LATIN CAPITAL LETTER K WITH HOOK */
+$config['0180_024F'][] = array('upper' => 412, 'status' => 'C', 'lower' => array(623)); /* LATIN CAPITAL LETTER TURNED M */
+$config['0180_024F'][] = array('upper' => 413, 'status' => 'C', 'lower' => array(626)); /* LATIN CAPITAL LETTER N WITH LEFT HOOK */
+$config['0180_024F'][] = array('upper' => 415, 'status' => 'C', 'lower' => array(629)); /* LATIN CAPITAL LETTER O WITH MIDDLE TILDE */
+$config['0180_024F'][] = array('upper' => 416, 'status' => 'C', 'lower' => array(417)); /* LATIN CAPITAL LETTER O WITH HORN */
+$config['0180_024F'][] = array('upper' => 418, 'status' => 'C', 'lower' => array(419)); /* LATIN CAPITAL LETTER OI */
+$config['0180_024F'][] = array('upper' => 420, 'status' => 'C', 'lower' => array(421)); /* LATIN CAPITAL LETTER P WITH HOOK */
+$config['0180_024F'][] = array('upper' => 422, 'status' => 'C', 'lower' => array(640)); /* LATIN LETTER YR */
+$config['0180_024F'][] = array('upper' => 423, 'status' => 'C', 'lower' => array(424)); /* LATIN CAPITAL LETTER TONE TWO */
+$config['0180_024F'][] = array('upper' => 425, 'status' => 'C', 'lower' => array(643)); /* LATIN CAPITAL LETTER ESH */
+$config['0180_024F'][] = array('upper' => 428, 'status' => 'C', 'lower' => array(429)); /* LATIN CAPITAL LETTER T WITH HOOK */
+$config['0180_024F'][] = array('upper' => 430, 'status' => 'C', 'lower' => array(648)); /* LATIN CAPITAL LETTER T WITH RETROFLEX HOOK */
+$config['0180_024F'][] = array('upper' => 431, 'status' => 'C', 'lower' => array(432)); /* LATIN CAPITAL LETTER U WITH HORN */
+$config['0180_024F'][] = array('upper' => 433, 'status' => 'C', 'lower' => array(650)); /* LATIN CAPITAL LETTER UPSILON */
+$config['0180_024F'][] = array('upper' => 434, 'status' => 'C', 'lower' => array(651)); /* LATIN CAPITAL LETTER V WITH HOOK */
+$config['0180_024F'][] = array('upper' => 435, 'status' => 'C', 'lower' => array(436)); /* LATIN CAPITAL LETTER Y WITH HOOK */
+$config['0180_024F'][] = array('upper' => 437, 'status' => 'C', 'lower' => array(438)); /* LATIN CAPITAL LETTER Z WITH STROKE */
+$config['0180_024F'][] = array('upper' => 439, 'status' => 'C', 'lower' => array(658)); /* LATIN CAPITAL LETTER EZH */
+$config['0180_024F'][] = array('upper' => 440, 'status' => 'C', 'lower' => array(441)); /* LATIN CAPITAL LETTER EZH REVERSED */
+$config['0180_024F'][] = array('upper' => 444, 'status' => 'C', 'lower' => array(445)); /* LATIN CAPITAL LETTER TONE FIVE */
+$config['0180_024F'][] = array('upper' => 452, 'status' => 'C', 'lower' => array(454)); /* LATIN CAPITAL LETTER DZ WITH CARON */
+$config['0180_024F'][] = array('upper' => 453, 'status' => 'C', 'lower' => array(454)); /* LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON */
+$config['0180_024F'][] = array('upper' => 455, 'status' => 'C', 'lower' => array(457)); /* LATIN CAPITAL LETTER LJ */
+$config['0180_024F'][] = array('upper' => 456, 'status' => 'C', 'lower' => array(457)); /* LATIN CAPITAL LETTER L WITH SMALL LETTER J */
+$config['0180_024F'][] = array('upper' => 458, 'status' => 'C', 'lower' => array(460)); /* LATIN CAPITAL LETTER NJ */
+$config['0180_024F'][] = array('upper' => 459, 'status' => 'C', 'lower' => array(460)); /* LATIN CAPITAL LETTER N WITH SMALL LETTER J */
+$config['0180_024F'][] = array('upper' => 461, 'status' => 'C', 'lower' => array(462)); /* LATIN CAPITAL LETTER A WITH CARON */
+$config['0180_024F'][] = array('upper' => 463, 'status' => 'C', 'lower' => array(464)); /* LATIN CAPITAL LETTER I WITH CARON */
+$config['0180_024F'][] = array('upper' => 465, 'status' => 'C', 'lower' => array(466)); /* LATIN CAPITAL LETTER O WITH CARON */
+$config['0180_024F'][] = array('upper' => 467, 'status' => 'C', 'lower' => array(468)); /* LATIN CAPITAL LETTER U WITH CARON */
+$config['0180_024F'][] = array('upper' => 469, 'status' => 'C', 'lower' => array(470)); /* LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON */
+$config['0180_024F'][] = array('upper' => 471, 'status' => 'C', 'lower' => array(472)); /* LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE */
+$config['0180_024F'][] = array('upper' => 473, 'status' => 'C', 'lower' => array(474)); /* LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON */
+$config['0180_024F'][] = array('upper' => 475, 'status' => 'C', 'lower' => array(476)); /* LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE */
+$config['0180_024F'][] = array('upper' => 478, 'status' => 'C', 'lower' => array(479)); /* LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON */
+$config['0180_024F'][] = array('upper' => 480, 'status' => 'C', 'lower' => array(481)); /* LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON */
+$config['0180_024F'][] = array('upper' => 482, 'status' => 'C', 'lower' => array(483)); /* LATIN CAPITAL LETTER AE WITH MACRON */
+$config['0180_024F'][] = array('upper' => 484, 'status' => 'C', 'lower' => array(485)); /* LATIN CAPITAL LETTER G WITH STROKE */
+$config['0180_024F'][] = array('upper' => 486, 'status' => 'C', 'lower' => array(487)); /* LATIN CAPITAL LETTER G WITH CARON */
+$config['0180_024F'][] = array('upper' => 488, 'status' => 'C', 'lower' => array(489)); /* LATIN CAPITAL LETTER K WITH CARON */
+$config['0180_024F'][] = array('upper' => 490, 'status' => 'C', 'lower' => array(491)); /* LATIN CAPITAL LETTER O WITH OGONEK */
+$config['0180_024F'][] = array('upper' => 492, 'status' => 'C', 'lower' => array(493)); /* LATIN CAPITAL LETTER O WITH OGONEK AND MACRON */
+$config['0180_024F'][] = array('upper' => 494, 'status' => 'C', 'lower' => array(495)); /* LATIN CAPITAL LETTER EZH WITH CARON */
+$config['0180_024F'][] = array('upper' => 496, 'status' => 'F', 'lower' => array(106, 780)); /* LATIN SMALL LETTER J WITH CARON */
+$config['0180_024F'][] = array('upper' => 497, 'status' => 'C', 'lower' => array(499)); /* LATIN CAPITAL LETTER DZ */
+$config['0180_024F'][] = array('upper' => 498, 'status' => 'C', 'lower' => array(499)); /* LATIN CAPITAL LETTER D WITH SMALL LETTER Z */
+$config['0180_024F'][] = array('upper' => 500, 'status' => 'C', 'lower' => array(501)); /* LATIN CAPITAL LETTER G WITH ACUTE */
+$config['0180_024F'][] = array('upper' => 502, 'status' => 'C', 'lower' => array(405)); /* LATIN CAPITAL LETTER HWAIR */
+$config['0180_024F'][] = array('upper' => 503, 'status' => 'C', 'lower' => array(447)); /* LATIN CAPITAL LETTER WYNN */
+$config['0180_024F'][] = array('upper' => 504, 'status' => 'C', 'lower' => array(505)); /* LATIN CAPITAL LETTER N WITH GRAVE */
+$config['0180_024F'][] = array('upper' => 506, 'status' => 'C', 'lower' => array(507)); /* LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE */
+$config['0180_024F'][] = array('upper' => 508, 'status' => 'C', 'lower' => array(509)); /* LATIN CAPITAL LETTER AE WITH ACUTE */
+$config['0180_024F'][] = array('upper' => 510, 'status' => 'C', 'lower' => array(511)); /* LATIN CAPITAL LETTER O WITH STROKE AND ACUTE */
+$config['0180_024F'][] = array('upper' => 512, 'status' => 'C', 'lower' => array(513)); /* LATIN CAPITAL LETTER A WITH DOUBLE GRAVE */
+$config['0180_024F'][] = array('upper' => 514, 'status' => 'C', 'lower' => array(515)); /* LATIN CAPITAL LETTER A WITH INVERTED BREVE */
+$config['0180_024F'][] = array('upper' => 516, 'status' => 'C', 'lower' => array(517)); /* LATIN CAPITAL LETTER E WITH DOUBLE GRAVE */
+$config['0180_024F'][] = array('upper' => 518, 'status' => 'C', 'lower' => array(519)); /* LATIN CAPITAL LETTER E WITH INVERTED BREVE */
+$config['0180_024F'][] = array('upper' => 520, 'status' => 'C', 'lower' => array(521)); /* LATIN CAPITAL LETTER I WITH DOUBLE GRAVE */
+$config['0180_024F'][] = array('upper' => 522, 'status' => 'C', 'lower' => array(523)); /* LATIN CAPITAL LETTER I WITH INVERTED BREVE */
+$config['0180_024F'][] = array('upper' => 524, 'status' => 'C', 'lower' => array(525)); /* LATIN CAPITAL LETTER O WITH DOUBLE GRAVE */
+$config['0180_024F'][] = array('upper' => 526, 'status' => 'C', 'lower' => array(527)); /* LATIN CAPITAL LETTER O WITH INVERTED BREVE */
+$config['0180_024F'][] = array('upper' => 528, 'status' => 'C', 'lower' => array(529)); /* LATIN CAPITAL LETTER R WITH DOUBLE GRAVE */
+$config['0180_024F'][] = array('upper' => 530, 'status' => 'C', 'lower' => array(531)); /* LATIN CAPITAL LETTER R WITH INVERTED BREVE */
+$config['0180_024F'][] = array('upper' => 532, 'status' => 'C', 'lower' => array(533)); /* LATIN CAPITAL LETTER U WITH DOUBLE GRAVE */
+$config['0180_024F'][] = array('upper' => 534, 'status' => 'C', 'lower' => array(535)); /* LATIN CAPITAL LETTER U WITH INVERTED BREVE */
+$config['0180_024F'][] = array('upper' => 536, 'status' => 'C', 'lower' => array(537)); /* LATIN CAPITAL LETTER S WITH COMMA BELOW */
+$config['0180_024F'][] = array('upper' => 538, 'status' => 'C', 'lower' => array(539)); /* LATIN CAPITAL LETTER T WITH COMMA BELOW */
+$config['0180_024F'][] = array('upper' => 540, 'status' => 'C', 'lower' => array(541)); /* LATIN CAPITAL LETTER YOGH */
+$config['0180_024F'][] = array('upper' => 542, 'status' => 'C', 'lower' => array(543)); /* LATIN CAPITAL LETTER H WITH CARON */
+$config['0180_024F'][] = array('upper' => 544, 'status' => 'C', 'lower' => array(414)); /* LATIN CAPITAL LETTER N WITH LONG RIGHT LEG */
+$config['0180_024F'][] = array('upper' => 546, 'status' => 'C', 'lower' => array(547)); /* LATIN CAPITAL LETTER OU */
+$config['0180_024F'][] = array('upper' => 548, 'status' => 'C', 'lower' => array(549)); /* LATIN CAPITAL LETTER Z WITH HOOK */
+$config['0180_024F'][] = array('upper' => 550, 'status' => 'C', 'lower' => array(551)); /* LATIN CAPITAL LETTER A WITH DOT ABOVE */
+$config['0180_024F'][] = array('upper' => 552, 'status' => 'C', 'lower' => array(553)); /* LATIN CAPITAL LETTER E WITH CEDILLA */
+$config['0180_024F'][] = array('upper' => 554, 'status' => 'C', 'lower' => array(555)); /* LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON */
+$config['0180_024F'][] = array('upper' => 556, 'status' => 'C', 'lower' => array(557)); /* LATIN CAPITAL LETTER O WITH TILDE AND MACRON */
+$config['0180_024F'][] = array('upper' => 558, 'status' => 'C', 'lower' => array(559)); /* LATIN CAPITAL LETTER O WITH DOT ABOVE */
+$config['0180_024F'][] = array('upper' => 560, 'status' => 'C', 'lower' => array(561)); /* LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON */
+$config['0180_024F'][] = array('upper' => 562, 'status' => 'C', 'lower' => array(563)); /* LATIN CAPITAL LETTER Y WITH MACRON */
+$config['0180_024F'][] = array('upper' => 570, 'status' => 'C', 'lower' => array(11365)); /* LATIN CAPITAL LETTER A WITH STROKE */
+$config['0180_024F'][] = array('upper' => 571, 'status' => 'C', 'lower' => array(572)); /* LATIN CAPITAL LETTER C WITH STROKE */
+$config['0180_024F'][] = array('upper' => 573, 'status' => 'C', 'lower' => array(410)); /* LATIN CAPITAL LETTER L WITH BAR */
+$config['0180_024F'][] = array('upper' => 574, 'status' => 'C', 'lower' => array(11366)); /* LATIN CAPITAL LETTER T WITH DIAGONAL STROKE */
+$config['0180_024F'][] = array('upper' => 577, 'status' => 'C', 'lower' => array(578)); /* LATIN CAPITAL LETTER GLOTTAL STOP */
+$config['0180_024F'][] = array('upper' => 579, 'status' => 'C', 'lower' => array(384)); /* LATIN CAPITAL LETTER B WITH STROKE */
+$config['0180_024F'][] = array('upper' => 580, 'status' => 'C', 'lower' => array(649)); /* LATIN CAPITAL LETTER U BAR */
+$config['0180_024F'][] = array('upper' => 581, 'status' => 'C', 'lower' => array(652)); /* LATIN CAPITAL LETTER TURNED V */
+$config['0180_024F'][] = array('upper' => 582, 'status' => 'C', 'lower' => array(583)); /* LATIN CAPITAL LETTER E WITH STROKE */
+$config['0180_024F'][] = array('upper' => 584, 'status' => 'C', 'lower' => array(585)); /* LATIN CAPITAL LETTER J WITH STROKE */
+$config['0180_024F'][] = array('upper' => 586, 'status' => 'C', 'lower' => array(587)); /* LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL */
+$config['0180_024F'][] = array('upper' => 588, 'status' => 'C', 'lower' => array(589)); /* LATIN CAPITAL LETTER R WITH STROKE */
+$config['0180_024F'][] = array('upper' => 590, 'status' => 'C', 'lower' => array(591)); /* LATIN CAPITAL LETTER Y WITH STROKE */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/0250_02af.php b/cake/config/unicode/casefolding/0250_02af.php
new file mode 100755
index 00000000..31d9b912
--- /dev/null
+++ b/cake/config/unicode/casefolding/0250_02af.php
@@ -0,0 +1,47 @@
+ 422, 'status' => 'C', 'lower' => array(640));
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/0370_03ff.php b/cake/config/unicode/casefolding/0370_03ff.php
new file mode 100755
index 00000000..776667a8
--- /dev/null
+++ b/cake/config/unicode/casefolding/0370_03ff.php
@@ -0,0 +1,108 @@
+ 902, 'status' => 'C', 'lower' => array(940)); /* GREEK CAPITAL LETTER ALPHA WITH TONOS */
+$config['0370_03ff'][] = array('upper' => 904, 'status' => 'C', 'lower' => array(941)); /* GREEK CAPITAL LETTER EPSILON WITH TONOS */
+$config['0370_03ff'][] = array('upper' => 905, 'status' => 'C', 'lower' => array(942)); /* GREEK CAPITAL LETTER ETA WITH TONOS */
+$config['0370_03ff'][] = array('upper' => 906, 'status' => 'C', 'lower' => array(943)); /* GREEK CAPITAL LETTER IOTA WITH TONOS */
+$config['0370_03ff'][] = array('upper' => 908, 'status' => 'C', 'lower' => array(972)); /* GREEK CAPITAL LETTER OMICRON WITH TONOS */
+$config['0370_03ff'][] = array('upper' => 910, 'status' => 'C', 'lower' => array(973)); /* GREEK CAPITAL LETTER UPSILON WITH TONOS */
+$config['0370_03ff'][] = array('upper' => 911, 'status' => 'C', 'lower' => array(974)); /* GREEK CAPITAL LETTER OMEGA WITH TONOS */
+//$config['0370_03ff'][] = array('upper' => 912, 'status' => 'F', 'lower' => array(953, 776, 769)); /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
+$config['0370_03ff'][] = array('upper' => 913, 'status' => 'C', 'lower' => array(945)); /* GREEK CAPITAL LETTER ALPHA */
+$config['0370_03ff'][] = array('upper' => 914, 'status' => 'C', 'lower' => array(946)); /* GREEK CAPITAL LETTER BETA */
+$config['0370_03ff'][] = array('upper' => 915, 'status' => 'C', 'lower' => array(947)); /* GREEK CAPITAL LETTER GAMMA */
+$config['0370_03ff'][] = array('upper' => 916, 'status' => 'C', 'lower' => array(948)); /* GREEK CAPITAL LETTER DELTA */
+$config['0370_03ff'][] = array('upper' => 917, 'status' => 'C', 'lower' => array(949)); /* GREEK CAPITAL LETTER EPSILON */
+$config['0370_03ff'][] = array('upper' => 918, 'status' => 'C', 'lower' => array(950)); /* GREEK CAPITAL LETTER ZETA */
+$config['0370_03ff'][] = array('upper' => 919, 'status' => 'C', 'lower' => array(951)); /* GREEK CAPITAL LETTER ETA */
+$config['0370_03ff'][] = array('upper' => 920, 'status' => 'C', 'lower' => array(952)); /* GREEK CAPITAL LETTER THETA */
+$config['0370_03ff'][] = array('upper' => 921, 'status' => 'C', 'lower' => array(953)); /* GREEK CAPITAL LETTER IOTA */
+$config['0370_03ff'][] = array('upper' => 922, 'status' => 'C', 'lower' => array(954)); /* GREEK CAPITAL LETTER KAPPA */
+$config['0370_03ff'][] = array('upper' => 923, 'status' => 'C', 'lower' => array(955)); /* GREEK CAPITAL LETTER LAMDA */
+$config['0370_03ff'][] = array('upper' => 924, 'status' => 'C', 'lower' => array(956)); /* GREEK CAPITAL LETTER MU */
+$config['0370_03ff'][] = array('upper' => 925, 'status' => 'C', 'lower' => array(957)); /* GREEK CAPITAL LETTER NU */
+$config['0370_03ff'][] = array('upper' => 926, 'status' => 'C', 'lower' => array(958)); /* GREEK CAPITAL LETTER XI */
+$config['0370_03ff'][] = array('upper' => 927, 'status' => 'C', 'lower' => array(959)); /* GREEK CAPITAL LETTER OMICRON */
+$config['0370_03ff'][] = array('upper' => 928, 'status' => 'C', 'lower' => array(960)); /* GREEK CAPITAL LETTER PI */
+$config['0370_03ff'][] = array('upper' => 929, 'status' => 'C', 'lower' => array(961)); /* GREEK CAPITAL LETTER RHO */
+$config['0370_03ff'][] = array('upper' => 931, 'status' => 'C', 'lower' => array(963)); /* GREEK CAPITAL LETTER SIGMA */
+$config['0370_03ff'][] = array('upper' => 932, 'status' => 'C', 'lower' => array(964)); /* GREEK CAPITAL LETTER TAU */
+$config['0370_03ff'][] = array('upper' => 933, 'status' => 'C', 'lower' => array(965)); /* GREEK CAPITAL LETTER UPSILON */
+$config['0370_03ff'][] = array('upper' => 934, 'status' => 'C', 'lower' => array(966)); /* GREEK CAPITAL LETTER PHI */
+$config['0370_03ff'][] = array('upper' => 935, 'status' => 'C', 'lower' => array(967)); /* GREEK CAPITAL LETTER CHI */
+$config['0370_03ff'][] = array('upper' => 936, 'status' => 'C', 'lower' => array(968)); /* GREEK CAPITAL LETTER PSI */
+$config['0370_03ff'][] = array('upper' => 937, 'status' => 'C', 'lower' => array(969)); /* GREEK CAPITAL LETTER OMEGA */
+$config['0370_03ff'][] = array('upper' => 938, 'status' => 'C', 'lower' => array(970)); /* GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
+$config['0370_03ff'][] = array('upper' => 939, 'status' => 'C', 'lower' => array(971)); /* GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
+$config['0370_03ff'][] = array('upper' => 944, 'status' => 'F', 'lower' => array(965, 776, 769)); /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
+$config['0370_03ff'][] = array('upper' => 962, 'status' => 'C', 'lower' => array(963)); /* GREEK SMALL LETTER FINAL SIGMA */
+$config['0370_03ff'][] = array('upper' => 976, 'status' => 'C', 'lower' => array(946)); /* GREEK BETA SYMBOL */
+$config['0370_03ff'][] = array('upper' => 977, 'status' => 'C', 'lower' => array(952)); /* GREEK THETA SYMBOL */
+$config['0370_03ff'][] = array('upper' => 981, 'status' => 'C', 'lower' => array(966)); /* GREEK PHI SYMBOL */
+$config['0370_03ff'][] = array('upper' => 982, 'status' => 'C', 'lower' => array(960)); /* GREEK PI SYMBOL */
+$config['0370_03ff'][] = array('upper' => 984, 'status' => 'C', 'lower' => array(985)); /* GREEK LETTER ARCHAIC KOPPA */
+$config['0370_03ff'][] = array('upper' => 986, 'status' => 'C', 'lower' => array(987)); /* GREEK LETTER STIGMA */
+$config['0370_03ff'][] = array('upper' => 988, 'status' => 'C', 'lower' => array(989)); /* GREEK LETTER DIGAMMA */
+$config['0370_03ff'][] = array('upper' => 990, 'status' => 'C', 'lower' => array(991)); /* GREEK LETTER KOPPA */
+$config['0370_03ff'][] = array('upper' => 992, 'status' => 'C', 'lower' => array(993)); /* GREEK LETTER SAMPI */
+$config['0370_03ff'][] = array('upper' => 994, 'status' => 'C', 'lower' => array(995)); /* COPTIC CAPITAL LETTER SHEI */
+$config['0370_03ff'][] = array('upper' => 996, 'status' => 'C', 'lower' => array(997)); /* COPTIC CAPITAL LETTER FEI */
+$config['0370_03ff'][] = array('upper' => 998, 'status' => 'C', 'lower' => array(999)); /* COPTIC CAPITAL LETTER KHEI */
+$config['0370_03ff'][] = array('upper' => 1000, 'status' => 'C', 'lower' => array(1001)); /* COPTIC CAPITAL LETTER HORI */
+$config['0370_03ff'][] = array('upper' => 1002, 'status' => 'C', 'lower' => array(1003)); /* COPTIC CAPITAL LETTER GANGIA */
+$config['0370_03ff'][] = array('upper' => 1004, 'status' => 'C', 'lower' => array(1005)); /* COPTIC CAPITAL LETTER SHIMA */
+$config['0370_03ff'][] = array('upper' => 1006, 'status' => 'C', 'lower' => array(1007)); /* COPTIC CAPITAL LETTER DEI */
+$config['0370_03ff'][] = array('upper' => 1008, 'status' => 'C', 'lower' => array(954)); /* GREEK KAPPA SYMBOL */
+$config['0370_03ff'][] = array('upper' => 1009, 'status' => 'C', 'lower' => array(961)); /* GREEK RHO SYMBOL */
+$config['0370_03ff'][] = array('upper' => 1012, 'status' => 'C', 'lower' => array(952)); /* GREEK CAPITAL THETA SYMBOL */
+$config['0370_03ff'][] = array('upper' => 1013, 'status' => 'C', 'lower' => array(949)); /* GREEK LUNATE EPSILON SYMBOL */
+$config['0370_03ff'][] = array('upper' => 1015, 'status' => 'C', 'lower' => array(1016)); /* GREEK CAPITAL LETTER SHO */
+$config['0370_03ff'][] = array('upper' => 1017, 'status' => 'C', 'lower' => array(1010)); /* GREEK CAPITAL LUNATE SIGMA SYMBOL */
+$config['0370_03ff'][] = array('upper' => 1018, 'status' => 'C', 'lower' => array(1019)); /* GREEK CAPITAL LETTER SAN */
+$config['0370_03ff'][] = array('upper' => 1021, 'status' => 'C', 'lower' => array(891)); /* GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL */
+$config['0370_03ff'][] = array('upper' => 1022, 'status' => 'C', 'lower' => array(892)); /* GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL */
+$config['0370_03ff'][] = array('upper' => 1023, 'status' => 'C', 'lower' => array(893)); /* GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/0400_04ff.php b/cake/config/unicode/casefolding/0400_04ff.php
new file mode 100755
index 00000000..6e1f6e9a
--- /dev/null
+++ b/cake/config/unicode/casefolding/0400_04ff.php
@@ -0,0 +1,170 @@
+ 1024, 'status' => 'C', 'lower' => array(1104)); /* CYRILLIC CAPITAL LETTER IE WITH GRAVE */
+$config['0400_04ff'][] = array('upper' => 1025, 'status' => 'C', 'lower' => array(1105)); /* CYRILLIC CAPITAL LETTER IO */
+$config['0400_04ff'][] = array('upper' => 1026, 'status' => 'C', 'lower' => array(1106)); /* CYRILLIC CAPITAL LETTER DJE */
+$config['0400_04ff'][] = array('upper' => 1027, 'status' => 'C', 'lower' => array(1107)); /* CYRILLIC CAPITAL LETTER GJE */
+$config['0400_04ff'][] = array('upper' => 1028, 'status' => 'C', 'lower' => array(1108)); /* CYRILLIC CAPITAL LETTER UKRAINIAN IE */
+$config['0400_04ff'][] = array('upper' => 1029, 'status' => 'C', 'lower' => array(1109)); /* CYRILLIC CAPITAL LETTER DZE */
+$config['0400_04ff'][] = array('upper' => 1030, 'status' => 'C', 'lower' => array(1110)); /* CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
+$config['0400_04ff'][] = array('upper' => 1031, 'status' => 'C', 'lower' => array(1111)); /* CYRILLIC CAPITAL LETTER YI */
+$config['0400_04ff'][] = array('upper' => 1032, 'status' => 'C', 'lower' => array(1112)); /* CYRILLIC CAPITAL LETTER JE */
+$config['0400_04ff'][] = array('upper' => 1033, 'status' => 'C', 'lower' => array(1113)); /* CYRILLIC CAPITAL LETTER LJE */
+$config['0400_04ff'][] = array('upper' => 1034, 'status' => 'C', 'lower' => array(1114)); /* CYRILLIC CAPITAL LETTER NJE */
+$config['0400_04ff'][] = array('upper' => 1035, 'status' => 'C', 'lower' => array(1115)); /* CYRILLIC CAPITAL LETTER TSHE */
+$config['0400_04ff'][] = array('upper' => 1036, 'status' => 'C', 'lower' => array(1116)); /* CYRILLIC CAPITAL LETTER KJE */
+$config['0400_04ff'][] = array('upper' => 1037, 'status' => 'C', 'lower' => array(1117)); /* CYRILLIC CAPITAL LETTER I WITH GRAVE */
+$config['0400_04ff'][] = array('upper' => 1038, 'status' => 'C', 'lower' => array(1118)); /* CYRILLIC CAPITAL LETTER SHORT U */
+$config['0400_04ff'][] = array('upper' => 1039, 'status' => 'C', 'lower' => array(1119)); /* CYRILLIC CAPITAL LETTER DZHE */
+$config['0400_04ff'][] = array('upper' => 1040, 'status' => 'C', 'lower' => array(1072)); /* CYRILLIC CAPITAL LETTER A */
+$config['0400_04ff'][] = array('upper' => 1041, 'status' => 'C', 'lower' => array(1073)); /* CYRILLIC CAPITAL LETTER BE */
+$config['0400_04ff'][] = array('upper' => 1042, 'status' => 'C', 'lower' => array(1074)); /* CYRILLIC CAPITAL LETTER VE */
+$config['0400_04ff'][] = array('upper' => 1043, 'status' => 'C', 'lower' => array(1075)); /* CYRILLIC CAPITAL LETTER GHE */
+$config['0400_04ff'][] = array('upper' => 1044, 'status' => 'C', 'lower' => array(1076)); /* CYRILLIC CAPITAL LETTER DE */
+$config['0400_04ff'][] = array('upper' => 1045, 'status' => 'C', 'lower' => array(1077)); /* CYRILLIC CAPITAL LETTER IE */
+$config['0400_04ff'][] = array('upper' => 1046, 'status' => 'C', 'lower' => array(1078)); /* CYRILLIC CAPITAL LETTER ZHE */
+$config['0400_04ff'][] = array('upper' => 1047, 'status' => 'C', 'lower' => array(1079)); /* CYRILLIC CAPITAL LETTER ZE */
+$config['0400_04ff'][] = array('upper' => 1048, 'status' => 'C', 'lower' => array(1080)); /* CYRILLIC CAPITAL LETTER I */
+$config['0400_04ff'][] = array('upper' => 1049, 'status' => 'C', 'lower' => array(1081)); /* CYRILLIC CAPITAL LETTER SHORT I */
+$config['0400_04ff'][] = array('upper' => 1050, 'status' => 'C', 'lower' => array(1082)); /* CYRILLIC CAPITAL LETTER KA */
+$config['0400_04ff'][] = array('upper' => 1051, 'status' => 'C', 'lower' => array(1083)); /* CYRILLIC CAPITAL LETTER EL */
+$config['0400_04ff'][] = array('upper' => 1052, 'status' => 'C', 'lower' => array(1084)); /* CYRILLIC CAPITAL LETTER EM */
+$config['0400_04ff'][] = array('upper' => 1053, 'status' => 'C', 'lower' => array(1085)); /* CYRILLIC CAPITAL LETTER EN */
+$config['0400_04ff'][] = array('upper' => 1054, 'status' => 'C', 'lower' => array(1086)); /* CYRILLIC CAPITAL LETTER O */
+$config['0400_04ff'][] = array('upper' => 1055, 'status' => 'C', 'lower' => array(1087)); /* CYRILLIC CAPITAL LETTER PE */
+$config['0400_04ff'][] = array('upper' => 1056, 'status' => 'C', 'lower' => array(1088)); /* CYRILLIC CAPITAL LETTER ER */
+$config['0400_04ff'][] = array('upper' => 1057, 'status' => 'C', 'lower' => array(1089)); /* CYRILLIC CAPITAL LETTER ES */
+$config['0400_04ff'][] = array('upper' => 1058, 'status' => 'C', 'lower' => array(1090)); /* CYRILLIC CAPITAL LETTER TE */
+$config['0400_04ff'][] = array('upper' => 1059, 'status' => 'C', 'lower' => array(1091)); /* CYRILLIC CAPITAL LETTER U */
+$config['0400_04ff'][] = array('upper' => 1060, 'status' => 'C', 'lower' => array(1092)); /* CYRILLIC CAPITAL LETTER EF */
+$config['0400_04ff'][] = array('upper' => 1061, 'status' => 'C', 'lower' => array(1093)); /* CYRILLIC CAPITAL LETTER HA */
+$config['0400_04ff'][] = array('upper' => 1062, 'status' => 'C', 'lower' => array(1094)); /* CYRILLIC CAPITAL LETTER TSE */
+$config['0400_04ff'][] = array('upper' => 1063, 'status' => 'C', 'lower' => array(1095)); /* CYRILLIC CAPITAL LETTER CHE */
+$config['0400_04ff'][] = array('upper' => 1064, 'status' => 'C', 'lower' => array(1096)); /* CYRILLIC CAPITAL LETTER SHA */
+$config['0400_04ff'][] = array('upper' => 1065, 'status' => 'C', 'lower' => array(1097)); /* CYRILLIC CAPITAL LETTER SHCHA */
+$config['0400_04ff'][] = array('upper' => 1066, 'status' => 'C', 'lower' => array(1098)); /* CYRILLIC CAPITAL LETTER HARD SIGN */
+$config['0400_04ff'][] = array('upper' => 1067, 'status' => 'C', 'lower' => array(1099)); /* CYRILLIC CAPITAL LETTER YERU */
+$config['0400_04ff'][] = array('upper' => 1068, 'status' => 'C', 'lower' => array(1100)); /* CYRILLIC CAPITAL LETTER SOFT SIGN */
+$config['0400_04ff'][] = array('upper' => 1069, 'status' => 'C', 'lower' => array(1101)); /* CYRILLIC CAPITAL LETTER E */
+$config['0400_04ff'][] = array('upper' => 1070, 'status' => 'C', 'lower' => array(1102)); /* CYRILLIC CAPITAL LETTER YU */
+$config['0400_04ff'][] = array('upper' => 1071, 'status' => 'C', 'lower' => array(1103)); /* CYRILLIC CAPITAL LETTER YA */
+$config['0400_04ff'][] = array('upper' => 1120, 'status' => 'C', 'lower' => array(1121)); /* CYRILLIC CAPITAL LETTER OMEGA */
+$config['0400_04ff'][] = array('upper' => 1122, 'status' => 'C', 'lower' => array(1123)); /* CYRILLIC CAPITAL LETTER YAT */
+$config['0400_04ff'][] = array('upper' => 1124, 'status' => 'C', 'lower' => array(1125)); /* CYRILLIC CAPITAL LETTER IOTIFIED E */
+$config['0400_04ff'][] = array('upper' => 1126, 'status' => 'C', 'lower' => array(1127)); /* CYRILLIC CAPITAL LETTER LITTLE YUS */
+$config['0400_04ff'][] = array('upper' => 1128, 'status' => 'C', 'lower' => array(1129)); /* CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS */
+$config['0400_04ff'][] = array('upper' => 1130, 'status' => 'C', 'lower' => array(1131)); /* CYRILLIC CAPITAL LETTER BIG YUS */
+$config['0400_04ff'][] = array('upper' => 1132, 'status' => 'C', 'lower' => array(1133)); /* CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS */
+$config['0400_04ff'][] = array('upper' => 1134, 'status' => 'C', 'lower' => array(1135)); /* CYRILLIC CAPITAL LETTER KSI */
+$config['0400_04ff'][] = array('upper' => 1136, 'status' => 'C', 'lower' => array(1137)); /* CYRILLIC CAPITAL LETTER PSI */
+$config['0400_04ff'][] = array('upper' => 1138, 'status' => 'C', 'lower' => array(1139)); /* CYRILLIC CAPITAL LETTER FITA */
+$config['0400_04ff'][] = array('upper' => 1140, 'status' => 'C', 'lower' => array(1141)); /* CYRILLIC CAPITAL LETTER IZHITSA */
+$config['0400_04ff'][] = array('upper' => 1142, 'status' => 'C', 'lower' => array(1143)); /* CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT */
+$config['0400_04ff'][] = array('upper' => 1144, 'status' => 'C', 'lower' => array(1145)); /* CYRILLIC CAPITAL LETTER UK */
+$config['0400_04ff'][] = array('upper' => 1146, 'status' => 'C', 'lower' => array(1147)); /* CYRILLIC CAPITAL LETTER ROUND OMEGA */
+$config['0400_04ff'][] = array('upper' => 1148, 'status' => 'C', 'lower' => array(1149)); /* CYRILLIC CAPITAL LETTER OMEGA WITH TITLO */
+$config['0400_04ff'][] = array('upper' => 1150, 'status' => 'C', 'lower' => array(1151)); /* CYRILLIC CAPITAL LETTER OT */
+$config['0400_04ff'][] = array('upper' => 1152, 'status' => 'C', 'lower' => array(1153)); /* CYRILLIC CAPITAL LETTER KOPPA */
+$config['0400_04ff'][] = array('upper' => 1162, 'status' => 'C', 'lower' => array(1163)); /* CYRILLIC CAPITAL LETTER SHORT I WITH TAIL */
+$config['0400_04ff'][] = array('upper' => 1164, 'status' => 'C', 'lower' => array(1165)); /* CYRILLIC CAPITAL LETTER SEMISOFT SIGN */
+$config['0400_04ff'][] = array('upper' => 1166, 'status' => 'C', 'lower' => array(1167)); /* CYRILLIC CAPITAL LETTER ER WITH TICK */
+$config['0400_04ff'][] = array('upper' => 1168, 'status' => 'C', 'lower' => array(1169)); /* CYRILLIC CAPITAL LETTER GHE WITH UPTURN */
+$config['0400_04ff'][] = array('upper' => 1170, 'status' => 'C', 'lower' => array(1171)); /* CYRILLIC CAPITAL LETTER GHE WITH STROKE */
+$config['0400_04ff'][] = array('upper' => 1172, 'status' => 'C', 'lower' => array(1173)); /* CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK */
+$config['0400_04ff'][] = array('upper' => 1174, 'status' => 'C', 'lower' => array(1175)); /* CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1176, 'status' => 'C', 'lower' => array(1177)); /* CYRILLIC CAPITAL LETTER ZE WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1178, 'status' => 'C', 'lower' => array(1179)); /* CYRILLIC CAPITAL LETTER KA WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1180, 'status' => 'C', 'lower' => array(1181)); /* CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE */
+$config['0400_04ff'][] = array('upper' => 1182, 'status' => 'C', 'lower' => array(1183)); /* CYRILLIC CAPITAL LETTER KA WITH STROKE */
+$config['0400_04ff'][] = array('upper' => 1184, 'status' => 'C', 'lower' => array(1185)); /* CYRILLIC CAPITAL LETTER BASHKIR KA */
+$config['0400_04ff'][] = array('upper' => 1186, 'status' => 'C', 'lower' => array(1187)); /* CYRILLIC CAPITAL LETTER EN WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1188, 'status' => 'C', 'lower' => array(1189)); /* CYRILLIC CAPITAL LIGATURE EN GHE */
+$config['0400_04ff'][] = array('upper' => 1190, 'status' => 'C', 'lower' => array(1191)); /* CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK */
+$config['0400_04ff'][] = array('upper' => 1192, 'status' => 'C', 'lower' => array(1193)); /* CYRILLIC CAPITAL LETTER ABKHASIAN HA */
+$config['0400_04ff'][] = array('upper' => 1194, 'status' => 'C', 'lower' => array(1195)); /* CYRILLIC CAPITAL LETTER ES WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1196, 'status' => 'C', 'lower' => array(1197)); /* CYRILLIC CAPITAL LETTER TE WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1198, 'status' => 'C', 'lower' => array(1199)); /* CYRILLIC CAPITAL LETTER STRAIGHT U */
+$config['0400_04ff'][] = array('upper' => 1200, 'status' => 'C', 'lower' => array(1201)); /* CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE */
+$config['0400_04ff'][] = array('upper' => 1202, 'status' => 'C', 'lower' => array(1203)); /* CYRILLIC CAPITAL LETTER HA WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1204, 'status' => 'C', 'lower' => array(1205)); /* CYRILLIC CAPITAL LIGATURE TE TSE */
+$config['0400_04ff'][] = array('upper' => 1206, 'status' => 'C', 'lower' => array(1207)); /* CYRILLIC CAPITAL LETTER CHE WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1208, 'status' => 'C', 'lower' => array(1209)); /* CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE */
+$config['0400_04ff'][] = array('upper' => 1210, 'status' => 'C', 'lower' => array(1211)); /* CYRILLIC CAPITAL LETTER SHHA */
+$config['0400_04ff'][] = array('upper' => 1212, 'status' => 'C', 'lower' => array(1213)); /* CYRILLIC CAPITAL LETTER ABKHASIAN CHE */
+$config['0400_04ff'][] = array('upper' => 1214, 'status' => 'C', 'lower' => array(1215)); /* CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1216, 'status' => 'C', 'lower' => array(1231)); /* CYRILLIC LETTER PALOCHKA */
+$config['0400_04ff'][] = array('upper' => 1217, 'status' => 'C', 'lower' => array(1218)); /* CYRILLIC CAPITAL LETTER ZHE WITH BREVE */
+$config['0400_04ff'][] = array('upper' => 1219, 'status' => 'C', 'lower' => array(1220)); /* CYRILLIC CAPITAL LETTER KA WITH HOOK */
+$config['0400_04ff'][] = array('upper' => 1221, 'status' => 'C', 'lower' => array(1222)); /* CYRILLIC CAPITAL LETTER EL WITH TAIL */
+$config['0400_04ff'][] = array('upper' => 1223, 'status' => 'C', 'lower' => array(1224)); /* CYRILLIC CAPITAL LETTER EN WITH HOOK */
+$config['0400_04ff'][] = array('upper' => 1225, 'status' => 'C', 'lower' => array(1226)); /* CYRILLIC CAPITAL LETTER EN WITH TAIL */
+$config['0400_04ff'][] = array('upper' => 1227, 'status' => 'C', 'lower' => array(1228)); /* CYRILLIC CAPITAL LETTER KHAKASSIAN CHE */
+$config['0400_04ff'][] = array('upper' => 1229, 'status' => 'C', 'lower' => array(1230)); /* CYRILLIC CAPITAL LETTER EM WITH TAIL */
+$config['0400_04ff'][] = array('upper' => 1232, 'status' => 'C', 'lower' => array(1233)); /* CYRILLIC CAPITAL LETTER A WITH BREVE */
+$config['0400_04ff'][] = array('upper' => 1234, 'status' => 'C', 'lower' => array(1235)); /* CYRILLIC CAPITAL LETTER A WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1236, 'status' => 'C', 'lower' => array(1237)); /* CYRILLIC CAPITAL LIGATURE A IE */
+$config['0400_04ff'][] = array('upper' => 1238, 'status' => 'C', 'lower' => array(1239)); /* CYRILLIC CAPITAL LETTER IE WITH BREVE */
+$config['0400_04ff'][] = array('upper' => 1240, 'status' => 'C', 'lower' => array(1241)); /* CYRILLIC CAPITAL LETTER SCHWA */
+$config['0400_04ff'][] = array('upper' => 1242, 'status' => 'C', 'lower' => array(1243)); /* CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1244, 'status' => 'C', 'lower' => array(1245)); /* CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1246, 'status' => 'C', 'lower' => array(1247)); /* CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1248, 'status' => 'C', 'lower' => array(1249)); /* CYRILLIC CAPITAL LETTER ABKHASIAN DZE */
+$config['0400_04ff'][] = array('upper' => 1250, 'status' => 'C', 'lower' => array(1251)); /* CYRILLIC CAPITAL LETTER I WITH MACRON */
+$config['0400_04ff'][] = array('upper' => 1252, 'status' => 'C', 'lower' => array(1253)); /* CYRILLIC CAPITAL LETTER I WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1254, 'status' => 'C', 'lower' => array(1255)); /* CYRILLIC CAPITAL LETTER O WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1256, 'status' => 'C', 'lower' => array(1257)); /* CYRILLIC CAPITAL LETTER BARRED O */
+$config['0400_04ff'][] = array('upper' => 1258, 'status' => 'C', 'lower' => array(1259)); /* CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1260, 'status' => 'C', 'lower' => array(1261)); /* CYRILLIC CAPITAL LETTER E WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1262, 'status' => 'C', 'lower' => array(1263)); /* CYRILLIC CAPITAL LETTER U WITH MACRON */
+$config['0400_04ff'][] = array('upper' => 1264, 'status' => 'C', 'lower' => array(1265)); /* CYRILLIC CAPITAL LETTER U WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1266, 'status' => 'C', 'lower' => array(1267)); /* CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE */
+$config['0400_04ff'][] = array('upper' => 1268, 'status' => 'C', 'lower' => array(1269)); /* CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1270, 'status' => 'C', 'lower' => array(1271)); /* CYRILLIC CAPITAL LETTER GHE WITH DESCENDER */
+$config['0400_04ff'][] = array('upper' => 1272, 'status' => 'C', 'lower' => array(1273)); /* CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS */
+$config['0400_04ff'][] = array('upper' => 1274, 'status' => 'C', 'lower' => array(1275)); /* CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK */
+$config['0400_04ff'][] = array('upper' => 1276, 'status' => 'C', 'lower' => array(1277)); /* CYRILLIC CAPITAL LETTER HA WITH HOOK */
+$config['0400_04ff'][] = array('upper' => 1278, 'status' => 'C', 'lower' => array(1279)); /* CYRILLIC CAPITAL LETTER HA WITH STROKE */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/0500_052f.php b/cake/config/unicode/casefolding/0500_052f.php
new file mode 100755
index 00000000..17853593
--- /dev/null
+++ b/cake/config/unicode/casefolding/0500_052f.php
@@ -0,0 +1,54 @@
+ 1280, 'status' => 'C', 'lower' => array(1281)); /* CYRILLIC CAPITAL LETTER KOMI DE */
+$config['0500_052f'][] = array('upper' => 1282, 'status' => 'C', 'lower' => array(1283)); /* CYRILLIC CAPITAL LETTER KOMI DJE */
+$config['0500_052f'][] = array('upper' => 1284, 'status' => 'C', 'lower' => array(1285)); /* CYRILLIC CAPITAL LETTER KOMI ZJE */
+$config['0500_052f'][] = array('upper' => 1286, 'status' => 'C', 'lower' => array(1287)); /* CYRILLIC CAPITAL LETTER KOMI DZJE */
+$config['0500_052f'][] = array('upper' => 1288, 'status' => 'C', 'lower' => array(1289)); /* CYRILLIC CAPITAL LETTER KOMI LJE */
+$config['0500_052f'][] = array('upper' => 1290, 'status' => 'C', 'lower' => array(1291)); /* CYRILLIC CAPITAL LETTER KOMI NJE */
+$config['0500_052f'][] = array('upper' => 1292, 'status' => 'C', 'lower' => array(1293)); /* CYRILLIC CAPITAL LETTER KOMI SJE */
+$config['0500_052f'][] = array('upper' => 1294, 'status' => 'C', 'lower' => array(1295)); /* CYRILLIC CAPITAL LETTER KOMI TJE */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/0530_058f.php b/cake/config/unicode/casefolding/0530_058f.php
new file mode 100755
index 00000000..83000a12
--- /dev/null
+++ b/cake/config/unicode/casefolding/0530_058f.php
@@ -0,0 +1,84 @@
+ 1329, 'status' => 'C', 'lower' => array(1377)); /* ARMENIAN CAPITAL LETTER AYB */
+$config['0530_058f'][] = array('upper' => 1330, 'status' => 'C', 'lower' => array(1378)); /* ARMENIAN CAPITAL LETTER BEN */
+$config['0530_058f'][] = array('upper' => 1331, 'status' => 'C', 'lower' => array(1379)); /* ARMENIAN CAPITAL LETTER GIM */
+$config['0530_058f'][] = array('upper' => 1332, 'status' => 'C', 'lower' => array(1380)); /* ARMENIAN CAPITAL LETTER DA */
+$config['0530_058f'][] = array('upper' => 1333, 'status' => 'C', 'lower' => array(1381)); /* ARMENIAN CAPITAL LETTER ECH */
+$config['0530_058f'][] = array('upper' => 1334, 'status' => 'C', 'lower' => array(1382)); /* ARMENIAN CAPITAL LETTER ZA */
+$config['0530_058f'][] = array('upper' => 1335, 'status' => 'C', 'lower' => array(1383)); /* ARMENIAN CAPITAL LETTER EH */
+$config['0530_058f'][] = array('upper' => 1336, 'status' => 'C', 'lower' => array(1384)); /* ARMENIAN CAPITAL LETTER ET */
+$config['0530_058f'][] = array('upper' => 1337, 'status' => 'C', 'lower' => array(1385)); /* ARMENIAN CAPITAL LETTER TO */
+$config['0530_058f'][] = array('upper' => 1338, 'status' => 'C', 'lower' => array(1386)); /* ARMENIAN CAPITAL LETTER ZHE */
+$config['0530_058f'][] = array('upper' => 1339, 'status' => 'C', 'lower' => array(1387)); /* ARMENIAN CAPITAL LETTER INI */
+$config['0530_058f'][] = array('upper' => 1340, 'status' => 'C', 'lower' => array(1388)); /* ARMENIAN CAPITAL LETTER LIWN */
+$config['0530_058f'][] = array('upper' => 1341, 'status' => 'C', 'lower' => array(1389)); /* ARMENIAN CAPITAL LETTER XEH */
+$config['0530_058f'][] = array('upper' => 1342, 'status' => 'C', 'lower' => array(1390)); /* ARMENIAN CAPITAL LETTER CA */
+$config['0530_058f'][] = array('upper' => 1343, 'status' => 'C', 'lower' => array(1391)); /* ARMENIAN CAPITAL LETTER KEN */
+$config['0530_058f'][] = array('upper' => 1344, 'status' => 'C', 'lower' => array(1392)); /* ARMENIAN CAPITAL LETTER HO */
+$config['0530_058f'][] = array('upper' => 1345, 'status' => 'C', 'lower' => array(1393)); /* ARMENIAN CAPITAL LETTER JA */
+$config['0530_058f'][] = array('upper' => 1346, 'status' => 'C', 'lower' => array(1394)); /* ARMENIAN CAPITAL LETTER GHAD */
+$config['0530_058f'][] = array('upper' => 1347, 'status' => 'C', 'lower' => array(1395)); /* ARMENIAN CAPITAL LETTER CHEH */
+$config['0530_058f'][] = array('upper' => 1348, 'status' => 'C', 'lower' => array(1396)); /* ARMENIAN CAPITAL LETTER MEN */
+$config['0530_058f'][] = array('upper' => 1349, 'status' => 'C', 'lower' => array(1397)); /* ARMENIAN CAPITAL LETTER YI */
+$config['0530_058f'][] = array('upper' => 1350, 'status' => 'C', 'lower' => array(1398)); /* ARMENIAN CAPITAL LETTER NOW */
+$config['0530_058f'][] = array('upper' => 1351, 'status' => 'C', 'lower' => array(1399)); /* ARMENIAN CAPITAL LETTER SHA */
+$config['0530_058f'][] = array('upper' => 1352, 'status' => 'C', 'lower' => array(1400)); /* ARMENIAN CAPITAL LETTER VO */
+$config['0530_058f'][] = array('upper' => 1353, 'status' => 'C', 'lower' => array(1401)); /* ARMENIAN CAPITAL LETTER CHA */
+$config['0530_058f'][] = array('upper' => 1354, 'status' => 'C', 'lower' => array(1402)); /* ARMENIAN CAPITAL LETTER PEH */
+$config['0530_058f'][] = array('upper' => 1355, 'status' => 'C', 'lower' => array(1403)); /* ARMENIAN CAPITAL LETTER JHEH */
+$config['0530_058f'][] = array('upper' => 1356, 'status' => 'C', 'lower' => array(1404)); /* ARMENIAN CAPITAL LETTER RA */
+$config['0530_058f'][] = array('upper' => 1357, 'status' => 'C', 'lower' => array(1405)); /* ARMENIAN CAPITAL LETTER SEH */
+$config['0530_058f'][] = array('upper' => 1358, 'status' => 'C', 'lower' => array(1406)); /* ARMENIAN CAPITAL LETTER VEW */
+$config['0530_058f'][] = array('upper' => 1359, 'status' => 'C', 'lower' => array(1407)); /* ARMENIAN CAPITAL LETTER TIWN */
+$config['0530_058f'][] = array('upper' => 1360, 'status' => 'C', 'lower' => array(1408)); /* ARMENIAN CAPITAL LETTER REH */
+$config['0530_058f'][] = array('upper' => 1361, 'status' => 'C', 'lower' => array(1409)); /* ARMENIAN CAPITAL LETTER CO */
+$config['0530_058f'][] = array('upper' => 1362, 'status' => 'C', 'lower' => array(1410)); /* ARMENIAN CAPITAL LETTER YIWN */
+$config['0530_058f'][] = array('upper' => 1363, 'status' => 'C', 'lower' => array(1411)); /* ARMENIAN CAPITAL LETTER PIWR */
+$config['0530_058f'][] = array('upper' => 1364, 'status' => 'C', 'lower' => array(1412)); /* ARMENIAN CAPITAL LETTER KEH */
+$config['0530_058f'][] = array('upper' => 1365, 'status' => 'C', 'lower' => array(1413)); /* ARMENIAN CAPITAL LETTER OH */
+$config['0530_058f'][] = array('upper' => 1366, 'status' => 'C', 'lower' => array(1414)); /* ARMENIAN CAPITAL LETTER FEH */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/1e00_1eff.php b/cake/config/unicode/casefolding/1e00_1eff.php
new file mode 100755
index 00000000..b8ad76f6
--- /dev/null
+++ b/cake/config/unicode/casefolding/1e00_1eff.php
@@ -0,0 +1,174 @@
+ 7680, 'status' => 'C', 'lower' => array(7681)); /* LATIN CAPITAL LETTER A WITH RING BELOW */
+$config['1e00_1eff'][] = array('upper' => 7682, 'status' => 'C', 'lower' => array(7683)); /* LATIN CAPITAL LETTER B WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7684, 'status' => 'C', 'lower' => array(7685)); /* LATIN CAPITAL LETTER B WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7686, 'status' => 'C', 'lower' => array(7687)); /* LATIN CAPITAL LETTER B WITH LINE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7688, 'status' => 'C', 'lower' => array(7689)); /* LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7690, 'status' => 'C', 'lower' => array(7691)); /* LATIN CAPITAL LETTER D WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7692, 'status' => 'C', 'lower' => array(7693)); /* LATIN CAPITAL LETTER D WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7694, 'status' => 'C', 'lower' => array(7695)); /* LATIN CAPITAL LETTER D WITH LINE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7696, 'status' => 'C', 'lower' => array(7697)); /* LATIN CAPITAL LETTER D WITH CEDILLA */
+$config['1e00_1eff'][] = array('upper' => 7698, 'status' => 'C', 'lower' => array(7699)); /* LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW */
+$config['1e00_1eff'][] = array('upper' => 7700, 'status' => 'C', 'lower' => array(7701)); /* LATIN CAPITAL LETTER E WITH MACRON AND GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7702, 'status' => 'C', 'lower' => array(7703)); /* LATIN CAPITAL LETTER E WITH MACRON AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7704, 'status' => 'C', 'lower' => array(7705)); /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW */
+$config['1e00_1eff'][] = array('upper' => 7706, 'status' => 'C', 'lower' => array(7707)); /* LATIN CAPITAL LETTER E WITH TILDE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7708, 'status' => 'C', 'lower' => array(7709)); /* LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE */
+$config['1e00_1eff'][] = array('upper' => 7710, 'status' => 'C', 'lower' => array(7711)); /* LATIN CAPITAL LETTER F WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7712, 'status' => 'C', 'lower' => array(7713)); /* LATIN CAPITAL LETTER G WITH MACRON */
+$config['1e00_1eff'][] = array('upper' => 7714, 'status' => 'C', 'lower' => array(7715)); /* LATIN CAPITAL LETTER H WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7716, 'status' => 'C', 'lower' => array(7717)); /* LATIN CAPITAL LETTER H WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7718, 'status' => 'C', 'lower' => array(7719)); /* LATIN CAPITAL LETTER H WITH DIAERESIS */
+$config['1e00_1eff'][] = array('upper' => 7720, 'status' => 'C', 'lower' => array(7721)); /* LATIN CAPITAL LETTER H WITH CEDILLA */
+$config['1e00_1eff'][] = array('upper' => 7722, 'status' => 'C', 'lower' => array(7723)); /* LATIN CAPITAL LETTER H WITH BREVE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7724, 'status' => 'C', 'lower' => array(7725)); /* LATIN CAPITAL LETTER I WITH TILDE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7726, 'status' => 'C', 'lower' => array(7727)); /* LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7728, 'status' => 'C', 'lower' => array(7729)); /* LATIN CAPITAL LETTER K WITH ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7730, 'status' => 'C', 'lower' => array(7731)); /* LATIN CAPITAL LETTER K WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7732, 'status' => 'C', 'lower' => array(7733)); /* LATIN CAPITAL LETTER K WITH LINE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7734, 'status' => 'C', 'lower' => array(7735)); /* LATIN CAPITAL LETTER L WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7736, 'status' => 'C', 'lower' => array(7737)); /* LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON */
+$config['1e00_1eff'][] = array('upper' => 7738, 'status' => 'C', 'lower' => array(7739)); /* LATIN CAPITAL LETTER L WITH LINE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7740, 'status' => 'C', 'lower' => array(7741)); /* LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW */
+$config['1e00_1eff'][] = array('upper' => 7742, 'status' => 'C', 'lower' => array(7743)); /* LATIN CAPITAL LETTER M WITH ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7744, 'status' => 'C', 'lower' => array(7745)); /* LATIN CAPITAL LETTER M WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7746, 'status' => 'C', 'lower' => array(7747)); /* LATIN CAPITAL LETTER M WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7748, 'status' => 'C', 'lower' => array(7749)); /* LATIN CAPITAL LETTER N WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7750, 'status' => 'C', 'lower' => array(7751)); /* LATIN CAPITAL LETTER N WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7752, 'status' => 'C', 'lower' => array(7753)); /* LATIN CAPITAL LETTER N WITH LINE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7754, 'status' => 'C', 'lower' => array(7755)); /* LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW */
+$config['1e00_1eff'][] = array('upper' => 7756, 'status' => 'C', 'lower' => array(7757)); /* LATIN CAPITAL LETTER O WITH TILDE AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7758, 'status' => 'C', 'lower' => array(7759)); /* LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS */
+$config['1e00_1eff'][] = array('upper' => 7760, 'status' => 'C', 'lower' => array(7761)); /* LATIN CAPITAL LETTER O WITH MACRON AND GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7762, 'status' => 'C', 'lower' => array(7763)); /* LATIN CAPITAL LETTER O WITH MACRON AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7764, 'status' => 'C', 'lower' => array(7765)); /* LATIN CAPITAL LETTER P WITH ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7766, 'status' => 'C', 'lower' => array(7767)); /* LATIN CAPITAL LETTER P WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7768, 'status' => 'C', 'lower' => array(7769)); /* LATIN CAPITAL LETTER R WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7770, 'status' => 'C', 'lower' => array(7771)); /* LATIN CAPITAL LETTER R WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7772, 'status' => 'C', 'lower' => array(7773)); /* LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON */
+$config['1e00_1eff'][] = array('upper' => 7774, 'status' => 'C', 'lower' => array(7775)); /* LATIN CAPITAL LETTER R WITH LINE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7776, 'status' => 'C', 'lower' => array(7777)); /* LATIN CAPITAL LETTER S WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7778, 'status' => 'C', 'lower' => array(7779)); /* LATIN CAPITAL LETTER S WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7780, 'status' => 'C', 'lower' => array(7781)); /* LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7782, 'status' => 'C', 'lower' => array(7783)); /* LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7784, 'status' => 'C', 'lower' => array(7785)); /* LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7786, 'status' => 'C', 'lower' => array(7787)); /* LATIN CAPITAL LETTER T WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7788, 'status' => 'C', 'lower' => array(7789)); /* LATIN CAPITAL LETTER T WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7790, 'status' => 'C', 'lower' => array(7791)); /* LATIN CAPITAL LETTER T WITH LINE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7792, 'status' => 'C', 'lower' => array(7793)); /* LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW */
+$config['1e00_1eff'][] = array('upper' => 7794, 'status' => 'C', 'lower' => array(7795)); /* LATIN CAPITAL LETTER U WITH DIAERESIS BELOW */
+$config['1e00_1eff'][] = array('upper' => 7796, 'status' => 'C', 'lower' => array(7797)); /* LATIN CAPITAL LETTER U WITH TILDE BELOW */
+$config['1e00_1eff'][] = array('upper' => 7798, 'status' => 'C', 'lower' => array(7799)); /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW */
+$config['1e00_1eff'][] = array('upper' => 7800, 'status' => 'C', 'lower' => array(7801)); /* LATIN CAPITAL LETTER U WITH TILDE AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7802, 'status' => 'C', 'lower' => array(7803)); /* LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS */
+$config['1e00_1eff'][] = array('upper' => 7804, 'status' => 'C', 'lower' => array(7805)); /* LATIN CAPITAL LETTER V WITH TILDE */
+$config['1e00_1eff'][] = array('upper' => 7806, 'status' => 'C', 'lower' => array(7807)); /* LATIN CAPITAL LETTER V WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7808, 'status' => 'C', 'lower' => array(7809)); /* LATIN CAPITAL LETTER W WITH GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7810, 'status' => 'C', 'lower' => array(7811)); /* LATIN CAPITAL LETTER W WITH ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7812, 'status' => 'C', 'lower' => array(7813)); /* LATIN CAPITAL LETTER W WITH DIAERESIS */
+$config['1e00_1eff'][] = array('upper' => 7814, 'status' => 'C', 'lower' => array(7815)); /* LATIN CAPITAL LETTER W WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7816, 'status' => 'C', 'lower' => array(7817)); /* LATIN CAPITAL LETTER W WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7818, 'status' => 'C', 'lower' => array(7819)); /* LATIN CAPITAL LETTER X WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7820, 'status' => 'C', 'lower' => array(7821)); /* LATIN CAPITAL LETTER X WITH DIAERESIS */
+$config['1e00_1eff'][] = array('upper' => 7822, 'status' => 'C', 'lower' => array(7823)); /* LATIN CAPITAL LETTER Y WITH DOT ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7824, 'status' => 'C', 'lower' => array(7825)); /* LATIN CAPITAL LETTER Z WITH CIRCUMFLEX */
+$config['1e00_1eff'][] = array('upper' => 7826, 'status' => 'C', 'lower' => array(7827)); /* LATIN CAPITAL LETTER Z WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7828, 'status' => 'C', 'lower' => array(7829)); /* LATIN CAPITAL LETTER Z WITH LINE BELOW */
+
+//$config['1e00_1eff'][] = array('upper' => 7830, 'status' => 'F', 'lower' => array(104, 817)); /* LATIN SMALL LETTER H WITH LINE BELOW */
+//$config['1e00_1eff'][] = array('upper' => 7831, 'status' => 'F', 'lower' => array(116, 776)); /* LATIN SMALL LETTER T WITH DIAERESIS */
+//$config['1e00_1eff'][] = array('upper' => 7832, 'status' => 'F', 'lower' => array(119, 778)); /* LATIN SMALL LETTER W WITH RING ABOVE */
+//$config['1e00_1eff'][] = array('upper' => 7833, 'status' => 'F', 'lower' => array(121, 778)); /* LATIN SMALL LETTER Y WITH RING ABOVE */
+//$config['1e00_1eff'][] = array('upper' => 7834, 'status' => 'F', 'lower' => array(97, 702)); /* LATIN SMALL LETTER A WITH RIGHT HALF RING */
+//$config['1e00_1eff'][] = array('upper' => 7835, 'status' => 'C', 'lower' => array(7777)); /* LATIN SMALL LETTER LONG S WITH DOT ABOVE */
+
+$config['1e00_1eff'][] = array('upper' => 7840, 'status' => 'C', 'lower' => array(7841)); /* LATIN CAPITAL LETTER A WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7842, 'status' => 'C', 'lower' => array(7843)); /* LATIN CAPITAL LETTER A WITH HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7844, 'status' => 'C', 'lower' => array(7845)); /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7846, 'status' => 'C', 'lower' => array(7847)); /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7848, 'status' => 'C', 'lower' => array(7849)); /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7850, 'status' => 'C', 'lower' => array(7851)); /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE */
+$config['1e00_1eff'][] = array('upper' => 7852, 'status' => 'C', 'lower' => array(7853)); /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7854, 'status' => 'C', 'lower' => array(7855)); /* LATIN CAPITAL LETTER A WITH BREVE AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7856, 'status' => 'C', 'lower' => array(7857)); /* LATIN CAPITAL LETTER A WITH BREVE AND GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7858, 'status' => 'C', 'lower' => array(7859)); /* LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7860, 'status' => 'C', 'lower' => array(7861)); /* LATIN CAPITAL LETTER A WITH BREVE AND TILDE */
+$config['1e00_1eff'][] = array('upper' => 7862, 'status' => 'C', 'lower' => array(7863)); /* LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7864, 'status' => 'C', 'lower' => array(7865)); /* LATIN CAPITAL LETTER E WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7866, 'status' => 'C', 'lower' => array(7867)); /* LATIN CAPITAL LETTER E WITH HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7868, 'status' => 'C', 'lower' => array(7869)); /* LATIN CAPITAL LETTER E WITH TILDE */
+$config['1e00_1eff'][] = array('upper' => 7870, 'status' => 'C', 'lower' => array(7871)); /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7872, 'status' => 'C', 'lower' => array(7873)); /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7874, 'status' => 'C', 'lower' => array(7875)); /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7876, 'status' => 'C', 'lower' => array(7877)); /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE */
+$config['1e00_1eff'][] = array('upper' => 7878, 'status' => 'C', 'lower' => array(7879)); /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7880, 'status' => 'C', 'lower' => array(7881)); /* LATIN CAPITAL LETTER I WITH HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7882, 'status' => 'C', 'lower' => array(7883)); /* LATIN CAPITAL LETTER I WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7884, 'status' => 'C', 'lower' => array(7885)); /* LATIN CAPITAL LETTER O WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7886, 'status' => 'C', 'lower' => array(7887)); /* LATIN CAPITAL LETTER O WITH HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7888, 'status' => 'C', 'lower' => array(7889)); /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7890, 'status' => 'C', 'lower' => array(7891)); /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7892, 'status' => 'C', 'lower' => array(7893)); /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7894, 'status' => 'C', 'lower' => array(7895)); /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE */
+$config['1e00_1eff'][] = array('upper' => 7896, 'status' => 'C', 'lower' => array(7897)); /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7898, 'status' => 'C', 'lower' => array(7899)); /* LATIN CAPITAL LETTER O WITH HORN AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7900, 'status' => 'C', 'lower' => array(7901)); /* LATIN CAPITAL LETTER O WITH HORN AND GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7902, 'status' => 'C', 'lower' => array(7903)); /* LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7904, 'status' => 'C', 'lower' => array(7905)); /* LATIN CAPITAL LETTER O WITH HORN AND TILDE */
+$config['1e00_1eff'][] = array('upper' => 7906, 'status' => 'C', 'lower' => array(7907)); /* LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7908, 'status' => 'C', 'lower' => array(7909)); /* LATIN CAPITAL LETTER U WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7910, 'status' => 'C', 'lower' => array(7911)); /* LATIN CAPITAL LETTER U WITH HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7912, 'status' => 'C', 'lower' => array(7913)); /* LATIN CAPITAL LETTER U WITH HORN AND ACUTE */
+$config['1e00_1eff'][] = array('upper' => 7914, 'status' => 'C', 'lower' => array(7915)); /* LATIN CAPITAL LETTER U WITH HORN AND GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7916, 'status' => 'C', 'lower' => array(7917)); /* LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7918, 'status' => 'C', 'lower' => array(7919)); /* LATIN CAPITAL LETTER U WITH HORN AND TILDE */
+$config['1e00_1eff'][] = array('upper' => 7920, 'status' => 'C', 'lower' => array(7921)); /* LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7922, 'status' => 'C', 'lower' => array(7923)); /* LATIN CAPITAL LETTER Y WITH GRAVE */
+$config['1e00_1eff'][] = array('upper' => 7924, 'status' => 'C', 'lower' => array(7925)); /* LATIN CAPITAL LETTER Y WITH DOT BELOW */
+$config['1e00_1eff'][] = array('upper' => 7926, 'status' => 'C', 'lower' => array(7927)); /* LATIN CAPITAL LETTER Y WITH HOOK ABOVE */
+$config['1e00_1eff'][] = array('upper' => 7928, 'status' => 'C', 'lower' => array(7929)); /* LATIN CAPITAL LETTER Y WITH TILDE */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/1f00_1fff.php b/cake/config/unicode/casefolding/1f00_1fff.php
new file mode 100755
index 00000000..2e758924
--- /dev/null
+++ b/cake/config/unicode/casefolding/1f00_1fff.php
@@ -0,0 +1,222 @@
+ 7944, 'status' => 'C', 'lower' => array(7936, 953)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI */
+$config['1f00_1fff'][] = array('upper' => 7945, 'status' => 'C', 'lower' => array(7937)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA */
+$config['1f00_1fff'][] = array('upper' => 7946, 'status' => 'C', 'lower' => array(7938)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 7947, 'status' => 'C', 'lower' => array(7939)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 7948, 'status' => 'C', 'lower' => array(7940)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 7949, 'status' => 'C', 'lower' => array(7941)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 7950, 'status' => 'C', 'lower' => array(7942)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 7951, 'status' => 'C', 'lower' => array(7943)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 7960, 'status' => 'C', 'lower' => array(7952)); /* GREEK CAPITAL LETTER EPSILON WITH PSILI */
+$config['1f00_1fff'][] = array('upper' => 7961, 'status' => 'C', 'lower' => array(7953)); /* GREEK CAPITAL LETTER EPSILON WITH DASIA */
+$config['1f00_1fff'][] = array('upper' => 7962, 'status' => 'C', 'lower' => array(7954)); /* GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 7963, 'status' => 'C', 'lower' => array(7955)); /* GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 7964, 'status' => 'C', 'lower' => array(7956)); /* GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 7965, 'status' => 'C', 'lower' => array(7957)); /* GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 7976, 'status' => 'C', 'lower' => array(7968)); /* GREEK CAPITAL LETTER ETA WITH PSILI */
+$config['1f00_1fff'][] = array('upper' => 7977, 'status' => 'C', 'lower' => array(7969)); /* GREEK CAPITAL LETTER ETA WITH DASIA */
+$config['1f00_1fff'][] = array('upper' => 7978, 'status' => 'C', 'lower' => array(7970)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 7979, 'status' => 'C', 'lower' => array(7971)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 7980, 'status' => 'C', 'lower' => array(7972)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 7981, 'status' => 'C', 'lower' => array(7973)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 7982, 'status' => 'C', 'lower' => array(7974)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 7983, 'status' => 'C', 'lower' => array(7975)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 7992, 'status' => 'C', 'lower' => array(7984)); /* GREEK CAPITAL LETTER IOTA WITH PSILI */
+$config['1f00_1fff'][] = array('upper' => 7993, 'status' => 'C', 'lower' => array(7985)); /* GREEK CAPITAL LETTER IOTA WITH DASIA */
+$config['1f00_1fff'][] = array('upper' => 7994, 'status' => 'C', 'lower' => array(7986)); /* GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 7995, 'status' => 'C', 'lower' => array(7987)); /* GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 7996, 'status' => 'C', 'lower' => array(7988)); /* GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 7997, 'status' => 'C', 'lower' => array(7989)); /* GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 7998, 'status' => 'C', 'lower' => array(7990)); /* GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 7999, 'status' => 'C', 'lower' => array(7991)); /* GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8008, 'status' => 'C', 'lower' => array(8000)); /* GREEK CAPITAL LETTER OMICRON WITH PSILI */
+$config['1f00_1fff'][] = array('upper' => 8009, 'status' => 'C', 'lower' => array(8001)); /* GREEK CAPITAL LETTER OMICRON WITH DASIA */
+$config['1f00_1fff'][] = array('upper' => 8010, 'status' => 'C', 'lower' => array(8002)); /* GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 8011, 'status' => 'C', 'lower' => array(8003)); /* GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 8012, 'status' => 'C', 'lower' => array(8004)); /* GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 8013, 'status' => 'C', 'lower' => array(8005)); /* GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 8016, 'status' => 'F', 'lower' => array(965, 787)); /* GREEK SMALL LETTER UPSILON WITH PSILI */
+$config['1f00_1fff'][] = array('upper' => 8018, 'status' => 'F', 'lower' => array(965, 787, 768)); /* GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 8020, 'status' => 'F', 'lower' => array(965, 787, 769)); /* GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 8022, 'status' => 'F', 'lower' => array(965, 787, 834)); /* GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8025, 'status' => 'C', 'lower' => array(8017)); /* GREEK CAPITAL LETTER UPSILON WITH DASIA */
+$config['1f00_1fff'][] = array('upper' => 8027, 'status' => 'C', 'lower' => array(8019)); /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 8029, 'status' => 'C', 'lower' => array(8021)); /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 8031, 'status' => 'C', 'lower' => array(8023)); /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8040, 'status' => 'C', 'lower' => array(8032)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI */
+$config['1f00_1fff'][] = array('upper' => 8041, 'status' => 'C', 'lower' => array(8033)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA */
+$config['1f00_1fff'][] = array('upper' => 8042, 'status' => 'C', 'lower' => array(8034)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 8043, 'status' => 'C', 'lower' => array(8035)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 8044, 'status' => 'C', 'lower' => array(8036)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 8045, 'status' => 'C', 'lower' => array(8037)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 8046, 'status' => 'C', 'lower' => array(8038)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8047, 'status' => 'C', 'lower' => array(8039)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8064, 'status' => 'F', 'lower' => array(7936, 953)); /* GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8065, 'status' => 'F', 'lower' => array(7937, 953)); /* GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8066, 'status' => 'F', 'lower' => array(7938, 953)); /* GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8067, 'status' => 'F', 'lower' => array(7939, 953)); /* GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8068, 'status' => 'F', 'lower' => array(7940, 953)); /* GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8069, 'status' => 'F', 'lower' => array(7941, 953)); /* GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8070, 'status' => 'F', 'lower' => array(7942, 953)); /* GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8071, 'status' => 'F', 'lower' => array(7943, 953)); /* GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8072, 'status' => 'F', 'lower' => array(7936, 953)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8072, 'status' => 'S', 'lower' => array(8064)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8073, 'status' => 'F', 'lower' => array(7937, 953)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8073, 'status' => 'S', 'lower' => array(8065)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8074, 'status' => 'F', 'lower' => array(7938, 953)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8074, 'status' => 'S', 'lower' => array(8066)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8075, 'status' => 'F', 'lower' => array(7939, 953)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8075, 'status' => 'S', 'lower' => array(8067)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8076, 'status' => 'F', 'lower' => array(7940, 953)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8076, 'status' => 'S', 'lower' => array(8068)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8077, 'status' => 'F', 'lower' => array(7941, 953)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8077, 'status' => 'S', 'lower' => array(8069)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8078, 'status' => 'F', 'lower' => array(7942, 953)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8078, 'status' => 'S', 'lower' => array(8070)); /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8079, 'status' => 'F', 'lower' => array(7943, 953)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8079, 'status' => 'S', 'lower' => array(8071)); /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8080, 'status' => 'F', 'lower' => array(7968, 953)); /* GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8081, 'status' => 'F', 'lower' => array(7969, 953)); /* GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8082, 'status' => 'F', 'lower' => array(7970, 953)); /* GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8083, 'status' => 'F', 'lower' => array(7971, 953)); /* GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8084, 'status' => 'F', 'lower' => array(7972, 953)); /* GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8085, 'status' => 'F', 'lower' => array(7973, 953)); /* GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8086, 'status' => 'F', 'lower' => array(7974, 953)); /* GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8087, 'status' => 'F', 'lower' => array(7975, 953)); /* GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8088, 'status' => 'F', 'lower' => array(7968, 953)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8088, 'status' => 'S', 'lower' => array(8080)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8089, 'status' => 'F', 'lower' => array(7969, 953)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8089, 'status' => 'S', 'lower' => array(8081)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8090, 'status' => 'F', 'lower' => array(7970, 953)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8090, 'status' => 'S', 'lower' => array(8082)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8091, 'status' => 'F', 'lower' => array(7971, 953)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8091, 'status' => 'S', 'lower' => array(8083)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8092, 'status' => 'F', 'lower' => array(7972, 953)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8092, 'status' => 'S', 'lower' => array(8084)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8093, 'status' => 'F', 'lower' => array(7973, 953)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8093, 'status' => 'S', 'lower' => array(8085)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8094, 'status' => 'F', 'lower' => array(7974, 953)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8094, 'status' => 'S', 'lower' => array(8086)); /* GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8095, 'status' => 'F', 'lower' => array(7975, 953)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8095, 'status' => 'S', 'lower' => array(8087)); /* GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8096, 'status' => 'F', 'lower' => array(8032, 953)); /* GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8097, 'status' => 'F', 'lower' => array(8033, 953)); /* GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8098, 'status' => 'F', 'lower' => array(8034, 953)); /* GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8099, 'status' => 'F', 'lower' => array(8035, 953)); /* GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8100, 'status' => 'F', 'lower' => array(8036, 953)); /* GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8101, 'status' => 'F', 'lower' => array(8037, 953)); /* GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8102, 'status' => 'F', 'lower' => array(8038, 953)); /* GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8103, 'status' => 'F', 'lower' => array(8039, 953)); /* GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8104, 'status' => 'F', 'lower' => array(8032, 953)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8104, 'status' => 'S', 'lower' => array(8096)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8105, 'status' => 'F', 'lower' => array(8033, 953)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8105, 'status' => 'S', 'lower' => array(8097)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8106, 'status' => 'F', 'lower' => array(8034, 953)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8106, 'status' => 'S', 'lower' => array(8098)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8107, 'status' => 'F', 'lower' => array(8035, 953)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8107, 'status' => 'S', 'lower' => array(8099)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8108, 'status' => 'F', 'lower' => array(8036, 953)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8108, 'status' => 'S', 'lower' => array(8100)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8109, 'status' => 'F', 'lower' => array(8037, 953)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8109, 'status' => 'S', 'lower' => array(8101)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8110, 'status' => 'F', 'lower' => array(8038, 953)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8110, 'status' => 'S', 'lower' => array(8102)); /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8111, 'status' => 'F', 'lower' => array(8039, 953)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8111, 'status' => 'S', 'lower' => array(8103)); /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8114, 'status' => 'F', 'lower' => array(8048, 953)); /* GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8115, 'status' => 'F', 'lower' => array(945, 953)); /* GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8116, 'status' => 'F', 'lower' => array(940, 953)); /* GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8118, 'status' => 'F', 'lower' => array(945, 834)); /* GREEK SMALL LETTER ALPHA WITH PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8119, 'status' => 'F', 'lower' => array(945, 834, 953)); /* GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8120, 'status' => 'C', 'lower' => array(8112)); /* GREEK CAPITAL LETTER ALPHA WITH VRACHY */
+$config['1f00_1fff'][] = array('upper' => 8121, 'status' => 'C', 'lower' => array(8113)); /* GREEK CAPITAL LETTER ALPHA WITH MACRON */
+$config['1f00_1fff'][] = array('upper' => 8122, 'status' => 'C', 'lower' => array(8048)); /* GREEK CAPITAL LETTER ALPHA WITH VARIA */
+$config['1f00_1fff'][] = array('upper' => 8123, 'status' => 'C', 'lower' => array(8049)); /* GREEK CAPITAL LETTER ALPHA WITH OXIA */
+$config['1f00_1fff'][] = array('upper' => 8124, 'status' => 'F', 'lower' => array(945, 953)); /* GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8124, 'status' => 'S', 'lower' => array(8115)); /* GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8126, 'status' => 'C', 'lower' => array(953)); /* GREEK PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8130, 'status' => 'F', 'lower' => array(8052, 953)); /* GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8131, 'status' => 'F', 'lower' => array(951, 953)); /* GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8132, 'status' => 'F', 'lower' => array(942, 953)); /* GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8134, 'status' => 'F', 'lower' => array(951, 834)); /* GREEK SMALL LETTER ETA WITH PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8135, 'status' => 'F', 'lower' => array(951, 834, 953)); /* GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8136, 'status' => 'C', 'lower' => array(8050)); /* GREEK CAPITAL LETTER EPSILON WITH VARIA */
+$config['1f00_1fff'][] = array('upper' => 8137, 'status' => 'C', 'lower' => array(8051)); /* GREEK CAPITAL LETTER EPSILON WITH OXIA */
+$config['1f00_1fff'][] = array('upper' => 8138, 'status' => 'C', 'lower' => array(8052)); /* GREEK CAPITAL LETTER ETA WITH VARIA */
+$config['1f00_1fff'][] = array('upper' => 8139, 'status' => 'C', 'lower' => array(8053)); /* GREEK CAPITAL LETTER ETA WITH OXIA */
+$config['1f00_1fff'][] = array('upper' => 8140, 'status' => 'F', 'lower' => array(951, 953)); /* GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8140, 'status' => 'S', 'lower' => array(8131)); /* GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8146, 'status' => 'F', 'lower' => array(953, 776, 768)); /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 8147, 'status' => 'F', 'lower' => array(953, 776, 769)); /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 8150, 'status' => 'F', 'lower' => array(953, 834)); /* GREEK SMALL LETTER IOTA WITH PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8151, 'status' => 'F', 'lower' => array(953, 776, 834)); /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8152, 'status' => 'C', 'lower' => array(8144)); /* GREEK CAPITAL LETTER IOTA WITH VRACHY */
+$config['1f00_1fff'][] = array('upper' => 8153, 'status' => 'C', 'lower' => array(8145)); /* GREEK CAPITAL LETTER IOTA WITH MACRON */
+$config['1f00_1fff'][] = array('upper' => 8154, 'status' => 'C', 'lower' => array(8054)); /* GREEK CAPITAL LETTER IOTA WITH VARIA */
+$config['1f00_1fff'][] = array('upper' => 8155, 'status' => 'C', 'lower' => array(8055)); /* GREEK CAPITAL LETTER IOTA WITH OXIA */
+$config['1f00_1fff'][] = array('upper' => 8162, 'status' => 'F', 'lower' => array(965, 776, 768)); /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA */
+$config['1f00_1fff'][] = array('upper' => 8163, 'status' => 'F', 'lower' => array(965, 776, 769)); /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA */
+$config['1f00_1fff'][] = array('upper' => 8164, 'status' => 'F', 'lower' => array(961, 787)); /* GREEK SMALL LETTER RHO WITH PSILI */
+$config['1f00_1fff'][] = array('upper' => 8166, 'status' => 'F', 'lower' => array(965, 834)); /* GREEK SMALL LETTER UPSILON WITH PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8167, 'status' => 'F', 'lower' => array(965, 776, 834)); /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8168, 'status' => 'C', 'lower' => array(8160)); /* GREEK CAPITAL LETTER UPSILON WITH VRACHY */
+$config['1f00_1fff'][] = array('upper' => 8169, 'status' => 'C', 'lower' => array(8161)); /* GREEK CAPITAL LETTER UPSILON WITH MACRON */
+$config['1f00_1fff'][] = array('upper' => 8170, 'status' => 'C', 'lower' => array(8058)); /* GREEK CAPITAL LETTER UPSILON WITH VARIA */
+$config['1f00_1fff'][] = array('upper' => 8171, 'status' => 'C', 'lower' => array(8059)); /* GREEK CAPITAL LETTER UPSILON WITH OXIA */
+$config['1f00_1fff'][] = array('upper' => 8172, 'status' => 'C', 'lower' => array(8165)); /* GREEK CAPITAL LETTER RHO WITH DASIA */
+$config['1f00_1fff'][] = array('upper' => 8178, 'status' => 'F', 'lower' => array(8060, 953)); /* GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8179, 'status' => 'F', 'lower' => array(969, 953)); /* GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8180, 'status' => 'F', 'lower' => array(974, 953)); /* GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8182, 'status' => 'F', 'lower' => array(969, 834)); /* GREEK SMALL LETTER OMEGA WITH PERISPOMENI */
+$config['1f00_1fff'][] = array('upper' => 8183, 'status' => 'F', 'lower' => array(969, 834, 953)); /* GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8184, 'status' => 'C', 'lower' => array(8056)); /* GREEK CAPITAL LETTER OMICRON WITH VARIA */
+$config['1f00_1fff'][] = array('upper' => 8185, 'status' => 'C', 'lower' => array(8057)); /* GREEK CAPITAL LETTER OMICRON WITH OXIA */
+$config['1f00_1fff'][] = array('upper' => 8186, 'status' => 'C', 'lower' => array(8060)); /* GREEK CAPITAL LETTER OMEGA WITH VARIA */
+$config['1f00_1fff'][] = array('upper' => 8187, 'status' => 'C', 'lower' => array(8061)); /* GREEK CAPITAL LETTER OMEGA WITH OXIA */
+$config['1f00_1fff'][] = array('upper' => 8188, 'status' => 'F', 'lower' => array(969, 953)); /* GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI */
+$config['1f00_1fff'][] = array('upper' => 8188, 'status' => 'S', 'lower' => array(8179)); /* GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/2100_214f.php b/cake/config/unicode/casefolding/2100_214f.php
new file mode 100755
index 00000000..42bf2070
--- /dev/null
+++ b/cake/config/unicode/casefolding/2100_214f.php
@@ -0,0 +1,50 @@
+ 8486, 'status' => 'C', 'lower' => array(969)); /* OHM SIGN */
+$config['2100_214f'][] = array('upper' => 8490, 'status' => 'C', 'lower' => array(107)); /* KELVIN SIGN */
+$config['2100_214f'][] = array('upper' => 8491, 'status' => 'C', 'lower' => array(229)); /* ANGSTROM SIGN */
+$config['2100_214f'][] = array('upper' => 8498, 'status' => 'C', 'lower' => array(8526)); /* TURNED CAPITAL F */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/2150_218f.php b/cake/config/unicode/casefolding/2150_218f.php
new file mode 100755
index 00000000..ac4f9d15
--- /dev/null
+++ b/cake/config/unicode/casefolding/2150_218f.php
@@ -0,0 +1,63 @@
+ 8544, 'status' => 'C', 'lower' => array(8560)); /* ROMAN NUMERAL ONE */
+$config['2150_218f'][] = array('upper' => 8545, 'status' => 'C', 'lower' => array(8561)); /* ROMAN NUMERAL TWO */
+$config['2150_218f'][] = array('upper' => 8546, 'status' => 'C', 'lower' => array(8562)); /* ROMAN NUMERAL THREE */
+$config['2150_218f'][] = array('upper' => 8547, 'status' => 'C', 'lower' => array(8563)); /* ROMAN NUMERAL FOUR */
+$config['2150_218f'][] = array('upper' => 8548, 'status' => 'C', 'lower' => array(8564)); /* ROMAN NUMERAL FIVE */
+$config['2150_218f'][] = array('upper' => 8549, 'status' => 'C', 'lower' => array(8565)); /* ROMAN NUMERAL SIX */
+$config['2150_218f'][] = array('upper' => 8550, 'status' => 'C', 'lower' => array(8566)); /* ROMAN NUMERAL SEVEN */
+$config['2150_218f'][] = array('upper' => 8551, 'status' => 'C', 'lower' => array(8567)); /* ROMAN NUMERAL EIGHT */
+$config['2150_218f'][] = array('upper' => 8552, 'status' => 'C', 'lower' => array(8568)); /* ROMAN NUMERAL NINE */
+$config['2150_218f'][] = array('upper' => 8553, 'status' => 'C', 'lower' => array(8569)); /* ROMAN NUMERAL TEN */
+$config['2150_218f'][] = array('upper' => 8554, 'status' => 'C', 'lower' => array(8570)); /* ROMAN NUMERAL ELEVEN */
+$config['2150_218f'][] = array('upper' => 8555, 'status' => 'C', 'lower' => array(8571)); /* ROMAN NUMERAL TWELVE */
+$config['2150_218f'][] = array('upper' => 8556, 'status' => 'C', 'lower' => array(8572)); /* ROMAN NUMERAL FIFTY */
+$config['2150_218f'][] = array('upper' => 8557, 'status' => 'C', 'lower' => array(8573)); /* ROMAN NUMERAL ONE HUNDRED */
+$config['2150_218f'][] = array('upper' => 8558, 'status' => 'C', 'lower' => array(8574)); /* ROMAN NUMERAL FIVE HUNDRED */
+$config['2150_218f'][] = array('upper' => 8559, 'status' => 'C', 'lower' => array(8575)); /* ROMAN NUMERAL ONE THOUSAND */
+$config['2150_218f'][] = array('upper' => 8579, 'status' => 'C', 'lower' => array(8580)); /* ROMAN NUMERAL REVERSED ONE HUNDRED */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/2460_24ff.php b/cake/config/unicode/casefolding/2460_24ff.php
new file mode 100755
index 00000000..9efd3916
--- /dev/null
+++ b/cake/config/unicode/casefolding/2460_24ff.php
@@ -0,0 +1,72 @@
+ 9398, 'status' => 'C', 'lower' => array(9424)); /* CIRCLED LATIN CAPITAL LETTER A */
+$config['2460_24ff'][] = array('upper' => 9399, 'status' => 'C', 'lower' => array(9425)); /* CIRCLED LATIN CAPITAL LETTER B */
+$config['2460_24ff'][] = array('upper' => 9400, 'status' => 'C', 'lower' => array(9426)); /* CIRCLED LATIN CAPITAL LETTER C */
+$config['2460_24ff'][] = array('upper' => 9401, 'status' => 'C', 'lower' => array(9427)); /* CIRCLED LATIN CAPITAL LETTER D */
+$config['2460_24ff'][] = array('upper' => 9402, 'status' => 'C', 'lower' => array(9428)); /* CIRCLED LATIN CAPITAL LETTER E */
+$config['2460_24ff'][] = array('upper' => 9403, 'status' => 'C', 'lower' => array(9429)); /* CIRCLED LATIN CAPITAL LETTER F */
+$config['2460_24ff'][] = array('upper' => 9404, 'status' => 'C', 'lower' => array(9430)); /* CIRCLED LATIN CAPITAL LETTER G */
+$config['2460_24ff'][] = array('upper' => 9405, 'status' => 'C', 'lower' => array(9431)); /* CIRCLED LATIN CAPITAL LETTER H */
+$config['2460_24ff'][] = array('upper' => 9406, 'status' => 'C', 'lower' => array(9432)); /* CIRCLED LATIN CAPITAL LETTER I */
+$config['2460_24ff'][] = array('upper' => 9407, 'status' => 'C', 'lower' => array(9433)); /* CIRCLED LATIN CAPITAL LETTER J */
+$config['2460_24ff'][] = array('upper' => 9408, 'status' => 'C', 'lower' => array(9434)); /* CIRCLED LATIN CAPITAL LETTER K */
+$config['2460_24ff'][] = array('upper' => 9409, 'status' => 'C', 'lower' => array(9435)); /* CIRCLED LATIN CAPITAL LETTER L */
+$config['2460_24ff'][] = array('upper' => 9410, 'status' => 'C', 'lower' => array(9436)); /* CIRCLED LATIN CAPITAL LETTER M */
+$config['2460_24ff'][] = array('upper' => 9411, 'status' => 'C', 'lower' => array(9437)); /* CIRCLED LATIN CAPITAL LETTER N */
+$config['2460_24ff'][] = array('upper' => 9412, 'status' => 'C', 'lower' => array(9438)); /* CIRCLED LATIN CAPITAL LETTER O */
+$config['2460_24ff'][] = array('upper' => 9413, 'status' => 'C', 'lower' => array(9439)); /* CIRCLED LATIN CAPITAL LETTER P */
+$config['2460_24ff'][] = array('upper' => 9414, 'status' => 'C', 'lower' => array(9440)); /* CIRCLED LATIN CAPITAL LETTER Q */
+$config['2460_24ff'][] = array('upper' => 9415, 'status' => 'C', 'lower' => array(9441)); /* CIRCLED LATIN CAPITAL LETTER R */
+$config['2460_24ff'][] = array('upper' => 9416, 'status' => 'C', 'lower' => array(9442)); /* CIRCLED LATIN CAPITAL LETTER S */
+$config['2460_24ff'][] = array('upper' => 9417, 'status' => 'C', 'lower' => array(9443)); /* CIRCLED LATIN CAPITAL LETTER T */
+$config['2460_24ff'][] = array('upper' => 9418, 'status' => 'C', 'lower' => array(9444)); /* CIRCLED LATIN CAPITAL LETTER U */
+$config['2460_24ff'][] = array('upper' => 9419, 'status' => 'C', 'lower' => array(9445)); /* CIRCLED LATIN CAPITAL LETTER V */
+$config['2460_24ff'][] = array('upper' => 9420, 'status' => 'C', 'lower' => array(9446)); /* CIRCLED LATIN CAPITAL LETTER W */
+$config['2460_24ff'][] = array('upper' => 9421, 'status' => 'C', 'lower' => array(9447)); /* CIRCLED LATIN CAPITAL LETTER X */
+$config['2460_24ff'][] = array('upper' => 9422, 'status' => 'C', 'lower' => array(9448)); /* CIRCLED LATIN CAPITAL LETTER Y */
+$config['2460_24ff'][] = array('upper' => 9423, 'status' => 'C', 'lower' => array(9449)); /* CIRCLED LATIN CAPITAL LETTER Z */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/2c00_2c5f.php b/cake/config/unicode/casefolding/2c00_2c5f.php
new file mode 100755
index 00000000..0b00aa57
--- /dev/null
+++ b/cake/config/unicode/casefolding/2c00_2c5f.php
@@ -0,0 +1,93 @@
+ 11264, 'status' => 'C', 'lower' => array(11312)); /* GLAGOLITIC CAPITAL LETTER AZU */
+$config['2c00_2c5f'][] = array('upper' => 11265, 'status' => 'C', 'lower' => array(11313)); /* GLAGOLITIC CAPITAL LETTER BUKY */
+$config['2c00_2c5f'][] = array('upper' => 11266, 'status' => 'C', 'lower' => array(11314)); /* GLAGOLITIC CAPITAL LETTER VEDE */
+$config['2c00_2c5f'][] = array('upper' => 11267, 'status' => 'C', 'lower' => array(11315)); /* GLAGOLITIC CAPITAL LETTER GLAGOLI */
+$config['2c00_2c5f'][] = array('upper' => 11268, 'status' => 'C', 'lower' => array(11316)); /* GLAGOLITIC CAPITAL LETTER DOBRO */
+$config['2c00_2c5f'][] = array('upper' => 11269, 'status' => 'C', 'lower' => array(11317)); /* GLAGOLITIC CAPITAL LETTER YESTU */
+$config['2c00_2c5f'][] = array('upper' => 11270, 'status' => 'C', 'lower' => array(11318)); /* GLAGOLITIC CAPITAL LETTER ZHIVETE */
+$config['2c00_2c5f'][] = array('upper' => 11271, 'status' => 'C', 'lower' => array(11319)); /* GLAGOLITIC CAPITAL LETTER DZELO */
+$config['2c00_2c5f'][] = array('upper' => 11272, 'status' => 'C', 'lower' => array(11320)); /* GLAGOLITIC CAPITAL LETTER ZEMLJA */
+$config['2c00_2c5f'][] = array('upper' => 11273, 'status' => 'C', 'lower' => array(11321)); /* GLAGOLITIC CAPITAL LETTER IZHE */
+$config['2c00_2c5f'][] = array('upper' => 11274, 'status' => 'C', 'lower' => array(11322)); /* GLAGOLITIC CAPITAL LETTER INITIAL IZHE */
+$config['2c00_2c5f'][] = array('upper' => 11275, 'status' => 'C', 'lower' => array(11323)); /* GLAGOLITIC CAPITAL LETTER I */
+$config['2c00_2c5f'][] = array('upper' => 11276, 'status' => 'C', 'lower' => array(11324)); /* GLAGOLITIC CAPITAL LETTER DJERVI */
+$config['2c00_2c5f'][] = array('upper' => 11277, 'status' => 'C', 'lower' => array(11325)); /* GLAGOLITIC CAPITAL LETTER KAKO */
+$config['2c00_2c5f'][] = array('upper' => 11278, 'status' => 'C', 'lower' => array(11326)); /* GLAGOLITIC CAPITAL LETTER LJUDIJE */
+$config['2c00_2c5f'][] = array('upper' => 11279, 'status' => 'C', 'lower' => array(11327)); /* GLAGOLITIC CAPITAL LETTER MYSLITE */
+$config['2c00_2c5f'][] = array('upper' => 11280, 'status' => 'C', 'lower' => array(11328)); /* GLAGOLITIC CAPITAL LETTER NASHI */
+$config['2c00_2c5f'][] = array('upper' => 11281, 'status' => 'C', 'lower' => array(11329)); /* GLAGOLITIC CAPITAL LETTER ONU */
+$config['2c00_2c5f'][] = array('upper' => 11282, 'status' => 'C', 'lower' => array(11330)); /* GLAGOLITIC CAPITAL LETTER POKOJI */
+$config['2c00_2c5f'][] = array('upper' => 11283, 'status' => 'C', 'lower' => array(11331)); /* GLAGOLITIC CAPITAL LETTER RITSI */
+$config['2c00_2c5f'][] = array('upper' => 11284, 'status' => 'C', 'lower' => array(11332)); /* GLAGOLITIC CAPITAL LETTER SLOVO */
+$config['2c00_2c5f'][] = array('upper' => 11285, 'status' => 'C', 'lower' => array(11333)); /* GLAGOLITIC CAPITAL LETTER TVRIDO */
+$config['2c00_2c5f'][] = array('upper' => 11286, 'status' => 'C', 'lower' => array(11334)); /* GLAGOLITIC CAPITAL LETTER UKU */
+$config['2c00_2c5f'][] = array('upper' => 11287, 'status' => 'C', 'lower' => array(11335)); /* GLAGOLITIC CAPITAL LETTER FRITU */
+$config['2c00_2c5f'][] = array('upper' => 11288, 'status' => 'C', 'lower' => array(11336)); /* GLAGOLITIC CAPITAL LETTER HERU */
+$config['2c00_2c5f'][] = array('upper' => 11289, 'status' => 'C', 'lower' => array(11337)); /* GLAGOLITIC CAPITAL LETTER OTU */
+$config['2c00_2c5f'][] = array('upper' => 11290, 'status' => 'C', 'lower' => array(11338)); /* GLAGOLITIC CAPITAL LETTER PE */
+$config['2c00_2c5f'][] = array('upper' => 11291, 'status' => 'C', 'lower' => array(11339)); /* GLAGOLITIC CAPITAL LETTER SHTA */
+$config['2c00_2c5f'][] = array('upper' => 11292, 'status' => 'C', 'lower' => array(11340)); /* GLAGOLITIC CAPITAL LETTER TSI */
+$config['2c00_2c5f'][] = array('upper' => 11293, 'status' => 'C', 'lower' => array(11341)); /* GLAGOLITIC CAPITAL LETTER CHRIVI */
+$config['2c00_2c5f'][] = array('upper' => 11294, 'status' => 'C', 'lower' => array(11342)); /* GLAGOLITIC CAPITAL LETTER SHA */
+$config['2c00_2c5f'][] = array('upper' => 11295, 'status' => 'C', 'lower' => array(11343)); /* GLAGOLITIC CAPITAL LETTER YERU */
+$config['2c00_2c5f'][] = array('upper' => 11296, 'status' => 'C', 'lower' => array(11344)); /* GLAGOLITIC CAPITAL LETTER YERI */
+$config['2c00_2c5f'][] = array('upper' => 11297, 'status' => 'C', 'lower' => array(11345)); /* GLAGOLITIC CAPITAL LETTER YATI */
+$config['2c00_2c5f'][] = array('upper' => 11298, 'status' => 'C', 'lower' => array(11346)); /* GLAGOLITIC CAPITAL LETTER SPIDERY HA */
+$config['2c00_2c5f'][] = array('upper' => 11299, 'status' => 'C', 'lower' => array(11347)); /* GLAGOLITIC CAPITAL LETTER YU */
+$config['2c00_2c5f'][] = array('upper' => 11300, 'status' => 'C', 'lower' => array(11348)); /* GLAGOLITIC CAPITAL LETTER SMALL YUS */
+$config['2c00_2c5f'][] = array('upper' => 11301, 'status' => 'C', 'lower' => array(11349)); /* GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL */
+$config['2c00_2c5f'][] = array('upper' => 11302, 'status' => 'C', 'lower' => array(11350)); /* GLAGOLITIC CAPITAL LETTER YO */
+$config['2c00_2c5f'][] = array('upper' => 11303, 'status' => 'C', 'lower' => array(11351)); /* GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS */
+$config['2c00_2c5f'][] = array('upper' => 11304, 'status' => 'C', 'lower' => array(11352)); /* GLAGOLITIC CAPITAL LETTER BIG YUS */
+$config['2c00_2c5f'][] = array('upper' => 11305, 'status' => 'C', 'lower' => array(11353)); /* GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS */
+$config['2c00_2c5f'][] = array('upper' => 11306, 'status' => 'C', 'lower' => array(11354)); /* GLAGOLITIC CAPITAL LETTER FITA */
+$config['2c00_2c5f'][] = array('upper' => 11307, 'status' => 'C', 'lower' => array(11355)); /* GLAGOLITIC CAPITAL LETTER IZHITSA */
+$config['2c00_2c5f'][] = array('upper' => 11308, 'status' => 'C', 'lower' => array(11356)); /* GLAGOLITIC CAPITAL LETTER SHTAPIC */
+$config['2c00_2c5f'][] = array('upper' => 11309, 'status' => 'C', 'lower' => array(11357)); /* GLAGOLITIC CAPITAL LETTER TROKUTASTI A */
+$config['2c00_2c5f'][] = array('upper' => 11310, 'status' => 'C', 'lower' => array(11358)); /* GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/2c60_2c7f.php b/cake/config/unicode/casefolding/2c60_2c7f.php
new file mode 100755
index 00000000..91dc2e47
--- /dev/null
+++ b/cake/config/unicode/casefolding/2c60_2c7f.php
@@ -0,0 +1,54 @@
+ 11360, 'status' => 'C', 'lower' => array(11361)); /* LATIN CAPITAL LETTER L WITH DOUBLE BAR */
+$config['2c60_2c7f'][] = array('upper' => 11362, 'status' => 'C', 'lower' => array(619)); /* LATIN CAPITAL LETTER L WITH MIDDLE TILDE */
+$config['2c60_2c7f'][] = array('upper' => 11363, 'status' => 'C', 'lower' => array(7549)); /* LATIN CAPITAL LETTER P WITH STROKE */
+$config['2c60_2c7f'][] = array('upper' => 11364, 'status' => 'C', 'lower' => array(637)); /* LATIN CAPITAL LETTER R WITH TAIL */
+$config['2c60_2c7f'][] = array('upper' => 11367, 'status' => 'C', 'lower' => array(11368)); /* LATIN CAPITAL LETTER H WITH DESCENDER */
+$config['2c60_2c7f'][] = array('upper' => 11369, 'status' => 'C', 'lower' => array(11370)); /* LATIN CAPITAL LETTER K WITH DESCENDER */
+$config['2c60_2c7f'][] = array('upper' => 11371, 'status' => 'C', 'lower' => array(11372)); /* LATIN CAPITAL LETTER Z WITH DESCENDER */
+$config['2c60_2c7f'][] = array('upper' => 11381, 'status' => 'C', 'lower' => array(11382)); /* LATIN CAPITAL LETTER HALF H */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/2c80_2cff.php b/cake/config/unicode/casefolding/2c80_2cff.php
new file mode 100755
index 00000000..8f07be27
--- /dev/null
+++ b/cake/config/unicode/casefolding/2c80_2cff.php
@@ -0,0 +1,96 @@
+ 11392, 'status' => 'C', 'lower' => array(11393)); /* COPTIC CAPITAL LETTER ALFA */
+$config['2c80_2cff'][] = array('upper' => 11394, 'status' => 'C', 'lower' => array(11395)); /* COPTIC CAPITAL LETTER VIDA */
+$config['2c80_2cff'][] = array('upper' => 11396, 'status' => 'C', 'lower' => array(11397)); /* COPTIC CAPITAL LETTER GAMMA */
+$config['2c80_2cff'][] = array('upper' => 11398, 'status' => 'C', 'lower' => array(11399)); /* COPTIC CAPITAL LETTER DALDA */
+$config['2c80_2cff'][] = array('upper' => 11400, 'status' => 'C', 'lower' => array(11401)); /* COPTIC CAPITAL LETTER EIE */
+$config['2c80_2cff'][] = array('upper' => 11402, 'status' => 'C', 'lower' => array(11403)); /* COPTIC CAPITAL LETTER SOU */
+$config['2c80_2cff'][] = array('upper' => 11404, 'status' => 'C', 'lower' => array(11405)); /* COPTIC CAPITAL LETTER ZATA */
+$config['2c80_2cff'][] = array('upper' => 11406, 'status' => 'C', 'lower' => array(11407)); /* COPTIC CAPITAL LETTER HATE */
+$config['2c80_2cff'][] = array('upper' => 11408, 'status' => 'C', 'lower' => array(11409)); /* COPTIC CAPITAL LETTER THETHE */
+$config['2c80_2cff'][] = array('upper' => 11410, 'status' => 'C', 'lower' => array(11411)); /* COPTIC CAPITAL LETTER IAUDA */
+$config['2c80_2cff'][] = array('upper' => 11412, 'status' => 'C', 'lower' => array(11413)); /* COPTIC CAPITAL LETTER KAPA */
+$config['2c80_2cff'][] = array('upper' => 11414, 'status' => 'C', 'lower' => array(11415)); /* COPTIC CAPITAL LETTER LAULA */
+$config['2c80_2cff'][] = array('upper' => 11416, 'status' => 'C', 'lower' => array(11417)); /* COPTIC CAPITAL LETTER MI */
+$config['2c80_2cff'][] = array('upper' => 11418, 'status' => 'C', 'lower' => array(11419)); /* COPTIC CAPITAL LETTER NI */
+$config['2c80_2cff'][] = array('upper' => 11420, 'status' => 'C', 'lower' => array(11421)); /* COPTIC CAPITAL LETTER KSI */
+$config['2c80_2cff'][] = array('upper' => 11422, 'status' => 'C', 'lower' => array(11423)); /* COPTIC CAPITAL LETTER O */
+$config['2c80_2cff'][] = array('upper' => 11424, 'status' => 'C', 'lower' => array(11425)); /* COPTIC CAPITAL LETTER PI */
+$config['2c80_2cff'][] = array('upper' => 11426, 'status' => 'C', 'lower' => array(11427)); /* COPTIC CAPITAL LETTER RO */
+$config['2c80_2cff'][] = array('upper' => 11428, 'status' => 'C', 'lower' => array(11429)); /* COPTIC CAPITAL LETTER SIMA */
+$config['2c80_2cff'][] = array('upper' => 11430, 'status' => 'C', 'lower' => array(11431)); /* COPTIC CAPITAL LETTER TAU */
+$config['2c80_2cff'][] = array('upper' => 11432, 'status' => 'C', 'lower' => array(11433)); /* COPTIC CAPITAL LETTER UA */
+$config['2c80_2cff'][] = array('upper' => 11434, 'status' => 'C', 'lower' => array(11435)); /* COPTIC CAPITAL LETTER FI */
+$config['2c80_2cff'][] = array('upper' => 11436, 'status' => 'C', 'lower' => array(11437)); /* COPTIC CAPITAL LETTER KHI */
+$config['2c80_2cff'][] = array('upper' => 11438, 'status' => 'C', 'lower' => array(11439)); /* COPTIC CAPITAL LETTER PSI */
+$config['2c80_2cff'][] = array('upper' => 11440, 'status' => 'C', 'lower' => array(11441)); /* COPTIC CAPITAL LETTER OOU */
+$config['2c80_2cff'][] = array('upper' => 11442, 'status' => 'C', 'lower' => array(11443)); /* COPTIC CAPITAL LETTER DIALECT-P ALEF */
+$config['2c80_2cff'][] = array('upper' => 11444, 'status' => 'C', 'lower' => array(11445)); /* COPTIC CAPITAL LETTER OLD COPTIC AIN */
+$config['2c80_2cff'][] = array('upper' => 11446, 'status' => 'C', 'lower' => array(11447)); /* COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE */
+$config['2c80_2cff'][] = array('upper' => 11448, 'status' => 'C', 'lower' => array(11449)); /* COPTIC CAPITAL LETTER DIALECT-P KAPA */
+$config['2c80_2cff'][] = array('upper' => 11450, 'status' => 'C', 'lower' => array(11451)); /* COPTIC CAPITAL LETTER DIALECT-P NI */
+$config['2c80_2cff'][] = array('upper' => 11452, 'status' => 'C', 'lower' => array(11453)); /* COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI */
+$config['2c80_2cff'][] = array('upper' => 11454, 'status' => 'C', 'lower' => array(11455)); /* COPTIC CAPITAL LETTER OLD COPTIC OOU */
+$config['2c80_2cff'][] = array('upper' => 11456, 'status' => 'C', 'lower' => array(11457)); /* COPTIC CAPITAL LETTER SAMPI */
+$config['2c80_2cff'][] = array('upper' => 11458, 'status' => 'C', 'lower' => array(11459)); /* COPTIC CAPITAL LETTER CROSSED SHEI */
+$config['2c80_2cff'][] = array('upper' => 11460, 'status' => 'C', 'lower' => array(11461)); /* COPTIC CAPITAL LETTER OLD COPTIC SHEI */
+$config['2c80_2cff'][] = array('upper' => 11462, 'status' => 'C', 'lower' => array(11463)); /* COPTIC CAPITAL LETTER OLD COPTIC ESH */
+$config['2c80_2cff'][] = array('upper' => 11464, 'status' => 'C', 'lower' => array(11465)); /* COPTIC CAPITAL LETTER AKHMIMIC KHEI */
+$config['2c80_2cff'][] = array('upper' => 11466, 'status' => 'C', 'lower' => array(11467)); /* COPTIC CAPITAL LETTER DIALECT-P HORI */
+$config['2c80_2cff'][] = array('upper' => 11468, 'status' => 'C', 'lower' => array(11469)); /* COPTIC CAPITAL LETTER OLD COPTIC HORI */
+$config['2c80_2cff'][] = array('upper' => 11470, 'status' => 'C', 'lower' => array(11471)); /* COPTIC CAPITAL LETTER OLD COPTIC HA */
+$config['2c80_2cff'][] = array('upper' => 11472, 'status' => 'C', 'lower' => array(11473)); /* COPTIC CAPITAL LETTER L-SHAPED HA */
+$config['2c80_2cff'][] = array('upper' => 11474, 'status' => 'C', 'lower' => array(11475)); /* COPTIC CAPITAL LETTER OLD COPTIC HEI */
+$config['2c80_2cff'][] = array('upper' => 11476, 'status' => 'C', 'lower' => array(11477)); /* COPTIC CAPITAL LETTER OLD COPTIC HAT */
+$config['2c80_2cff'][] = array('upper' => 11478, 'status' => 'C', 'lower' => array(11479)); /* COPTIC CAPITAL LETTER OLD COPTIC GANGIA */
+$config['2c80_2cff'][] = array('upper' => 11480, 'status' => 'C', 'lower' => array(11481)); /* COPTIC CAPITAL LETTER OLD COPTIC DJA */
+$config['2c80_2cff'][] = array('upper' => 11482, 'status' => 'C', 'lower' => array(11483)); /* COPTIC CAPITAL LETTER OLD COPTIC SHIMA */
+$config['2c80_2cff'][] = array('upper' => 11484, 'status' => 'C', 'lower' => array(11485)); /* COPTIC CAPITAL LETTER OLD NUBIAN SHIMA */
+$config['2c80_2cff'][] = array('upper' => 11486, 'status' => 'C', 'lower' => array(11487)); /* COPTIC CAPITAL LETTER OLD NUBIAN NGI */
+$config['2c80_2cff'][] = array('upper' => 11488, 'status' => 'C', 'lower' => array(11489)); /* COPTIC CAPITAL LETTER OLD NUBIAN NYI */
+$config['2c80_2cff'][] = array('upper' => 11490, 'status' => 'C', 'lower' => array(11491)); /* COPTIC CAPITAL LETTER OLD NUBIAN WAU */
+?>
\ No newline at end of file
diff --git a/cake/config/unicode/casefolding/ff00_ffef.php b/cake/config/unicode/casefolding/ff00_ffef.php
new file mode 100755
index 00000000..ddcd35e4
--- /dev/null
+++ b/cake/config/unicode/casefolding/ff00_ffef.php
@@ -0,0 +1,72 @@
+ 65313, 'status' => 'C', 'lower' => array(65345)); /* FULLWIDTH LATIN CAPITAL LETTER A */
+$config['ff00_ffef'][] = array('upper' => 65314, 'status' => 'C', 'lower' => array(65346)); /* FULLWIDTH LATIN CAPITAL LETTER B */
+$config['ff00_ffef'][] = array('upper' => 65315, 'status' => 'C', 'lower' => array(65347)); /* FULLWIDTH LATIN CAPITAL LETTER C */
+$config['ff00_ffef'][] = array('upper' => 65316, 'status' => 'C', 'lower' => array(65348)); /* FULLWIDTH LATIN CAPITAL LETTER D */
+$config['ff00_ffef'][] = array('upper' => 65317, 'status' => 'C', 'lower' => array(65349)); /* FULLWIDTH LATIN CAPITAL LETTER E */
+$config['ff00_ffef'][] = array('upper' => 65318, 'status' => 'C', 'lower' => array(65350)); /* FULLWIDTH LATIN CAPITAL LETTER F */
+$config['ff00_ffef'][] = array('upper' => 65319, 'status' => 'C', 'lower' => array(65351)); /* FULLWIDTH LATIN CAPITAL LETTER G */
+$config['ff00_ffef'][] = array('upper' => 65320, 'status' => 'C', 'lower' => array(65352)); /* FULLWIDTH LATIN CAPITAL LETTER H */
+$config['ff00_ffef'][] = array('upper' => 65321, 'status' => 'C', 'lower' => array(65353)); /* FULLWIDTH LATIN CAPITAL LETTER I */
+$config['ff00_ffef'][] = array('upper' => 65322, 'status' => 'C', 'lower' => array(65354)); /* FULLWIDTH LATIN CAPITAL LETTER J */
+$config['ff00_ffef'][] = array('upper' => 65323, 'status' => 'C', 'lower' => array(65355)); /* FULLWIDTH LATIN CAPITAL LETTER K */
+$config['ff00_ffef'][] = array('upper' => 65324, 'status' => 'C', 'lower' => array(65356)); /* FULLWIDTH LATIN CAPITAL LETTER L */
+$config['ff00_ffef'][] = array('upper' => 65325, 'status' => 'C', 'lower' => array(65357)); /* FULLWIDTH LATIN CAPITAL LETTER M */
+$config['ff00_ffef'][] = array('upper' => 65326, 'status' => 'C', 'lower' => array(65358)); /* FULLWIDTH LATIN CAPITAL LETTER N */
+$config['ff00_ffef'][] = array('upper' => 65327, 'status' => 'C', 'lower' => array(65359)); /* FULLWIDTH LATIN CAPITAL LETTER O */
+$config['ff00_ffef'][] = array('upper' => 65328, 'status' => 'C', 'lower' => array(65360)); /* FULLWIDTH LATIN CAPITAL LETTER P */
+$config['ff00_ffef'][] = array('upper' => 65329, 'status' => 'C', 'lower' => array(65361)); /* FULLWIDTH LATIN CAPITAL LETTER Q */
+$config['ff00_ffef'][] = array('upper' => 65330, 'status' => 'C', 'lower' => array(65362)); /* FULLWIDTH LATIN CAPITAL LETTER R */
+$config['ff00_ffef'][] = array('upper' => 65331, 'status' => 'C', 'lower' => array(65363)); /* FULLWIDTH LATIN CAPITAL LETTER S */
+$config['ff00_ffef'][] = array('upper' => 65332, 'status' => 'C', 'lower' => array(65364)); /* FULLWIDTH LATIN CAPITAL LETTER T */
+$config['ff00_ffef'][] = array('upper' => 65333, 'status' => 'C', 'lower' => array(65365)); /* FULLWIDTH LATIN CAPITAL LETTER U */
+$config['ff00_ffef'][] = array('upper' => 65334, 'status' => 'C', 'lower' => array(65366)); /* FULLWIDTH LATIN CAPITAL LETTER V */
+$config['ff00_ffef'][] = array('upper' => 65335, 'status' => 'C', 'lower' => array(65367)); /* FULLWIDTH LATIN CAPITAL LETTER W */
+$config['ff00_ffef'][] = array('upper' => 65336, 'status' => 'C', 'lower' => array(65368)); /* FULLWIDTH LATIN CAPITAL LETTER X */
+$config['ff00_ffef'][] = array('upper' => 65337, 'status' => 'C', 'lower' => array(65369)); /* FULLWIDTH LATIN CAPITAL LETTER Y */
+$config['ff00_ffef'][] = array('upper' => 65338, 'status' => 'C', 'lower' => array(65370)); /* FULLWIDTH LATIN CAPITAL LETTER Z */
+?>
\ No newline at end of file
diff --git a/cake/console/cake b/cake/console/cake
new file mode 100755
index 00000000..1257251d
--- /dev/null
+++ b/cake/console/cake
@@ -0,0 +1,32 @@
+#!/bin/bash
+################################################################################
+#
+# Bake is a shell script for running CakePHP bake script
+# PHP versions 4 and 5
+#
+# CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org)
+# Copyright 2005-2007, Cake Software Foundation, Inc.
+#
+# Licensed under The MIT License
+# Redistributions of files must retain the above copyright notice.
+#
+# @filesource
+# @copyright Copyright 2005-2007, Cake Software Foundation, Inc.
+# @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+# @package cake
+# @subpackage cake.cake.console
+# @since CakePHP(tm) v 1.2.0.5012
+# @version $Revision$
+# @modifiedby $LastChangedBy$
+# @lastmodified $Date$
+# @license http://www.opensource.org/licenses/mit-license.php The MIT License
+#
+################################################################################
+clear
+
+LIB=${0/%cake/}
+APP=`pwd`
+
+exec php -q ${LIB}cake.php -working "${APP}" "$@"
+
+exit;
\ No newline at end of file
diff --git a/cake/console/cake.bat b/cake/console/cake.bat
new file mode 100755
index 00000000..c0a531c8
--- /dev/null
+++ b/cake/console/cake.bat
@@ -0,0 +1,35 @@
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::
+:: Bake is a shell script for running CakePHP bake script
+:: PHP versions 4 and 5
+::
+:: CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org)
+:: Copyright 2005-2007, Cake Software Foundation, Inc.
+::
+:: Licensed under The MIT License
+:: Redistributions of files must retain the above copyright notice.
+::
+:: @filesource
+:: @copyright Copyright 2005-2007, Cake Software Foundation, Inc.
+:: @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+:: @package cake
+:: @subpackage cake.cake.console
+:: @since CakePHP(tm) v 1.2.0.5012
+:: @version $Revision$
+:: @modifiedby $LastChangedBy$
+:: @lastmodified $Date$
+:: @license http://www.opensource.org/licenses/mit-license.php The MIT License
+::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+:: In order for this script to work as intended, the cake\console\ folder must be in your PATH
+
+@echo.
+@echo off
+
+SET app=%0
+SET lib=%~dp0
+
+php -q "%lib%cake.php" -working "%CD%" %*
+
+echo.
\ No newline at end of file
diff --git a/cake/console/cake.php b/cake/console/cake.php
new file mode 100755
index 00000000..2210beec
--- /dev/null
+++ b/cake/console/cake.php
@@ -0,0 +1,592 @@
+#!/usr/bin/php -q
+__construct($args);
+ }
+/**
+ * Constructor
+ *
+ * @param array $args the argv.
+ */
+ function __construct($args = array()) {
+ set_time_limit(0);
+ $this->__initConstants();
+ $this->parseParams($args);
+ $this->_initEnvironment();
+ $this->__buildPaths();
+ $this->_stop($this->dispatch());
+ }
+/**
+ * Defines core configuration.
+ *
+ * @access private
+ */
+ function __initConstants() {
+ if (function_exists('ini_set')) {
+ ini_set('display_errors', '1');
+ ini_set('error_reporting', E_ALL & ~E_DEPRECATED);
+ ini_set('html_errors', false);
+ ini_set('implicit_flush', true);
+ ini_set('max_execution_time', 0);
+ }
+
+ if (!defined('CAKE_CORE_INCLUDE_PATH')) {
+ define('PHP5', (PHP_VERSION >= 5));
+ define('DS', DIRECTORY_SEPARATOR);
+ define('CAKE_CORE_INCLUDE_PATH', dirname(dirname(dirname(__FILE__))));
+ define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS);
+ define('DISABLE_DEFAULT_ERROR_HANDLING', false);
+ define('CAKEPHP_SHELL', true);
+ }
+ require_once(CORE_PATH . 'cake' . DS . 'basics.php');
+ }
+/**
+ * Defines current working environment.
+ *
+ * @access protected
+ */
+ function _initEnvironment() {
+ $this->stdin = fopen('php://stdin', 'r');
+ $this->stdout = fopen('php://stdout', 'w');
+ $this->stderr = fopen('php://stderr', 'w');
+
+ if (!$this->__bootstrap()) {
+ $this->stderr("\nCakePHP Console: ");
+ $this->stderr("\nUnable to load Cake core:");
+ $this->stderr("\tMake sure " . DS . 'cake' . DS . 'libs exists in ' . CAKE_CORE_INCLUDE_PATH);
+ $this->_stop();
+ }
+
+ if (!isset($this->args[0]) || !isset($this->params['working'])) {
+ $this->stderr("\nCakePHP Console: ");
+ $this->stderr('This file has been loaded incorrectly and cannot continue.');
+ $this->stderr('Please make sure that ' . DIRECTORY_SEPARATOR . 'cake' . DIRECTORY_SEPARATOR . 'console is in your system path,');
+ $this->stderr('and check the manual for the correct usage of this command.');
+ $this->stderr('(http://manual.cakephp.org/)');
+ $this->_stop();
+ }
+
+ if (basename(__FILE__) != basename($this->args[0])) {
+ $this->stderr("\nCakePHP Console: ");
+ $this->stderr('Warning: the dispatcher may have been loaded incorrectly, which could lead to unexpected results...');
+ if ($this->getInput('Continue anyway?', array('y', 'n'), 'y') == 'n') {
+ $this->_stop();
+ }
+ }
+
+ $this->shiftArgs();
+ }
+/**
+ * Builds the shell paths.
+ *
+ * @access private
+ * @return void
+ */
+ function __buildPaths() {
+ $paths = array();
+ $pluginPaths = Configure::read('pluginPaths');
+ if (!class_exists('Folder')) {
+ require LIBS . 'folder.php';
+ }
+
+ foreach ($pluginPaths as $pluginPath) {
+ $Folder = new Folder($pluginPath);
+ list($plugins,) = $Folder->read(false, true);
+ foreach ((array)$plugins as $plugin) {
+ $path = $pluginPath . Inflector::underscore($plugin) . DS . 'vendors' . DS . 'shells' . DS;
+ if (file_exists($path)) {
+ $paths[] = $path;
+ }
+ }
+ }
+
+ $vendorPaths = array_values(Configure::read('vendorPaths'));
+ foreach ($vendorPaths as $vendorPath) {
+ $path = rtrim($vendorPath, DS) . DS . 'shells' . DS;
+ if (file_exists($path)) {
+ $paths[] = $path;
+ }
+ }
+
+ $this->shellPaths = array_values(array_unique(array_merge($paths, Configure::read('shellPaths'))));
+ }
+/**
+ * Initializes the environment and loads the Cake core.
+ *
+ * @return boolean Success.
+ * @access private
+ */
+ function __bootstrap() {
+
+ define('ROOT', $this->params['root']);
+ define('APP_DIR', $this->params['app']);
+ define('APP_PATH', $this->params['working'] . DS);
+ define('WWW_ROOT', APP_PATH . $this->params['webroot'] . DS);
+
+ $includes = array(
+ CORE_PATH . 'cake' . DS . 'config' . DS . 'paths.php',
+ CORE_PATH . 'cake' . DS . 'libs' . DS . 'object.php',
+ CORE_PATH . 'cake' . DS . 'libs' . DS . 'inflector.php',
+ CORE_PATH . 'cake' . DS . 'libs' . DS . 'configure.php',
+ CORE_PATH . 'cake' . DS . 'libs' . DS . 'file.php',
+ CORE_PATH . 'cake' . DS . 'libs' . DS . 'cache.php',
+ CORE_PATH . 'cake' . DS . 'libs' . DS . 'string.php',
+ CORE_PATH . 'cake' . DS . 'libs' . DS . 'class_registry.php',
+ CORE_PATH . 'cake' . DS . 'console' . DS . 'error.php'
+ );
+
+ foreach ($includes as $inc) {
+ if (!require($inc)) {
+ $this->stderr("Failed to load Cake core file {$inc}");
+ return false;
+ }
+ }
+
+ Configure::getInstance(file_exists(CONFIGS . 'bootstrap.php'));
+
+ if (!file_exists(APP_PATH . 'config' . DS . 'core.php')) {
+ include_once CORE_PATH . 'cake' . DS . 'console' . DS . 'libs' . DS . 'templates' . DS . 'skel' . DS . 'config' . DS . 'core.php';
+ Configure::buildPaths(array());
+ }
+
+ Configure::write('debug', 1);
+ return true;
+ }
+/**
+ * Dispatches a CLI request
+ *
+ * @access public
+ */
+ function dispatch() {
+ if (isset($this->args[0])) {
+ $plugin = null;
+ $shell = $this->args[0];
+ if (strpos($shell, '.') !== false) {
+ list($plugin, $shell) = explode('.', $this->args[0]);
+ }
+
+ $this->shell = $shell;
+ $this->shiftArgs();
+ $this->shellName = Inflector::camelize($this->shell);
+ $this->shellClass = $this->shellName . 'Shell';
+
+ if ($this->shell === 'help') {
+ $this->help();
+ } else {
+ $loaded = false;
+ foreach ($this->shellPaths as $path) {
+ $this->shellPath = $path . $this->shell . '.php';
+
+ $isPlugin = ($plugin && strpos($path, DS . $plugin . DS . 'vendors' . DS . 'shells' . DS) !== false);
+ if (($isPlugin && file_exists($this->shellPath)) || (!$plugin && file_exists($this->shellPath))) {
+ $loaded = true;
+ break;
+ }
+ }
+
+ if ($loaded) {
+ if (!class_exists('Shell')) {
+ require CONSOLE_LIBS . 'shell.php';
+ }
+ require $this->shellPath;
+ if (class_exists($this->shellClass)) {
+ $command = null;
+ if (isset($this->args[0])) {
+ $command = $this->args[0];
+ }
+ $this->shellCommand = Inflector::variable($command);
+ $shell = new $this->shellClass($this);
+
+ if (strtolower(get_parent_class($shell)) == 'shell') {
+ $shell->initialize();
+ $shell->loadTasks();
+
+ foreach ($shell->taskNames as $task) {
+ if (strtolower(get_parent_class($shell)) == 'shell') {
+ $shell->{$task}->initialize();
+ $shell->{$task}->loadTasks();
+ }
+ }
+
+ $task = Inflector::camelize($command);
+ if (in_array($task, $shell->taskNames)) {
+ $this->shiftArgs();
+ $shell->{$task}->startup();
+ if (isset($this->args[0]) && $this->args[0] == 'help') {
+ if (method_exists($shell->{$task}, 'help')) {
+ $shell->{$task}->help();
+ $this->_stop();
+ } else {
+ $this->help();
+ }
+ }
+ return $shell->{$task}->execute();
+ }
+ }
+
+ $classMethods = get_class_methods($shell);
+
+ $privateMethod = $missingCommand = false;
+ if ((in_array($command, $classMethods) || in_array(strtolower($command), $classMethods)) && strpos($command, '_', 0) === 0) {
+ $privateMethod = true;
+ }
+
+ if (!in_array($command, $classMethods) && !in_array(strtolower($command), $classMethods)) {
+ $missingCommand = true;
+ }
+
+ $protectedCommands = array(
+ 'initialize','in','out','err','hr',
+ 'createfile', 'isdir','copydir','object','tostring',
+ 'requestaction','log','cakeerror', 'shelldispatcher',
+ '__initconstants','__initenvironment','__construct',
+ 'dispatch','__bootstrap','getinput','stdout','stderr','parseparams','shiftargs'
+ );
+
+ if (in_array(strtolower($command), $protectedCommands)) {
+ $missingCommand = true;
+ }
+
+ if ($missingCommand && method_exists($shell, 'main')) {
+ $shell->startup();
+ return $shell->main();
+ } elseif (!$privateMethod && method_exists($shell, $command)) {
+ $this->shiftArgs();
+ $shell->startup();
+ return $shell->{$command}();
+ } else {
+ $this->stderr("Unknown {$this->shellName} command '$command'.\nFor usage, try 'cake {$this->shell} help'.\n\n");
+ }
+ } else {
+ $this->stderr('Class '.$this->shellClass.' could not be loaded');
+ }
+ } else {
+ $this->help();
+ }
+ }
+ } else {
+ $this->help();
+ }
+ }
+/**
+ * Prompts the user for input, and returns it.
+ *
+ * @param string $prompt Prompt text.
+ * @param mixed $options Array or string of options.
+ * @param string $default Default input value.
+ * @return Either the default value, or the user-provided input.
+ * @access public
+ */
+ function getInput($prompt, $options = null, $default = null) {
+ if (!is_array($options)) {
+ $printOptions = '';
+ } else {
+ $printOptions = '(' . implode('/', $options) . ')';
+ }
+
+ if ($default == null) {
+ $this->stdout($prompt . " $printOptions \n" . '> ', false);
+ } else {
+ $this->stdout($prompt . " $printOptions \n" . "[$default] > ", false);
+ }
+ $result = fgets($this->stdin);
+
+ if ($result === false) {
+ exit;
+ }
+ $result = trim($result);
+
+ if ($default != null && empty($result)) {
+ return $default;
+ }
+ return $result;
+ }
+/**
+ * Outputs to the stdout filehandle.
+ *
+ * @param string $string String to output.
+ * @param boolean $newline If true, the outputs gets an added newline.
+ * @access public
+ */
+ function stdout($string, $newline = true) {
+ if ($newline) {
+ fwrite($this->stdout, $string . "\n");
+ } else {
+ fwrite($this->stdout, $string);
+ }
+ }
+/**
+ * Outputs to the stderr filehandle.
+ *
+ * @param string $string Error text to output.
+ * @access public
+ */
+ function stderr($string) {
+ fwrite($this->stderr, 'Error: '. $string);
+ }
+/**
+ * Parses command line options
+ *
+ * @param array $params Parameters to parse
+ * @access public
+ */
+ function parseParams($params) {
+ $this->__parseParams($params);
+ $defaults = array('app' => 'app', 'root' => dirname(dirname(dirname(__FILE__))), 'working' => null, 'webroot' => 'webroot');
+ $params = array_merge($defaults, array_intersect_key($this->params, $defaults));
+ $isWin = false;
+ foreach ($defaults as $default => $value) {
+ if (strpos($params[$default], '\\') !== false) {
+ $isWin = true;
+ break;
+ }
+ }
+ $params = str_replace('\\', '/', $params);
+
+ if (!empty($params['working']) && (!isset($this->args[0]) || isset($this->args[0]) && $this->args[0]{0} !== '.')) {
+ if (empty($this->params['app']) && $params['working'] != $params['root']) {
+ $params['root'] = dirname($params['working']);
+ $params['app'] = basename($params['working']);
+ } else {
+ $params['root'] = $params['working'];
+ }
+ }
+
+ if ($params['app'][0] == '/' || preg_match('/([a-z])(:)/i', $params['app'], $matches)) {
+ $params['root'] = dirname($params['app']);
+ } elseif (strpos($params['app'], '/')) {
+ $params['root'] .= '/' . dirname($params['app']);
+ }
+
+ $params['app'] = basename($params['app']);
+ $params['working'] = rtrim($params['root'], '/') . '/' . $params['app'];
+
+ if (!empty($matches[0]) || !empty($isWin)) {
+ $params = str_replace('/', '\\', $params);
+ }
+
+ $this->params = array_merge($this->params, $params);
+ }
+/**
+ * Helper for recursively paraing params
+ *
+ * @return array params
+ * @access private
+ */
+ function __parseParams($params) {
+ $count = count($params);
+ for ($i = 0; $i < $count; $i++) {
+ if (isset($params[$i])) {
+ if ($params[$i]{0} === '-') {
+ $key = substr($params[$i], 1);
+ $this->params[$key] = true;
+ unset($params[$i]);
+ if (isset($params[++$i])) {
+ if ($params[$i]{0} !== '-') {
+ $this->params[$key] = str_replace('"', '', $params[$i]);
+ unset($params[$i]);
+ } else {
+ $i--;
+ $this->__parseParams($params);
+ }
+ }
+ } else {
+ $this->args[] = $params[$i];
+ unset($params[$i]);
+ }
+
+ }
+ }
+ }
+/**
+ * Removes first argument and shifts other arguments up
+ *
+ * @return boolean False if there are no arguments
+ * @access public
+ */
+ function shiftArgs() {
+ if (empty($this->args)) {
+ return false;
+ }
+ unset($this->args[0]);
+ $this->args = array_values($this->args);
+ return true;
+ }
+/**
+ * Shows console help
+ *
+ * @access public
+ */
+ function help() {
+ $this->stdout("\nWelcome to CakePHP v" . Configure::version() . " Console");
+ $this->stdout("---------------------------------------------------------------");
+ $this->stdout("Current Paths:");
+ $this->stdout(" -app: ". $this->params['app']);
+ $this->stdout(" -working: " . rtrim($this->params['working'], DS));
+ $this->stdout(" -root: " . rtrim($this->params['root'], DS));
+ $this->stdout(" -core: " . rtrim(CORE_PATH, DS));
+ $this->stdout("");
+ $this->stdout("Changing Paths:");
+ $this->stdout("your working path should be the same as your application path");
+ $this->stdout("to change your path use the '-app' param.");
+ $this->stdout("Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp");
+
+ $this->stdout("\nAvailable Shells:");
+ $_shells = array();
+
+ foreach ($this->shellPaths as $path) {
+ if (is_dir($path)) {
+ $shells = Configure::listObjects('file', $path);
+ $path = str_replace(CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS, 'CORE' . DS, $path);
+ $path = str_replace(APP, 'APP' . DS, $path);
+ $path = str_replace(ROOT, 'ROOT', $path);
+ $path = rtrim($path, DS);
+ $this->stdout("\n " . $path . ":");
+ if (empty($shells)) {
+ $this->stdout("\t - none");
+ } else {
+ sort($shells);
+ foreach ($shells as $shell) {
+ if ($shell !== 'shell.php') {
+ $this->stdout("\t " . str_replace('.php', '', $shell));
+ }
+ }
+ }
+ }
+ }
+ $this->stdout("\nTo run a command, type 'cake shell_name [args]'");
+ $this->stdout("To get help on a specific command, type 'cake shell_name help'");
+ $this->_stop();
+ }
+/**
+ * Stop execution of the current script
+ *
+ * @param $status see http://php.net/exit for values
+ * @return void
+ * @access protected
+ */
+ function _stop($status = 0) {
+ exit($status);
+ }
+}
+if (!defined('DISABLE_AUTO_DISPATCH')) {
+ $dispatcher = new ShellDispatcher($argv);
+}
+?>
\ No newline at end of file
diff --git a/cake/console/error.php b/cake/console/error.php
new file mode 100755
index 00000000..9c88a40b
--- /dev/null
+++ b/cake/console/error.php
@@ -0,0 +1,254 @@
+stdout = fopen('php://stdout', 'w');
+ $this->stderr = fopen('php://stderr', 'w');
+ if (Configure::read() > 0 || $method == 'error') {
+ call_user_func_array(array(&$this, $method), $messages);
+ } else {
+ call_user_func_array(array(&$this, 'error404'), $messages);
+ }
+ }
+/**
+ * Displays an error page (e.g. 404 Not found).
+ *
+ * @param array $params Parameters (code, name, and message)
+ * @access public
+ */
+ function error($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr($code . $name . $message."\n");
+ $this->_stop();
+ }
+/**
+ * Convenience method to display a 404 page.
+ *
+ * @param array $params Parameters (url, message)
+ * @access public
+ */
+ function error404($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->error(array('code' => '404',
+ 'name' => 'Not found',
+ 'message' => sprintf(__("The requested address %s was not found on this server.", true), $url, $message)));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Controller web page.
+ *
+ * @param array $params Parameters (className)
+ * @access public
+ */
+ function missingController($params) {
+ extract($params, EXTR_OVERWRITE);
+ $controllerName = str_replace('Controller', '', $className);
+ $this->stderr(sprintf(__("Missing Controller '%s'", true), $controllerName));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Action web page.
+ *
+ * @param array $params Parameters (action, className)
+ * @access public
+ */
+ function missingAction($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing Method '%s' in '%s'", true), $action, $className));
+ $this->_stop();
+ }
+/**
+ * Renders the Private Action web page.
+ *
+ * @param array $params Parameters (action, className)
+ * @access public
+ */
+ function privateAction($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Trying to access private method '%s' in '%s'", true), $action, $className));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Table web page.
+ *
+ * @param array $params Parameters (table, className)
+ * @access public
+ */
+ function missingTable($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing database table '%s' for model '%s'", true), $table, $className));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Database web page.
+ *
+ * @param array $params Parameters
+ * @access public
+ */
+ function missingDatabase($params = array()) {
+ $this->stderr(__("Missing Database", true));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing View web page.
+ *
+ * @param array $params Parameters (file, action, className)
+ * @access public
+ */
+ function missingView($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing View '%s' for '%s' in '%s'", true), $file, $action, $className));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Layout web page.
+ *
+ * @param array $params Parameters (file)
+ * @access public
+ */
+ function missingLayout($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing Layout '%s'", true), $file));
+ $this->_stop();
+ }
+/**
+ * Renders the Database Connection web page.
+ *
+ * @param array $params Parameters
+ * @access public
+ */
+ function missingConnection($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(__("Missing Database Connection. Try 'cake bake'", true));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Helper file web page.
+ *
+ * @param array $params Parameters (file, helper)
+ * @access public
+ */
+ function missingHelperFile($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing Helper file '%s' for '%s'", true), $file, Inflector::camelize($helper)));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Helper class web page.
+ *
+ * @param array $params Parameters (file, helper)
+ * @access public
+ */
+ function missingHelperClass($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing Helper class '%s' in '%s'", true), Inflector::camelize($helper), $file));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Component file web page.
+ *
+ * @param array $params Parameters (file, component)
+ * @access public
+ */
+ function missingComponentFile($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing Component file '%s' for '%s'", true), $file, Inflector::camelize($component)));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Component class web page.
+ *
+ * @param array $params Parameters (file, component)
+ * @access public
+ */
+ function missingComponentClass($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing Component class '%s' in '%s'", true), Inflector::camelize($component), $file));
+ $this->_stop();
+ }
+/**
+ * Renders the Missing Model class web page.
+ *
+ * @param array $params Parameters (className)
+ * @access public
+ */
+ function missingModel($params) {
+ extract($params, EXTR_OVERWRITE);
+ $this->stderr(sprintf(__("Missing model '%s'", true), $className));
+ $this->_stop();
+ }
+/**
+ * Outputs to the stdout filehandle.
+ *
+ * @param string $string String to output.
+ * @param boolean $newline If true, the outputs gets an added newline.
+ * @access public
+ */
+ function stdout($string, $newline = true) {
+ if ($newline) {
+ fwrite($this->stdout, $string . "\n");
+ } else {
+ fwrite($this->stdout, $string);
+ }
+ }
+/**
+ * Outputs to the stderr filehandle.
+ *
+ * @param string $string Error text to output.
+ * @access public
+ */
+ function stderr($string) {
+ fwrite($this->stderr, "Error: ". $string . "\n");
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/acl.php b/cake/console/libs/acl.php
new file mode 100755
index 00000000..5a7873da
--- /dev/null
+++ b/cake/console/libs/acl.php
@@ -0,0 +1,522 @@
+dataSource = 'default';
+
+ if (isset($this->params['datasource'])) {
+ $this->dataSource = $this->params['datasource'];
+ }
+
+ if (!in_array(Configure::read('Acl.classname'), array('DbAcl', 'DB_ACL'))) {
+ $out = "--------------------------------------------------\n";
+ $out .= __("Error: Your current Cake configuration is set to", true) . "\n";
+ $out .= __("an ACL implementation other than DB. Please change", true) . "\n";
+ $out .= __("your core config to reflect your decision to use", true) . "\n";
+ $out .= __("DbAcl before attempting to use this script", true) . ".\n";
+ $out .= "--------------------------------------------------\n";
+ $out .= sprintf(__("Current ACL Classname: %s", true), Configure::read('Acl.classname')) . "\n";
+ $out .= "--------------------------------------------------\n";
+ $this->err($out);
+ $this->_stop();
+ }
+
+ if ($this->command && !in_array($this->command, array('help'))) {
+ if (!config('database')) {
+ $this->out(__("Your database configuration was not found. Take a moment to create one.", true), true);
+ $this->args = null;
+ return $this->DbConfig->execute();
+ }
+ require_once (CONFIGS.'database.php');
+
+ if (!in_array($this->command, array('initdb'))) {
+ $this->Acl = new AclComponent();
+ $controller = null;
+ $this->Acl->startup($controller);
+ }
+ }
+ }
+/**
+ * Override main() for help message hook
+ *
+ * @access public
+ */
+ function main() {
+ $out = __("Available ACL commands:", true) . "\n";
+ $out .= "\t - create\n";
+ $out .= "\t - delete\n";
+ $out .= "\t - setParent\n";
+ $out .= "\t - getPath\n";
+ $out .= "\t - check\n";
+ $out .= "\t - grant\n";
+ $out .= "\t - deny\n";
+ $out .= "\t - inherit\n";
+ $out .= "\t - view\n";
+ $out .= "\t - initdb\n";
+ $out .= "\t - help\n\n";
+ $out .= __("For help, run the 'help' command. For help on a specific command, run 'help '", true);
+ $this->out($out);
+ }
+/**
+ * Creates an ARO/ACO node
+ *
+ * @access public
+ */
+ function create() {
+
+ $this->_checkArgs(3, 'create');
+ $this->checkNodeType();
+ extract($this->__dataVars());
+
+ $class = ucfirst($this->args[0]);
+ $object = new $class();
+
+ if (preg_match('/^([\w]+)\.(.*)$/', $this->args[1], $matches) && count($matches) == 3) {
+ $parent = array(
+ 'model' => $matches[1],
+ 'foreign_key' => $matches[2],
+ );
+ } else {
+ $parent = $this->args[1];
+ }
+
+ if (!empty($parent) && $parent != '/' && $parent != 'root') {
+ @$parent = $object->node($parent);
+ if (empty($parent)) {
+ $this->err(sprintf(__('Could not find parent node using reference "%s"', true), $this->args[1]));
+ return;
+ } else {
+ $parent = Set::extract($parent, "0.{$class}.id");
+ }
+ } else {
+ $parent = null;
+ }
+
+ if (preg_match('/^([\w]+)\.(.*)$/', $this->args[2], $matches) && count($matches) == 3) {
+ $data = array(
+ 'model' => $matches[1],
+ 'foreign_key' => $matches[2],
+ );
+ } else {
+ if (!($this->args[2] == '/')) {
+ $data = array('alias' => $this->args[2]);
+ } else {
+ $this->error(__('/ can not be used as an alias!', true), __('\t/ is the root, please supply a sub alias', true));
+ }
+ }
+
+ $data['parent_id'] = $parent;
+ $object->create();
+
+ if ($object->save($data)) {
+ $this->out(sprintf(__("New %s '%s' created.\n", true), $class, $this->args[2]), true);
+ } else {
+ $this->err(sprintf(__("There was a problem creating a new %s '%s'.", true), $class, $this->args[2]));
+ }
+ }
+/**
+ * Delete an ARO/ACO node.
+ *
+ * @access public
+ */
+ function delete() {
+ $this->_checkArgs(2, 'delete');
+ $this->checkNodeType();
+ extract($this->__dataVars());
+ if (!$this->Acl->{$class}->delete($this->args[1])) {
+ $this->error(__("Node Not Deleted", true), sprintf(__("There was an error deleting the %s. Check that the node exists", true), $class) . ".\n");
+ }
+ $this->out(sprintf(__("%s deleted", true), $class) . ".\n", true);
+ }
+
+/**
+ * Set parent for an ARO/ACO node.
+ *
+ * @access public
+ */
+ function setParent() {
+ $this->_checkArgs(3, 'setParent');
+ $this->checkNodeType();
+ extract($this->__dataVars());
+ $data = array(
+ $class => array(
+ 'id' => $this->args[1],
+ 'parent_id' => $this->args[2]
+ )
+ );
+ $this->Acl->{$class}->create();
+ if (!$this->Acl->{$class}->save($data)) {
+ $this->out(__("Error in setting new parent. Please make sure the parent node exists, and is not a descendant of the node specified.", true), true);
+ } else {
+ $this->out(sprintf(__("Node parent set to %s", true), $this->args[2]) . "\n", true);
+ }
+ }
+/**
+ * Get path to specified ARO/ACO node.
+ *
+ * @access public
+ */
+ function getPath() {
+ $this->_checkArgs(2, 'getPath');
+ $this->checkNodeType();
+ extract($this->__dataVars());
+ $id = ife(is_numeric($this->args[1]), intval($this->args[1]), $this->args[1]);
+ $nodes = $this->Acl->{$class}->getPath($id);
+ if (empty($nodes)) {
+ $this->error(sprintf(__("Supplied Node '%s' not found", true), $this->args[1]), __("No tree returned.", true));
+ }
+ for ($i = 0; $i < count($nodes); $i++) {
+ $this->out(str_repeat(' ', $i) . "[" . $nodes[$i][$class]['id'] . "]" . $nodes[$i][$class]['alias'] . "\n");
+ }
+ }
+/**
+ * Check permission for a given ARO to a given ACO.
+ *
+ * @access public
+ */
+ function check() {
+ $this->_checkArgs(3, 'check');
+ extract($this->__getParams());
+
+ if ($this->Acl->check($aro, $aco, $action)) {
+ $this->out(sprintf(__("%s is allowed.", true), $aro), true);
+ } else {
+ $this->out(sprintf(__("%s is not allowed.", true), $aro), true);
+ }
+ }
+/**
+ * Grant permission for a given ARO to a given ACO.
+ *
+ * @access public
+ */
+ function grant() {
+ $this->_checkArgs(3, 'grant');
+ extract($this->__getParams());
+
+ if ($this->Acl->allow($aro, $aco, $action)) {
+ $this->out(__("Permission granted.", true), true);
+ } else {
+ $this->out(__("Permission was not granted.", true), true);
+ }
+ }
+/**
+ * Deny access for an ARO to an ACO.
+ *
+ * @access public
+ */
+ function deny() {
+ $this->_checkArgs(3, 'deny');
+ extract($this->__getParams());
+
+ if ($this->Acl->deny($aro, $aco, $action)) {
+ $this->out(__("Permission denied.", true), true);
+ } else {
+ $this->out(__("Permission was not denied.", true), true);
+ }
+ }
+/**
+ * Set an ARO to inhermit permission to an ACO.
+ *
+ * @access public
+ */
+ function inherit() {
+ $this->_checkArgs(3, 'inherit');
+ extract($this->__getParams());
+
+ if ($this->Acl->inherit($aro, $aco, $action)) {
+ $this->out(__("Permission inherited.", true), true);
+ } else {
+ $this->out(__("Permission was not inherited.", true), true);
+ }
+ }
+/**
+ * Show a specific ARO/ACO node.
+ *
+ * @access public
+ */
+ function view() {
+ $this->_checkArgs(1, 'view');
+ $this->checkNodeType();
+ extract($this->__dataVars());
+ if (isset($this->args[1]) && !is_null($this->args[1])) {
+ $key = ife(is_numeric($this->args[1]), $secondary_id, 'alias');
+ $conditions = array($class . '.' . $key => $this->args[1]);
+ } else {
+ $conditions = null;
+ }
+ $nodes = $this->Acl->{$class}->find('all', array('conditions' => $conditions, 'order' => 'lft ASC'));
+ if (empty($nodes)) {
+ if (isset($this->args[1])) {
+ $this->error(sprintf(__("%s not found", true), $this->args[1]), __("No tree returned.", true));
+ } elseif (isset($this->args[0])) {
+ $this->error(sprintf(__("%s not found", true), $this->args[0]), __("No tree returned.", true));
+ }
+ }
+ $this->out($class . " tree:");
+ $this->hr();
+ $stack = array();
+ $last = null;
+ foreach ($nodes as $n) {
+ $stack[] = $n;
+ if (!empty($last)) {
+ $end = end($stack);
+ if ($end[$class]['rght'] > $last) {
+ foreach ($stack as $k => $v) {
+ $end = end($stack);
+ if ($v[$class]['rght'] < $end[$class]['rght']) {
+ unset($stack[$k]);
+ }
+ }
+ }
+ }
+ $last = $n[$class]['rght'];
+ $count = count($stack);
+ $indent = str_repeat(' ', $count);
+ if ($n[$class]['alias']) {
+ $this->out($indent . "[" . $n[$class]['id'] . "]" . $n[$class]['alias']."\n");
+ } else {
+ $this->out($indent . "[" . $n[$class]['id'] . "]" . $n[$class]['model'] . '.' . $n[$class]['foreign_key'] . "\n");
+ }
+ }
+ $this->hr();
+ }
+/**
+ * Initialize ACL database.
+ *
+ * @access public
+ */
+ function initdb() {
+ $this->Dispatch->args = array('schema', 'run', 'create', 'DbAcl');
+ $this->Dispatch->dispatch();
+ }
+/**
+ * Show help screen.
+ *
+ * @access public
+ */
+ function help() {
+ $head = __("Usage: cake acl ...", true) . "\n";
+ $head .= "-----------------------------------------------\n";
+ $head .= __("Commands:", true) . "\n\n";
+
+ $commands = array(
+ 'create' => "\tcreate aro|aco \n" .
+ "\t\t" . __("Creates a new ACL object under the parent specified by , an id/alias.", true) . "\n" .
+ "\t\t" . __("The and references can be in one of the following formats:", true) . "\n" .
+ "\t\t\t- " . __(". - The node will be bound to a specific record of the given model", true) . "\n" .
+ "\t\t\t- " . __(" - The node will be given a string alias (or path, in the case of ),", true) . "\n" .
+ "\t\t\t " . __("i.e. 'John'. When used with , this takes the form of an alias path,", true) . "\n" .
+ "\t\t\t " . __("i.e. //.", true) . "\n" .
+ "\t\t" . __("To add a node at the root level, enter 'root' or '/' as the parameter.", true) . "\n",
+
+ 'delete' => "\tdelete aro|aco \n" .
+ "\t\t" . __("Deletes the ACL object with the given reference (see 'create' for info on node references).", true) . "\n",
+
+ 'setparent' => "\tsetParent aro|aco \n" .
+ "\t\t" . __("Moves the ACL object specified by beneath the parent ACL object specified by .", true) . "\n" .
+ "\t\t" . __("To identify the node and parent, use the row id.", true) . "\n",
+
+ 'getpath' => "\tgetPath aro|aco \n" .
+ "\t\t" . __("Returns the path to the ACL object specified by . This command", true) . "\n" .
+ "\t\t" . __("is useful in determining the inhertiance of permissions for a certain", true) . "\n" .
+ "\t\t" . __("object in the tree.", true) . "\n" .
+ "\t\t" . __("For more detailed parameter usage info, see help for the 'create' command.", true) . "\n",
+
+ 'check' => "\tcheck [] " . __("or", true) . " all\n" .
+ "\t\t" . __("Use this command to check ACL permissions.", true) . "\n" .
+ "\t\t" . __("For more detailed parameter usage info, see help for the 'create' command.", true) . "\n",
+
+ 'grant' => "\tgrant [] " . __("or", true) . " all\n" .
+ "\t\t" . __("Use this command to grant ACL permissions. Once executed, the ARO", true) . "\n" .
+ "\t\t" . __("specified (and its children, if any) will have ALLOW access to the", true) . "\n" .
+ "\t\t" . __("specified ACO action (and the ACO's children, if any).", true) . "\n" .
+ "\t\t" . __("For more detailed parameter usage info, see help for the 'create' command.", true) . "\n",
+
+ 'deny' => "\tdeny []" . __("or", true) . " all\n" .
+ "\t\t" . __("Use this command to deny ACL permissions. Once executed, the ARO", true) . "\n" .
+ "\t\t" . __("specified (and its children, if any) will have DENY access to the", true) . "\n" .
+ "\t\t" . __("specified ACO action (and the ACO's children, if any).", true) . "\n" .
+ "\t\t" . __("For more detailed parameter usage info, see help for the 'create' command.", true) . "\n",
+
+ 'inherit' => "\tinherit []" . __("or", true) . " all\n" .
+ "\t\t" . __("Use this command to force a child ARO object to inherit its", true) . "\n" .
+ "\t\t" . __("permissions settings from its parent.", true) . "\n" .
+ "\t\t" . __("For more detailed parameter usage info, see help for the 'create' command.", true) . "\n",
+
+ 'view' => "\tview aro|aco []\n" .
+ "\t\t" . __("The view command will return the ARO or ACO tree. The optional", true) . "\n" .
+ "\t\t" . __("id/alias parameter allows you to return only a portion of the requested tree.", true) . "\n" .
+ "\t\t" . __("For more detailed parameter usage info, see help for the 'create' command.", true) . "\n",
+
+ 'initdb' => "\tinitdb\n".
+ "\t\t" . __("Uses this command : cake schema run create DbAcl", true) . "\n",
+
+ 'help' => "\thelp []\n" .
+ "\t\t" . __("Displays this help message, or a message on a specific command.", true) . "\n"
+ );
+
+ $this->out($head);
+ if (!isset($this->args[0])) {
+ foreach ($commands as $cmd) {
+ $this->out("{$cmd}\n\n");
+ }
+ } elseif (isset($commands[low($this->args[0])])) {
+ $this->out($commands[low($this->args[0])] . "\n\n");
+ } else {
+ $this->out(sprintf(__("Command '%s' not found", true), $this->args[0]));
+ }
+ }
+/**
+ * Check that first argument specifies a valid Node type (ARO/ACO)
+ *
+ * @access public
+ */
+ function checkNodeType() {
+ if (!isset($this->args[0])) {
+ return false;
+ }
+ if ($this->args[0] != 'aco' && $this->args[0] != 'aro') {
+ $this->error(sprintf(__("Missing/Unknown node type: '%s'", true), $this->args[1]), __('Please specify which ACL object type you wish to create.', true));
+ }
+ }
+/**
+ * Checks that given node exists
+ *
+ * @param string $type Node type (ARO/ACO)
+ * @param integer $id Node id
+ * @return boolean Success
+ * @access public
+ */
+ function nodeExists() {
+ if (!$this->checkNodeType() && !isset($this->args[1])) {
+ return false;
+ }
+ extract($this->__dataVars($this->args[0]));
+ $key = (ife(is_numeric($this->args[1]), $secondary_id, 'alias'));
+ $conditions = array($class . '.' . $key => $this->args[1]);
+ $possibility = $this->Acl->{$class}->find('all', compact('conditions'));
+ if (empty($possibility)) {
+ $this->error(sprintf(__("%s not found", true), $this->args[1]), __("No tree returned.", true));
+ }
+ return $possibility;
+ }
+/**
+ * get params for standard Acl methods
+ *
+ * @return array aro, aco, action
+ * @access private
+ */
+ function __getParams() {
+ $aro = ife(is_numeric($this->args[0]), intval($this->args[0]), $this->args[0]);
+ $aco = ife(is_numeric($this->args[1]), intval($this->args[1]), $this->args[1]);
+
+ if (is_string($aro) && preg_match('/^([\w]+)\.(.*)$/', $aro, $matches)) {
+ $aro = array(
+ 'model' => $matches[1],
+ 'foreign_key' => $matches[2],
+ );
+ }
+
+ if (is_string($aco) && preg_match('/^([\w]+)\.(.*)$/', $aco, $matches)) {
+ $aco = array(
+ 'model' => $matches[1],
+ 'foreign_key' => $matches[2],
+ );
+ }
+
+ $action = null;
+ if (isset($this->args[2])) {
+ $action = $this->args[2];
+ if ($action == '' || $action == 'all') {
+ $action = '*';
+ }
+ }
+ return compact('aro', 'aco', 'action');
+ }
+
+/**
+ * Build data parameters based on node type
+ *
+ * @param string $type Node type (ARO/ACO)
+ * @return array Variables
+ * @access private
+ */
+ function __dataVars($type = null) {
+ if ($type == null) {
+ $type = $this->args[0];
+ }
+ $vars = array();
+ $class = ucwords($type);
+ $vars['secondary_id'] = ife(strtolower($class) == 'aro', 'foreign_key', 'object_id');
+ $vars['data_name'] = $type;
+ $vars['table_name'] = $type . 's';
+ $vars['class'] = $class;
+ return $vars;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/api.php b/cake/console/libs/api.php
new file mode 100755
index 00000000..87aefc10
--- /dev/null
+++ b/cake/console/libs/api.php
@@ -0,0 +1,216 @@
+paths = array_merge($this->paths, array(
+ 'behavior' => LIBS . 'model' . DS . 'behaviors' . DS,
+ 'cache' => LIBS . 'cache' . DS,
+ 'controller' => LIBS . 'controller' . DS,
+ 'component' => LIBS . 'controller' . DS . 'components' . DS,
+ 'helper' => LIBS . 'view' . DS . 'helpers' . DS,
+ 'model' => LIBS . 'model' . DS,
+ 'view' => LIBS . 'view' . DS,
+ 'core' => LIBS
+ ));
+ }
+/**
+ * Override main() to handle action
+ *
+ * @access public
+ */
+ function main() {
+ if (empty($this->args)) {
+ return $this->help();
+ }
+
+ $type = strtolower($this->args[0]);
+
+ if (isset($this->paths[$type])) {
+ $path = $this->paths[$type];
+ } else {
+ $path = $this->paths['core'];
+ }
+
+ if (count($this->args) == 1) {
+ $file = $type;
+ $class = Inflector::camelize($type);
+ } elseif (count($this->args) > 1) {
+ $file = Inflector::underscore($this->args[1]);
+ $class = Inflector::camelize($file);
+ }
+
+ $objects = Configure::listObjects('class', $path);
+ if (in_array($class, $objects)) {
+ if (in_array($type, array('behavior', 'component', 'helper')) && $type !== $file) {
+ if (!preg_match('/' . Inflector::camelize($type) . '$/', $class)) {
+ $class .= Inflector::camelize($type);
+ }
+ }
+
+ } else {
+ $this->err(sprintf(__("%s not found", true), $class));
+ $this->_stop();
+ }
+
+ $parsed = $this->__parseClass($path . $file .'.php');
+
+ if (!empty($parsed)) {
+ if (isset($this->params['m'])) {
+ if (!isset($parsed[$this->params['m']])) {
+ $this->err(sprintf(__("%s::%s() could not be found", true), $class, $this->params['m']));
+ $this->_stop();
+ }
+ $method = $parsed[$this->params['m']];
+ $this->out($class .'::'.$method['method'] . $method['parameters']);
+ $this->hr();
+ $this->out($method['comment'], true);
+ } else {
+ $this->out(ucwords($class));
+ $this->hr();
+ $i = 0;
+ foreach ($parsed as $method) {
+ $list[] = ++$i . ". " . $method['method'] . $method['parameters'];
+ }
+ $this->out($list);
+
+ $methods = array_keys($parsed);
+ while ($number = strtolower($this->in(__('Select a number to see the more information about a specific method. q to quit. l to list.', true), null, 'q'))) {
+ if ($number === 'q') {
+ $this->out(__('Done', true));
+ $this->_stop();
+ }
+
+ if ($number === 'l') {
+ $this->out($list);
+ }
+
+ if (isset($methods[--$number])) {
+ $method = $parsed[$methods[$number]];
+ $this->hr();
+ $this->out($class .'::'.$method['method'] . $method['parameters']);
+ $this->hr();
+ $this->out($method['comment'], true);
+ }
+ }
+ }
+ }
+ }
+
+/**
+ * Show help for this shell.
+ *
+ * @access public
+ */
+ function help() {
+ $head = "Usage: cake api [] [-m ]\n";
+ $head .= "-----------------------------------------------\n";
+ $head .= "Parameters:\n\n";
+
+ $commands = array(
+ 'path' => "\t\n" .
+ "\t\tEither a full path or type of class (model, behavior, controller, component, view, helper).\n".
+ "\t\tAvailable values:\n\n".
+ "\t\tbehavior\tLook for class in CakePHP behavior path\n".
+ "\t\tcache\tLook for class in CakePHP cache path\n".
+ "\t\tcontroller\tLook for class in CakePHP controller path\n".
+ "\t\tcomponent\tLook for class in CakePHP component path\n".
+ "\t\thelper\tLook for class in CakePHP helper path\n".
+ "\t\tmodel\tLook for class in CakePHP model path\n".
+ "\t\tview\tLook for class in CakePHP view path\n",
+ 'className' => "\t\n" .
+ "\t\tA CakePHP core class name (e.g: Component, HtmlHelper).\n"
+ );
+
+ $this->out($head);
+ if (!isset($this->args[1])) {
+ foreach ($commands as $cmd) {
+ $this->out("{$cmd}\n\n");
+ }
+ } elseif (isset($commands[low($this->args[1])])) {
+ $this->out($commands[low($this->args[1])] . "\n\n");
+ } else {
+ $this->out("Command '" . $this->args[1] . "' not found");
+ }
+ }
+
+/**
+ * Parse a given class (located on given file) and get public methods and their
+ * signatures.
+ *
+ * @param object $File File object
+ * @param string $class Class name
+ * @return array Methods and signatures indexed by method name
+ * @access private
+ */
+ function __parseClass($path) {
+ $parsed = array();
+
+ $File = new File($path);
+ if (!$File->exists()) {
+ $this->err(sprintf(__("%s could not be found", true), $File->name));
+ $this->_stop();
+ }
+
+ $contents = $File->read();
+
+ if (preg_match_all('%(/\\*\\*[\\s\\S]*?\\*/)(\\s+function\\s+\\w+)(\\(.*\\))%', $contents, $result, PREG_PATTERN_ORDER)) {
+ foreach ($result[2] as $key => $method) {
+ $method = str_replace('function ', '', trim($method));
+
+ if (strpos($method, '__') === false && $method[0] != '_') {
+ $parsed[$method] = array(
+ 'comment' => str_replace(array('/*', '*/', '*'), '', trim($result[1][$key])),
+ 'method' => $method,
+ 'parameters' => trim($result[3][$key])
+ );
+ }
+ }
+ }
+ ksort($parsed);
+ return $parsed;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/bake.php b/cake/console/libs/bake.php
new file mode 100755
index 00000000..f891ca2a
--- /dev/null
+++ b/cake/console/libs/bake.php
@@ -0,0 +1,211 @@
+command);
+ if (isset($this->{$task}) && !in_array($task, array('Project', 'DbConfig'))) {
+ $path = Inflector::underscore(Inflector::pluralize($this->command));
+ $this->{$task}->path = $this->params['working'] . DS . $path . DS;
+ if (!is_dir($this->{$task}->path)) {
+ $this->err(sprintf(__("%s directory could not be found.\nBe sure you have created %s", true), $task, $this->{$task}->path));
+ $this->_stop();
+ }
+ }
+ }
+/**
+ * Override main() to handle action
+ *
+ * @access public
+ */
+ function main() {
+ if (!is_dir($this->DbConfig->path)) {
+ if ($this->Project->execute()) {
+ $this->DbConfig->path = $this->params['working'] . DS . 'config' . DS;
+ }
+ }
+
+ if (!config('database')) {
+ $this->out(__("Your database configuration was not found. Take a moment to create one.", true));
+ $this->args = null;
+ return $this->DbConfig->execute();
+ }
+ $this->out('Interactive Bake Shell');
+ $this->hr();
+ $this->out('[D]atabase Configuration');
+ $this->out('[M]odel');
+ $this->out('[V]iew');
+ $this->out('[C]ontroller');
+ $this->out('[P]roject');
+ $this->out('[Q]uit');
+
+ $classToBake = strtoupper($this->in(__('What would you like to Bake?', true), array('D', 'M', 'V', 'C', 'P', 'Q')));
+ switch ($classToBake) {
+ case 'D':
+ $this->DbConfig->execute();
+ break;
+ case 'M':
+ $this->Model->execute();
+ break;
+ case 'V':
+ $this->View->execute();
+ break;
+ case 'C':
+ $this->Controller->execute();
+ break;
+ case 'P':
+ $this->Project->execute();
+ break;
+ case 'Q':
+ exit(0);
+ break;
+ default:
+ $this->out(__('You have made an invalid selection. Please choose a type of class to Bake by entering D, M, V, or C.', true));
+ }
+ $this->hr();
+ $this->main();
+ }
+/**
+ * Quickly bake the MVC
+ *
+ * @access public
+ */
+ function all() {
+ $ds = 'default';
+ $this->hr();
+ $this->out('Bake All');
+ $this->hr();
+
+ if (isset($this->params['connection'])) {
+ $ds = $this->params['connection'];
+ }
+
+ if (empty($this->args)) {
+ $name = $this->Model->getName($ds);
+ }
+
+ if (!empty($this->args[0])) {
+ $name = $this->args[0];
+ $this->Model->listAll($ds, false);
+ }
+
+ $modelExists = false;
+ $model = $this->_modelName($name);
+ if (App::import('Model', $model)) {
+ $object = new $model();
+ $modelExists = true;
+ } else {
+ App::import('Model');
+ $object = new Model(array('name' => $name, 'ds' => $ds));
+ }
+
+ $modelBaked = $this->Model->bake($object, false);
+
+ if ($modelBaked && $modelExists === false) {
+ $this->out(sprintf(__('%s Model was baked.', true), $model));
+ if ($this->_checkUnitTest()) {
+ $this->Model->bakeTest($model);
+ }
+ $modelExists = true;
+ }
+
+ if ($modelExists === true) {
+ $controller = $this->_controllerName($name);
+ if ($this->Controller->bake($controller, $this->Controller->bakeActions($controller))) {
+ $this->out(sprintf(__('%s Controller was baked.', true), $name));
+ if ($this->_checkUnitTest()) {
+ $this->Controller->bakeTest($controller);
+ }
+ }
+ if (App::import('Controller', $controller)) {
+ $this->View->args = array($controller);
+ $this->View->execute();
+ }
+ $this->out(__('Bake All complete'));
+ array_shift($this->args);
+ } else {
+ $this->err(__('Bake All could not continue without a valid model', true));
+ }
+
+ if (empty($this->args)) {
+ $this->all();
+ }
+ $this->_stop();
+ }
+
+/**
+ * Displays help contents
+ *
+ * @access public
+ */
+ function help() {
+ $this->out('CakePHP Bake:');
+ $this->hr();
+ $this->out('The Bake script generates controllers, views and models for your application.');
+ $this->out('If run with no command line arguments, Bake guides the user through the class');
+ $this->out('creation process. You can customize the generation process by telling Bake');
+ $this->out('where different parts of your application are using command line arguments.');
+ $this->hr();
+ $this->out("Usage: cake bake ...");
+ $this->hr();
+ $this->out('Params:');
+ $this->out("\t-app Absolute/Relative path to your app folder.\n");
+ $this->out('Commands:');
+ $this->out("\n\tbake help\n\t\tshows this help message.");
+ $this->out("\n\tbake all \n\t\tbakes complete MVC. optional of a Model");
+ $this->out("\n\tbake project \n\t\tbakes a new app folder in the path supplied\n\t\tor in current directory if no path is specified");
+ $this->out("\n\tbake plugin \n\t\tbakes a new plugin folder in the path supplied\n\t\tor in current directory if no path is specified.");
+ $this->out("\n\tbake db_config\n\t\tbakes a database.php file in config directory.");
+ $this->out("\n\tbake model\n\t\tbakes a model. run 'bake model help' for more info");
+ $this->out("\n\tbake view\n\t\tbakes views. run 'bake view help' for more info");
+ $this->out("\n\tbake controller\n\t\tbakes a controller. run 'bake controller help' for more info");
+ $this->out("");
+
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/console.php b/cake/console/libs/console.php
new file mode 100755
index 00000000..19b07509
--- /dev/null
+++ b/cake/console/libs/console.php
@@ -0,0 +1,338 @@
+Dispatcher = new Dispatcher();
+ $this->models = Configure::listObjects('model');
+ App::import('Model', $this->models);
+
+ foreach ($this->models as $model) {
+ $class = Inflector::camelize(r('.php', '', $model));
+ $this->models[$model] = $class;
+ $this->{$class} =& new $class();
+ }
+ $this->out('Model classes:');
+ $this->out('--------------');
+
+ foreach ($this->models as $model) {
+ $this->out(" - {$model}");
+ }
+ $this->__loadRoutes();
+ }
+/**
+ * Prints the help message
+ *
+ * @access public
+ */
+ function help() {
+ $this->main('help');
+ }
+/**
+ * Override main() to handle action
+ *
+ * @access public
+ */
+ function main($command = null) {
+ while (true) {
+ if (empty($command)) {
+ $command = trim($this->in(''));
+ }
+
+ switch ($command) {
+ case 'help':
+ $this->out('Console help:');
+ $this->out('-------------');
+ $this->out('The interactive console is a tool for testing parts of your app before you commit code');
+ $this->out('');
+ $this->out('Model testing:');
+ $this->out('To test model results, use the name of your model without a leading $');
+ $this->out('e.g. Foo->find("all")');
+ $this->out('');
+ $this->out('To dynamically set associations, you can do the following:');
+ $this->out("\tModelA bind ModelB");
+ $this->out("where the supported assocations are hasOne, hasMany, belongsTo, hasAndBelongsToMany");
+ $this->out('');
+ $this->out('To dynamically remove associations, you can do the following:');
+ $this->out("\t ModelA unbind ModelB");
+ $this->out("where the supported associations are the same as above");
+ $this->out('');
+ $this->out("To save a new field in a model, you can do the following:");
+ $this->out("\tModelA->save(array('foo' => 'bar', 'baz' => 0))");
+ $this->out("where you are passing a hash of data to be saved in the format");
+ $this->out("of field => value pairs");
+ $this->out('');
+ $this->out("To get column information for a model, use the following:");
+ $this->out("\tModelA columns");
+ $this->out("which returns a list of columns and their type");
+ $this->out('');
+ $this->out('Route testing:');
+ $this->out('To test URLs against your app\'s route configuration, type:');
+ $this->out("\tRoute ");
+ $this->out("where url is the path to your your action plus any query parameters, minus the");
+ $this->out("application's base path");
+ $this->out('');
+ $this->out('To reload your routes config (config/routes.php), do the following:');
+ $this->out("\tRoutes reload");
+ $this->out('');
+ $this->out('');
+ $this->out('To show all connected routes, do the following:');
+ $this->out("\tRoutes show");
+ $this->out('');
+ break;
+ case 'quit':
+ case 'exit':
+ return true;
+ break;
+ case 'models':
+ $this->out('Model classes:');
+ $this->hr();
+ foreach ($this->models as $model) {
+ $this->out(" - {$model}");
+ }
+ break;
+ case (preg_match("/^(\w+) bind (\w+) (\w+)/", $command, $tmp) == true):
+ foreach ($tmp as $data) {
+ $data = strip_tags($data);
+ $data = str_replace($this->badCommandChars, "", $data);
+ }
+
+ $modelA = $tmp[1];
+ $association = $tmp[2];
+ $modelB = $tmp[3];
+
+ if ($this->__isValidModel($modelA) && $this->__isValidModel($modelB) && in_array($association, $this->associations)) {
+ $this->{$modelA}->bindModel(array($association => array($modelB => array('className' => $modelB))), false);
+ $this->out("Created $association association between $modelA and $modelB");
+ } else {
+ $this->out("Please verify you are using valid models and association types");
+ }
+ break;
+ case (preg_match("/^(\w+) unbind (\w+) (\w+)/", $command, $tmp) == true):
+ foreach ($tmp as $data) {
+ $data = strip_tags($data);
+ $data = str_replace($this->badCommandChars, "", $data);
+ }
+
+ $modelA = $tmp[1];
+ $association = $tmp[2];
+ $modelB = $tmp[3];
+
+ // Verify that there is actually an association to unbind
+ $currentAssociations = $this->{$modelA}->getAssociated();
+ $validCurrentAssociation = false;
+
+ foreach ($currentAssociations as $model => $currentAssociation) {
+ if ($model == $modelB && $association == $currentAssociation) {
+ $validCurrentAssociation = true;
+ }
+ }
+
+ if ($this->__isValidModel($modelA) && $this->__isValidModel($modelB) && in_array($association, $this->associations) && $validCurrentAssociation) {
+ $this->{$modelA}->unbindModel(array($association => array($modelB)));
+ $this->out("Removed $association association between $modelA and $modelB");
+ } else {
+ $this->out("Please verify you are using valid models, valid current association, and valid association types");
+ }
+ break;
+ case (strpos($command, "->find") > 0):
+ // Remove any bad info
+ $command = strip_tags($command);
+ $command = str_replace($this->badCommandChars, "", $command);
+
+ // Do we have a valid model?
+ list($modelToCheck, $tmp) = explode('->', $command);
+
+ if ($this->__isValidModel($modelToCheck)) {
+ $findCommand = "\$data = \$this->$command;";
+ @eval($findCommand);
+
+ if (is_array($data)) {
+ foreach ($data as $idx => $results) {
+ if (is_numeric($idx)) { // findAll() output
+ foreach ($results as $modelName => $result) {
+ $this->out("$modelName");
+
+ foreach ($result as $field => $value) {
+ if (is_array($value)) {
+ foreach ($value as $field2 => $value2) {
+ $this->out("\t$field2: $value2");
+ }
+
+ $this->out("");
+ } else {
+ $this->out("\t$field: $value");
+ }
+ }
+ }
+ } else { // find() output
+ $this->out($idx);
+
+ foreach ($results as $field => $value) {
+ if (is_array($value)) {
+ foreach ($value as $field2 => $value2) {
+ $this->out("\t$field2: $value2");
+ }
+
+ $this->out("");
+ } else {
+ $this->out("\t$field: $value");
+ }
+ }
+ }
+ }
+ } else {
+ $this->out("\nNo result set found");
+ }
+ } else {
+ $this->out("$modelToCheck is not a valid model");
+ }
+
+ break;
+ case (strpos($command, '->save') > 0):
+ // Validate the model we're trying to save here
+ $command = strip_tags($command);
+ $command = str_replace($this->badCommandChars, "", $command);
+ list($modelToSave, $tmp) = explode("->", $command);
+
+ if ($this->__isValidModel($modelToSave)) {
+ // Extract the array of data we are trying to build
+ list($foo, $data) = explode("->save", $command);
+ $data = preg_replace('/^\(*(array)?\(*(.+?)\)*$/i', '\\2', $data);
+ $saveCommand = "\$this->{$modelToSave}->save(array('{$modelToSave}' => array({$data})));";
+ @eval($saveCommand);
+ $this->out('Saved record for ' . $modelToSave);
+ }
+ break;
+ case (preg_match("/^(\w+) columns/", $command, $tmp) == true):
+ $modelToCheck = strip_tags(str_replace($this->badCommandChars, "", $tmp[1]));
+
+ if ($this->__isValidModel($modelToCheck)) {
+ // Get the column info for this model
+ $fieldsCommand = "\$data = \$this->{$modelToCheck}->getColumnTypes();";
+ @eval($fieldsCommand);
+
+ if (is_array($data)) {
+ foreach ($data as $field => $type) {
+ $this->out("\t{$field}: {$type}");
+ }
+ }
+ } else {
+ $this->out("Please verify that you selected a valid model");
+ }
+ break;
+ case (preg_match("/^routes\s+reload/i", $command, $tmp) == true):
+ $router =& Router::getInstance();
+ if (!$this->__loadRoutes()) {
+ $this->out("There was an error loading the routes config. Please check that the file");
+ $this->out("exists and is free of parse errors.");
+ break;
+ }
+ $this->out("Routes configuration reloaded, " . count($router->routes) . " routes connected");
+ break;
+ case (preg_match("/^routes\s+show/i", $command, $tmp) == true):
+ $router =& Router::getInstance();
+ $this->out(join("\n", Set::extract($router->routes, '{n}.0')));
+ break;
+ case (preg_match("/^route\s+(.*)/i", $command, $tmp) == true):
+ $this->out(var_export(Router::parse($tmp[1]), true));
+ break;
+ default:
+ $this->out("Invalid command\n");
+ break;
+ }
+ $command = '';
+ }
+ }
+/**
+ * Tells if the specified model is included in the list of available models
+ *
+ * @param string $modelToCheck
+ * @return boolean true if is an available model, false otherwise
+ * @access private
+ */
+ function __isValidModel($modelToCheck) {
+ return in_array($modelToCheck, $this->models);
+ }
+/**
+ * Reloads the routes configuration from config/routes.php, and compiles
+ * all routes found
+ *
+ * @return boolean True if config reload was a success, otherwise false
+ * @access private
+ */
+ function __loadRoutes() {
+ $router =& Router::getInstance();
+
+ $router->reload();
+ extract($router->getNamedExpressions());
+
+ if (!@include(CONFIGS . 'routes.php')) {
+ return false;
+ }
+ $router->parse('/');
+
+ foreach (array_keys($router->getNamedExpressions()) as $var) {
+ unset(${$var});
+ }
+ for ($i = 0; $i < count($router->routes); $i++) {
+ $router->compile($i);
+ }
+ return true;
+ }
+}
+?>
diff --git a/cake/console/libs/i18n.php b/cake/console/libs/i18n.php
new file mode 100755
index 00000000..f0e23b91
--- /dev/null
+++ b/cake/console/libs/i18n.php
@@ -0,0 +1,129 @@
+_welcome();
+ if (isset($this->params['datasource'])) {
+ $this->dataSource = $this->params['datasource'];
+ }
+
+ if ($this->command && !in_array($this->command, array('help'))) {
+ if (!config('database')) {
+ $this->out(__('Your database configuration was not found. Take a moment to create one.', true), true);
+ return $this->DbConfig->execute();
+ }
+ }
+ }
+/**
+ * Override main() for help message hook
+ *
+ * @access public
+ */
+ function main() {
+ $this->out(__('I18n Shell', true));
+ $this->hr();
+ $this->out(__('[E]xtract POT file from sources', true));
+ $this->out(__('[I]nitialize i18n database table', true));
+ $this->out(__('[H]elp', true));
+ $this->out(__('[Q]uit', true));
+
+ $choice = strtolower($this->in(__('What would you like to do?', true), array('E', 'I', 'H', 'Q')));
+ switch ($choice) {
+ case 'e':
+ $this->Extract->execute();
+ break;
+ case 'i':
+ $this->initdb();
+ break;
+ case 'h':
+ $this->help();
+ break;
+ case 'q':
+ exit(0);
+ break;
+ default:
+ $this->out(__('You have made an invalid selection. Please choose a command to execute by entering E, I, H, or Q.', true));
+ }
+ $this->hr();
+ $this->main();
+ }
+/**
+ * Initialize I18N database.
+ *
+ * @access public
+ */
+ function initdb() {
+ $this->Dispatch->args = array('schema', 'run', 'create', 'i18n');
+ $this->Dispatch->dispatch();
+ }
+/**
+ * Show help screen.
+ *
+ * @access public
+ */
+ function help() {
+ $this->hr();
+ $this->out(__('I18n Shell:', true));
+ $this->hr();
+ $this->out(__('I18n Shell initializes i18n database table for your application', true));
+ $this->out(__('and generates .pot file(s) with translations.', true));
+ $this->hr();
+ $this->out(__('usage:', true));
+ $this->out(' cake i18n help');
+ $this->out(' cake i18n initdb [-datasource custom]');
+ $this->out('');
+ $this->hr();
+
+ $this->Extract->help();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/schema.php b/cake/console/libs/schema.php
new file mode 100755
index 00000000..36974eb5
--- /dev/null
+++ b/cake/console/libs/schema.php
@@ -0,0 +1,430 @@
+_welcome();
+ $this->out('Cake Schema Shell');
+ $this->hr();
+ }
+/**
+ * Override startup
+ *
+ * @access public
+ */
+ function startup() {
+ $name = null;
+ if (!empty($this->params['name'])) {
+ $name = $this->params['name'];
+ $this->params['file'] = Inflector::underscore($name);
+ }
+
+ $path = null;
+ if (!empty($this->params['path'])) {
+ $path = $this->params['path'];
+ }
+
+ $file = null;
+ if (empty($this->params['file'])) {
+ $this->params['file'] = 'schema.php';
+ }
+ if (strpos($this->params['file'], '.php') === false) {
+ $this->params['file'] .= '.php';
+ }
+ $file = $this->params['file'];
+
+ $connection = null;
+ if (!empty($this->params['connection'])) {
+ $connection = $this->params['connection'];
+ }
+
+ $this->Schema =& new CakeSchema(compact('name', 'path', 'file', 'connection'));
+ }
+/**
+ * Override main
+ *
+ * @access public
+ */
+ function main() {
+ $this->help();
+ }
+/**
+ * Read and output contents of schema object
+ * path to read as second arg
+ *
+ * @access public
+ */
+ function view() {
+ $File = new File($this->Schema->path . DS . $this->params['file']);
+ if ($File->exists()) {
+ $this->out($File->read());
+ $this->_stop();
+ } else {
+ $this->err(__('Schema could not be found', true));
+ $this->_stop();
+ }
+ }
+/**
+ * Read database and Write schema object
+ * accepts a connection as first arg or path to save as second arg
+ *
+ * @access public
+ */
+ function generate() {
+ $this->out(__('Generating Schema...', true));
+ $options = array();
+ if (isset($this->params['f'])) {
+ $options = array('models' => false);
+ }
+
+ $snapshot = false;
+ if (isset($this->args[0]) && $this->args[0] === 'snapshot') {
+ $snapshot = true;
+ }
+
+ if (!$snapshot && file_exists($this->Schema->path . DS . $this->params['file'])) {
+ $snapshot = true;
+ $result = strtolower($this->in("Schema file exists.\n [O]verwrite\n [S]napshot\n [Q]uit\nWould you like to do?", array('o', 's', 'q'), 's'));
+ if ($result === 'q') {
+ return $this->_stop();
+ }
+ if ($result === 'o') {
+ $snapshot = false;
+ }
+ }
+
+ $content = $this->Schema->read($options);
+ $content['file'] = $this->params['file'];
+
+ if ($snapshot === true) {
+ $Folder =& new Folder($this->Schema->path);
+ $result = $Folder->read();
+
+ $numToUse = false;
+ if (isset($this->params['s'])) {
+ $numToUse = $this->params['s'];
+ }
+
+ $count = 1;
+ if (!empty($result[1])) {
+ foreach ($result[1] as $file) {
+ if (preg_match('/schema(?:[_\d]*)?\.php$/', $file)) {
+ $count++;
+ }
+ }
+ }
+
+ if ($numToUse !== false) {
+ if ($numToUse > $count) {
+ $count = $numToUse;
+ }
+ }
+
+ $fileName = rtrim($this->params['file'], '.php');
+ $content['file'] = $fileName . '_' . $count . '.php';
+ }
+
+ if ($this->Schema->write($content)) {
+ $this->out(sprintf(__('Schema file: %s generated', true), $content['file']));
+ $this->_stop();
+ } else {
+ $this->err(__('Schema file: %s generated', true));
+ $this->_stop();
+ }
+ }
+/**
+ * Dump Schema object to sql file
+ * if first arg == write, file will be written to sql file
+ * or it will output sql
+ *
+ * @access public
+ */
+ function dump() {
+ $write = false;
+ $Schema = $this->Schema->load();
+ if (!$Schema) {
+ $this->err(__('Schema could not be loaded', true));
+ $this->_stop();
+ }
+ if (!empty($this->args[0])) {
+ if ($this->args[0] == 'write') {
+ $write = Inflector::underscore($this->Schema->name);
+ } else {
+ $write = $this->args[0];
+ }
+ }
+ $db =& ConnectionManager::getDataSource($this->Schema->connection);
+ $contents = "#" . $Schema->name . " sql generated on: " . date('Y-m-d H:i:s') . " : " . time() . "\n\n";
+ $contents .= $db->dropSchema($Schema) . "\n\n". $db->createSchema($Schema);
+ if ($write) {
+ if (strpos($write, '.sql') === false) {
+ $write .= '.sql';
+ }
+ $File = new File($this->Schema->path . DS . $write, true);
+ if ($File->write($contents)) {
+ $this->out(sprintf(__('SQL dump file created in %s', true), $File->pwd()));
+ $this->_stop();
+ } else {
+ $this->err(__('SQL dump could not be created', true));
+ $this->_stop();
+ }
+ }
+ $this->out($contents);
+ return $contents;
+ }
+/**
+ * Run database commands: create, update
+ *
+ * @access public
+ */
+ function run() {
+ if (!isset($this->args[0])) {
+ $this->err(__('Command not found', true));
+ $this->_stop();
+ }
+
+ $command = $this->args[0];
+
+ $this->Dispatch->shiftArgs();
+
+ $name = null;
+ if (isset($this->args[0])) {
+ $name = $this->args[0];
+ }
+ if (isset($this->params['name'])) {
+ $name = $this->params['name'];
+ }
+
+ if (isset($this->params['dry'])) {
+ $this->__dry = true;
+ $this->out(__('Performing a dry run.', true));
+ }
+
+ $options = array('name' => $name);
+ if (isset($this->params['s'])) {
+ $fileName = rtrim($this->Schema->file, '.php');
+ $options['file'] = $fileName . '_' . $this->params['s'] . '.php';
+ }
+
+ $Schema = $this->Schema->load($options);
+
+ if (!$Schema) {
+ $this->err(sprintf(__('%s could not be loaded', true), $this->Schema->file));
+ $this->_stop();
+ }
+
+ $table = null;
+ if (isset($this->args[1])) {
+ $table = $this->args[1];
+ }
+
+ switch ($command) {
+ case 'create':
+ $this->__create($Schema, $table);
+ break;
+ case 'update':
+ $this->__update($Schema, $table);
+ break;
+ default:
+ $this->err(__('Command not found', true));
+ $this->_stop();
+ }
+ }
+/**
+ * Create database from Schema object
+ * Should be called via the run method
+ *
+ * @access private
+ */
+ function __create(&$Schema, $table = null) {
+ $db =& ConnectionManager::getDataSource($this->Schema->connection);
+
+ $drop = $create = array();
+
+ if (!$table) {
+ foreach ($Schema->tables as $table => $fields) {
+ $drop[$table] = $db->dropSchema($Schema, $table);
+ $create[$table] = $db->createSchema($Schema, $table);
+ }
+ } elseif (isset($Schema->tables[$table])) {
+ $drop[$table] = $db->dropSchema($Schema, $table);
+ $create[$table] = $db->createSchema($Schema, $table);
+ }
+ if (empty($drop) || empty($create)) {
+ $this->out(__('Schema is up to date.', true));
+ $this->_stop();
+ }
+
+ $this->out("\n" . __('The following table(s) will be dropped.', true));
+ $this->out(array_keys($drop));
+
+ if ('y' == $this->in(__('Are you sure you want to drop the table(s)?', true), array('y', 'n'), 'n')) {
+ $this->out(__('Dropping table(s).', true));
+ $this->__run($drop, 'drop', $Schema);
+ }
+
+ $this->out("\n" . __('The following table(s) will be created.', true));
+ $this->out(array_keys($create));
+
+ if ('y' == $this->in(__('Are you sure you want to create the table(s)?', true), array('y', 'n'), 'y')) {
+ $this->out(__('Creating table(s).', true));
+ $this->__run($create, 'create', $Schema);
+ }
+
+ $this->out(__('End create.', true));
+ }
+/**
+ * Update database with Schema object
+ * Should be called via the run method
+ *
+ * @access private
+ */
+ function __update(&$Schema, $table = null) {
+ $db =& ConnectionManager::getDataSource($this->Schema->connection);
+
+ $this->out(__('Comparing Database to Schema...', true));
+ $options = array();
+ if (isset($this->params['f'])) {
+ $options['models'] = false;
+ }
+ $Old = $this->Schema->read($options);
+ $compare = $this->Schema->compare($Old, $Schema);
+
+ $contents = array();
+
+ if (empty($table)) {
+ foreach ($compare as $table => $changes) {
+ $contents[$table] = $db->alterSchema(array($table => $changes), $table);
+ }
+ } elseif (isset($compare[$table])) {
+ $contents[$table] = $db->alterSchema(array($table => $compare[$table]), $table);
+ }
+
+ if (empty($contents)) {
+ $this->out(__('Schema is up to date.', true));
+ $this->_stop();
+ }
+
+ $this->out("\n" . __('The following statements will run.', true));
+ $this->out(array_map('trim', $contents));
+ if ('y' == $this->in(__('Are you sure you want to alter the tables?', true), array('y', 'n'), 'n')) {
+ $this->out('');
+ $this->out(__('Updating Database...', true));
+ $this->__run($contents, 'update', $Schema);
+ }
+
+ $this->out(__('End update.', true));
+ }
+/**
+ * Runs sql from __create() or __update()
+ *
+ * @access private
+ */
+ function __run($contents, $event, &$Schema) {
+ if (empty($contents)) {
+ $this->err(__('Sql could not be run', true));
+ return;
+ }
+ Configure::write('debug', 2);
+ $db =& ConnectionManager::getDataSource($this->Schema->connection);
+ $db->fullDebug = true;
+
+ foreach ($contents as $table => $sql) {
+ if (empty($sql)) {
+ $this->out(sprintf(__('%s is up to date.', true), $table));
+ } else {
+ if ($this->__dry === true) {
+ $this->out(sprintf(__('Dry run for %s :', true), $table));
+ $this->out($sql);
+ } else {
+ if (!$Schema->before(array($event => $table))) {
+ return false;
+ }
+ $error = null;
+ if (!$db->execute($sql)) {
+ $error = $table . ': ' . $db->lastError();
+ }
+
+ $Schema->after(array($event => $table, 'errors' => $error));
+
+ if (!empty($error)) {
+ $this->out($error);
+ } else {
+ $this->out(sprintf(__('%s updated.', true), $table));
+ }
+ }
+ }
+ }
+ }
+/**
+ * Displays help contents
+ *
+ * @access public
+ */
+ function help() {
+ $this->out("The Schema Shell generates a schema object from");
+ $this->out("the database and updates the database from the schema.");
+ $this->hr();
+ $this->out("Usage: cake schema ...");
+ $this->hr();
+ $this->out('Params:');
+ $this->out("\n\t-connection \n\t\tset db config . uses 'default' if none is specified");
+ $this->out("\n\t-path \n\t\tpath to read and write schema.php.\n\t\tdefault path: ". $this->Schema->path);
+ $this->out("\n\t-name \n\t\tclassname to use.");
+ $this->out("\n\t-file \n\t\tfile to read and write.\n\t\tdefault file: ". $this->Schema->file);
+ $this->out("\n\t-s \n\t\tsnapshot to use for run.");
+ $this->out("\n\t-dry\n\t\tPerform a dry run on 'run' commands.\n\t\tQueries will be output to window instead of executed.");
+ $this->out("\n\t-f\n\t\tforce 'generate' to create a new schema.");
+ $this->out('Commands:');
+ $this->out("\n\tschema help\n\t\tshows this help message.");
+ $this->out("\n\tschema view\n\t\tread and output contents of schema file");
+ $this->out("\n\tschema generate\n\t\treads from 'connection' writes to 'path'\n\t\tTo force generation of all tables into the schema, use the -f param.\n\t\tUse 'schema generate snapshot ' to generate snapshots\n\t\twhich you can use with the -s parameter in the other operations.");
+ $this->out("\n\tschema dump \n\t\tDump database sql based on schema file to . \n\t\tIf is write, schema dump will be written to a file\n\t\tthat has the same name as the app directory.");
+ $this->out("\n\tschema run create \n\t\tDrop and create tables based on schema file\n\t\toptional arg for selecting schema name\n\t\toptional arg for creating only one table\n\t\tpass the -s param with a number to use a snapshot\n\t\tTo see the changes, perform a dry run with the -dry param");
+ $this->out("\n\tschema run update \n\t\talter tables based on schema file\n\t\toptional arg for selecting schema name.\n\t\toptional arg for altering only one table.\n\t\tTo use a snapshot, pass the -s param with the snapshot number\n\t\tTo see the changes, perform a dry run with the -dry param");
+ $this->out("");
+ $this->_stop();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/shell.php b/cake/console/libs/shell.php
new file mode 100755
index 00000000..eed6bff8
--- /dev/null
+++ b/cake/console/libs/shell.php
@@ -0,0 +1,615 @@
+ 'command');
+ foreach ($vars as $key => $var) {
+ if (is_string($key)) {
+ $this->{$var} =& $dispatch->{$key};
+ } else {
+ $this->{$var} =& $dispatch->{$var};
+ }
+ }
+
+ if ($this->name == null) {
+ $this->name = get_class($this);
+ }
+
+ if ($this->alias == null) {
+ $this->alias = $this->name;
+ }
+
+ ClassRegistry::addObject($this->name, $this);
+ ClassRegistry::map($this->name, $this->alias);
+
+ if (!PHP5 && isset($this->args[0])) {
+ if (strpos($this->name, strtolower(Inflector::camelize($this->args[0]))) !== false) {
+ $dispatch->shiftArgs();
+ }
+ if (strtolower($this->command) == strtolower(Inflector::variable($this->args[0])) && method_exists($this, $this->command)) {
+ $dispatch->shiftArgs();
+ }
+ }
+
+ $this->Dispatch =& $dispatch;
+ }
+/**
+ * Initializes the Shell
+ * acts as constructor for subclasses
+ * allows configuration of tasks prior to shell execution
+ *
+ * @access public
+ */
+ function initialize() {
+ $this->_loadModels();
+ }
+/**
+ * Starts up the the Shell
+ * allows for checking and configuring prior to command or main execution
+ * can be overriden in subclasses
+ *
+ * @access public
+ */
+ function startup() {
+ $this->_welcome();
+ }
+/**
+ * Displays a header for the shell
+ *
+ * @access protected
+ */
+ function _welcome() {
+ $this->out("\nWelcome to CakePHP v" . Configure::version() . " Console");
+ $this->out("---------------------------------------------------------------");
+ $this->out('App : '. $this->params['app']);
+ $this->out('Path: '. $this->params['working']);
+ $this->hr();
+ }
+/**
+ * Loads database file and constructs DATABASE_CONFIG class
+ * makes $this->DbConfig available to subclasses
+ *
+ * @return bool
+ * @access protected
+ */
+ function _loadDbConfig() {
+ if (config('database') && class_exists('DATABASE_CONFIG')) {
+ $this->DbConfig =& new DATABASE_CONFIG();
+ return true;
+ }
+ $this->err('Database config could not be loaded');
+ $this->out('Run \'bake\' to create the database configuration');
+ return false;
+ }
+/**
+ * if var $uses = true
+ * Loads AppModel file and constructs AppModel class
+ * makes $this->AppModel available to subclasses
+ * if var $uses is an array of models will load those models
+ *
+ * @return bool
+ * @access protected
+ */
+ function _loadModels() {
+ if ($this->uses === null || $this->uses === false) {
+ return;
+ }
+
+ if ($this->uses === true && App::import('Model', 'AppModel')) {
+ $this->AppModel =& new AppModel(false, false, false);
+ return true;
+ }
+
+ if ($this->uses !== true && !empty($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) {
+ $plugin = null;
+ if (strpos($modelClass, '.') !== false) {
+ list($plugin, $modelClass) = explode('.', $modelClass);
+ $plugin = $plugin . '.';
+ }
+ if (PHP5) {
+ $this->{$modelClass} = ClassRegistry::init($plugin . $modelClass);
+ } else {
+ $this->{$modelClass} =& ClassRegistry::init($plugin . $modelClass);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+/**
+ * Loads tasks defined in var $tasks
+ *
+ * @return bool
+ * @access public
+ */
+ function loadTasks() {
+ if ($this->tasks === null || $this->tasks === false || $this->tasks === true || empty($this->tasks)) {
+ return true;
+ }
+
+ $tasks = $this->tasks;
+ if (!is_array($tasks)) {
+ $tasks = array($tasks);
+ }
+
+ foreach ($tasks as $taskName) {
+ $task = Inflector::underscore($taskName);
+ $taskClass = Inflector::camelize($taskName . 'Task');
+
+ if (!class_exists($taskClass)) {
+ foreach ($this->Dispatch->shellPaths as $path) {
+ $taskPath = $path . 'tasks' . DS . $task.'.php';
+ if (file_exists($taskPath)) {
+ require_once $taskPath;
+ break;
+ }
+ }
+ }
+ if (ClassRegistry::isKeySet($taskClass)) {
+ $this->taskNames[] = $taskName;
+ if (!PHP5) {
+ $this->{$taskName} =& ClassRegistry::getObject($taskClass);
+ } else {
+ $this->{$taskName} = ClassRegistry::getObject($taskClass);
+ }
+ } else {
+ $this->taskNames[] = $taskName;
+ if (!PHP5) {
+ $this->{$taskName} =& new $taskClass($this->Dispatch);
+ } else {
+ $this->{$taskName} = new $taskClass($this->Dispatch);
+ }
+ }
+
+ if (!isset($this->{$taskName})) {
+ $this->err("Task '" . $taskName . "' could not be loaded");
+ $this->_stop();
+ }
+ }
+
+ return true;
+ }
+/**
+ * Prompts the user for input, and returns it.
+ *
+ * @param string $prompt Prompt text.
+ * @param mixed $options Array or string of options.
+ * @param string $default Default input value.
+ * @return Either the default value, or the user-provided input.
+ * @access public
+ */
+ function in($prompt, $options = null, $default = null) {
+ if (!$this->interactive) {
+ return $default;
+ }
+ $in = $this->Dispatch->getInput($prompt, $options, $default);
+
+ if ($options && is_string($options)) {
+ if (strpos($options, ',')) {
+ $options = explode(',', $options);
+ } elseif (strpos($options, '/')) {
+ $options = explode('/', $options);
+ } else {
+ $options = array($options);
+ }
+ }
+ if (is_array($options)) {
+ while ($in == '' || ($in && (!in_array(strtolower($in), $options) && !in_array(strtoupper($in), $options)) && !in_array($in, $options))) {
+ $in = $this->Dispatch->getInput($prompt, $options, $default);
+ }
+ }
+ if ($in) {
+ return $in;
+ }
+ }
+/**
+ * Outputs to the stdout filehandle.
+ *
+ * @param string $string String to output.
+ * @param boolean $newline If true, the outputs gets an added newline.
+ * @access public
+ */
+ function out($string, $newline = true) {
+ if (is_array($string)) {
+ $str = '';
+ foreach ($string as $message) {
+ $str .= $message ."\n";
+ }
+ $string = $str;
+ }
+ return $this->Dispatch->stdout($string, $newline);
+ }
+/**
+ * Outputs to the stderr filehandle.
+ *
+ * @param string $string Error text to output.
+ * @access public
+ */
+ function err($string) {
+ if (is_array($string)) {
+ $str = '';
+ foreach ($string as $message) {
+ $str .= $message ."\n";
+ }
+ $string = $str;
+ }
+ return $this->Dispatch->stderr($string."\n");
+ }
+/**
+ * Outputs a series of minus characters to the standard output, acts as a visual separator.
+ *
+ * @param boolean $newline If true, the outputs gets an added newline.
+ * @access public
+ */
+ function hr($newline = false) {
+ if ($newline) {
+ $this->out("\n");
+ }
+ $this->out('---------------------------------------------------------------');
+ if ($newline) {
+ $this->out("\n");
+ }
+ }
+/**
+ * Displays a formatted error message and exits the application
+ *
+ * @param string $title Title of the error message
+ * @param string $msg Error message
+ * @access public
+ */
+ function error($title, $msg) {
+ $out = "$title\n";
+ $out .= "$msg\n";
+ $out .= "\n";
+ $this->err($out);
+ $this->_stop();
+ }
+/**
+ * Will check the number args matches otherwise throw an error
+ *
+ * @param integer $expectedNum Expected number of paramters
+ * @param string $command Command
+ * @access protected
+ */
+ function _checkArgs($expectedNum, $command = null) {
+ if (!$command) {
+ $command = $this->command;
+ }
+ if (count($this->args) < $expectedNum) {
+ $this->error("Wrong number of parameters: ".count($this->args), "Expected: {$expectedNum}\nPlease type 'cake {$this->shell} help' for help on usage of the {$this->name} {$command}");
+ }
+ }
+/**
+ * Creates a file at given path
+ *
+ * @param string $path Where to put the file.
+ * @param string $contents Content to put in the file.
+ * @return boolean Success
+ * @access public
+ */
+ function createFile ($path, $contents) {
+ $path = str_replace(DS . DS, DS, $path);
+ $this->out("\n" . sprintf(__("Creating file %s", true), $path));
+ if (is_file($path) && $this->interactive === true) {
+ $key = $this->in(__("File exists, overwrite?", true). " {$path}", array('y', 'n', 'q'), 'n');
+ if (strtolower($key) == 'q') {
+ $this->out(__("Quitting.", true) ."\n");
+ exit;
+ } elseif (strtolower($key) != 'y') {
+ $this->out(__("Skip", true) ." {$path}\n");
+ return false;
+ }
+ }
+ if (!class_exists('File')) {
+ uses('file');
+ }
+
+ if ($File = new File($path, true)) {
+ $data = $File->prepare($contents);
+ $File->write($data);
+ $this->out(__("Wrote", true) ." {$path}");
+ return true;
+ } else {
+ $this->err(__("Error! Could not write to", true)." {$path}.\n");
+ return false;
+ }
+ }
+/**
+ * Outputs usage text on the standard output. Implement it in subclasses.
+ *
+ * @access public
+ */
+ function help() {
+ if ($this->command != null) {
+ $this->err("Unknown {$this->name} command '$this->command'.\nFor usage, try 'cake {$this->shell} help'.\n\n");
+ } else {
+ $this->Dispatch->help();
+ }
+ }
+/**
+ * Action to create a Unit Test
+ *
+ * @return boolean Success
+ * @access protected
+ */
+ function _checkUnitTest() {
+ if (App::import('vendor', 'simpletest' . DS . 'simpletest')) {
+ return true;
+ }
+ $unitTest = $this->in('SimpleTest is not installed. Do you want to bake unit test files anyway?', array('y','n'), 'y');
+ $result = strtolower($unitTest) == 'y' || strtolower($unitTest) == 'yes';
+
+ if ($result) {
+ $this->out("\nYou can download SimpleTest from http://simpletest.org", true);
+ }
+ return $result;
+ }
+/**
+ * Makes absolute file path easier to read
+ *
+ * @param string $file Absolute file path
+ * @return sting short path
+ * @access public
+ */
+ function shortPath($file) {
+ $shortPath = str_replace(ROOT, null, $file);
+ $shortPath = str_replace('..' . DS, '', $shortPath);
+ return str_replace(DS . DS, DS, $shortPath);
+ }
+/**
+ * Checks for Configure::read('Routing.admin') and forces user to input it if not enabled
+ *
+ * @return string Admin route to use
+ * @access public
+ */
+ function getAdmin() {
+ $admin = '';
+ $cakeAdmin = null;
+ $adminRoute = Configure::read('Routing.admin');
+ if (!empty($adminRoute)) {
+ $cakeAdmin = $adminRoute . '_';
+ } else {
+ $this->out('You need to enable Configure::write(\'Routing.admin\',\'admin\') in /app/config/core.php to use admin routing.');
+ $this->out('What would you like the admin route to be?');
+ $this->out('Example: www.example.com/admin/controller');
+ while ($admin == '') {
+ $admin = $this->in("What would you like the admin route to be?", null, 'admin');
+ }
+ if ($this->Project->cakeAdmin($admin) !== true) {
+ $this->out('Unable to write to /app/config/core.php.');
+ $this->out('You need to enable Configure::write(\'Routing.admin\',\'admin\') in /app/config/core.php to use admin routing.');
+ $this->_stop();
+ } else {
+ $cakeAdmin = $admin . '_';
+ }
+ }
+ return $cakeAdmin;
+ }
+/**
+ * Creates the proper controller path for the specified controller class name
+ *
+ * @param string $name Controller class name
+ * @return string Path to controller
+ * @access protected
+ */
+ function _controllerPath($name) {
+ return strtolower(Inflector::underscore($name));
+ }
+/**
+ * Creates the proper controller plural name for the specified controller class name
+ *
+ * @param string $name Controller class name
+ * @return string Controller plural name
+ * @access protected
+ */
+ function _controllerName($name) {
+ return Inflector::pluralize(Inflector::camelize($name));
+ }
+/**
+ * Creates the proper controller camelized name (singularized) for the specified name
+ *
+ * @param string $name Name
+ * @return string Camelized and singularized controller name
+ * @access protected
+ */
+ function _modelName($name) {
+ return Inflector::camelize(Inflector::singularize($name));
+ }
+/**
+ * Creates the proper singular model key for associations
+ *
+ * @param string $name Controller class name
+ * @return string Singular model key
+ * @access protected
+ */
+ function _modelKey($name) {
+ return Inflector::underscore(Inflector::singularize($name)).'_id';
+ }
+/**
+ * Creates the proper model name from a foreign key
+ *
+ * @param string $key Foreign key
+ * @return string Model name
+ * @access protected
+ */
+ function _modelNameFromKey($key) {
+ $name = str_replace('_id', '',$key);
+ return Inflector::camelize($name);
+ }
+/**
+ * creates the singular name for use in views.
+ *
+ * @param string $name
+ * @return string $name
+ * @access protected
+ */
+ function _singularName($name) {
+ return Inflector::variable(Inflector::singularize($name));
+ }
+/**
+ * Creates the plural name for views
+ *
+ * @param string $name Name to use
+ * @return string Plural name for views
+ * @access protected
+ */
+ function _pluralName($name) {
+ return Inflector::variable(Inflector::pluralize($name));
+ }
+/**
+ * Creates the singular human name used in views
+ *
+ * @param string $name Controller name
+ * @return string Singular human name
+ * @access protected
+ */
+ function _singularHumanName($name) {
+ return Inflector::humanize(Inflector::underscore(Inflector::singularize($name)));
+ }
+/**
+ * Creates the plural human name used in views
+ *
+ * @param string $name Controller name
+ * @return string Plural human name
+ * @access protected
+ */
+ function _pluralHumanName($name) {
+ return Inflector::humanize(Inflector::underscore(Inflector::pluralize($name)));
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/tasks/controller.php b/cake/console/libs/tasks/controller.php
new file mode 100755
index 00000000..736522a8
--- /dev/null
+++ b/cake/console/libs/tasks/controller.php
@@ -0,0 +1,579 @@
+args)) {
+ $this->__interactive();
+ }
+
+ if (isset($this->args[0])) {
+ $controller = Inflector::camelize($this->args[0]);
+ $actions = null;
+ if (isset($this->args[1]) && $this->args[1] == 'scaffold') {
+ $this->out('Baking scaffold for ' . $controller);
+ $actions = $this->bakeActions($controller);
+ } else {
+ $actions = 'scaffold';
+ }
+ if ((isset($this->args[1]) && $this->args[1] == 'admin') || (isset($this->args[2]) && $this->args[2] == 'admin')) {
+ if ($admin = $this->getAdmin()) {
+ $this->out('Adding ' . Configure::read('Routing.admin') .' methods');
+ if ($actions == 'scaffold') {
+ $actions = $this->bakeActions($controller, $admin);
+ } else {
+ $actions .= $this->bakeActions($controller, $admin);
+ }
+ }
+ }
+ if ($this->bake($controller, $actions)) {
+ if ($this->_checkUnitTest()) {
+ $this->bakeTest($controller);
+ }
+ }
+ }
+ }
+/**
+ * Interactive
+ *
+ * @access private
+ */
+ function __interactive($controllerName = false) {
+ if (!$controllerName) {
+ $this->interactive = true;
+ $this->hr();
+ $this->out(sprintf("Bake Controller\nPath: %s", $this->path));
+ $this->hr();
+ $actions = '';
+ $uses = array();
+ $helpers = array();
+ $components = array();
+ $wannaUseSession = 'y';
+ $wannaDoAdmin = 'n';
+ $wannaUseScaffold = 'n';
+ $wannaDoScaffolding = 'y';
+ $controllerName = $this->getName();
+ }
+ $this->hr();
+ $this->out("Baking {$controllerName}Controller");
+ $this->hr();
+
+ $controllerFile = strtolower(Inflector::underscore($controllerName));
+
+ $question[] = __("Would you like to build your controller interactively?", true);
+ if (file_exists($this->path . $controllerFile .'_controller.php')) {
+ $question[] = sprintf(__("Warning: Choosing no will overwrite the %sController.", true), $controllerName);
+ }
+ $doItInteractive = $this->in(join("\n", $question), array('y','n'), 'y');
+
+ if (strtolower($doItInteractive) == 'y' || strtolower($doItInteractive) == 'yes') {
+ $this->interactive = true;
+
+ $wannaUseScaffold = $this->in(__("Would you like to use scaffolding?", true), array('y','n'), 'n');
+
+ if (strtolower($wannaUseScaffold) == 'n' || strtolower($wannaUseScaffold) == 'no') {
+
+ $wannaDoScaffolding = $this->in(__("Would you like to include some basic class methods (index(), add(), view(), edit())?", true), array('y','n'), 'n');
+
+ if (strtolower($wannaDoScaffolding) == 'y' || strtolower($wannaDoScaffolding) == 'yes') {
+ $wannaDoAdmin = $this->in(__("Would you like to create the methods for admin routing?", true), array('y','n'), 'n');
+ }
+
+ $wannaDoHelpers = $this->in(__("Would you like this controller to use other helpers besides HtmlHelper and FormHelper?", true), array('y','n'), 'n');
+
+ if (strtolower($wannaDoHelpers) == 'y' || strtolower($wannaDoHelpers) == 'yes') {
+ $helpersList = $this->in(__("Please provide a comma separated list of the other helper names you'd like to use.\nExample: 'Ajax, Javascript, Time'", true));
+ $helpersListTrimmed = str_replace(' ', '', $helpersList);
+ $helpers = explode(',', $helpersListTrimmed);
+ }
+ $wannaDoComponents = $this->in(__("Would you like this controller to use any components?", true), array('y','n'), 'n');
+
+ if (strtolower($wannaDoComponents) == 'y' || strtolower($wannaDoComponents) == 'yes') {
+ $componentsList = $this->in(__("Please provide a comma separated list of the component names you'd like to use.\nExample: 'Acl, Security, RequestHandler'", true));
+ $componentsListTrimmed = str_replace(' ', '', $componentsList);
+ $components = explode(',', $componentsListTrimmed);
+ }
+
+ $wannaUseSession = $this->in(__("Would you like to use Sessions?", true), array('y','n'), 'y');
+ } else {
+ $wannaDoScaffolding = 'n';
+ }
+ } else {
+ $wannaDoScaffolding = $this->in(__("Would you like to include some basic class methods (index(), add(), view(), edit())?", true), array('y','n'), 'y');
+
+ if (strtolower($wannaDoScaffolding) == 'y' || strtolower($wannaDoScaffolding) == 'yes') {
+ $wannaDoAdmin = $this->in(__("Would you like to create the methods for admin routing?", true), array('y','n'), 'y');
+ }
+ }
+ $admin = false;
+
+ if ((strtolower($wannaDoAdmin) == 'y' || strtolower($wannaDoAdmin) == 'yes')) {
+ $admin = $this->getAdmin();
+ }
+
+ if (strtolower($wannaDoScaffolding) == 'y' || strtolower($wannaDoScaffolding) == 'yes') {
+ $actions = $this->bakeActions($controllerName, null, in_array(strtolower($wannaUseSession), array('y', 'yes')));
+ if ($admin) {
+ $actions .= $this->bakeActions($controllerName, $admin, in_array(strtolower($wannaUseSession), array('y', 'yes')));
+ }
+ }
+
+ if ($this->interactive === true) {
+ $this->out('');
+ $this->hr();
+ $this->out('The following controller will be created:');
+ $this->hr();
+ $this->out("Controller Name: $controllerName");
+
+ if (strtolower($wannaUseScaffold) == 'y' || strtolower($wannaUseScaffold) == 'yes') {
+ $this->out(" var \$scaffold;");
+ $actions = 'scaffold';
+ }
+
+ if (count($helpers)) {
+ $this->out("Helpers: ", false);
+
+ foreach ($helpers as $help) {
+ if ($help != $helpers[count($helpers) - 1]) {
+ $this->out(ucfirst($help) . ", ", false);
+ } else {
+ $this->out(ucfirst($help));
+ }
+ }
+ }
+
+ if (count($components)) {
+ $this->out("Components: ", false);
+
+ foreach ($components as $comp) {
+ if ($comp != $components[count($components) - 1]) {
+ $this->out(ucfirst($comp) . ", ", false);
+ } else {
+ $this->out(ucfirst($comp));
+ }
+ }
+ }
+ $this->hr();
+ $looksGood = $this->in(__('Look okay?', true), array('y','n'), 'y');
+
+ if (strtolower($looksGood) == 'y' || strtolower($looksGood) == 'yes') {
+ $baked = $this->bake($controllerName, $actions, $helpers, $components, $uses);
+ if ($baked && $this->_checkUnitTest()) {
+ $this->bakeTest($controllerName);
+ }
+ } else {
+ $this->__interactive($controllerName);
+ }
+ } else {
+ $baked = $this->bake($controllerName, $actions, $helpers, $components, $uses);
+ if ($baked && $this->_checkUnitTest()) {
+ $this->bakeTest($controllerName);
+ }
+ }
+ }
+/**
+ * Bake scaffold actions
+ *
+ * @param string $controllerName Controller name
+ * @param string $admin Admin route to use
+ * @param boolean $wannaUseSession Set to true to use sessions, false otherwise
+ * @return string Baked actions
+ * @access private
+ */
+ function bakeActions($controllerName, $admin = null, $wannaUseSession = true) {
+ $currentModelName = $modelImport = $this->_modelName($controllerName);
+ if ($this->plugin) {
+ $modelImport = $this->plugin . '.' . $modelImport;
+ }
+ if (!App::import('Model', $modelImport)) {
+ $this->err(__('You must have a model for this class to build scaffold methods. Please try again.', true));
+ exit;
+ }
+ $actions = null;
+ $modelObj =& new $currentModelName();
+ $controllerPath = $this->_controllerPath($controllerName);
+ $pluralName = $this->_pluralName($currentModelName);
+ $singularName = Inflector::variable($currentModelName);
+ $singularHumanName = Inflector::humanize($currentModelName);
+ $pluralHumanName = Inflector::humanize($controllerName);
+ $actions .= "\n";
+ $actions .= "\tfunction {$admin}index() {\n";
+ $actions .= "\t\t\$this->{$currentModelName}->recursive = 0;\n";
+ $actions .= "\t\t\$this->set('{$pluralName}', \$this->paginate());\n";
+ $actions .= "\t}\n";
+ $actions .= "\n";
+ $actions .= "\tfunction {$admin}view(\$id = null) {\n";
+ $actions .= "\t\tif (!\$id) {\n";
+ if ($wannaUseSession) {
+ $actions .= "\t\t\t\$this->Session->setFlash(__('Invalid {$singularHumanName}.', true));\n";
+ $actions .= "\t\t\t\$this->redirect(array('action'=>'index'));\n";
+ } else {
+ $actions .= "\t\t\t\$this->flash(__('Invalid {$singularHumanName}', true), array('action'=>'index'));\n";
+ }
+ $actions .= "\t\t}\n";
+ $actions .= "\t\t\$this->set('" . $singularName . "', \$this->{$currentModelName}->read(null, \$id));\n";
+ $actions .= "\t}\n";
+ $actions .= "\n";
+
+ /* ADD ACTION */
+ $compact = array();
+ $actions .= "\tfunction {$admin}add() {\n";
+ $actions .= "\t\tif (!empty(\$this->data)) {\n";
+ $actions .= "\t\t\t\$this->{$currentModelName}->create();\n";
+ $actions .= "\t\t\tif (\$this->{$currentModelName}->save(\$this->data)) {\n";
+ if ($wannaUseSession) {
+ $actions .= "\t\t\t\t\$this->Session->setFlash(__('The " . $singularHumanName . " has been saved', true));\n";
+ $actions .= "\t\t\t\t\$this->redirect(array('action'=>'index'));\n";
+ } else {
+ $actions .= "\t\t\t\t\$this->flash(__('{$currentModelName} saved.', true), array('action'=>'index'));\n";
+ }
+ $actions .= "\t\t\t} else {\n";
+ if ($wannaUseSession) {
+ $actions .= "\t\t\t\t\$this->Session->setFlash(__('The {$singularHumanName} could not be saved. Please, try again.', true));\n";
+ }
+ $actions .= "\t\t\t}\n";
+ $actions .= "\t\t}\n";
+ foreach ($modelObj->hasAndBelongsToMany as $associationName => $relation) {
+ if (!empty($associationName)) {
+ $habtmModelName = $this->_modelName($associationName);
+ $habtmSingularName = $this->_singularName($associationName);
+ $habtmPluralName = $this->_pluralName($associationName);
+ $actions .= "\t\t\${$habtmPluralName} = \$this->{$currentModelName}->{$habtmModelName}->find('list');\n";
+ $compact[] = "'{$habtmPluralName}'";
+ }
+ }
+ foreach ($modelObj->belongsTo as $associationName => $relation) {
+ if (!empty($associationName)) {
+ $belongsToModelName = $this->_modelName($associationName);
+ $belongsToPluralName = $this->_pluralName($associationName);
+ $actions .= "\t\t\${$belongsToPluralName} = \$this->{$currentModelName}->{$belongsToModelName}->find('list');\n";
+ $compact[] = "'{$belongsToPluralName}'";
+ }
+ }
+ if (!empty($compact)) {
+ $actions .= "\t\t\$this->set(compact(" . join(', ', $compact) . "));\n";
+ }
+ $actions .= "\t}\n";
+ $actions .= "\n";
+
+ /* EDIT ACTION */
+ $compact = array();
+ $actions .= "\tfunction {$admin}edit(\$id = null) {\n";
+ $actions .= "\t\tif (!\$id && empty(\$this->data)) {\n";
+ if ($wannaUseSession) {
+ $actions .= "\t\t\t\$this->Session->setFlash(__('Invalid {$singularHumanName}', true));\n";
+ $actions .= "\t\t\t\$this->redirect(array('action'=>'index'));\n";
+ } else {
+ $actions .= "\t\t\t\$this->flash(__('Invalid {$singularHumanName}', true), array('action'=>'index'));\n";
+ }
+ $actions .= "\t\t}\n";
+ $actions .= "\t\tif (!empty(\$this->data)) {\n";
+ $actions .= "\t\t\tif (\$this->{$currentModelName}->save(\$this->data)) {\n";
+ if ($wannaUseSession) {
+ $actions .= "\t\t\t\t\$this->Session->setFlash(__('The " . $singularHumanName . " has been saved', true));\n";
+ $actions .= "\t\t\t\t\$this->redirect(array('action'=>'index'));\n";
+ } else {
+ $actions .= "\t\t\t\t\$this->flash(__('The " . $singularHumanName . " has been saved.', true), array('action'=>'index'));\n";
+ }
+ $actions .= "\t\t\t} else {\n";
+ if ($wannaUseSession) {
+ $actions .= "\t\t\t\t\$this->Session->setFlash(__('The {$singularHumanName} could not be saved. Please, try again.', true));\n";
+ }
+ $actions .= "\t\t\t}\n";
+ $actions .= "\t\t}\n";
+ $actions .= "\t\tif (empty(\$this->data)) {\n";
+ $actions .= "\t\t\t\$this->data = \$this->{$currentModelName}->read(null, \$id);\n";
+ $actions .= "\t\t}\n";
+
+ foreach ($modelObj->hasAndBelongsToMany as $associationName => $relation) {
+ if (!empty($associationName)) {
+ $habtmModelName = $this->_modelName($associationName);
+ $habtmSingularName = $this->_singularName($associationName);
+ $habtmPluralName = $this->_pluralName($associationName);
+ $actions .= "\t\t\${$habtmPluralName} = \$this->{$currentModelName}->{$habtmModelName}->find('list');\n";
+ $compact[] = "'{$habtmPluralName}'";
+ }
+ }
+ foreach ($modelObj->belongsTo as $associationName => $relation) {
+ if (!empty($associationName)) {
+ $belongsToModelName = $this->_modelName($associationName);
+ $belongsToPluralName = $this->_pluralName($associationName);
+ $actions .= "\t\t\${$belongsToPluralName} = \$this->{$currentModelName}->{$belongsToModelName}->find('list');\n";
+ $compact[] = "'{$belongsToPluralName}'";
+ }
+ }
+ if (!empty($compact)) {
+ $actions .= "\t\t\$this->set(compact(" . join(',', $compact) . "));\n";
+ }
+ $actions .= "\t}\n";
+ $actions .= "\n";
+ $actions .= "\tfunction {$admin}delete(\$id = null) {\n";
+ $actions .= "\t\tif (!\$id) {\n";
+ if ($wannaUseSession) {
+ $actions .= "\t\t\t\$this->Session->setFlash(__('Invalid id for {$singularHumanName}', true));\n";
+ $actions .= "\t\t\t\$this->redirect(array('action'=>'index'));\n";
+ } else {
+ $actions .= "\t\t\t\$this->flash(__('Invalid {$singularHumanName}', true), array('action'=>'index'));\n";
+ }
+ $actions .= "\t\t}\n";
+ $actions .= "\t\tif (\$this->{$currentModelName}->del(\$id)) {\n";
+ if ($wannaUseSession) {
+ $actions .= "\t\t\t\$this->Session->setFlash(__('{$singularHumanName} deleted', true));\n";
+ $actions .= "\t\t\t\$this->redirect(array('action'=>'index'));\n";
+ } else {
+ $actions .= "\t\t\t\$this->flash(__('{$singularHumanName} deleted', true), array('action'=>'index'));\n";
+ }
+ $actions .= "\t\t}\n";
+ $actions .= "\t}\n";
+ $actions .= "\n";
+ return $actions;
+ }
+
+
+/**
+ * Assembles and writes a Controller file
+ *
+ * @param string $controllerName Controller name
+ * @param string $actions Actions to add, or set the whole controller to use $scaffold (set $actions to 'scaffold')
+ * @param array $helpers Helpers to use in controller
+ * @param array $components Components to use in controller
+ * @param array $uses Models to use in controller
+ * @return string Baked controller
+ * @access private
+ */
+ function bake($controllerName, $actions = '', $helpers = null, $components = null, $uses = null) {
+ $out = "plugin}AppController {\n\n";
+ $out .= "\tvar \$name = '$controllerName';\n";
+
+ if (strtolower($actions) == 'scaffold') {
+ $out .= "\tvar \$scaffold;\n";
+ } else {
+ if (count($uses)) {
+ $out .= "\tvar \$uses = array('" . $this->_modelName($controllerName) . "', ";
+
+ foreach ($uses as $use) {
+ if ($use != $uses[count($uses) - 1]) {
+ $out .= "'" . $this->_modelName($use) . "', ";
+ } else {
+ $out .= "'" . $this->_modelName($use) . "'";
+ }
+ }
+ $out .= ");\n";
+ }
+
+ $out .= "\tvar \$helpers = array('Html', 'Form'";
+ if (count($helpers)) {
+ foreach ($helpers as $help) {
+ $out .= ", '" . Inflector::camelize($help) . "'";
+ }
+ }
+ $out .= ");\n";
+
+ if (count($components)) {
+ $out .= "\tvar \$components = array(";
+
+ foreach ($components as $comp) {
+ if ($comp != $components[count($components) - 1]) {
+ $out .= "'" . Inflector::camelize($comp) . "', ";
+ } else {
+ $out .= "'" . Inflector::camelize($comp) . "'";
+ }
+ }
+ $out .= ");\n";
+ }
+ $out .= $actions;
+ }
+ $out .= "}\n";
+ $out .= "?>";
+ $filename = $this->path . $this->_controllerPath($controllerName) . '_controller.php';
+ return $this->createFile($filename, $out);
+ }
+/**
+ * Assembles and writes a unit test file
+ *
+ * @param string $className Controller class name
+ * @return string Baked test
+ * @access private
+ */
+ function bakeTest($className) {
+ $import = $className;
+ if ($this->plugin) {
+ $import = $this->plugin . '.' . $className;
+ }
+ $out = "App::import('Controller', '$import');\n\n";
+ $out .= "class Test{$className} extends {$className}Controller {\n";
+ $out .= "\tvar \$autoRender = false;\n}\n\n";
+ $out .= "class {$className}ControllerTest extends CakeTestCase {\n";
+ $out .= "\tvar \${$className} = null;\n\n";
+ $out .= "\tfunction startTest() {\n\t\t\$this->{$className} = new Test{$className}();";
+ $out .= "\n\t\t\$this->{$className}->constructClasses();\n\t}\n\n";
+ $out .= "\tfunction test{$className}ControllerInstance() {\n";
+ $out .= "\t\t\$this->assertTrue(is_a(\$this->{$className}, '{$className}Controller'));\n\t}\n\n";
+ $out .= "\tfunction endTest() {\n\t\tunset(\$this->{$className});\n\t}\n}\n";
+
+ $path = CONTROLLER_TESTS;
+ if (isset($this->plugin)) {
+ $pluginPath = 'plugins' . DS . Inflector::underscore($this->plugin) . DS;
+ $path = APP . $pluginPath . 'tests' . DS . 'cases' . DS . 'controllers' . DS;
+ }
+
+ $filename = Inflector::underscore($className).'_controller.test.php';
+ $this->out("\nBaking unit test for $className...");
+
+ $header = '$Id';
+ $content = "";
+ return $this->createFile($path . $filename, $content);
+ }
+/**
+ * Outputs and gets the list of possible models or controllers from database
+ *
+ * @param string $useDbConfig Database configuration name
+ * @return array Set of controllers
+ * @access public
+ */
+ function listAll($useDbConfig = 'default') {
+ $db =& ConnectionManager::getDataSource($useDbConfig);
+ $usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix'];
+ if ($usePrefix) {
+ $tables = array();
+ foreach ($db->listSources() as $table) {
+ if (!strncmp($table, $usePrefix, strlen($usePrefix))) {
+ $tables[] = substr($table, strlen($usePrefix));
+ }
+ }
+ } else {
+ $tables = $db->listSources();
+ }
+
+ if (empty($tables)) {
+ $this->err(__('Your database does not have any tables.', true));
+ $this->_stop();
+ }
+
+ $this->__tables = $tables;
+ $this->out('Possible Controllers based on your current database:');
+ $this->_controllerNames = array();
+ $count = count($tables);
+ for ($i = 0; $i < $count; $i++) {
+ $this->_controllerNames[] = $this->_controllerName($this->_modelName($tables[$i]));
+ $this->out($i + 1 . ". " . $this->_controllerNames[$i]);
+ }
+ return $this->_controllerNames;
+ }
+
+/**
+ * Forces the user to specify the controller he wants to bake, and returns the selected controller name.
+ *
+ * @return string Controller name
+ * @access public
+ */
+ function getName() {
+ $useDbConfig = 'default';
+ $controllers = $this->listAll($useDbConfig, 'Controllers');
+ $enteredController = '';
+
+ while ($enteredController == '') {
+ $enteredController = $this->in(__("Enter a number from the list above, type in the name of another controller, or 'q' to exit", true), null, 'q');
+
+ if ($enteredController === 'q') {
+ $this->out(__("Exit", true));
+ $this->_stop();
+ }
+
+ if ($enteredController == '' || intval($enteredController) > count($controllers)) {
+ $this->out(__('Error:', true));
+ $this->out(__("The Controller name you supplied was empty, or the number \nyou selected was not an option. Please try again.", true));
+ $enteredController = '';
+ }
+ }
+
+ if (intval($enteredController) > 0 && intval($enteredController) <= count($controllers) ) {
+ $controllerName = $controllers[intval($enteredController) - 1];
+ } else {
+ $controllerName = Inflector::camelize($enteredController);
+ }
+
+ return $controllerName;
+ }
+/**
+ * Displays help contents
+ *
+ * @access public
+ */
+ function help() {
+ $this->hr();
+ $this->out("Usage: cake bake controller ...");
+ $this->hr();
+ $this->out('Commands:');
+ $this->out("\n\tcontroller \n\t\tbakes controller with var \$scaffold");
+ $this->out("\n\tcontroller scaffold\n\t\tbakes controller with scaffold actions.\n\t\t(index, view, add, edit, delete)");
+ $this->out("\n\tcontroller scaffold admin\n\t\tbakes a controller with scaffold actions for both public and Configure::read('Routing.admin')");
+ $this->out("\n\tcontroller admin\n\t\tbakes a controller with scaffold actions only for Configure::read('Routing.admin')");
+ $this->out("");
+ $this->_stop();
+ }
+}
+?>
diff --git a/cake/console/libs/tasks/db_config.php b/cake/console/libs/tasks/db_config.php
new file mode 100755
index 00000000..d2f05393
--- /dev/null
+++ b/cake/console/libs/tasks/db_config.php
@@ -0,0 +1,353 @@
+ 'default', 'driver'=> 'mysql', 'persistent'=> 'false', 'host'=> 'localhost',
+ 'login'=> 'root', 'password'=> 'password', 'database'=> 'project_name',
+ 'schema'=> null, 'prefix'=> null, 'encoding' => null, 'port' => null
+ );
+/**
+ * initialization callback
+ *
+ * @var string
+ * @access public
+ */
+ function initialize() {
+ $this->path = $this->params['working'] . DS . 'config' . DS;
+ }
+/**
+ * Execution method always used for tasks
+ *
+ * @access public
+ */
+ function execute() {
+ if (empty($this->args)) {
+ $this->__interactive();
+ $this->_stop();
+ }
+ }
+/**
+ * Interactive interface
+ *
+ * @access private
+ */
+ function __interactive() {
+ $this->hr();
+ $this->out('Database Configuration:');
+ $this->hr();
+ $done = false;
+ $dbConfigs = array();
+
+ while ($done == false) {
+ $name = '';
+
+ while ($name == '') {
+ $name = $this->in("Name:", null, 'default');
+ if (preg_match('/[^a-z0-9_]/i', $name)) {
+ $name = '';
+ $this->out('The name may only contain unaccented latin characters, numbers or underscores');
+ }
+ else if (preg_match('/^[^a-z_]/i', $name)) {
+ $name = '';
+ $this->out('The name must start with an unaccented latin character or an underscore');
+ }
+ }
+ $driver = '';
+
+ while ($driver == '') {
+ $driver = $this->in('Driver:', array('db2', 'firebird', 'mssql', 'mysql', 'mysqli', 'odbc', 'oracle', 'postgres', 'sqlite', 'sybase'), 'mysql');
+ }
+ $persistent = '';
+
+ while ($persistent == '') {
+ $persistent = $this->in('Persistent Connection?', array('y', 'n'), 'n');
+ }
+
+ if (low($persistent) == 'n') {
+ $persistent = 'false';
+ } else {
+ $persistent = 'true';
+ }
+ $host = '';
+
+ while ($host == '') {
+ $host = $this->in('Database Host:', null, 'localhost');
+ }
+ $port = '';
+
+ while ($port == '') {
+ $port = $this->in('Port?', null, 'n');
+ }
+
+ if (low($port) == 'n') {
+ $port = null;
+ }
+ $login = '';
+
+ while ($login == '') {
+ $login = $this->in('User:', null, 'root');
+ }
+ $password = '';
+ $blankPassword = false;
+
+ while ($password == '' && $blankPassword == false) {
+ $password = $this->in('Password:');
+
+ if ($password == '') {
+ $blank = $this->in('The password you supplied was empty. Use an empty password?', array('y', 'n'), 'n');
+ if ($blank == 'y')
+ {
+ $blankPassword = true;
+ }
+ }
+ }
+ $database = '';
+
+ while ($database == '') {
+ $database = $this->in('Database Name:', null, 'cake');
+ }
+ $prefix = '';
+
+ while ($prefix == '') {
+ $prefix = $this->in('Table Prefix?', null, 'n');
+ }
+
+ if (low($prefix) == 'n') {
+ $prefix = null;
+ }
+ $encoding = '';
+
+ while ($encoding == '') {
+ $encoding = $this->in('Table encoding?', null, 'n');
+ }
+
+ if (low($encoding) == 'n') {
+ $encoding = null;
+ }
+ $schema = '';
+
+ if ($driver == 'postgres') {
+ while ($schema == '') {
+ $schema = $this->in('Table schema?', null, 'n');
+ }
+ }
+
+ if (low($schema) == 'n') {
+ $schema = null;
+ }
+
+ $config = compact('name', 'driver', 'persistent', 'host', 'login', 'password', 'database', 'prefix', 'encoding', 'port', 'schema');
+
+ while ($this->__verify($config) == false) {
+ $this->__interactive();
+ }
+ $dbConfigs[] = $config;
+ $doneYet = $this->in('Do you wish to add another database configuration?', null, 'n');
+
+ if (low($doneYet == 'n')) {
+ $done = true;
+ }
+ }
+
+ $this->bake($dbConfigs);
+ config('database');
+ return true;
+ }
+/**
+ * Output verification message and bake if it looks good
+ *
+ * @return boolean True if user says it looks good, false otherwise
+ * @access private
+ */
+ function __verify($config) {
+ $config = array_merge($this->__defaultConfig, $config);
+ extract($config);
+ $this->out('');
+ $this->hr();
+ $this->out('The following database configuration will be created:');
+ $this->hr();
+ $this->out("Name: $name");
+ $this->out("Driver: $driver");
+ $this->out("Persistent: $persistent");
+ $this->out("Host: $host");
+
+ if ($port) {
+ $this->out("Port: $port");
+ }
+
+ $this->out("User: $login");
+ $this->out("Pass: " . str_repeat('*', strlen($password)));
+ $this->out("Database: $database");
+
+ if ($prefix) {
+ $this->out("Table prefix: $prefix");
+ }
+
+ if ($schema) {
+ $this->out("Schema: $schema");
+ }
+
+ if ($encoding) {
+ $this->out("Encoding: $encoding");
+ }
+
+ $this->hr();
+ $looksGood = $this->in('Look okay?', array('y', 'n'), 'y');
+
+ if (strtolower($looksGood) == 'y' || strtolower($looksGood) == 'yes') {
+ return $config;
+ }
+ return false;
+ }
+/**
+ * Assembles and writes database.php
+ *
+ * @param array $configs Configuration settings to use
+ * @return boolean Success
+ * @access public
+ */
+ function bake($configs) {
+ if (!is_dir($this->path)) {
+ $this->err($this->path . ' not found');
+ return false;
+ }
+
+ $filename = $this->path . 'database.php';
+ $oldConfigs = array();
+
+ if (file_exists($filename)) {
+ $db = new DATABASE_CONFIG;
+ $temp = get_class_vars(get_class($db));
+
+ foreach ($temp as $configName => $info) {
+ $info = array_merge($this->__defaultConfig, $info);
+
+ if (!isset($info['schema'])) {
+ $info['schema'] = null;
+ }
+ if (!isset($info['encoding'])) {
+ $info['encoding'] = null;
+ }
+ if (!isset($info['port'])) {
+ $info['port'] = null;
+ }
+
+ if ($info['persistent'] === false) {
+ $info['persistent'] = 'false';
+ } else {
+ $info['persistent'] = ($info['persistent'] == true) ? 'true' : 'false';
+ }
+
+ $oldConfigs[] = array(
+ 'name' => $configName,
+ 'driver' => $info['driver'],
+ 'persistent' => $info['persistent'],
+ 'host' => $info['host'],
+ 'port' => $info['port'],
+ 'login' => $info['login'],
+ 'password' => $info['password'],
+ 'database' => $info['database'],
+ 'prefix' => $info['prefix'],
+ 'schema' => $info['schema'],
+ 'encoding' => $info['encoding']
+ );
+ }
+ }
+
+ foreach ($oldConfigs as $key => $oldConfig) {
+ foreach ($configs as $key1 => $config) {
+ if ($oldConfig['name'] == $config['name']) {
+ unset($oldConfigs[$key]);
+ }
+ }
+ }
+
+ $configs = array_merge($oldConfigs, $configs);
+ $out = "__defaultConfig, $config);
+ extract($config);
+
+ $out .= "\tvar \${$name} = array(\n";
+ $out .= "\t\t'driver' => '{$driver}',\n";
+ $out .= "\t\t'persistent' => {$persistent},\n";
+ $out .= "\t\t'host' => '{$host}',\n";
+
+ if ($port) {
+ $out .= "\t\t'port' => {$port},\n";
+ }
+
+ $out .= "\t\t'login' => '{$login}',\n";
+ $out .= "\t\t'password' => '{$password}',\n";
+ $out .= "\t\t'database' => '{$database}',\n";
+
+ if ($schema) {
+ $out .= "\t\t'schema' => '{$schema}',\n";
+ }
+
+ if ($prefix) {
+ $out .= "\t\t'prefix' => '{$prefix}',\n";
+ }
+
+ if ($encoding) {
+ $out .= "\t\t'encoding' => '{$encoding}'\n";
+ }
+
+ $out .= "\t);\n";
+ }
+
+ $out .= "}\n";
+ $out .= "?>";
+ $filename = $this->path.'database.php';
+ return $this->createFile($filename, $out);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/tasks/extract.php b/cake/console/libs/tasks/extract.php
new file mode 100755
index 00000000..7918e8cd
--- /dev/null
+++ b/cake/console/libs/tasks/extract.php
@@ -0,0 +1,685 @@
+params['files']) && !is_array($this->params['files'])) {
+ $this->files = explode(',', $this->params['files']);
+ }
+ if (isset($this->params['path'])) {
+ $this->path = $this->params['path'];
+ } else {
+ $response = '';
+ while ($response == '') {
+ $response = $this->in("What is the full path you would like to extract?\nExample: " . $this->params['root'] . DS . "myapp\n[Q]uit", null, $this->params['working']);
+ if (strtoupper($response) === 'Q') {
+ $this->out('Extract Aborted');
+ $this->_stop();
+ }
+ }
+
+ if (is_dir($response)) {
+ $this->path = $response;
+ } else {
+ $this->err('The directory path you supplied was not found. Please try again.');
+ $this->execute();
+ }
+ }
+
+ if (isset($this->params['debug'])) {
+ $this->path = ROOT;
+ $this->files = array(__FILE__);
+ }
+
+ if (isset($this->params['output'])) {
+ $this->__output = $this->params['output'];
+ } else {
+ $response = '';
+ while ($response == '') {
+ $response = $this->in("What is the full path you would like to output?\nExample: " . $this->path . DS . "locale\n[Q]uit", null, $this->path . DS . "locale");
+ if (strtoupper($response) === 'Q') {
+ $this->out('Extract Aborted');
+ $this->_stop();
+ }
+ }
+
+ if (is_dir($response)) {
+ $this->__output = $response . DS;
+ } else {
+ $this->err('The directory path you supplied was not found. Please try again.');
+ $this->execute();
+ }
+ }
+
+ if (empty($this->files)) {
+ $this->files = $this->__searchDirectory();
+ }
+ $this->__extract();
+ }
+/**
+ * Extract text
+ *
+ * @access private
+ */
+ function __extract() {
+ $this->out('');
+ $this->out('');
+ $this->out(__('Extracting...', true));
+ $this->hr();
+ $this->out(__('Path: ', true). $this->path);
+ $this->out(__('Output Directory: ', true). $this->__output);
+ $this->hr();
+
+ $response = '';
+ $filename = '';
+ while ($response == '') {
+ $response = $this->in(__('Would you like to merge all translations into one file?', true), array('y','n'), 'y');
+ if (strtolower($response) == 'n') {
+ $this->__oneFile = false;
+ } else {
+ while ($filename == '') {
+ $filename = $this->in(__('What should we name this file?', true), null, $this->__filename);
+ if ($filename == '') {
+ $this->out(__('The filesname you supplied was empty. Please try again.', true));
+ }
+ }
+ $this->__filename = $filename;
+ }
+ }
+ $this->__extractTokens();
+ }
+/**
+ * Show help options
+ *
+ * @access public
+ */
+ function help() {
+ $this->out(__('CakePHP Language String Extraction:', true));
+ $this->hr();
+ $this->out(__('The Extract script generates .pot file(s) with translations', true));
+ $this->out(__('By default the .pot file(s) will be place in the locale directory of -app', true));
+ $this->out(__('By default -app is ROOT/app', true));
+ $this->hr();
+ $this->out(__('usage: cake i18n extract [command] [path...]', true));
+ $this->out('');
+ $this->out(__('commands:', true));
+ $this->out(__(' -app [path...]: directory where your application is located', true));
+ $this->out(__(' -root [path...]: path to install', true));
+ $this->out(__(' -core [path...]: path to cake directory', true));
+ $this->out(__(' -path [path...]: Full path to directory to extract strings', true));
+ $this->out(__(' -output [path...]: Full path to output directory', true));
+ $this->out(__(' -files: [comma separated list of files, full path to file is needed]', true));
+ $this->out(__(' cake i18n extract help: Shows this help message.', true));
+ $this->out(__(' -debug: Perform self test.', true));
+ $this->out('');
+ }
+/**
+ * Extract tokens out of all files to be processed
+ *
+ * @access private
+ */
+ function __extractTokens() {
+ foreach ($this->files as $file) {
+ $this->__file = $file;
+ $this->out(sprintf(__('Processing %s...', true), $file));
+
+ $code = file_get_contents($file);
+
+ $this->__findVersion($code, $file);
+ $allTokens = token_get_all($code);
+ $this->__tokens = array();
+ $lineNumber = 1;
+
+ foreach ($allTokens as $token) {
+ if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) {
+ if (is_array($token)) {
+ $token[] = $lineNumber;
+ }
+ $this->__tokens[] = $token;
+ }
+
+ if (is_array($token)) {
+ $lineNumber += count(split("\n", $token[1])) - 1;
+ } else {
+ $lineNumber += count(split("\n", $token)) - 1;
+ }
+ }
+ unset($allTokens);
+ $this->basic();
+ $this->basic('__c');
+ $this->extended();
+ $this->extended('__dc', 2);
+ $this->extended('__n', 0, true);
+ $this->extended('__dn', 2, true);
+ $this->extended('__dcn', 4, true);
+ }
+ $this->__buildFiles();
+ $this->__writeFiles();
+ $this->out('Done.');
+ }
+/**
+ * Will parse __(), __c() functions
+ *
+ * @param string $functionName Function name that indicates translatable string (e.g: '__')
+ * @access public
+ */
+ function basic($functionName = '__') {
+ $count = 0;
+ $tokenCount = count($this->__tokens);
+
+ while (($tokenCount - $count) > 3) {
+ list($countToken, $parenthesis, $middle, $right) = array($this->__tokens[$count], $this->__tokens[$count + 1], $this->__tokens[$count + 2], $this->__tokens[$count + 3]);
+ if (!is_array($countToken)) {
+ $count++;
+ continue;
+ }
+
+ list($type, $string, $line) = $countToken;
+ if (($type == T_STRING) && ($string == $functionName) && ($parenthesis == '(')) {
+
+ if (in_array($right, array(')', ','))
+ && (is_array($middle) && ($middle[0] == T_CONSTANT_ENCAPSED_STRING))) {
+
+ if ($this->__oneFile === true) {
+ $this->__strings[$this->__formatString($middle[1])][$this->__file][] = $line;
+ } else {
+ $this->__strings[$this->__file][$this->__formatString($middle[1])][] = $line;
+ }
+ } else {
+ $this->__markerError($this->__file, $line, $functionName, $count);
+ }
+ }
+ $count++;
+ }
+ }
+/**
+ * Will parse __d(), __dc(), __n(), __dn(), __dcn()
+ *
+ * @param string $functionName Function name that indicates translatable string (e.g: '__')
+ * @param integer $shift Number of parameters to shift to find translateable string
+ * @param boolean $plural Set to true if function supports plural format, false otherwise
+ * @access public
+ */
+ function extended($functionName = '__d', $shift = 0, $plural = false) {
+ $count = 0;
+ $tokenCount = count($this->__tokens);
+
+ while (($tokenCount - $count) > 7) {
+ list($countToken, $firstParenthesis) = array($this->__tokens[$count], $this->__tokens[$count + 1]);
+ if (!is_array($countToken)) {
+ $count++;
+ continue;
+ }
+
+ list($type, $string, $line) = $countToken;
+ if (($type == T_STRING) && ($string == $functionName) && ($firstParenthesis == '(')) {
+ $position = $count;
+ $depth = 0;
+
+ while ($depth == 0) {
+ if ($this->__tokens[$position] == '(') {
+ $depth++;
+ } elseif ($this->__tokens[$position] == ')') {
+ $depth--;
+ }
+ $position++;
+ }
+
+ if ($plural) {
+ $end = $position + $shift + 7;
+
+ if ($this->__tokens[$position + $shift + 5] === ')') {
+ $end = $position + $shift + 5;
+ }
+
+ if (empty($shift)) {
+ list($singular, $firstComma, $plural, $seoncdComma, $endParenthesis) = array($this->__tokens[$position], $this->__tokens[$position + 1], $this->__tokens[$position + 2], $this->__tokens[$position + 3], $this->__tokens[$end]);
+ $condition = ($seoncdComma == ',');
+ } else {
+ list($domain, $firstComma, $singular, $seoncdComma, $plural, $comma3, $endParenthesis) = array($this->__tokens[$position], $this->__tokens[$position + 1], $this->__tokens[$position + 2], $this->__tokens[$position + 3], $this->__tokens[$position + 4], $this->__tokens[$position + 5], $this->__tokens[$end]);
+ $condition = ($comma3 == ',');
+ }
+ $condition = $condition &&
+ (is_array($singular) && ($singular[0] == T_CONSTANT_ENCAPSED_STRING)) &&
+ (is_array($plural) && ($plural[0] == T_CONSTANT_ENCAPSED_STRING));
+ } else {
+ if ($this->__tokens[$position + $shift + 5] === ')') {
+ $comma = $this->__tokens[$position + $shift + 3];
+ $end = $position + $shift + 5;
+ } else {
+ $comma = null;
+ $end = $position + $shift + 3;
+ }
+
+ list($domain, $firstComma, $text, $seoncdComma, $endParenthesis) = array($this->__tokens[$position], $this->__tokens[$position + 1], $this->__tokens[$position + 2], $comma, $this->__tokens[$end]);
+ $condition = ($seoncdComma == ',' || $seoncdComma === null) &&
+ (is_array($domain) && ($domain[0] == T_CONSTANT_ENCAPSED_STRING)) &&
+ (is_array($text) && ($text[0] == T_CONSTANT_ENCAPSED_STRING));
+ }
+
+ if (($endParenthesis == ')') && $condition) {
+ if ($this->__oneFile === true) {
+ if ($plural) {
+ $this->__strings[$this->__formatString($singular[1]) . "\0" . $this->__formatString($plural[1])][$this->__file][] = $line;
+ } else {
+ $this->__strings[$this->__formatString($text[1])][$this->__file][] = $line;
+ }
+ } else {
+ if ($plural) {
+ $this->__strings[$this->__file][$this->__formatString($singular[1]) . "\0" . $this->__formatString($plural[1])][] = $line;
+ } else {
+ $this->__strings[$this->__file][$this->__formatString($text[1])][] = $line;
+ }
+ }
+ } else {
+ $this->__markerError($this->__file, $line, $functionName, $count);
+ }
+ }
+ $count++;
+ }
+ }
+/**
+ * Build the translate template file contents out of obtained strings
+ *
+ * @access private
+ */
+ function __buildFiles() {
+ foreach ($this->__strings as $str => $fileInfo) {
+ $output = '';
+ $occured = $fileList = array();
+
+ if ($this->__oneFile === true) {
+ foreach ($fileInfo as $file => $lines) {
+ $occured[] = "$file:" . join(';', $lines);
+
+ if (isset($this->__fileVersions[$file])) {
+ $fileList[] = $this->__fileVersions[$file];
+ }
+ }
+ $occurances = join("\n#: ", $occured);
+ $occurances = str_replace($this->path, '', $occurances);
+ $output = "#: $occurances\n";
+ $filename = $this->__filename;
+
+ if (strpos($str, "\0") === false) {
+ $output .= "msgid \"$str\"\n";
+ $output .= "msgstr \"\"\n";
+ } else {
+ list($singular, $plural) = explode("\0", $str);
+ $output .= "msgid \"$singular\"\n";
+ $output .= "msgid_plural \"$plural\"\n";
+ $output .= "msgstr[0] \"\"\n";
+ $output .= "msgstr[1] \"\"\n";
+ }
+ $output .= "\n";
+ } else {
+ foreach ($fileInfo as $file => $lines) {
+ $filename = $str;
+ $occured = array("$str:" . join(';', $lines));
+
+ if (isset($this->__fileVersions[$str])) {
+ $fileList[] = $this->__fileVersions[$str];
+ }
+ $occurances = join("\n#: ", $occured);
+ $occurances = str_replace($this->path, '', $occurances);
+ $output .= "#: $occurances\n";
+
+ if (strpos($file, "\0") === false) {
+ $output .= "msgid \"$file\"\n";
+ $output .= "msgstr \"\"\n";
+ } else {
+ list($singular, $plural) = explode("\0", $file);
+ $output .= "msgid \"$singular\"\n";
+ $output .= "msgid_plural \"$plural\"\n";
+ $output .= "msgstr[0] \"\"\n";
+ $output .= "msgstr[1] \"\"\n";
+ }
+ $output .= "\n";
+ }
+ }
+ $this->__store($filename, $output, $fileList);
+ }
+ }
+/**
+ * Prepare a file to be stored
+ *
+ * @param string $file Filename
+ * @param string $input What to store
+ * @param array $fileList File list
+ * @param integer $get Set to 1 to get files to store, false to set
+ * @return mixed If $get == 1, files to store, otherwise void
+ * @access private
+ */
+ function __store($file = 0, $input = 0, $fileList = array(), $get = 0) {
+ static $storage = array();
+
+ if (!$get) {
+ if (isset($storage[$file])) {
+ $storage[$file][1] = array_unique(array_merge($storage[$file][1], $fileList));
+ $storage[$file][] = $input;
+ } else {
+ $storage[$file] = array();
+ $storage[$file][0] = $this->__writeHeader();
+ $storage[$file][1] = $fileList;
+ $storage[$file][2] = $input;
+ }
+ } else {
+ return $storage;
+ }
+ }
+/**
+ * Write the files that need to be stored
+ *
+ * @access private
+ */
+ function __writeFiles() {
+ $output = $this->__store(0, 0, array(), 1);
+ $output = $this->__mergeFiles($output);
+
+ foreach ($output as $file => $content) {
+ $tmp = str_replace(array($this->path, '.php','.ctp','.thtml', '.inc','.tpl' ), '', $file);
+ $tmp = str_replace(DS, '.', $tmp);
+ $file = str_replace('.', '-', $tmp) .'.pot';
+ $fileList = $content[1];
+
+ unset($content[1]);
+
+ $fileList = str_replace(array($this->path), '', $fileList);
+
+ if (count($fileList) > 1) {
+ $fileList = "Generated from files:\n# " . join("\n# ", $fileList);
+ } elseif (count($fileList) == 1) {
+ $fileList = 'Generated from file: ' . join('', $fileList);
+ } else {
+ $fileList = 'No version information was available in the source files.';
+ }
+
+ if (is_file($this->__output . $file)) {
+ $response = '';
+ while ($response == '') {
+ $response = $this->in("\n\nError: ".$file . ' already exists in this location. Overwrite?', array('y','n', 'q'), 'n');
+ if (strtoupper($response) === 'Q') {
+ $this->out('Extract Aborted');
+ $this->_stop();
+ } elseif (strtoupper($response) === 'N') {
+ $response = '';
+ while ($response == '') {
+ $response = $this->in("What would you like to name this file?\nExample: new_" . $file, null, "new_" . $file);
+ $file = $response;
+ }
+ }
+ }
+ }
+ $fp = fopen($this->__output . $file, 'w');
+ fwrite($fp, str_replace('--VERSIONS--', $fileList, join('', $content)));
+ fclose($fp);
+ }
+ }
+/**
+ * Merge output files
+ *
+ * @param array $output Output to merge
+ * @return array Merged output
+ * @access private
+ */
+ function __mergeFiles($output) {
+ foreach ($output as $file => $content) {
+ if (count($content) <= 1 && $file != $this->__filename) {
+ @$output[$this->__filename][1] = array_unique(array_merge($output[$this->__filename][1], $content[1]));
+
+ if (!isset($output[$this->__filename][0])) {
+ $output[$this->__filename][0] = $content[0];
+ }
+ unset($content[0]);
+ unset($content[1]);
+
+ foreach ($content as $msgid) {
+ $output[$this->__filename][] = $msgid;
+ }
+ unset($output[$file]);
+ }
+ }
+ return $output;
+ }
+/**
+ * Build the translation template header
+ *
+ * @return string Translation template header
+ * @access private
+ */
+ function __writeHeader() {
+ $output = "# LANGUAGE translation of CakePHP Application\n";
+ $output .= "# Copyright YEAR NAME \n";
+ $output .= "# --VERSIONS--\n";
+ $output .= "#\n";
+ $output .= "#, fuzzy\n";
+ $output .= "msgid \"\"\n";
+ $output .= "msgstr \"\"\n";
+ $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
+ $output .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
+ $output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
+ $output .= "\"Last-Translator: NAME \\n\"\n";
+ $output .= "\"Language-Team: LANGUAGE \\n\"\n";
+ $output .= "\"MIME-Version: 1.0\\n\"\n";
+ $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
+ $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
+ $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
+ return $output;
+ }
+/**
+ * Find the version number of a file looking for SVN commands
+ *
+ * @param string $code Source code of file
+ * @param string $file File
+ * @access private
+ */
+ function __findVersion($code, $file) {
+ $header = '$Id' . ':';
+ if (preg_match('/\\' . $header . ' [\\w.]* ([\\d]*)/', $code, $versionInfo)) {
+ $version = str_replace(ROOT, '', 'Revision: ' . $versionInfo[1] . ' ' .$file);
+ $this->__fileVersions[$file] = $version;
+ }
+ }
+/**
+ * Format a string to be added as a translateable string
+ *
+ * @param string $string String to format
+ * @return string Formatted string
+ * @access private
+ */
+ function __formatString($string) {
+ $quote = substr($string, 0, 1);
+ $string = substr($string, 1, -1);
+ if ($quote == '"') {
+ $string = stripcslashes($string);
+ } else {
+ $string = strtr($string, array("\\'" => "'", "\\\\" => "\\"));
+ }
+ $string = str_replace("\r\n", "\n", $string);
+ return addcslashes($string, "\0..\37\\\"");
+ }
+/**
+ * Indicate an invalid marker on a processed file
+ *
+ * @param string $file File where invalid marker resides
+ * @param integer $line Line number
+ * @param string $marker Marker found
+ * @param integer $count Count
+ * @access private
+ */
+ function __markerError($file, $line, $marker, $count) {
+ $this->out("Invalid marker content in $file:$line\n* $marker(", true);
+ $count += 2;
+ $tokenCount = count($this->__tokens);
+ $parenthesis = 1;
+
+ while ((($tokenCount - $count) > 0) && $parenthesis) {
+ if (is_array($this->__tokens[$count])) {
+ $this->out($this->__tokens[$count][1], false);
+ } else {
+ $this->out($this->__tokens[$count], false);
+ if ($this->__tokens[$count] == '(') {
+ $parenthesis++;
+ }
+
+ if ($this->__tokens[$count] == ')') {
+ $parenthesis--;
+ }
+ }
+ $count++;
+ }
+ $this->out("\n", true);
+ }
+/**
+ * Search the specified path for files that may contain translateable strings
+ *
+ * @param string $path Path (or set to null to use current)
+ * @return array Files
+ * @access private
+ */
+ function __searchDirectory($path = null) {
+ if ($path === null) {
+ $path = $this->path .DS;
+ }
+ $files = glob("$path*.{php,ctp,thtml,inc,tpl}", GLOB_BRACE);
+ $dirs = glob("$path*", GLOB_ONLYDIR);
+
+ $files = $files ? $files : array();
+ $dirs = $dirs ? $dirs : array();
+
+ foreach ($dirs as $dir) {
+ if (!preg_match("!(^|.+/)(CVS|.svn)$!", $dir)) {
+ $files = array_merge($files, $this->__searchDirectory("$dir" . DS));
+ if (($id = array_search($dir . DS . 'extract.php', $files)) !== FALSE) {
+ unset($files[$id]);
+ }
+ }
+ }
+ return $files;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/tasks/model.php b/cake/console/libs/tasks/model.php
new file mode 100755
index 00000000..779d088f
--- /dev/null
+++ b/cake/console/libs/tasks/model.php
@@ -0,0 +1,938 @@
+args)) {
+ $this->__interactive();
+ }
+
+ if (!empty($this->args[0])) {
+ $model = Inflector::camelize($this->args[0]);
+ if ($this->bake($model)) {
+ if ($this->_checkUnitTest()) {
+ $this->bakeTest($model);
+ }
+ }
+ }
+ }
+/**
+ * Handles interactive baking
+ *
+ * @access private
+ */
+ function __interactive() {
+ $this->hr();
+ $this->out(sprintf("Bake Model\nPath: %s", $this->path));
+ $this->hr();
+ $this->interactive = true;
+
+ $useTable = null;
+ $primaryKey = 'id';
+ $validate = array();
+ $associations = array('belongsTo'=> array(), 'hasOne'=> array(), 'hasMany' => array(), 'hasAndBelongsToMany'=> array());
+
+ $useDbConfig = 'default';
+ $configs = get_class_vars('DATABASE_CONFIG');
+
+ if (!is_array($configs)) {
+ return $this->DbConfig->execute();
+ }
+
+ $connections = array_keys($configs);
+ if (count($connections) > 1) {
+ $useDbConfig = $this->in(__('Use Database Config', true) .':', $connections, 'default');
+ }
+
+ $currentModelName = $this->getName($useDbConfig);
+ $db =& ConnectionManager::getDataSource($useDbConfig);
+ $useTable = Inflector::tableize($currentModelName);
+ $fullTableName = $db->fullTableName($useTable, false);
+ $tableIsGood = false;
+
+ if (array_search($useTable, $this->__tables) === false) {
+ $this->out('');
+ $this->out(sprintf(__("Given your model named '%s', Cake would expect a database table named %s", true), $currentModelName, $fullTableName));
+ $tableIsGood = $this->in(__('Do you want to use this table?', true), array('y','n'), 'y');
+ }
+
+ if (strtolower($tableIsGood) == 'n' || strtolower($tableIsGood) == 'no') {
+ $useTable = $this->in(__('What is the name of the table (enter "null" to use NO table)?', true));
+ }
+
+ while ($tableIsGood == false && strtolower($useTable) != 'null') {
+ if (is_array($this->__tables) && !in_array($useTable, $this->__tables)) {
+ $fullTableName = $db->fullTableName($useTable, false);
+ $this->out($fullTableName . ' does not exist.');
+ $useTable = $this->in(__('What is the name of the table (enter "null" to use NO table)?', true));
+ $tableIsGood = false;
+ } else {
+ $tableIsGood = true;
+ }
+ }
+
+ $wannaDoValidation = $this->in(__('Would you like to supply validation criteria for the fields in your model?', true), array('y','n'), 'y');
+
+ if (in_array($useTable, $this->__tables)) {
+ App::import('Model');
+ $tempModel = new Model(array('name' => $currentModelName, 'table' => $useTable, 'ds' => $useDbConfig));
+
+ $fields = $tempModel->schema();
+ if (!array_key_exists('id', $fields)) {
+ foreach ($fields as $name => $field) {
+ if (isset($field['key']) && $field['key'] == 'primary') {
+ break;
+ }
+ }
+ $primaryKey = $this->in(__('What is the primaryKey?', true), null, $name);
+ }
+ }
+
+ if (array_search($useTable, $this->__tables) !== false && (strtolower($wannaDoValidation) == 'y' || strtolower($wannaDoValidation) == 'yes')) {
+ $validate = $this->doValidation($tempModel);
+ }
+
+ $wannaDoAssoc = $this->in(__('Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)?', true), array('y','n'), 'y');
+ if ((strtolower($wannaDoAssoc) == 'y' || strtolower($wannaDoAssoc) == 'yes')) {
+ $associations = $this->doAssociations($tempModel);
+ }
+
+ $this->out('');
+ $this->hr();
+ $this->out(__('The following Model will be created:', true));
+ $this->hr();
+ $this->out("Name: " . $currentModelName);
+
+ if ($useDbConfig !== 'default') {
+ $this->out("DB Config: " . $useDbConfig);
+ }
+ if ($fullTableName !== Inflector::tableize($currentModelName)) {
+ $this->out("DB Table: " . $fullTableName);
+ }
+ if ($primaryKey != 'id') {
+ $this->out("Primary Key: " . $primaryKey);
+ }
+ if (!empty($validate)) {
+ $this->out("Validation: " . print_r($validate, true));
+ }
+ if (!empty($associations)) {
+ $this->out("Associations:");
+
+ if (!empty($associations['belongsTo'])) {
+ for ($i = 0; $i < count($associations['belongsTo']); $i++) {
+ $this->out(" $currentModelName belongsTo {$associations['belongsTo'][$i]['alias']}");
+ }
+ }
+
+ if (!empty($associations['hasOne'])) {
+ for ($i = 0; $i < count($associations['hasOne']); $i++) {
+ $this->out(" $currentModelName hasOne {$associations['hasOne'][$i]['alias']}");
+ }
+ }
+
+ if (!empty($associations['hasMany'])) {
+ for ($i = 0; $i < count($associations['hasMany']); $i++) {
+ $this->out(" $currentModelName hasMany {$associations['hasMany'][$i]['alias']}");
+ }
+ }
+
+ if (!empty($associations['hasAndBelongsToMany'])) {
+ for ($i = 0; $i < count($associations['hasAndBelongsToMany']); $i++) {
+ $this->out(" $currentModelName hasAndBelongsToMany {$associations['hasAndBelongsToMany'][$i]['alias']}");
+ }
+ }
+ }
+ $this->hr();
+ $looksGood = $this->in(__('Look okay?', true), array('y','n'), 'y');
+
+ if (strtolower($looksGood) == 'y' || strtolower($looksGood) == 'yes') {
+ if ($this->bake($currentModelName, $associations, $validate, $primaryKey, $useTable, $useDbConfig)) {
+ if ($this->_checkUnitTest()) {
+ $this->bakeTest($currentModelName, $useTable, $associations);
+ }
+ }
+ } else {
+ return false;
+ }
+ }
+/**
+ * Handles associations
+ *
+ * @param object $model
+ * @param boolean $interactive
+ * @return array $validate
+ * @access public
+ */
+ function doValidation(&$model, $interactive = true) {
+ if (!is_object($model)) {
+ return false;
+ }
+ $fields = $model->schema();
+
+ if (empty($fields)) {
+ return false;
+ }
+
+ $validate = array();
+
+ $options = array();
+
+ if (class_exists('Validation')) {
+ $parent = get_class_methods(get_parent_class('Validation'));
+ $options = array_diff(get_class_methods('Validation'), $parent);
+ }
+
+ foreach ($fields as $fieldName => $field) {
+ $prompt = 'Field: ' . $fieldName . "\n";
+ $prompt .= 'Type: ' . $field['type'] . "\n";
+ $prompt .= '---------------------------------------------------------------'."\n";
+ $prompt .= 'Please select one of the following validation options:'."\n";
+ $prompt .= '---------------------------------------------------------------'."\n";
+
+ sort($options);
+
+ $skip = 1;
+ foreach ($options as $key => $option) {
+ if ($option{0} != '_' && strtolower($option) != 'getinstance') {
+ $prompt .= "{$skip} - {$option}\n";
+ $choices[$skip] = strtolower($option);
+ $skip++;
+ }
+ }
+
+ $methods = array_flip($choices);
+
+ $prompt .= "{$skip} - Do not do any validation on this field.\n";
+ $prompt .= "... or enter in a valid regex validation string.\n";
+
+ $guess = $skip;
+ if ($field['null'] != 1 && $fieldName != $model->primaryKey && !in_array($fieldName, array('created', 'modified', 'updated'))) {
+ if ($fieldName == 'email') {
+ $guess = $methods['email'];
+ } elseif ($field['type'] == 'string') {
+ $guess = $methods['notempty'];
+ } elseif ($field['type'] == 'integer') {
+ $guess = $methods['numeric'];
+ } elseif ($field['type'] == 'boolean') {
+ $guess = $methods['numeric'];
+ } elseif ($field['type'] == 'datetime') {
+ $guess = $methods['date'];
+ }
+ }
+
+ if ($interactive === true) {
+ $this->out('');
+ $choice = $this->in($prompt, null, $guess);
+ } else {
+ $choice = $guess;
+ }
+ if ($choice != $skip) {
+ if (is_numeric($choice) && isset($choices[$choice])) {
+ $validate[$fieldName] = $choices[$choice];
+ } else {
+ $validate[$fieldName] = $choice;
+ }
+ }
+ }
+ return $validate;
+ }
+
+/**
+ * Handles associations
+ *
+ * @param object $model
+ * @param boolean $interactive
+ * @return array $assocaitons
+ * @access public
+ */
+ function doAssociations(&$model, $interactive = true) {
+
+ if (!is_object($model)) {
+ return false;
+ }
+ $this->out(__('One moment while the associations are detected.', true));
+
+ $fields = $model->schema();
+
+ if (empty($fields)) {
+ return false;
+ }
+
+ $primaryKey = $model->primaryKey;
+ $foreignKey = $this->_modelKey($model->name);
+
+ $associations = array('belongsTo' => array(), 'hasMany' => array(), 'hasOne'=> array(), 'hasAndBelongsToMany' => array());
+ $possibleKeys = array();
+
+ //Look for belongsTo
+ $i = 0;
+ foreach ($fields as $fieldName => $field) {
+ $offset = strpos($fieldName, '_id');
+ if ($fieldName != $model->primaryKey && $offset !== false) {
+ $tmpModelName = $this->_modelNameFromKey($fieldName);
+ $associations['belongsTo'][$i]['alias'] = $tmpModelName;
+ $associations['belongsTo'][$i]['className'] = $tmpModelName;
+ $associations['belongsTo'][$i]['foreignKey'] = $fieldName;
+ $i++;
+ }
+ }
+ //Look for hasOne and hasMany and hasAndBelongsToMany
+ $i = $j = 0;
+
+ foreach ($this->__tables as $otherTable) {
+ App::import('Model');
+ $tmpModelName = $this->_modelName($otherTable);
+ $tempOtherModel = & new Model(array('name' => $tmpModelName, 'table' => $otherTable, 'ds' => $model->useDbConfig));
+ $modelFieldsTemp = $tempOtherModel->schema();
+
+ $offset = strpos($otherTable, $model->table . '_');
+ $otherOffset = strpos($otherTable, '_' . $model->table);
+
+ foreach ($modelFieldsTemp as $fieldName => $field) {
+ if ($field['type'] == 'integer' || $field['type'] == 'string') {
+ $possibleKeys[$otherTable][] = $fieldName;
+ }
+ if ($fieldName != $model->primaryKey && $fieldName == $foreignKey && $offset === false && $otherOffset === false) {
+ $associations['hasOne'][$j]['alias'] = $tempOtherModel->name;
+ $associations['hasOne'][$j]['className'] = $tempOtherModel->name;
+ $associations['hasOne'][$j]['foreignKey'] = $fieldName;
+
+ $associations['hasMany'][$j]['alias'] = $tempOtherModel->name;
+ $associations['hasMany'][$j]['className'] = $tempOtherModel->name;
+ $associations['hasMany'][$j]['foreignKey'] = $fieldName;
+ $j++;
+ }
+ }
+
+ if ($offset !== false) {
+ $offset = strlen($model->table . '_');
+ $tmpModelName = $this->_modelName(substr($otherTable, $offset));
+ $associations['hasAndBelongsToMany'][$i]['alias'] = $tmpModelName;
+ $associations['hasAndBelongsToMany'][$i]['className'] = $tmpModelName;
+ $associations['hasAndBelongsToMany'][$i]['foreignKey'] = $foreignKey;
+ $associations['hasAndBelongsToMany'][$i]['associationForeignKey'] = $this->_modelKey($tmpModelName);
+ $associations['hasAndBelongsToMany'][$i]['joinTable'] = $otherTable;
+ $i++;
+ }
+
+ if ($otherOffset !== false) {
+ $tmpModelName = $this->_modelName(substr($otherTable, 0, $otherOffset));
+ $associations['hasAndBelongsToMany'][$i]['alias'] = $tmpModelName;
+ $associations['hasAndBelongsToMany'][$i]['className'] = $tmpModelName;
+ $associations['hasAndBelongsToMany'][$i]['foreignKey'] = $foreignKey;
+ $associations['hasAndBelongsToMany'][$i]['associationForeignKey'] = $this->_modelKey($tmpModelName);
+ $associations['hasAndBelongsToMany'][$i]['joinTable'] = $otherTable;
+ $i++;
+ }
+ }
+
+ if ($interactive !== true) {
+ unset($associations['hasOne']);
+ }
+
+ if ($interactive === true) {
+ $this->hr();
+ if (empty($associations)) {
+ $this->out(__('None found.', true));
+ } else {
+ $this->out(__('Please confirm the following associations:', true));
+ $this->hr();
+ foreach ($associations as $type => $settings) {
+ if (!empty($associations[$type])) {
+ $count = count($associations[$type]);
+ $response = 'y';
+ for ($i = 0; $i < $count; $i++) {
+ $prompt = "{$model->name} {$type} {$associations[$type][$i]['alias']}";
+ $response = $this->in("{$prompt}?", array('y','n'), 'y');
+
+ if ('n' == strtolower($response) || 'no' == strtolower($response)) {
+ unset($associations[$type][$i]);
+ } else {
+ if ($model->name === $associations[$type][$i]['alias']) {
+ if ($type === 'belongsTo') {
+ $alias = 'Parent' . $associations[$type][$i]['alias'];
+ }
+ if ($type === 'hasOne' || $type === 'hasMany') {
+ $alias = 'Child' . $associations[$type][$i]['alias'];
+ }
+
+ $alternateAlias = $this->in(sprintf(__('This is a self join. Use %s as the alias', true), $alias), array('y', 'n'), 'y');
+
+ if ('n' == strtolower($alternateAlias) || 'no' == strtolower($alternateAlias)) {
+ $associations[$type][$i]['alias'] = $this->in(__('Specify an alternate alias.', true));
+ } else {
+ $associations[$type][$i]['alias'] = $alias;
+ }
+ }
+ }
+ }
+ $associations[$type] = array_merge($associations[$type]);
+ }
+ }
+ }
+
+ $wannaDoMoreAssoc = $this->in(__('Would you like to define some additional model associations?', true), array('y','n'), 'n');
+
+ while ((strtolower($wannaDoMoreAssoc) == 'y' || strtolower($wannaDoMoreAssoc) == 'yes')) {
+ $assocs = array(1 => 'belongsTo', 2 => 'hasOne', 3 => 'hasMany', 4 => 'hasAndBelongsToMany');
+ $bad = true;
+ while ($bad) {
+ $this->out(__('What is the association type?', true));
+ $prompt = "1. belongsTo\n";
+ $prompt .= "2. hasOne\n";
+ $prompt .= "3. hasMany\n";
+ $prompt .= "4. hasAndBelongsToMany\n";
+ $assocType = intval($this->in($prompt, null, __("Enter a number", true)));
+
+ if (intval($assocType) < 1 || intval($assocType) > 4) {
+ $this->out(__('The selection you entered was invalid. Please enter a number between 1 and 4.', true));
+ } else {
+ $bad = false;
+ }
+ }
+ $this->out(__('For the following options be very careful to match your setup exactly. Any spelling mistakes will cause errors.', true));
+ $this->hr();
+ $alias = $this->in(__('What is the alias for this association?', true));
+ $className = $this->in(sprintf(__('What className will %s use?', true), $alias), null, $alias );
+ $suggestedForeignKey = null;
+ if ($assocType == '1') {
+ $showKeys = $possibleKeys[$model->table];
+ $suggestedForeignKey = $this->_modelKey($alias);
+ } else {
+ $otherTable = Inflector::tableize($className);
+ if (in_array($otherTable, $this->__tables)) {
+ if ($assocType < '4') {
+ $showKeys = $possibleKeys[$otherTable];
+ } else {
+ $showKeys = null;
+ }
+ } else {
+ $otherTable = $this->in(__('What is the table for this model?', true));
+ $showKeys = $possibleKeys[$otherTable];
+ }
+ $suggestedForeignKey = $this->_modelKey($model->name);
+ }
+ if (!empty($showKeys)) {
+ $this->out(__('A helpful List of possible keys', true));
+ for ($i = 0; $i < count($showKeys); $i++) {
+ $this->out($i + 1 . ". " . $showKeys[$i]);
+ }
+ $foreignKey = $this->in(__('What is the foreignKey?', true), null, __("Enter a number", true));
+ if (intval($foreignKey) > 0 && intval($foreignKey) <= $i ) {
+ $foreignKey = $showKeys[intval($foreignKey) - 1];
+ }
+ }
+ if (!isset($foreignKey)) {
+ $foreignKey = $this->in(__('What is the foreignKey? Specify your own.', true), null, $suggestedForeignKey);
+ }
+ if ($assocType == '4') {
+ $associationForeignKey = $this->in(__('What is the associationForeignKey?', true), null, $this->_modelKey($model->name));
+ $joinTable = $this->in(__('What is the joinTable?', true));
+ }
+ $associations[$assocs[$assocType]] = array_values((array)$associations[$assocs[$assocType]]);
+ $count = count($associations[$assocs[$assocType]]);
+ $i = ($count > 0) ? $count : 0;
+ $associations[$assocs[$assocType]][$i]['alias'] = $alias;
+ $associations[$assocs[$assocType]][$i]['className'] = $className;
+ $associations[$assocs[$assocType]][$i]['foreignKey'] = $foreignKey;
+ if ($assocType == '4') {
+ $associations[$assocs[$assocType]][$i]['associationForeignKey'] = $associationForeignKey;
+ $associations[$assocs[$assocType]][$i]['joinTable'] = $joinTable;
+ }
+ $wannaDoMoreAssoc = $this->in(__('Define another association?', true), array('y','n'), 'y');
+ }
+ }
+ return $associations;
+ }
+/**
+ * Assembles and writes a Model file.
+ *
+ * @param mixed $name Model name or object
+ * @param mixed $associations if array and $name is not an object assume Model associations array otherwise boolean interactive
+ * @param array $validate Validation rules
+ * @param string $primaryKey Primary key to use
+ * @param string $useTable Table to use
+ * @param string $useDbConfig Database configuration setting to use
+ * @access private
+ */
+ function bake($name, $associations = array(), $validate = array(), $primaryKey = 'id', $useTable = null, $useDbConfig = 'default') {
+
+ if (is_object($name)) {
+ if (!is_array($associations)) {
+ $associations = $this->doAssociations($name, $associations);
+ $validate = $this->doValidation($name, $associations);
+ }
+ $primaryKey = $name->primaryKey;
+ $useTable = $name->table;
+ $useDbConfig = $name->useDbConfig;
+ $name = $name->name;
+ }
+
+ $out = "plugin}AppModel {\n\n";
+ $out .= "\tvar \$name = '{$name}';\n";
+
+ if ($useDbConfig !== 'default') {
+ $out .= "\tvar \$useDbConfig = '$useDbConfig';\n";
+ }
+
+ if (($useTable && $useTable !== Inflector::tableize($name)) || $useTable === false) {
+ $table = "'$useTable'";
+ if (!$useTable) {
+ $table = 'false';
+ }
+ $out .= "\tvar \$useTable = $table;\n";
+ }
+
+ if ($primaryKey !== 'id') {
+ $out .= "\tvar \$primaryKey = '$primaryKey';\n";
+ }
+
+ $validateCount = count($validate);
+ if (is_array($validate) && $validateCount > 0) {
+ $out .= "\tvar \$validate = array(\n";
+ $keys = array_keys($validate);
+ for ($i = 0; $i < $validateCount; $i++) {
+ $val = "'" . $validate[$keys[$i]] . "'";
+ $out .= "\t\t'" . $keys[$i] . "' => array({$val})";
+ if ($i + 1 < $validateCount) {
+ $out .= ",";
+ }
+ $out .= "\n";
+ }
+ $out .= "\t);\n";
+ }
+ $out .= "\n";
+
+ if (!empty($associations)) {
+ if (!empty($associations['belongsTo']) || !empty($associations['hasOne']) || !empty($associations['hasMany']) || !empty($associations['hasAndBelongsToMany'])) {
+ $out.= "\t//The Associations below have been created with all possible keys, those that are not needed can be removed\n";
+ }
+
+ if (!empty($associations['belongsTo'])) {
+ $out .= "\tvar \$belongsTo = array(\n";
+ $belongsToCount = count($associations['belongsTo']);
+
+ for ($i = 0; $i < $belongsToCount; $i++) {
+ $out .= "\t\t'{$associations['belongsTo'][$i]['alias']}' => array(\n";
+ $out .= "\t\t\t'className' => '{$associations['belongsTo'][$i]['className']}',\n";
+ $out .= "\t\t\t'foreignKey' => '{$associations['belongsTo'][$i]['foreignKey']}',\n";
+ $out .= "\t\t\t'conditions' => '',\n";
+ $out .= "\t\t\t'fields' => '',\n";
+ $out .= "\t\t\t'order' => ''\n";
+ $out .= "\t\t)";
+ if ($i + 1 < $belongsToCount) {
+ $out .= ",";
+ }
+ $out .= "\n";
+
+ }
+ $out .= "\t);\n\n";
+ }
+
+ if (!empty($associations['hasOne'])) {
+ $out .= "\tvar \$hasOne = array(\n";
+ $hasOneCount = count($associations['hasOne']);
+
+ for ($i = 0; $i < $hasOneCount; $i++) {
+ $out .= "\t\t'{$associations['hasOne'][$i]['alias']}' => array(\n";
+ $out .= "\t\t\t'className' => '{$associations['hasOne'][$i]['className']}',\n";
+ $out .= "\t\t\t'foreignKey' => '{$associations['hasOne'][$i]['foreignKey']}',\n";
+ $out .= "\t\t\t'dependent' => false,\n";
+ $out .= "\t\t\t'conditions' => '',\n";
+ $out .= "\t\t\t'fields' => '',\n";
+ $out .= "\t\t\t'order' => ''\n";
+ $out .= "\t\t)";
+ if ($i + 1 < $hasOneCount) {
+ $out .= ",";
+ }
+ $out .= "\n";
+
+ }
+ $out .= "\t);\n\n";
+ }
+
+ if (!empty($associations['hasMany'])) {
+ $out .= "\tvar \$hasMany = array(\n";
+ $hasManyCount = count($associations['hasMany']);
+
+ for ($i = 0; $i < $hasManyCount; $i++) {
+ $out .= "\t\t'{$associations['hasMany'][$i]['alias']}' => array(\n";
+ $out .= "\t\t\t'className' => '{$associations['hasMany'][$i]['className']}',\n";
+ $out .= "\t\t\t'foreignKey' => '{$associations['hasMany'][$i]['foreignKey']}',\n";
+ $out .= "\t\t\t'dependent' => false,\n";
+ $out .= "\t\t\t'conditions' => '',\n";
+ $out .= "\t\t\t'fields' => '',\n";
+ $out .= "\t\t\t'order' => '',\n";
+ $out .= "\t\t\t'limit' => '',\n";
+ $out .= "\t\t\t'offset' => '',\n";
+ $out .= "\t\t\t'exclusive' => '',\n";
+ $out .= "\t\t\t'finderQuery' => '',\n";
+ $out .= "\t\t\t'counterQuery' => ''\n";
+ $out .= "\t\t)";
+ if ($i + 1 < $hasManyCount) {
+ $out .= ",";
+ }
+ $out .= "\n";
+ }
+ $out .= "\t);\n\n";
+ }
+
+ if (!empty($associations['hasAndBelongsToMany'])) {
+ $out .= "\tvar \$hasAndBelongsToMany = array(\n";
+ $hasAndBelongsToManyCount = count($associations['hasAndBelongsToMany']);
+
+ for ($i = 0; $i < $hasAndBelongsToManyCount; $i++) {
+ $out .= "\t\t'{$associations['hasAndBelongsToMany'][$i]['alias']}' => array(\n";
+ $out .= "\t\t\t'className' => '{$associations['hasAndBelongsToMany'][$i]['className']}',\n";
+ $out .= "\t\t\t'joinTable' => '{$associations['hasAndBelongsToMany'][$i]['joinTable']}',\n";
+ $out .= "\t\t\t'foreignKey' => '{$associations['hasAndBelongsToMany'][$i]['foreignKey']}',\n";
+ $out .= "\t\t\t'associationForeignKey' => '{$associations['hasAndBelongsToMany'][$i]['associationForeignKey']}',\n";
+ $out .= "\t\t\t'unique' => true,\n";
+ $out .= "\t\t\t'conditions' => '',\n";
+ $out .= "\t\t\t'fields' => '',\n";
+ $out .= "\t\t\t'order' => '',\n";
+ $out .= "\t\t\t'limit' => '',\n";
+ $out .= "\t\t\t'offset' => '',\n";
+ $out .= "\t\t\t'finderQuery' => '',\n";
+ $out .= "\t\t\t'deleteQuery' => '',\n";
+ $out .= "\t\t\t'insertQuery' => ''\n";
+ $out .= "\t\t)";
+ if ($i + 1 < $hasAndBelongsToManyCount) {
+ $out .= ",";
+ }
+ $out .= "\n";
+ }
+ $out .= "\t);\n\n";
+ }
+ }
+ $out .= "}\n";
+ $out .= "?>";
+ $filename = $this->path . Inflector::underscore($name) . '.php';
+ $this->out("\nBaking model class for $name...");
+ return $this->createFile($filename, $out);
+ }
+
+/**
+ * Assembles and writes a unit test file
+ *
+ * @param string $className Model class name
+ * @access private
+ */
+ function bakeTest($className, $useTable = null, $associations = array()) {
+ $results = $this->fixture($className, $useTable);
+
+ if ($results) {
+ $fixtureInc = 'app';
+ if ($this->plugin) {
+ $fixtureInc = 'plugin.'.Inflector::underscore($this->plugin);
+ }
+
+ $fixture[] = "'{$fixtureInc}." . Inflector::underscore($className) ."'";
+
+ if (!empty($associations)) {
+ $assoc[] = Set::extract($associations, 'belongsTo.{n}.className');
+ $assoc[] = Set::extract($associations, 'hasOne.{n}.className');
+ $assoc[] = Set::extract($associations, 'hasMany.{n}.className');
+ foreach ($assoc as $key => $value) {
+ if (is_array($value)) {
+ foreach ($value as $class) {
+ $fixture[] = "'{$fixtureInc}." . Inflector::underscore($class) ."'";
+ }
+ }
+ }
+ }
+ $fixture = join(", ", $fixture);
+
+ $import = $className;
+ if (isset($this->plugin)) {
+ $import = $this->plugin . '.' . $className;
+ }
+
+ $out = "App::import('Model', '$import');\n\n";
+ $out .= "class {$className}TestCase extends CakeTestCase {\n";
+ $out .= "\tvar \${$className} = null;\n";
+ $out .= "\tvar \$fixtures = array($fixture);\n\n";
+ $out .= "\tfunction startTest() {\n";
+ $out .= "\t\t\$this->{$className} =& ClassRegistry::init('{$className}');\n";
+ $out .= "\t}\n\n";
+ $out .= "\tfunction test{$className}Instance() {\n";
+ $out .= "\t\t\$this->assertTrue(is_a(\$this->{$className}, '{$className}'));\n";
+ $out .= "\t}\n\n";
+ $out .= "\tfunction test{$className}Find() {\n";
+ $out .= "\t\t\$this->{$className}->recursive = -1;\n";
+ $out .= "\t\t\$results = \$this->{$className}->find('first');\n\t\t\$this->assertTrue(!empty(\$results));\n\n";
+ $out .= "\t\t\$expected = array('$className' => array(\n$results\n\t\t));\n";
+ $out .= "\t\t\$this->assertEqual(\$results, \$expected);\n";
+ $out .= "\t}\n";
+ $out .= "}\n";
+
+ $path = MODEL_TESTS;
+ if (isset($this->plugin)) {
+ $pluginPath = 'plugins' . DS . Inflector::underscore($this->plugin) . DS;
+ $path = APP . $pluginPath . 'tests' . DS . 'cases' . DS . 'models' . DS;
+ }
+
+ $filename = Inflector::underscore($className).'.test.php';
+ $this->out("\nBaking unit test for $className...");
+
+ $header = '$Id';
+ $content = "";
+ return $this->createFile($path . $filename, $content);
+ }
+ return false;
+ }
+/**
+ * outputs the a list of possible models or controllers from database
+ *
+ * @param string $useDbConfig Database configuration name
+ * @access public
+ */
+ function listAll($useDbConfig = 'default', $interactive = true) {
+ $db =& ConnectionManager::getDataSource($useDbConfig);
+ $usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix'];
+ if ($usePrefix) {
+ $tables = array();
+ foreach ($db->listSources() as $table) {
+ if (!strncmp($table, $usePrefix, strlen($usePrefix))) {
+ $tables[] = substr($table, strlen($usePrefix));
+ }
+ }
+ } else {
+ $tables = $db->listSources();
+ }
+ if (empty($tables)) {
+ $this->err(__('Your database does not have any tables.', true));
+ $this->_stop();
+ }
+
+ $this->__tables = $tables;
+
+ if ($interactive === true) {
+ $this->out(__('Possible Models based on your current database:', true));
+ $this->_modelNames = array();
+ $count = count($tables);
+ for ($i = 0; $i < $count; $i++) {
+ $this->_modelNames[] = $this->_modelName($tables[$i]);
+ $this->out($i + 1 . ". " . $this->_modelNames[$i]);
+ }
+ }
+ }
+/**
+ * Forces the user to specify the model he wants to bake, and returns the selected model name.
+ *
+ * @return string the model name
+ * @access public
+ */
+ function getName($useDbConfig) {
+ $this->listAll($useDbConfig);
+
+ $enteredModel = '';
+
+ while ($enteredModel == '') {
+ $enteredModel = $this->in(__("Enter a number from the list above, type in the name of another model, or 'q' to exit", true), null, 'q');
+
+ if ($enteredModel === 'q') {
+ $this->out(__("Exit", true));
+ $this->_stop();
+ }
+
+ if ($enteredModel == '' || intval($enteredModel) > count($this->_modelNames)) {
+ $this->err(__("The model name you supplied was empty, or the number you selected was not an option. Please try again.", true));
+ $enteredModel = '';
+ }
+ }
+
+ if (intval($enteredModel) > 0 && intval($enteredModel) <= count($this->_modelNames)) {
+ $currentModelName = $this->_modelNames[intval($enteredModel) - 1];
+ } else {
+ $currentModelName = $enteredModel;
+ }
+
+ return $currentModelName;
+ }
+/**
+ * Displays help contents
+ *
+ * @access public
+ */
+ function help() {
+ $this->hr();
+ $this->out("Usage: cake bake model ");
+ $this->hr();
+ $this->out('Commands:');
+ $this->out("\n\tmodel\n\t\tbakes model in interactive mode.");
+ $this->out("\n\tmodel \n\t\tbakes model file with no associations or validation");
+ $this->out("");
+ $this->_stop();
+ }
+/**
+ * Builds the tests fixtures for the model and create the file
+ *
+ * @param string $model the name of the model
+ * @param string $useTable table name
+ * @return array $records, used in ModelTask::bakeTest() to create $expected
+ * @todo move this to a task
+ */
+ function fixture($model, $useTable = null) {
+ if (!class_exists('CakeSchema')) {
+ App::import('Model', 'Schema');
+ }
+ $out = "\nclass {$model}Fixture extends CakeTestFixture {\n";
+ $out .= "\tvar \$name = '$model';\n";
+
+ if (!$useTable) {
+ $useTable = Inflector::tableize($model);
+ } else {
+ $out .= "\tvar \$table = '$useTable';\n";
+ }
+ $schema = new CakeSchema();
+ $data = $schema->read(array('models' => false));
+
+ if (!isset($data['tables'][$useTable])) {
+ return false;
+ }
+ $tables[$model] = $data['tables'][$useTable];
+
+ foreach ($tables as $table => $fields) {
+ if (!is_numeric($table) && $table !== 'missing') {
+ $out .= "\tvar \$fields = array(\n";
+ $records = array();
+ if (is_array($fields)) {
+ $cols = array();
+ foreach ($fields as $field => $value) {
+ if ($field != 'indexes') {
+ if (is_string($value)) {
+ $type = $value;
+ $value = array('type'=> $type);
+ }
+ $col = "\t\t'{$field}' => array('type'=>'" . $value['type'] . "', ";
+
+ switch ($value['type']) {
+ case 'integer':
+ $insert = 1;
+ break;
+ case 'string';
+ $insert = "Lorem ipsum dolor sit amet";
+ if (!empty($value['length'])) {
+ $insert = substr($insert, 0, (int)$value['length'] - 2);
+ }
+ $insert = "'$insert'";
+ break;
+ case 'datetime':
+ $ts = date('Y-m-d H:i:s');
+ $insert = "'$ts'";
+ break;
+ case 'date':
+ $ts = date('Y-m-d');
+ $insert = "'$ts'";
+ break;
+ case 'time':
+ $ts = date('H:i:s');
+ $insert = "'$ts'";
+ break;
+ case 'boolean':
+ $insert = 1;
+ break;
+ case 'text':
+ $insert =
+ "'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida,";
+ $insert .= "phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam,";
+ $insert .= "vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit,";
+ $insert .= "feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.'";
+ break;
+ }
+ $records[] = "\t\t'$field' => $insert";
+ unset($value['type']);
+ $col .= join(', ', $schema->__values($value));
+ } else {
+ $col = "\t\t'indexes' => array(";
+ $props = array();
+ foreach ((array)$value as $key => $index) {
+ $props[] = "'{$key}' => array(" . join(', ', $schema->__values($index)) . ")";
+ }
+ $col .= join(', ', $props);
+ }
+ $col .= ")";
+ $cols[] = $col;
+ }
+ $out .= join(",\n", $cols);
+ }
+ $out .= "\n\t);\n";
+ }
+ }
+ $records = join(",\n", $records);
+ $out .= "\tvar \$records = array(array(\n$records\n\t));\n";
+ $out .= "}\n";
+ $path = TESTS . DS . 'fixtures' . DS;
+ if (isset($this->plugin)) {
+ $pluginPath = 'plugins' . DS . Inflector::underscore($this->plugin) . DS;
+ $path = APP . $pluginPath . 'tests' . DS . 'fixtures' . DS;
+ }
+ $filename = Inflector::underscore($model) . '_fixture.php';
+ $header = '$Id';
+ $content = "";
+ $this->out("\nBaking test fixture for $model...");
+ if ($this->createFile($path . $filename, $content)) {
+ return str_replace("\t\t", "\t\t\t", $records);
+ }
+ return false;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/tasks/plugin.php b/cake/console/libs/tasks/plugin.php
new file mode 100755
index 00000000..a2318132
--- /dev/null
+++ b/cake/console/libs/tasks/plugin.php
@@ -0,0 +1,202 @@
+path = APP . 'plugins' . DS;
+ }
+/**
+ * Execution method always used for tasks
+ *
+ * @return void
+ */
+ function execute() {
+ if (empty($this->params['skel'])) {
+ $this->params['skel'] = '';
+ if (is_dir(CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'skel') === true) {
+ $this->params['skel'] = CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'skel';
+ }
+ }
+
+ $plugin = null;
+
+ if (isset($this->args[0])) {
+ $plugin = Inflector::camelize($this->args[0]);
+ $pluginPath = Inflector::underscore($plugin) . DS;
+ $this->Dispatch->shiftArgs();
+ if (is_dir($this->path . $pluginPath)) {
+ $this->out(sprintf('Plugin: %s', $plugin));
+ $this->out(sprintf('Path: %s', $this->path . $pluginPath));
+ $this->hr();
+ } elseif (isset($this->args[0])) {
+ $this->err(sprintf('%s in path %s not found.', $plugin, $this->path . $pluginPath));
+ $this->_stop();
+ } else {
+ $this->__interactive($plugin);
+ }
+ }
+
+ if (isset($this->args[0])) {
+ $task = Inflector::classify($this->args[0]);
+ $this->Dispatch->shiftArgs();
+ if (in_array($task, $this->tasks)) {
+ $this->{$task}->plugin = $plugin;
+ $this->{$task}->path = $this->path . $pluginPath . Inflector::underscore(Inflector::pluralize($task)) . DS;
+
+ if (!is_dir($this->{$task}->path)) {
+ $this->err(sprintf(__("%s directory could not be found.\nBe sure you have created %s", true), $task, $this->{$task}->path));
+ }
+ $this->{$task}->loadTasks();
+ $this->{$task}->execute();
+ }
+ }
+ }
+
+/**
+ * Interactive interface
+ *
+ * @access private
+ * @return void
+ */
+ function __interactive($plugin = null) {
+ while ($plugin === null) {
+ $plugin = $this->in(__('Enter the name of the plugin in CamelCase format', true));
+ }
+
+ if (!$this->bake($plugin)) {
+ $this->err(sprintf(__("An error occured trying to bake: %s in %s", true), $plugin, $this->path . $pluginPath));
+ }
+ }
+
+/**
+ * Bake the plugin, create directories and files
+ *
+ * @params $plugin name of the plugin in CamelCased format
+ * @access public
+ * @return bool
+ */
+ function bake($plugin) {
+
+ $pluginPath = Inflector::underscore($plugin);
+
+ $this->hr();
+ $this->out("Plugin Name: $plugin");
+ $this->out("Plugin Directory: {$this->path}{$pluginPath}");
+ $this->hr();
+
+
+ $looksGood = $this->in('Look okay?', array('y', 'n', 'q'), 'y');
+
+ if (strtolower($looksGood) == 'y' || strtolower($looksGood) == 'yes') {
+ $verbose = $this->in(__('Do you want verbose output?', true), array('y', 'n'), 'n');
+
+ $Folder = new Folder($this->path . $pluginPath);
+ $directories = array('models' . DS . 'behaviors', 'controllers' . DS . 'components', 'views' . DS . 'helpers');
+
+ foreach ($directories as $directory) {
+ $Folder->create($this->path . $pluginPath . DS . $directory);
+ }
+
+ if (strtolower($verbose) == 'y' || strtolower($verbose) == 'yes') {
+ foreach ($Folder->messages() as $message) {
+ $this->out($message);
+ }
+ }
+
+ $errors = $Folder->errors();
+ if (!empty($errors)) {
+ return false;
+ }
+
+ $controllerFileName = $pluginPath . '_app_controller.php';
+
+ $out = "";
+ $this->createFile($this->path . $pluginPath. DS . $controllerFileName, $out);
+
+ $modelFileName = $pluginPath . '_app_model.php';
+
+ $out = "";
+ $this->createFile($this->path . $pluginPath . DS . $modelFileName, $out);
+
+ $this->hr();
+ $this->out(sprintf(__("Created: %s in %s", true), $plugin, $this->path . $pluginPath));
+ $this->hr();
+ }
+
+ return true;
+ }
+/**
+ * Help
+ *
+ * @return void
+ * @access public
+ */
+ function help() {
+ $this->hr();
+ $this->out("Usage: cake bake plugin ...");
+ $this->hr();
+ $this->out('Commands:');
+ $this->out("\n\tplugin \n\t\tbakes plugin directory structure");
+ $this->out("\n\tplugin model\n\t\tbakes model. Run 'cake bake model help' for more info.");
+ $this->out("\n\tplugin controller\n\t\tbakes controller. Run 'cake bake controller help' for more info.");
+ $this->out("\n\tplugin view\n\t\tbakes view. Run 'cake bake view help' for more info.");
+ $this->out("");
+ $this->_stop();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/tasks/project.php b/cake/console/libs/tasks/project.php
new file mode 100755
index 00000000..f7a0c302
--- /dev/null
+++ b/cake/console/libs/tasks/project.php
@@ -0,0 +1,282 @@
+args[0])) {
+ $project = $this->args[0];
+ $this->Dispatch->shiftArgs();
+ }
+ }
+
+ if ($project) {
+ $this->Dispatch->parseParams(array('-app', $project));
+ $project = $this->params['working'];
+ }
+
+ if (empty($this->params['skel'])) {
+ $this->params['skel'] = '';
+ if (is_dir(CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'skel') === true) {
+ $this->params['skel'] = CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'skel';
+ }
+ }
+
+ while (!$project) {
+ $project = $this->in("What is the full path for this app including the app directory name?\nExample: ".$this->params['working'] . DS . "myapp", null, $this->params['working'] . DS . 'myapp');
+ }
+
+ if ($project) {
+ $response = false;
+ while ($response == false && is_dir($project) === true && file_exists($project . 'config' . 'core.php')) {
+ $response = $this->in('A project already exists in this location: '.$project.' Overwrite?', array('y','n'), 'n');
+ if (strtolower($response) === 'n') {
+ $response = $project = false;
+ }
+ }
+ }
+
+ if ($this->bake($project)) {
+ $path = Folder::slashTerm($project);
+ if ($this->createHome($path)) {
+ $this->out(__('Welcome page created', true));
+ } else {
+ $this->out(__('The Welcome page was NOT created', true));
+ }
+
+ if ($this->securitySalt($path) === true ) {
+ $this->out(__('Random hash key created for \'Security.salt\'', true));
+ } else {
+ $this->err(sprintf(__('Unable to generate random hash for \'Security.salt\', you should change it in %s', true), CONFIGS . 'core.php'));
+ }
+
+ $corePath = $this->corePath($path);
+ if ($corePath === true ) {
+ $this->out(sprintf(__('CAKE_CORE_INCLUDE_PATH set to %s in webroot/index.php', true), CAKE_CORE_INCLUDE_PATH));
+ $this->out(sprintf(__('CAKE_CORE_INCLUDE_PATH set to %s in webroot/test.php', true), CAKE_CORE_INCLUDE_PATH));
+ $this->out(__('Remember to check these value after moving to production server', true));
+ } elseif ($corePath === false) {
+ $this->err(sprintf(__('Unable to set CAKE_CORE_INCLUDE_PATH, you should change it in %s', true), $path . 'webroot' .DS .'index.php'));
+ }
+ $Folder = new Folder($path);
+ if (!$Folder->chmod($path . 'tmp', 0777)) {
+ $this->err(sprintf(__('Could not set permissions on %s', true), $path . DS .'tmp'));
+ $this->out(sprintf(__('chmod -R 0777 %s', true), $path . DS .'tmp'));
+ }
+
+ $this->params['working'] = $path;
+ $this->params['app'] = basename($path);
+ return true;
+ }
+ }
+/**
+ * Looks for a skeleton template of a Cake application,
+ * and if not found asks the user for a path. When there is a path
+ * this method will make a deep copy of the skeleton to the project directory.
+ * A default home page will be added, and the tmp file storage will be chmod'ed to 0777.
+ *
+ * @param string $path Project path
+ * @param string $skel Path to copy from
+ * @param string $skip array of directories to skip when copying
+ * @access private
+ */
+ function bake($path, $skel = null, $skip = array('empty')) {
+ if (!$skel) {
+ $skel = $this->params['skel'];
+ }
+
+ while (!$skel) {
+ $skel = $this->in(sprintf(__("What is the path to the directory layout you wish to copy?\nExample: %s"), APP, null, ROOT . DS . 'myapp' . DS));
+ if ($skel == '') {
+ $this->out(__('The directory path you supplied was empty. Please try again.', true));
+ } else {
+ while (is_dir($skel) === false) {
+ $skel = $this->in(__('Directory path does not exist please choose another:', true));
+ }
+ }
+ }
+
+ $app = basename($path);
+
+ $this->out('Bake Project');
+ $this->out("Skel Directory: $skel");
+ $this->out("Will be copied to: {$path}");
+ $this->hr();
+
+ $looksGood = $this->in('Look okay?', array('y', 'n', 'q'), 'y');
+
+ if (strtolower($looksGood) == 'y' || strtolower($looksGood) == 'yes') {
+ $verbose = $this->in(__('Do you want verbose output?', true), array('y', 'n'), 'n');
+
+ $Folder = new Folder($skel);
+ if ($Folder->copy(array('to' => $path, 'skip' => $skip))) {
+ $this->hr();
+ $this->out(sprintf(__("Created: %s in %s", true), $app, $path));
+ $this->hr();
+ } else {
+ $this->err(" '" . $app . "' could not be created properly");
+ return false;
+ }
+
+ if (strtolower($verbose) == 'y' || strtolower($verbose) == 'yes') {
+ foreach ($Folder->messages() as $message) {
+ $this->out($message);
+ }
+ }
+
+ return true;
+ } elseif (strtolower($looksGood) == 'q' || strtolower($looksGood) == 'quit') {
+ $this->out('Bake Aborted.');
+ } else {
+ $this->execute(false);
+ return false;
+ }
+ }
+/**
+ * Writes a file with a default home page to the project.
+ *
+ * @param string $dir Path to project
+ * @return boolean Success
+ * @access public
+ */
+ function createHome($dir) {
+ $app = basename($dir);
+ $path = $dir . 'views' . DS . 'pages' . DS;
+ include(CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'views'.DS.'home.ctp');
+ return $this->createFile($path.'home.ctp', $output);
+ }
+/**
+ * Generates and writes 'Security.salt'
+ *
+ * @param string $path Project path
+ * @return boolean Success
+ * @access public
+ */
+ function securitySalt($path) {
+ $File =& new File($path . 'config' . DS . 'core.php');
+ $contents = $File->read();
+ if (preg_match('/([\\t\\x20]*Configure::write\\(\\\'Security.salt\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) {
+ if (!class_exists('Security')) {
+ uses('Security');
+ }
+ $string = Security::generateAuthKey();
+ $result = str_replace($match[0], "\t" . 'Configure::write(\'Security.salt\', \''.$string.'\');', $contents);
+ if ($File->write($result)) {
+ return true;
+ }
+ return false;
+ }
+ return false;
+ }
+/**
+ * Generates and writes CAKE_CORE_INCLUDE_PATH
+ *
+ * @param string $path Project path
+ * @return boolean Success
+ * @access public
+ */
+ function corePath($path) {
+ if (dirname($path) !== CAKE_CORE_INCLUDE_PATH) {
+ $File =& new File($path . 'webroot' . DS . 'index.php');
+ $contents = $File->read();
+ if (preg_match('/([\\t\\x20]*define\\(\\\'CAKE_CORE_INCLUDE_PATH\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) {
+ $result = str_replace($match[0], "\t\tdefine('CAKE_CORE_INCLUDE_PATH', '" . CAKE_CORE_INCLUDE_PATH . "');", $contents);
+ if (!$File->write($result)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ $File =& new File($path . 'webroot' . DS . 'test.php');
+ $contents = $File->read();
+ if (preg_match('/([\\t\\x20]*define\\(\\\'CAKE_CORE_INCLUDE_PATH\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) {
+ $result = str_replace($match[0], "\t\tdefine('CAKE_CORE_INCLUDE_PATH', '" . CAKE_CORE_INCLUDE_PATH . "');", $contents);
+ if (!$File->write($result)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+ }
+ }
+/**
+ * Enables Configure::read('Routing.admin') in /app/config/core.php
+ *
+ * @param string $name Name to use as admin routing
+ * @return boolean Success
+ * @access public
+ */
+ function cakeAdmin($name) {
+ $File =& new File(CONFIGS . 'core.php');
+ $contents = $File->read();
+ if (preg_match('%([/\\t\\x20]*Configure::write\(\'Routing.admin\',[\\t\\x20\'a-z]*\\);)%', $contents, $match)) {
+ $result = str_replace($match[0], "\t" . 'Configure::write(\'Routing.admin\', \''.$name.'\');', $contents);
+ if ($File->write($result)) {
+ Configure::write('Routing.admin', $name);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+/**
+ * Help
+ *
+ * @return void
+ * @access public
+ */
+ function help() {
+ $this->hr();
+ $this->out("Usage: cake bake project ");
+ $this->hr();
+ $this->out('Commands:');
+ $this->out("\n\tproject \n\t\tbakes app directory structure.\n\t\tif begins with '/' path is absolute.");
+ $this->out("");
+ $this->_stop();
+ }
+
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/tasks/test.php b/cake/console/libs/tasks/test.php
new file mode 100755
index 00000000..416d3bd2
--- /dev/null
+++ b/cake/console/libs/tasks/test.php
@@ -0,0 +1,203 @@
+args)) {
+ $this->__interactive();
+ }
+
+ if (count($this->args) == 1) {
+ $this->__interactive($this->args[0]);
+ }
+
+ if (count($this->args) > 1) {
+ $class = Inflector::underscore($this->args[0]);
+ if ($this->bake($class, $this->args[1])) {
+ $this->out('done');
+ }
+ }
+ }
+/**
+ * Handles interactive baking
+ *
+ * @access private
+ */
+ function __interactive($class = null) {
+ $this->hr();
+ $this->out(sprintf("Bake Tests\nPath: %s", $this->path));
+ $this->hr();
+
+ $key = null;
+ $options = array('Behavior', 'Helper', 'Component', 'Model', 'Controller');
+
+ if ($class !== null) {
+ $class = Inflector::camelize($class);
+ if (in_array($class, $options)) {
+ $key = array_search($class);
+ }
+ }
+
+ while ($class == null) {
+ $cases = array();
+ $this->hr();
+ $this->out("Select a class:");
+ $this->hr();
+
+ $keys = array();
+ foreach ($options as $key => $option) {
+ $this->out(++$key . '. ' . $option);
+ $keys[] = $key;
+ }
+ $keys[] = 'q';
+
+ $key = $this->in(__("Enter the class to test or (q)uit", true), $keys, 'q');
+
+ if ($key != 'q') {
+ if (isset($options[--$key])) {
+ $class = $options[$key];
+ }
+
+ if ($class) {
+ $name = $this->in(__("Enter the name for the test or (q)uit", true), null, 'q');
+ if ($name !== 'q') {
+ $case = null;
+ while ($case !== 'q') {
+ $case = $this->in(__("Enter a test case or (q)uit", true), null, 'q');
+ if ($case !== 'q') {
+ $cases[] = $case;
+ }
+ }
+ if ($this->bake($class, $name, $cases)) {
+ $this->out(__("Test baked\n", true));
+ $type = null;
+ }
+ $class = null;
+ }
+ }
+ } else {
+ $this->_stop();
+ }
+ }
+ }
+/**
+ * Writes File
+ *
+ * @access public
+ */
+ function bake($class, $name = null, $cases = array()) {
+ if (!$name) {
+ return false;
+ }
+
+ if (!is_array($cases)) {
+ $cases = array($cases);
+ }
+
+ if (strpos($this->path, $class) === false) {
+ $this->filePath = $this->path . 'cases' . DS . Inflector::tableize($class) . DS;
+ }
+
+ $class = Inflector::classify($class);
+ $name = Inflector::classify($name);
+
+ $import = $name;
+ if (isset($this->plugin)) {
+ $import = $this->plugin . '.' . $name;
+ }
+ $extras = $this->__extras($class);
+ $out = "App::import('$class', '$import');\n";
+ if ($class == 'Model') {
+ $class = null;
+ }
+ $out .= "class Test{$name} extends {$name}{$class} {\n";
+ $out .= "{$extras}";
+ $out .= "}\n\n";
+ $out .= "class {$name}{$class}Test extends CakeTestCase {\n";
+ $out .= "\n\tfunction startTest() {";
+ $out .= "\n\t\t\$this->{$name} = new Test{$name}();";
+ $out .= "\n\t}\n";
+ $out .= "\n\tfunction test{$name}Instance() {\n";
+ $out .= "\t\t\$this->assertTrue(is_a(\$this->{$name}, '{$name}{$class}'));\n\t}\n";
+ foreach ($cases as $case) {
+ $case = Inflector::classify($case);
+ $out .= "\n\tfunction test{$case}() {\n\n\t}\n";
+ }
+ $out .= "}\n";
+
+ $this->out("Baking unit test for $name...");
+ $this->out($out);
+ $ok = $this->in(__('Is this correct?', true), array('y', 'n'), 'y');
+ if ($ok == 'n') {
+ return false;
+ }
+
+ $header = '$Id';
+ $content = "";
+ return $this->createFile($this->filePath . Inflector::underscore($name) . '.test.php', $content);
+ }
+/**
+ * Handles the extra stuff needed
+ *
+ * @access private
+ */
+ function __extras($class) {
+ $extras = null;
+ switch ($class) {
+ case 'Model':
+ $extras = "\n\tvar \$cacheSources = false;";
+ $extras .= "\n\tvar \$useDbConfig = 'test_suite';\n";
+ break;
+ }
+ return $extras;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/tasks/view.php b/cake/console/libs/tasks/view.php
new file mode 100755
index 00000000..cd60df61
--- /dev/null
+++ b/cake/console/libs/tasks/view.php
@@ -0,0 +1,389 @@
+args)) {
+ $this->__interactive();
+ }
+
+ if (isset($this->args[0])) {
+ $controller = $action = $alias = null;
+ $this->controllerName = Inflector::camelize($this->args[0]);
+ $this->controllerPath = Inflector::underscore($this->controllerName);
+
+ if (isset($this->args[1])) {
+ $this->template = $this->args[1];
+ }
+
+ if (isset($this->args[2])) {
+ $action = $this->args[2];
+ }
+
+ if (!$action) {
+ $action = $this->template;
+ }
+
+ if (in_array($action, $this->scaffoldActions)) {
+ $this->bake($action, true);
+ } elseif ($action) {
+ $this->bake($action, true);
+ } else {
+ $vars = $this->__loadController();
+ if ($vars) {
+
+ $methods = array_diff(
+ array_map('strtolower', get_class_methods($this->controllerName . 'Controller')),
+ array_map('strtolower', get_class_methods('appcontroller'))
+ );
+ if (empty($methods)) {
+ $methods = $this->scaffoldActions;
+ }
+ $adminDelete = null;
+
+ $adminRoute = Configure::read('Routing.admin');
+ if (!empty($adminRoute)) {
+ $adminDelete = $adminRoute.'_delete';
+ }
+ foreach ($methods as $method) {
+ if ($method{0} != '_' && !in_array($method, array('delete', $adminDelete))) {
+ $content = $this->getContent($method, $vars);
+ $this->bake($method, $content);
+ }
+ }
+ }
+ }
+ }
+ }
+/**
+ * Handles interactive baking
+ *
+ * @access private
+ */
+ function __interactive() {
+ $this->hr();
+ $this->out(sprintf("Bake View\nPath: %s", $this->path));
+ $this->hr();
+ $wannaDoAdmin = 'n';
+ $wannaDoScaffold = 'y';
+ $this->interactive = false;
+
+ $this->controllerName = $this->Controller->getName();
+
+ $this->controllerPath = strtolower(Inflector::underscore($this->controllerName));
+
+ $interactive = $this->in("Would you like bake to build your views interactively?\nWarning: Choosing no will overwrite {$this->controllerName} views if it exist.", array('y','n'), 'y');
+
+ if (strtolower($interactive) == 'y' || strtolower($interactive) == 'yes') {
+ $this->interactive = true;
+ $wannaDoScaffold = $this->in("Would you like to create some scaffolded views (index, add, view, edit) for this controller?\nNOTE: Before doing so, you'll need to create your controller and model classes (including associated models).", array('y','n'), 'n');
+ }
+
+ if (strtolower($wannaDoScaffold) == 'y' || strtolower($wannaDoScaffold) == 'yes') {
+ $wannaDoAdmin = $this->in("Would you like to create the views for admin routing?", array('y','n'), 'y');
+ }
+ $admin = false;
+
+ if ((strtolower($wannaDoAdmin) == 'y' || strtolower($wannaDoAdmin) == 'yes')) {
+ $admin = $this->getAdmin();
+ }
+
+ if (strtolower($wannaDoScaffold) == 'y' || strtolower($wannaDoScaffold) == 'yes') {
+ $actions = $this->scaffoldActions;
+ if ($admin) {
+ foreach ($actions as $action) {
+ $actions[] = $admin . $action;
+ }
+ }
+ $vars = $this->__loadController();
+ if ($vars) {
+ foreach ($actions as $action) {
+ $content = $this->getContent($action, $vars);
+ $this->bake($action, $content);
+ }
+ }
+ $this->hr();
+ $this->out('');
+ $this->out('View Scaffolding Complete.'."\n");
+ } else {
+ $action = '';
+ while ($action == '') {
+ $action = $this->in('Action Name? (use camelCased function name)');
+ if ($action == '') {
+ $this->out('The action name you supplied was empty. Please try again.');
+ }
+ }
+ $this->out('');
+ $this->hr();
+ $this->out('The following view will be created:');
+ $this->hr();
+ $this->out("Controller Name: {$this->controllerName}");
+ $this->out("Action Name: {$action}");
+ $this->out("Path: ".$this->params['app'] . DS . $this->controllerPath . DS . Inflector::underscore($action) . ".ctp");
+ $this->hr();
+ $looksGood = $this->in('Look okay?', array('y','n'), 'y');
+ if (strtolower($looksGood) == 'y' || strtolower($looksGood) == 'yes') {
+ $this->bake($action);
+ $this->_stop();
+ } else {
+ $this->out('Bake Aborted.');
+ }
+ }
+ }
+/**
+ * Loads Controller and sets variables for the template
+ * Available template variables
+ * 'modelClass', 'primaryKey', 'displayField', 'singularVar', 'pluralVar',
+ * 'singularHumanName', 'pluralHumanName', 'fields', 'foreignKeys',
+ * 'belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'
+ *
+ * @return array Returns an variables to be made available to a view template
+ * @access private
+ */
+ function __loadController() {
+ if (!$this->controllerName) {
+ $this->err(__('Controller not found', true));
+ }
+
+ $import = $this->controllerName;
+ if ($this->plugin) {
+ $import = $this->plugin . '.' . $this->controllerName;
+ }
+
+ if (!App::import('Controller', $import)) {
+ $file = $this->controllerPath . '_controller.php';
+ $this->err(sprintf(__("The file '%s' could not be found.\nIn order to bake a view, you'll need to first create the controller.", true), $file));
+ $this->_stop();
+ }
+ $controllerClassName = $this->controllerName . 'Controller';
+ $controllerObj = & new $controllerClassName();
+ $controllerObj->constructClasses();
+ $modelClass = $controllerObj->modelClass;
+ $modelObj =& ClassRegistry::getObject($controllerObj->modelKey);
+
+ if ($modelObj) {
+ $primaryKey = $modelObj->primaryKey;
+ $displayField = $modelObj->displayField;
+ $singularVar = Inflector::variable($modelClass);
+ $pluralVar = Inflector::variable($this->controllerName);
+ $singularHumanName = Inflector::humanize($modelClass);
+ $pluralHumanName = Inflector::humanize($this->controllerName);
+ $schema = $modelObj->schema();
+ $fields = array_keys($schema);
+ $associations = $this->__associations($modelObj);
+ } else {
+ $primaryKey = null;
+ $displayField = null;
+ $singularVar = Inflector::variable(Inflector::singularize($this->controllerName));
+ $pluralVar = Inflector::variable($this->controllerName);
+ $singularHumanName = Inflector::humanize(Inflector::singularize($this->controllerName));
+ $pluralHumanName = Inflector::humanize($this->controllerName);
+ $fields = array();
+ $schema = array();
+ $associations = array();
+ }
+
+ return compact('modelClass', 'schema', 'primaryKey', 'displayField', 'singularVar', 'pluralVar',
+ 'singularHumanName', 'pluralHumanName', 'fields','associations');
+ }
+/**
+ * Assembles and writes bakes the view file.
+ *
+ * @param string $action Action to bake
+ * @param string $content Content to write
+ * @return boolean Success
+ * @access public
+ */
+ function bake($action, $content = '') {
+ if ($content === true) {
+ $content = $this->getContent();
+ }
+ $filename = $this->path . $this->controllerPath . DS . Inflector::underscore($action) . '.ctp';
+ $Folder =& new Folder($this->path . $this->controllerPath, true);
+ $errors = $Folder->errors();
+ if (empty($errors)) {
+ $path = $Folder->slashTerm($Folder->pwd());
+ return $this->createFile($filename, $content);
+ } else {
+ foreach ($errors as $error) {
+ $this->err($error);
+ }
+ }
+ return false;
+ }
+/**
+ * Builds content from template and variables
+ *
+ * @param string $template file to use
+ * @param array $vars passed for use in templates
+ * @return string content from template
+ * @access public
+ */
+ function getContent($template = null, $vars = null) {
+ if (!$template) {
+ $template = $this->template;
+ }
+ $action = $template;
+
+ $adminRoute = Configure::read('Routing.admin');
+ if (!empty($adminRoute) && strpos($template, $adminRoute) !== false) {
+ $template = str_replace($adminRoute.'_', '', $template);
+ }
+ if (in_array($template, array('add', 'edit'))) {
+ $action = $template;
+ $template = 'form';
+ }
+ $loaded = false;
+ foreach ($this->Dispatch->shellPaths as $path) {
+ $templatePath = $path . 'templates' . DS . 'views' . DS .Inflector::underscore($template).'.ctp';
+ if (file_exists($templatePath) && is_file($templatePath)) {
+ $loaded = true;
+ break;
+ }
+ }
+ if (!$vars) {
+ $vars = $this->__loadController();
+ }
+ if ($loaded) {
+ extract($vars);
+ ob_start();
+ ob_implicit_flush(0);
+ include($templatePath);
+ $content = ob_get_clean();
+ return $content;
+ }
+ $this->hr();
+ $this->err(sprintf(__('Template for %s could not be found', true), $template));
+ return false;
+ }
+/**
+ * Displays help contents
+ *
+ * @access public
+ */
+ function help() {
+ $this->hr();
+ $this->out("Usage: cake bake view ...");
+ $this->hr();
+ $this->out('Commands:');
+ $this->out("\n\tview \n\t\twill read the given controller for methods\n\t\tand bake corresponding views.\n\t\tIf var scaffold is found it will bake the scaffolded actions\n\t\t(index,view,add,edit)");
+ $this->out("\n\tview \n\t\twill bake a template. core templates: (index, add, edit, view)");
+ $this->out("\n\tview \n\t\twill use the template specified but name the file based on the alias");
+ $this->out("");
+ $this->_stop();
+ }
+/**
+ * Returns associations for controllers models.
+ *
+ * @return array $associations
+ * @access private
+ */
+ function __associations($model) {
+ $keys = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
+ $associations = array();
+
+ foreach ($keys as $key => $type) {
+ foreach ($model->{$type} as $assocKey => $assocData) {
+ $associations[$type][$assocKey]['primaryKey'] = $model->{$assocKey}->primaryKey;
+ $associations[$type][$assocKey]['displayField'] = $model->{$assocKey}->displayField;
+ $associations[$type][$assocKey]['foreignKey'] = $assocData['foreignKey'];
+ $associations[$type][$assocKey]['controller'] = Inflector::pluralize(Inflector::underscore($assocData['className']));
+ $associations[$type][$assocKey]['fields'] = array_keys($model->{$assocKey}->schema());
+ }
+ }
+ return $associations;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/.htaccess b/cake/console/libs/templates/skel/.htaccess
new file mode 100755
index 00000000..0ed8662e
--- /dev/null
+++ b/cake/console/libs/templates/skel/.htaccess
@@ -0,0 +1,5 @@
+
+ RewriteEngine on
+ RewriteRule ^$ webroot/ [L]
+ RewriteRule (.*) webroot/$1 [L]
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/app_controller.php b/cake/console/libs/templates/skel/app_controller.php
new file mode 100755
index 00000000..2412447b
--- /dev/null
+++ b/cake/console/libs/templates/skel/app_controller.php
@@ -0,0 +1,39 @@
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/app_helper.php b/cake/console/libs/templates/skel/app_helper.php
new file mode 100755
index 00000000..c57f438b
--- /dev/null
+++ b/cake/console/libs/templates/skel/app_helper.php
@@ -0,0 +1,41 @@
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/app_model.php b/cake/console/libs/templates/skel/app_model.php
new file mode 100755
index 00000000..80938823
--- /dev/null
+++ b/cake/console/libs/templates/skel/app_model.php
@@ -0,0 +1,41 @@
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/acl.ini.php b/cake/console/libs/templates/skel/config/acl.ini.php
new file mode 100755
index 00000000..a9868e68
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/acl.ini.php
@@ -0,0 +1,74 @@
+;
+; SVN FILE: $Id$
+;/**
+; * Short description for file.
+; *
+; *
+; * PHP versions 4 and 5
+; *
+; * CakePHP(tm) : Rapid Development Framework http://www.cakephp.org/
+; * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+; *
+; * Licensed under The MIT License
+; * Redistributions of files must retain the above copyright notice.
+; *
+; * @filesource
+; * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+; * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+; * @package cake
+; * @subpackage cake.app.config
+; * @since CakePHP(tm) v 0.10.0.1076
+; * @version $Revision$
+; * @modifiedby $LastChangedBy$
+; * @lastmodified $Date$
+; * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+; */
+
+; acl.ini.php - Cake ACL Configuration
+; ---------------------------------------------------------------------
+; Use this file to specify user permissions.
+; aco = access control object (something in your application)
+; aro = access request object (something requesting access)
+;
+; User records are added as follows:
+;
+; [uid]
+; groups = group1, group2, group3
+; allow = aco1, aco2, aco3
+; deny = aco4, aco5, aco6
+;
+; Group records are added in a similar manner:
+;
+; [gid]
+; allow = aco1, aco2, aco3
+; deny = aco4, aco5, aco6
+;
+; The allow, deny, and groups sections are all optional.
+; NOTE: groups names *cannot* ever be the same as usernames!
+;
+; ACL permissions are checked in the following order:
+; 1. Check for user denies (and DENY if specified)
+; 2. Check for user allows (and ALLOW if specified)
+; 3. Gather user's groups
+; 4. Check group denies (and DENY if specified)
+; 5. Check group allows (and ALLOW if specified)
+; 6. If no aro, aco, or group information is found, DENY
+;
+; ---------------------------------------------------------------------
+
+;-------------------------------------
+;Users
+;-------------------------------------
+
+[username-goes-here]
+groups = group1, group2
+deny = aco1, aco2
+allow = aco3, aco4
+
+;-------------------------------------
+;Groups
+;-------------------------------------
+
+[groupname-goes-here]
+deny = aco5, aco6
+allow = aco7, aco8
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/bootstrap.php b/cake/console/libs/templates/skel/config/bootstrap.php
new file mode 100755
index 00000000..b817172c
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/bootstrap.php
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/core.php b/cake/console/libs/templates/skel/config/core.php
new file mode 100755
index 00000000..fbd02e74
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/core.php
@@ -0,0 +1,232 @@
+ admin_index() and /admin/controller/index
+ * 'superuser' -> superuser_index() and /superuser/controller/index
+ */
+ //Configure::write('Routing.admin', 'admin');
+
+/**
+ * Turn off all caching application-wide.
+ *
+ */
+ //Configure::write('Cache.disable', true);
+/**
+ * Enable cache checking.
+ *
+ * If set to true, for view caching you must still use the controller
+ * var $cacheAction inside your controllers to define caching settings.
+ * You can either set it controller-wide by setting var $cacheAction = true,
+ * or in each action using $this->cacheAction = true.
+ *
+ */
+ //Configure::write('Cache.check', true);
+/**
+ * Defines the default error type when using the log() function. Used for
+ * differentiating error logging and debugging. Currently PHP supports LOG_DEBUG.
+ */
+ define('LOG_ERROR', 2);
+/**
+ * The preferred session handling method. Valid values:
+ *
+ * 'php' Uses settings defined in your php.ini.
+ * 'cake' Saves session files in CakePHP's /tmp directory.
+ * 'database' Uses CakePHP's database sessions.
+ *
+ * To define a custom session handler, save it at /app/config/.php.
+ * Set the value of 'Session.save' to to utilize it in CakePHP.
+ *
+ * To use database sessions, execute the SQL file found at /app/config/sql/sessions.sql.
+ *
+ */
+ Configure::write('Session.save', 'php');
+/**
+ * The name of the table used to store CakePHP database sessions.
+ *
+ * 'Session.save' must be set to 'database' in order to utilize this constant.
+ *
+ * The table name set here should *not* include any table prefix defined elsewhere.
+ */
+ //Configure::write('Session.table', 'cake_sessions');
+/**
+ * The DATABASE_CONFIG::$var to use for database session handling.
+ *
+ * 'Session.save' must be set to 'database' in order to utilize this constant.
+ */
+ //Configure::write('Session.database', 'default');
+/**
+ * The name of CakePHP's session cookie.
+ */
+ Configure::write('Session.cookie', 'CAKEPHP');
+/**
+ * Session time out time (in seconds).
+ * Actual value depends on 'Security.level' setting.
+ */
+ Configure::write('Session.timeout', '120');
+/**
+ * If set to false, sessions are not automatically started.
+ */
+ Configure::write('Session.start', true);
+/**
+ * When set to false, HTTP_USER_AGENT will not be checked
+ * in the session
+ */
+ Configure::write('Session.checkAgent', true);
+/**
+ * The level of CakePHP security. The session timeout time defined
+ * in 'Session.timeout' is multiplied according to the settings here.
+ * Valid values:
+ *
+ * 'high' Session timeout in 'Session.timeout' x 10
+ * 'medium' Session timeout in 'Session.timeout' x 100
+ * 'low' Session timeout in 'Session.timeout' x 300
+ *
+ * CakePHP session IDs are also regenerated between requests if
+ * 'Security.level' is set to 'high'.
+ */
+ Configure::write('Security.level', 'high');
+/**
+ * A random string used in security hashing methods.
+ */
+ Configure::write('Security.salt', 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi');
+/**
+ * Compress CSS output by removing comments, whitespace, repeating tags, etc.
+ * This requires a/var/cache directory to be writable by the web server for caching.
+ * and /vendors/csspp/csspp.php
+ *
+ * To use, prefix the CSS link URL with '/ccss/' instead of '/css/' or use HtmlHelper::css().
+ */
+ //Configure::write('Asset.filter.css', 'css.php');
+/**
+ * Plug in your own custom JavaScript compressor by dropping a script in your webroot to handle the
+ * output, and setting the config below to the name of the script.
+ *
+ * To use, prefix your JavaScript link URLs with '/cjs/' instead of '/js/' or use JavaScriptHelper::link().
+ */
+ //Configure::write('Asset.filter.js', 'custom_javascript_output_filter.php');
+/**
+ * The classname and database used in CakePHP's
+ * access control lists.
+ */
+ Configure::write('Acl.classname', 'DbAcl');
+ Configure::write('Acl.database', 'default');
+/**
+ * If you are on PHP 5.3 uncomment this line and correct your server timezone
+ * to fix the date & time related errors.
+ */
+ //date_default_timezone_set('UTC');
+/**
+ *
+ * Cache Engine Configuration
+ * Default settings provided below
+ *
+ * File storage engine.
+ *
+ * Cache::config('default', array(
+ * 'engine' => 'File', //[required]
+ * 'duration'=> 3600, //[optional]
+ * 'probability'=> 100, //[optional]
+ * 'path' => CACHE, //[optional] use system tmp directory - remember to use absolute path
+ * 'prefix' => 'cake_', //[optional] prefix every cache file with this string
+ * 'lock' => false, //[optional] use file locking
+ * 'serialize' => true, [optional]
+ * ));
+ *
+ *
+ * APC (http://pecl.php.net/package/APC)
+ *
+ * Cache::config('default', array(
+ * 'engine' => 'Apc', //[required]
+ * 'duration'=> 3600, //[optional]
+ * 'probability'=> 100, //[optional]
+ * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string
+ * ));
+ *
+ * Xcache (http://xcache.lighttpd.net/)
+ *
+ * Cache::config('default', array(
+ * 'engine' => 'Xcache', //[required]
+ * 'duration'=> 3600, //[optional]
+ * 'probability'=> 100, //[optional]
+ * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string
+ * 'user' => 'user', //user from xcache.admin.user settings
+ * 'password' => 'password', //plaintext password (xcache.admin.pass)
+ * ));
+ *
+ *
+ * Memcache (http://www.danga.com/memcached/)
+ *
+ * Cache::config('default', array(
+ * 'engine' => 'Memcache', //[required]
+ * 'duration'=> 3600, //[optional]
+ * 'probability'=> 100, //[optional]
+ * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string
+ * 'servers' => array(
+ * '127.0.0.1:11211' // localhost, default port 11211
+ * ), //[optional]
+ * 'compress' => false, // [optional] compress data in Memcache (slower, but uses less memory)
+ * ));
+ *
+ */
+ Cache::config('default', array('engine' => 'File'));
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/database.php.default b/cake/console/libs/templates/skel/config/database.php.default
new file mode 100755
index 00000000..5c20804d
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/database.php.default
@@ -0,0 +1,101 @@
+ The name of a supported driver; valid options are as follows:
+ * mysql - MySQL 4 & 5,
+ * mysqli - MySQL 4 & 5 Improved Interface (PHP5 only),
+ * sqlite - SQLite (PHP5 only),
+ * postgres - PostgreSQL 7 and higher,
+ * mssql - Microsoft SQL Server 2000 and higher,
+ * db2 - IBM DB2, Cloudscape, and Apache Derby (http://php.net/ibm-db2)
+ * oracle - Oracle 8 and higher
+ * firebird - Firebird/Interbase
+ * sybase - Sybase ASE
+ * adodb-[drivername] - ADOdb interface wrapper (see below),
+ * odbc - ODBC DBO driver
+ *
+ * You can add custom database drivers (or override existing drivers) by adding the
+ * appropriate file to app/models/datasources/dbo. Drivers should be named 'dbo_x.php',
+ * where 'x' is the name of the database.
+ *
+ * persistent => true / false
+ * Determines whether or not the database should use a persistent connection
+ *
+ * connect =>
+ * ADOdb set the connect to one of these
+ * (http://phplens.com/adodb/supported.databases.html) and
+ * append it '|p' for persistent connection. (mssql|p for example, or just mssql for not persistent)
+ * For all other databases, this setting is deprecated.
+ *
+ * host =>
+ * the host you connect to the database. To add a socket or port number, use 'port' => #
+ *
+ * prefix =>
+ * Uses the given prefix for all the tables in this database. This setting can be overridden
+ * on a per-table basis with the Model::$tablePrefix property.
+ *
+ * schema =>
+ * For Postgres and DB2, specifies which schema you would like to use the tables in. Postgres defaults to
+ * 'public', DB2 defaults to empty.
+ *
+ * encoding =>
+ * For MySQL, MySQLi, Postgres and DB2, specifies the character encoding to use when connecting to the
+ * database. Uses database default.
+ *
+ */
+class DATABASE_CONFIG {
+
+ var $default = array(
+ 'driver' => 'mysql',
+ 'persistent' => false,
+ 'host' => 'localhost',
+ 'login' => 'user',
+ 'password' => 'password',
+ 'database' => 'database_name',
+ 'prefix' => '',
+ );
+
+ var $test = array(
+ 'driver' => 'mysql',
+ 'persistent' => false,
+ 'host' => 'localhost',
+ 'login' => 'user',
+ 'password' => 'password',
+ 'database' => 'test_database_name',
+ 'prefix' => '',
+ );
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/inflections.php b/cake/console/libs/templates/skel/config/inflections.php
new file mode 100755
index 00000000..ed8c08b3
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/inflections.php
@@ -0,0 +1,70 @@
+ value array of regex used to match words.
+ * If key matches then the value is returned.
+ *
+ * $pluralRules = array('/(s)tatus$/i' => '\1\2tatuses', '/^(ox)$/i' => '\1\2en', '/([m|l])ouse$/i' => '\1ice');
+ */
+ $pluralRules = array();
+/**
+ * This is a key only array of plural words that should not be inflected.
+ * Notice the last comma
+ *
+ * $uninflectedPlural = array('.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox');
+ */
+ $uninflectedPlural = array();
+/**
+ * This is a key => value array of plural irregular words.
+ * If key matches then the value is returned.
+ *
+ * $irregularPlural = array('atlas' => 'atlases', 'beef' => 'beefs', 'brother' => 'brothers')
+ */
+ $irregularPlural = array();
+/**
+ * This is a key => value array of regex used to match words.
+ * If key matches then the value is returned.
+ *
+ * $singularRules = array('/(s)tatuses$/i' => '\1\2tatus', '/(matr)ices$/i' =>'\1ix','/(vert|ind)ices$/i')
+ */
+ $singularRules = array();
+/**
+ * This is a key only array of singular words that should not be inflected.
+ * You should not have to change this value below if you do change it use same format
+ * as the $uninflectedPlural above.
+ */
+ $uninflectedSingular = $uninflectedPlural;
+/**
+ * This is a key => value array of singular irregular words.
+ * Most of the time this will be a reverse of the above $irregularPlural array
+ * You should not have to change this value below if you do change it use same format
+ *
+ * $irregularSingular = array('atlases' => 'atlas', 'beefs' => 'beef', 'brothers' => 'brother')
+ */
+ $irregularSingular = array_flip($irregularPlural);
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/routes.php b/cake/console/libs/templates/skel/config/routes.php
new file mode 100755
index 00000000..b14e435c
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/routes.php
@@ -0,0 +1,39 @@
+ 'pages', 'action' => 'display', 'home'));
+/**
+ * ...and connect the rest of 'Pages' controller's urls.
+ */
+ Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/sql/db_acl.php b/cake/console/libs/templates/skel/config/sql/db_acl.php
new file mode 100755
index 00000000..19434d0c
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/sql/db_acl.php
@@ -0,0 +1,79 @@
+ array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'model' => array('type'=>'string', 'null' => true),
+ 'foreign_key' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'alias' => array('type'=>'string', 'null' => true),
+ 'lft' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'rght' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
+ );
+
+ var $aros = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'model' => array('type'=>'string', 'null' => true),
+ 'foreign_key' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'alias' => array('type'=>'string', 'null' => true),
+ 'lft' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'rght' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
+ );
+
+ var $aros_acos = array(
+ 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'),
+ 'aro_id' => array('type'=>'integer', 'null' => false, 'length' => 10, 'key' => 'index'),
+ 'aco_id' => array('type'=>'integer', 'null' => false, 'length' => 10),
+ '_create' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2),
+ '_read' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2),
+ '_update' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2),
+ '_delete' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'ARO_ACO_KEY' => array('column' => array('aro_id', 'aco_id'), 'unique' => 1))
+ );
+
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/sql/db_acl.sql b/cake/console/libs/templates/skel/config/sql/db_acl.sql
new file mode 100755
index 00000000..3b8089bc
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/sql/db_acl.sql
@@ -0,0 +1,40 @@
+# $Id$
+#
+# Copyright 2005-2008, Cake Software Foundation, Inc.
+#
+# Licensed under The MIT License
+# Redistributions of files must retain the above copyright notice.
+# http://www.opensource.org/licenses/mit-license.php The MIT License
+
+CREATE TABLE acos (
+ id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ parent_id INTEGER(10) DEFAULT NULL,
+ model VARCHAR(255) DEFAULT '',
+ foreign_key INTEGER(10) UNSIGNED DEFAULT NULL,
+ alias VARCHAR(255) DEFAULT '',
+ lft INTEGER(10) DEFAULT NULL,
+ rght INTEGER(10) DEFAULT NULL,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE aros_acos (
+ id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ aro_id INTEGER(10) UNSIGNED NOT NULL,
+ aco_id INTEGER(10) UNSIGNED NOT NULL,
+ _create CHAR(2) NOT NULL DEFAULT 0,
+ _read CHAR(2) NOT NULL DEFAULT 0,
+ _update CHAR(2) NOT NULL DEFAULT 0,
+ _delete CHAR(2) NOT NULL DEFAULT 0,
+ PRIMARY KEY(id)
+);
+
+CREATE TABLE aros (
+ id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ parent_id INTEGER(10) DEFAULT NULL,
+ model VARCHAR(255) DEFAULT '',
+ foreign_key INTEGER(10) UNSIGNED DEFAULT NULL,
+ alias VARCHAR(255) DEFAULT '',
+ lft INTEGER(10) DEFAULT NULL,
+ rght INTEGER(10) DEFAULT NULL,
+ PRIMARY KEY (id)
+);
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/sql/i18n.php b/cake/console/libs/templates/skel/config/sql/i18n.php
new file mode 100755
index 00000000..b8158dd5
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/sql/i18n.php
@@ -0,0 +1,56 @@
+ 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))
+ );
+
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/sql/i18n.sql b/cake/console/libs/templates/skel/config/sql/i18n.sql
new file mode 100755
index 00000000..7629b609
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/sql/i18n.sql
@@ -0,0 +1,26 @@
+# $Id$
+#
+# Copyright 2005-2008, Cake Software Foundation, Inc.
+#
+# Licensed under The MIT License
+# Redistributions of files must retain the above copyright notice.
+# http://www.opensource.org/licenses/mit-license.php The MIT License
+
+CREATE TABLE i18n (
+ id int(10) NOT NULL auto_increment,
+ locale varchar(6) NOT NULL,
+ model varchar(255) NOT NULL,
+ foreign_key int(10) NOT NULL,
+ field varchar(255) NOT NULL,
+ content mediumtext,
+ PRIMARY KEY (id),
+# UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field),
+# INDEX I18N_LOCALE_ROW(locale, model, foreign_key),
+# INDEX I18N_LOCALE_MODEL(locale, model),
+# INDEX I18N_FIELD(model, foreign_key, field),
+# INDEX I18N_ROW(model, foreign_key),
+ INDEX locale (locale),
+ INDEX model (model),
+ INDEX row_id (foreign_key),
+ INDEX field (field)
+);
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/sql/sessions.php b/cake/console/libs/templates/skel/config/sql/sessions.php
new file mode 100755
index 00000000..19736610
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/sql/sessions.php
@@ -0,0 +1,53 @@
+ array('type'=>'string', 'null' => false, 'key' => 'primary'),
+ 'data' => array('type'=>'text', 'null' => true, 'default' => NULL),
+ 'expires' => array('type'=>'integer', 'null' => true, 'default' => NULL),
+ 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
+ );
+
+}
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/config/sql/sessions.sql b/cake/console/libs/templates/skel/config/sql/sessions.sql
new file mode 100755
index 00000000..75be419a
--- /dev/null
+++ b/cake/console/libs/templates/skel/config/sql/sessions.sql
@@ -0,0 +1,16 @@
+# $Id$
+#
+# Copyright 2005-2008, 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.
+# http://www.opensource.org/licenses/mit-license.php The MIT License
+
+CREATE TABLE cake_sessions (
+ id varchar(255) NOT NULL default '',
+ data text,
+ expires int(11) default NULL,
+ PRIMARY KEY (id)
+);
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/controllers/components/empty b/cake/console/libs/templates/skel/controllers/components/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/controllers/pages_controller.php b/cake/console/libs/templates/skel/controllers/pages_controller.php
new file mode 100755
index 00000000..8196616b
--- /dev/null
+++ b/cake/console/libs/templates/skel/controllers/pages_controller.php
@@ -0,0 +1,86 @@
+redirect('/');
+ }
+ $page = $subpage = $title = null;
+
+ if (!empty($path[0])) {
+ $page = $path[0];
+ }
+ if (!empty($path[1])) {
+ $subpage = $path[1];
+ }
+ if (!empty($path[$count - 1])) {
+ $title = Inflector::humanize($path[$count - 1]);
+ }
+ $this->set(compact('page', 'subpage', 'title'));
+ $this->render(join('/', $path));
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/index.php b/cake/console/libs/templates/skel/index.php
new file mode 100755
index 00000000..5724a1c4
--- /dev/null
+++ b/cake/console/libs/templates/skel/index.php
@@ -0,0 +1,24 @@
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/locale/eng/LC_MESSAGES/empty b/cake/console/libs/templates/skel/locale/eng/LC_MESSAGES/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/models/behaviors/empty b/cake/console/libs/templates/skel/models/behaviors/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/models/datasources/empty b/cake/console/libs/templates/skel/models/datasources/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/plugins/empty b/cake/console/libs/templates/skel/plugins/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tests/cases/behaviors/empty b/cake/console/libs/templates/skel/tests/cases/behaviors/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tests/cases/components/empty b/cake/console/libs/templates/skel/tests/cases/components/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tests/cases/controllers/empty b/cake/console/libs/templates/skel/tests/cases/controllers/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tests/cases/helpers/empty b/cake/console/libs/templates/skel/tests/cases/helpers/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tests/cases/models/empty b/cake/console/libs/templates/skel/tests/cases/models/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tests/fixtures/empty b/cake/console/libs/templates/skel/tests/fixtures/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tests/groups/empty b/cake/console/libs/templates/skel/tests/groups/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tmp/cache/models/empty b/cake/console/libs/templates/skel/tmp/cache/models/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tmp/cache/persistent/empty b/cake/console/libs/templates/skel/tmp/cache/persistent/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tmp/cache/views/empty b/cake/console/libs/templates/skel/tmp/cache/views/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tmp/logs/empty b/cake/console/libs/templates/skel/tmp/logs/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tmp/sessions/empty b/cake/console/libs/templates/skel/tmp/sessions/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/tmp/tests/empty b/cake/console/libs/templates/skel/tmp/tests/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/vendors/shells/tasks/empty b/cake/console/libs/templates/skel/vendors/shells/tasks/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/vendors/shells/templates/empty b/cake/console/libs/templates/skel/vendors/shells/templates/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/views/elements/email/html/default.ctp b/cake/console/libs/templates/skel/views/elements/email/html/default.ctp
new file mode 100755
index 00000000..6e51c0fd
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/elements/email/html/default.ctp
@@ -0,0 +1,31 @@
+
+ ' . $line . '';
+endforeach;
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/views/elements/email/text/default.ctp b/cake/console/libs/templates/skel/views/elements/email/text/default.ctp
new file mode 100755
index 00000000..cbb261c6
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/elements/email/text/default.ctp
@@ -0,0 +1,25 @@
+
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/views/elements/empty b/cake/console/libs/templates/skel/views/elements/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/views/errors/empty b/cake/console/libs/templates/skel/views/errors/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/views/helpers/empty b/cake/console/libs/templates/skel/views/helpers/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/views/layouts/ajax.ctp b/cake/console/libs/templates/skel/views/layouts/ajax.ctp
new file mode 100755
index 00000000..ca3459af
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/layouts/ajax.ctp
@@ -0,0 +1,25 @@
+
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/views/layouts/default.ctp b/cake/console/libs/templates/skel/views/layouts/default.ctp
new file mode 100755
index 00000000..8cc34671
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/layouts/default.ctp
@@ -0,0 +1,64 @@
+
+
+
+
+ charset(); ?>
+
+
+
+
+ meta('icon');
+
+ echo $html->css('cake.generic');
+
+ echo $scripts_for_layout;
+ ?>
+
+
+
+
+ link(__('CakePHP: the rapid development php framework', true), 'http://cakephp.org'); ?>
+
+
+
+ flash(); ?>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/views/layouts/email/html/default.ctp b/cake/console/libs/templates/skel/views/layouts/email/html/default.ctp
new file mode 100755
index 00000000..1853a7b3
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/layouts/email/html/default.ctp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+ This email was sent using the CakePHP Framework
+
+
diff --git a/cake/console/libs/templates/skel/views/layouts/email/text/default.ctp b/cake/console/libs/templates/skel/views/layouts/email/text/default.ctp
new file mode 100755
index 00000000..77fda3bd
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/layouts/email/text/default.ctp
@@ -0,0 +1,29 @@
+
+
+
+
+This email was sent using the CakePHP Framework, http://cakephp.org.
+
diff --git a/cake/console/libs/templates/skel/views/layouts/flash.ctp b/cake/console/libs/templates/skel/views/layouts/flash.ctp
new file mode 100755
index 00000000..ddd63085
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/layouts/flash.ctp
@@ -0,0 +1,44 @@
+
+
+
+
+charset(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/views/layouts/js/default.ctp b/cake/console/libs/templates/skel/views/layouts/js/default.ctp
new file mode 100755
index 00000000..d94dc903
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/layouts/js/default.ctp
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/views/layouts/rss/default.ctp b/cake/console/libs/templates/skel/views/layouts/rss/default.ctp
new file mode 100755
index 00000000..94067f2b
--- /dev/null
+++ b/cake/console/libs/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/cake/console/libs/templates/skel/views/layouts/xml/default.ctp b/cake/console/libs/templates/skel/views/layouts/xml/default.ctp
new file mode 100755
index 00000000..c6887029
--- /dev/null
+++ b/cake/console/libs/templates/skel/views/layouts/xml/default.ctp
@@ -0,0 +1,2 @@
+header()); ?>
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/views/pages/empty b/cake/console/libs/templates/skel/views/pages/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/views/scaffolds/empty b/cake/console/libs/templates/skel/views/scaffolds/empty
new file mode 100755
index 00000000..e69de29b
diff --git a/cake/console/libs/templates/skel/webroot/.htaccess b/cake/console/libs/templates/skel/webroot/.htaccess
new file mode 100755
index 00000000..f9d8b938
--- /dev/null
+++ b/cake/console/libs/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/cake/console/libs/templates/skel/webroot/css.php b/cake/console/libs/templates/skel/webroot/css.php
new file mode 100755
index 00000000..52b57fee
--- /dev/null
+++ b/cake/console/libs/templates/skel/webroot/css.php
@@ -0,0 +1,102 @@
+compress($data);
+ $ratio = 100 - (round(strlen($output) / strlen($data), 3) * 100);
+ $output = " /* file: $name, ratio: $ratio% */ " . $output;
+ return $output;
+ }
+/**
+ * Enter description here...
+ *
+ * @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)) {
+ die('Wrong file name.');
+ }
+
+ $filename = 'css/' . $regs[1];
+ $filepath = CSS . $regs[1];
+ $cachepath = CACHE . 'css' . DS . str_replace(array('/','\\'), '-', $regs[1]);
+
+ if (!file_exists($filepath)) {
+ die('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;
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/webroot/css/cake.generic.css b/cake/console/libs/templates/skel/webroot/css/cake.generic.css
new file mode 100755
index 00000000..5042adfb
--- /dev/null
+++ b/cake/console/libs/templates/skel/webroot/css/cake.generic.css
@@ -0,0 +1,480 @@
+/* SVN FILE: $Id$ */
+/**
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org)
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cake
+ * @subpackage cake.app.webroot.css
+ * @since CakePHP(tm)
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+
+* {
+ 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 {
+ background:#fff;
+ color: #003d4c;
+ text-decoration: underline;
+ font-weight: bold;
+}
+a:hover {
+ background:#fff;
+ color: #003d4c;
+ text-decoration:none;
+}
+a img {
+ border:none;
+}
+h1, h2, h3, h4 {
+ font-weight: normal;
+}
+h1 {
+ background:#fff;
+ color: #003d4c;
+ font-size: 100%;
+ margin: 0.1em 0;
+}
+h2 {
+ background:#fff;
+ color: #e32;
+ font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif;
+ font-size: 190%;
+ margin: 0.3em 0;
+ padding-top: 0.8em;
+}
+h3 {
+ color: #993;
+ font-family:'Gill Sans','lucida grande',helvetica, arial, sans-serif;
+ font-size: 165%;
+ padding-top: 1.5em;
+}
+h4 {
+ color: #993;
+ font-weight: normal;
+ padding-top: 0.5em;
+}
+ul, li {
+ margin: 0 12px;
+}
+
+/* Layout */
+#container {
+ text-align: left;
+}
+
+#header{
+ padding: 10px 20px;
+}
+#header h1 {
+ background: #003d4c url('../img/cake.icon.gif') 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;
+}
+
+/* Tables */
+table {
+ background: #fff;
+ border:1px solid #ccc;
+ border-right:0;
+ clear: both;
+ color: #333;
+ margin-bottom: 10px;
+ width: 100%;
+}
+th {
+ background: #f2f2f2;
+ border:1px solid #bbb;
+ border-top: 1px solid #fff;
+ border-left: 1px solid #fff;
+ text-align: center;
+}
+th a {
+ background:#f2f2f2;
+ display: block;
+ padding: 2px 4px;
+ text-decoration: none;
+}
+th a:hover {
+ background: #ccc;
+ color: #333;
+ text-decoration: none;
+}
+table tr td {
+ background: #fff;
+ border-right: 1px solid #ccc;
+ padding: 4px;
+ text-align: center;
+ vertical-align: top;
+}
+table tr.altrow td {
+ background: #f4f4f4;
+}
+td.actions {
+ text-align: center;
+ white-space: nowrap;
+}
+td.actions a {
+ margin: 0px 6px;
+}
+.cake-sql-log table {
+ background: #f4f4f4;
+}
+.cake-sql-log td {
+ padding: 4px 8px;
+ text-align: left;
+}
+
+/* Paging */
+div.paging {
+ background:#fff;
+ color: #ccc;
+ margin-bottom: 2em;
+}
+div.paging div.disabled {
+ color: #ddd;
+ display: inline;
+}
+div.paging span {
+}
+div.paging span.current {
+ color: #000;
+}
+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: 80%;
+}
+fieldset {
+ border: 1px solid #ccc;
+ margin-top: 30px;
+ 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 div.input {
+ color: #444;
+}
+form div.required {
+ color: #333;
+ font-weight: bold;
+}
+form div.submit {
+ border: 0;
+ clear: both;
+ margin-top: 10px;
+ margin-left: 140px;
+}
+label {
+ display: block;
+ font-size: 110%;
+ padding-right: 20px;
+}
+input, textarea {
+ clear: both;
+ font-size: 140%;
+ font-family: "frutiger linotype", "lucida grande", "verdana", sans-serif;
+ padding: 2px;
+ width: 100%;
+}
+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;
+}
+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%;
+ padding: 2px 5px;
+ width: auto;
+ vertical-align: bottom;
+}
+
+/* Notices and Errors */
+div.message {
+ clear: both;
+ color: #900;
+ font-size: 140%;
+ font-weight: bold;
+ margin: 1em 0;
+}
+div.error-message {
+ clear: both;
+ color: #900;
+ font-weight: bold;
+}
+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: 0px 0;
+ padding: 0;
+}
+div.actions li {
+ display: inline;
+ list-style-type: none;
+ line-height: 2em;
+ margin: 0 2em 0 0;
+ white-space: nowrap;
+}
+div.actions ul li a {
+ background:#fff;
+ color: #003d4c;
+ text-decoration: none;
+}
+div.actions ul li a:hover {
+ color: #333;
+ text-decoration: underline;
+}
+
+/* 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;
+ border: 4px dotted #ffcc00;
+ 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;
+}
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/webroot/favicon.ico b/cake/console/libs/templates/skel/webroot/favicon.ico
new file mode 100755
index 00000000..b36e81f2
Binary files /dev/null and b/cake/console/libs/templates/skel/webroot/favicon.ico differ
diff --git a/cake/console/libs/templates/skel/webroot/img/cake.icon.gif b/cake/console/libs/templates/skel/webroot/img/cake.icon.gif
new file mode 100755
index 00000000..f29f72eb
Binary files /dev/null and b/cake/console/libs/templates/skel/webroot/img/cake.icon.gif differ
diff --git a/cake/console/libs/templates/skel/webroot/img/cake.power.gif b/cake/console/libs/templates/skel/webroot/img/cake.power.gif
new file mode 100755
index 00000000..8f8d570a
Binary files /dev/null and b/cake/console/libs/templates/skel/webroot/img/cake.power.gif differ
diff --git a/cake/console/libs/templates/skel/webroot/index.php b/cake/console/libs/templates/skel/webroot/index.php
new file mode 100755
index 00000000..01be9dd6
--- /dev/null
+++ b/cake/console/libs/templates/skel/webroot/index.php
@@ -0,0 +1,93 @@
+dispatch($url);
+ }
+ if (Configure::read() > 0) {
+ echo "";
+ }
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/webroot/js/vendors.php b/cake/console/libs/templates/skel/webroot/js/vendors.php
new file mode 100755
index 00000000..5fda0b7b
--- /dev/null
+++ b/cake/console/libs/templates/skel/webroot/js/vendors.php
@@ -0,0 +1,42 @@
+
\ No newline at end of file
diff --git a/cake/console/libs/templates/skel/webroot/test.php b/cake/console/libs/templates/skel/webroot/test.php
new file mode 100755
index 00000000..a57d1574
--- /dev/null
+++ b/cake/console/libs/templates/skel/webroot/test.php
@@ -0,0 +1,180 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.cake.tests.libs
+ * @since CakePHP(tm) v 1.2.0.4433
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+set_time_limit(0);
+ini_set('memory_limit','128M');
+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 = Configure::corePaths('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);
+}
+
+require_once CAKE_TESTS_LIB . 'test_manager.php';
+
+if (Configure::read('debug') < 1) {
+ die(__('Debug setting does not allow access to this url.', true));
+}
+
+if (!isset($_SERVER['SERVER_NAME'])) {
+ $_SERVER['SERVER_NAME'] = '';
+}
+if (empty( $_GET['output'])) {
+ $_GET['output'] = 'html';
+}
+/**
+ *
+ * Used to determine output to display
+ */
+define('CAKE_TEST_OUTPUT_HTML', 1);
+define('CAKE_TEST_OUTPUT_TEXT', 2);
+
+if (isset($_GET['output']) && $_GET['output'] == 'html') {
+ define('CAKE_TEST_OUTPUT', CAKE_TEST_OUTPUT_HTML);
+} else {
+ Debugger::output('txt');
+ define('CAKE_TEST_OUTPUT', CAKE_TEST_OUTPUT_TEXT);
+}
+
+if (!App::import('Vendor', 'simpletest' . DS . 'reporter')) {
+ CakePHPTestHeader();
+ include CAKE_TESTS_LIB . 'simpletest.php';
+ CakePHPTestSuiteFooter();
+ exit();
+}
+
+$analyzeCodeCoverage = false;
+if (isset($_GET['code_coverage'])) {
+ $analyzeCodeCoverage = true;
+ require_once CAKE_TESTS_LIB . 'code_coverage_manager.php';
+ if (!extension_loaded('xdebug')) {
+ CakePHPTestHeader();
+ include CAKE_TESTS_LIB . 'xdebug.php';
+ CakePHPTestSuiteFooter();
+ exit();
+ }
+}
+
+CakePHPTestHeader();
+CakePHPTestSuiteHeader();
+define('RUN_TEST_LINK', $_SERVER['PHP_SELF']);
+
+if (isset($_GET['group'])) {
+ if ('all' == $_GET['group']) {
+ TestManager::runAllTests(CakeTestsGetReporter());
+ } else {
+ if ($analyzeCodeCoverage) {
+ CodeCoverageManager::start($_GET['group'], CakeTestsGetReporter());
+ }
+ TestManager::runGroupTest(ucfirst($_GET['group']), CakeTestsGetReporter());
+ if ($analyzeCodeCoverage) {
+ CodeCoverageManager::report();
+ }
+ }
+
+ CakePHPTestRunMore();
+ CakePHPTestAnalyzeCodeCoverage();
+} elseif (isset($_GET['case'])) {
+ if ($analyzeCodeCoverage) {
+ CodeCoverageManager::start($_GET['case'], CakeTestsGetReporter());
+ }
+
+ TestManager::runTestCase($_GET['case'], CakeTestsGetReporter());
+
+ if ($analyzeCodeCoverage) {
+ CodeCoverageManager::report();
+ }
+
+ CakePHPTestRunMore();
+ CakePHPTestAnalyzeCodeCoverage();
+} elseif (isset($_GET['show']) && $_GET['show'] == 'cases') {
+ CakePHPTestCaseList();
+} else {
+ CakePHPTestGroupTestList();
+}
+CakePHPTestSuiteFooter();
+$output = ob_get_clean();
+echo $output;
+?>
\ No newline at end of file
diff --git a/cake/console/libs/templates/views/form.ctp b/cake/console/libs/templates/views/form.ctp
new file mode 100755
index 00000000..fda3eddc
--- /dev/null
+++ b/cake/console/libs/templates/views/form.ctp
@@ -0,0 +1,69 @@
+
+
+create('{$modelClass}');?>\n";?>
+
+end('Submit');?>\n";
+?>
+
+
+
+
+ - link(__('Delete', true), array('action' => 'delete', \$form->value('{$modelClass}.{$primaryKey}')), null, sprintf(__('Are you sure you want to delete # %s?', true), \$form->value('{$modelClass}.{$primaryKey}'))); ?>";?>
+
+ - link(__('List {$pluralHumanName}', true), array('action' => 'index'));?>";?>
+ $data) {
+ foreach ($data as $alias => $details) {
+ if ($details['controller'] != $this->name && !in_array($details['controller'], $done)) {
+ echo "\t\t- link(__('List " . Inflector::humanize($details['controller']) . "', true), array('controller' => '{$details['controller']}', 'action' => 'index')); ?>
\n";
+ echo "\t\t- link(__('New " . Inflector::humanize(Inflector::underscore($alias)) . "', true), array('controller' => '{$details['controller']}', 'action' => 'add')); ?>
\n";
+ $done[] = $details['controller'];
+ }
+ }
+ }
+?>
+
+
diff --git a/cake/console/libs/templates/views/home.ctp b/cake/console/libs/templates/views/home.ctp
new file mode 100755
index 00000000..5b3dc316
--- /dev/null
+++ b/cake/console/libs/templates/views/home.ctp
@@ -0,0 +1,82 @@
+Sweet, \"" . Inflector::humanize($app) . "\" got Baked by CakePHP!\n";
+$output .="
+ 0):
+ Debugger::checkSessionKey();
+endif;
+?>
+
+';
+ __('Your tmp directory is writable.');
+ echo '';
+ else:
+ echo '';
+ __('Your tmp directory is NOT writable.');
+ echo '';
+ endif;
+?>
+
+
+';
+ echo sprintf(__('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;
+?>
+
\n";
+$output .= "\n";
+$output .= "\n";
+$output .= "\n";
+$output .= "', APP . 'views' . DS . 'layouts' . DS . 'default.ctp.
', APP . 'webroot' . DS . 'css');\n";
+$output .= "?>\n";
+$output .= "
\n";
+?>
diff --git a/cake/console/libs/templates/views/index.ctp b/cake/console/libs/templates/views/index.ctp
new file mode 100755
index 00000000..49c69dba
--- /dev/null
+++ b/cake/console/libs/templates/views/index.ctp
@@ -0,0 +1,99 @@
+
+
+";?>
+
+counter(array(
+'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
+));
+?>";?>
+
+
+
+
+ sort('{$field}');?>";?>
+
+ ";?>
+
+\n";
+ echo "\t>\n";
+ foreach ($fields as $field) {
+ $isKey = false;
+ if (!empty($associations['belongsTo'])) {
+ foreach ($associations['belongsTo'] as $alias => $details) {
+ if ($field === $details['foreignKey']) {
+ $isKey = true;
+ echo "\t\t\n\t\t\tlink(\${$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\n\t\t\t\n\t\t \n";
+ }
+ }
+
+ echo "\t\t\n";
+ echo "\t\t\tlink(__('View', true), array('action' => 'view', \${$singularVar}['{$modelClass}']['{$primaryKey}'])); ?>\n";
+ echo "\t\t\tlink(__('Edit', true), array('action' => 'edit', \${$singularVar}['{$modelClass}']['{$primaryKey}'])); ?>\n";
+ echo "\t\t\tlink(__('Delete', true), array('action' => 'delete', \${$singularVar}['{$modelClass}']['{$primaryKey}']), null, sprintf(__('Are you sure you want to delete # %s?', true), \${$singularVar}['{$modelClass}']['{$primaryKey}'])); ?>\n";
+ echo "\t\t \n";
+ echo "\t \n";
+
+echo "\n";
+?>
+
+
+
+prev('<< '.__('previous', true), array(), null, array('class'=>'disabled'));?>\n";?>
+ | numbers();?>\n"?>
+next(__('next', true).' >>', array(), null, array('class' => 'disabled'));?>\n";?>
+
+
+
+ - link(__('New {$singularHumanName}', true), array('action' => 'add')); ?>";?>
+ $data) {
+ foreach ($data as $alias => $details) {
+ if ($details['controller'] != $this->name && !in_array($details['controller'], $done)) {
+ echo "\t\t- link(__('List " . Inflector::humanize($details['controller']) . "', true), array('controller' => '{$details['controller']}', 'action' => 'index')); ?>
\n";
+ echo "\t\t- link(__('New " . Inflector::humanize(Inflector::underscore($alias)) . "', true), array('controller' => '{$details['controller']}', 'action' => 'add')); ?>
\n";
+ $done[] = $details['controller'];
+ }
+ }
+ }
+?>
+
+
diff --git a/cake/console/libs/templates/views/view.ctp b/cake/console/libs/templates/views/view.ctp
new file mode 100755
index 00000000..e390bb02
--- /dev/null
+++ b/cake/console/libs/templates/views/view.ctp
@@ -0,0 +1,150 @@
+
+
+";?>
+ \n";?>
+ $details) {
+ if ($field === $details['foreignKey']) {
+ $isKey = true;
+ echo "\t\t- >
\n";
+ echo "\t\t- >\n\t\t\tlink(\${$singularVar}['{$alias}']['{$details['displayField']}'], array('controller' => '{$details['controller']}', 'action' => 'view', \${$singularVar}['{$alias}']['{$details['primaryKey']}'])); ?>\n\t\t\t \n\t\t
\n";
+ break;
+ }
+ }
+ }
+ if ($isKey !== true) {
+ echo "\t\t- >
\n";
+ echo "\t\t- >\n\t\t\t\n\t\t\t \n\t\t
\n";
+ }
+}
+?>
+
+
+
+
+link(__('Edit {$singularHumanName}', true), array('action' => 'edit', \${$singularVar}['{$modelClass}']['{$primaryKey}'])); ?> \n";
+ echo "\t\t- link(__('Delete {$singularHumanName}', true), array('action' => 'delete', \${$singularVar}['{$modelClass}']['{$primaryKey}']), null, sprintf(__('Are you sure you want to delete # %s?', true), \${$singularVar}['{$modelClass}']['{$primaryKey}'])); ?>
\n";
+ echo "\t\t- link(__('List {$pluralHumanName}', true), array('action' => 'index')); ?>
\n";
+ echo "\t\t- link(__('New {$singularHumanName}', true), 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- link(__('List " . Inflector::humanize($details['controller']) . "', true), array('controller' => '{$details['controller']}', 'action' => 'index')); ?>
\n";
+ echo "\t\t- link(__('New " . Inflector::humanize(Inflector::underscore($alias)) . "', true), array('controller' => '{$details['controller']}', 'action' => 'add')); ?>
\n";
+ $done[] = $details['controller'];
+ }
+ }
+ }
+?>
+
+
+ $details): ?>
+
+ $details):
+ $otherSingularVar = Inflector::variable($alias);
+ $otherPluralHumanName = Inflector::humanize($details['controller']);
+ ?>
+
+
\ No newline at end of file
diff --git a/cake/console/libs/testsuite.php b/cake/console/libs/testsuite.php
new file mode 100755
index 00000000..b11a7ea1
--- /dev/null
+++ b/cake/console/libs/testsuite.php
@@ -0,0 +1,360 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.cake.console.libs
+ * @since CakePHP(tm) v 1.2.0.4433
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+class TestSuiteShell extends Shell {
+/**
+ * The test category, "app", "core" or the name of a plugin
+ *
+ * @var string
+ * @access public
+ */
+ var $category = '';
+/**
+ * "group", "case" or "all"
+ *
+ * @var string
+ * @access public
+ */
+ var $type = '';
+/**
+ * Path to the test case/group file
+ *
+ * @var string
+ * @access public
+ */
+ var $file = '';
+/**
+ * Storage for plugins that have tests
+ *
+ * @var string
+ * @access public
+ */
+ var $plugins = array();
+/**
+ * Convenience variable to avoid duplicated code
+ *
+ * @var string
+ * @access public
+ */
+ var $isPluginTest = false;
+/**
+ * Stores if the user wishes to get a code coverage analysis report
+ *
+ * @var string
+ * @access public
+ */
+ var $doCoverage = false;
+/**
+ * The headline for the test output
+ *
+ * @var string
+ * @access public
+ */
+ var $headline = 'CakePHP Test Shell';
+/**
+ * Initialization method installs Simpletest and loads all plugins
+ *
+ * @return void
+ * @access public
+ */
+ function initialize() {
+ $corePath = Configure::corePaths('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);
+ }
+
+ $this->__installSimpleTest();
+
+ require_once CAKE . 'tests' . DS . 'lib' . DS . 'test_manager.php';
+ require_once CAKE . 'tests' . DS . 'lib' . DS . 'cli_reporter.php';
+
+ $plugins = Configure::listObjects('plugin');
+ foreach ($plugins as $p) {
+ $this->plugins[] = Inflector::underscore($p);
+ }
+ }
+/**
+ * Main entry point to this shell
+ *
+ * @return void
+ * @access public
+ */
+ function main() {
+ $this->out($this->headline);
+ $this->hr();
+
+ if (count($this->args) > 0) {
+ $this->category = $this->args[0];
+
+ if (!in_array($this->category, array('app', 'core'))) {
+ $this->isPluginTest = true;
+ }
+
+ if (isset($this->args[1])) {
+ $this->type = $this->args[1];
+ }
+
+ if (isset($this->args[2])) {
+ if ($this->args[2] == 'cov') {
+ $this->doCoverage = true;
+ } else {
+ $this->file = Inflector::underscore($this->args[2]);
+ }
+ }
+
+ if (isset($this->args[3]) && $this->args[3] == 'cov') {
+ $this->doCoverage = true;
+ }
+ } else {
+ $this->err('Sorry, you did not pass any arguments!');
+ }
+
+ if ($this->__canRun()) {
+ $this->out('Running '.$this->category.' '.$this->type.' '.$this->file);
+
+ $exitCode = 0;
+ if (!$this->__run()) {
+ $exitCode = 1;
+ }
+ exit($exitCode);
+ } else {
+ $this->err('Sorry, the tests could not be found.');
+ exit(1);
+ }
+ }
+/**
+ * Help screen
+ *
+ * @return void
+ * @access public
+ */
+ function help() {
+ $this->out('Usage: ');
+ $this->out("\tcake testsuite category test_type file");
+ $this->out("\t\t- category - \"app\", \"core\" or name of a plugin");
+ $this->out("\t\t- test_type - \"case\", \"group\" or \"all\"");
+ $this->out("\t\t- test_file - file name with folder prefix and without the (test|group).php suffix");
+ $this->out('');
+ $this->out('Examples: ');
+ $this->out("\t\tcake testsuite app all");
+ $this->out("\t\tcake testsuite core all");
+ $this->out('');
+ $this->out("\t\tcake testsuite app case behaviors/debuggable");
+ $this->out("\t\tcake testsuite app case models/my_model");
+ $this->out("\t\tcake testsuite app case controllers/my_controller");
+ $this->out('');
+ $this->out("\t\tcake testsuite core case file");
+ $this->out("\t\tcake testsuite core case router");
+ $this->out("\t\tcake testsuite core case set");
+ $this->out('');
+ $this->out("\t\tcake testsuite app group mygroup");
+ $this->out("\t\tcake testsuite core group acl");
+ $this->out("\t\tcake testsuite core group socket");
+ $this->out('');
+ $this->out("\t\tcake testsuite bugs case models/bug");
+ $this->out("\t\t // for the plugin 'bugs' and its test case 'models/bug'");
+ $this->out("\t\tcake testsuite bugs group bug");
+ $this->out("\t\t // for the plugin bugs and its test group 'bug'");
+ $this->out('');
+ $this->out('Code Coverage Analysis: ');
+ $this->out("\n\nAppend 'cov' to any of the above in order to enable code coverage analysis");
+ }
+/**
+ * Checks if the arguments supplied point to a valid test file and thus the shell can be run.
+ *
+ * @return bool true if it's a valid test file, false otherwise
+ * @access private
+ */
+ function __canRun() {
+ $isNeitherAppNorCore = !in_array($this->category, array('app', 'core'));
+ $isPlugin = in_array(Inflector::underscore($this->category), $this->plugins);
+
+ if ($isNeitherAppNorCore && !$isPlugin) {
+ $this->err($this->category.' is an invalid test category (either "app", "core" or name of a plugin)');
+ return false;
+ }
+
+ $folder = $this->__findFolderByCategory($this->category);
+ if (!file_exists($folder)) {
+ $this->err($folder . ' not found');
+ return false;
+ }
+
+ if (!in_array($this->type, array('all', 'group', 'case'))) {
+ $this->err($this->type.' is invalid. Should be case, group or all');
+ return false;
+ }
+
+ switch ($this->type) {
+ case 'all':
+ return true;
+ break;
+ case 'group':
+ if (file_exists($folder.DS.'groups'.DS.$this->file.'.group.php')) {
+ return true;
+ }
+ break;
+ case 'case':
+ if ($this->category == 'app' && file_exists($folder.DS.'cases'.DS.$this->file.'.test.php')) {
+ return true;
+ }
+
+ if ($this->category == 'core' && file_exists($folder.DS.'cases'.DS.'libs'.DS.$this->file.'.test.php')) {
+ return true;
+ }
+
+ if ($isPlugin && file_exists($folder.DS.'cases'.DS.$this->file.'.test.php')) {
+ return true;
+ }
+ break;
+ }
+
+ $this->err($this->category.' '.$this->type.' '.$this->file.' is an invalid test identifier');
+ return false;
+ }
+/**
+ * Executes the tests depending on our settings
+ *
+ * @return void
+ * @access private
+ */
+ function __run() {
+ $reporter = new CLIReporter();
+ $this->__setGetVars();
+
+ if ($this->type == 'all') {
+ return TestManager::runAllTests($reporter);
+ }
+
+ if ($this->doCoverage) {
+ if (!extension_loaded('xdebug')) {
+ $this->out('You must install Xdebug to use the CakePHP(tm) Code Coverage Analyzation. Download it from http://www.xdebug.org/docs/install');
+ exit(0);
+ }
+ }
+
+ if ($this->type == 'group') {
+ $ucFirstGroup = ucfirst($this->file);
+
+ $path = CORE_TEST_GROUPS;
+ if ($this->category == 'app') {
+ $path = APP_TEST_GROUPS;
+ } elseif ($this->isPluginTest) {
+ $path = APP.'plugins'.DS.$this->category.DS.'tests'.DS.'groups';
+ }
+
+ if ($this->doCoverage) {
+ require_once CAKE . 'tests' . DS . 'lib' . DS . 'code_coverage_manager.php';
+ CodeCoverageManager::start($ucFirstGroup, $reporter);
+ }
+ $result = TestManager::runGroupTest($ucFirstGroup, $reporter);
+ if ($this->doCoverage) {
+ CodeCoverageManager::report();
+ }
+ return $result;
+ }
+
+ $case = 'libs'.DS.$this->file.'.test.php';
+ if ($this->category == 'app') {
+ $case = $this->file.'.test.php';
+ } elseif ($this->isPluginTest) {
+ $case = $this->file.'.test.php';
+ }
+
+ if ($this->doCoverage) {
+ require_once CAKE . 'tests' . DS . 'lib' . DS . 'code_coverage_manager.php';
+ CodeCoverageManager::start($case, $reporter);
+ }
+
+ $result = TestManager::runTestCase($case, $reporter);
+ if ($this->doCoverage) {
+ CodeCoverageManager::report();
+ }
+
+ return $result;
+ }
+/**
+ * Finds the correct folder to look for tests for based on the input category
+ *
+ * @return string the folder path
+ * @access private
+ */
+ function __findFolderByCategory($category) {
+ $folder = '';
+ $paths = array(
+ 'core' => CAKE,
+ 'app' => APP
+ );
+
+ if (array_key_exists($category, $paths)) {
+ $folder = $paths[$category] . 'tests';
+ } else {
+ $scoredCategory = Inflector::underscore($category);
+ $folder = APP . 'plugins' . DS . $scoredCategory . DS;
+ $pluginPaths = Configure::read('pluginPaths');
+ foreach ($pluginPaths as $path) {
+ if (file_exists($path . $scoredCategory . DS . 'tests')) {
+ $folder = $path . $scoredCategory . DS . 'tests';
+ break;
+ }
+ }
+ }
+ return $folder;
+ }
+/**
+ * Sets some get vars needed for TestManager
+ *
+ * @return void
+ * @access private
+ */
+ function __setGetVars() {
+ if (in_array($this->category, $this->plugins)) {
+ $_GET['plugin'] = $this->category;
+ } elseif (in_array(Inflector::Humanize($this->category), $this->plugins)) {
+ $_GET['plugin'] = Inflector::Humanize($this->category);
+ } elseif ($this->category == 'app') {
+ $_GET['app'] = true;
+ }
+ if ($this->type == 'group') {
+ $_GET['group'] = true;
+ }
+ }
+/**
+ * tries to install simpletest and exits gracefully if it is not there
+ *
+ * @return void
+ * @access private
+ */
+ function __installSimpleTest() {
+ if (!App::import('Vendor', 'simpletest' . DS . 'reporter')) {
+ $this->err('Sorry, Simpletest could not be found. Download it from http://simpletest.org and install it to your vendors directory.');
+ exit;
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/dispatcher.php b/cake/dispatcher.php
new file mode 100755
index 00000000..ca147f30
--- /dev/null
+++ b/cake/dispatcher.php
@@ -0,0 +1,689 @@
+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->cached($url)) {
+ $this->_stop();
+ }
+
+ $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 = (bool)(strpos($this->params['action'], '_', 0) === 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'], '_') !== false && !$privateAction) {
+ 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 = $this->plugin;
+ $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 (array_key_exists('return', $this->params) && $this->params['return'] == 1) {
+ $controller->autoRender = false;
+ }
+ if (!empty($this->params['bare'])) {
+ $controller->autoLayout = false;
+ }
+ if (array_key_exists('layout', $this->params)) {
+ if (empty($this->params['layout'])) {
+ $controller->autoLayout = false;
+ } else {
+ $controller->layout = $this->params['layout'];
+ }
+ }
+ if (isset($this->params['viewPath'])) {
+ $controller->viewPath = $this->params['viewPath'];
+ }
+ return $this->_invoke($controller, $this->params);
+ }
+/**
+ * Invokes given controller's render action if autoRender option is set. Otherwise the
+ * contents of the operation are returned as a string.
+ *
+ * @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->Component->initialize($controller);
+ $controller->beforeFilter();
+ $controller->Component->startup($controller);
+
+ $methods = array_flip($controller->methods);
+
+ if (!isset($methods[strtolower($params['action'])])) {
+ if ($controller->scaffold !== false) {
+ App::import('Core', 'Scaffold');
+ 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 = $controller->dispatchMethod($params['action'], $params['pass']);
+
+ if ($controller->autoRender) {
+ $controller->output = $controller->render();
+ } elseif (empty($controller->output)) {
+ $controller->output = $output;
+ }
+ $controller->Component->shutdown($controller);
+ $controller->afterFilter();
+
+ if (isset($params['return'])) {
+ return $controller->output;
+ }
+ echo($controller->output);
+ }
+/**
+ * Sets the params when $url is passed as an array to Object::requestAction();
+ *
+ * @param array $url
+ * @param array $additionalParams
+ * @return string $url
+ * @access private
+ */
+ function __extractParams($url, $additionalParams = array()) {
+ $defaults = array('pass' => array(), 'named' => array(), 'form' => array());
+ $this->params = array_merge($defaults, $url, $additionalParams);
+ return Router::url($url);
+ }
+/**
+ * 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 (isset($_SERVER) && !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(Router::parse($fromUrl), $params);
+
+ if (strlen($params['action']) === 0) {
+ $params['action'] = 'index';
+ }
+ if (isset($params['form']['data'])) {
+ $params['data'] = Router::stripEscape($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 = null;
+
+ if ($baseUrl) {
+ $file = '/' . basename($baseUrl);
+ $base = dirname($baseUrl);
+
+ if ($base === DS || $base === '.') {
+ $base = '';
+ }
+ $this->webroot = $base .'/';
+
+ if (strpos($this->webroot, $dir) === false) {
+ $this->webroot .= $dir . '/' ;
+ }
+ if (strpos($this->webroot, $webroot) === false) {
+ $this->webroot .= $webroot . '/';
+ }
+ return $base . $file;
+ }
+ return false;
+ }
+/**
+ * Restructure params in case we're serving a plugin.
+ *
+ * @param array $params Array on where to re-set 'controller', 'action', and 'pass' indexes
+ * @param boolean $reverse
+ * @return array Restructured array
+ * @access protected
+ */
+ function _restructureParams($params, $reverse = false) {
+ if ($reverse === true) {
+ extract(Router::getArgs($params['action']));
+ $params = array_merge($params, array(
+ 'controller'=> $params['plugin'],
+ 'action'=> $params['controller'],
+ 'pass' => array_merge($pass, $params['pass']),
+ 'named' => array_merge($named, $params['named'])
+ ));
+ $this->plugin = $params['plugin'];
+ } else {
+ $params['plugin'] = $params['controller'];
+ $params['controller'] = $params['action'];
+ if (isset($params['pass'][0])) {
+ $params['action'] = $params['pass'][0];
+ array_shift($params['pass']);
+ } else {
+ $params['action'] = null;
+ }
+ }
+ return $params;
+ }
+/**
+ * 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($params = null) {
+ if (!is_array($params)) {
+ $original = $params = $this->params;
+ }
+ $controller = false;
+ $ctrlClass = $this->__loadController($params);
+ if (!$ctrlClass) {
+ if (!isset($params['plugin'])) {
+ $params = $this->_restructureParams($params);
+ } else {
+ if (empty($original['pass']) && $original['action'] == 'index') {
+ $params['action'] = null;
+ }
+ $params = $this->_restructureParams($params, true);
+ }
+ $ctrlClass = $this->__loadController($params);
+ if (!$ctrlClass) {
+ $this->params = $original;
+ return $controller;
+ }
+ } else {
+ $params = $this->params;
+ }
+ $name = $ctrlClass;
+ $ctrlClass = $ctrlClass . 'Controller';
+ if (class_exists($ctrlClass)) {
+ if (strtolower(get_parent_class($ctrlClass)) === strtolower($name . 'AppController') && empty($params['plugin'])) {
+ $params = $this->_restructureParams($params);
+ $params = $this->_restructureParams($params, true);
+ }
+ $this->params = $params;
+ $controller =& new $ctrlClass();
+ }
+ return $controller;
+ }
+/**
+ * Load controller and return controller class
+ *
+ * @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'])) {
+ $this->plugin = $params['plugin'];
+ $pluginName = Inflector::camelize($params['plugin']);
+ $pluginPath = $pluginName . '.';
+ $this->params['controller'] = $this->plugin;
+ $controller = $pluginName;
+ }
+ if (!empty($params['controller'])) {
+ $this->params['controller'] = $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 = preg_split('/\?/', $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) !== false) {
+ $elements = explode($base, $uri);
+ } 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 for js, css, img, view cache
+ *
+ * @param string $url Requested URL
+ * @access public
+ */
+ function cached($url) {
+ if (strpos($url, 'css/') !== false || strpos($url, 'js/') !== false || strpos($url, 'img/') !== false) {
+ if (strpos($url, 'ccss/') === 0) {
+ include WWW_ROOT . DS . Configure::read('Asset.filter.css');
+ $this->_stop();
+ } elseif (strpos($url, 'cjs/') === 0) {
+ include WWW_ROOT . DS . Configure::read('Asset.filter.js');
+ $this->_stop();
+ }
+ $isAsset = false;
+ $assets = array('js' => 'text/javascript', 'css' => 'text/css', 'gif' => 'image/gif', 'jpg' => 'image/jpeg', 'png' => 'image/png');
+ $ext = array_pop(explode('.', $url));
+
+ foreach ($assets as $type => $contentType) {
+ if ($type === $ext) {
+ if ($type === 'css' || $type === 'js') {
+ $pos = strpos($url, $type . '/');
+ } else {
+ $pos = strpos($url, 'img/');
+ }
+ $isAsset = true;
+ break;
+ }
+ }
+
+ if ($isAsset === true) {
+ $ob = @ini_get("zlib.output_compression") !== '1' && extension_loaded("zlib") && (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false);
+
+ if ($ob && Configure::read('Asset.compress')) {
+ ob_start();
+ ob_start('ob_gzhandler');
+ }
+ $assetFile = null;
+ $paths = array();
+
+ if ($pos > 0) {
+ $plugin = substr($url, 0, $pos - 1);
+ $url = preg_replace('/^' . preg_quote($plugin, '/') . '\//i', '', $url);
+ $pluginPaths = Configure::read('pluginPaths');
+ $count = count($pluginPaths);
+ for ($i = 0; $i < $count; $i++) {
+ $paths[] = $pluginPaths[$i] . $plugin . DS . 'vendors' . DS;
+ }
+ }
+ $paths = array_merge($paths, Configure::read('vendorPaths'));
+ foreach ($paths as $path) {
+ if (is_file($path . $url) && file_exists($path . $url)) {
+ $assetFile = $path . $url;
+ break;
+ }
+ }
+
+ if ($assetFile !== null) {
+ $fileModified = filemtime($assetFile);
+ header("Date: " . date("D, j M Y G:i:s ", $fileModified) . 'GMT');
+ header('Content-type: ' . $assets[$type]);
+ header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT");
+ header("Cache-Control: cache");
+ header("Pragma: cache");
+ if ($type === 'css' || $type === 'js') {
+ include($assetFile);
+ } else {
+ readfile($assetFile);
+ }
+
+ if (Configure::read('Asset.compress')) {
+ ob_end_flush();
+ }
+ return true;
+ }
+ }
+ }
+
+ 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('Core', 'View');
+ }
+ $controller = null;
+ $view =& new View($controller, false);
+ return $view->renderCache($filename, getMicrotime());
+ }
+ }
+ return false;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/.DS_Store b/cake/libs/.DS_Store
new file mode 100755
index 00000000..de4b33c8
Binary files /dev/null and b/cake/libs/.DS_Store differ
diff --git a/cake/libs/cache.php b/cake/libs/cache.php
new file mode 100755
index 00000000..aabd2795
--- /dev/null
+++ b/cake/libs/cache.php
@@ -0,0 +1,506 @@
+__name;
+ }
+
+ $current = array();
+ if (isset($_this->__config[$name])) {
+ $current = $_this->__config[$name];
+ }
+
+ if (!empty($settings)) {
+ $_this->__name = null;
+ $_this->__config[$name] = array_merge($current, $settings);
+ }
+
+ if (empty($_this->__config[$name]['engine'])) {
+ return false;
+ }
+
+ $_this->__name = $name;
+ $engine = $_this->__config[$name]['engine'];
+
+ if (!$_this->isInitialized($engine)) {
+ if ($_this->engine($engine, $_this->__config[$name]) === false) {
+ return false;
+ }
+ $settings = $_this->__config[$name] = $_this->settings($engine);
+ } else {
+ $settings = $_this->__config[$name] = $_this->set($_this->__config[$name]);
+ }
+ return compact('engine', 'settings');
+ }
+/**
+ * Set the cache engine to use or modify settings for one instance
+ *
+ * @param string $name Name of the engine (without 'Engine')
+ * @param array $settings Optional associative array of settings passed to the engine
+ * @return boolean True on success, false on failure
+ * @access public
+ * @static
+ */
+ function engine($name = 'File', $settings = array()) {
+ $cacheClass = $name . 'Engine';
+ $_this =& Cache::getInstance();
+ if (!isset($_this->_Engine[$name])) {
+ if ($_this->__loadEngine($name) === false) {
+ return false;
+ }
+ $_this->_Engine[$name] =& new $cacheClass();
+ }
+
+ if ($_this->_Engine[$name]->init($settings)) {
+ if (time() % $_this->_Engine[$name]->settings['probability'] === 0) {
+ $_this->_Engine[$name]->gc();
+ }
+ return true;
+ }
+ $_this->_Engine[$name] = null;
+ return 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 of settings
+ * @access public
+ * @static
+ */
+ function set($settings = array(), $value = null) {
+ $_this =& Cache::getInstance();
+ if (!isset($_this->__config[$_this->__name])) {
+ return false;
+ }
+
+ $engine = $_this->__config[$_this->__name]['engine'];
+
+ if (!empty($settings)) {
+ $_this->__reset = true;
+ }
+
+ if ($_this->__reset === true) {
+ if (empty($settings)) {
+ $_this->__reset = false;
+ $settings = $_this->__config[$_this->__name];
+ } else {
+ if (is_string($settings) && $value !== null) {
+ $settings = array($settings => $value);
+ }
+ $settings = array_merge($_this->__config[$_this->__name], $settings);
+ }
+ $_this->_Engine[$engine]->init($settings);
+ }
+
+ return $_this->settings($engine);
+ }
+/**
+ * Garbage collection
+ *
+ * Permanently remove all expired and deleted data
+ *
+ * @return void
+ * @access public
+ * @static
+ */
+ function gc() {
+ $_this =& Cache::getInstance();
+ $config = $_this->config();
+ $_this->_Engine[$config['engine']]->gc();
+ }
+/**
+ * Write data for key into cache
+ *
+ * @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
+ * @return boolean True if the data was successfully cached, false on failure
+ * @access public
+ * @static
+ */
+ function write($key, $value, $config = null) {
+ $_this =& Cache::getInstance();
+
+ if (is_array($config)) {
+ extract($config);
+ } else if ($config && (is_numeric($config) || is_numeric($config[0]) || (isset($config[1]) && is_numeric($config[1])))) {
+ $config = null;
+ }
+
+ if ($config && isset($_this->__config[$config])) {
+ $settings = $_this->set($_this->__config[$config]);
+ } else {
+ $settings = $_this->settings();
+ }
+
+ if (empty($settings)) {
+ return null;
+ }
+ extract($settings);
+
+ if (!$_this->isInitialized($engine)) {
+ return false;
+ }
+
+ if (!$key = $_this->_Engine[$engine]->key($key)) {
+ return false;
+ }
+
+ if (is_resource($value)) {
+ return false;
+ }
+
+ if ($duration < 1) {
+ return false;
+ }
+
+ $success = $_this->_Engine[$engine]->write($settings['prefix'] . $key, $value, $duration);
+ $settings = $_this->set();
+ return $success;
+ }
+/**
+ * Read a key from the cache
+ *
+ * @param string $key Identifier for the data
+ * @param string $config 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) {
+ $_this =& Cache::getInstance();
+
+ if (isset($_this->__config[$config])) {
+ $settings = $_this->set($_this->__config[$config]);
+ } else {
+ $settings = $_this->settings();
+ }
+
+ if (empty($settings)) {
+ return null;
+ }
+ extract($settings);
+
+ if (!$_this->isInitialized($engine)) {
+ return false;
+ }
+ if (!$key = $_this->_Engine[$engine]->key($key)) {
+ return false;
+ }
+ $success = $_this->_Engine[$engine]->read($settings['prefix'] . $key);
+
+ if ($config !== null && $config !== $_this->__name) {
+ $settings = $_this->set();
+ }
+ return $success;
+ }
+/**
+ * Delete a key from the cache
+ *
+ * @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) {
+ $_this =& Cache::getInstance();
+ if (isset($_this->__config[$config])) {
+ $settings = $_this->set($_this->__config[$config]);
+ } else {
+ $settings = $_this->settings();
+ }
+
+ if (empty($settings)) {
+ return null;
+ }
+ extract($settings);
+
+ if (!$_this->isInitialized($engine)) {
+ return false;
+ }
+
+ if (!$key = $_this->_Engine[$engine]->key($key)) {
+ return false;
+ }
+
+ $success = $_this->_Engine[$engine]->delete($settings['prefix'] . $key);
+ $settings = $_this->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) {
+ $_this =& Cache::getInstance();
+ if (isset($_this->__config[$config])) {
+ $settings = $_this->set($_this->__config[$config]);
+ } else {
+ $settings = $_this->settings();
+ }
+
+ if (empty($settings)) {
+ return null;
+ }
+ extract($settings);
+
+ if (isset($engine) && !$_this->isInitialized($engine)) {
+ return false;
+ }
+ $success = $_this->_Engine[$engine]->clear($check);
+ $settings = $_this->set();
+ return $success;
+ }
+/**
+ * Check if Cache has initialized a working storage engine
+ *
+ * @param string $engine Name of the engine
+ * @param string $config Name of the configuration setting
+ * @return bool
+ * @access public
+ * @static
+ */
+ function isInitialized($engine = null) {
+ if (Configure::read('Cache.disable')) {
+ return false;
+ }
+ $_this =& Cache::getInstance();
+ if (!$engine && isset($_this->__config[$_this->__name]['engine'])) {
+ $engine = $_this->__config[$_this->__name]['engine'];
+ }
+ return isset($_this->_Engine[$engine]);
+ }
+
+/**
+ * Return the settings for current cache engine
+ *
+ * @param string $engine Name of the engine
+ * @return array list of settings for this engine
+ * @access public
+ * @static
+ */
+ function settings($engine = null) {
+ $_this =& Cache::getInstance();
+ if (!$engine && isset($_this->__config[$_this->__name]['engine'])) {
+ $engine = $_this->__config[$_this->__name]['engine'];
+ }
+
+ if (isset($_this->_Engine[$engine]) && !is_null($_this->_Engine[$engine])) {
+ return $_this->_Engine[$engine]->settings();
+ }
+ return array();
+ }
+}
+/**
+ * Storage engine for CakePHP caching
+ *
+ * @package cake
+ * @subpackage cake.cake.libs
+ */
+class CacheEngine extends Object {
+/**
+ * settings of current engine instance
+ *
+ * @var int
+ * @access public
+ */
+ var $settings = array();
+/**
+ * Iitialize 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);
+ }
+/**
+ * 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
+ *
+ * @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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/cache/apc.php b/cake/libs/cache/apc.php
new file mode 100755
index 00000000..8e2a781a
--- /dev/null
+++ b/cake/libs/cache/apc.php
@@ -0,0 +1,97 @@
+ '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);
+ }
+/**
+ * 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');
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/cache/file.php b/cake/libs/cache/file.php
new file mode 100755
index 00000000..abbd5a0e
--- /dev/null
+++ b/cake/libs/cache/file.php
@@ -0,0 +1,269 @@
+ 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();
+/**
+ * Set to true if FileEngine::init(); and FileEngine::__active(); do not fail.
+ *
+ * @var boolean
+ * @access private
+ */
+ var $__active = false;
+/**
+ * True unless FileEngine::__active(); fails
+ *
+ * @var boolean
+ * @access private
+ */
+ 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)) {
+ if (!class_exists('File')) {
+ require LIBS . 'file.php';
+ }
+ $this->__File =& new File($this->settings['path'] . DS . 'cake');
+ }
+
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->settings['isWindows'] = true;
+ }
+
+ $this->settings['path'] = $this->__File->Folder->cd($this->settings['path']);
+ if (empty($this->settings['path'])) {
+ return false;
+ }
+ 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'];
+ }
+ while (($entry = $dir->read()) !== false) {
+ 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->__active && $this->__init && !is_writable($this->settings['path'])) {
+ $this->__init = false;
+ trigger_error(sprintf(__('%s is not writable', true), $this->settings['path']), E_USER_WARNING);
+ } else {
+ $this->__active = true;
+ }
+ return true;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/cache/memcache.php b/cake/libs/cache/memcache.php
new file mode 100755
index 00000000..2d70a6e8
--- /dev/null
+++ b/cake/libs/cache/memcache.php
@@ -0,0 +1,158 @@
+ 127.0.0.1
+ * 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
+ ), $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) {
+ $parts = explode(':', $server);
+ $host = $parts[0];
+ $port = 11211;
+ if (isset($parts[1])) {
+ $port = $parts[1];
+ }
+ if ($this->__Memcache->addServer($host, $port)) {
+ $return = true;
+ }
+ }
+ return $return;
+ }
+ return true;
+ }
+/**
+ * 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;
+ $this->__Memcache->set($key.'_expires', $expires, $this->settings['compress'], $expires);
+ return $this->__Memcache->set($key, $value, $this->settings['compress'], $expires);
+ }
+/**
+ * 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($this->__Memcache->get($key.'_expires'));
+ if ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime) {
+ return false;
+ }
+ return $this->__Memcache->get($key);
+ }
+/**
+ * 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/cake/libs/cache/xcache.php b/cake/libs/cache/xcache.php
new file mode 100755
index 00000000..1ed9b0b5
--- /dev/null
+++ b/cake/libs/cache/xcache.php
@@ -0,0 +1,154 @@
+ '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;
+ }
+/**
+ * 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;
+ }
+ }
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/cake_log.php b/cake/libs/cake_log.php
new file mode 100755
index 00000000..da779b2a
--- /dev/null
+++ b/cake/libs/cake_log.php
@@ -0,0 +1,101 @@
+ '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];
+ }
+
+ if ($type == 'error' || $type == 'warning') {
+ $filename = LOGS . 'error.log';
+ } elseif (in_array($type, $levels)) {
+ $filename = LOGS . 'debug.log';
+ } else {
+ $filename = LOGS . $type . '.log';
+ }
+ $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $msg . "\n";
+ $log = new File($filename, true);
+ if ($log->writable()) {
+ return $log->append($output);
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/class_registry.php b/cake/libs/class_registry.php
new file mode 100755
index 00000000..5ff5ec32
--- /dev/null
+++ b/cake/libs/class_registry.php
@@ -0,0 +1,353 @@
+ 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'TypeOfClass');```
+ *
+ * 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' => 'TypeOfClass'),
+ * array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'TypeOfClass'),
+ * array('class' => 'ClassName', 'alias' => 'AliasNameStoredInTheRegistry', 'type' => 'TypeOfClass')
+ * );
+ * }}}
+ * @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 TypeOfClass
+ * @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)) {
+ $plugin = $pluginPath = null;
+ $settings = array_merge($defaults, $settings);
+ $class = $settings['class'];
+
+ if (strpos($class, '.') !== false) {
+ list($plugin, $class) = explode('.', $class);
+ $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
+ * @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();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/configure.php b/cake/libs/configure.php
new file mode 100755
index 00000000..30aeaf56
--- /dev/null
+++ b/cake/libs/configure.php
@@ -0,0 +1,1194 @@
+ value array of objects' types.
+ *
+ * @var array
+ * @access private
+ */
+ var $__objects = array();
+/**
+ * Returns a singleton instance of the Configure class.
+ *
+ * @return Configure instance
+ * @access public
+ */
+ function &getInstance($boot = true) {
+ static $instance = array();
+ if (!$instance) {
+ $instance[0] =& new Configure();
+ $instance[0]->__loadBootstrap($boot);
+ }
+ return $instance[0];
+ }
+/**
+ * Returns an index of objects of the given type, with the physical path to each object.
+ *
+ * @param string $type Type of object, i.e. 'model', 'controller', 'helper', or 'plugin'
+ * @param mixed $path Optional
+ * @return Configure instance
+ * @access public
+ */
+ function listObjects($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 =& Configure::getInstance();
+
+ if (empty($_this->__objects) && $cache === true) {
+ $_this->__objects = Cache::read('object_map', '_cake_core_');
+ }
+
+ if (empty($_this->__objects) || !isset($_this->__objects[$type]) || $cache !== true) {
+ $types = array(
+ 'model' => array('suffix' => '.php', 'base' => 'AppModel', 'core' => false),
+ 'behavior' => array('suffix' => '.php', 'base' => 'ModelBehavior'),
+ 'controller' => array('suffix' => '_controller.php', 'base' => 'AppController'),
+ 'component' => array('suffix' => '.php', 'base' => null),
+ 'view' => array('suffix' => '.php', 'base' => null),
+ 'helper' => array('suffix' => '.php', 'base' => 'AppHelper'),
+ 'plugin' => array('suffix' => '', 'base' => null),
+ 'vendor' => array('suffix' => '', 'base' => null),
+ 'class' => array('suffix' => '.php', 'base' => null),
+ 'file' => array('suffix' => '.php', 'base' => null)
+ );
+
+ if (!isset($types[$type])) {
+ return false;
+ }
+ $objects = array();
+
+ if (empty($path)) {
+ $path = $_this->{$type . 'Paths'};
+ if (isset($types[$type]['core']) && $types[$type]['core'] === false) {
+ array_pop($path);
+ }
+ }
+ $items = array();
+
+ foreach ((array)$path as $dir) {
+ if ($type === 'file' || $type === 'class' || strpos($dir, $type) !== false) {
+ $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 && !empty($objects)) {
+ $_this->__objects[$name] = $objects;
+ $_this->__cache = true;
+ } else {
+ return $objects;
+ }
+ }
+ return $_this->__objects[$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
+ */
+ 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;
+ }
+/**
+ * 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/412/write
+ * @param array $config Name of var to write
+ * @param mixed $value Value to set for var
+ * @return void
+ * @access public
+ */
+ function write($config, $value = null) {
+ $_this =& Configure::getInstance();
+
+ if (!is_array($config)) {
+ $config = array($config => $value);
+ }
+
+ foreach ($config as $names => $value) {
+ $name = $_this->__configVarNames($names);
+
+ switch (count($name)) {
+ case 3:
+ $_this->{$name[0]}[$name[1]][$name[2]] = $value;
+ break;
+ case 2:
+ $_this->{$name[0]}[$name[1]] = $value;
+ break;
+ case 1:
+ $_this->{$name[0]} = $value;
+ break;
+ }
+ }
+
+ if (isset($config['debug'])) {
+ if ($_this->debug) {
+ error_reporting(E_ALL & ~E_DEPRECATED);
+
+ if (function_exists('ini_set')) {
+ ini_set('display_errors', 1);
+ }
+
+ if (!class_exists('Debugger')) {
+ require LIBS . 'debugger.php';
+ }
+ if (!class_exists('CakeLog')) {
+ require LIBS . 'cake_log.php';
+ }
+ Configure::write('log', LOG_NOTICE);
+ } else {
+ error_reporting(0);
+ Configure::write('log', LOG_NOTICE);
+ }
+ }
+ }
+/**
+ * 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/413/read
+ * @param string $var Variable to obtain
+ * @return string value of Configure::$var
+ * @access public
+ */
+ function read($var = 'debug') {
+ $_this =& Configure::getInstance();
+
+ if ($var === 'debug') {
+ if (!isset($_this->debug)) {
+ if (defined('DEBUG')) {
+ $_this->debug = DEBUG;
+ } else {
+ $_this->debug = 0;
+ }
+ }
+ return $_this->debug;
+ }
+ $name = $_this->__configVarNames($var);
+
+ switch (count($name)) {
+ case 3:
+ if (isset($_this->{$name[0]}[$name[1]][$name[2]])) {
+ return $_this->{$name[0]}[$name[1]][$name[2]];
+ }
+ break;
+ case 2:
+ if (isset($_this->{$name[0]}[$name[1]])) {
+ return $_this->{$name[0]}[$name[1]];
+ }
+ break;
+ case 1:
+ if (isset($_this->{$name[0]})) {
+ return $_this->{$name[0]};
+ }
+ 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/414/delete
+ * @param string $var the var to be deleted
+ * @return void
+ * @access public
+ */
+ function delete($var = null) {
+ $_this =& Configure::getInstance();
+ $name = $_this->__configVarNames($var);
+
+ if (count($name) > 1) {
+ unset($_this->{$name[0]}[$name[1]]);
+ } else {
+ unset($_this->{$name[0]});
+ }
+ }
+/**
+ * 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.
+ *
+ * Usage Configure::load('configure_file');
+ *
+ * @link http://book.cakephp.org/view/415/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 = false;
+
+ if (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 (Configure::corePaths('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)) {
+ $error = __("Configure::load() - no variable \$config found in %s.php", true);
+ trigger_error(sprintf($error, $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/416/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']";
+
+ if (is_array($value)) {
+ $content .= " = array(";
+
+ foreach ($value as $key1 => $value2) {
+ $value2 = addslashes($value2);
+ $content .= "'$key1' => '$value2', ";
+ }
+ $content .= ");\n";
+ } else {
+ $value = addslashes($value);
+ $content .= " = '$value';\n";
+ }
+ }
+ if (is_null($type)) {
+ $write = false;
+ }
+ Configure::__writeConfig($content, $name, $write);
+ }
+/**
+ * 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 corePaths($type = null) {
+ $paths = Cache::read('core_paths', '_cake_core_');
+ if (!$paths) {
+ $paths = array();
+ $openBasedir = ini_get('open_basedir');
+ if ($openBasedir) {
+ $all = explode(PATH_SEPARATOR, $openBasedir);
+ $all = array_flip(array_flip((array_merge(array(CAKE_CORE_INCLUDE_PATH), $all))));
+ } else {
+ $all = explode(PATH_SEPARATOR, ini_get('include_path'));
+ $all = array_flip(array_flip((array_merge(array(CAKE_CORE_INCLUDE_PATH), $all))));
+ }
+ foreach ($all as $path) {
+ if ($path !== DS) {
+ $path = rtrim($path, DS);
+ }
+ if (empty($path) || $path === '.') {
+ continue;
+ }
+ $cake = $path . DS . 'cake' . DS;
+ $libs = $cake . 'libs' . DS;
+ if (is_dir($libs)) {
+ $paths['libs'][] = $libs;
+ $paths['model'][] = $libs . 'model' . DS;
+ $paths['behavior'][] = $libs . 'model' . DS . 'behaviors' . DS;
+ $paths['controller'][] = $libs . 'controller' . DS;
+ $paths['component'][] = $libs . 'controller' . DS . 'components' . DS;
+ $paths['view'][] = $libs . 'view' . DS;
+ $paths['helper'][] = $libs . 'view' . DS . 'helpers' . DS;
+ $paths['cake'][] = $cake;
+ $paths['vendor'][] = $path . DS . 'vendors' . DS;
+ $paths['shell'][] = $cake . 'console' . DS . 'libs' . DS;
+ break;
+ }
+ }
+ Cache::write('core_paths', array_filter($paths), '_cake_core_');
+ }
+ if ($type && isset($paths[$type])) {
+ return $paths[$type];
+ }
+ return $paths;
+ }
+/**
+ * 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);
+ }
+ }
+ }
+/**
+ * Checks $name for dot notation to create dynamic Configure::$var as an array when needed.
+ *
+ * @param mixed $name Name to split
+ * @return array Name separated in items through dot notation
+ * @access private
+ */
+ function __configVarNames($name) {
+ if (is_string($name)) {
+ if (strpos($name, ".")) {
+ return explode(".", $name);
+ }
+ return array($name);
+ }
+ return $name;
+ }
+/**
+ * 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
+ * @return void
+ * @access public
+ */
+ function buildPaths($paths) {
+ $_this =& Configure::getInstance();
+ $core = $_this->corePaths();
+ $basePaths = array(
+ 'model' => array(MODELS),
+ 'behavior' => array(BEHAVIORS),
+ 'controller' => array(CONTROLLERS),
+ 'component' => array(COMPONENTS),
+ 'view' => array(VIEWS),
+ 'helper' => array(HELPERS),
+ 'plugin' => array(APP . 'plugins' . DS),
+ 'vendor' => array(APP . 'vendors' . DS, VENDORS),
+ 'locale' => array(APP . 'locale' . DS),
+ 'shell' => array(),
+ 'datasource' => array(MODELS . 'datasources')
+ );
+
+ foreach ($basePaths as $type => $default) {
+ $pathsVar = $type . 'Paths';
+ $merge = array();
+
+ if (isset($core[$type])) {
+ $merge = $core[$type];
+ }
+ if ($type === 'model' || $type === 'controller' || $type === 'helper') {
+ $merge = array_merge(array(APP), $merge);
+ }
+
+ if (!is_array($default)) {
+ $default = array($default);
+ }
+ $_this->{$pathsVar} = $default;
+
+ if (isset($paths[$pathsVar]) && !empty($paths[$pathsVar])) {
+ $path = array_flip(array_flip((array_merge(
+ $_this->{$pathsVar}, (array)$paths[$pathsVar], $merge
+ ))));
+ $_this->{$pathsVar} = array_values($path);
+ } else {
+ $path = array_flip(array_flip((array_merge($_this->{$pathsVar}, $merge))));
+ $_this->{$pathsVar} = array_values($path);
+ }
+ }
+ }
+/**
+ * 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) {
+ $modelPaths = $behaviorPaths = $controllerPaths = $componentPaths = $viewPaths = $helperPaths = $pluginPaths = $vendorPaths = $localePaths = $shellPaths = null;
+
+ if ($boot) {
+ Configure::write('App', array('base' => false, 'baseUrl' => false, 'dir' => APP_DIR, 'webroot' => WEBROOT_DIR));
+
+ 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 (!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);
+ }
+
+ 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', 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($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($cache['settings'], array(
+ 'prefix' => $prefix . 'cake_model_', 'path' => $path . DS . 'models' . DS,
+ 'serialize' => true, 'duration' => $duration
+ )));
+ }
+ Cache::config('default');
+ }
+ Configure::buildPaths(compact(
+ 'modelPaths', 'viewPaths', 'controllerPaths', 'helperPaths', 'componentPaths',
+ 'behaviorPaths', 'pluginPaths', 'vendorPaths', 'localePaths', 'shellPaths'
+ ));
+ }
+ }
+/**
+ * Caches the object map when the instance of the Configure class is destroyed
+ *
+ * @access public
+ */
+ function __destruct() {
+ if ($this->__cache) {
+ Cache::write('object_map', array_filter($this->__objects), '_cake_core_');
+ }
+ }
+}
+/**
+ * Class and file loader.
+ *
+ * @link http://book.cakephp.org/view/499/The-App-Class
+ * @since CakePHP(tm) v 1.2.0.6001
+ * @package cake
+ * @subpackage cake.cake.libs
+ */
+class App extends Object {
+/**
+ * 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;
+/**
+ * Determines if $__maps and $__paths cache should be written.
+ *
+ * @var boolean
+ * @access private
+ */
+ var $__cache = 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();
+/**
+ * Finds classes based on $name or specific file(s) to search.
+ *
+ * @link http://book.cakephp.org/view/529/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)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if ($name != null && strpos($name, '.') !== false) {
+ list($plugin, $name) = explode('.', $name);
+ }
+ $_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']);
+
+ if ($_this->return) {
+ $value = include $load;
+ return $value;
+ }
+ return true;
+ } else {
+ $_this->__remove($name . $ext['class'], $type, $plugin);
+ $_this->__cache = 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'];
+ }
+ $plugin = Inflector::camelize($plugin);
+ }
+ }
+
+ if (strtolower($type) !== 'vendor' && empty($search) && $_this->__load($file)) {
+ $directory = false;
+ } else {
+ $file = $find;
+ $directory = $_this->__find($find, true);
+ }
+
+ if ($directory !== null) {
+ $_this->__cache = true;
+ $_this->__map($directory . $file, $name . $ext['class'], $type, $plugin);
+ $_this->__overload($type, $name . $ext['class']);
+
+ if ($_this->return) {
+ $value = include $directory . $file;
+ return $value;
+ }
+ 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 = 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) {
+ 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) {
+ $path = rtrim($path, DS);
+
+ if ($path === rtrim(APP, DS)) {
+ $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, false, 'dir');
+ $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 if object is from a plugin, the name of the plugin
+ * @access private
+ */
+ function __map($file, $name, $type, $plugin) {
+ if ($plugin) {
+ $plugin = Inflector::camelize($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 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) {
+ $plugin = Inflector::camelize($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) {
+ if (($type === 'Model' || $type === 'Helper') && strtolower($name) != 'schema') {
+ 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 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 null;
+ }
+
+ if ($plugin) {
+ $plugin = Inflector::underscore($plugin);
+ $name = Inflector::camelize($plugin);
+ }
+ $path = null;
+ $load = strtolower($type);
+
+ switch ($load) {
+ case 'model':
+ if (!class_exists('Model')) {
+ App::import('Core', 'Model', false, Configure::corePaths('model'));
+ }
+ if (!class_exists('AppModel')) {
+ App::import($type, 'AppModel', false, Configure::read('modelPaths'));
+ }
+ if ($plugin) {
+ if (!class_exists($name . 'AppModel')) {
+ App::import($type, $plugin . '.' . $name . 'AppModel', false, array(), $plugin . DS . $plugin . '_app_model.php');
+ }
+ $path = $plugin . DS . 'models' . DS;
+ }
+ return array('class' => null, 'suffix' => null, 'path' => $path);
+ break;
+ case 'behavior':
+ if ($plugin) {
+ $path = $plugin . DS . 'models' . DS . 'behaviors' . DS;
+ }
+ return array('class' => $type, 'suffix' => null, 'path' => $path);
+ break;
+ case 'controller':
+ App::import($type, 'AppController', false);
+ if ($plugin) {
+ App::import($type, $plugin . '.' . $name . 'AppController', false, array(), $plugin . DS . $plugin . '_app_controller.php');
+ $path = $plugin . DS . 'controllers' . DS;
+ }
+ return array('class' => $type, 'suffix' => $type, 'path' => $path);
+ break;
+ case 'component':
+ if ($plugin) {
+ $path = $plugin . DS . 'controllers' . DS . 'components' . DS;
+ }
+ return array('class' => $type, 'suffix' => null, 'path' => $path);
+ break;
+ case 'view':
+ if ($plugin) {
+ $path = $plugin . 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 = $plugin . DS . 'views' . DS . 'helpers' . DS;
+ }
+ return array('class' => $type, 'suffix' => null, 'path' => $path);
+ break;
+ case 'vendor':
+ if ($plugin) {
+ $path = $plugin . 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);
+
+ if ($type === 'core') {
+ $path = Configure::corePaths();
+ $paths = array();
+
+ foreach ($path as $key => $value) {
+ $count = count($key);
+ for ($i = 0; $i < $count; $i++) {
+ $paths[] = $path[$key][$i];
+ }
+ }
+ return $paths;
+ }
+
+ if ($paths = Configure::read($type . 'Paths')) {
+ return $paths;
+ }
+
+ switch ($type) {
+ case 'plugin':
+ return array(APP . 'plugins' . DS);
+ case 'vendor':
+ return array(APP . 'vendors' . DS, VENDORS, APP . 'plugins' . DS);
+ case 'controller':
+ return array(APP . 'controllers' . DS, APP);
+ case 'model':
+ return array(APP . 'models' . DS, APP);
+ case 'view':
+ return array(APP . 'views' . DS);
+ }
+ }
+/**
+ * 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 name of plugin
+ * @return void
+ * @access private
+ */
+ function __remove($name, $type, $plugin) {
+ if ($plugin) {
+ $plugin = Inflector::camelize($plugin);
+ unset($this->__map['Plugin'][$plugin][$type][$name]);
+ } else {
+ unset($this->__map[$type][$name]);
+ }
+ }
+/**
+ * Object destructor.
+ *
+ * Writes cache file if changes have been made to the $__map or $__paths
+ *
+ * @return void
+ * @access private
+ */
+ function __destruct() {
+ if ($this->__cache) {
+ $core = Configure::corePaths('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_');
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/.DS_Store b/cake/libs/controller/.DS_Store
new file mode 100755
index 00000000..d1c4d2a8
Binary files /dev/null and b/cake/libs/controller/.DS_Store differ
diff --git a/cake/libs/controller/app_controller.php b/cake/libs/controller/app_controller.php
new file mode 100755
index 00000000..e791fa9f
--- /dev/null
+++ b/cake/libs/controller/app_controller.php
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/cake/libs/controller/component.php b/cake/libs/controller/component.php
new file mode 100755
index 00000000..c40571cd
--- /dev/null
+++ b/cake/libs/controller/component.php
@@ -0,0 +1,255 @@
+ 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/65/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/65/MVC-Class-Access-Within-Components
+ */
+ function startup(&$controller) {
+ foreach ($this->_primary as $name) {
+ $component =& $this->_loaded[$name];
+ if ($component->enabled === true && method_exists($component, 'startup')) {
+ $component->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
+ */
+ function beforeRender(&$controller) {
+ foreach ($this->_primary as $name) {
+ $component =& $this->_loaded[$name];
+ if ($component->enabled === true && method_exists($component,'beforeRender')) {
+ $component->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
+ */
+ function shutdown(&$controller) {
+ foreach ($this->_primary as $name) {
+ $component =& $this->_loaded[$name];
+ if (method_exists($component,'shutdown') && $component->enabled === true) {
+ $component->shutdown($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);
+ if ($parent == null) {
+ $normal = Set::merge(array('Session' => null), $normal);
+ }
+ foreach ((array)$normal as $component => $config) {
+ $plugin = null;
+
+ if (isset($this->__controllerVars['plugin'])) {
+ $plugin = $this->__controllerVars['plugin'] . '.';
+ }
+
+ if (strpos($component, '.') !== false) {
+ list($plugin, $component) = explode('.', $component);
+ $plugin = $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 {
+ $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);
+ }
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/components/acl.php b/cake/libs/controller/components/acl.php
new file mode 100755
index 00000000..d68c61f0
--- /dev/null
+++ b/cake/libs/controller/components/acl.php
@@ -0,0 +1,588 @@
+_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.
+ *
+ * @param string $aro ARO
+ * @param string $aco ACO
+ * @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.
+ *
+ * @param string $aro ARO
+ * @param string $aco ACO
+ * @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.
+ *
+ * @param string $aro ARO
+ * @param string $aco ACO
+ * @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.
+ *
+ * @param string $aro ARO
+ * @param string $aco ACO
+ * @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.
+ *
+ * @param string $aro ARO
+ * @param string $aco ACO
+ * @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.
+ *
+ * @param string $aro ARO
+ * @param string $aco ACO
+ * @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
+ * @param string $aco ACO
+ * @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) {
+ }
+}
+/**
+ * In this file you can extend the AclBase.
+ *
+ * @package cake
+ * @subpackage cake.cake.libs.model
+ */
+class DbAcl extends AclBase {
+/**
+ * Constructor
+ *
+ */
+ function __construct() {
+ parent::__construct();
+ if (!class_exists('AclNode')) {
+ uses('model' . DS . 'db_acl');
+ }
+ $this->Aro =& ClassRegistry::init(array('class' => 'Aro', 'alias' => 'Aro'));
+ $this->Aco =& ClassRegistry::init(array('class' => 'Aco', 'alias' => 'Aco'));
+ }
+/**
+ * Enter description here...
+ *
+ * @param object $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
+ * @param string $aco ACO
+ * @param string $action Action (defaults to *)
+ * @return boolean Success (true if ARO has access to action in ACO, false otherwise)
+ * @access public
+ */
+ 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: " . 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: " . 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
+ * @param string $aco ACO
+ * @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
+ */
+ 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 && count($perms['link']) > 0) {
+ $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
+ * @param string $aco ACO
+ * @param string $actions Action (defaults to *)
+ * @return boolean Success
+ * @access public
+ */
+ 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
+ * @param string $aco ACO
+ * @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
+ * @param string $aco ACO
+ * @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
+ * @param string $aco ACO
+ * @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
+ * @param string $aco ACO
+ * @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;
+ }
+}
+/**
+ * In this file you can extend the AclBase.
+ *
+ * @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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/components/auth.php b/cake/libs/controller/components/auth.php
new file mode 100755
index 00000000..1aa37e52
--- /dev/null
+++ b/cake/libs/controller/components/auth.php
@@ -0,0 +1,881 @@
+ '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
+ */
+ 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
+ */
+ var $ajaxLogin = null;
+/**
+ * The name of the model that represents users which will be authenticated. Defaults to 'User'.
+ *
+ * @var string
+ * @access public
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ var $actionPath = null;
+/**
+ * A URL (defined as a string or array) to the controller action that handles
+ * logins.
+ *
+ * @var mixed
+ * @access public
+ */
+ 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
+ */
+ var $loginRedirect = null;
+/**
+ * The 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()
+ */
+ 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
+ */
+ 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
+ */
+ var $authError = null;
+/**
+ * Determines whether AuthComponent will automatically redirect and exit if login is successful.
+ *
+ * @var boolean
+ * @access public
+ */
+ var $autoRedirect = true;
+/**
+ * Controller actions for which user validation is not required.
+ *
+ * @var array
+ * @access public
+ * @see AuthComponent::allow()
+ */
+ 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) {
+ $this->params = $controller->params;
+ $crud = array('create', 'read', 'update', 'delete');
+ $this->actionMap = array_merge($this->actionMap, array_combine($crud, $crud));
+ $this->_methods = $controller->methods;
+
+ $admin = Configure::read('Routing.admin');
+ if (!empty($admin)) {
+ $this->actionMap = array_merge($this->actionMap, array(
+ $admin . '_index' => 'read',
+ $admin . '_add' => 'create',
+ $admin . '_edit' => 'update',
+ $admin . '_view' => 'read',
+ $admin . '_remove' => 'delete',
+ $admin . '_create' => 'create',
+ $admin . '_read' => 'read',
+ $admin . '_update' => 'update',
+ $admin . '_delete' => 'delete'
+ ));
+ }
+ if (Configure::read() > 0) {
+ App::import('Debugger');
+ Debugger::checkSessionKey();
+ }
+ }
+/**
+ * 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) {
+ $methods = array_flip($controller->methods);
+ $action = strtolower($controller->params['action']);
+ $allowedActions = array_map('strtolower', $this->allowedActions);
+
+ $isErrorOrTests = (
+ strtolower($controller->name) == 'cakeerror' ||
+ (strtolower($controller->name) == 'tests' && Configure::read() > 0)
+ );
+ if ($isErrorOrTests) {
+ return true;
+ }
+
+ $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);
+
+ $isAllowed = (
+ $this->allowedActions == array('*') ||
+ in_array($action, $allowedActions)
+ );
+
+ if ($loginAction != $url && $isAllowed) {
+ return true;
+ }
+
+ if ($loginAction == $url) {
+ if (empty($controller->data) || !isset($controller->data[$this->userModel])) {
+ if (!$this->Session->check('Auth.redirect') && env('HTTP_REFERER')) {
+ $this->Session->write('Auth.redirect', $controller->referer(null, true));
+ }
+ return false;
+ }
+
+ $isValid = !empty($controller->data[$this->userModel][$this->fields['username']]) &&
+ !empty($controller->data[$this->userModel][$this->fields['password']]);
+
+ if ($isValid) {
+ $username = $controller->data[$this->userModel][$this->fields['username']];
+ $password = $controller->data[$this->userModel][$this->fields['password']];
+
+ $data = array(
+ $this->userModel . '.' . $this->fields['username'] => $username,
+ $this->userModel . '.' . $this->fields['password'] => $password
+ );
+
+ if ($this->login($data)) {
+ if ($this->autoRedirect) {
+ $controller->redirect($this->redirect(), null, true);
+ }
+ return true;
+ }
+ }
+
+ $this->Session->setFlash($this->loginError, 'default', array(), 'auth');
+ $controller->data[$this->userModel][$this->fields['password']] = null;
+ return false;
+ } else {
+ if (!$this->user()) {
+ if (!$this->RequestHandler->isAjax()) {
+ $this->Session->setFlash($this->authError, 'default', 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 {
+ $err = 'Could not find AclComponent. Please include Acl in ';
+ $err .= 'Controller::$components.';
+ trigger_error(__($err, 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)) {
+ return true;
+ }
+
+ $this->Session->setFlash($this->authError, 'default', 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;
+ }
+ $defaults = array(
+ 'loginAction' => Router::normalize(array(
+ 'controller'=> Inflector::underscore(Inflector::pluralize($this->userModel)),
+ 'action' => 'login'
+ )),
+ 'sessionKey' => 'Auth.' . $this->userModel,
+ 'logoutRedirect' => $this->loginAction,
+ 'loginError' => __('Login failed. Invalid username or password.', true),
+ 'authError' => __('You are not authorised 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':
+ $this->mapActions();
+ if (!isset($this->actionMap[$this->params['action']])) {
+ $err = 'Auth::startup() - Attempted access of un-mapped action "%1$s" in';
+ $err .= ' controller "%2$s"';
+ trigger_error(
+ sprintf(__($err, 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':
+ $this->mapActions();
+ $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 string $action Controller action name
+ * @param string $action Controller action name
+ * @param string ... etc.
+ * @return void
+ * @access public
+ */
+ 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, $args);
+ }
+ }
+/**
+ * Removes items from the list of allowed actions.
+ *
+ * @param string $action Controller action name
+ * @param string $action Controller action name
+ * @param string ... etc.
+ * @return void
+ * @see AuthComponent::allow()
+ * @access public
+ */
+ function deny() {
+ $args = func_get_args();
+ foreach ($args as $arg) {
+ $i = array_search($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
+ */
+ 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
+ */
+ 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
+ */
+ function logout() {
+ $this->__setDefaults();
+ $this->Session->del($this->sessionKey);
+ $this->Session->del('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
+ */
+ function user($key = null) {
+ $this->__setDefaults();
+ if (!$this->Session->check($this->sessionKey)) {
+ return null;
+ }
+
+ if ($key == null) {
+ return array($this->userModel => $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
+ */
+ function action($action = ':controller/:action') {
+ return str_replace(
+ array(':controller', ':action'),
+ array(Inflector::camelize($this->params['controller']), $this->params['action']),
+ $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 = null;
+ } elseif (is_array($conditions)) {
+ $conditions = array_merge((array)$this->userScope, $conditions);
+ } else {
+ $conditions = $this->userScope;
+ }
+ 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[$this->userModel];
+ } elseif (is_array($user) && isset($user[$this->userModel])) {
+ $user = $user[$this->userModel];
+ }
+
+ if (is_array($user) && (isset($user[$this->fields['username']]) || isset($user[$this->userModel . '.' . $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(
+ $this->userModel.'.'.$this->fields['username'] => $user[$this->fields['username']],
+ $this->userModel.'.'.$this->fields['password'] => $user[$this->fields['password']]
+ );
+ } elseif (isset($user[$this->userModel . '.' . $this->fields['username']]) && !empty($user[$this->userModel . '.' . $this->fields['username']])) {
+ if (trim($user[$this->userModel . '.' . $this->fields['username']]) == '=' || trim($user[$this->userModel . '.' . $this->fields['password']]) == '=') {
+ return false;
+ }
+ $find = array(
+ $this->userModel.'.'.$this->fields['username'] => $user[$this->userModel . '.' . $this->fields['username']],
+ $this->userModel.'.'.$this->fields['password'] => $user[$this->userModel . '.' . $this->fields['password']]
+ );
+ } else {
+ return false;
+ }
+ $model =& $this->getModel();
+ $data = $model->find(array_merge($find, $conditions), null, null, 0);
+ if (empty($data) || empty($data[$this->userModel])) {
+ return null;
+ }
+ } elseif (!empty($user) && is_string($user)) {
+ $model =& $this->getModel();
+ $data = $model->find(array_merge(array($model->escapeField() => $user), $conditions));
+
+ if (empty($data) || empty($data[$this->userModel])) {
+ return null;
+ }
+ }
+
+ if (!empty($data)) {
+ if (!empty($data[$this->userModel][$this->fields['password']])) {
+ unset($data[$this->userModel][$this->fields['password']]);
+ }
+ return $data[$this->userModel];
+ }
+ 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
+ */
+ function hashPasswords($data) {
+ if (is_object($this->authenticate) && method_exists($this->authenticate, 'hashPasswords')) {
+ return $this->authenticate->hashPasswords($data);
+ }
+
+ if (is_array($data) && isset($data[$this->userModel])) {
+ if (isset($data[$this->userModel][$this->fields['username']]) && isset($data[$this->userModel][$this->fields['password']])) {
+ $data[$this->userModel][$this->fields['password']] = $this->password($data[$this->userModel][$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
+ */
+ 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->del('Auth.redirect');
+ }
+ }
+}
+?>
diff --git a/cake/libs/controller/components/cookie.php b/cake/libs/controller/components/cookie.php
new file mode 100755
index 00000000..56713f5c
--- /dev/null
+++ b/cake/libs/controller/components/cookie.php
@@ -0,0 +1,485 @@
+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);
+ }
+/**
+ * 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.], reguired 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) && $value !== null) {
+ $name = $this->__cookieVarNames($key);
+
+ if (count($name) > 1) {
+ $this->__values[$name[0]][$name[1]] = $value;
+ $this->__write("[" . $name[0] . "][" . $name[1] . "]", $value);
+ } else {
+ $this->__values[$name[0]] = $value;
+ $this->__write("[" . $name[0] . "]", $value);
+ }
+ } else {
+ foreach ($key as $names => $value) {
+ $name = $this->__cookieVarNames($names);
+
+ if (count($name) > 1) {
+ $this->__values[$name[0]][$name[1]] = $value;
+ $this->__write("[" . $name[0] . "][" . $name[1] . "]", $value);
+ } else {
+ $this->__values[$name[0]] = $value;
+ $this->__write("[" . $name[0] . "]", $value);
+ }
+ }
+ }
+ $this->__encrypted = true;
+ }
+/**
+ * Read the value of the $_COOKIE[$key];
+ *
+ * Optional [Name.], reguired 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;
+ }
+ $name = $this->__cookieVarNames($key);
+
+ if (count($name) > 1) {
+ if (isset($this->__values[$name[0]])) {
+ if (isset($this->__values[$name[0]][$name[1]])) {
+ return $this->__values[$name[0]][$name[1]];
+ }
+ }
+ return null;
+ } else {
+ if (isset($this->__values[$name[0]])) {
+ $value = $this->__values[$name[0]];
+ return $value;
+ }
+ return null;
+ }
+ }
+/**
+ * Delete a cookie value
+ *
+ * Optional [Name.], reguired 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 del($key) {
+ if (empty($this->__values)) {
+ $this->read();
+ }
+ $name = $this->__cookieVarNames($key);
+ if (count($name) > 1) {
+ if (isset($this->__values[$name[0]])) {
+ $this->__delete("[" . $name[0] . "][" . $name[1] . "]");
+ unset($this->__values[$name[0]][$name[1]]);
+ }
+ } else {
+ if (isset($this->__values[$name[0]])) {
+ if (is_array($this->__values[$name[0]])) {
+ foreach ($this->__values[$name[0]] as $key => $value) {
+ $this->__delete("[" . $name[0] . "][" . $key . "]");
+ }
+ }
+ $this->__delete("[" . $name[0] . "]");
+ unset($this->__values[$name[0]]);
+ }
+ }
+ }
+/**
+ * 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 (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 ($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);
+ }
+
+/**
+ * Creates an array from the $name parameter which allows the dot notation
+ * similar to one used by Session and Configure classes
+ *
+ * @param string $name Name with or without dot notation
+ * @return array Extracted names
+ * @access private
+ */
+ function __cookieVarNames($name) {
+ if (is_string($name)) {
+ if (strpos($name, ".")) {
+ $name = explode(".", $name);
+ } else {
+ $name = array($name);
+ }
+ }
+ return $name;
+ }
+/**
+ * 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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/components/email.php b/cake/libs/controller/components/email.php
new file mode 100755
index 00000000..b2e2ea42
--- /dev/null
+++ b/cake/libs/controller/components/email.php
@@ -0,0 +1,797 @@
+ 25, 'host' => 'localhost', 'timeout' => 30
+ );
+/**
+ * Placeholder for any errors that might happen with the
+ * smtp mail methods
+ *
+ * @var string
+ * @access public
+ */
+ var $smtpError = null;
+/**
+ * If set to true, the mail method will be auto-set to 'debug'
+ *
+ * @var string
+ * @access protected
+ */
+ var $_debug = false;
+/**
+ * Temporary store of message header lines
+ *
+ * @var array
+ * @access private
+ */
+ var $__header = array();
+/**
+ * If set, boundary to use for multipart mime messages
+ *
+ * @var string
+ * @access private
+ */
+ var $__boundary = null;
+/**
+ * Temporary store of message lines
+ *
+ * @var array
+ * @access private
+ */
+ var $__message = array();
+/**
+ * Variable that holds SMTP connection
+ *
+ * @var resource
+ * @access private
+ */
+ var $__smtpConnection = null;
+/**
+ * Initialize component
+ *
+ * @param object $controller Instantiating controller
+ * @access public
+ */
+ function initialize(&$controller, $settings = array()) {
+ $this->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
+ * @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";
+ }
+
+ $message = $this->__wrap($content);
+ if ($this->template === null) {
+ $message = $this->__formatMessage($message);
+ } else {
+ $message = $this->__renderTemplate($message);
+ }
+ $message[] = '';
+ $this->__message = $message;
+
+ if (!empty($this->attachments)) {
+ $this->__attachFiles();
+ }
+
+ if (!is_null($this->__boundary)) {
+ $this->__message[] = '';
+ $this->__message[] = '--' . $this->__boundary . '--';
+ $this->__message[] = '';
+ }
+
+ if ($this->_debug) {
+ return $this->__debug();
+ }
+ $__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
+ */
+ function reset() {
+ $this->template = null;
+ $this->to = null;
+ $this->from = null;
+ $this->replyTo = null;
+ $this->return = null;
+ $this->cc = array();
+ $this->bcc = array();
+ $this->subject = null;
+ $this->additionalParams = null;
+ $this->smtpError = null;
+ $this->attachments = array();
+ $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 __renderTemplate($content) {
+ $viewClass = $this->Controller->view;
+
+ if ($viewClass != 'View') {
+ if (strpos($viewClass, '.') !== false) {
+ list($plugin, $viewClass) = explode('.', $viewClass);
+ }
+ $viewClass = $viewClass . 'View';
+ App::import('View', $this->Controller->view);
+ }
+ $View = new $viewClass($this->Controller, false);
+ $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", 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", str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($htmlContent)));
+ $msg = array_merge($msg, $htmlContent);
+ $msg[] = '';
+ $msg[] = '--alt-' . $this->__boundary . '--';
+ $msg[] = '';
+
+ 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", str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content)));
+ $msg = array_merge($msg, $content);
+
+ return $msg;
+ }
+/**
+ * Create unique boundary identifier
+ *
+ * @access private
+ */
+ function __createBoundary() {
+ $this->__boundary = md5(uniqid(time()));
+ }
+/**
+ * Create emails headers including (but not limited to) from email address, reply to,
+ * bcc and cc.
+ *
+ * @access private
+ */
+ function __createHeader() {
+ if ($this->delivery == 'smtp') {
+ $this->__header[] = 'To: ' . $this->__formatAddress($this->to);
+ }
+ $this->__header[] = 'From: ' . $this->__formatAddress($this->from);
+
+ if (!empty($this->replyTo)) {
+ $this->__header[] = 'Reply-To: ' . $this->__formatAddress($this->replyTo);
+ }
+ if (!empty($this->return)) {
+ $this->__header[] = 'Return-Path: ' . $this->__formatAddress($this->return);
+ }
+ if (!empty($this->readReceipt)) {
+ $this->__header[] = 'Disposition-Notification-To: ' . $this->__formatAddress($this->readReceipt);
+ }
+
+ if (!empty($this->cc)) {
+ $this->__header[] = 'cc: ' .implode(', ', array_map(array($this, '__formatAddress'), $this->cc));
+ }
+
+ if (!empty($this->bcc) && $this->delivery != 'smtp') {
+ $this->__header[] = 'Bcc: ' .implode(', ', array_map(array($this, '__formatAddress'), $this->bcc));
+ }
+ if ($this->delivery == 'smtp') {
+ $this->__header[] = 'Subject: ' . $this->__encode($this->subject);
+ }
+ $this->__header[] = 'X-Mailer: ' . $this->xMailer;
+
+ if (!empty($this->headers)) {
+ foreach ($this->headers as $key => $val) {
+ $this->__header[] = 'X-' . $key . ': ' . $val;
+ }
+ }
+
+ if (!empty($this->attachments)) {
+ $this->__createBoundary();
+ $this->__header[] = 'MIME-Version: 1.0';
+ $this->__header[] = 'Content-Type: multipart/mixed; boundary="' . $this->__boundary . '"';
+ $this->__header[] = 'This part of the E-mail should never be seen. If';
+ $this->__header[] = 'you are reading this, consider upgrading your e-mail';
+ $this->__header[] = 'client to a MIME-compatible client.';
+ } elseif ($this->sendAs === 'text') {
+ $this->__header[] = 'Content-Type: text/plain; charset=' . $this->charset;
+ } elseif ($this->sendAs === 'html') {
+ $this->__header[] = 'Content-Type: text/html; charset=' . $this->charset;
+ } elseif ($this->sendAs === 'both') {
+ $this->__header[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->__boundary . '"';
+ }
+
+ $this->__header[] = 'Content-Transfer-Encoding: 7bit';
+ }
+/**
+ * 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 $attachment) {
+ $file = $this->__findFiles($attachment);
+ if (!empty($file)) {
+ $files[] = $file;
+ }
+ }
+
+ foreach ($files as $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($file) . '"';
+ $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
+ * @return array Wrapped message
+ * @access private
+ */
+ function __wrap($message) {
+ $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', E_USER_WARNING);
+ $this->lineLength = $this->_lineLength;
+ }
+
+ foreach ($lines as $line) {
+ if (substr($line, 0, 1) == '.') {
+ $line = '.' . $line;
+ }
+ $formatted = array_merge($formatted, explode("\n", wordwrap($line, $this->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 = '';
+ }
+ return mb_encode_mimeheader($subject, $this->charset, 'B', $nl);
+ }
+/**
+ * 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) {
+ if (strpos($string, '<') !== false) {
+ $value = explode('<', $string);
+ if ($smtp) {
+ $string = '<' . $value[1];
+ } else {
+ $string = $this->__encode($value[0]) . ' <' . $value[1];
+ }
+ }
+ return $this->__strip($string);
+ }
+/**
+ * Remove certain elements (such as bcc:, to:, %0a) from given value
+ *
+ * @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("\n", $this->__header);
+ $message = implode("\n", $this->__message);
+ if (ini_get('safe_mode')) {
+ return @mail($this->to, $this->__encode($this->subject), $message, $header);
+ }
+ return @mail($this->to, $this->__encode($this->subject), $message, $header, $this->additionalParams);
+ }
+/**
+ * Sends out email via SMTP
+ *
+ * @return bool Success
+ * @access private
+ */
+ function __smtp() {
+ App::import('Core', array('Socket'));
+
+ $this->__smtpConnection =& new CakeSocket(array_merge(array('protocol'=>'smtp'), $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)) {
+ $host = $httpHost;
+ } else {
+ $host = 'localhost';
+ }
+
+ if (!$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 (!$this->__smtpSend('RCPT TO: ' . $this->__formatAddress($this->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;
+ }
+/**
+ * Private 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 private
+ */
+ function __smtpSend($data, $checkCode = '250') {
+ if (!is_null($data)) {
+ $this->__smtpConnection->write($data . "\r\n");
+ }
+ if ($checkCode !== false) {
+ $response = $this->__smtpConnection->read();
+
+ if (preg_match('/^(' . $checkCode . ')/', $response, $code)) {
+ return $code[0];
+ }
+ $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 ($this->delivery == 'smtp') {
+ $fm .= sprintf('%s %s%s', 'Host:', $this->smtpOptions['host'], $nl);
+ $fm .= sprintf('%s %s%s', 'Port:', $this->smtpOptions['port'], $nl);
+ $fm .= sprintf('%s %s%s', 'Timeout:', $this->smtpOptions['timeout'], $nl);
+ }
+ $fm .= sprintf('%s %s%s', 'To:', $this->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 .= '';
+
+ $this->Controller->Session->setFlash($fm, 'default', null, 'email');
+ return true;
+ }
+
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/components/request_handler.php b/cake/libs/controller/components/request_handler.php
new file mode 100755
index 00000000..efca6b2c
--- /dev/null
+++ b/cake/libs/controller/components/request_handler.php
@@ -0,0 +1,741 @@
+ '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'
+ );
+/**
+ * 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
+ * @return void
+ * @see Router::parseExtensions()
+ * @access public
+ */
+ function initialize(&$controller) {
+ if (isset($controller->params['url']['ext'])) {
+ $this->ext = $controller->params['url']['ext'];
+ }
+ }
+/**
+ * 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');
+ }
+
+ if ($this->requestedWith('xml')) {
+ if (!class_exists('XmlNode')) {
+ App::import('Core', 'Xml');
+ }
+ $xml = new Xml(trim(file_get_contents('php://input')));
+
+ if (is_object($xml->child('data')) && count($xml->children) == 1) {
+ $controller->data = $xml->child('data');
+ } else {
+ $controller->data = $xml;
+ }
+ }
+ }
+/**
+ * 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
+ * @access public
+ */
+ function beforeRedirect(&$controller, $url) {
+ if (!$this->isAjax()) {
+ return;
+ }
+ foreach ($_POST as $key => $val) {
+ unset($_POST[$key]);
+ }
+ echo $this->requestAction($url, array('return'));
+ $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
+ */
+ function isMobile() {
+ preg_match('/' . REQUEST_MOBILE_UA . '/i', env('HTTP_USER_AGENT'), $match);
+ if (!empty($match) || $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 getReferrer() {
+ 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 .= '/' . $type;
+ } else {
+ $remove = preg_replace("/(?:\/{$this->__renderType})$/", '/' . $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 ($this->__responseTypeSet != null) {
+ return false;
+ }
+ 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'])) {
+ header("Content-Disposition: attachment; filename=\"{$options['attachment']}\"");
+ }
+ if (Configure::read() < 2 && !defined('CAKEPHP_SHELL')) {
+ @header($header);
+ }
+ $this->__responseTypeSet = $cType;
+ return true;
+ }
+ return false;
+ }
+/**
+ * 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;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/components/security.php b/cake/libs/controller/components/security.php
new file mode 100755
index 00000000..96d14025
--- /dev/null
+++ b/cake/libs/controller/components/security.php
@@ -0,0 +1,692 @@
+ '', 'prompt' => null);
+/**
+ * An associative array of usernames/passwords used for HTTP-authenticated logins.
+ * If using digest authentication, passwords should be MD5-hashed.
+ *
+ * @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;
+/**
+ * Component startup. All security checking happens here.
+ *
+ * @param object $controller Instantiating controller
+ * @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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ */
+ 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 . ' ' . join(',', $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
+ */
+ 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()
+ */
+ 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
+ */
+ function blackHole(&$controller, $error = '') {
+ $this->Session->del('_Token');
+
+ 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()) {
+ $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'])) {
+ 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']);
+
+ $lockedFields = array();
+ $fields = Set::flatten($check);
+ $fieldList = array_keys($fields);
+ $locked = unserialize(str_rot13($locked));
+ $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) {
+ 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 {
+ // Debug::warning('Callback method ' . $method . ' in controller ' . get_class($controller)
+ return null;
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/components/session.php b/cake/libs/controller/components/session.php
new file mode 100755
index 00000000..f5164d4a
--- /dev/null
+++ b/cake/libs/controller/components/session.php
@@ -0,0 +1,315 @@
+__active = false;
+ }
+ }
+/**
+ * Initializes the component, gets a reference to Controller::$param['bare'].
+ *
+ * @param object $controller A reference to the controller
+ * @return void
+ * @access public
+ */
+ function initialize(&$controller) {
+ if (isset($controller->params['bare'])) {
+ $this->__bare = $controller->params['bare'];
+ }
+ }
+/**
+ * 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
+ */
+ 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
+ */
+ function read($name = null) {
+ if ($this->__active === true) {
+ $this->__start();
+ return parent::read($name);
+ }
+ return false;
+ }
+/**
+ * Used to delete a session variable.
+ *
+ * In your controller: $this->Session->del('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
+ */
+ function del($name) {
+ if ($this->__active === true) {
+ $this->__start();
+ return parent::del($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
+ */
+ function delete($name) {
+ if ($this->__active === true) {
+ $this->__start();
+ return $this->del($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
+ */
+ 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
+ */
+ 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 $layout Layout 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
+ */
+ function setFlash($message, $layout = 'default', $params = array(), $key = 'flash') {
+ if ($this->__active === true) {
+ $this->__start();
+ $this->write('Message.' . $key, compact('message', 'layout', '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
+ */
+ 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()) {
+ $this->__started = true;
+ parent::_checkValid();
+ } else {
+ $this->__started = parent::start();
+ }
+ }
+ return $this->__started;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/controller.php b/cake/libs/controller/controller.php
new file mode 100755
index 00000000..6a582bfc
--- /dev/null
+++ b/cake/libs/controller/controller.php
@@ -0,0 +1,1178 @@
+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/164/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();
+/**
+ * Text to be used for the $title_for_layout layout variable (usually
+ * placed inside tags.)
+ *
+ * @var boolean
+ * @access public
+ * @link http://book.cakephp.org/view/54/Page-related-Attributes-layout-and-pageTitle
+ */
+ var $pageTitle = false;
+/**
+ * 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/54/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/53/components-helpers-and-uses
+ */
+ var $components = array();
+/**
+ * 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/346/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/105/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;
+/**
+ * Constructor.
+ *
+ */
+ function __construct() {
+ if ($this->name === null) {
+ $r = null;
+ if (!preg_match('/(.*)Controller/i', get_class($this), $r)) {
+ die (__("Controller::__construct() : Can not get or parse my own class name, exiting."));
+ }
+ $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 === 'components') {
+ $normal = Set::normalize($this->{$var});
+ $app = Set::normalize($appVars[$var]);
+ if ($app !== $normal) {
+ $this->{$var} = Set::merge($app, $normal);
+ }
+ } else {
+ $this->{$var} = Set::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 === 'components') {
+ $normal = Set::normalize($this->{$var});
+ $app = Set::normalize($appVars[$var]);
+ if ($app !== $normal) {
+ $this->{$var} = Set::merge($app, $normal);
+ }
+ } else {
+ $this->{$var} = Set::merge($this->{$var}, array_diff($appVars[$var], $this->{$var}));
+ }
+ }
+ }
+ }
+ }
+/**
+ * Loads Model classes based on the 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/429/constructClasses
+ */
+ 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;
+ }
+/**
+ * Loads and instantiates models required by this controller.
+ * If Controller::persistModel; is true, controller will create cached 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 models 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 . '.';
+ }
+ }
+
+ if (strpos($modelClass, '.') !== false) {
+ list($plugin, $modelClass) = explode('.', $modelClass);
+ $plugin = $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;
+ }
+ }
+/**
+ * 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/425/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 = 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 (is_string($status)) {
+ $codes = array_combine(array_values($codes), array_keys($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;
+ }
+ }
+
+ if (!empty($status)) {
+ $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 method for header()
+ *
+ * @param string $status
+ * @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/427/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);
+ }
+
+ foreach ($data as $name => $value) {
+ if ($name === 'title') {
+ $this->pageTitle = $value;
+ } else {
+ if ($two === null && is_array($one)) {
+ $this->viewVars[Inflector::variable($name)] = $value;
+ } else {
+ $this->viewVars[$name] = $value;
+ }
+ }
+ }
+ }
+/**
+ * Internally redirects one action to another. 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/396/authorize
+ */
+ function isAuthorized() {
+ trigger_error(sprintf(__('%s::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 (!count($objects)) {
+ return false;
+ }
+
+ $errors = array();
+ foreach ($objects as $object) {
+ $this->{$object->alias}->set($object->data);
+ $errors = array_merge($errors, $this->{$object->alias}->invalidFields());
+ }
+
+ return $this->validationErrors = (count($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/428/render
+ */
+ function render($action = null, $layout = null, $file = null) {
+ $this->beforeRender();
+
+ $viewClass = $this->view;
+ if ($this->view != 'View') {
+ if (strpos($viewClass, '.') !== false) {
+ list($plugin, $viewClass) = explode('.', $viewClass);
+ }
+ $viewClass = $viewClass . 'View';
+ App::import('View', $this->view);
+ }
+
+ $this->Component->beforeRender($this);
+
+ $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);
+ }
+ if (isset($this->$currentModel) && is_a($this->$currentModel, 'Model') && !empty($this->$currentModel->validationErrors)) {
+ $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/430/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) {
+ return $default;
+ }
+ 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/431/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
+ * @return void Renders flash layout
+ * @access public
+ * @link http://book.cakephp.org/view/426/flash
+ */
+ function flash($message, $url, $pause = 1) {
+ $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, 'flash');
+ }
+/**
+ * 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/432/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 = '';
+ }
+
+ foreach ($data as $model => $fields) {
+ foreach ($fields as $field => $value) {
+ $key = $model.'.'.$field;
+ $fieldOp = $op;
+ if (is_array($op) && array_key_exists($key, $op)) {
+ $fieldOp = $op[$key];
+ } elseif (is_array($op) && array_key_exists($field, $op)) {
+ $fieldOp = $op[$field];
+ } elseif (is_array($op)) {
+ $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/165/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) = explode('.', $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 (isset($object->{$alias}) && $object->{$alias}->hasField($field)) {
+ $options['order'][$alias . '.' . $field] = $value;
+ } elseif ($object->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]);
+ }
+
+ extract($options = array_merge(array('page' => 1, 'limit' => 20), $defaults, $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));
+ }
+ $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/60/Callbacks
+ */
+ function beforeFilter() {
+ }
+/**
+ * Called after the controller action is run, but before the view is rendered.
+ *
+ * @access public
+ * @link http://book.cakephp.org/view/60/Callbacks
+ */
+ function beforeRender() {
+ }
+/**
+ * Called after the controller action is run and rendered.
+ *
+ * @access public
+ * @link http://book.cakephp.org/view/60/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/60/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/60/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/60/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/60/Callbacks
+ */
+ function _scaffoldError($method) {
+ return false;
+ }
+}
+?>
diff --git a/cake/libs/controller/pages_controller.php b/cake/libs/controller/pages_controller.php
new file mode 100755
index 00000000..8196616b
--- /dev/null
+++ b/cake/libs/controller/pages_controller.php
@@ -0,0 +1,86 @@
+redirect('/');
+ }
+ $page = $subpage = $title = null;
+
+ if (!empty($path[0])) {
+ $page = $path[0];
+ }
+ if (!empty($path[1])) {
+ $subpage = $path[1];
+ }
+ if (!empty($path[$count - 1])) {
+ $title = Inflector::humanize($path[$count - 1]);
+ }
+ $this->set(compact('page', 'subpage', 'title'));
+ $this->render(join('/', $path));
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/controller/scaffold.php b/cake/libs/controller/scaffold.php
new file mode 100755
index 00000000..165e2b56
--- /dev/null
+++ b/cake/libs/controller/scaffold.php
@@ -0,0 +1,528 @@
+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;
+ $this->controller->pageTitle = __('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('modelClass', 'primaryKey', 'displayField', 'singularVar', 'pluralVar',
+ 'singularHumanName', 'pluralHumanName', 'scaffoldFields', 'associations'));
+
+ if ($this->controller->view && $this->controller->view !== 'Theme') {
+ $this->controller->view = 'scaffold';
+ }
+ $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')) {
+
+ if (isset($params['pass'][0])) {
+ $this->ScaffoldModel->id = $params['pass'][0];
+ } elseif (isset($this->controller->Session) && $this->controller->Session->valid() != false) {
+ $this->controller->Session->setFlash(sprintf(__("No id set for %s::view()", true), Inflector::humanize($this->modelKey)));
+ $this->controller->redirect($this->redirect);
+ } else {
+ return $this->controller->flash(sprintf(__("No id set for %s::view()", true), Inflector::humanize($this->modelKey)),
+ '/' . 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 (isset($this->controller->Session) && $this->controller->Session->valid() != false) {
+ $this->controller->Session->setFlash($message);
+ $this->controller->redirect($this->redirect);
+ } else {
+ return $this->controller->flash($message, $this->redirect);
+ }
+ }
+ }
+
+ 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 (isset($this->controller->Session) && $this->controller->Session->valid() != false) {
+ $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 (isset($this->controller->Session) && $this->controller->Session->valid() != false) {
+ $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')) {
+ if (isset($params['pass'][0])) {
+ $id = $params['pass'][0];
+ } elseif (isset($this->controller->Session) && $this->controller->Session->valid() != false) {
+ $this->controller->Session->setFlash(sprintf(__("No id set for %s::delete()", true), Inflector::humanize($this->modelKey)));
+ $this->controller->redirect($this->redirect);
+ } else {
+ $this->controller->flash(sprintf(__("No id set for %s::delete()", true), Inflector::humanize($this->modelKey)), '/' . Inflector::underscore($this->controller->viewPath));
+ return $this->_output();
+ }
+
+ if ($this->ScaffoldModel->del($id)) {
+ if (isset($this->controller->Session) && $this->controller->Session->valid() != false) {
+ $this->controller->Session->setFlash(sprintf(__('The %1$s with id: %2$d has been deleted.', true), Inflector::humanize($this->modelClass), $id));
+ $this->controller->redirect($this->redirect);
+ } else {
+ $this->controller->flash(sprintf(__('The %1$s with id: %2$d has been deleted.', true), Inflector::humanize($this->modelClass), $id), '/' . $this->viewPath);
+ return $this->_output();
+ }
+ } else {
+ if (isset($this->controller->Session) && $this->controller->Session->valid() != false) {
+ $this->controller->Session->setFlash(sprintf(__('There was an error deleting the %1$s with id: %2$d', true), Inflector::humanize($this->modelClass), $id));
+ $this->controller->redirect($this->redirect);
+ } else {
+ $this->controller->flash(sprintf(__('There was an error deleting the %1$s with id: %2$d', true), Inflector::humanize($this->modelClass), $id), '/' . $this->viewPath);
+ 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);
+ $admin = Configure::read('Routing.admin');
+
+ if (isset($db)) {
+ if (empty($this->scaffoldActions)) {
+ $this->scaffoldActions = array('index', 'list', 'view', 'add', 'create', 'edit', 'update', 'delete');
+ } elseif (!empty($admin) && $this->scaffoldActions === $admin) {
+ $this->scaffoldActions = array($admin .'_index', $admin .'_list', $admin .'_view', $admin .'_add', $admin .'_create', $admin .'_edit', $admin .'_update', $admin .'_delete');
+ }
+
+ if (in_array($params['action'], $this->scaffoldActions)) {
+ if (!empty($admin)) {
+ $params['action'] = str_replace($admin . '_', '', $params['action']);
+ }
+ switch ($params['action']) {
+ case 'index':
+ $this->__scaffoldIndex($params);
+ break;
+ case 'view':
+ $this->__scaffoldView($params);
+ break;
+ case 'list':
+ $this->__scaffoldIndex($params);
+ break;
+ case 'add':
+ $this->__scaffoldSave($params, 'add');
+ break;
+ case 'edit':
+ $this->__scaffoldSave($params, 'edit');
+ break;
+ case 'create':
+ $this->__scaffoldSave($params, 'add');
+ break;
+ 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']));
+ }
+ }
+ return $associations;
+ }
+}
+
+/**
+ * Scaffold View.
+ *
+ * @package cake
+ * @subpackage cake.cake.libs.controller
+*/
+if (!class_exists('ThemeView')) {
+ App::import('View', 'Theme');
+}
+
+class ScaffoldView extends ThemeView {
+/**
+ * Override _getViewFileName
+ *
+ * @return string action
+ * @access protected
+ */
+ function _getViewFileName($name = null) {
+ if ($name === null) {
+ $name = $this->action;
+ }
+ $name = Inflector::underscore($name);
+ $admin = Configure::read('Routing.admin');
+
+ if (!empty($admin) && strpos($name, $admin . '_') !== false) {
+ $name = substr($name, strlen($admin) + 1);
+ }
+
+ 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, '.ctp', '.thtml');
+ foreach ($paths as $path) {
+ foreach ($names as $name) {
+ foreach ($exts as $ext) {
+ 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');
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/debugger.php b/cake/libs/debugger.php
new file mode 100755
index 00000000..1e942e5f
--- /dev/null
+++ b/cake/libs/debugger.php
@@ -0,0 +1,577 @@
+ 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 exportVar
+ * @access public
+ * @static
+ * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
+*/
+ function dump($var) {
+ $_this = Debugger::getInstance();
+ pr($_this->exportVar($var));
+ }
+/**
+ * Creates a detailed stack trace log at the time of invocation, much like dump()
+ * but to 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/460/Using-the-Debugger-Class
+ */
+ function log($var, $level = LOG_DEBUG) {
+ $_this = Debugger::getInstance();
+ $trace = $_this->trace(array('start' => 1, 'depth' => 2, 'format' => 'array'));
+ $source = null;
+
+ if (is_object($trace[0]['object']) && isset($trace[0]['object']->_reporter->_test_stack)) {
+ $stack = $trace[0]['object']->_reporter->_test_stack;
+ $source = sprintf('[%1$s, %3$s::%2$s()]' . "\n",
+ array_shift($stack), array_pop($stack), array_pop($stack));
+ }
+
+ CakeLog::write($level, $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 = '??';
+ }
+ $file = $_this->trimPath($file);
+
+ $info = compact('code', 'description', 'file', 'line');
+ if (!in_array($info, $_this->errors)) {
+ $_this->errors[] = $info;
+ } else {
+ return;
+ }
+
+ $level = LOG_DEBUG;
+ 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 false;
+ break;
+ }
+
+ $helpCode = null;
+ if (!empty($_this->helpPath) && preg_match('/.*\[([0-9]+)\]$/', $description, $codes)) {
+ if (isset($codes[1])) {
+ $helpCode = $codes[1];
+ $description = trim(preg_replace('/\[[0-9]+\]$/', '', $description));
+ }
+ }
+
+ echo $_this->_output($level, $error, $code, $helpCode, $description, $file, $line, $context);
+
+ if (Configure::read('log')) {
+ CakeLog::write($level, "{$error} ({$code}): {$description} in [{$file}, line {$line}]");
+ }
+
+ if ($error == 'Fatal Error') {
+ die();
+ }
+ return true;
+ }
+/**
+ * Outputs a stack trace based on the supplied options.
+ *
+ * @param array $options Format for outputting stack trace
+ * @return string Formatted stack trace
+ * @access public
+ * @static
+ * @link http://book.cakephp.org/view/460/Using-the-Debugger-Class
+ */
+ function trace($options = array()) {
+ $options = array_merge(array(
+ 'depth' => 999,
+ 'format' => '',
+ 'args' => false,
+ 'start' => 0,
+ 'scope' => null,
+ 'exclude' => null
+ ),
+ $options
+ );
+
+ $backtrace = debug_backtrace();
+ $back = array();
+ $count = count($backtrace);
+
+ 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(
+ array(
+ 'line' => '??',
+ 'file' => '[internal]',
+ 'class' => null,
+ 'function' => '[main]'
+ ),
+ $backtrace[$i + 1]
+ );
+ $function = $next['function'];
+
+ if (!empty($next['class'])) {
+ $function = $next['class'] . '::' . $function . '(';
+ if ($options['args'] && isset($next['args'])) {
+ $args = array();
+ foreach ($next['args'] as $arg) {
+ $args[] = Debugger::exportVar($arg);
+ }
+ $function .= join(', ', $args);
+ }
+ $function .= ')';
+ }
+ } else {
+ $function = '[main]';
+ }
+ if (in_array($function, array('call_user_func_array', 'trigger_error'))) {
+ continue;
+ }
+ if ($options['format'] == 'points' && $trace['file'] != '[internal]') {
+ $back[] = array('file' => $trace['file'], 'line' => $trace['line']);
+ } elseif (empty($options['format'])) {
+ $back[] = $function . ' - ' . Debugger::trimPath($trace['file']) . ', line ' . $trace['line'];
+ } else {
+ $back[] = $trace;
+ }
+ }
+
+ if ($options['format'] == 'array' || $options['format'] == 'points') {
+ return $back;
+ }
+ return join("\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 = Configure::corePaths('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/460/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/460/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 (count($vars) > 0) {
+ $n = "\n";
+ }
+ return $out . join(",", $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 join("\n", $out);
+ }
+/**
+ * Handles object conversion to debug string.
+ *
+ * @param string $var Object to convert
+ * @access protected
+ */
+ function output($format = 'js') {
+ $_this = Debugger::getInstance();
+ $data = null;
+
+ if ($format === true && !empty($_this->__data)) {
+ $data = $_this->__data;
+ $_this->__data = array();
+ $format = false;
+ }
+ $_this->_outputFormat = $format;
+
+ return $data;
+ }
+/**
+ * Handles object conversion to debug string.
+ *
+ * @param string $var Object to convert
+ * @access private
+ */
+ function _output($level, $error, $code, $helpCode, $description, $file, $line, $kontext) {
+ $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'));
+ $context = array();
+
+ foreach ((array)$kontext as $var => $value) {
+ $context[] = "\${$var}\t=\t" . $this->exportVar($value, 1);
+ }
+
+ switch ($this->_outputFormat) {
+ default:
+ case 'js':
+ $link = "document.getElementById(\"CakeStackTrace" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeStackTrace" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
+ $out = "{$error} ({$code}): {$description} [{$file}, line {$line}]";
+ if (Configure::read() > 0) {
+ debug($out, false, false);
+ echo '';
+ }
+ break;
+ case 'html':
+ echo "{$error} ({$code}) : {$description} [{$file}, line {$line}]";
+ if (!empty($context)) {
+ echo "Context:\n" .implode("\n", $context) . "\n";
+ }
+ echo "Context " . implode("\n", $context) . "
";
+ echo "Trace " . $trace. "
";
+ break;
+ case 'text':
+ case 'txt':
+ echo "{$error}: {$code} :: {$description} on line {$line} of {$file}\n";
+ if (!empty($context)) {
+ echo "Context:\n" .implode("\n", $context) . "\n";
+ }
+ echo "Trace:\n" . $trace;
+ break;
+ case 'log':
+ $this->log(compact('error', 'code', 'description', 'line', 'file', 'context', 'trace'));
+ break;
+ case false:
+ $this->__data[] = compact('error', 'code', 'description', 'line', 'file', 'context', 'trace');
+ break;
+ }
+ }
+/**
+ * Verifies that the application's salt value has been changed from the default value.
+ *
+ * @access public
+ * @static
+ */
+ function checkSessionKey() {
+ 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);
+ }
+ }
+/**
+ * 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/460/Using-the-Debugger-Class
+ */
+ function invoke(&$debugger) {
+ set_error_handler(array(&$debugger, 'handleError'));
+ }
+}
+
+if (!defined('DISABLE_DEFAULT_ERROR_HANDLING')) {
+ Debugger::invoke(Debugger::getInstance());
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/error.php b/cake/libs/error.php
new file mode 100755
index 00000000..e8db8276
--- /dev/null
+++ b/cake/libs/error.php
@@ -0,0 +1,378 @@
+_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() == 0) {
+ $method = 'error404';
+ if (isset($code) && $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);
+ 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');
+ }
+/**
+ * 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->set(array(
+ '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->set(array(
+ '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->set(array(
+ '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 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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/file.php b/cake/libs/file.php
new file mode 100755
index 00000000..b57cb4c7
--- /dev/null
+++ b/cake/libs/file.php
@@ -0,0 +1,508 @@
+Folder =& new Folder(dirname($path), $create, $mode);
+ if (!is_dir($path)) {
+ $this->name = basename($path);
+ }
+ $this->pwd();
+
+ if (!$this->exists()) {
+ if ($create === true) {
+ if ($this->safe($path) && $this->create() === false) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+/**
+ * Closes the current file if it is opened
+ *
+ * @access private
+ */
+ 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
+ * @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);
+ }
+ $data = trim($data);
+
+ if ($this->lock !== null) {
+ flock($this->handle, LOCK_UN);
+ }
+ if ($bytes === false) {
+ $this->close();
+ }
+ return $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
+ * fixes line endings
+ *
+ * @param string $data Data to prepare for writing.
+ * @return string
+ * @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 extension.
+ *
+ * @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
+ * @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 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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/flay.php b/cake/libs/flay.php
new file mode 100755
index 00000000..ed23cf7f
--- /dev/null
+++ b/cake/libs/flay.php
@@ -0,0 +1,285 @@
+text = $text;
+ parent::__construct();
+ }
+/**
+ * Returns given text translated to HTML using the Flay syntax.
+ *
+ * @param string $text String to format
+ * @param boolean $bare Set this to only do transforms and > to >, no typography additions.
+ * @param boolean $allowHtml Set this to trim whitespace and disable all HTML
+ * @return string Formatted text
+ * @access public
+ */
+ function toHtml($text = null, $bare = false, $allowHtml = false) {
+ if (empty($text) && empty($this->text)) {
+ return false;
+ }
+ $text = $text ? $text : $this->text;
+ // trim whitespace and disable all HTML
+ if ($allowHtml) {
+ $text = trim($text);
+ } else {
+ $text = str_replace('<', '<', str_replace('>', '>', trim($text)));
+ }
+
+ if (!$bare) {
+ // multi-paragraph functions
+ $text=preg_replace('#(?:[\n]{0,2})"""(.*)"""(?:[\n]{0,2})#s', "\n\n%BLOCKQUOTE%\n\n\\1\n\n%ENDBLOCKQUOTE%\n\n", $text);
+ $text=preg_replace('#(?:[\n]{0,2})===(.*)===(?:[\n]{0,2})#s', "\n\n%CENTER%\n\n\\1\n\n%ENDCENTER%\n\n", $text);
+ }
+
+ // pre-parse newlines
+ $text=preg_replace("#\r\n#", "\n", $text);
+ $text=preg_replace("#[\n]{2,}#", "%PARAGRAPH%", $text);
+ $text=preg_replace('#[\n]{1}#', "%LINEBREAK%", $text);
+ $out ='';
+
+ foreach (split('%PARAGRAPH%', $text)as $line) {
+ if ($line) {
+ if (!$bare) {
+ $links = array();
+ $regs = null;
+
+ if (preg_match_all('#\[([^\[]{4,})\]#', $line, $regs)) {
+ foreach ($regs[1] as $reg) {
+ $links[] = $reg;
+ $line = str_replace("[{$reg}]", '%LINK' . (count($links) - 1) . '%', $line);
+ }
+ }
+ // bold
+ $line = ereg_replace("\*([^\*]*)\*", "\\1", $line);
+ // italic
+ $line = ereg_replace("_([^_]*)_", "\\1", $line);
+ }
+ // entities
+ $line = str_replace(' - ', ' – ', $line);
+ $line = str_replace(' -- ', ' — ', $line);
+ $line = str_replace('(C)', '©', $line);
+ $line = str_replace('(R)', '®', $line);
+ $line = str_replace('(TM)', '™', $line);
+ // guess e-mails
+ $emails = null;
+ if (preg_match_all("#([_A-Za-z0-9+-+]+(?:\.[_A-Za-z0-9+-]+)*@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*)#", $line, $emails)) {
+ foreach ($emails[1] as $email) {
+ $line = str_replace($email, "{$email}", $line);
+ }
+ }
+
+ if (!$bare) {
+ $urls = null;
+ if (preg_match_all("#((?:http|https|ftp|nntp)://[^ ]+)#", $line, $urls)) {
+ foreach ($urls[1] as $url) {
+ $line = str_replace($url, "{$url}", $line);
+ }
+ }
+
+ if (preg_match_all("#(www\.[^\n\%\ ]+[^\n\%\,\.\ ])#", $line, $urls)) {
+ foreach ($urls[1] as $url) {
+ $line = str_replace($url, "{$url}", $line);
+ }
+ }
+
+ if ($count = count($links)) {
+ for ($ii = 0; $ii < $count; $ii++) {
+ if (preg_match("#^(http|https|ftp|nntp)://#", $links[$ii])) {
+ $prefix = null;
+ } else {
+ $prefix = 'http://';
+ }
+ if (preg_match('#^[^\ ]+\.(jpg|jpeg|gif|png)$#', $links[$ii])) {
+ $with = "
";
+ } elseif (preg_match('#^([^\]\ ]+)(?:\ ([^\]]+))?$#', $links[$ii], $regs)) {
+ if (isset($regs[2])) {
+ if (preg_match('#\.(jpg|jpeg|gif|png)$#', $regs[2])) {
+ $body = "
";
+ } else {
+ $body = $regs[2];
+ }
+ } else {
+ $body = $links[$ii];
+ }
+ $with = "{$body}";
+ } else {
+ $with = $prefix . $links[$ii];
+ }
+ $line = str_replace("%LINK{$ii}%", $with, $line);
+ }
+ }
+ }
+ $out .= str_replace('%LINEBREAK%', "
\n", "
{$line}
\n");
+ }
+ }
+
+ if (!$bare) {
+ $out = str_replace('%BLOCKQUOTE%
', "", $out);
+ $out = str_replace('%ENDBLOCKQUOTE%
', "
", $out);
+ $out = str_replace('%CENTER%
', "", $out);
+ $out = str_replace('%ENDCENTER%
', " ", $out);
+ }
+ return $out;
+ }
+/**
+ * Return the words of the string as an array.
+ *
+ * @param string $string
+ * @return array Array of words
+ * @access public
+ */
+ function extractWords($string) {
+ $split = preg_split('/[\s,\.:\/="!\(\)<>~\[\]]+/', $string);
+ return $split;
+ }
+/**
+ * Return given string with words in array colorMarked, up to a number of times (defaults to 5).
+ *
+ * @param array $words Words to look for and markup
+ * @param string $string String to look in
+ * @param integer $max_snippets Max number of snippets to extract
+ * @return string String with words marked
+ * @see colorMark
+ * @access public
+ */
+ function markedSnippets($words, $string, $max_snippets = 5) {
+ $string = strip_tags($string);
+ $snips = array();
+ $rest = $string;
+ foreach ($words as $word) {
+ if (preg_match_all("/[\s,]+.{0,40}{$word}.{0,40}[\s,]+/i", $rest, $r)) {
+ foreach ($r as $result) {
+ $rest = str_replace($result, '', $rest);
+ }
+ $snips = array_merge($snips, $r[0]);
+ }
+ }
+
+ if (count($snips) > $max_snippets) {
+ $snips = array_slice($snips, 0, $max_snippets);
+ }
+ $joined = join(' ... ', $snips);
+ $snips = $joined ? "... {$joined} ..." : substr($string, 0, 80) . '...';
+ return $this->colorMark($words, $snips);
+ }
+/**
+ * Returns string with EM elements with color classes added.
+ *
+ * @param array $words Array of words to be colorized
+ * @param string $string Text in which the words might be found
+ * @return string String with words colorized
+ * @access public
+ */
+ function colorMark($words, $string) {
+ $colors=array('yl', 'gr', 'rd', 'bl', 'fu', 'cy');
+ $nextColorIndex = 0;
+ foreach ($words as $word) {
+ $string = preg_replace("/({$word})/i", '\\1", $string);
+ $nextColorIndex++;
+ }
+ return $string;
+ }
+/**
+ * Returns given text with tags stripped out.
+ *
+ * @param string $text Text to clean
+ * @return string Cleaned text
+ * @access public
+ */
+ function toClean($text) {
+ $strip = strip_tags(html_entity_decode($text, ENT_QUOTES));
+ return $strip;
+ }
+/**
+ * Return parsed text with tags stripped out.
+ *
+ * @param string $text Text to parse and clean
+ * @return string Cleaned text
+ * @access public
+ */
+ function toParsedAndClean($text) {
+ return $this->toClean(Flay::toHtml($text));
+ }
+/**
+ * Return a fragment of a text, up to $length characters long, with an ellipsis after it.
+ *
+ * @param string $text Text to be truncated.
+ * @param integer $length Max length of text.
+ * @param string $ellipsis Sign to print after truncated text.
+ * @return string Fragment
+ * @access public
+ */
+ function fragment($text, $length, $ellipsis = '...') {
+ $soft = $length - 5;
+ $hard = $length + 5;
+ $rx = '/(.{' . $soft . ',' . $hard . '})[\s,\.:\/="!\(\)<>~\[\]]+.*/';
+
+ if (preg_match($rx, $text, $r)) {
+ $out = $r[1];
+ } else {
+ $out = substr($text, 0, $length);
+ }
+ $out = $out . (strlen($out) < strlen($text) ? $ellipsis : null);
+ return $out;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/folder.php b/cake/libs/folder.php
new file mode 100755
index 00000000..28f32672
--- /dev/null
+++ b/cake/libs/folder.php
@@ -0,0 +1,773 @@
+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
+ * @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 (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: .*)
+ * @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: .*)
+ * @return array Files matching $pattern
+ * @access public
+ */
+ function findRecursive($pattern = '.*', $sort = false) {
+ $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
+ * @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) {
+ if (preg_match('/^[A-Z]:\\\\/i', $path)) {
+ return true;
+ }
+ return false;
+ }
+/**
+ * Returns true if given $path is an absolute path.
+ *
+ * @param string $path Path to check
+ * @return bool
+ * @access public
+ * @static
+ */
+ function isAbsolute($path) {
+ $match = preg_match('/^\\//', $path) || preg_match('/^[A-Z]:\\\\/i', $path);
+ return $match;
+ }
+/**
+ * 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) {
+ if (Folder::isWindowsPath($path)) {
+ return '\\';
+ }
+ return '/';
+ }
+/**
+ * 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 Folder::slashTerm($path) . $element;
+ }
+/**
+ * Returns true if the File is in a given CakePath.
+ *
+ * @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.
+ *
+ * @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);
+ }
+ if ($return == 1) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+/**
+ * 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
+ * @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 boolean $hidden return hidden files and directories
+ * @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);
+ $this->__files = array();
+ $this->__directories = array($path);
+ $directories = array();
+
+ if ($exceptions === false) {
+ $exceptions = true;
+ }
+ while (count($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
+ * @param = boolean $hidden
+ * @access private
+ */
+ function __tree($path, $exceptions) {
+ if ($this->cd($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.
+ *
+ * @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;
+ }
+ $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 true;
+ }
+/**
+ * Returns the size in bytes of this Folder.
+ *
+ * @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 system allow.
+ *
+ * @param string $path Path of directory to delete
+ * @return boolean Success
+ * @access public
+ */
+ function delete($path = null) {
+ if (!$path) {
+ $path = $this->pwd();
+ }
+ $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.
+ *
+ * @param array $options (to, from, chmod, skip)
+ * @return bool
+ * @access public
+ */
+ function copy($options = array()) {
+ $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->mkdir($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.
+ *
+ * @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;
+ }
+/**
+ * nix flavored alias
+ *
+ * @see read
+ * @access public
+ */
+ function ls($sort = true, $exceptions = false) {
+ return $this->read($sort, $exceptions);
+ }
+/**
+ * nix flavored alias
+ *
+ * @see create
+ * @access public
+ */
+ function mkdir($pathname, $mode = 0755) {
+ return $this->create($pathname, $mode);
+ }
+/**
+ * nix flavored alias
+ *
+ * @see copy
+ * @access public
+ */
+ function cp($options) {
+ return $this->copy($options);
+ }
+/**
+ * nix flavored alias
+ *
+ * @see move
+ * @access public
+ */
+ function mv($options) {
+ return $this->move($options);
+ }
+/**
+ * nix flavored alias
+ *
+ * @see delete
+ * @access public
+ */
+ function rm($path) {
+ return $this->delete($path);
+ }
+/**
+ * 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 (count($newparts) > 0) {
+ 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 === '\\';
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/http_socket.php b/cake/libs/http_socket.php
new file mode 100755
index 00000000..76d12d71
--- /dev/null
+++ b/cake/libs/http_socket.php
@@ -0,0 +1,992 @@
+ '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.
+ *
+ * @param array $config Configuration
+ */
+ 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.
+ *
+ * @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']);
+
+ 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);
+
+ $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']);
+ }
+ $this->request['header'] = array_merge(array('Host' => $this->request['uri']['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.
+ *
+ * @param mixed $uri URI to request (see {@link parseUri()})
+ * @param array $query 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 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.
+ *
+ * @param mixed $uri URI to request (see {@link parseUri()})
+ * @param array $query 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 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 {@link parseUri()})
+ * @param array $query 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 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 $query 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);
+ }
+/**
+ * undocumented function
+ *
+ * @param unknown $url
+ * @param unknown $uriTemplate
+ * @return void
+ * @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 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 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 {@link 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 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
+ * @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@'
+ );
+
+ 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);
+ } 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 = join(',', $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;
+ }
+/**
+ * undocumented function
+ *
+ * @param unknown $header
+ * @return void
+ * @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__}", '";"', preg_split('/\;/', $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;
+ }
+/**
+ * undocumented function
+ *
+ * @param unknown $cookies
+ * @return void
+ * @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' => $header), 'pragmatic');
+ return $header;
+ }
+/**
+ * undocumented function
+ *
+ * @return void
+ * @access public
+ */
+ function saveCookies() {
+
+ }
+/**
+ * undocumented function
+ *
+ * @return void
+ * @access public
+ */
+ function loadCookies() {
+
+ }
+/**
+ * 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 private
+ * @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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/i18n.php b/cake/libs/i18n.php
new file mode 100755
index 00000000..6e21ca5c
--- /dev/null
+++ b/cake/libs/i18n.php
@@ -0,0 +1,451 @@
+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
+ * @param string $category Category
+ * @param integer $count Count
+ * @return string translated strings.
+ * @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->locale;
+
+ if (empty($_this->__domains)) {
+ $_this->__domains = Cache::read($_this->domain, '_cake_core_');
+ }
+
+ if (!isset($_this->__domains[$_this->category][$_this->__lang][$domain])) {
+ $_this->__bindTextDomain($domain);
+ $_this->__cache = true;
+ }
+
+ if (!isset($count)) {
+ $plurals = 0;
+ } elseif (!empty($_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"]) && $_this->__noLocale === false) {
+ $header = $_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"];
+ $plurals = $_this->__pluralGuess($header, $count);
+ } else {
+ if ($count != 1) {
+ $plurals = 1;
+ } else {
+ $plurals = 0;
+ }
+ }
+
+ if (!empty($_this->__domains[$_this->category][$_this->__lang][$domain][$singular])) {
+ if (($trans = $_this->__domains[$_this->category][$_this->__lang][$domain][$singular]) || ($plurals) && ($trans = $_this->__domains[$_this->category][$_this->__lang][$domain][$plural])) {
+ if (is_array($trans)) {
+ if (isset($trans[$plurals])) {
+ $trans = $trans[$plurals];
+ }
+ }
+ if (strlen($trans)) {
+ $singular = $trans;
+ return $singular;
+ }
+ }
+ }
+
+ if (!empty($plurals)) {
+ return($plural);
+ }
+ return($singular);
+ }
+/**
+ * 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 = Configure::read('localePaths');
+ $plugins = Configure::listObjects('plugin');
+
+ if (!empty($plugins)) {
+ $pluginPaths = Configure::read('pluginPaths');
+
+ foreach ($plugins as $plugin) {
+ $plugin = Inflector::underscore($plugin);
+ if ($plugin === $domain) {
+ foreach ($pluginPaths as $pluginPath) {
+ $searchPaths[] = $pluginPath . DS . $plugin . DS . 'locale';
+ }
+ $searchPaths = array_reverse($searchPaths);
+ break;
+ }
+ }
+ }
+
+ foreach ($searchPaths as $directory) {
+ foreach ($this->l10n->languagePath as $lang) {
+ $file = $directory . DS . $lang . DS . $this->category . DS . $domain;
+
+ if ($core) {
+ $app = $directory . DS . $lang . DS . $this->category . DS . 'core';
+ if (file_exists($fn = "$app.mo")) {
+ $this->__loadMo($fn, $domain);
+ $this->__noLocale = false;
+ $merge[$this->category][$this->__lang][$domain] = $this->__domains[$this->category][$this->__lang][$domain];
+ $core = null;
+ } elseif (file_exists($fn = "$app.po") && ($f = fopen($fn, "r"))) {
+ $this->__loadPo($f, $domain);
+ $this->__noLocale = false;
+ $merge[$this->category][$this->__lang][$domain] = $this->__domains[$this->category][$this->__lang][$domain];
+ $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;
+ }
+ }
+ }
+
+ if (empty($this->__domains[$this->category][$this->__lang][$domain])) {
+ $this->__domains[$this->category][$this->__lang][$domain] = array();
+ return($domain);
+ }
+
+ if ($head = $this->__domains[$this->category][$this->__lang][$domain][""]) {
+ foreach (explode("\n", $head) as $line) {
+ $header = strtok($line,":");
+ $line = trim(strtok("\n"));
+ $this->__domains[$this->category][$this->__lang][$domain]["%po-header"][strtolower($header)] = $line;
+ }
+
+ if (isset($this->__domains[$this->category][$this->__lang][$domain]["%po-header"]["plural-forms"])) {
+ $switch = preg_replace("/(?:[() {}\\[\\]^\\s*\\]]+)/", "", $this->__domains[$this->category][$this->__lang][$domain]["%po-header"]["plural-forms"]);
+ $this->__domains[$this->category][$this->__lang][$domain]["%plural-c"] = $switch;
+ unset($this->__domains[$this->category][$this->__lang][$domain]["%po-header"]);
+ }
+ $this->__domains = Set::pushDiff($this->__domains, $merge);
+
+ if (isset($this->__domains[$this->category][$this->__lang][$domain][null])) {
+ unset($this->__domains[$this->category][$this->__lang][$domain][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[$this->category][$this->__lang][$domain][$msgid] = $msgstr;
+
+ if (isset($msgid_plural)) {
+ $this->__domains[$this->category][$this->__lang][$domain][$msgid_plural] =& $this->__domains[$this->category][$this->__lang][$domain][$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, 1024));
+ 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[$this->category][$this->__lang][$domain] = array_merge($merge ,$translations);
+ }
+/**
+ * Object destructor
+ *
+ * Write cache file if changes have been made to the $__map or $__paths
+ * @access private
+ */
+ function __destruct() {
+ if ($this->__cache) {
+ Cache::write($this->domain, array_filter($this->__domains), '_cake_core_');
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/inflector.php b/cake/libs/inflector.php
new file mode 100755
index 00000000..71f07b4e
--- /dev/null
+++ b/cake/libs/inflector.php
@@ -0,0 +1,531 @@
+test();
+ *
+ * @package cake
+ * @subpackage cake.cake.libs
+ * @link http://book.cakephp.org/view/491/Inflector
+ */
+class Inflector extends Object {
+/**
+ * Pluralized words.
+ *
+ * @var array
+ * @access private
+ **/
+ var $pluralized = array();
+/**
+ * List of pluralization rules in the form of pattern => replacement.
+ *
+ * @var array
+ * @access public
+ * @link http://book.cakephp.org/view/47/Custom-Inflections
+ **/
+ var $pluralRules = array();
+/**
+ * Singularized words.
+ *
+ * @var array
+ * @access private
+ **/
+ var $singularized = array();
+/**
+ * List of singularization rules in the form of pattern => replacement.
+ *
+ * @var array
+ * @access public
+ * @link http://book.cakephp.org/view/47/Custom-Inflections
+ **/
+ var $singularRules = array();
+/**
+ * Plural rules from inflections.php
+ *
+ * @var array
+ * @access private
+ **/
+ var $__pluralRules = array();
+/**
+ * Un-inflected plural rules from inflections.php
+ *
+ * @var array
+ * @access private
+ **/
+ var $__uninflectedPlural = array();
+/**
+ * Irregular plural rules from inflections.php
+ *
+ * @var array
+ * @access private
+ **/
+ var $__irregularPlural = array();
+/**
+ * Singular rules from inflections.php
+ *
+ * @var array
+ * @access private
+ **/
+ var $__singularRules = array();
+/**
+ * Un-inflectd singular rules from inflections.php
+ *
+ * @var array
+ * @access private
+ **/
+ var $__uninflectedSingular = array();
+/**
+ * Irregular singular rules from inflections.php
+ *
+ * @var array
+ * @access private
+ **/
+ var $__irregularSingular = array();
+/**
+ * Gets a reference to the Inflector object instance
+ *
+ * @return object
+ * @access public
+ */
+ function &getInstance() {
+ static $instance = array();
+
+ if (!$instance) {
+ $instance[0] =& new Inflector();
+ if (file_exists(CONFIGS.'inflections.php')) {
+ include(CONFIGS.'inflections.php');
+ $instance[0]->__pluralRules = $pluralRules;
+ $instance[0]->__uninflectedPlural = $uninflectedPlural;
+ $instance[0]->__irregularPlural = $irregularPlural;
+ $instance[0]->__singularRules = $singularRules;
+ $instance[0]->__uninflectedSingular = $uninflectedPlural;
+ $instance[0]->__irregularSingular = array_flip($irregularPlural);
+ }
+ }
+ return $instance[0];
+ }
+/**
+ * Initializes plural inflection rules.
+ *
+ * @return void
+ * @access private
+ */
+ function __initPluralRules() {
+ $corePluralRules = 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$/' => 'uses',
+ '/(alias)$/i' => '\1es',
+ '/(ax|cris|test)is$/i' => '\1es',
+ '/s$/' => 's',
+ '/^$/' => '',
+ '/$/' => 's');
+
+ $coreUninflectedPlural = array(
+ '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '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', 'People', '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');
+
+ $coreIrregularPlural = 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',
+ 'numen' => 'numina',
+ 'occiput' => 'occiputs',
+ 'octopus' => 'octopuses',
+ 'opus' => 'opuses',
+ 'ox' => 'oxen',
+ 'penis' => 'penises',
+ 'person' => 'people',
+ 'sex' => 'sexes',
+ 'soliloquy' => 'soliloquies',
+ 'testis' => 'testes',
+ 'trilby' => 'trilbys',
+ 'turf' => 'turfs');
+
+ $pluralRules = Set::pushDiff($this->__pluralRules, $corePluralRules);
+ $uninflected = Set::pushDiff($this->__uninflectedPlural, $coreUninflectedPlural);
+ $irregular = Set::pushDiff($this->__irregularPlural, $coreIrregularPlural);
+
+ $this->pluralRules = array('pluralRules' => $pluralRules, 'uninflected' => $uninflected, 'irregular' => $irregular);
+ $this->pluralized = array();
+ }
+/**
+ * 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/572/Class-methods
+ */
+ function pluralize($word) {
+ $_this =& Inflector::getInstance();
+ if (!isset($_this->pluralRules) || empty($_this->pluralRules)) {
+ $_this->__initPluralRules();
+ }
+
+ if (isset($_this->pluralized[$word])) {
+ return $_this->pluralized[$word];
+ }
+ extract($_this->pluralRules);
+
+ if (!isset($regexUninflected) || !isset($regexIrregular)) {
+ $regexUninflected = __enclose(join( '|', $uninflected));
+ $regexIrregular = __enclose(join( '|', array_keys($irregular)));
+ $_this->pluralRules['regexUninflected'] = $regexUninflected;
+ $_this->pluralRules['regexIrregular'] = $regexIrregular;
+ }
+
+ if (preg_match('/^(' . $regexUninflected . ')$/i', $word, $regs)) {
+ $_this->pluralized[$word] = $word;
+ return $word;
+ }
+
+ if (preg_match('/(.*)\\b(' . $regexIrregular . ')$/i', $word, $regs)) {
+ $_this->pluralized[$word] = $regs[1] . substr($word, 0, 1) . substr($irregular[strtolower($regs[2])], 1);
+ return $_this->pluralized[$word];
+ }
+
+ foreach ($pluralRules as $rule => $replacement) {
+ if (preg_match($rule, $word)) {
+ $_this->pluralized[$word] = preg_replace($rule, $replacement, $word);
+ return $_this->pluralized[$word];
+ }
+ }
+ }
+/**
+ * Initializes singular inflection rules.
+ *
+ * @return void
+ * @access protected
+ */
+ function __initSingularRules() {
+ $coreSingularRules = 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/' => '\1',
+ '/(cris|ax|test)es$/i' => '\1is',
+ '/(shoe)s$/i' => '\1',
+ '/(o)es$/i' => '\1',
+ '/ouses$/' => 'ouse',
+ '/uses$/' => 'us',
+ '/([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',
+ '/((a)naly|(b)a|(d)iagno|(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',
+ '/^(.*us)$/' => '\\1',
+ '/s$/i' => '');
+
+ $coreUninflectedSingular = array(
+ '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss', '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'
+ );
+
+ $coreIrregularSingular = array(
+ 'atlases' => 'atlas',
+ 'beefs' => 'beef',
+ 'brothers' => 'brother',
+ 'children' => 'child',
+ 'corpuses' => 'corpus',
+ 'cows' => 'cow',
+ 'ganglions' => 'ganglion',
+ 'genies' => 'genie',
+ 'genera' => 'genus',
+ 'graffiti' => 'graffito',
+ 'hoofs' => 'hoof',
+ 'loaves' => 'loaf',
+ 'men' => 'man',
+ 'monies' => 'money',
+ 'mongooses' => 'mongoose',
+ 'moves' => 'move',
+ 'mythoi' => 'mythos',
+ 'numina' => 'numen',
+ 'occiputs' => 'occiput',
+ 'octopuses' => 'octopus',
+ 'opuses' => 'opus',
+ 'oxen' => 'ox',
+ 'penises' => 'penis',
+ 'people' => 'person',
+ 'sexes' => 'sex',
+ 'soliloquies' => 'soliloquy',
+ 'testes' => 'testis',
+ 'trilbys' => 'trilby',
+ 'turfs' => 'turf',
+ 'waves' => 'wave'
+ );
+
+ $singularRules = Set::pushDiff($this->__singularRules, $coreSingularRules);
+ $uninflected = Set::pushDiff($this->__uninflectedSingular, $coreUninflectedSingular);
+ $irregular = Set::pushDiff($this->__irregularSingular, $coreIrregularSingular);
+
+ $this->singularRules = array('singularRules' => $singularRules, 'uninflected' => $uninflected, 'irregular' => $irregular);
+ $this->singularized = array();
+ }
+/**
+ * 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/572/Class-methods
+ */
+ function singularize($word) {
+ $_this =& Inflector::getInstance();
+ if (!isset($_this->singularRules) || empty($_this->singularRules)) {
+ $_this->__initSingularRules();
+ }
+
+ if (isset($_this->singularized[$word])) {
+ return $_this->singularized[$word];
+ }
+ extract($_this->singularRules);
+
+ if (!isset($regexUninflected) || !isset($regexIrregular)) {
+ $regexUninflected = __enclose(join( '|', $uninflected));
+ $regexIrregular = __enclose(join( '|', array_keys($irregular)));
+ $_this->singularRules['regexUninflected'] = $regexUninflected;
+ $_this->singularRules['regexIrregular'] = $regexIrregular;
+ }
+
+ if (preg_match('/^(' . $regexUninflected . ')$/i', $word, $regs)) {
+ $_this->singularized[$word] = $word;
+ return $word;
+ }
+
+ if (preg_match('/(.*)\\b(' . $regexIrregular . ')$/i', $word, $regs)) {
+ $_this->singularized[$word] = $regs[1] . substr($word, 0, 1) . substr($irregular[strtolower($regs[2])], 1);
+ return $_this->singularized[$word];
+ }
+
+ foreach ($singularRules 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/572/Class-methods
+ */
+ function camelize($lowerCaseAndUnderscoredWord) {
+ return str_replace(" ", "", ucwords(str_replace("_", " ", $lowerCaseAndUnderscoredWord)));
+ }
+/**
+ * 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/572/Class-methods
+ */
+ function underscore($camelCasedWord) {
+ return strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $camelCasedWord));
+ }
+/**
+ * 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/572/Class-methods
+ */
+ function humanize($lowerCaseAndUnderscoredWord) {
+ return ucwords(str_replace("_", " ", $lowerCaseAndUnderscoredWord));
+ }
+/**
+ * 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/572/Class-methods
+ */
+ function tableize($className) {
+ return Inflector::pluralize(Inflector::underscore($className));
+ }
+/**
+ * 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/572/Class-methods
+ */
+ function classify($tableName) {
+ return Inflector::camelize(Inflector::singularize($tableName));
+ }
+/**
+ * Returns camelBacked version of an underscored string.
+ *
+ * @param string $string
+ * @return string in variable form
+ * @access public
+ * @static
+ * @link http://book.cakephp.org/view/572/Class-methods
+ */
+ function variable($string) {
+ $string = Inflector::camelize(Inflector::underscore($string));
+ $replace = strtolower(substr($string, 0, 1));
+ return preg_replace('/\\w/', $replace, $string, 1);
+ }
+/**
+ * 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
+ * @param string $replacement
+ * @return string
+ * @access public
+ * @static
+ * @link http://book.cakephp.org/view/572/Class-methods
+ */
+ function slug($string, $replacement = '_') {
+ if (!class_exists('String')) {
+ require LIBS . 'string.php';
+ }
+ $map = array(
+ '/à|á|å|â/' => 'a',
+ '/è|é|ê|ẽ|ë/' => 'e',
+ '/ì|í|î/' => 'i',
+ '/ò|ó|ô|ø/' => 'o',
+ '/ù|ú|ů|û/' => 'u',
+ '/ç/' => 'c',
+ '/ñ/' => 'n',
+ '/ä|æ/' => 'ae',
+ '/ö/' => 'oe',
+ '/ü/' => 'ue',
+ '/Ä/' => 'Ae',
+ '/Ü/' => 'Ue',
+ '/Ö/' => 'Oe',
+ '/ß/' => 'ss',
+ '/[^\w\s]/' => ' ',
+ '/\\s+/' => $replacement,
+ String::insert('/^[:replacement]+|[:replacement]+$/', array('replacement' => preg_quote($replacement, '/'))) => '',
+ );
+ return preg_replace(array_keys($map), array_values($map), $string);
+ }
+}
+/**
+ * Enclose a string for preg matching.
+ *
+ * @param string $string String to enclose
+ * @return string Enclosed string
+ */
+ function __enclose($string) {
+ return '(?:' . $string . ')';
+ }
+?>
\ No newline at end of file
diff --git a/cake/libs/l10n.php b/cake/libs/l10n.php
new file mode 100755
index 00000000..95f66c25
--- /dev/null
+++ b/cake/libs/l10n.php
@@ -0,0 +1,466 @@
+ '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',
+ /* 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'),
+ 'ar' => array('language' => 'Arabic', 'locale' => 'ara', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-ae' => array('language' => 'Arabic (U.A.E.)', 'locale' => 'ar_ae', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-bh' => array('language' => 'Arabic (Bahrain)', 'locale' => 'ar_bh', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-dz' => array('language' => 'Arabic (Algeria)', 'locale' => 'ar_dz', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-eg' => array('language' => 'Arabic (Egypt)', 'locale' => 'ar_eg', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-iq' => array('language' => 'Arabic (Iraq)', 'locale' => 'ar_iq', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-jo' => array('language' => 'Arabic (Jordan)', 'locale' => 'ar_jo', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-kw' => array('language' => 'Arabic (Kuwait)', 'locale' => 'ar_kw', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-ly' => array('language' => 'Arabic (Libya)', 'locale' => 'ar_ly', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-ma' => array('language' => 'Arabic (Morocco)', 'locale' => 'ar_ma', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-om' => array('language' => 'Arabic (Oman)', 'locale' => 'ar_om', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-qa' => array('language' => 'Arabic (Qatar)', 'locale' => 'ar_qa', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-sa' => array('language' => 'Arabic (Saudi Arabia)', 'locale' => 'ar_sa', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-sy' => array('language' => 'Arabic (Syria)', 'locale' => 'ar_sy', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-tn' => array('language' => 'Arabic (Tunisia)', 'locale' => 'ar_tn', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'ar-ye' => array('language' => 'Arabic (Yemen)', 'locale' => 'ar_ye', 'localeFallback' => 'ara', 'charset' => 'utf-8'),
+ 'be' => array('language' => 'Byelorussian', 'locale' => 'bel', 'localeFallback' => 'bel', 'charset' => 'utf-8'),
+ 'bg' => array('language' => 'Bulgarian', 'locale' => 'bul', 'localeFallback' => 'bul', 'charset' => 'utf-8'),
+ 'bs' => array('language' => 'Bosnian', 'locale' => 'bos', 'localeFallback' => 'bos', 'charset' => 'utf-8'),
+ 'ca' => array('language' => 'Catalan', 'locale' => 'cat', 'localeFallback' => 'cat', 'charset' => 'utf-8'),
+ 'cs' => array('language' => 'Czech', 'locale' => 'cze', 'localeFallback' => 'cze', 'charset' => 'utf-8'),
+ 'da' => array('language' => 'Danish', 'locale' => 'dan', 'localeFallback' => 'dan', 'charset' => 'utf-8'),
+ 'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8'),
+ 'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8'),
+ 'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8'),
+ 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8'),
+ 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8'),
+ 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8'),
+ 'e' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8'),
+ 'el' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8'),
+ 'en' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-au' => array('language' => 'English (Australian)', 'locale' => 'en_au', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-bz' => array('language' => 'English (Belize)', 'locale' => 'en_bz', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-ca' => array('language' => 'English (Canadian)', 'locale' => 'en_ca', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-gb' => array('language' => 'English (British)', 'locale' => 'en_gb', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-ie' => array('language' => 'English (Ireland)', 'locale' => 'en_ie', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-jm' => array('language' => 'English (Jamaica)', 'locale' => 'en_jm', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-tt' => array('language' => 'English (Trinidad)', 'locale' => 'en_tt', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-us' => array('language' => 'English (United States)', 'locale' => 'en_us', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'en-za' => array('language' => 'English (South Africa)', 'locale' => 'en_za', 'localeFallback' => 'eng', 'charset' => 'utf-8'),
+ 'es' => array('language' => 'Spanish (Spain - Traditional)', 'locale' => 'spa', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-ar' => array('language' => 'Spanish (Argentina)', 'locale' => 'es_ar', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-bo' => array('language' => 'Spanish (Bolivia)', 'locale' => 'es_bo', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-cl' => array('language' => 'Spanish (Chile)', 'locale' => 'es_cl', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-co' => array('language' => 'Spanish (Colombia)', 'locale' => 'es_co', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-cr' => array('language' => 'Spanish (Costa Rica)', 'locale' => 'es_cr', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-ec' => array('language' => 'Spanish (Ecuador)', 'locale' => 'es_ec', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-es' => array('language' => 'Spanish (Spain)', 'locale' => 'es_es', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-gt' => array('language' => 'Spanish (Guatemala)', 'locale' => 'es_gt', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-hn' => array('language' => 'Spanish (Honduras)', 'locale' => 'es_hn', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-mx' => array('language' => 'Spanish (Mexican)', 'locale' => 'es_mx', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-ni' => array('language' => 'Spanish (Nicaragua)', 'locale' => 'es_ni', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-pa' => array('language' => 'Spanish (Panama)', 'locale' => 'es_pa', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-pe' => array('language' => 'Spanish (Peru)', 'locale' => 'es_pe', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-pr' => array('language' => 'Spanish (Puerto Rico)', 'locale' => 'es_pr', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-py' => array('language' => 'Spanish (Paraguay)', 'locale' => 'es_py', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-sv' => array('language' => 'Spanish (El Salvador)', 'locale' => 'es_sv', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-uy' => array('language' => 'Spanish (Uruguay)', 'locale' => 'es_uy', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'es-ve' => array('language' => 'Spanish (Venezuela)', 'locale' => 'es_ve', 'localeFallback' => 'spa', 'charset' => 'utf-8'),
+ 'et' => array('language' => 'Estonian', 'locale' => 'est', 'localeFallback' => 'est', 'charset' => 'utf-8'),
+ 'eu' => array('language' => 'Basque', 'locale' => 'baq', 'localeFallback' => 'baq', 'charset' => 'utf-8'),
+ 'fa' => array('language' => 'Farsi', 'locale' => 'per', 'localeFallback' => 'per', 'charset' => 'utf-8'),
+ 'fi' => array('language' => 'Finnish', 'locale' => 'fin', 'localeFallback' => 'fin', 'charset' => 'utf-8'),
+ 'fo' => array('language' => 'Faeroese', 'locale' => 'fao', 'localeFallback' => 'fao', 'charset' => 'utf-8'),
+ 'fr' => array('language' => 'French (Standard)', 'locale' => 'fre', 'localeFallback' => 'fre', 'charset' => 'utf-8'),
+ 'fr-be' => array('language' => 'French (Belgium)', 'locale' => 'fr_be', 'localeFallback' => 'fre', 'charset' => 'utf-8'),
+ 'fr-ca' => array('language' => 'French (Canadian)', 'locale' => 'fr_ca', 'localeFallback' => 'fre', 'charset' => 'utf-8'),
+ 'fr-ch' => array('language' => 'French (Swiss)', 'locale' => 'fr_ch', 'localeFallback' => 'fre', 'charset' => 'utf-8'),
+ 'fr-fr' => array('language' => 'French (France)', 'locale' => 'fr_fr', 'localeFallback' => 'fre', 'charset' => 'utf-8'),
+ 'fr-lu' => array('language' => 'French (Luxembourg)', 'locale' => 'fr_lu', 'localeFallback' => 'fre', 'charset' => 'utf-8'),
+ 'ga' => array('language' => 'Irish', 'locale' => 'gle', 'localeFallback' => 'gle', 'charset' => 'utf-8'),
+ 'gd' => array('language' => 'Gaelic (Scots)', 'locale' => 'gla', 'localeFallback' => 'gla', 'charset' => 'utf-8'),
+ 'gd-ie' => array('language' => 'Gaelic (Irish)', 'locale' => 'gd_ie', 'localeFallback' => 'gla', 'charset' => 'utf-8'),
+ 'gl' => array('language' => 'Galician', 'locale' => 'glg', 'localeFallback' => 'glg', 'charset' => 'utf-8'),
+ 'he' => array('language' => 'Hebrew', 'locale' => 'heb', 'localeFallback' => 'heb', 'charset' => 'utf-8'),
+ 'hi' => array('language' => 'Hindi', 'locale' => 'hin', 'localeFallback' => 'hin', 'charset' => 'utf-8'),
+ 'hr' => array('language' => 'Croatian', 'locale' => 'hrv', 'localeFallback' => 'hrv', 'charset' => 'utf-8'),
+ 'hu' => array('language' => 'Hungarian', 'locale' => 'hun', 'localeFallback' => 'hun', 'charset' => 'utf-8'),
+ 'hy' => array('language' => 'Armenian - Armenia', 'locale' => 'hye', 'localeFallback' => 'hye', 'charset' => 'utf-8'),
+ 'id' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8'),
+ 'in' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8'),
+ 'is' => array('language' => 'Icelandic', 'locale' => 'ice', 'localeFallback' => 'ice', 'charset' => 'utf-8'),
+ 'it' => array('language' => 'Italian', 'locale' => 'ita', 'localeFallback' => 'ita', 'charset' => 'utf-8'),
+ 'it-ch' => array('language' => 'Italian (Swiss) ', 'locale' => 'it_ch', 'localeFallback' => 'ita', 'charset' => 'utf-8'),
+ 'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8'),
+ 'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr'),
+ 'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr'),
+ 'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr'),
+ 'koi8-r' => array('language' => 'Russian', 'locale' => 'koi8_r', 'localeFallback' => 'rus', 'charset' => 'koi8-r'),
+ 'lt' => array('language' => 'Lithuanian', 'locale' => 'lit', 'localeFallback' => 'lit', 'charset' => 'utf-8'),
+ 'lv' => array('language' => 'Latvian', 'locale' => 'lav', 'localeFallback' => 'lav', 'charset' => 'utf-8'),
+ 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mk', 'localeFallback' => 'mac', 'charset' => 'utf-8'),
+ 'mk-mk' => array('language' => 'Macedonian', 'locale' => 'mk_mk', 'localeFallback' => 'mac', 'charset' => 'utf-8'),
+ 'ms' => array('language' => 'Malaysian', 'locale' => 'may', 'localeFallback' => 'may', 'charset' => 'utf-8'),
+ 'mt' => array('language' => 'Maltese', 'locale' => 'mlt', 'localeFallback' => 'mlt', 'charset' => 'utf-8'),
+ 'n' => array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8'),
+ 'nb' => array('language' => 'Norwegian Bokmal', 'locale' => 'nob', 'localeFallback' => 'nor', 'charset' => 'utf-8'),
+ 'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'dut', 'localeFallback' => 'dut', 'charset' => 'utf-8'),
+ 'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'dut', 'charset' => 'utf-8'),
+ 'nn' => array('language' => 'Norwegian Nynorsk', 'locale' => 'nno', 'localeFallback' => 'nor', 'charset' => 'utf-8'),
+ 'no' => array('language' => 'Norwegian', 'locale' => 'nor', 'localeFallback' => 'nor', 'charset' => 'utf-8'),
+ 'p' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8'),
+ 'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8'),
+ 'pt' => array('language' => 'Portuguese (Portugal)', 'locale' => 'por', 'localeFallback' => 'por', 'charset' => 'utf-8'),
+ 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8'),
+ 'rm' => array('language' => 'Rhaeto-Romanic', 'locale' => 'roh', 'localeFallback' => 'roh', 'charset' => 'utf-8'),
+ 'ro' => array('language' => 'Romanian', 'locale' => 'rum', 'localeFallback' => 'rum', 'charset' => 'utf-8'),
+ 'ru' => array('language' => 'Russian', 'locale' => 'rus', 'localeFallback' => 'rus', 'charset' => 'utf-8'),
+ 'ro-mo' => array('language' => 'Romanian (Moldavia)', 'locale' => 'ro_mo', 'localeFallback' => 'rum', 'charset' => 'utf-8'),
+ 'ru-mo' => array('language' => 'Russian (Moldavia)', 'locale' => 'ru_mo', 'localeFallback' => 'rus', 'charset' => 'utf-8'),
+ 'sb' => array('language' => 'Sorbian', 'locale' => 'wen', 'localeFallback' => 'wen', 'charset' => 'utf-8'),
+ 'sk' => array('language' => 'Slovak', 'locale' => 'slo', 'localeFallback' => 'slo', 'charset' => 'utf-8'),
+ 'sl' => array('language' => 'Slovenian', 'locale' => 'slv', 'localeFallback' => 'slv', 'charset' => 'utf-8'),
+ 'sq' => array('language' => 'Albanian', 'locale' => 'alb', 'localeFallback' => 'alb', 'charset' => 'utf-8'),
+ 'sr' => array('language' => 'Serbian', 'locale' => 'scc', 'localeFallback' => 'scc', 'charset' => 'utf-8'),
+ 'sv' => array('language' => 'Swedish', 'locale' => 'swe', 'localeFallback' => 'swe', 'charset' => 'utf-8'),
+ 'sv-fi' => array('language' => 'Swedish (Findland)', 'locale' => 'sv_fi', 'localeFallback' => 'swe', 'charset' => 'utf-8'),
+ 'sx' => array('language' => 'Sutu', 'locale' => 'sx', 'localeFallback' => 'sx', 'charset' => 'utf-8'),
+ 'sz' => array('language' => 'Sami (Lappish)', 'locale' => 'smi', 'localeFallback' => 'smi', 'charset' => 'utf-8'),
+ 'th' => array('language' => 'Thai', 'locale' => 'tha', 'localeFallback' => 'tha', 'charset' => 'utf-8'),
+ 'tn' => array('language' => 'Tswana', 'locale' => 'tsn', 'localeFallback' => 'tsn', 'charset' => 'utf-8'),
+ 'tr' => array('language' => 'Turkish', 'locale' => 'tur', 'localeFallback' => 'tur', 'charset' => 'utf-8'),
+ 'ts' => array('language' => 'Tsonga', 'locale' => 'tso', 'localeFallback' => 'tso', 'charset' => 'utf-8'),
+ 'uk' => array('language' => 'Ukrainian', 'locale' => 'ukr', 'localeFallback' => 'ukr', 'charset' => 'utf-8'),
+ 'ur' => array('language' => 'Urdu', 'locale' => 'urd', 'localeFallback' => 'urd', 'charset' => 'utf-8'),
+ 've' => array('language' => 'Venda', 'locale' => 'ven', 'localeFallback' => 'ven', 'charset' => 'utf-8'),
+ 'vi' => array('language' => 'Vietnamese', 'locale' => 'vie', 'localeFallback' => 'vie', 'charset' => 'utf-8'),
+ 'xh' => array('language' => 'Xhosa', 'locale' => 'xho', 'localeFallback' => 'xho', 'charset' => 'utf-8'),
+ 'yi' => array('language' => 'Yiddish', 'locale' => 'yid', 'localeFallback' => 'yid', 'charset' => 'utf-8'),
+ 'zh' => array('language' => 'Chinese', 'locale' => 'chi', 'localeFallback' => 'chi', 'charset' => 'utf-8'),
+ 'zh-cn' => array('language' => 'Chinese (PRC)', 'locale' => 'zh_cn', 'localeFallback' => 'chi', 'charset' => 'GB2312'),
+ 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'chi', 'charset' => 'utf-8'),
+ 'zh-sg' => array('language' => 'Chinese (Singapore)', 'locale' => 'zh_sg', 'localeFallback' => 'chi', 'charset' => 'utf-8'),
+ 'zh-tw' => array('language' => 'Chinese (Taiwan)', 'locale' => 'zh_tw', 'localeFallback' => 'chi', 'charset' => 'utf-8'),
+ 'zu' => array('language' => 'Zulu', 'locale' => 'zul', 'localeFallback' => 'zul', 'charset' => 'utf-8'));
+/**
+ * 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 I10n::__autoLanguage(); if this fails
+ * the method will get the settings from I10n::__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 = 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'];
+ } 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 = 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
+ */
+ 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];
+ }
+ return false;
+ }
+ return $this->__l10nCatalog;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/magic_db.php b/cake/libs/magic_db.php
new file mode 100755
index 00000000..73b16017
--- /dev/null
+++ b/cake/libs/magic_db.php
@@ -0,0 +1,297 @@
+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;
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/model/app_model.php b/cake/libs/model/app_model.php
new file mode 100755
index 00000000..120aa563
--- /dev/null
+++ b/cake/libs/model/app_model.php
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/cake/libs/model/behavior.php b/cake/libs/model/behavior.php
new file mode 100755
index 00000000..fdbc64e3
--- /dev/null
+++ b/cake/libs/model/behavior.php
@@ -0,0 +1,494 @@
+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 boolean True if the operation should continue, false if it should abort
+ * @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 Result of the find operation
+ * @access public
+ */
+ function afterFind(&$model, $results, $primary) { }
+/**
+ * Before validate callback
+ *
+ * @param object $model Model using this behavior
+ * @return boolean True if validate operation should continue, false to abort
+ * @access public
+ */
+ function beforeValidate(&$model) { }
+/**
+ * Before save callback
+ *
+ * @param object $model Model using this behavior
+ * @return boolean True if the operation should continue, false if it should abort
+ * @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 boolean True if the operation should continue, false if it should abort
+ * @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:
+ array_unshift($params, $model);
+ 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()) {
+ $name = $behavior;
+ if (strpos($behavior, '.')) {
+ list($plugin, $name) = explode('.', $behavior, 2);
+ }
+ $class = $name . 'Behavior';
+
+ if (!App::import('Behavior', $behavior)) {
+ 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});
+ }
+ } 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) {
+ 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("BehaviorCollection::dispatchMethod() - Method {$method} not found in any attached behavior", 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;
+ }
+ $_params = $params;
+ $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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/behaviors/acl.php b/cake/libs/model/behaviors/acl.php
new file mode 100755
index 00000000..0b867a2c
--- /dev/null
+++ b/cake/libs/model/behaviors/acl.php
@@ -0,0 +1,119 @@
+ '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);
+
+ $type = $this->__typeMaps[$this->settings[$model->name]['type']];
+ if (!class_exists('AclNode')) {
+ uses('model' . DS . 'db_acl');
+ }
+ $model->{$type} =& ClassRegistry::init($type);
+ if (!method_exists($model, 'parentNode')) {
+ trigger_error("Callback parentNode() not defined in {$model->alias}", E_USER_WARNING);
+ }
+ }
+/**
+ * Retrieves the Aro/Aco node for this model
+ *
+ * @param mixed $ref
+ * @return array
+ * @access public
+ */
+ function node(&$model, $ref = null) {
+ $type = $this->__typeMaps[strtolower($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) {
+ if ($created) {
+ $type = $this->__typeMaps[strtolower($this->settings[$model->name]['type'])];
+ $parent = $model->parentNode();
+ if (!empty($parent)) {
+ $parent = $this->node($model, $parent);
+ } else {
+ $parent = null;
+ }
+
+ $model->{$type}->create();
+ $model->{$type}->save(array(
+ 'parent_id' => Set::extract($parent, "0.{$type}.id"),
+ 'model' => $model->name,
+ 'foreign_key' => $model->id
+ ));
+ }
+ }
+/**
+ * Destroys the ARO/ACO node bound to the deleted record
+ *
+ * @return void
+ * @access public
+ */
+ function afterDelete(&$model) {
+ $type = $this->__typeMaps[strtolower($this->settings[$model->name]['type'])];
+ $node = Set::extract($this->node($model), "0.{$type}.id");
+ if (!empty($node)) {
+ $model->{$type}->delete($node);
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/model/behaviors/containable.php b/cake/libs/model/behaviors/containable.php
new file mode 100755
index 00000000..a602efff
--- /dev/null
+++ b/cake/libs/model/behaviors/containable.php
@@ -0,0 +1,429 @@
+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) {
+ $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] = $instance->{$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'];
+ if (!empty($Model->belongsTo)) {
+ foreach ($Model->belongsTo as $assoc => $data) {
+ if (!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);
+ $field = $modelName . '.' . (($field === '--primaryKey--') ? $Model->$modelName->primaryKey : $field);
+ }
+ $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
+ */
+ 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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/behaviors/translate.php b/cake/libs/model/behaviors/translate.php
new file mode 100755
index 00000000..7e959f50
--- /dev/null
+++ b/cake/libs/model/behaviors/translate.php
@@ -0,0 +1,512 @@
+ 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 array $config
+ * @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);
+ }
+/**
+ * Callback
+ *
+ * @return void
+ * @access public
+ */
+ function cleanup(&$model) {
+ $this->unbindTranslation($model);
+ unset($this->settings[$model->alias]);
+ unset($this->runtime[$model->alias]);
+ }
+/**
+ * beforeFind Callback
+ *
+ * @param array $query
+ * @return array Modified query
+ * @access public
+ */
+ function beforeFind(&$model, $query) {
+ $locale = $this->_getLocale($model);
+ if (empty($locale)) {
+ return $query;
+ }
+ $db =& ConnectionManager::getDataSource($model->useDbConfig);
+ $tablePrefix = $db->config['prefix'];
+ $RuntimeModel =& $this->translateModel($model);
+
+ 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 array $results
+ * @param boolean $primary
+ * @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
+ *
+ * @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 boolean $created
+ * @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
+ *
+ * @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
+ *
+ * @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
+ *
+ * @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');
+ }
+ return $this->runtime[$model->alias]['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 instance of model
+ * @param mixed 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)) {
+ 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';
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/behaviors/tree.php b/cake/libs/model/behaviors/tree.php
new file mode 100755
index 00000000..da23c193
--- /dev/null
+++ b/cake/libs/model/behaviors/tree.php
@@ -0,0 +1,941 @@
+ '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]);
+
+ if (isset($Model->data[$Model->alias][$Model->primaryKey])) {
+ if ($Model->data[$Model->alias][$Model->primaryKey]) {
+ if (!$Model->id) {
+ $Model->id = $Model->data[$Model->alias][$Model->primaryKey];
+ }
+ }
+ unset($Model->data[$Model->alias][$Model->primaryKey]);
+ }
+
+ $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 {
+ list($node) = array_values($Model->find('first', array(
+ 'conditions' => array($scope,$Model->escapeField() => $Model->id),
+ 'fields' => array($Model->primaryKey, $parent, $left, $right ), 'recursive' => $recursive)
+ ));
+
+ $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
+ */
+ 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
+ */
+ 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($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
+ */
+ 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
+ */
+ 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->read($parent, $id);
+
+ 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
+ */
+ 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 mixed $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
+ */
+ 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 mixed $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
+ */
+ 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
+ */
+ 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
+ */
+ 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);
+
+ 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'));
+ }
+ }
+ }
+ 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
+ */
+ 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($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
+ */
+ 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 {
+ $parentNode = array_values($Model->find('first', array(
+ 'conditions' => array($scope, $Model->escapeField() => $parentId),
+ 'fields' => array($Model->primaryKey, $left, $right),
+ 'recursive' => $recursive
+ )));
+
+ 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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/connection_manager.php b/cake/libs/model/connection_manager.php
new file mode 100755
index 00000000..32f16984
--- /dev/null
+++ b/cake/libs/model/connection_manager.php
@@ -0,0 +1,263 @@
+config =& new DATABASE_CONFIG();
+ }
+ }
+/**
+ * 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;
+ }
+
+ $connections = $_this->enumConnectionObjects();
+ if (!empty($connections[$name])) {
+ $conn = $connections[$name];
+ $class = $conn['classname'];
+ $_this->loadDataSource($name);
+ $_this->_dataSources[$name] =& new $class($_this->config->{$name});
+ $_this->_dataSources[$name]->configKeyName = $name;
+ } else {
+ trigger_error(sprintf(__("ConnectionManager::getDataSource - Non-existent data source %s", true), $name), E_USER_ERROR);
+ return null;
+ }
+
+ $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
+ *
+ * @param object $source DataSource object
+ * @return string Datasource name
+ * @access public
+ * @static
+ */
+ function getSourceName(&$source) {
+ $_this =& ConnectionManager::getInstance();
+ $names = array_keys($_this->_dataSources);
+ for ($i = 0; $i < count($names); $i++) {
+ if ($_this->_dataSources[$names[$i]] === $source) {
+ return $names[$i];
+ }
+ }
+ return null;
+ }
+/**
+ * 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 {
+ $connections = $_this->enumConnectionObjects();
+ $conn = $connections[$connName];
+ }
+
+ if (!empty($conn['parent'])) {
+ $_this->loadDataSource($conn['parent']);
+ }
+
+ if (class_exists($conn['classname'])) {
+ return false;
+ }
+
+ if (file_exists(MODELS . 'datasources' . DS . $conn['filename'] . '.php')) {
+ require (MODELS . 'datasources' . DS . $conn['filename'] . '.php');
+ } elseif (fileExistsInPath(LIBS . 'model' . DS . 'datasources' . DS . $conn['filename'] . '.php')) {
+ require (LIBS . 'model' . DS . 'datasources' . DS . $conn['filename'] . '.php');
+ } else {
+ $error = __('Unable to load DataSource file %s.php', true);
+ trigger_error(sprintf($error, $conn['filename']), E_USER_ERROR);
+ return null;
+ }
+ }
+/**
+ * Gets a list of class and file names associated with the user-defined DataSource 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();
+
+ if (!empty($_this->_connectionsEnum)) {
+ return $_this->_connectionsEnum;
+ }
+ $connections = get_object_vars($_this->config);
+
+ if ($connections != null) {
+ foreach ($connections as $name => $config) {
+ $_this->_connectionsEnum[$name] = $_this->__getDriver($config);
+ }
+ return $_this->_connectionsEnum;
+ } else {
+ $_this->cakeError('missingConnection', array(array('className' => 'ConnectionManager')));
+ }
+ }
+/**
+ * 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->__getDriver($config);
+ $return =& $_this->getDataSource($name);
+ return $return;
+ }
+/**
+ * Returns the file, class name, and parent for the given driver.
+ *
+ * @return array An indexed array with: filename, classname, and parent
+ * @access private
+ */
+ function __getDriver($config) {
+ if (!isset($config['datasource'])) {
+ $config['datasource'] = 'dbo';
+ }
+
+ if (isset($config['driver']) && $config['driver'] != null && !empty($config['driver'])) {
+ $filename = $config['datasource'] . DS . $config['datasource'] . '_' . $config['driver'];
+ $classname = Inflector::camelize(strtolower($config['datasource'] . '_' . $config['driver']));
+ $parent = $this->__getDriver(array('datasource' => $config['datasource']));
+ } else {
+ $filename = $config['datasource'] . '_source';
+ $classname = Inflector::camelize(strtolower($config['datasource'] . '_source'));
+ $parent = null;
+ }
+ return array('filename' => $filename, 'classname' => $classname, 'parent' => $parent);
+ }
+/**
+ * Destructor.
+ *
+ * @access private
+ */
+ function __destruct() {
+ if (Configure::read('Session.save') == 'database' && function_exists('session_write_close')) {
+ session_write_close();
+ }
+ }
+}
+?>
diff --git a/cake/libs/model/datasources/datasource.php b/cake/libs/model/datasources/datasource.php
new file mode 100755
index 00000000..b1b2baef
--- /dev/null
+++ b/cake/libs/model/datasources/datasource.php
@@ -0,0 +1,510 @@
+6000 queries on one system.
+ *
+ * @var int Maximum number of queries in the queries log.
+ * @access protected
+ */
+ var $_queriesLogMax = 200;
+/**
+ * Caches serialzed results of executed queries
+ *
+ * @var array Maximum number of queries in the queries log.
+ * @access protected
+ */
+ var $_queryCache = array();
+/**
+ * The default configuration of a specific DataSource
+ *
+ * @var array
+ * @access protected
+ */
+ var $_baseConfig = array();
+/**
+ * Holds references to descriptions loaded by the DataSource
+ *
+ * @var array
+ * @access private
+ */
+ var $__descriptions = array();
+/**
+ * Holds a list of sources (tables) contained in the DataSource
+ *
+ * @var array
+ * @access protected
+ */
+ var $_sources = null;
+/**
+ * A reference to the physical connection of this DataSource
+ *
+ * @var array
+ * @access public
+ */
+ var $connection = null;
+/**
+ * The DataSource configuration
+ *
+ * @var array
+ * @access public
+ */
+ var $config = array();
+/**
+ * The DataSource configuration key name
+ *
+ * @var string
+ * @access public
+ */
+ var $configKeyName = null;
+/**
+ * Whether or not this DataSource is in the middle of a transaction
+ *
+ * @var boolean
+ * @access protected
+ */
+ var $_transactionStarted = false;
+/**
+ * Whether or not source data like available tables and schema descriptions
+ * should be cached
+ *
+ * @var boolean
+ */
+ var $cacheSources = true;
+/**
+ * Constructor.
+ */
+ function __construct($config = array()) {
+ parent::__construct();
+ $this->setConfig($config);
+ }
+/**
+ * Caches/returns cached results for child instances
+ *
+ * @return array
+ */
+ 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.
+ *
+ * @return array
+ */
+ 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 mixed
+ */
+ function describe($model) {
+ if ($this->cacheSources === false) {
+ return null;
+ }
+ $table = $this->fullTableName($model, false);
+ 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
+ */
+ function begin(&$model) {
+ return !$this->_transactionStarted;
+ }
+/**
+ * Commit a transaction
+ *
+ * @return boolean Returns true if a transaction is in progress
+ */
+ function commit(&$model) {
+ return $this->_transactionStarted;
+ }
+/**
+ * Rollback a transaction
+ *
+ * @return boolean Returns true if a transaction is in progress
+ */
+ 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")
+ */
+ function column($real) {
+ return false;
+ }
+/**
+ * To-be-overridden in subclasses.
+ *
+ * @param unknown_type $model
+ * @param unknown_type $fields
+ * @param unknown_type $values
+ * @return unknown
+ */
+ function create(&$model, $fields = null, $values = null) {
+ return false;
+ }
+/**
+ * To-be-overridden in subclasses.
+ *
+ * @param unknown_type $model
+ * @param unknown_type $queryData
+ * @return unknown
+ */
+ function read(&$model, $queryData = array()) {
+ return false;
+ }
+/**
+ * To-be-overridden in subclasses.
+ *
+ * @param unknown_type $model
+ * @param unknown_type $fields
+ * @param unknown_type $values
+ * @return unknown
+ */
+ function update(&$model, $fields = null, $values = null) {
+ return false;
+ }
+/**
+ * To-be-overridden in subclasses.
+ *
+ * @param unknown_type $model
+ * @param unknown_type $id
+ */
+ 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 in
+ */
+ function lastInsertId($source = null) {
+ return false;
+ }
+/**
+ * Returns the ID generated from the previous INSERT operation.
+ *
+ * @param unknown_type $source
+ * @return in
+ */
+ function lastNumRows($source = null) {
+ return false;
+ }
+/**
+ * Returns the ID generated from the previous INSERT operation.
+ *
+ * @param unknown_type $source
+ * @return in
+ */
+ function lastAffected($source = null) {
+ return false;
+ }
+/**
+ * Returns true if the DataSource supports the given interface (method)
+ *
+ * @param string $interface The name of the interface (method)
+ * @return boolean True on success
+ */
+ function isInterfaceSupported($interface) {
+ $methods = get_class_methods(get_class($this));
+ $methods = strtolower(implode('|', $methods));
+ $methods = explode('|', $methods);
+ $return = in_array(strtolower($interface), $methods);
+ return $return;
+ }
+/**
+ * Sets the configuration for the DataSource
+ *
+ * @param array $config The configuration array
+ * @return void
+ */
+ 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
+ */
+ 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;
+ }
+/**
+ * Enter description here...
+ *
+ * @param unknown_type $query
+ * @param unknown_type $data
+ * @param unknown_type $association
+ * @param unknown_type $assocData
+ * @param Model $model
+ * @param Model $linkModel
+ * @param array $stack
+ * @return unknown
+ */
+ function insertQueryData($query, $data, $association, $assocData, &$model, &$linkModel, $stack) {
+ $keys = array('{$__cakeID__$}', '{$__cakeForeignKey__$}');
+
+ foreach ($keys as $key) {
+ $val = 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 = '';
+ }
+ }
+ 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'];
+
+ 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, $model->getColumnType($model->primaryKey)), $query);
+ }
+ }
+ return $query;
+ }
+/**
+ * To-be-overridden in subclasses.
+ *
+ * @param unknown_type $model
+ * @param unknown_type $key
+ * @return unknown
+ */
+ function resolveKey($model, $key) {
+ return $model->alias . $key;
+ }
+/**
+ * Closes the current datasource.
+ *
+ */
+ function __destruct() {
+ if ($this->_transactionStarted) {
+ $null = null;
+ $this->rollback($null);
+ }
+ if ($this->connected) {
+ $this->close();
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_adodb.php b/cake/libs/model/datasources/dbo/dbo_adodb.php
new file mode 100755
index 00000000..53d6f960
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_adodb.php
@@ -0,0 +1,516 @@
+ 'adodb' . DS . 'adodb.inc.php'));
+/**
+ * AdoDB DBO implementation.
+ *
+ * Database abstraction implementation for the AdoDB library.
+ *
+ * @package cake
+ * @subpackage cake.cake.libs.model.datasources.dbo
+ */
+class DboAdodb extends DboSource {
+/**
+ * Enter description here...
+ *
+ * @var string
+ */
+ var $description = "ADOdb DBO Driver";
+/**
+ * ADOConnection object with which we connect.
+ *
+ * @var ADOConnection The connection object.
+ * @access private
+ */
+ var $_adodb = null;
+/**
+ * Array translating ADOdb column MetaTypes to cake-supported metatypes
+ *
+ * @var array
+ * @access private
+ */
+ var $_adodbColumnTypes = array(
+ 'string' => 'C',
+ 'text' => 'X',
+ 'date' => 'D',
+ 'timestamp' => 'T',
+ 'time' => 'T',
+ 'datetime' => 'T',
+ 'boolean' => 'L',
+ 'float' => 'N',
+ 'integer' => 'I',
+ 'binary' => 'R',
+ );
+/**
+ * ADOdb column definition
+ *
+ * @var array
+ */
+ var $columns = array(
+ 'primary_key' => array('name' => 'R', 'limit' => 11),
+ 'string' => array('name' => 'C', 'limit' => '255'),
+ 'text' => array('name' => 'X'),
+ 'integer' => array('name' => 'I', 'limit' => '11', 'formatter' => 'intval'),
+ 'float' => array('name' => 'N', 'formatter' => 'floatval'),
+ 'timestamp' => array('name' => 'T', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
+ 'time' => array('name' => 'T', 'format' => 'H:i:s', 'formatter' => 'date'),
+ 'datetime' => array('name' => 'T', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
+ 'date' => array('name' => 'D', 'format' => 'Y-m-d', 'formatter' => 'date'),
+ 'binary' => array('name' => 'B'),
+ 'boolean' => array('name' => 'L', 'limit' => '1')
+ );
+/**
+ * Connects to the database using options in the given configuration array.
+ *
+ * @param array $config Configuration array for connecting
+ */
+ function connect() {
+ $config = $this->config;
+ $persistent = strrpos($config['connect'], '|p');
+
+ if ($persistent === false) {
+ $adodb_driver = $config['connect'];
+ $connect = 'Connect';
+ } else {
+ $adodb_driver = substr($config['connect'], 0, $persistent);
+ $connect = 'PConnect';
+ }
+
+ $this->_adodb = NewADOConnection($adodb_driver);
+
+ $this->_adodbDataDict = NewDataDictionary($this->_adodb, $adodb_driver);
+
+ $this->startQuote = $this->_adodb->nameQuote;
+ $this->endQuote = $this->_adodb->nameQuote;
+
+ $this->connected = $this->_adodb->$connect($config['host'], $config['login'], $config['password'], $config['database']);
+ $this->_adodbMetatyper = &$this->_adodb->execute('Select 1');
+ return $this->connected;
+ }
+/**
+ * Disconnects from database.
+ *
+ * @return boolean True if the database could be disconnected, else false
+ */
+ function disconnect() {
+ return $this->_adodb->Close();
+ }
+/**
+ * Executes given SQL statement.
+ *
+ * @param string $sql SQL statement
+ * @return resource Result resource identifier
+ */
+ function _execute($sql) {
+ global $ADODB_FETCH_MODE;
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+ return $this->_adodb->execute($sql);
+ }
+/**
+ * Returns a row from current resultset as an array .
+ *
+ * @return array The fetched row as an array
+ */
+ function fetchRow($sql = null) {
+ if (!empty($sql) && is_string($sql) && strlen($sql) > 5) {
+ if (!$this->execute($sql)) {
+ return null;
+ }
+ }
+
+ if (!$this->hasResult()) {
+ return null;
+ } else {
+ $resultRow = $this->_result->FetchRow();
+ $this->resultSet($resultRow);
+ return $this->fetchResult();
+ }
+ }
+/**
+ * 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(&$model) {
+ if (parent::begin($model)) {
+ if ($this->_adodb->BeginTrans()) {
+ $this->_transactionStarted = true;
+ return true;
+ }
+ }
+ return false;
+ }
+/**
+ * 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(&$model) {
+ if (parent::commit($model)) {
+ $this->_transactionStarted = false;
+ return $this->_adodb->CommitTrans();
+ }
+ return false;
+ }
+/**
+ * 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(&$model) {
+ if (parent::rollback($model)) {
+ return $this->_adodb->RollbackTrans();
+ }
+ return false;
+ }
+/**
+ * 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() {
+ $tables = $this->_adodb->MetaTables('TABLES');
+
+ if (!sizeof($tables) > 0) {
+ trigger_error(ERROR_NO_TABLE_LIST, E_USER_NOTICE);
+ exit;
+ }
+ return $tables;
+ }
+/**
+ * Returns an array of the fields in the table used by the given model.
+ *
+ * @param AppModel $model Model object
+ * @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->_adodb->MetaColumns($this->fullTableName($model, false));
+
+ foreach ($cols as $column) {
+ $fields[$column->name] = array(
+ 'type' => $this->column($column->type),
+ 'null' => !$column->not_null,
+ 'length' => $column->max_length,
+ );
+ if ($column->has_default) {
+ $fields[$column->name]['default'] = $column->default_value;
+ }
+ if ($column->primary_key == 1) {
+ $fields[$column->name]['key'] = 'primary';
+ }
+ }
+
+ $this->__cacheDescription($this->fullTableName($model, false), $fields);
+ return $fields;
+ }
+/**
+ * Returns a formatted error message from previous database operation.
+ *
+ * @return string Error message
+ */
+ function lastError() {
+ return $this->_adodb->ErrorMsg();
+ }
+/**
+ * Returns number of affected rows in previous database operation, or false if no previous operation exists.
+ *
+ * @return integer Number of affected rows
+ */
+ function lastAffected() {
+ return $this->_adodb->Affected_Rows();
+ }
+/**
+ * Returns number of rows in previous resultset, or false if no previous resultset exists.
+ *
+ * @return integer Number of rows in resultset
+ */
+ function lastNumRows() {
+ return $this->_result ? $this->_result->RecordCount() : false;
+ }
+/**
+ * Returns the ID generated from the previous INSERT operation.
+ *
+ * @return int
+ *
+ * @Returns the last autonumbering ID inserted. Returns false if function not supported.
+ */
+ function lastInsertId() {
+ return $this->_adodb->Insert_ID();
+ }
+/**
+ * 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
+ * @todo Please change output string to whatever select your database accepts. adodb doesn't allow us to get the correct limit string out of it.
+ */
+ 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;
+ // please change to whatever select your database accepts
+ // adodb doesn't allow us to get the correct limit string out of it
+ }
+/**
+ * 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) {
+ $metaTypes = array_flip($this->_adodbColumnTypes);
+
+ $interpreted_type = $this->_adodbMetatyper->MetaType($real);
+
+ if (!isset($metaTypes[$interpreted_type])) {
+ return 'text';
+ }
+ return $metaTypes[$interpreted_type];
+ }
+/**
+ * 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_type The type of 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 ($data === '') {
+ return "''";
+ }
+ return $this->_adodb->qstr($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 && $fields[0] != '*' && strpos($fields[0], 'COUNT(*)') === false) {
+ for ($i = 0; $i < $count; $i++) {
+ if (!preg_match('/^.+\\(.*\\)/', $fields[$i]) && !preg_match('/\s+AS\s+/', $fields[$i])) {
+ $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]);
+ }
+ }
+ }
+ }
+ return $fields;
+ }
+/**
+ * Build ResultSets and map data
+ *
+ * @param array $results
+ */
+ function resultSet(&$results) {
+ $num_fields = count($results);
+ $fields = array_keys($results);
+ $this->results =& $results;
+ $this->map = array();
+ $index = 0;
+ $j = 0;
+
+ while ($j < $num_fields) {
+ $columnName = $fields[$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 (!empty($this->results)) {
+ $row = $this->results;
+ $this->results = null;
+ } else {
+ $row = $this->_result->FetchRow();
+ }
+
+ if (empty($row)) {
+ return false;
+ }
+
+ $resultRow = array();
+ $fields = array_keys($row);
+ $count = count($fields);
+ $i = 0;
+ for ($i = 0; $i < $count; $i++) { //$row as $index => $field) {
+ list($table, $column) = $this->map[$i];
+ $resultRow[$table][$column] = $row[$fields[$i]];
+ }
+ return $resultRow;
+ }
+/**
+ * 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;
+ extract(array_merge(array('null' => true), $column));
+
+ if (empty($name) || empty($type)) {
+ trigger_error('Column name or type not defined in schema', E_USER_WARNING);
+ return null;
+ }
+
+ //$metaTypes = array_flip($this->_adodbColumnTypes);
+ if (!isset($this->_adodbColumnTypes[$type])) {
+ trigger_error("Column type {$type} does not exist", E_USER_WARNING);
+ return null;
+ }
+ $metaType = $this->_adodbColumnTypes[$type];
+ $concreteType = $this->_adodbDataDict->ActualType($metaType);
+ $real = $this->columns[$type];
+
+ //UUIDs are broken so fix them.
+ if ($type == 'string' && isset($real['length']) && $real['length'] == 36) {
+ $concreteType = 'CHAR';
+ }
+
+ $out = $this->name($name) . ' ' . $concreteType;
+
+ 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 . ')';
+ }
+ $_notNull = $_default = $_autoInc = $_constraint = $_unsigned = false;
+
+ if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') {
+ $_constraint = '';
+ $_autoInc = true;
+ } elseif (isset($column['key']) && $column['key'] == 'primary') {
+ $_notNull = '';
+ } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) {
+ $_notNull = true;
+ $_default = $column['default'];
+ } elseif ( isset($column['null']) && $column['null'] == true) {
+ $_notNull = false;
+ $_default = 'NULL';
+ }
+ if (isset($column['default']) && $_default == false) {
+ $_default = $this->value($column['default']);
+ }
+ if (isset($column['null']) && $column['null'] == false) {
+ $_notNull = true;
+ }
+ //use concrete instance of DataDict to make the suffixes for us.
+ $out .= $this->_adodbDataDict->_CreateSuffix($out, $metaType, $_notNull, $_default, $_autoInc, $_constraint, $_unsigned);
+ return $out;
+
+ }
+/**
+ * Checks if the result is valid
+ *
+ * @return boolean True if the result is valid, else false
+ */
+ function hasResult() {
+ return is_object($this->_result) && !$this->_result->EOF;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_db2.php b/cake/libs/model/datasources/dbo/dbo_db2.php
new file mode 100755
index 00000000..4e65d3b4
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_db2.php
@@ -0,0 +1,564 @@
+ true,
+ 'login' => 'db2inst1',
+ 'password' => '',
+ 'database' => 'cake',
+ 'schema' => '',
+ 'hostname' => '127.0.0.1',
+ 'port' => '50001',
+ 'encoding' => 'UTF-8',
+ 'cataloged' => true,
+ 'autocommit' => true
+ );
+/**
+ * An array that maps Cake column types to database native column types.
+ * The mapped information can include a reference to a function that should
+ * be used to format the data, as well as a string that defines the
+ * formatting according to that function.
+ *
+ * @var array
+ */
+ var $columns = array(
+ 'primary_key' => array('name' => 'not null generated by default as identity (start with 1, increment by 1)'),
+ 'string' => array('name' => 'varchar', 'limit' => '255'),
+ 'text' => array('name' => 'clob'),
+ 'integer' => array('name' => 'integer', 'limit' => '10', 'formatter' => 'intval'),
+ 'float' => array('name' => 'double', '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' => 'blob'),
+ 'boolean' => array('name' => 'smallint', 'limit' => '1')
+ );
+/**
+ * A map for every result mapping tables to columns
+ *
+ * @var array result -> ( table -> column )
+ */
+ var $_resultMap = array();
+/**
+ * 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;
+ $connect = 'db2_connect';
+ if ($config['persistent']) {
+ $connect = 'db2_pconnect';
+ }
+ $this->connected = false;
+
+ if ($config['cataloged']) {
+ $this->connection = $connect($config['database'], $config['login'], $config['password']);
+ } else {
+ $connString = sprintf(
+ "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;",
+ $config['database'],
+ $config['hostname'],
+ $config['port'],
+ $config['login'],
+ $config['password']
+ );
+ $this->connection = db2_connect($connString, '', '');
+ }
+
+ if ($this->connection) {
+ $this->connected = true;
+ }
+
+ if ($config['schema'] !== '') {
+ $this->_execute('SET CURRENT SCHEMA = ' . $config['schema']);
+ }
+ return $this->connected;
+ }
+/**
+ * Disconnects from database.
+ *
+ * @return boolean True if the database could be disconnected, else false
+ */
+ function disconnect() {
+ @db2_free_result($this->results);
+ $this->connected = !@db2_close($this->connection);
+ return !$this->connected;
+ }
+/**
+ * Executes given SQL statement. We should use prepare / execute to allow the
+ * database server to reuse its access plan and increase the efficiency
+ * of your database access
+ *
+ * @param string $sql SQL statement
+ * @return resource Result resource identifier
+ * @access protected
+ */
+ function _execute($sql) {
+ // get result from db
+ $result = db2_exec($this->connection, $sql);
+
+ if (!is_bool($result)) {
+ // build table/column map for this result
+ $map = array();
+ $numFields = db2_num_fields($result);
+ $index = 0;
+ $j = 0;
+ $offset = 0;
+
+ while ($j < $numFields) {
+ $columnName = strtolower(db2_field_name($result, $j));
+ $tmp = strpos($sql, '.' . $columnName, $offset);
+ $tableName = substr($sql, $offset, ($tmp-$offset));
+ $tableName = substr($tableName, strrpos($tableName, ' ') + 1);
+ $map[$index++] = array($tableName, $columnName);
+ $j++;
+ $offset = strpos($sql, ' ', $tmp);
+ }
+
+ $this->_resultMap[$result] = $map;
+ }
+
+ return $result;
+ }
+/**
+ * Returns an array of all the tables in the database.
+ * Should call parent::listSources twice in the method:
+ * once to see if the list is cached, and once to cache
+ * the list if not.
+ *
+ * @return array Array of tablenames in the database
+ */
+ function listSources() {
+ $cache = parent::listSources();
+
+ if ($cache != null) {
+ return $cache;
+ }
+ $result = db2_tables($this->connection);
+ $tables = array();
+
+ while (db2_fetch_row($result)) {
+ $tables[] = strtolower(db2_result($result, '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;
+ }
+ $fields = array();
+ $result = db2_columns($this->connection, '', '', strtoupper($this->fullTableName($model)));
+
+ while (db2_fetch_row($result)) {
+ $fields[strtolower(db2_result($result, 'COLUMN_NAME'))] = array(
+ 'type' => $this->column(strtolower(db2_result($result, 'TYPE_NAME'))),
+ 'null' => db2_result($result, 'NULLABLE'),
+ 'default' => db2_result($result, 'COLUMN_DEF'),
+ 'length' => db2_result($result, 'COLUMN_SIZE')
+ );
+ }
+ $this->__cacheDescription($model->tablePrefix . $model->table, $fields);
+ return $fields;
+ }
+/**
+ * Returns a quoted name of $data for use in an SQL statement.
+ *
+ * @param string $data Name (table.field) to be prepared for use in an SQL statement
+ * @return string Quoted for MySQL
+ */
+ function name($data) {
+ return $data;
+ }
+/**
+ * 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
+ * @return string Quoted and escaped
+ * @todo Add logic that formats/escapes data based on column type
+ */
+ 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 'boolean':
+ $data = $this->boolean((bool)$data);
+ break;
+ case 'integer':
+ $data = intval($data);
+ break;
+ default:
+ $data = str_replace("'", "''", $data);
+ break;
+ }
+
+ if ($column == 'integer' || $column == 'float') {
+ return $data;
+ }
+ return "'" . $data . "'";
+ }
+/**
+ * Not sure about this one, MySQL needs it but does ODBC? Safer just to leave it
+ * Translates between PHP boolean values and MySQL (faked) boolean values
+ *
+ * @param mixed $data Value to be translated
+ * @return mixed Converted boolean value
+ */
+ function boolean($data) {
+ if ($data === true || $data === false) {
+ if ($data === true) {
+ return 1;
+ }
+ return 0;
+ } else {
+ if (intval($data !== 0)) {
+ return true;
+ }
+ return false;
+ }
+ }
+/**
+ * Begins a transaction. Returns true if the transaction was
+ * started successfully, otherwise false.
+ *
+ * @param unknown_type $model
+ * @return boolean True on success, false on fail
+ * (i.e. if the database/model does not support transactions).
+ */
+ function begin(&$model) {
+ if (parent::begin($model)) {
+ if (db2_autocommit($this->connection, DB2_AUTOCOMMIT_OFF)) {
+ $this->_transactionStarted = true;
+ return true;
+ }
+ }
+ return false;
+ }
+/**
+ * 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(&$model) {
+ if (parent::commit($model)) {
+ if (db2_commit($this->connection)) {
+ $this->_transactionStarted = false;
+ db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON);
+ return true;
+ }
+ }
+ return false;
+ }
+/**
+ * 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(&$model) {
+ if (parent::rollback($model)) {
+ $this->_transactionStarted = false;
+ db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON);
+ return db2_rollback($this->connection);
+ }
+ return false;
+ }
+/**
+ * Removes Identity (primary key) column from update data before returning to parent
+ *
+ * @param Model $model
+ * @param array $fields
+ * @param array $values
+ * @return array
+ */
+ function update(&$model, $fields = array(), $values = array()) {
+ foreach ($fields as $i => $field) {
+ if ($field == $model->primaryKey) {
+ unset ($fields[$i]);
+ unset ($values[$i]);
+ break;
+ }
+ }
+ return parent::update($model, $fields, $values);
+ }
+/**
+ * Returns a formatted error message from previous database operation.
+ * DB2 distinguishes between statement and connnection errors so we
+ * must check for both.
+ *
+ * @return string Error message with error number
+ */
+ function lastError() {
+ if (db2_stmt_error()) {
+ return db2_stmt_error() . ': ' . db2_stmt_errormsg();
+ } elseif (db2_conn_error()) {
+ return db2_conn_error() . ': ' . db2_conn_errormsg();
+ }
+ 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 db2_num_rows($this->_result);
+ }
+ 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 db2_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) {
+ $data = $this->fetchRow(sprintf('SELECT SYSIBM.IDENTITY_VAL_LOCAL() AS ID FROM %s FETCH FIRST ROW ONLY', $source));
+
+ if ($data && isset($data[0]['id'])) {
+ return $data[0]['id'];
+ }
+ return null;
+ }
+/**
+ * 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 limit is not in the passed value already, add a limit clause.
+ if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
+ $rt = sprintf('FETCH FIRST %d ROWS ONLY', $limit);
+ }
+
+ // TODO: Implement paging with the offset. This could get hairy.
+ /*
+ WITH WHOLE AS
+ (SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY,
+ ROW_NUMBER() OVER (ORDER BY SALARY DESC) AS RN
+ FROM EMPLOYEE)
+ SELECT FIRSTNME, MIDINIT, LASTNAME, SALARY, RN
+ FROM WHOLE
+ WHERE RN BETWEEN 10 AND 15
+ */
+
+ /*
+ if ($offset) {
+ $rt .= ' ' . $offset . ',';
+ }
+
+ $rt .= ' ' . $limit;
+ */
+
+ 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 == 'smallint') {
+ return 'boolean';
+ }
+
+ if (strpos($col, 'char') !== false) {
+ return 'string';
+ }
+
+ if (strpos($col, 'clob') !== false) {
+ return 'text';
+ }
+
+ if (strpos($col, 'blob') !== false || $col == 'image') {
+ return 'binary';
+ }
+
+ if (in_array($col, array('double', 'real', 'decimal'))) {
+ return 'float';
+ }
+ return 'text';
+ }
+/**
+ * Maps a result set to an array so that returned fields are
+ * grouped by model. Any calculated fields, or fields that
+ * do not correspond to a particular model belong under array
+ * key 0.
+ *
+ * 1. Gets the column headers
+ * {{{
+ * Post.id
+ * Post.title
+ *
+ * [0] => Array
+ * (
+ * [0] => Post
+ * [1] => id
+ * )
+ *
+ * [1] => Array
+ * (
+ * [0] => Post
+ * [1] => title
+ * )
+ * }}}
+ * @param unknown_type $results
+ */
+ function resultSet(&$results, $sql = null) {
+ $this->results =& $results;
+ $this->map = $this->_resultMap[$this->results];
+ }
+/**
+ * Fetches the next row from the current result set
+ * Maps the records in the $result property to the map
+ * created in resultSet().
+ *
+ * 2. Gets the actual values.
+ *
+ * @return unknown
+ */
+ function fetchResult() {
+ if ($row = db2_fetch_array($this->results)) {
+ $resultRow = array();
+ $i = 0;
+
+ foreach ($row as $index => $field) {
+ $table = $this->map[$index][0];
+ $column = strtolower($this->map[$index][1]);
+ $resultRow[$table][$column] = $row[$index];
+ $i++;
+ }
+ return $resultRow;
+ }
+ return false;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_firebird.php b/cake/libs/model/datasources/dbo/dbo_firebird.php
new file mode 100755
index 00000000..d02c45b7
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_firebird.php
@@ -0,0 +1,513 @@
+ true,
+ 'host' => 'localhost',
+ 'login' => 'SYSDBA',
+ 'password' => 'masterkey',
+ 'database' => 'c:\\CAKE.FDB',
+ 'port' => '3050',
+ 'connect' => 'ibase_connect'
+ );
+/**
+ * Firebird 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' => 'BLOB SUB_TYPE 1 SEGMENT SIZE 100 CHARACTER SET NONE'),
+ 'integer' => array('name' => 'integer'),
+ 'float' => array('name' => 'float', 'formatter' => 'floatval'),
+ 'datetime' => array('name' => 'timestamp', 'format' => 'd.m.Y H:i:s', 'formatter' => 'date'),
+ 'timestamp' => array('name' => 'timestamp', 'format' => 'd.m.Y H:i:s', 'formatter' => 'date'),
+ 'time' => array('name' => 'time', 'format' => 'H:i:s', 'formatter' => 'date'),
+ 'date' => array('name' => 'date', 'format' => 'd.m.Y', 'formatter' => 'date'),
+ 'binary' => array('name' => 'blob'),
+ 'boolean' => array('name' => 'smallint')
+ );
+/**
+ * Firebird Transaction commands.
+ *
+ * @var array
+ **/
+ var $_commands = array(
+ 'begin' => 'SET TRANSACTION',
+ 'commit' => 'COMMIT',
+ 'rollback' => 'ROLLBACK'
+ );
+/**
+ * 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;
+ $connect = $config['connect'];
+
+ $this->connected = false;
+ $this->connection = $connect($config['host'] . ':' . $config['database'], $config['login'], $config['password']);
+ $this->connected = true;
+ }
+/**
+ * Disconnects from database.
+ *
+ * @return boolean True if the database could be disconnected, else false
+ */
+ function disconnect() {
+ $this->connected = false;
+ return @ibase_close($this->connection);
+ }
+/**
+ * Executes given SQL statement.
+ *
+ * @param string $sql SQL statement
+ * @return resource Result resource identifier
+ * @access protected
+ */
+ function _execute($sql) {
+ return @ibase_query($this->connection, $sql);
+ }
+/**
+ * Returns a row from given resultset as an array .
+ *
+ * @return array The fetched row as an array
+ */
+ function fetchRow() {
+ if ($this->hasResult()) {
+ $this->resultSet($this->_result);
+ $resultRow = $this->fetchResult();
+ return $resultRow;
+ } else {
+ return null;
+ }
+ }
+/**
+ * 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;
+ }
+ $sql = "select RDB" . "$" . "RELATION_NAME as name
+ FROM RDB" ."$" . "RELATIONS
+ Where RDB" . "$" . "SYSTEM_FLAG =0";
+
+ $result = @ibase_query($this->connection,$sql);
+ $tables = array();
+ while ($row = ibase_fetch_row ($result)) {
+ $tables[] = strtolower(trim($row[0]));
+ }
+ 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) {
+ $this->modeltmp[$model->table] = $model->alias;
+ $cache = parent::describe($model);
+
+ if ($cache != null) {
+ return $cache;
+ }
+ $fields = false;
+ $sql = "SELECT * FROM " . $this->fullTableName($model, false);
+ $rs = ibase_query($sql);
+ $coln = ibase_num_fields($rs);
+ $fields = false;
+
+ for ($i = 0; $i < $coln; $i++) {
+ $col_info = ibase_field_info($rs, $i);
+ $fields[strtolower($col_info['name'])] = array(
+ 'type' => $this->column($col_info['type']),
+ 'null' => '',
+ 'length' => $col_info['length']
+ );
+ }
+ $this->__cacheDescription($this->fullTableName($model, false), $fields);
+ return $fields;
+ }
+/**
+ * Returns a quoted name of $data for use in an SQL statement.
+ *
+ * @param string $data Name (table.field) to be prepared for use in an SQL statement
+ * @return string Quoted for Firebird
+ */
+ function name($data) {
+ if ($data == '*') {
+ return '*';
+ }
+ $pos = strpos($data, '"');
+
+ if ($pos === false) {
+ if (!strpos($data, ".")) {
+ $data = '"' . strtoupper($data) . '"';
+ } else {
+ $build = explode('.', $data);
+ $data = '"' . strtoupper($build[0]) . '"."' . strtoupper($build[1]) . '"';
+ }
+ }
+ return $data;
+ }
+/**
+ * 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 ($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;
+ }
+ return "'" . $data . "'";
+ }
+/**
+ * Removes Identity (primary key) column from update data before returning to parent
+ *
+ * @param Model $model
+ * @param array $fields
+ * @param array $values
+ * @return array
+ */
+ function update(&$model, $fields = array(), $values = array()) {
+ foreach ($fields as $i => $field) {
+ if ($field == $model->primaryKey) {
+ unset ($fields[$i]);
+ unset ($values[$i]);
+ break;
+ }
+ }
+ return parent::update($model, $fields, $values);
+ }
+/**
+ * Returns a formatted error message from previous database operation.
+ *
+ * @return string Error message with error number
+ */
+ function lastError() {
+ $error = ibase_errmsg();
+
+ if ($error !== false) {
+ 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 ibase_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() {
+ return $this->_result? /*ibase_affected_rows($this->_result)*/ 1: false;
+ }
+/**
+ * Returns the ID generated from the previous INSERT operation.
+ *
+ * @param unknown_type $source
+ * @return in
+ */
+ function lastInsertId($source = null, $field = 'id') {
+ $query = "SELECT RDB\$TRIGGER_SOURCE
+ FROM RDB\$TRIGGERS WHERE RDB\$RELATION_NAME = '". strtoupper($source) . "' AND
+ RDB\$SYSTEM_FLAG IS NULL AND RDB\$TRIGGER_TYPE = 1 ";
+
+ $result = @ibase_query($this->connection,$query);
+ $generator = "";
+
+ while ($row = ibase_fetch_row($result, IBASE_TEXT)) {
+ if (strpos($row[0], "NEW." . strtoupper($field))) {
+ $pos = strpos($row[0], "GEN_ID(");
+
+ if ($pos > 0) {
+ $pos2 = strpos($row[0],",",$pos + 7);
+
+ if ($pos2 > 0) {
+ $generator = substr($row[0], $pos +7, $pos2 - $pos- 7);
+ }
+ }
+ break;
+ }
+ }
+
+ if (!empty($generator)) {
+ $sql = "SELECT GEN_ID(". $generator . ",0) AS maxi FROM RDB" . "$" . "DATABASE";
+ $res = $this->rawQuery($sql);
+ $data = $this->fetchRow($res);
+ return $data['maxi'];
+ } 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), 'top') || strpos(strtolower($limit), 'top') === 0) {
+ $rt = ' FIRST';
+ }
+ $rt .= ' ' . $limit;
+
+ if (is_int($offset) && $offset > 0) {
+ $rt .= ' SKIP ' . $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'))) {
+ return strtolower($col);
+ }
+ if ($col == 'TIMESTAMP') {
+ return 'datetime';
+ }
+ if ($col == 'SMALLINT') {
+ return 'boolean';
+ }
+ if (strpos($col, 'int') !== false || $col == 'numeric' || $col == 'INTEGER') {
+ return 'integer';
+ }
+ if (strpos($col, 'char') !== false) {
+ return 'string';
+ }
+ if (strpos($col, 'text') !== false) {
+ return 'text';
+ }
+ if (strpos($col, 'VARCHAR') !== false) {
+ return 'string';
+ }
+ if (strpos($col, 'BLOB') !== false) {
+ return 'text';
+ }
+ if (in_array($col, array('FLOAT', 'NUMERIC', 'DECIMAL'))) {
+ return 'float';
+ }
+ return 'text';
+ }
+/**
+ * Enter description here...
+ *
+ * @param unknown_type $results
+ */
+ function resultSet(&$results) {
+ $this->results =& $results;
+ $this->map = array();
+ $num_fields = ibase_num_fields($results);
+ $index = 0;
+ $j = 0;
+
+ while ($j < $num_fields) {
+ $column = ibase_field_info($results, $j);
+ if (!empty($column[2])) {
+ $this->map[$index++] = array(ucfirst(strtolower($this->modeltmp[strtolower($column[2])])), strtolower($column[1]));
+ } else {
+ $this->map[$index++] = array(0, strtolower($column[1]));
+ }
+ $j++;
+ }
+ }
+/**
+ * Builds final SQL statement
+ *
+ * @param string $type Query type
+ * @param array $data Query data
+ * @return string
+ */
+ function renderStatement($type, $data) {
+ extract($data);
+
+ if (strtolower($type) == 'select') {
+ 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} {$order}) AS Set1 {$rOrder}) AS Set2 {$order2}";
+ } else {
+ return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}";
+ }
+ } else {
+ return parent::renderStatement($type, $data);
+ }
+ }
+/**
+ * Fetches the next row from the current result set
+ *
+ * @return unknown
+ */
+ function fetchResult() {
+ if ($row = ibase_fetch_row($this->results, IBASE_TEXT)) {
+ $resultRow = array();
+ $i = 0;
+
+ foreach ($row as $index => $field) {
+ list($table, $column) = $this->map[$index];
+
+ if (trim($table) == "") {
+ $resultRow[0][$column] = $row[$index];
+ } else {
+ $resultRow[$table][$column] = $row[$index];
+ $i++;
+ }
+ }
+ return $resultRow;
+ } else {
+ return false;
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_mssql.php b/cake/libs/model/datasources/dbo/dbo_mssql.php
new file mode 100755
index 00000000..1b1219d5
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_mssql.php
@@ -0,0 +1,746 @@
+ 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'
+ );
+/**
+ * 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/", 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;
+ }
+/**
+ * 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) {
+ return mssql_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->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() {
+ $error = mssql_get_last_message($this->connection);
+
+ if ($error) {
+ if (!preg_match('/contexto de la base de datos a|contesto di database|changed database|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" . join(",\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'] = join(', ', 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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_mysql.php b/cake/libs/model/datasources/dbo/dbo_mysql.php
new file mode 100755
index 00000000..2d97e141
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_mysql.php
@@ -0,0 +1,665 @@
+= 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'
+ );
+/**
+ * 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')
+ );
+/**
+ * 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 = join(', ', $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;
+ }
+ $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 = 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 '.$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));
+ $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 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 .' (' . join(', ', 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 = join(', ', array_map(array(&$this, 'name'), $fields));
+ }
+ $values = implode(', ', $values);
+ $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values}");
+ }
+}
+
+/**
+ * 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 {
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ */
+ 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',
+ 'connect' => 'mysql_pconnect'
+ );
+/**
+ * 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;
+ $connect = $config['connect'];
+ $this->connected = false;
+
+ if (!$config['persistent']) {
+ $this->connection = mysql_connect($config['host'] . ':' . $config['port'], $config['login'], $config['password'], true);
+ } else {
+ $this->connection = $connect($config['host'] . ':' . $config['port'], $config['login'], $config['password']);
+ }
+
+ if (mysql_select_db($config['database'], $this->connection)) {
+ $this->connected = true;
+ }
+
+ if (isset($config['encoding']) && !empty($config['encoding'])) {
+ $this->setEncoding($config['encoding']);
+ }
+
+ $this->_useAlias = (bool)version_compare(mysql_get_server_info($this->connection), "4.1", ">=");
+
+ return $this->connected;
+ }
+/**
+ * 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_array($result)) {
+ $tables[] = $line[0];
+ }
+ 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) {
+ $cache = parent::describe($model);
+ if ($cache != null) {
+ return $cache;
+ }
+ $fields = false;
+ $cols = $this->query('DESCRIBE ' . $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']];
+ }
+ }
+ }
+ $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 || (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_int($data) || is_float($data) || $data === '0') || (
+ is_numeric($data) && strpos($data, ',') === false &&
+ $data[0] != '0' && strpos($data, 'e') === false)) {
+ return $data;
+ }
+ default:
+ $data = "'" . mysql_real_escape_string($data, $this->connection) . "'";
+ break;
+ }
+ return $data;
+ }
+/**
+ * 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;
+ }
+/**
+ * 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';
+ }
+/**
+ * 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)) {
+ $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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_mysqli.php b/cake/libs/model/datasources/dbo/dbo_mysqli.php
new file mode 100755
index 00000000..b0adf8d2
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_mysqli.php
@@ -0,0 +1,411 @@
+ true,
+ 'host' => 'localhost',
+ 'login' => 'root',
+ 'password' => '',
+ 'database' => 'cake',
+ 'port' => '3306',
+ 'connect' => 'mysqli_connect'
+ );
+/**
+ * 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 (is_numeric($config['port'])) {
+ $config['socket'] = null;
+ } else {
+ $config['socket'] = $config['port'];
+ $config['port'] = null;
+ }
+
+ $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;
+ }
+/**
+ * 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_array($result)) {
+ $tables[] = $line[0];
+ }
+ 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) {
+
+ $cache = parent::describe($model);
+ if ($cache != null) {
+ return $cache;
+ }
+
+ $fields = false;
+ $cols = $this->query('DESCRIBE ' . $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']];
+ }
+ }
+ }
+
+ $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 || (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;
+ }
+/**
+ * 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';
+ }
+/**
+ * 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 integer 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 ($limit != null) {
+ return intval($limit);
+ }
+ 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();
+ $i = 0;
+ 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];
+ $i++;
+ }
+ return $resultRow;
+ }
+ return false;
+ }
+/**
+ * Gets the database encoding
+ *
+ * @return string The database encoding
+ */
+ function getEncoding() {
+ return mysqli_client_encoding($this->connection);
+ }
+/**
+ * Checks if the result is valid
+ *
+ * @return boolean True if the result is valid, else false
+ */
+ function hasResult() {
+ return is_object($this->_result);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_odbc.php b/cake/libs/model/datasources/dbo/dbo_odbc.php
new file mode 100755
index 00000000..a8572a71
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_odbc.php
@@ -0,0 +1,360 @@
+ true,
+ 'login' => 'root',
+ 'password' => '',
+ 'database' => 'cake',
+ 'connect' => 'odbc_pconnect'
+ );
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ */
+ var $columns = array();
+
+ // var $columns = array('primary_key' => array('name' => 'int(11) DEFAULT NULL auto_increment'),
+ // 'string' => array('name' => 'varchar', 'limit' => '255'),
+ // 'text' => array('name' => 'text'),
+ // 'integer' => array('name' => 'int', 'limit' => '11'),
+ // 'float' => array('name' => 'float'),
+ // 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d h:i:s', 'formatter' => 'date'),
+ // 'timestamp' => array('name' => 'datetime', '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'));
+/**
+ * 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;
+ $connect = $config['connect'];
+ if (!$config['persistent']) {
+ $connect = 'odbc_connect';
+ }
+ if (!function_exists($connect)) {
+ die('no odbc?');
+ }
+ $this->connected = false;
+ $this->connection = $connect($config['database'], $config['login'], $config['password'], SQL_CUR_USE_ODBC);
+ if ($this->connection) {
+ $this->connected = true;
+ }
+
+ return $this->connected;
+ }
+/**
+ * Disconnects from database.
+ *
+ * @return boolean True if the database could be disconnected, else false
+ */
+ function disconnect() {
+ return @odbc_close($this->connection);
+ }
+/**
+ * Executes given SQL statement.
+ *
+ * @param string $sql SQL statement
+ * @return resource Result resource identifier
+ * @access protected
+ */
+ function _execute($sql) {
+ switch ($sql) {
+ case 'BEGIN':
+ return odbc_autocommit($this->connection, false);
+ case 'COMMIT':
+ return odbc_commit($this->connection);
+ case 'ROLLBACK':
+ return odbc_rollback($this->connection);
+ }
+ // TODO: should flags be set? possible requirement: SQL_CURSOR_STATIC
+ return odbc_exec($this->connection, $sql);
+ }
+/**
+ * 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 = odbc_tables($this->connection);
+
+ $tables = array();
+ while (odbc_fetch_row($result)) {
+ array_push($tables, odbc_result($result, '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;
+ }
+
+ $fields = array();
+ $sql = 'SELECT * FROM ' . $this->fullTableName($model);
+ $result = odbc_exec($this->connection, $sql);
+
+ $count = odbc_num_fields($result);
+
+ for ($i = 1; $i <= $count; $i++) {
+ $cols[$i - 1] = odbc_field_name($result, $i);
+ }
+
+ foreach ($cols as $column) {
+ $type = odbc_field_type(odbc_exec($this->connection, 'SELECT ' . $column . ' FROM ' . $this->fullTableName($model)), 1);
+ $fields[$column] = array('type' => $type);
+ }
+
+ $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
+ * @param string $column The column into which this data will be inserted
+ * @return string Quoted and escaped
+ * @todo Add logic that formats/escapes data based on column type
+ */
+ function value($data, $column = null) {
+ $parent=parent::value($data, $column);
+
+ if ($parent != null) {
+ return $parent;
+ }
+
+ if ($data === null) {
+ return 'NULL';
+ }
+
+ if (!is_numeric($data)) {
+ return "'" . $data . "'";
+ }
+
+ return $data;
+ }
+/**
+ * Returns a formatted error message from previous database operation.
+ *
+ * @return string Error message with error number
+ */
+ function lastError() {
+ if ($error = odbc_errormsg($this->connection)) {
+ return odbc_error($this->connection) . ': ' . $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->hasResult()) {
+ return odbc_num_rows($this->_result);
+ }
+ return null;
+ }
+/**
+ * Returns number of rows in previous resultset. If no previous resultset exists,
+ * this returns false.
+ *
+ * @return int Number of rows in resultset
+ */
+ function lastNumRows() {
+ if ($this->hasResult()) {
+ return odbc_num_rows($this->_result);
+ }
+ return null;
+ }
+/**
+ * Returns the ID generated from the previous INSERT operation.
+ *
+ * @param unknown_type $source
+ * @return int
+ */
+ function lastInsertId($source = null) {
+ $result = $this->fetchRow('SELECT @@IDENTITY');
+ return $result[0];
+ }
+/**
+ * Enter description here...
+ *
+ * @param string $real Real database-layer column type (i.e. "varchar(255)")
+ */
+ function column($real) {
+ if (is_array($real)) {
+ $col=$real['name'];
+ if (isset($real['limit'])) {
+ $col .= '(' . $real['limit'] . ')';
+ }
+ return $col;
+ }
+ return $real;
+ }
+/**
+* Enter description here...
+*
+* @param unknown_type $results
+*/
+ function resultSet(&$results) {
+ $this->results =& $results;
+ $num_fields = odbc_num_fields($results);
+ $this->map = array();
+ $index = 0;
+ $j = 0;
+ while ($j < $num_fields) {
+ $column = odbc_field_name($results, $j+1);
+
+ if (strpos($column, '_dot_') !== false) {
+ list($table, $column) = explode('_dot_', $column);
+ $this->map[$index++] = array($table, $column);
+ } else {
+ $this->map[$index++] = array(0, $column);
+ }
+ $j++;
+ }
+ }
+/**
+* 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 = null, $quote = true) {
+ if (empty($alias)) {
+ $alias = $model->name;
+ }
+ if (!is_array($fields)) {
+ if ($fields != null) {
+ $fields = array_map('trim', explode(',', $fields));
+ } else {
+ foreach($model->tableToModel as $tableName => $modelName) {
+ foreach($this->__descriptions[$model->tablePrefix .$tableName] as $field => $type) {
+ $fields[] = $modelName .'.' .$field;
+ }
+ }
+ }
+ }
+
+ $count = count($fields);
+
+ if ($count >= 1 && $fields[0] != '*' && strpos($fields[0], 'COUNT(*)') === false) {
+ for ($i = 0; $i < $count; $i++) {
+ if (!preg_match('/^.+\\(.*\\)/', $fields[$i])) {
+ $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 . '_dot_' . $fields[$i]);
+ } else {
+ $build = explode('.', $fields[$i]);
+ $fields[$i] = $prepend . $this->name($build[0]) . '.' . $this->name($build[1]) . ' AS ' . $this->name($build[0] . '_dot_' . $build[1]);
+ }
+ }
+ }
+ }
+ return $fields;
+ }
+/**
+ * Fetches the next row from the current result set
+ *
+ * @return unknown
+ */
+ function fetchResult() {
+ if ($row = odbc_fetch_row($this->results)) {
+ $resultRow = array();
+ $numFields = odbc_num_fields($this->results);
+ $i = 0;
+ for($i = 0; $i < $numFields; $i++) {
+ list($table, $column) = $this->map[$i];
+ $resultRow[$table][$column] = odbc_result($this->results, $i + 1);
+ }
+ return $resultRow;
+ }
+ return false;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_oracle.php b/cake/libs/model/datasources/dbo/dbo_oracle.php
new file mode 100755
index 00000000..4aa25a6a
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_oracle.php
@@ -0,0 +1,1121 @@
+ 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'));
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ * @access protected
+ */
+ var $connection;
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ * @access protected
+ */
+ var $_limit = -1;
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ * @access protected
+ */
+ var $_offset = 0;
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ * @access protected
+ */
+ var $_map;
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ * @access protected
+ */
+ var $_currentRow;
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ * @access protected
+ */
+ var $_numRows;
+/**
+ * Enter description here...
+ *
+ * @var unknown_type
+ * @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 ($this->config['persistent']) {
+ $connect = 'ociplogon';
+ } else {
+ $connect = 'ocilogon';
+ }
+ $this->connection = @$connect($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;
+ }
+/**
+ * Enter description here...
+ *
+ * @return unknown
+ * @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);
+ }
+/**
+ * Enter description here...
+ *
+ * @param unknown_type $table
+ * @return unknown
+ * @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'));
+ }
+
+ $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'));
+ }
+ }
+ 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" . join(",\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} {$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" . join(",\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__$}', join(', ', $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__$}', '(' .join(', ', $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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_postgres.php b/cake/libs/model/datasources/dbo/dbo_postgres.php
new file mode 100755
index 00000000..2298fc01
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_postgres.php
@@ -0,0 +1,867 @@
+ 'BEGIN',
+ 'commit' => 'COMMIT',
+ 'rollback' => 'ROLLBACK'
+ );
+/**
+ * Base driver configuration settings. Merged with user settings.
+ *
+ * @var array
+ * @access protected
+ */
+ var $_baseConfig = array(
+ 'connect' => 'pg_pconnect',
+ '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')
+ );
+
+ var $startQuote = '"';
+
+ 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;
+ }
+/**
+ * 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'])) {
+ $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];
+ }
+ }
+ }
+ }
+ $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) {
+ return 'NULL';
+ }
+ if (empty($column)) {
+ $column = $this->introspectType($data);
+ }
+
+ switch($column) {
+ case 'inet':
+ case 'float':
+ case 'integer':
+ case 'date':
+ case 'datetime':
+ case 'timestamp':
+ if ($data === '') {
+ return $read ? 'NULL' : 'DEFAULT';
+ }
+ 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;
+ 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 && $fields[0] != '*' && strpos($fields[0], 'COUNT(*)') === false) {
+ for ($i = 0; $i < $count; $i++) {
+ if (!preg_match('/^.+\\(.*\\)/', $fields[$i]) && !preg_match('/\s+AS\s+/', $fields[$i])) {
+ $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]);
+ }
+ }
+ }
+ }
+ return $fields;
+ }
+/**
+ * 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 = 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);
+ $colList[] = 'ALTER COLUMN '. $fieldName .' TYPE ' . str_replace($fieldName, '', $this->buildColumn($col));
+ }
+ 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" . join(",\n\t", $colList) . ";\n\n";
+ } else {
+ $out = '';
+ }
+ $out .= join(";\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 . ' (' . join(', ', 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'] = join(', ', 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} = join($join[$var], array_filter(${$var}));
+ }
+ }
+ return "CREATE TABLE {$table} (\n\t{$columns}\n);\n{$indexes}";
+ break;
+ default:
+ return parent::renderStatement($type, $data);
+ break;
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_sqlite.php b/cake/libs/model/datasources/dbo/dbo_sqlite.php
new file mode 100755
index 00000000..c365b6fe
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_sqlite.php
@@ -0,0 +1,595 @@
+ true,
+ 'database' => null,
+ 'connect' => 'sqlite_popen'
+ );
+/**
+ * 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')
+ );
+/**
+ * Connects to the database using config['database'] as a filename.
+ *
+ * @param array $config Configuration array for connecting
+ * @return mixed
+ */
+ function connect() {
+ $config = $this->config;
+ $this->connection = $config['connect']($config['database']);
+ $this->connected = is_resource($this->connection);
+
+ if ($this->connected) {
+ $this->_execute('PRAGMA count_changes = 1;');
+ }
+ return $this->connected;
+ }
+/**
+ * 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', E_USER_WARNING);
+ return null;
+ }
+
+ if (!isset($this->columns[$type])) {
+ trigger_error("Column type {$type} does not exist", 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'];
+ }
+ 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 (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 (isset($column['null']) && $column['null'] == true) {
+ $out .= ' DEFAULT NULL';
+ } elseif (isset($column['null']) && $column['null'] == false) {
+ $out .= ' NOT NULL';
+ }
+ return $out;
+ }
+/**
+ * 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'] = join(', ', 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" . join(",\n\t", array_filter(${$var}));
+ }
+ }
+ return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}";
+ break;
+ default:
+ return parent::renderStatement($type, $data);
+ break;
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo/dbo_sybase.php b/cake/libs/model/datasources/dbo/dbo_sybase.php
new file mode 100755
index 00000000..9e2a1185
--- /dev/null
+++ b/cake/libs/model/datasources/dbo/dbo_sybase.php
@@ -0,0 +1,384 @@
+ true,
+ 'host' => 'localhost',
+ 'login' => 'sa',
+ 'password' => '',
+ 'database' => 'cake',
+ 'port' => '4100'
+ );
+/**
+ * Sybase column definition
+ *
+ * @var array
+ */
+ var $columns = array(
+ 'primary_key' => array('name' => 'numeric(9,0) IDENTITY PRIMARY KEY'),
+ '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' => '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')
+ );
+/**
+ * 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 = sybase_connect($config['host'], $config['login'], $config['password'], true);
+ } else {
+ $this->connection = sybase_pconnect($config['host'], $config['login'], $config['password']);
+ }
+
+ if (sybase_select_db($config['database'], $this->connection)) {
+ $this->connected = true;
+ }
+ return $this->connected;
+ }
+/**
+ * Disconnects from database.
+ *
+ * @return boolean True if the database could be disconnected, else false
+ */
+ function disconnect() {
+ $this->connected = !@sybase_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 sybase_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("select name from sysobjects where type='U'");
+ if (!$result) {
+ return array();
+ } else {
+
+ $tables = array();
+ while ($line = sybase_fetch_array($result)) {
+ $tables[] = $line[0];
+ }
+
+ 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) {
+
+ $cache = parent::describe($model);
+ if ($cache != null) {
+ return $cache;
+ }
+
+ $fields = false;
+ $cols = $this->query('DESC ' . $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'],
+ 'length' => $this->length($column[0]['Type']),
+ );
+ }
+ }
+
+ $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
+ * @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 ($data === '') {
+ return "''";
+ }
+
+ switch ($column) {
+ case 'boolean':
+ $data = $this->boolean((bool)$data);
+ break;
+ default:
+ $data = str_replace("'", "''", $data);
+ break;
+ }
+
+ return "'" . $data . "'";
+ }
+/**
+ * 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(&$model) {
+ if (parent::begin($model)) {
+ if ($this->execute('BEGIN TRAN')) {
+ $this->_transactionStarted = true;
+ return true;
+ }
+ }
+ return false;
+ }
+/**
+ * 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(&$model) {
+ if (parent::commit($model)) {
+ $this->_transactionStarted = false;
+ return $this->execute('COMMIT TRAN');
+ }
+ return false;
+ }
+/**
+ * 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(&$model) {
+ if (parent::rollback($model)) {
+ return $this->execute('ROLLBACK TRAN');
+ }
+ return false;
+ }
+/**
+ * Returns a formatted error message from previous database operation.
+ *
+ * @todo not implemented
+ * @return string Error message with error number
+ */
+ function lastError() {
+ 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 sybase_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 @sybase_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) {
+ $result=$this->fetchRow('SELECT @@IDENTITY');
+ return $result[0];
+ }
+/**
+ * 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('datetime', 'smalldatetime'))) {
+ return 'datetime';
+ } elseif (in_array($col, array('int', 'bigint', 'smallint', 'tinyint'))) {
+ return 'integer';
+ } elseif (in_array($col, array('float', 'double', 'real', 'decimal', 'money', 'numeric', 'smallmoney'))) {
+ return 'float';
+ } elseif (strpos($col, 'text') !== false) {
+ return 'text';
+ } elseif (in_array($col, array('char', 'nchar', 'nvarchar', 'string', 'varchar'))) {
+ return 'binary';
+ } elseif (in_array($col, array('binary', 'image', 'varbinary'))) {
+ return 'binary';
+ }
+
+ return 'text';
+ }
+/**
+ * Enter description here...
+ *
+ * @param unknown_type $results
+ */
+ function resultSet(&$results) {
+ $this->results =& $results;
+ $this->map = array();
+ $num_fields = sybase_num_fields($results);
+ $index = 0;
+ $j = 0;
+
+ while ($j < $num_fields) {
+
+ $column = sybase_fetch_field($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 = sybase_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;
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/datasources/dbo_source.php b/cake/libs/model/datasources/dbo_source.php
new file mode 100755
index 00000000..63d2577f
--- /dev/null
+++ b/cake/libs/model/datasources/dbo_source.php
@@ -0,0 +1,2461 @@
+ 'primary', 'MUL' => 'index', 'UNI' => 'unique');
+/**
+ * Database keyword used to assign aliases to identifiers.
+ *
+ * @var string
+ */
+ var $alias = 'AS ';
+/**
+ * Caches fields quoted in DboSource::name()
+ *
+ * @var array
+ */
+ var $fieldCache = array();
+/**
+ * Bypass automatic adding of joined fields/associations.
+ *
+ * @var boolean
+ */
+ var $__bypass = false;
+/**
+ * The set of valid SQL operations usable in a WHERE statement
+ *
+ * @var array
+ */
+ 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'
+ );
+/**
+ * Constructor
+ */
+ function __construct($config = null, $autoConnect = true) {
+ if (!isset($config['prefix'])) {
+ $config['prefix'] = '';
+ }
+ parent::__construct($config);
+ $this->fullDebug = Configure::read() > 1;
+
+ 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
+ */
+ function reconnect($config = null) {
+ $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.
+ */
+ 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
+ */
+ 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
+ */
+ function expression($expression) {
+ $obj = new stdClass();
+ $obj->type = 'expression';
+ $obj->value = $expression;
+ return $obj;
+ }
+/**
+ * Executes given SQL statement.
+ *
+ * @param string $sql SQL statement
+ * @return unknown
+ */
+ 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 DEBUG is set, the log is shown all the time, else it is only shown on errors.
+ *
+ * @param string $sql
+ * @param array $options
+ * @return mixed Resource or object representing the result set, or false on failure
+ */
+ function execute($sql, $options = array()) {
+ $defaults = array('stats' => true, 'log' => $this->fullDebug);
+ $options = array_merge($defaults, $options);
+
+ if ($options['stats']) {
+ $t = getMicrotime();
+ $this->_result = $this->_execute($sql);
+ $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
+ */
+ 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
+ */
+ 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();
+ 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
+ */
+ 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()) {
+ $out[] = $item;
+ }
+
+ if ($cache) {
+ if (strpos(trim(strtolower($sql)), 'select') !== false) {
+ $this->_queryCache[$sql] = $out;
+ }
+ }
+ return $out;
+
+ } else {
+ return false;
+ }
+ }
+/**
+ * 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 unknown
+ */
+ function field($name, $sql) {
+ $data = $this->fetchRow($sql);
+
+ if (!isset($data[$name]) || empty($data[$name])) {
+ return false;
+ } else {
+ return $data[$name];
+ }
+ }
+/**
+ * Returns a quoted name of $data for use in an SQL statement.
+ * Strips fields out of SQL functions before quoting.
+ *
+ * @param string $data
+ * @return string SQL field
+ */
+ function name($data) {
+ if ($data == '*') {
+ return '*';
+ }
+ if (is_object($data) && isset($data->type)) {
+ return $data->value;
+ }
+ $array = is_array($data);
+ $data = (array)$data;
+ $count = count($data);
+
+ for ($i = 0; $i < $count; $i++) {
+ if ($data[$i] == '*') {
+ continue;
+ }
+ if (strpos($data[$i], '(') !== false && preg_match_all('/([^(]*)\((.*)\)(.*)/', $data[$i], $fields)) {
+ $fields = Set::extract($fields, '{n}.0');
+
+ if (!empty($fields[1])) {
+ if (!empty($fields[2])) {
+ $data[$i] = $fields[1] . '(' . $this->name($fields[2]) . ')' . $fields[3];
+ } else {
+ $data[$i] = $fields[1] . '()' . $fields[3];
+ }
+ }
+ }
+ $data[$i] = str_replace('.', $this->endQuote . '.' . $this->startQuote, $data[$i]);
+ $data[$i] = $this->startQuote . $data[$i] . $this->endQuote;
+ $data[$i] = str_replace($this->startQuote . $this->startQuote, $this->startQuote, $data[$i]);
+ $data[$i] = str_replace($this->startQuote . '(', '(', $data[$i]);
+ $data[$i] = str_replace(')' . $this->startQuote, ')', $data[$i]);
+ $alias = !empty($this->alias) ? $this->alias : 'AS ';
+
+ if (preg_match('/\s+' . $alias . '\s*/', $data[$i])) {
+ if (preg_match('/\w+\s+' . $alias . '\s*/', $data[$i])) {
+ $quoted = $this->endQuote . ' ' . $alias . $this->startQuote;
+ $data[$i] = str_replace(' ' . $alias, $quoted, $data[$i]);
+ } else {
+ $quoted = $alias . $this->startQuote;
+ $data[$i] = str_replace($alias, $quoted, $data[$i]) . $this->endQuote;
+ }
+ }
+
+ if (!empty($this->endQuote) && $this->endQuote == $this->startQuote) {
+ if (substr_count($data[$i], $this->endQuote) % 2 == 1) {
+ if (substr($data[$i], -2) == $this->endQuote . $this->endQuote) {
+ $data[$i] = substr($data[$i], 0, -1);
+ } else {
+ $data[$i] = trim($data[$i], $this->endQuote);
+ }
+ }
+ }
+ if (strpos($data[$i], '*')) {
+ $data[$i] = str_replace($this->endQuote . '*' . $this->endQuote, '*', $data[$i]);
+ }
+ $data[$i] = str_replace($this->endQuote . $this->endQuote, $this->endQuote, $data[$i]);
+ }
+ return (!$array) ? $data[0] : $data;
+ }
+/**
+ * Checks if it's connected to the database
+ *
+ * @return boolean True if the database is connected, else false
+ */
+ function isConnected() {
+ return $this->connected;
+ }
+/**
+ * Checks if the result is valid
+ *
+ * @return boolean True if the result is valid else false
+ */
+ function hasResult() {
+ return is_resource($this->_result);
+ }
+/**
+ * Outputs the contents of the queries log.
+ *
+ * @param boolean $sorted
+ */
+ function showLog($sorted = false) {
+ if ($sorted) {
+ $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
+ } else {
+ $log = $this->_queriesLog;
+ }
+
+ if ($this->_queriesCnt > 1) {
+ $text = 'queries';
+ } else {
+ $text = 'query';
+ }
+
+ if (PHP_SAPI != 'cli') {
+ print ("\n({$this->configKeyName}) {$this->_queriesCnt} {$text} took {$this->_queriesTime} ms \n");
+ print ("\nNr Query Error Affected Num. rows Took (ms) \n\n\n");
+
+ foreach ($log as $k => $i) {
+ print ("" . ($k + 1) . " " . h($i['query']) . " {$i['error']} {$i['affected']} {$i['numRows']} {$i['took']} \n");
+ }
+ print ("
\n");
+ } else {
+ foreach ($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
+ */
+ 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.
+ */
+ 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: {$this->error}", E_USER_WARNING);
+ } else {
+ $out = ("[Aff:{$this->affected} Num:{$this->numRows} Took:{$this->took}ms]");
+ }
+ pr(sprintf("Query: %s %s
", $sql, $out));
+ }
+ }
+/**
+ * Gets full table name including prefix
+ *
+ * @param mixed $model
+ * @param boolean $quote
+ * @return string Full quoted table name
+ */
+ 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
+ *
+ * @param Model $model
+ * @param array $fields
+ * @param array $values
+ * @return boolean Success
+ */
+ 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' => join(', ', $fieldInsert),
+ 'values' => join(', ', $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
+ *
+ * @param Model $model
+ * @param array $queryData
+ * @param integer $recursive Number of levels of association
+ * @return unknown
+ */
+ 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)) {
+ $stack = array($assoc);
+ $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
+ unset($db);
+ }
+ }
+ }
+ $this->__filterResults($resultSet, $model, $filtered);
+ }
+
+ if (!is_null($recursive)) {
+ $model->recursive = $_recursive;
+ }
+ return $resultSet;
+ }
+/**
+ * Private method. 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 return
+ */
+ 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;
+ }
+/**
+ * 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' && 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)) {
+ $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)) {
+ if (count($ins) > 1) {
+ $query = str_replace('{$__cakeID__$}', '(' .join(', ', $ins) .')', $query);
+ $query = str_replace('= (', 'IN (', $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]);
+ }
+ } 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
+ */
+ function fetchAssociated($model, $query, $ids) {
+ $query = str_replace('{$__cakeID__$}', join(', ', $ids), $query);
+ if (count($ids) > 1) {
+ $query = str_replace('= (', 'IN (', $query);
+ $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
+ */
+ 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
+ */
+ 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 = strpos($passedFields[0], 'COUNT(');
+ 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
+ */
+ 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
+ * @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
+ * @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' => join(', ', $query['fields']),
+ 'table' => $query['table'],
+ 'alias' => $this->alias . $this->name($query['alias']),
+ 'order' => $this->order($query['order']),
+ 'limit' => $this->limit($query['limit'], $query['offset']),
+ 'joins' => join(' ', $query['joins']),
+ 'group' => $this->group($query['group'])
+ ));
+ }
+/**
+ * Renders a final SQL JOIN statement
+ *
+ * @param array $data
+ * @return string
+ */
+ 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
+ * @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} {$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') as $var) {
+ if (is_array(${$var})) {
+ ${$var} = "\t" . join(",\n\t", array_filter(${$var}));
+ }
+ }
+ if (trim($indexes) != '') {
+ $columns .= ',';
+ }
+ return "CREATE TABLE {$table} (\n{$columns}{$indexes});";
+ break;
+ case 'alter':
+ break;
+ }
+ }
+/**
+ * Merges a mixed set of string/array conditions
+ *
+ * @return array
+ */
+ 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
+ */
+ function update(&$model, $fields = array(), $values = null, $conditions = null) {
+ if ($values == null) {
+ $combined = $fields;
+ } else {
+ $combined = array_combine($fields, $values);
+ }
+ $fields = join(', ', $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
+ */
+ 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 {
+ $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
+ */
+ 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';
+ }
+ return 'COUNT(' . $this->name($params[0]) . ') AS ' . $this->name($params[1]);
+ case 'max':
+ case 'min':
+ if (!isset($params[1])) {
+ $params[1] = $params[0];
+ }
+ return strtoupper($func) . '(' . $this->name($params[0]) . ') 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ 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.
+ *
+ * @param object $model
+ * @param mixed $conditions
+ * @param boolean $useAlias Use model aliases rather than table names when generating conditions
+ * @return mixed
+ */
+ function defaultConditions(&$model, $conditions, $useAlias = true) {
+ if (!empty($conditions)) {
+ return $conditions;
+ }
+ if (!$model->exists()) {
+ return false;
+ }
+ $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
+ */
+ 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
+ */
+ function __scrubQueryData($data) {
+ foreach (array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group') as $key) {
+ if (!isset($data[$key]) || empty($data[$key])) {
+ $data[$key] = array();
+ }
+ }
+ return $data;
+ }
+/**
+ * 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
+ */
+ function fields(&$model, $alias = null, $fields = array(), $quote = true) {
+ if (empty($alias)) {
+ $alias = $model->alias;
+ }
+ if (empty($fields)) {
+ $fields = array_keys($model->schema());
+ } elseif (!is_array($fields)) {
+ $fields = String::tokenize($fields);
+ }
+ $fields = array_values(array_filter($fields));
+
+ if (!$quote) {
+ return $fields;
+ }
+ $count = count($fields);
+
+ if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) {
+ for ($i = 0; $i < $count; $i++) {
+ if (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($build[0] . '.' . $build[1]);
+ }
+ $comma = String::tokenize($fields[$i]);
+ foreach ($comma as $string) {
+ if (preg_match('/^[0-9]+\.[0-9]+$/', $string)) {
+ $value[] = $string;
+ } else {
+ $build = explode('.', $string);
+ $value[] = $this->name(trim($build[0]) . '.' . trim($build[1]));
+ }
+ }
+ $fields[$i] = implode(', ', $value);
+ }
+ }
+ $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] = join('.', array_map(array($this, 'name'), $field[0]));
+ $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1);
+ }
+ }
+ }
+ }
+ }
+ }
+ return array_unique($fields);
+ }
+/**
+ * Creates a WHERE clause by parsing given conditions data.
+ *
+ * @param mixed $conditions Array or string of conditions
+ * @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
+ */
+ function conditions($conditions, $quoteValues = true, $where = true, $model = null) {
+ $clause = $out = '';
+
+ if ($where) {
+ $clause = ' WHERE ';
+ }
+
+ if (is_array($conditions) && !empty($conditions)) {
+ $out = $this->conditionKeysToString($conditions, $quoteValues, $model);
+
+ if (empty($out)) {
+ return $clause . ' 1 = 1';
+ }
+ return $clause . join(' AND ', $out);
+ }
+
+ if (empty($conditions) || trim($conditions) == '' || $conditions === true) {
+ return $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 $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
+ */
+ 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 . '(' . join(') ' . 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 (array_keys($value) === array_values(array_keys($value))) {
+ $count = count($value);
+ if ($count === 1) {
+ $data = $this->__quoteFields($key) . ' = (';
+ } else {
+ $data = $this->__quoteFields($key) . ' IN (';
+ }
+ if ($quoteValues || strpos($value[0], '-!') !== 0) {
+ if (is_object($model)) {
+ $columnType = $model->getColumnType($key);
+ }
+ $data .= join(', ', $this->value($value, $columnType));
+ }
+ $data .= ')';
+ } else {
+ $ret = $this->conditionKeysToString($value, $quoteValues, $model);
+ if (count($ret) > 1) {
+ $data = '(' . join(') 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) {
+ if (preg_match('/^\(\(\((.+)\)\)\)$/', $data)) {
+ $data = substr($data, 1, strlen($data) - 2);
+ }
+ $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 = '/^((' . join(')|(', $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);
+ }
+ }
+
+
+ $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 ($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 = join(', ', $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;
+ }
+ }
+
+ 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);
+ preg_match_all('/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', $conditions, $replace, PREG_PATTERN_ORDER);
+
+ if (isset($replace['1']['0'])) {
+ $pregCount = count($replace['1']);
+
+ for ($i = 0; $i < $pregCount; $i++) {
+ if (!empty($replace['1'][$i]) && !is_numeric($replace['1'][$i])) {
+ $conditions = preg_replace('/\b' . preg_quote($replace['1'][$i]) . '\b/', $this->name($replace['1'][$i]), $conditions);
+ }
+ }
+ return $conditions;
+ }
+ return $original;
+ }
+/**
+ * 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';
+ }
+
+ 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)
+ * @return string ORDER BY clause
+ */
+ function order($keys, $direction = 'ASC') {
+ if (is_string($keys) && strpos($keys, ',') && !preg_match('/\(.+\,.+\)/', $keys)) {
+ $keys = array_map('trim', explode(',', $keys));
+ }
+
+ if (is_array($keys)) {
+ $keys = array_filter($keys);
+ }
+
+ if (empty($keys) || (is_array($keys) && count($keys) && isset($keys[0]) && empty($keys[0]))) {
+ return '';
+ }
+
+ if (is_array($keys)) {
+ $keys = (Set::countDim($keys) > 1) ? array_map(array(&$this, 'order'), $keys) : $keys;
+
+ foreach ($keys as $key => $value) {
+ if (is_numeric($key)) {
+ $key = $value = ltrim(str_replace('ORDER BY ', '', $this->order($value)));
+ $value = (!preg_match('/\\x20ASC|\\x20DESC/i', $key) ? ' ' . $direction : '');
+ } else {
+ $value = ' ' . $value;
+ }
+
+ if (!preg_match('/^.+\\(.*\\)/', $key) && !strpos($key, ',')) {
+ if (preg_match('/\\x20ASC|\\x20DESC/i', $key, $dir)) {
+ $dir = $dir[0];
+ $key = preg_replace('/\\x20ASC|\\x20DESC/i', '', $key);
+ } else {
+ $dir = '';
+ }
+ $key = trim($key);
+ if (!preg_match('/\s/', $key)) {
+ $key = $this->name($key);
+ }
+ $key .= ' ' . trim($dir);
+ }
+ $order[] = $this->order($key . $value);
+ }
+ return ' ORDER BY ' . trim(str_replace('ORDER BY', '', join(',', $order)));
+ }
+ $keys = preg_replace('/ORDER\\x20BY/i', '', $keys);
+
+ if (strpos($keys, '.')) {
+ preg_match_all('/([a-zA-Z0-9_]{1,})\\.([a-zA-Z0-9_]{1,})/', $keys, $result, PREG_PATTERN_ORDER);
+ $pregCount = count($result[0]);
+
+ for ($i = 0; $i < $pregCount; $i++) {
+ if (!is_numeric($result[0][$i])) {
+ $keys = preg_replace('/' . $result[0][$i] . '/', $this->name($result[0][$i]), $keys);
+ }
+ }
+ $result = ' ORDER BY ' . $keys;
+ return $result . (!preg_match('/\\x20ASC|\\x20DESC/i', $keys) ? ' ' . $direction : '');
+
+ } elseif (preg_match('/(\\x20ASC|\\x20DESC)/i', $keys, $match)) {
+ $direction = $match[1];
+ return ' ORDER BY ' . preg_replace('/' . $match[1] . '/', '', $keys) . $direction;
+ }
+ return ' ORDER BY ' . $keys . ' ' . $direction;
+ }
+/**
+ * Create a GROUP BY SQL clause
+ *
+ * @param string $group Group By Condition
+ * @return mixed string condition or null
+ */
+ function group($group) {
+ if ($group) {
+ if (is_array($group)) {
+ $group = join(', ', $group);
+ }
+ return ' GROUP BY ' . $this->__quoteFields($group);
+ }
+ return null;
+ }
+/**
+ * Disconnects database, kills the connection and says the connection is closed,
+ * and if DEBUG is turned on, the log for this object is shown.
+ *
+ */
+ function close() {
+ if (Configure::read() > 1) {
+ $this->showLog();
+ }
+ $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
+ */
+ 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
+ */
+ 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
+ */
+ 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 = join(', ', 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
+ */
+ 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
+ */
+ 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 = 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') {
+ $col['name'] = $name;
+ if (!isset($col['type'])) {
+ $col['type'] = 'string';
+ }
+ $cols[] = $this->buildColumn($col);
+ } else {
+ $indexes = array_merge($indexes, $this->buildIndex($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')) . "\n\n";
+ }
+ }
+ return $out;
+ }
+/**
+ * Generate a alter syntax from CakeSchema::compare()
+ *
+ * @param unknown_type $schema
+ * @return unknown
+ */
+ 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
+ */
+ 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
+ */
+ 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', E_USER_WARNING);
+ return null;
+ }
+
+ if (!isset($this->columns[$type])) {
+ trigger_error("Column type {$type} does not exist", 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;
+ }
+
+ 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 (isset($column['null']) && $column['null'] == true) {
+ $out .= ' DEFAULT NULL';
+ } elseif (isset($column['null']) && $column['null'] == false) {
+ $out .= ' NOT NULL';
+ }
+ return $out;
+ }
+/**
+ * Format indexes for create table
+ *
+ * @param array $indexes
+ * @param string $table
+ * @return array
+ */
+ 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 . ' (' . join(', ', array_map(array(&$this, 'name'), $value['column'])) . ')';
+ } else {
+ $out .= 'KEY ' . $name . ' (' . $this->name($value['column']) . ')';
+ }
+ $join[] = $out;
+ }
+ return $join;
+ }
+/**
+ * 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';
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/db_acl.php b/cake/libs/model/db_acl.php
new file mode 100755
index 00000000..a2a943cf
--- /dev/null
+++ b/cake/libs/model/db_acl.php
@@ -0,0 +1,317 @@
+ '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')
+ )
+ );
+
+ $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("Model class '$name' not found in AclNode::node() when trying to bind {$this->alias} object", 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("AclNode::node() - Couldn't find {$type} node identified by \"" . 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();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/model.php b/cake/libs/model/model.php
new file mode 100755
index 00000000..d437edf8
--- /dev/null
+++ b/cake/libs/model/model.php
@@ -0,0 +1,2894 @@
+ 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/66/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/435/useDbConfig
+ */
+ 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/436/useTable
+ */
+ 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/438/displayField
+ */
+ 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/441/data
+ */
+ 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/437/primaryKey
+ */
+ var $primaryKey = null;
+/**
+ * Field-by-field table metadata.
+ *
+ * @var array
+ * @access protected
+ * @link http://book.cakephp.org/view/442/_schema
+ */
+ 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/443/validate
+ * @link http://book.cakephp.org/view/125/Data-Validation
+ */
+ var $validate = array();
+/**
+ * List of validation errors.
+ *
+ * @var array
+ * @access public
+ * @link http://book.cakephp.org/view/410/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/475/tablePrefix
+ */
+ var $tablePrefix = null;
+/**
+ * Name of the model.
+ *
+ * @var string
+ * @access public
+ * @link http://book.cakephp.org/view/444/name
+ */
+ 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 enable transactions for this model (i.e. BEGIN/COMMIT/ROLLBACK statements)
+ *
+ * @var boolean
+ * @access public
+ */
+ var $transactional = 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/445/cacheQueries
+ */
+ var $cacheQueries = false;
+/**
+ * Detailed list of belongsTo associations.
+ *
+ * @var array
+ * @access public
+ * @link http://book.cakephp.org/view/81/belongsTo
+ */
+ var $belongsTo = array();
+/**
+ * Detailed list of hasOne associations.
+ *
+ * @var array
+ * @access public
+ * @link http://book.cakephp.org/view/80/hasOne
+ */
+ var $hasOne = array();
+/**
+ * Detailed list of hasMany associations.
+ *
+ * @var array
+ * @access public
+ * @link http://book.cakephp.org/view/82/hasMany
+ */
+ var $hasMany = array();
+/**
+ * Detailed list of hasAndBelongsToMany associations.
+ *
+ * @var array
+ * @access public
+ * @link http://book.cakephp.org/view/83/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/90/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/439/recursive
+ */
+ 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/440/order
+ */
+ var $order = null;
+/**
+ * Whether or not the model record exists, set by Model::exists().
+ *
+ * @var bool
+ * @access private
+ */
+ var $__exists = null;
+/**
+ * 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.
+ *
+ * @param integer $id Set this ID for this model on startup
+ * @param string $table Name of database table to use.
+ * @param object $ds DataSource connection object.
+ */
+ 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);
+ }
+ if (method_exists($this, 'setTablePrefix')) {
+ $this->setTablePrefix();
+ }
+ $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 $permanent is true, association will not be reset
+ * to the originals defined in the model.
+ *
+ * @param mixed $model A model or association name (string) or set of binding options (indexed by model name type)
+ * @param array $options If $model is a string, this is the list of association properties with which $model will
+ * be bound
+ * @param boolean $permanent Set to true to make the binding permanent
+ * @return void
+ * @access public
+ * @todo
+ */
+ function bind($model, $options = array(), $permanent = true) {
+ if (!is_array($model)) {
+ $model = array($model => $options);
+ }
+
+ foreach ($model as $name => $options) {
+ if (isset($options['type'])) {
+ $assoc = $options['type'];
+ } elseif (isset($options[0])) {
+ $assoc = $options[0];
+ } else {
+ $assoc = 'belongsTo';
+ }
+
+ if (!$permanent) {
+ $this->__backAssociation[$assoc] = $this->{$assoc};
+ }
+ foreach ($model as $key => $value) {
+ $assocName = $modelName = $key;
+
+ if (isset($this->{$assoc}[$assocName])) {
+ $this->{$assoc}[$assocName] = array_merge($this->{$assoc}[$assocName], $options);
+ } else {
+ if (isset($value['className'])) {
+ $modelName = $value['className'];
+ }
+
+ $this->__constructLinkedModel($assocName, $modelName);
+ $this->{$assoc}[$assocName] = $model[$assocName];
+ $this->__generateAssociation($assoc);
+ }
+ unset($this->{$assoc}[$assocName]['type'], $this->{$assoc}[$assocName][0]);
+ }
+ }
+ }
+/**
+ * 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')) );
+ *
+ *
+ * @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/86/Creating-and-Destroying-Associations-on-the-Fly
+ */
+ function bindModel($params, $reset = true) {
+ foreach ($params as $assoc => $model) {
+ if ($reset === true) {
+ $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;
+ }
+ }
+ $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')) );
+ *
+ *
+ * @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/86/Creating-and-Destroying-Associations-on-the-Fly
+ */
+ function unbindModel($params, $reset = true) {
+ foreach ($params as $assoc => $models) {
+ if ($reset === true) {
+ $this->__backAssociation[$assoc] = $this->{$assoc};
+ }
+
+ foreach ($models as $model) {
+ $this->__backAssociation = array_merge($this->__backAssociation, $this->{$assoc});
+ unset ($this->__backAssociation[$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) = explode('.', $assoc);
+ $this->{$type}[$assoc] = $value;
+ $plugin = $plugin . '.';
+ }
+ }
+ $className = $assoc;
+
+ if (isset($value['className']) && !empty($value['className'])) {
+ $className = $value['className'];
+ if (strpos($className, '.') !== false) {
+ list($plugin, $className) = explode('.', $className);
+ $plugin = $plugin . '.';
+ $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 ($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 = $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) {
+ $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
+ )));
+ }
+ $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
+ */
+ 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
+ * @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) {
+ if (is_array($name)) {
+ foreach ($name as $n) {
+ if ($this->hasField($n)) {
+ return $n;
+ }
+ }
+ return false;
+ }
+
+ if (empty($this->_schema)) {
+ $this->schema();
+ }
+
+ if ($this->_schema != null) {
+ return isset($this->_schema[$name]);
+ }
+ return false;
+ }
+/**
+ * Initializes the model for writing a new record, loading the default values
+ * for those fields that are not defined in $data. 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/75/Saving-Your-Data
+ */
+ function create($data = array(), $filterKey = false) {
+ $defaults = array();
+ $this->id = false;
+ $this->data = array();
+ $this->__exists = null;
+ $this->validationErrors = array();
+
+ if ($data !== null && $data !== false) {
+ foreach ($this->schema() as $field => $properties) {
+ if ($this->primaryKey !== $field && isset($properties['default'])) {
+ $defaults[$field] = $properties['default'];
+ }
+ }
+ $this->set(Set::filter($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
+ */
+ 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/453/field
+ */
+ 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;
+ }
+ if ($data = $this->find($conditions, $name, $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) {
+ $name = key($data[0]);
+ return $data[0][$name];
+ }
+ } 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/75/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/75/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]);
+ }
+ }
+
+ $this->exists();
+ $dateFields = array('modified', 'updated');
+
+ if (!$this->__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;
+ }
+ }
+ $fields = $values = array();
+
+ if (isset($this->data[$this->alias][$this->primaryKey]) && empty($this->data[$this->alias][$this->primaryKey])) {
+ unset($this->data[$this->alias][$this->primaryKey]);
+ }
+
+ 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 (!$this->__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 {
+ foreach ($this->_schema as $field => $properties) {
+ if ($this->primaryKey === $field) {
+ $fInfo = $this->_schema[$field];
+ $isUUID = ($fInfo['length'] == 36 &&
+ ($fInfo['type'] === 'string' || $fInfo['type'] === 'binary')
+ );
+ if (empty($this->data[$this->alias][$this->primaryKey]) && $isUUID) {
+ list($fields[], $values[]) = array($this->primaryKey, String::uuid());
+ }
+ break;
+ }
+ }
+
+ 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);
+ }
+
+ 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->__exists = null;
+ $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 =& ConnectionManager::getDataSource($this->useDbConfig);
+
+ foreach ($joined as $assoc => $data) {
+
+ if (isset($this->hasAndBelongsToMany[$assoc])) {
+ list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']);
+
+ $conditions = array($join . '.' . $this->hasAndBelongsToMany[$assoc]['foreignKey'] => $id);
+
+ $links = $this->{$join}->find('all', array(
+ 'conditions' => $conditions,
+ 'recursive' => -1,
+ 'fields' => $this->hasAndBelongsToMany[$assoc]['associationForeignKey']
+ ));
+
+ $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 = join(',', $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']) {
+ $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 = join(',', $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.
+ *
+ * @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, which are as follows:
+ * - validate: Set to false to disable validation, true to validate each record before
+ * saving, 'first' to validate *all* records before any are saved, 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.
+ * If false, we return an array similar to the $data array passed, but values are set to true/false
+ * depending on whether each record saved successfully.
+ * - fieldList: Equivalent to the $fieldList parameter in Model::save()
+ * @return mixed True on success, or false on failure
+ * @access public
+ * @link http://book.cakephp.org/view/84/Saving-Related-Model-Data-hasOne-hasMany-belongsTo
+ * @link http://book.cakephp.org/view/75/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' => true, 'atomic' => true), $options);
+ $this->validationErrors = $validationErrors = array();
+ $validates = true;
+ $return = array();
+
+ if ($options['atomic'] && $options['validate'] !== 'only') {
+ $db->begin($this);
+ }
+
+ if (Set::numeric(array_keys($data))) {
+ while ($validates) {
+ 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;
+ continue;
+ break;
+ default:
+ if ($options['atomic']) {
+ if ($validates && ($db->commit($this) !== false)) {
+ return true;
+ }
+ $db->rollback($this);
+ return false;
+ }
+ return $return;
+ break;
+ }
+ }
+ 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;
+ continue;
+ break;
+ default:
+ if ($options['atomic']) {
+ if ($validates) {
+ return ($db->commit($this) !== false);
+ } else {
+ $db->rollback($this);
+ }
+ }
+ return $return;
+ break;
+ }
+ }
+ }
+/**
+ * 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/75/Saving-Your-Data
+ */
+ function updateAll($fields, $conditions = true) {
+ $db =& ConnectionManager::getDataSource($this->useDbConfig);
+ return $db->update($this, $fields, null, $conditions);
+ }
+/**
+ * Alias for del().
+ *
+ * @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
+ * @see Model::del()
+ * @link http://book.cakephp.org/view/691/remove
+ */
+ function remove($id = null, $cascade = true) {
+ return $this->del($id, $cascade);
+ }
+/**
+ * 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/690/del
+ */
+ function del($id = null, $cascade = true) {
+ if (!empty($id)) {
+ $this->id = $id;
+ }
+ $id = $this->id;
+
+ if ($this->exists() && $this->beforeDelete($cascade)) {
+ $db =& ConnectionManager::getDataSource($this->useDbConfig);
+ if (!$this->Behaviors->trigger($this, 'beforeDelete', array($cascade), array('break' => true, 'breakOn' => false))) {
+ return false;
+ }
+ $this->_deleteDependent($id, $cascade);
+ $this->_deleteLinks($id);
+ $this->id = $id;
+
+ if (!empty($this->belongsTo)) {
+ $keys = $this->find('first', array('fields' => $this->__collectForeignKeys()));
+ }
+
+ if ($db->delete($this)) {
+ if (!empty($this->belongsTo)) {
+ $this->updateCounterCache($keys[$this->alias]);
+ }
+ $this->Behaviors->trigger($this, 'afterDelete');
+ $this->afterDelete();
+ $this->_clearCache();
+ $this->id = false;
+ $this->__exists = null;
+ return true;
+ }
+ }
+ return false;
+ }
+/**
+ * Alias for del().
+ *
+ * @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
+ * @see Model::del()
+ */
+ function delete($id = null, $cascade = true) {
+ return $this->del($id, $cascade);
+ }
+/**
+ * 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($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) {
+ $records = $this->{$data['with']}->find('all', array(
+ 'conditions' => array_merge(array($this->{$data['with']}->escapeField($data['foreignKey']) => $id)),
+ 'fields' => $this->{$data['with']}->primaryKey,
+ 'recursive' => -1
+ ));
+ if (!empty($records)) {
+ foreach ($records as $record) {
+ $this->{$data['with']}->delete($record[$this->{$data['with']}->alias][$this->{$data['with']}->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 (not being used)
+ * @return boolean True on success, false on failure
+ * @access public
+ * @link http://book.cakephp.org/view/692/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 = Set::extract(
+ $this->find('all', array_merge(array('fields' => "{$this->alias}.{$this->primaryKey}", 'recursive' => 0), compact('conditions'))),
+ "{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.
+ *
+ * @param boolean $reset if true will force database query
+ * @return boolean True if such a record exists
+ * @access public
+ */
+ function exists($reset = false) {
+ if (is_array($reset)) {
+ extract($reset, EXTR_OVERWRITE);
+ }
+
+ if ($this->getID() === false || $this->useTable === false) {
+ return false;
+ }
+ if (!empty($this->__exists) && $reset !== true) {
+ return $this->__exists;
+ }
+ $conditions = array($this->alias . '.' . $this->primaryKey => $this->getID());
+ $query = array('conditions' => $conditions, 'recursive' => -1, 'callbacks' => false);
+
+ if (is_array($reset)) {
+ $query = array_merge($query, $reset);
+ }
+ return $this->__exists = ($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);
+ }
+/**
+ * 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'));
+ *
+ * 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/449/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);
+ }
+
+ $db =& ConnectionManager::getDataSource($this->useDbConfig);
+ $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;
+ }
+ }
+
+ $results = $db->read($this, $query);
+ $this->resetAssociations();
+ $this->findQueryType = null;
+
+ if ($query['callbacks'] === true || $query['callbacks'] === 'after') {
+ $results = $this->__filterResults($results);
+ }
+
+ 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;
+ if (empty($query['conditions']) && !empty($this->id)) {
+ $query['conditions'] = array($this->escapeField() => $this->id);
+ }
+ 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);
+ }
+/**
+ * Called only when bindTo() is used.
+ * This resets the association arrays for the model back
+ * to those originally defined in the model.
+ *
+ * @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/456/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.
+ *
+ * @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/410/Validating-Data-from-the-Controller
+ */
+ function validates($options = array()) {
+ $errors = $this->invalidFields($options);
+ if (is_array($errors)) {
+ return count($errors) === 0;
+ }
+ return $errors;
+ }
+/**
+ * Returns an array of fields that have failed validation.
+ *
+ * @param string $options An optional array of custom options to be made available in the beforeValidate callback
+ * @return array Array of invalid fields
+ * @access public
+ * @link http://book.cakephp.org/view/410/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 $this->validationErrors;
+ }
+
+ 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();
+ $this->exists();
+
+ $_validate = $this->validate;
+ $whitelist = $this->whitelist;
+
+ if (array_key_exists('fieldList', $options)) {
+ $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' &&
+ !$this->__exists) || ($validator['on'] == 'update' && $this->__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]);
+ }
+
+ 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;
+ }
+/**
+ * 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);
+ }
+/**
+ * Returns the display field for this model.
+ *
+ * @return string The name of the display field for this Model (i.e. 'name', 'title').
+ * @access public
+ * @deprecated
+ */
+ function getDisplayField() {
+ return $this->displayField;
+ }
+/**
+ * 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) || $db == null || !is_object($db)) {
+ return $this->cakeError('missingConnection', array(array('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)));
+ } else {
+ 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/680/beforeFind
+ */
+ 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/681/afterFind
+ */
+ 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/683/beforeSave
+ */
+ 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/684/afterSave
+ */
+ 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/685/beforeDelete
+ */
+ function beforeDelete($cascade = true) {
+ return true;
+ }
+/**
+ * Called after every deletion operation.
+ *
+ * @access public
+ * @link http://book.cakephp.org/view/686/afterDelete
+ */
+ function afterDelete() {
+ }
+/**
+ * Called during save 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/682/beforeValidate
+ */
+ function beforeValidate($options = array()) {
+ return true;
+ }
+/**
+ * Called when a DataSource-level error occurs.
+ *
+ * @access public
+ * @link http://book.cakephp.org/view/687/onError
+ */
+ 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() {
+ }
+/**
+ * @deprecated
+ * @see Model::find('all')
+ */
+ function findAll($conditions = null, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null) {
+ //trigger_error(__('(Model::findAll) Deprecated, use Model::find("all")', true), E_USER_WARNING);
+ return $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
+ }
+/**
+ * @deprecated
+ * @see Model::find('count')
+ */
+ function findCount($conditions = null, $recursive = 0) {
+ //trigger_error(__('(Model::findCount) Deprecated, use Model::find("count")', true), E_USER_WARNING);
+ return $this->find('count', compact('conditions', 'recursive'));
+ }
+/**
+ * @deprecated
+ * @see Model::find('threaded')
+ */
+ function findAllThreaded($conditions = null, $fields = null, $order = null) {
+ //trigger_error(__('(Model::findAllThreaded) Deprecated, use Model::find("threaded")', true), E_USER_WARNING);
+ return $this->find('threaded', compact('conditions', 'fields', 'order'));
+ }
+/**
+ * @deprecated
+ * @see Model::find('neighbors')
+ */
+ function findNeighbours($conditions = null, $field, $value) {
+ //trigger_error(__('(Model::findNeighbours) Deprecated, use Model::find("neighbors")', true), E_USER_WARNING);
+ $query = compact('conditions', 'field', 'value');
+ $query['fields'] = $field;
+ if (is_array($field)) {
+ $query['field'] = $field[0];
+ }
+ return $this->find('neighbors', $query);
+ }
+}
+if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) {
+ Overloadable::overload('Model');
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/model/schema.php b/cake/libs/model/schema.php
new file mode 100755
index 00000000..d0368992
--- /dev/null
+++ b/cake/libs/model/schema.php
@@ -0,0 +1,552 @@
+name = preg_replace('/schema$/i', '', get_class($this));
+ }
+
+ if (strtolower($this->name) === 'cake') {
+ $this->name = Inflector::camelize(Inflector::slug(Configure::read('App.dir')));
+ }
+
+ if (empty($options['path'])) {
+ $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('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;
+ }
+ }
+/**
+ * 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;
+ }
+
+ 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');
+
+ $tables = array();
+ $currentTables = $db->listSources();
+
+ $prefix = null;
+ if (isset($db->config['prefix'])) {
+ $prefix = $db->config['prefix'];
+ }
+
+ if (!is_array($models) && $models !== false) {
+ $models = Configure::listObjects('model');
+ }
+
+ if (is_array($models)) {
+ foreach ($models as $model) {
+ 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) {
+ $Object->setDataSource($connection);
+ $table = $db->fullTableName($Object, false);
+
+ if (in_array($table, $currentTables)) {
+ $key = array_search($table, $currentTables);
+ if (empty($tables[$Object->table])) {
+ $tables[$Object->table] = $this->__columns($Object);
+ $tables[$Object->table]['indexes'] = $db->index($Object);
+ unset($currentTables[$key]);
+ }
+ if (!empty($Object->hasAndBelongsToMany)) {
+ foreach ($Object->hasAndBelongsToMany as $Assoc => $assocData) {
+ if (isset($assocData['with'])) {
+ $class = $assocData['with'];
+ } elseif ($assocData['_with']) {
+ $class = $assocData['_with'];
+ }
+ if (is_object($Object->$class)) {
+ $table = $db->fullTableName($Object->$class, false);
+ if (in_array($table, $currentTables)) {
+ $key = array_search($table, $currentTables);
+ $tables[$Object->$class->table] = $this->__columns($Object->$class);
+ $tables[$Object->$class->table]['indexes'] = $db->index($Object->$class);
+ 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);
+ } elseif ($models === false) {
+ $tables[$table] = $this->__columns($Object);
+ $tables[$table]['indexes'] = $db->index($Object);
+ } else {
+ $tables['missing'][$table] = $this->__columns($Object);
+ $tables['missing'][$table]['indexes'] = $db->index($Object);
+ }
+ }
+ }
+
+ 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 .= "\tvar \${$table} = array(\n";
+ if (is_array($fields)) {
+ $cols = array();
+ foreach ($fields as $field => $value) {
+ if ($field != 'indexes') {
+ 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));
+ } else {
+ $col = "\t\t'indexes' => array(";
+ $props = array();
+ foreach ((array)$value as $key => $index) {
+ $props[] = "'{$key}' => array(" . join(', ', $this->__values($index)) . ")";
+ }
+ $col .= join(', ', $props);
+ }
+ $col .= ")";
+ $cols[] = $col;
+ }
+ $out .= join(",\n", $cols);
+ }
+ $out .= "\n\t);\n";
+ }
+ }
+ $out .="}\n";
+
+
+ $File =& new File($path . DS . $file, true);
+ $header = '$Id';
+ $content = "";
+ $content = $File->prepare($content);
+ if ($File->write($content)) {
+ return $content;
+ }
+ return false;
+ }
+/**
+ * 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') {
+ break;
+ }
+ if (!array_key_exists($table, $old)) {
+ $tables[$table]['add'] = $fields;
+ } else {
+ $diff = array_diff_assoc($fields, $old[$table]);
+ if (!empty($diff)) {
+ $tables[$table]['add'] = $diff;
+ }
+ $diff = array_diff_assoc($old[$table], $fields);
+ if (!empty($diff)) {
+ $tables[$table]['drop'] = $diff;
+ }
+ }
+ foreach ($fields as $field => $value) {
+ if (isset($old[$table][$field])) {
+ $diff = array_diff_assoc($value, $old[$table][$field]);
+ if (!empty($diff) && $field !== 'indexes') {
+ $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) {
+ $tables[$table]['drop']['indexes'] = $diff['drop'];
+ $tables[$table]['add']['indexes'] = $diff['add'];
+ }
+ }
+ }
+ return $tables;
+ }
+/**
+ * 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('" . join("', '", $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('Schema generation error: invalid column type ' . $value['type'] . ' does not exist in DBO', 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 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 = array_diff_assoc($new, $old);
+ if (!empty($diff)) {
+ $add = $diff;
+ }
+
+ $diff = array_diff_assoc($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'));
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/multibyte.php b/cake/libs/multibyte.php
new file mode 100755
index 00000000..724b6592
--- /dev/null
+++ b/cake/libs/multibyte.php
@@ -0,0 +1,1126 @@
+ 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; $ii < count($keys[$key]['lower']); $ii++) {
+ $nextChar = $utf8Map[$i + $ii];
+
+ if (isset($nextChar) && ($nextChar == $keys[$key]['lower'][$j + $ii])) {
+ $replace++;
+ }
+ }
+ if ($replace == count($keys[$key]['lower'])) {
+ $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; $jj < count($keys[$ii]['lower']); $jj++) {
+ $nextChar = $utf8Map[$i + $jj];
+
+ if (isset($nextChar) && ($nextChar == $keys[$ii]['lower'][$j + $jj])) {
+ $replace++;
+ }
+ }
+ if ($replace == count($keys[$ii]['lower'])) {
+ $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/cake/libs/object.php b/cake/libs/object.php
new file mode 100755
index 00000000..61c0896c
--- /dev/null
+++ b/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, and improves performance by using straight method calls
+ * in most cases.
+ *
+ * @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
+ *
+ * @param $status see http://php.net/exit for values
+ * @return void
+ * @access public
+ */
+ function _stop($status = 0) {
+ exit($status);
+ }
+/**
+ * API for logging events.
+ *
+ * @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')) {
+ uses('cake_log');
+ }
+ if (is_null($this->_log)) {
+ $this->_log = new CakeLog();
+ }
+ if (!is_string($msg)) {
+ $msg = print_r($msg, true);
+ }
+ return $this->_log->write($type, $msg);
+ }
+/**
+ * Allows setting of multiple properties of the object in a single line of code.
+ *
+ * @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::classify($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;
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/overloadable.php b/cake/libs/overloadable.php
new file mode 100755
index 00000000..a3b170a0
--- /dev/null
+++ b/cake/libs/overloadable.php
@@ -0,0 +1,41 @@
+
\ No newline at end of file
diff --git a/cake/libs/overloadable_php4.php b/cake/libs/overloadable_php4.php
new file mode 100755
index 00000000..b60411c5
--- /dev/null
+++ b/cake/libs/overloadable_php4.php
@@ -0,0 +1,164 @@
+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');
+
+?>
\ No newline at end of file
diff --git a/cake/libs/overloadable_php5.php b/cake/libs/overloadable_php5.php
new file mode 100755
index 00000000..906b6b37
--- /dev/null
+++ b/cake/libs/overloadable_php5.php
@@ -0,0 +1,107 @@
+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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/router.php b/cake/libs/router.php
new file mode 100755
index 00000000..0b2b3b61
--- /dev/null
+++ b/cake/libs/router.php
@@ -0,0 +1,1367 @@
+ '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();
+/**
+ * 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'
+ );
+/**
+ * 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;
+/**
+ * Gets a reference to the Router object instance
+ *
+ * @return object Object instance
+ * @access public
+ * @static
+ */
+ function &getInstance() {
+ static $instance = array();
+
+ if (!$instance) {
+ $instance[0] =& new Router();
+ $instance[0]->__admin = Configure::read('Routing.admin');
+ }
+ 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() {
+ $_this =& Router::getInstance();
+ return $_this->__named;
+ }
+/**
+ * Returns this object's routes array. Returns false if there are no routes available.
+ *
+ * @param string $route An empty string, or a route string "/"
+ * @param array $default NULL or an array describing the default route
+ * @param array $params An array matching the named elements in the route to regular expressions which that element should match.
+ * @see routes
+ * @return array Array of routes
+ * @access public
+ * @static
+ */
+ function connect($route, $default = array(), $params = array()) {
+ $_this =& Router::getInstance();
+
+ if (!isset($default['action'])) {
+ $default['action'] = 'index';
+ }
+ if (isset($default[$_this->__admin])) {
+ $default['prefix'] = $_this->__admin;
+ }
+ if (isset($default['prefix'])) {
+ $_this->__prefixes[] = $default['prefix'];
+ $_this->__prefixes = array_keys(array_flip($_this->__prefixes));
+ }
+ $_this->routes[] = array($route, $default, $params);
+ return $_this->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()) {
+ $_this =& Router::getInstance();
+
+ if (isset($options['argSeparator'])) {
+ $_this->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();
+ }
+ $options = array_merge(array('default' => false, 'reset' => false, 'greedy' => true), $options);
+
+ if ($options['reset'] == true || $_this->named['rules'] === false) {
+ $_this->named['rules'] = array();
+ }
+
+ if ($options['default']) {
+ $named = array_merge($named, $_this->named['default']);
+ }
+
+ foreach ($named as $key => $val) {
+ if (is_numeric($key)) {
+ $_this->named['rules'][$val] = true;
+ } else {
+ $_this->named['rules'][$key] = $val;
+ }
+ }
+ $_this->named['greedy'] = $options['greedy'];
+ return $_this->named;
+ }
+/**
+ * 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()) {
+ $_this =& Router::getInstance();
+ $options = array_merge(array('prefix' => '/', 'id' => $_this->__named['ID'] . '|' . $_this->__named['UUID']), $options);
+ $prefix = $options['prefix'];
+
+ foreach ((array)$controller as $ctlName) {
+ $urlName = Inflector::underscore($ctlName);
+
+ foreach ($_this->__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'))
+ );
+ }
+ $_this->__resourceMapped[] = $urlName;
+ }
+ }
+/**
+ * Builds a route regular expression
+ *
+ * @param string $route An empty string, or a route string "/"
+ * @param array $default NULL or an array describing the default route
+ * @param array $params An array matching the named elements in the route to regular expressions which that element should match.
+ * @return array
+ * @see routes
+ * @access public
+ * @static
+ */
+ function writeRoute($route, $default, $params) {
+ if (empty($route) || ($route === '/')) {
+ return array('/^[\/]*$/', array());
+ }
+ $names = array();
+ $elements = explode('/', $route);
+
+ foreach ($elements as $element) {
+ if (empty($element)) {
+ continue;
+ }
+ $q = null;
+ $element = trim($element);
+ $namedParam = strpos($element, ':') !== false;
+
+ if ($namedParam && preg_match('/^:([^:]+)$/', $element, $r)) {
+ if (isset($params[$r[1]])) {
+ if ($r[1] != 'plugin' && array_key_exists($r[1], $default)) {
+ $q = '?';
+ }
+ $parsed[] = '(?:/(' . $params[$r[1]] . ')' . $q . ')' . $q;
+ } else {
+ $parsed[] = '(?:/([^\/]+))?';
+ }
+ $names[] = $r[1];
+ } elseif ($element === '*') {
+ $parsed[] = '(?:/(.*))?';
+ } else if ($namedParam && preg_match_all('/(?!\\\\):([a-z_0-9]+)/i', $element, $matches)) {
+ $matchCount = count($matches[1]);
+
+ foreach ($matches[1] as $i => $name) {
+ $pos = strpos($element, ':' . $name);
+ $before = substr($element, 0, $pos);
+ $element = substr($element, $pos + strlen($name) + 1);
+ $after = null;
+
+ if ($i + 1 === $matchCount && $element) {
+ $after = preg_quote($element);
+ }
+
+ if ($i === 0) {
+ $before = '/' . $before;
+ }
+ $before = preg_quote($before, '#');
+
+ if (isset($params[$name])) {
+ if (isset($default[$name]) && $name != 'plugin') {
+ $q = '?';
+ }
+ $parsed[] = '(?:' . $before . '(' . $params[$name] . ')' . $q . $after . ')' . $q;
+ } else {
+ $parsed[] = '(?:' . $before . '([^\/]+)' . $after . ')?';
+ }
+ $names[] = $name;
+ }
+ } else {
+ $parsed[] = '/' . $element;
+ }
+ }
+ return array('#^' . join('', $parsed) . '[\/]*$#', $names);
+ }
+/**
+ * Returns the list of prefixes used in connected routes
+ *
+ * @return array A list of prefixes used in connected routes
+ * @access public
+ * @static
+ */
+ function prefixes() {
+ $_this =& Router::getInstance();
+ return $_this->__prefixes;
+ }
+/**
+ * Parses given URL and returns an array of controllers, 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) {
+ $_this =& Router::getInstance();
+ if (!$_this->__defaultsMapped) {
+ $_this->__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($_this->__parseExtension($url));
+
+ foreach ($_this->routes as $i => $route) {
+ if (count($route) === 3) {
+ $route = $_this->compile($i);
+ }
+
+ if (($r = $_this->__matchRoute($route, $url)) !== false) {
+ $_this->__currentRoute[] = $route;
+ list($route, $regexp, $names, $defaults, $params) = $route;
+ $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']);
+ }
+ array_shift($r);
+
+ foreach ($names as $name) {
+ $out[$name] = null;
+ }
+ if (is_array($defaults)) {
+ foreach ($defaults as $name => $value) {
+ if (preg_match('#[a-zA-Z_\-]#i', $name)) {
+ $out[$name] = $value;
+ } else {
+ $out['pass'][] = $value;
+ }
+ }
+ }
+
+ foreach ($r as $key => $found) {
+ if (empty($found) && $found != 0) {
+ continue;
+ }
+
+ if (isset($names[$key])) {
+ $out[$names[$key]] = $_this->stripEscape($found);
+ } else {
+ $argOptions['context'] = array('action' => $out['action'], 'controller' => $out['controller']);
+ extract($_this->getArgs($found, $argOptions));
+ $out['pass'] = array_merge($out['pass'], $pass);
+ $out['named'] = $named;
+ }
+ }
+
+ if (isset($params['pass'])) {
+ for ($j = count($params['pass']) - 1; $j > -1; $j--) {
+ if (isset($out[$params['pass'][$j]])) {
+ array_unshift($out['pass'], $out[$params['pass'][$j]]);
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ if (!empty($ext)) {
+ $out['url']['ext'] = $ext;
+ }
+ return $out;
+ }
+/**
+ * Checks to see if the given URL matches the given route
+ *
+ * @param array $route
+ * @param string $url
+ * @return mixed Boolean false on failure, otherwise array
+ * @access private
+ */
+ function __matchRoute($route, $url) {
+ list($route, $regexp, $names, $defaults) = $route;
+
+ if (!preg_match($regexp, $url, $r)) {
+ return false;
+ } else {
+ foreach ($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;
+ }
+ }
+ }
+ }
+ return $r;
+ }
+/**
+ * Compiles a route by numeric key and returns the compiled expression, replacing
+ * the existing uncompiled route. Do not call statically.
+ *
+ * @param integer $i
+ * @return array Returns an array containing the compiled route
+ * @access public
+ */
+ function compile($i) {
+ $route = $this->routes[$i];
+
+ list($pattern, $names) = $this->writeRoute($route[0], $route[1], $route[2]);
+ $this->routes[$i] = array(
+ $route[0], $pattern, $names,
+ array_merge(array('plugin' => null, 'controller' => null), (array)$route[1]),
+ $route[2]
+ );
+ return $this->routes[$i];
+ }
+/**
+ * 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;
+ }
+ }
+ }
+ }
+ if (empty($ext)) {
+ $ext = 'html';
+ }
+ }
+ return compact('ext', 'url');
+ }
+/**
+ * Connects the default, built-in routes, including admin routes, and (deprecated) web services
+ * routes.
+ *
+ * @return void
+ * @access private
+ */
+ function __connectDefaultRoutes() {
+ if ($this->__defaultsMapped) {
+ return;
+ }
+
+ if ($this->__admin) {
+ $params = array('prefix' => $this->__admin, $this->__admin => true);
+ }
+
+ if ($plugins = Configure::listObjects('plugin')) {
+ foreach ($plugins as $key => $value) {
+ $plugins[$key] = Inflector::underscore($value);
+ }
+
+ $match = array('plugin' => implode('|', $plugins));
+ $this->connect('/:plugin/:controller/:action/*', array(), $match);
+
+ if ($this->__admin) {
+ $this->connect("/{$this->__admin}/:plugin/:controller", $params, $match);
+ $this->connect("/{$this->__admin}/:plugin/:controller/:action/*", $params, $match);
+ }
+ }
+
+ if ($this->__admin) {
+ $this->connect("/{$this->__admin}/:controller", $params);
+ $this->connect("/{$this->__admin}/: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
+ *
+ * @param array $params Parameters and path information
+ * @return void
+ * @access public
+ * @static
+ */
+ function setRequestInfo($params) {
+ $_this =& 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($_this->__params[], $_this->__paths[]) = $params;
+
+ if (count($_this->__paths)) {
+ if (isset($_this->__paths[0]['namedArgs'])) {
+ foreach ($_this->__paths[0]['namedArgs'] as $arg => $value) {
+ $_this->named['rules'][$arg] = true;
+ }
+ }
+ }
+ }
+/**
+ * Gets parameter information
+ *
+ * @param boolean $current Get current parameter (true)
+ * @return array Parameter information
+ * @access public
+ * @static
+ */
+ function getParams($current = false) {
+ $_this =& Router::getInstance();
+ if ($current) {
+ return $_this->__params[count($_this->__params) - 1];
+ }
+ if (isset($_this->__params[0])) {
+ return $_this->__params[0];
+ }
+ return array();
+ }
+/**
+ * Gets URL parameter by name
+ *
+ * @param string $name Parameter name
+ * @param boolean $current Current parameter
+ * @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
+ * @return array
+ * @access public
+ * @static
+ */
+ function getPaths($current = false) {
+ $_this =& Router::getInstance();
+ if ($current) {
+ return $_this->__paths[count($_this->__paths) - 1];
+ }
+ if (!isset($_this->__paths[0])) {
+ return array('base' => null);
+ }
+ return $_this->__paths[0];
+ }
+/**
+ * Reloads default Router settings
+ *
+ * @access public
+ * @return void
+ * @static
+ */
+ function reload() {
+ $_this =& Router::getInstance();
+ foreach (get_class_vars('Router') as $key => $val) {
+ $_this->{$key} = $val;
+ }
+ $_this->__admin = Configure::read('Routing.admin');
+ }
+/**
+ * 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 Retuns false if no route exists at the position specified by $which.
+ * @access public
+ * @static
+ */
+ function promote($which = null) {
+ $_this =& Router::getInstance();
+ if ($which === null) {
+ $which = count($_this->routes) - 1;
+ }
+ if (!isset($_this->routes[$which])) {
+ return false;
+ }
+ $route = $_this->routes[$which];
+ unset($_this->routes[$which]);
+ array_unshift($_this->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 adress to actuall controller/action.
+ * - '/' - the method will find base URL of application.
+ * - A combination of controller/action - the method will find url for it.
+ *
+ * @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) {
+ $_this =& Router::getInstance();
+ $defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
+
+ if (is_bool($full)) {
+ $escape = false;
+ } else {
+ extract(array_merge(array('escape' => false, 'full' => false), $full));
+ }
+
+ if (!empty($_this->__params)) {
+ if (isset($this) && !isset($this->params['requested'])) {
+ $params = $_this->__params[0];
+ } else {
+ $params = end($_this->__params);
+ }
+ if (isset($params['prefix']) && strpos($params['action'], $params['prefix']) === 0) {
+ $params['action'] = substr($params['action'], strlen($params['prefix']) + 1);
+ }
+ }
+ $path = array('base' => null);
+
+ if (!empty($_this->__paths)) {
+ if (isset($this) && !isset($this->params['requested'])) {
+ $path = $_this->__paths[0];
+ } else {
+ $path = end($_this->__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';
+ }
+ }
+ if ($_this->__admin) {
+ if (!isset($url[$_this->__admin]) && !empty($params[$_this->__admin])) {
+ $url[$_this->__admin] = true;
+ } elseif ($_this->__admin && isset($url[$_this->__admin]) && !$url[$_this->__admin]) {
+ unset($url[$_this->__admin]);
+ }
+ }
+ $plugin = false;
+
+ if (array_key_exists('plugin', $url)) {
+ $plugin = $url['plugin'];
+ }
+
+ $url = array_merge(array('controller' => $params['controller'], 'plugin' => $params['plugin']), Set::filter($url, true));
+
+ if ($plugin !== false) {
+ $url['plugin'] = $plugin;
+ }
+
+ if (isset($url['ext'])) {
+ $extension = '.' . $url['ext'];
+ unset($url['ext']);
+ }
+ $match = false;
+
+ foreach ($_this->routes as $i => $route) {
+ if (count($route) === 3) {
+ $route = $_this->compile($i);
+ }
+ $originalUrl = $url;
+
+ if (isset($route[4]['persist'], $_this->__params[0])) {
+ $url = array_merge(array_intersect_key($params, Set::combine($route[4]['persist'], '/')), $url);
+ }
+ if ($match = $_this->mapRouteElements($route, $url)) {
+ $output = trim($match, '/');
+ $url = array();
+ break;
+ }
+ $url = $originalUrl;
+ }
+
+ $named = $args = array();
+ $skip = array(
+ 'bare', 'action', 'controller', 'plugin', 'ext', '?', '#', 'prefix', $_this->__admin
+ );
+
+ $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 ($i === 0 && is_numeric($keys[$i]) && in_array('id', $keys)) {
+ $args[0] = $url[$keys[$i]];
+ } elseif (is_numeric($keys[$i]) || $keys[$i] === 'id') {
+ $args[] = $url[$keys[$i]];
+ } else {
+ $named[$keys[$i]] = $url[$keys[$i]];
+ }
+ }
+
+ if ($match === false) {
+ list($args, $named) = array(Set::filter($args, true), Set::filter($named));
+ if (!empty($url[$_this->__admin])) {
+ $url['action'] = str_replace($_this->__admin . '_', '', $url['action']);
+ }
+
+ if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) {
+ $url['action'] = null;
+ }
+
+ $urlOut = Set::filter(array($url['controller'], $url['action']));
+
+ if (isset($url['plugin']) && $url['plugin'] != $url['controller']) {
+ array_unshift($urlOut, $url['plugin']);
+ }
+
+ if ($_this->__admin && isset($url[$_this->__admin])) {
+ array_unshift($urlOut, $_this->__admin);
+ }
+ $output = join('/', $urlOut) . '/';
+ }
+
+ if (!empty($args)) {
+ $args = join('/', $args);
+ if ($output{strlen($output) - 1} != '/') {
+ $args = '/'. $args;
+ }
+ $output .= $args;
+ }
+
+ if (!empty($named)) {
+ foreach ($named as $name => $value) {
+ $output .= '/' . $name . $_this->named['separator'] . $value;
+ }
+ }
+ $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 . '/';
+ if ($_this->__admin && isset($params[$_this->__admin])) {
+ $output .= $_this->__admin . '/';
+ }
+ 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 . $_this->queryString($q, array(), $escape) . $frag;
+ }
+/**
+ * Maps a URL array onto a route and returns the string result, or false if no match
+ *
+ * @param array $route Route Route
+ * @param array $url URL URL to map
+ * @return mixed Result (as string) or false if no match
+ * @access public
+ * @static
+ */
+ function mapRouteElements($route, $url) {
+ if (isset($route[3]['prefix'])) {
+ $prefix = $route[3]['prefix'];
+ unset($route[3]['prefix']);
+ }
+
+ $pass = array();
+ $defaults = $route[3];
+ $routeParams = $route[2];
+ $params = Set::diff($url, $defaults);
+ $urlInv = array_combine(array_values($url), array_keys($url));
+
+ $i = 0;
+ while (isset($defaults[$i])) {
+ if (isset($urlInv[$defaults[$i]])) {
+ if (!in_array($defaults[$i], $url) && is_int($urlInv[$defaults[$i]])) {
+ return false;
+ }
+ unset($urlInv[$defaults[$i]], $defaults[$i]);
+ } else {
+ return false;
+ }
+ $i++;
+ }
+
+ foreach ($params as $key => $value) {
+ if (is_int($key)) {
+ $pass[] = $value;
+ unset($params[$key]);
+ }
+ }
+ list($named, $params) = Router::getNamedElements($params);
+
+ if (!strpos($route[0], '*') && (!empty($pass) || !empty($named))) {
+ return false;
+ }
+
+ $urlKeys = array_keys($url);
+ $paramsKeys = array_keys($params);
+ $defaultsKeys = array_keys($defaults);
+
+ if (!empty($params)) {
+ if (array_diff($paramsKeys, $routeParams) != array()) {
+ return false;
+ }
+ $required = array_values(array_diff($routeParams, $urlKeys));
+ $reqCount = count($required);
+
+ for ($i = 0; $i < $reqCount; $i++) {
+ if (array_key_exists($required[$i], $defaults) && $defaults[$required[$i]] === null) {
+ unset($required[$i]);
+ }
+ }
+ }
+ $isFilled = true;
+
+ if (!empty($routeParams)) {
+ $filled = array_intersect_key($url, array_combine($routeParams, array_keys($routeParams)));
+ $isFilled = (array_diff($routeParams, array_keys($filled)) === array());
+ if (!$isFilled && empty($params)) {
+ return false;
+ }
+ }
+
+ if (empty($params)) {
+ return Router::__mapRoute($route, array_merge($url, compact('pass', 'named', 'prefix')));
+ } elseif (!empty($routeParams) && !empty($route[3])) {
+
+ if (!empty($required)) {
+ return false;
+ }
+ foreach ($params as $key => $val) {
+ if ((!isset($url[$key]) || $url[$key] != $val) || (!isset($defaults[$key]) || $defaults[$key] != $val) && !in_array($key, $routeParams)) {
+ if (!isset($defaults[$key])) {
+ continue;
+ }
+ return false;
+ }
+ }
+ } else {
+ if (empty($required) && $defaults['plugin'] === $url['plugin'] && $defaults['controller'] === $url['controller'] && $defaults['action'] === $url['action']) {
+ return Router::__mapRoute($route, array_merge($url, compact('pass', 'named', 'prefix')));
+ }
+ return false;
+ }
+
+ if (!empty($route[4])) {
+ foreach ($route[4] as $key => $reg) {
+ if (array_key_exists($key, $url) && !preg_match('#' . $reg . '#', $url[$key])) {
+ return false;
+ }
+ }
+ }
+ return Router::__mapRoute($route, array_merge($filled, compact('pass', 'named', 'prefix')));
+ }
+/**
+ * Merges URL parameters into a route string
+ *
+ * @param array $route Route
+ * @param array $params Parameters
+ * @return string Merged URL with parameters
+ * @access private
+ */
+ function __mapRoute($route, $params = array()) {
+ if (isset($params['plugin']) && isset($params['controller']) && $params['plugin'] === $params['controller']) {
+ unset($params['controller']);
+ }
+
+ if (isset($params['prefix']) && isset($params['action'])) {
+ $params['action'] = str_replace($params['prefix'] . '_', '', $params['action']);
+ unset($params['prefix']);
+ }
+
+ if (isset($params['pass']) && is_array($params['pass'])) {
+ $params['pass'] = implode('/', Set::filter($params['pass'], true));
+ } elseif (!isset($params['pass'])) {
+ $params['pass'] = '';
+ }
+
+ if (isset($params['named'])) {
+ if (is_array($params['named'])) {
+ $count = count($params['named']);
+ $keys = array_keys($params['named']);
+ $named = array();
+
+ for ($i = 0; $i < $count; $i++) {
+ $named[] = $keys[$i] . $this->named['separator'] . $params['named'][$keys[$i]];
+ }
+ $params['named'] = join('/', $named);
+ }
+ $params['pass'] = str_replace('//', '/', $params['pass'] . '/' . $params['named']);
+ }
+ $out = $route[0];
+
+ foreach ($route[2] as $key) {
+ $string = null;
+ if (isset($params[$key])) {
+ $string = $params[$key];
+ unset($params[$key]);
+ } elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
+ $key = $key . '/';
+ }
+ $out = str_replace(':' . $key, $string, $out);
+ }
+
+ if (strpos($route[0], '*')) {
+ $out = str_replace('*', $params['pass'], $out);
+ }
+
+ return $out;
+ }
+/**
+ * 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) {
+ $_this =& Router::getInstance();
+ $named = array();
+
+ foreach ($params as $param => $val) {
+ if (isset($_this->named['rules'][$param])) {
+ $rule = $_this->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
+ */
+ 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;
+ }
+ $valueMatches = !isset($rule['match']) || preg_match(sprintf('/%s/', $rule['match']), $val);
+ return $valueMatches;
+ }
+/**
+ * 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;
+ }
+/**
+ * Normalizes a URL for purposes of comparison
+ *
+ * @param mixed $url URL to normalize
+ * @return string Normalized URL
+ * @access public
+ */
+ 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 array Matching route
+ * @access public
+ * @static
+ */
+ function requestRoute() {
+ $_this =& Router::getInstance();
+ return $_this->__currentRoute[0];
+ }
+/**
+ * Returns the route matching the current request (useful for requestAction traces)
+ *
+ * @return array Matching route
+ * @access public
+ * @static
+ */
+ function currentRoute() {
+ $_this =& Router::getInstance();
+ return $_this->__currentRoute[count($_this->__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;
+ }
+/**
+ * Strip escape characters from parameter values.
+ *
+ * @param mixed $param Either an array, or a string
+ * @return mixed Array or string escaped
+ * @access public
+ * @static
+ */
+ function stripEscape($param) {
+ $_this =& Router::getInstance();
+ if (!is_array($param) || empty($param)) {
+ if (is_bool($param)) {
+ return $param;
+ }
+
+ return preg_replace('/^(?:[\\t ]*(?:-!)+)/', '', $param);
+ }
+
+ foreach ($param as $key => $value) {
+ if (is_string($value)) {
+ $return[$key] = preg_replace('/^(?:[\\t ]*(?:-!)+)/', '', $value);
+ } else {
+ foreach ($value as $array => $string) {
+ $return[$key][$array] = $_this->stripEscape($string);
+ }
+ }
+ }
+ return $return;
+ }
+/**
+ * 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() {
+ $_this =& Router::getInstance();
+ $_this->__parseExtensions = true;
+ if (func_num_args() > 0) {
+ $_this->__validExtensions = func_get_args();
+ }
+ }
+/**
+ * Takes an 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()) {
+ $_this =& Router::getInstance();
+ $pass = $named = array();
+ $args = explode('/', $args);
+
+ $greedy = $_this->named['greedy'];
+ if (isset($options['greedy'])) {
+ $greedy = $options['greedy'];
+ }
+ $context = array();
+ if (isset($options['context'])) {
+ $context = $options['context'];
+ }
+ $rules = $_this->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;
+ }
+ $param = $_this->stripEscape($param);
+
+ $separatorIsPresent = strpos($param, $_this->named['separator']) !== false;
+ if ((!isset($options['named']) || !empty($options['named'])) && $separatorIsPresent) {
+ list($key, $val) = explode($_this->named['separator'], $param, 2);
+ $hasRule = isset($rules[$key]);
+ $passIt = (!$hasRule && !$greedy) || ($hasRule && !Router::matchNamed($key, $val, $rules[$key], $context));
+ if ($passIt) {
+ $pass[] = $param;
+ } else {
+ $named[$key] = $val;
+ }
+ } else {
+ $pass[] = $param;
+ }
+ }
+ return compact('pass', 'named');
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/sanitize.php b/cake/libs/sanitize.php
new file mode 100755
index 00000000..30d424fa
--- /dev/null
+++ b/cake/libs/sanitize.php
@@ -0,0 +1,308 @@
+ $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.
+ *
+ * @param string $string String from where to strip tags
+ * @param boolean $remove If true, the string is stripped of all HTML tags
+ * @return string Sanitized string
+ * @access public
+ * @static
+ */
+ function html($string, $remove = false) {
+ if ($remove) {
+ $string = strip_tags($string);
+ } else {
+ $patterns = array("/\&/", "/%/", "/", "/>/", '/"/', "/'/", "/\(/", "/\)/", "/\+/", "/-/");
+ $replacements = array("&", "%", "<", ">", """, "'", "(", ")", "+", "-");
+ $string = preg_replace($patterns, $replacements, $string);
+ }
+ return $string;
+ }
+/**
+ * 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 ',
+ 'javascriptblock' => '',
+ 'javascriptlink' => ''
+ );
+/**
+ * Holds options passed to codeBlock(), saved for when block is dumped to output
+ *
+ * @var array
+ * @access protected
+ * @see JavascriptHelper::codeBlock()
+ */
+ var $_blockOptions = array();
+/**
+ * Caches events written by event() for output at the end of page execution
+ *
+ * @var array
+ * @access protected
+ * @see JavascriptHelper::event()
+ */
+ var $_cachedEvents = array();
+/**
+ * Indicates whether generated events should be cached for later output (can be written at the
+ * end of the page, in the , or to an external file).
+ *
+ * @var boolean
+ * @access protected
+ * @see JavascriptHelper::event()
+ * @see JavascriptHelper::writeEvents()
+ */
+ var $_cacheEvents = false;
+/**
+ * Indicates whether cached events should be written to an external file
+ *
+ * @var boolean
+ * @access protected
+ * @see JavascriptHelper::event()
+ * @see JavascriptHelper::writeEvents()
+ */
+ var $_cacheToFile = false;
+/**
+ * Indicates whether *all* generated JavaScript should be cached for later output
+ *
+ * @var boolean
+ * @access protected
+ * @see JavascriptHelper::codeBlock()
+ * @see JavascriptHelper::blockEnd()
+ */
+ var $_cacheAll = false;
+/**
+ * Contains event rules attached with CSS selectors. Used with the event:Selectors JavaScript
+ * library.
+ *
+ * @var array
+ * @access protected
+ * @see JavascriptHelper::event()
+ * @link http://alternateidea.com/event-selectors/
+ */
+ var $_rules = array();
+/**
+ * @var string
+ * @access private
+ */
+ var $__scriptBuffer = null;
+/**
+ * Constructor. Checks for presence of native PHP JSON extension to use for object encoding
+ *
+ * @access public
+ */
+ function __construct($options = array()) {
+ if (!empty($options)) {
+ foreach ($options as $key => $val) {
+ if (is_numeric($key)) {
+ $key = $val;
+ $val = true;
+ }
+ switch ($key) {
+ case 'cache':
+
+ break;
+ case 'safe':
+ $this->safe = $val;
+ break;
+ }
+ }
+ }
+ $this->useNative = function_exists('json_encode');
+ return parent::__construct($options);
+ }
+/**
+ * Returns a JavaScript script tag.
+ *
+ * Options:
+ *
+ * - allowCache: boolean, designates whether this block is cacheable using the
+ * current cache settings.
+ * - safe: boolean, whether this block should be wrapped in CDATA tags. Defaults
+ * to helper's object configuration.
+ * - inline: whether the block should be printed inline, or written
+ * to cached for later output (i.e. $scripts_for_layout).
+ *
+ * @param string $script The JavaScript to be wrapped in SCRIPT tags.
+ * @param array $options Set of options:
+ * @return string The full SCRIPT element, with the JavaScript inside it, or null,
+ * if 'inline' is set to false.
+ */
+ function codeBlock($script = null, $options = array()) {
+ if (!empty($options) && !is_array($options)) {
+ $options = array('allowCache' => $options);
+ } elseif (empty($options)) {
+ $options = array();
+ }
+ $defaultOptions = array('allowCache' => true, 'safe' => true, 'inline' => true);
+ $options = array_merge($defaultOptions, $options);
+
+ if (empty($script)) {
+ $this->__scriptBuffer = @ob_get_contents();
+ $this->_blockOptions = $options;
+ $this->inBlock = true;
+ @ob_end_clean();
+ ob_start();
+ return null;
+ }
+ if ($this->_cacheEvents && $this->_cacheAll && $options['allowCache']) {
+ $this->_cachedEvents[] = $script;
+ return null;
+ }
+ if ($options['safe'] || $this->safe) {
+ $script = "\n" . '//' . "\n";
+ }
+ if ($options['inline']) {
+ return sprintf($this->tags['javascriptblock'], $script);
+ } else {
+ $view =& ClassRegistry::getObject('view');
+ $view->addScript(sprintf($this->tags['javascriptblock'], $script));
+ }
+ }
+/**
+ * Ends a block of cached JavaScript code
+ *
+ * @return mixed
+ */
+ function blockEnd() {
+ if (!isset($this->inBlock) || !$this->inBlock) {
+ return;
+ }
+ $script = @ob_get_contents();
+ @ob_end_clean();
+ ob_start();
+ echo $this->__scriptBuffer;
+ $this->__scriptBuffer = null;
+ $options = $this->_blockOptions;
+ $this->_blockOptions = array();
+ $this->inBlock = false;
+
+ if (empty($script)) {
+ return null;
+ }
+
+ return $this->codeBlock($script, $options);
+ }
+/**
+ * Returns a JavaScript include tag (SCRIPT element). If the filename is prefixed with "/",
+ * the path will be relative to the base path of your application. Otherwise, the path will
+ * be relative to your JavaScript path, usually webroot/js.
+ *
+ * @param mixed $url String URL to JavaScript file, or an array of URLs.
+ * @param boolean $inline If true, the tag will be printed inline,
+ * otherwise it will be printed in the , using $scripts_for_layout
+ * @see JS_URL
+ * @return string
+ */
+ function link($url, $inline = true) {
+ if (is_array($url)) {
+ $out = '';
+ foreach ($url as $i) {
+ $out .= "\n\t" . $this->link($i, $inline);
+ }
+ if ($inline) {
+ return $out . "\n";
+ }
+ return;
+ }
+
+ if (strpos($url, '://') === false) {
+ if ($url[0] !== '/') {
+ $url = JS_URL . $url;
+ }
+ if (strpos($url, '?') === false) {
+ if (strpos($url, '.js') === false) {
+ $url .= '.js';
+ }
+ }
+
+ $url = $this->webroot($url);
+ $timestampEnabled = (
+ (Configure::read('Asset.timestamp') === true && Configure::read() > 0) ||
+ Configure::read('Asset.timestamp') === 'force'
+ );
+
+ if (strpos($url, '?') === false && $timestampEnabled) {
+ $url .= '?' . @filemtime(WWW_ROOT . str_replace('/', DS, $url));
+ }
+
+ if (Configure::read('Asset.filter.js')) {
+ $url = str_replace(JS_URL, 'cjs/', $url);
+ }
+ }
+ $out = $this->output(sprintf($this->tags['javascriptlink'], $url));
+
+ if ($inline) {
+ return $out;
+ } else {
+ $view =& ClassRegistry::getObject('view');
+ $view->addScript($out);
+ }
+ }
+/**
+ * Escape carriage returns and single and double quotes for JavaScript segments.
+ *
+ * @param string $script string that might have javascript elements
+ * @return string escaped string
+ */
+ function escapeScript($script) {
+ $script = str_replace(array("\r\n", "\n", "\r"), '\n', $script);
+ $script = str_replace(array('"', "'"), array('\"', "\\'"), $script);
+ return $script;
+ }
+/**
+ * Escape a string to be JavaScript friendly.
+ *
+ * List of escaped ellements:
+ * + "\r\n" => '\n'
+ * + "\r" => '\n'
+ * + "\n" => '\n'
+ * + '"' => '\"'
+ * + "'" => "\\'"
+ *
+ * @param string $script String that needs to get escaped.
+ * @return string Escaped string.
+ */
+ function escapeString($string) {
+ App::import('Core', 'Multibyte');
+ $escape = array("\r\n" => "\n", "\r" => "\n");
+ $string = str_replace(array_keys($escape), array_values($escape), $string);
+ return $this->_utf8ToHex($string);
+ }
+/**
+ * Encode a string into JSON. Converts and escapes necessary characters.
+ *
+ * @return void
+ **/
+ function _utf8ToHex($string) {
+ $length = strlen($string);
+ $return = '';
+ for ($i = 0; $i < $length; ++$i) {
+ $ord = ord($string{$i});
+ switch (true) {
+ case $ord == 0x08:
+ $return .= '\b';
+ break;
+ case $ord == 0x09:
+ $return .= '\t';
+ break;
+ case $ord == 0x0A:
+ $return .= '\n';
+ break;
+ case $ord == 0x0C:
+ $return .= '\f';
+ break;
+ case $ord == 0x0D:
+ $return .= '\r';
+ break;
+ case $ord == 0x22:
+ case $ord == 0x2F:
+ case $ord == 0x5C:
+ case $ord == 0x27:
+ $return .= '\\' . $string{$i};
+ break;
+ case (($ord >= 0x20) && ($ord <= 0x7F)):
+ $return .= $string{$i};
+ break;
+ case (($ord & 0xE0) == 0xC0):
+ if ($i + 1 >= $length) {
+ $i += 1;
+ $return .= '?';
+ break;
+ }
+ $charbits = $string{$i} . $string{$i + 1};
+ $char = Multibyte::utf8($charbits);
+ $return .= sprintf('\u%04s', dechex($char[0]));
+ $i += 1;
+ break;
+ case (($ord & 0xF0) == 0xE0):
+ if ($i + 2 >= $length) {
+ $i += 2;
+ $return .= '?';
+ break;
+ }
+ $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2};
+ $char = Multibyte::utf8($charbits);
+ $return .= sprintf('\u%04s', dechex($char[0]));
+ $i += 2;
+ break;
+ case (($ord & 0xF8) == 0xF0):
+ if ($i + 3 >= $length) {
+ $i += 3;
+ $return .= '?';
+ break;
+ }
+ $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3};
+ $char = Multibyte::utf8($charbits);
+ $return .= sprintf('\u%04s', dechex($char[0]));
+ $i += 3;
+ break;
+ case (($ord & 0xFC) == 0xF8):
+ if ($i + 4 >= $length) {
+ $i += 4;
+ $return .= '?';
+ break;
+ }
+ $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3} . $string{$i + 4};
+ $char = Multibyte::utf8($charbits);
+ $return .= sprintf('\u%04s', dechex($char[0]));
+ $i += 4;
+ break;
+ case (($ord & 0xFE) == 0xFC):
+ if ($i + 5 >= $length) {
+ $i += 5;
+ $return .= '?';
+ break;
+ }
+ $charbits = $string{$i} . $string{$i + 1} . $string{$i + 2} . $string{$i + 3} . $string{$i + 4} . $string{$i + 5};
+ $char = Multibyte::utf8($charbits);
+ $return .= sprintf('\u%04s', dechex($char[0]));
+ $i += 5;
+ break;
+ }
+ }
+ return $return;
+ }
+/**
+ * Attach an event to an element. Used with the Prototype library.
+ *
+ * @param string $object Object to be observed
+ * @param string $event event to observe
+ * @param string $observer function to call
+ * @param array $options Set options: useCapture, allowCache, safe
+ * @return boolean true on success
+ */
+ function event($object, $event, $observer = null, $options = array()) {
+ if (!empty($options) && !is_array($options)) {
+ $options = array('useCapture' => $options);
+ } else if (empty($options)) {
+ $options = array();
+ }
+
+ $defaultOptions = array('useCapture' => false);
+ $options = array_merge($defaultOptions, $options);
+
+ if ($options['useCapture'] == true) {
+ $options['useCapture'] = 'true';
+ } else {
+ $options['useCapture'] = 'false';
+ }
+ $isObject = (
+ strpos($object, 'window') !== false || strpos($object, 'document') !== false ||
+ strpos($object, '$(') !== false || strpos($object, '"') !== false ||
+ strpos($object, '\'') !== false
+ );
+
+ if ($isObject) {
+ $b = "Event.observe({$object}, '{$event}', function(event) { {$observer} }, ";
+ $b .= "{$options['useCapture']});";
+ } elseif ($object[0] == '\'') {
+ $b = "Event.observe(" . substr($object, 1) . ", '{$event}', function(event) { ";
+ $b .= "{$observer} }, {$options['useCapture']});";
+ } else {
+ $chars = array('#', ' ', ', ', '.', ':');
+ $found = false;
+ foreach ($chars as $char) {
+ if (strpos($object, $char) !== false) {
+ $found = true;
+ break;
+ }
+ }
+ if ($found) {
+ $this->_rules[$object] = $event;
+ } else {
+ $b = "Event.observe(\$('{$object}'), '{$event}', function(event) { ";
+ $b .= "{$observer} }, {$options['useCapture']});";
+ }
+ }
+
+ if (isset($b) && !empty($b)) {
+ if ($this->_cacheEvents === true) {
+ $this->_cachedEvents[] = $b;
+ return;
+ } else {
+ return $this->codeBlock($b, array_diff_key($options, $defaultOptions));
+ }
+ }
+ }
+/**
+ * Cache JavaScript events created with event()
+ *
+ * @param boolean $file If true, code will be written to a file
+ * @param boolean $all If true, all code written with JavascriptHelper will be sent to a file
+ * @return null
+ */
+ function cacheEvents($file = false, $all = false) {
+ $this->_cacheEvents = true;
+ $this->_cacheToFile = $file;
+ $this->_cacheAll = $all;
+ }
+/**
+ * Gets (and clears) the current JavaScript event cache
+ *
+ * @param boolean $clear
+ * @return string
+ */
+ function getCache($clear = true) {
+ $out = '';
+ $rules = array();
+
+ if (!empty($this->_rules)) {
+ foreach ($this->_rules as $sel => $event) {
+ $rules[] = "\t'{$sel}': function(element, event) {\n\t\t{$event}\n\t}";
+ }
+ }
+ $data = implode("\n", $this->_cachedEvents);
+
+ if (!empty($rules)) {
+ $data .= "\nvar Rules = {\n" . implode(",\n\n", $rules) . "\n}";
+ $data .= "\nEventSelectors.start(Rules);\n";
+ }
+ if ($clear) {
+ $this->_rules = array();
+ $this->_cacheEvents = false;
+ $this->_cachedEvents = array();
+ }
+ return $data;
+ }
+/**
+ * Write cached JavaScript events
+ *
+ * @param boolean $inline If true, returns JavaScript event code. Otherwise it is added to the
+ * output of $scripts_for_layout in the layout.
+ * @param array $options Set options for codeBlock
+ * @return string
+ */
+ function writeEvents($inline = true, $options = array()) {
+ $out = '';
+ $rules = array();
+
+ if (!$this->_cacheEvents) {
+ return;
+ }
+ $data = $this->getCache();
+
+ if (empty($data)) {
+ return;
+ }
+
+ if ($this->_cacheToFile) {
+ $filename = md5($data);
+ if (!file_exists(JS . $filename . '.js')) {
+ cache(str_replace(WWW_ROOT, '', JS) . $filename . '.js', $data, '+999 days', 'public');
+ }
+ $out = $this->link($filename);
+ } else {
+ $out = $this->codeBlock("\n" . $data . "\n", $options);
+ }
+
+ if ($inline) {
+ return $out;
+ } else {
+ $view =& ClassRegistry::getObject('view');
+ $view->addScript($out);
+ }
+ }
+/**
+ * Includes the Prototype Javascript library (and anything else) inside a single script tag.
+ *
+ * Note: The recommended approach is to copy the contents of
+ * javascripts into your application's
+ * public/javascripts/ directory, and use @see javascriptIncludeTag() to
+ * create remote script links.
+ *
+ * @param string $script Script file to include
+ * @param array $options Set options for codeBlock
+ * @return string script with all javascript in/javascripts folder
+ */
+ function includeScript($script = "", $options = array()) {
+ if ($script == "") {
+ $files = scandir(JS);
+ $javascript = '';
+
+ foreach ($files as $file) {
+ if (substr($file, -3) == '.js') {
+ $javascript .= file_get_contents(JS . "{$file}") . "\n\n";
+ }
+ }
+ } else {
+ $javascript = file_get_contents(JS . "$script.js") . "\n\n";
+ }
+ return $this->codeBlock("\n\n" . $javascript, $options);
+ }
+/**
+ * Generates a JavaScript object in JavaScript Object Notation (JSON)
+ * from an array
+ *
+ * ### Options
+ *
+ * - block - Wraps the return value in a script tag if true. Default is false
+ * - prefix - Prepends the string to the returned data. Default is ''
+ * - postfix - Appends the string to the returned data. Default is ''
+ * - stringKeys - A list of array keys to be treated as a string.
+ * - quoteKeys - If false treats $stringKeys as a list of keys **not** to be quoted. Default is true.
+ * - q - The type of quote to use. Default is "'"
+ *
+ * @param array $data Data to be converted
+ * @param array $options Set of options: block, prefix, postfix, stringKeys, quoteKeys, q
+ * @param string $prefix DEPRECATED, use $options['prefix'] instead. Prepends the string to the returned data
+ * @param string $postfix DEPRECATED, use $options['postfix'] instead. Appends the string to the returned data
+ * @param array $stringKeys DEPRECATED, use $options['stringKeys'] instead. A list of array keys to be treated as a string
+ * @param boolean $quoteKeys DEPRECATED, use $options['quoteKeys'] instead. If false, treats $stringKey as a list of keys *not* to be quoted
+ * @param string $q DEPRECATED, use $options['q'] instead. The type of quote to use
+ * @return string A JSON code block
+ */
+ function object($data = array(), $options = array(), $prefix = null, $postfix = null, $stringKeys = null, $quoteKeys = null, $q = null) {
+ if (!empty($options) && !is_array($options)) {
+ $options = array('block' => $options);
+ } else if (empty($options)) {
+ $options = array();
+ }
+
+ $defaultOptions = array(
+ 'block' => false, 'prefix' => '', 'postfix' => '',
+ 'stringKeys' => array(), 'quoteKeys' => true, 'q' => '"'
+ );
+ $options = array_merge($defaultOptions, $options, array_filter(compact(array_keys($defaultOptions))));
+
+ if (is_object($data)) {
+ $data = get_object_vars($data);
+ }
+
+ $out = $keys = array();
+ $numeric = true;
+
+ if ($this->useNative) {
+ $rt = json_encode($data);
+ } else {
+ if (is_null($data)) {
+ return 'null';
+ }
+ if (is_bool($data)) {
+ return $data ? 'true' : 'false';
+ }
+
+ if (is_array($data)) {
+ $keys = array_keys($data);
+ }
+
+ if (!empty($keys)) {
+ $numeric = (array_values($keys) === array_keys(array_values($keys)));
+ }
+
+ foreach ($data as $key => $val) {
+ if (is_array($val) || is_object($val)) {
+ $val = $this->object($val, array_merge($options, array('block' => false)));
+ } else {
+ $quoteStrings = (
+ !count($options['stringKeys']) ||
+ ($options['quoteKeys'] && in_array($key, $options['stringKeys'], true)) ||
+ (!$options['quoteKeys'] && !in_array($key, $options['stringKeys'], true))
+ );
+ $val = $this->value($val, $quoteStrings);
+ }
+ if (!$numeric) {
+ $val = $options['q'] . $this->value($key, false) . $options['q'] . ':' . $val;
+ }
+ $out[] = $val;
+ }
+
+ if (!$numeric) {
+ $rt = '{' . join(',', $out) . '}';
+ } else {
+ $rt = '[' . join(',', $out) . ']';
+ }
+ }
+ $rt = $options['prefix'] . $rt . $options['postfix'];
+
+ if ($options['block']) {
+ $rt = $this->codeBlock($rt, array_diff_key($options, $defaultOptions));
+ }
+
+ return $rt;
+ }
+/**
+ * Converts a PHP-native variable of any type to a JSON-equivalent representation
+ *
+ * @param mixed $val A PHP variable to be converted to JSON
+ * @param boolean $quoteStrings If false, leaves string values unquoted
+ * @return string a JavaScript-safe/JSON representation of $val
+ */
+ function value($val, $quoteStrings = true) {
+ switch (true) {
+ case (is_array($val) || is_object($val)):
+ $val = $this->object($val);
+ break;
+ case ($val === null):
+ $val = 'null';
+ break;
+ case (is_bool($val)):
+ $val = ife($val, 'true', 'false');
+ break;
+ case (is_int($val)):
+ $val = $val;
+ break;
+ case (is_float($val)):
+ $val = sprintf("%.11f", $val);
+ break;
+ default:
+ $val = $this->escapeString($val);
+ if ($quoteStrings) {
+ $val = '"' . $val . '"';
+ }
+ break;
+ }
+ return $val;
+ }
+/**
+ * AfterRender callback. Writes any cached events to the view, or to a temp file.
+ *
+ * @return null
+ */
+ function afterRender() {
+ if (!$this->enabled) {
+ return;
+ }
+ echo $this->writeEvents(true);
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/view/helpers/js.php b/cake/libs/view/helpers/js.php
new file mode 100755
index 00000000..15e25099
--- /dev/null
+++ b/cake/libs/view/helpers/js.php
@@ -0,0 +1,451 @@
+effectMap = array_combine(
+ array_map('strtolower', $this->effectMap),
+ $this->effectMap
+ );
+ parent::__construct();
+ }
+
+ function call__($method, $params) {
+ if (is_object($this->hook) && method_exists($this->hook, $method)) {
+ $this->hook->dispatchMethod($method . '_', $params);
+ }
+ if (method_exists($this, $method . '_')) {
+ return $this->dispatchMethod($method . '_', $params);
+ }
+ }
+
+ function alert_($message) {
+ return 'alert("' . $this->escape($message) . '");';
+ }
+
+ function if_($if, $then, $else = null, $elseIf = array()) {
+ $len = strlen($if) - 1;
+ if ($if{$len} == ';') {
+ $if{$len} = null;
+ }
+
+ $out = 'if (' . $if . ') { ' . $then . ' }';
+
+ foreach ($elseIf as $cond => $exec) {
+ //$out .=
+ }
+
+ if (!empty($else)) {
+ $out .= ' else { ' . $else . ' }';
+ }
+
+ return $out;
+ }
+
+ function confirm_($message) {
+ return 'confirm("' . $this->escape($message) . '");';
+ }
+
+ function prompt_($message, $default = '') {
+ return 'prompt("' . $this->escape($message) . '", "' . $this->escape($default) . '");';
+ }
+/*
+ * Tries a series of expressions, and executes after first successful completion.
+ * (See Prototype's Try.these).
+ *
+ * @return string
+ */
+ function tryThese_($expr1, $expr2, $expr3) {
+ }
+/**
+ * Loads a remote URL
+ *
+ * @param string $url
+ * @param array $options
+ * @return string
+ */
+ function load_($url = null, $options = array()) {
+
+ if (isset($options['update'])) {
+ if (!is_array($options['update'])) {
+ $func = "new Ajax.Updater('{$options['update']}',";
+ } else {
+ $func = "new Ajax.Updater(document.createElement('div'),";
+ }
+ if (!isset($options['requestHeaders'])) {
+ $options['requestHeaders'] = array();
+ }
+ if (is_array($options['update'])) {
+ $options['update'] = join(' ', $options['update']);
+ }
+ $options['requestHeaders']['X-Update'] = $options['update'];
+ } else {
+ $func = "new Ajax.Request(";
+ }
+
+ $func .= "'" . Router::url($url) . "'";
+ $ajax =& new AjaxHelper();
+ $func .= ", " . $ajax->__optionsForAjax($options) . ")";
+
+ if (isset($options['before'])) {
+ $func = "{$options['before']}; $func";
+ }
+ if (isset($options['after'])) {
+ $func = "$func; {$options['after']};";
+ }
+ if (isset($options['condition'])) {
+ $func = "if ({$options['condition']}) { $func; }";
+ }
+ if (isset($options['confirm'])) {
+ $func = "if (confirm('" . $this->Javascript->escapeString($options['confirm'])
+ . "')) { $func; } else { return false; }";
+ }
+ return $func;
+ }
+/**
+ * Redirects to a URL
+ *
+ * @param mixed $url
+ * @param array $options
+ * @return string
+ */
+ function redirect_($url = null) {
+ return 'window.location = "' . Router::url($url) . '";';
+ }
+/**
+ * Escape a string to be JavaScript friendly.
+ *
+ * List of escaped ellements:
+ * + "\r\n" => '\n'
+ * + "\r" => '\n'
+ * + "\n" => '\n'
+ * + '"' => '\"'
+ * + "'" => "\\'"
+ *
+ * @param string $script String that needs to get escaped.
+ * @return string Escaped string.
+ */
+ function escape($string) {
+ $escape = array("\r\n" => '\n', "\r" => '\n', "\n" => '\n', '"' => '\"', "'" => "\\'");
+ return str_replace(array_keys($escape), array_values($escape), $string);
+ }
+
+ function get__($name) {
+ return $this->__object($name, 'id');
+ }
+
+ function select($pattern) {
+ return $this->__object($pattern, 'pattern');
+ }
+
+ function real($var) {
+ return $this->__object($var, 'real');
+ }
+
+ function __object($name, $var) {
+ if (!isset($this->__objects[$name])) {
+ $this->__objects[$name] = new JsHelperObject($this);
+ $this->__objects[$name]->{$var} = $name;
+ }
+ return $this->__objects[$name];
+ }
+/**
+ * Generates a JavaScript object in JavaScript Object Notation (JSON)
+ * from an array
+ *
+ * @param array $data Data to be converted
+ * @param boolean $block Wraps return value in a block if true
+ * @param string $prefix Prepends the string to the returned data
+ * @param string $postfix Appends the string to the returned data
+ * @param array $stringKeys A list of array keys to be treated as a string
+ * @param boolean $quoteKeys If false, treats $stringKey as a list of keys *not* to be quoted
+ * @param string $q The type of quote to use
+ * @return string A JSON code block
+ */
+ function object($data = array(), $block = false, $prefix = '', $postfix = '', $stringKeys = array(), $quoteKeys = true, $q = "\"") {
+ if (is_object($data)) {
+ $data = get_object_vars($data);
+ }
+
+ $out = array();
+ $key = array();
+
+ if (is_array($data)) {
+ $keys = array_keys($data);
+ }
+
+ $numeric = true;
+
+ if (!empty($keys)) {
+ foreach ($keys as $key) {
+ if (!is_numeric($key)) {
+ $numeric = false;
+ break;
+ }
+ }
+ }
+
+ foreach ($data as $key => $val) {
+ if (is_array($val) || is_object($val)) {
+ $val = $this->object($val, false, '', '', $stringKeys, $quoteKeys, $q);
+ } else {
+ if ((!count($stringKeys) && !is_numeric($val) && !is_bool($val)) || ($quoteKeys && in_array($key, $stringKeys)) || (!$quoteKeys && !in_array($key, $stringKeys)) && $val !== null) {
+ $val = $q . $this->escapeString($val) . $q;
+ }
+ if ($val == null) {
+ $val = 'null';
+ }
+ }
+
+ if (!$numeric) {
+ $val = $q . $key . $q . ':' . $val;
+ }
+
+ $out[] = $val;
+ }
+
+ if (!$numeric) {
+ $rt = '{' . join(', ', $out) . '}';
+ } else {
+ $rt = '[' . join(', ', $out) . ']';
+ }
+ $rt = $prefix . $rt . $postfix;
+
+ if ($block) {
+ $rt = $this->codeBlock($rt);
+ }
+
+ return $rt;
+ }
+}
+
+class JsHelperObject {
+ var $__parent = null;
+
+ var $id = null;
+
+ var $pattern = null;
+
+ var $real = null;
+
+ function __construct(&$parent) {
+ if (is_object($parent)) {
+ $this->setParent($parent);
+ }
+ }
+
+ function toString() {
+ return $this->__toString();
+ }
+
+ function __toString() {
+ return $this->literal;
+ }
+
+ function ref($ref = null) {
+ if ($ref == null) {
+ foreach (array('id', 'pattern', 'real') as $ref) {
+ if ($this->{$ref} !== null) {
+ return $this->{$ref};
+ }
+ }
+ } else {
+ return ($this->{$ref} !== null);
+ }
+ return null;
+ }
+
+ function literal($append = null) {
+ if (!empty($this->id)) {
+ $data = '$("' . $this->id . '")';
+ }
+ if (!empty($this->pattern)) {
+ $data = '$$("' . $this->pattern . '")';
+ }
+ if (!empty($this->real)) {
+ $data = $this->real;
+ }
+ if (!empty($append)) {
+ $data .= '.' . $append;
+ }
+ return $data;
+ }
+
+ function __call($name, $args) {
+ $data = '';
+
+ if (isset($this->__parent->effectMap[strtolower($name)])) {
+ array_unshift($args, $this->__parent->effectMap[strtolower($name)]);
+ $name = 'effect';
+ }
+
+ switch ($name) {
+ case 'effect':
+ case 'visualEffect':
+
+ if (strpos($args[0], '_') || $args[0]{0} != strtoupper($args[0]{0})) {
+ $args[0] = Inflector::camelize($args[0]);
+ }
+
+ if (strtolower($args[0]) == 'highlight') {
+ $data .= 'new ';
+ }
+ if ($this->pattern == null) {
+ $data .= 'Effect.' . $args[0] . '(' . $this->literal();
+ } else {
+ $data .= 'Effect.' . $args[0] . '(item';
+ }
+
+ if (isset($args[1]) && is_array($args[1])) {
+ $data .= ', {' . $this->__options($args[1]) . '}';
+ }
+ $data .= ');';
+
+ if ($this->pattern !== null) {
+ $data = $this->each($data);
+ }
+ break;
+ case 'remove':
+ case 'toggle':
+ case 'show':
+ case 'hide':
+ if (empty($args)) {
+ $obj = 'Element';
+ $params = '';
+ } else {
+ $obj = 'Effect';
+ $params = ', "' . $args[0] . '"';
+ }
+
+ if ($this->pattern != null) {
+ $data = $this->each($obj . ".{$name}(item);");
+ } else {
+ $data = $obj . ".{$name}(" . $this->literal() . ');';
+ }
+ break;
+ case 'visible':
+ $data = $this->literal() . '.visible();';
+ break;
+ case 'update':
+ $data = $this->literal() . ".update({$args[0]});";
+ break;
+ case 'load':
+ $data = 'new Ajax.Updater("' . $this->id . '", "' . $args[0] . '"';
+ if (isset($args[1]) && is_array($args[1])) {
+ $data .= ', {' . $this->__options($args[1]) . '}';
+ }
+ $data .= ');';
+ break;
+ case 'each':
+ case 'all':
+ case 'any':
+ case 'detect':
+ case 'findAll':
+ if ($this->pattern != null) {
+ $data = $this->__iterate($name, $args[0]);
+ }
+ break;
+ case 'addClass':
+ case 'removeClass':
+ case 'hasClass':
+ case 'toggleClass':
+ $data = $this->literal() . ".{$name}Name(\"{$args[0]}\");";
+ break;
+ case 'clone':
+ case 'inspect':
+ case 'keys':
+ case 'values':
+ $data = "Object.{$name}(" . $this->literal() . ");";
+ break;
+ case 'extend':
+ $data = "Object.extend(" . $this->literal() . ", {$args[0]});";
+ break;
+ case '...':
+ // Handle other methods here
+ // including interfaces to load other files on-the-fly
+ // that add support for additional methods/replacing existing methods
+ break;
+ default:
+ $data = $this->literal() . '.' . $name . '();';
+ break;
+ }
+
+ if ($this->__parent->output) {
+ echo $data;
+ } else {
+ return $data;
+ }
+ }
+
+ function __iterate($method, $data) {
+ return '$$("' . $this->pattern . '").' . $method . '(function(item) {' . $data . '});';
+ }
+
+ function setParent(&$parent) {
+ $this->__parent =& $parent;
+ }
+
+ function __options($opts) {
+ $options = array();
+ foreach ($opts as $key => $val) {
+ if (!is_int($val)) {
+ $val = '"' . $val . '"';
+ }
+ $options[] = $key . ':' . $val;
+ }
+ return join(', ', $options);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/view/helpers/number.php b/cake/libs/view/helpers/number.php
new file mode 100755
index 00000000..fec43d66
--- /dev/null
+++ b/cake/libs/view/helpers/number.php
@@ -0,0 +1,194 @@
+precision($size / 1024, 0));
+ case round($size / 1024 / 1024, 2) < 1024:
+ return sprintf(__('%.2f MB', true), $this->precision($size / 1024 / 1024, 2));
+ case round($size / 1024 / 1024 / 1024, 2) < 1024:
+ return sprintf(__('%.2f GB', true), $this->precision($size / 1024 / 1024 / 1024, 2));
+ default:
+ return sprintf(__('%.2f TB', true), $this->precision($size / 1024 / 1024 / 1024 / 1024, 2));
+ }
+ }
+/**
+ * Formats a number into a percentage string.
+ *
+ * @param float $number A floating point number
+ * @param integer $precision The precision of the returned number
+ * @return string Percentage string
+ * @static
+ */
+ function toPercentage($number, $precision = 2) {
+ return $this->precision($number, $precision) . '%';
+ }
+/**
+ * Formats a number into a currency format.
+ *
+ * @param float $number A floating point number
+ * @param integer $options if int then places, if string then before, if (,.-) then use it
+ * or array with places and before keys
+ * @return string formatted number
+ * @static
+ */
+ function format($number, $options = false) {
+ $places = 0;
+ if (is_int($options)) {
+ $places = $options;
+ }
+
+ $separators = array(',', '.', '-', ':');
+
+ $before = $after = null;
+ if (is_string($options) && !in_array($options, $separators)) {
+ $before = $options;
+ }
+ $thousands = ',';
+ if (!is_array($options) && in_array($options, $separators)) {
+ $thousands = $options;
+ }
+ $decimals = '.';
+ if (!is_array($options) && in_array($options, $separators)) {
+ $decimals = $options;
+ }
+
+ $escape = true;
+ if (is_array($options)) {
+ $options = array_merge(array('before'=>'$', 'places' => 2, 'thousands' => ',', 'decimals' => '.'), $options);
+ extract($options);
+ }
+
+ $out = $before . number_format($number, $places, $decimals, $thousands) . $after;
+
+ if ($escape) {
+ return h($out);
+ }
+ return $out;
+ }
+/**
+ * Formats a number into a currency format.
+ *
+ * @param float $number
+ * @param string $currency Shortcut to default options. Valid values are 'USD', 'EUR', 'GBP', otherwise
+ * set at least 'before' and 'after' options.
+ * @param array $options
+ * @return string Number formatted as a currency.
+ */
+ function currency($number, $currency = 'USD', $options = array()) {
+ $default = array(
+ 'before'=>'', 'after' => '', 'zero' => '0', 'places' => 2, 'thousands' => ',',
+ 'decimals' => '.','negative' => '()', 'escape' => true
+ );
+ $currencies = array(
+ 'USD' => array(
+ 'before' => '$', 'after' => 'c', 'zero' => 0, 'places' => 2, 'thousands' => ',',
+ 'decimals' => '.', 'negative' => '()', 'escape' => true
+ ),
+ 'GBP' => array(
+ 'before'=>'£', 'after' => 'p', 'zero' => 0, 'places' => 2, 'thousands' => ',',
+ 'decimals' => '.', 'negative' => '()','escape' => false
+ ),
+ 'EUR' => array(
+ 'before'=>'€', 'after' => 'c', 'zero' => 0, 'places' => 2, 'thousands' => ',',
+ 'decimals' => '.', 'negative' => '()', 'escape' => false
+ )
+ ,'AUD' => array(
+ 'before' => '$', 'after' => 'c', 'zero' => 0, 'places' => 2, 'thousands' => ',',
+ 'decimals' => '.','negative' => '()', 'escape' => true
+ )
+ );
+
+ if (isset($currencies[$currency])) {
+ $default = $currencies[$currency];
+ } elseif (is_string($currency)) {
+ $options['before'] = $currency;
+ }
+
+ $options = array_merge($default, $options);
+
+ $result = null;
+
+ if ($number == 0 ) {
+ if ($options['zero'] !== 0 ) {
+ return $options['zero'];
+ }
+ $options['after'] = null;
+ } elseif ($number < 1 && $number > -1 ) {
+ $multiply = intval('1' . str_pad('', $options['places'], '0'));
+ $number = $number * $multiply;
+ $options['before'] = null;
+ $options['places'] = null;
+ } elseif (empty($options['before'])) {
+ $options['before'] = null;
+ } else {
+ $options['after'] = null;
+ }
+
+ $abs = abs($number);
+ $result = $this->format($abs, $options);
+
+ if ($number < 0 ) {
+ if ($options['negative'] == '()') {
+ $result = '(' . $result .')';
+ } else {
+ $result = $options['negative'] . $result;
+ }
+ }
+ return $result;
+ }
+}
+?>
diff --git a/cake/libs/view/helpers/paginator.php b/cake/libs/view/helpers/paginator.php
new file mode 100755
index 00000000..7b5dd09a
--- /dev/null
+++ b/cake/libs/view/helpers/paginator.php
@@ -0,0 +1,644 @@
+$options['format'] Format of the counter. Supported formats are 'range' and 'pages'
+ * and custom (default). In the default mode the supplied string is parsed and constants are replaced
+ * by their actual values.
+ * Constants: %page%, %pages%, %current%, %count%, %start%, %end% .
+ * - $options['separator'] The separator of the actual page and number of pages (default: ' of ').
+ * - $options['url'] Url of the action. See Router::url()
+ * - $options['url']['sort'] the key that the recordset is sorted.
+ * - $options['url']['direction'] Direction of the sorting (default: 'asc').
+ * - $options['url']['page'] Page # to display.
+ * - $options['model'] The name of the model.
+ * - $options['escape'] Defines if the title field for the link should be escaped (default: true).
+ * - $options['update'] DOM id of the element updated with the results of the AJAX call.
+ * If this key isn't specified Paginator will use plain HTML links.
+ * - $options['indicator'] DOM id of the element that will be shown when doing AJAX requests.
+ *
+ * @var array
+ */
+ var $options = array();
+/**
+ * Gets the current paging parameters from the resultset for the given model
+ *
+ * @param string $model Optional model name. Uses the default if none is specified.
+ * @return array The array of paging parameters for the paginated resultset.
+ */
+ function params($model = null) {
+ if (empty($model)) {
+ $model = $this->defaultModel();
+ }
+ if (!isset($this->params['paging']) || empty($this->params['paging'][$model])) {
+ return null;
+ }
+ return $this->params['paging'][$model];
+ }
+/**
+ * Sets default options for all pagination links
+ *
+ * @param mixed $options Default options for pagination links. If a string is supplied - it
+ * is used as the DOM id element to update. See #options for list of keys.
+ */
+ function options($options = array()) {
+ if (is_string($options)) {
+ $options = array('update' => $options);
+ }
+
+ if (!empty($options['paging'])) {
+ if (!isset($this->params['paging'])) {
+ $this->params['paging'] = array();
+ }
+ $this->params['paging'] = array_merge($this->params['paging'], $options['paging']);
+ unset($options['paging']);
+ }
+ $model = $this->defaultModel();
+
+ if (!empty($options[$model])) {
+ if (!isset($this->params['paging'][$model])) {
+ $this->params['paging'][$model] = array();
+ }
+ $this->params['paging'][$model] = array_merge($this->params['paging'][$model], $options[$model]);
+ unset($options[$model]);
+ }
+ $this->options = array_filter(array_merge($this->options, $options));
+ }
+/**
+ * Gets the current page of the recordset for the given model
+ *
+ * @param string $model Optional model name. Uses the default if none is specified.
+ * @return string The current page number of the recordset.
+ */
+ function current($model = null) {
+ $params = $this->params($model);
+
+ if (isset($params['page'])) {
+ return $params['page'];
+ }
+ return 1;
+ }
+/**
+ * Gets the current key by which the recordset is sorted
+ *
+ * @param string $model Optional model name. Uses the default if none is specified.
+ * @param mixed $options Options for pagination links. See #options for list of keys.
+ * @return string The name of the key by which the recordset is being sorted, or
+ * null if the results are not currently sorted.
+ */
+ function sortKey($model = null, $options = array()) {
+ if (empty($options)) {
+ $params = $this->params($model);
+ $options = array_merge($params['defaults'], $params['options']);
+ }
+
+ if (isset($options['sort']) && !empty($options['sort'])) {
+ if (preg_match('/(?:\w+\.)?(\w+)/', $options['sort'], $result) && isset($result[1])) {
+ if ($result[0] == $this->defaultModel()) {
+ return $result[1];
+ }
+ }
+ return $options['sort'];
+ } elseif (isset($options['order']) && is_array($options['order'])) {
+ return key($options['order']);
+ } elseif (isset($options['order']) && is_string($options['order'])) {
+ if (preg_match('/(?:\w+\.)?(\w+)/', $options['order'], $result) && isset($result[1])) {
+ return $result[1];
+ }
+ return $options['order'];
+ }
+ return null;
+ }
+/**
+ * Gets the current direction the recordset is sorted
+ *
+ * @param string $model Optional model name. Uses the default if none is specified.
+ * @param mixed $options Options for pagination links. See #options for list of keys.
+ * @return string The direction by which the recordset is being sorted, or
+ * null if the results are not currently sorted.
+ */
+ function sortDir($model = null, $options = array()) {
+ $dir = null;
+
+ if (empty($options)) {
+ $params = $this->params($model);
+ $options = array_merge($params['defaults'], $params['options']);
+ }
+
+ if (isset($options['direction'])) {
+ $dir = strtolower($options['direction']);
+ } elseif (isset($options['order']) && is_array($options['order'])) {
+ $dir = strtolower(current($options['order']));
+ }
+
+ if ($dir == 'desc') {
+ return 'desc';
+ }
+ return 'asc';
+ }
+/**
+ * Generates a "previous" link for a set of paged records
+ *
+ * @param string $title Title for the link. Defaults to '<< Previous'.
+ * @param mixed $options Options for pagination link. See #options for list of keys.
+ * @param string $disabledTitle Title when the link is disabled.
+ * @param mixed $disabledOptions Options for the disabled pagination link. See #options for list of keys.
+ * @return string A "previous" link or $disabledTitle text if the link is disabled.
+ */
+ function prev($title = '<< Previous', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
+ return $this->__pagingLink('Prev', $title, $options, $disabledTitle, $disabledOptions);
+ }
+/**
+ * Generates a "next" link for a set of paged records
+ *
+ * @param string $title Title for the link. Defaults to 'Next >>'.
+ * @param mixed $options Options for pagination link. See #options for list of keys.
+ * @param string $disabledTitle Title when the link is disabled.
+ * @param mixed $disabledOptions Options for the disabled pagination link. See #options for list of keys.
+ * @return string A "next" link or or $disabledTitle text if the link is disabled.
+ */
+ function next($title = 'Next >>', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
+ return $this->__pagingLink('Next', $title, $options, $disabledTitle, $disabledOptions);
+ }
+/**
+ * Generates a sorting link
+ *
+ * @param string $title Title for the link.
+ * @param string $key The name of the key that the recordset should be sorted.
+ * @param array $options Options for sorting link. See #options for list of keys.
+ * @return string A link sorting default by 'asc'. If the resultset is sorted 'asc' by the specified
+ * key the returned link will sort by 'desc'.
+ */
+ function sort($title, $key = null, $options = array()) {
+ $options = array_merge(array('url' => array(), 'model' => null), $options);
+ $url = $options['url'];
+ unset($options['url']);
+
+ if (empty($key)) {
+ $key = $title;
+ $title = __(Inflector::humanize(preg_replace('/_id$/', '', $title)), true);
+ }
+ $dir = 'asc';
+ $sortKey = $this->sortKey($options['model']);
+ $isSorted = ($sortKey === $key || $sortKey === $this->defaultModel() . '.' . $key);
+
+ if ($isSorted && $this->sortDir($options['model']) === 'asc') {
+ $dir = 'desc';
+ }
+
+ if (is_array($title) && array_key_exists($dir, $title)) {
+ $title = $title[$dir];
+ }
+
+ $url = array_merge(array('sort' => $key, 'direction' => $dir), $url, array('order' => null));
+ return $this->link($title, $url, $options);
+ }
+/**
+ * Generates a plain or Ajax link with pagination parameters
+ *
+ * @param string $title Title for the link.
+ * @param mixed $url Url for the action. See Router::url()
+ * @param array $options Options for the link. See #options for list of keys.
+ * @return string A link with pagination parameters.
+ */
+ function link($title, $url = array(), $options = array()) {
+ $options = array_merge(array('model' => null, 'escape' => true), $options);
+ $model = $options['model'];
+ unset($options['model']);
+
+ if (!empty($this->options)) {
+ $options = array_merge($this->options, $options);
+ }
+ if (isset($options['url'])) {
+ $url = array_merge((array)$options['url'], (array)$url);
+ unset($options['url']);
+ }
+ $url = $this->url($url, true, $model);
+
+ $obj = isset($options['update']) ? 'Ajax' : 'Html';
+ $url = array_merge(array('page' => $this->current($model)), $url);
+ $url = array_merge(Set::filter($url, true), array_intersect_key($url, array('plugin'=>true)));
+ return $this->{$obj}->link($title, $url, $options);
+ }
+/**
+ * Merges passed URL options with current pagination state to generate a pagination URL.
+ *
+ * @param array $options Pagination/URL options array
+ * @param boolean $asArray
+ * @param string $model Which model to paginate on
+ * @return mixed By default, returns a full pagination URL string for use in non-standard contexts (i.e. JavaScript)
+ */
+ function url($options = array(), $asArray = false, $model = null) {
+ $paging = $this->params($model);
+ $url = array_merge(array_filter(Set::diff(array_merge($paging['defaults'], $paging['options']), $paging['defaults'])), $options);
+
+ if (isset($url['order'])) {
+ $sort = $direction = null;
+ if (is_array($url['order'])) {
+ list($sort, $direction) = array($this->sortKey($model, $url), current($url['order']));
+ }
+ unset($url['order']);
+ $url = array_merge($url, compact('sort', 'direction'));
+ }
+
+ if ($asArray) {
+ return $url;
+ }
+ return parent::url($url);
+ }
+/**
+ * Protected method for generating prev/next links
+ *
+ */
+ function __pagingLink($which, $title = null, $options = array(), $disabledTitle = null, $disabledOptions = array()) {
+ $check = 'has' . $which;
+ $_defaults = array('url' => array(), 'step' => 1, 'escape' => true, 'model' => null, 'tag' => 'div');
+ $options = array_merge($_defaults, (array)$options);
+ $paging = $this->params($options['model']);
+
+ if (!$this->{$check}($options['model']) && (!empty($disabledTitle) || !empty($disabledOptions))) {
+ if (!empty($disabledTitle) && $disabledTitle !== true) {
+ $title = $disabledTitle;
+ }
+ $options = array_merge($_defaults, (array)$disabledOptions);
+ } elseif (!$this->{$check}($options['model'])) {
+ return null;
+ }
+
+ foreach (array_keys($_defaults) as $key) {
+ ${$key} = $options[$key];
+ unset($options[$key]);
+ }
+ $url = array_merge(array('page' => $paging['page'] + ($which == 'Prev' ? $step * -1 : $step)), $url);
+
+ if ($this->{$check}($model)) {
+ return $this->link($title, $url, array_merge($options, array('escape' => $escape)));
+ } else {
+ return $this->Html->tag($tag, $title, $options, $escape);
+ }
+ }
+/**
+ * Returns true if the given result set is not at the first page
+ *
+ * @param string $model Optional model name. Uses the default if none is specified.
+ * @return boolean True if the result set is not at the first page.
+ */
+ function hasPrev($model = null) {
+ return $this->__hasPage($model, 'prev');
+ }
+/**
+ * Returns true if the given result set is not at the last page
+ *
+ * @param string $model Optional model name. Uses the default if none is specified.
+ * @return boolean True if the result set is not at the last page.
+ */
+ function hasNext($model = null) {
+ return $this->__hasPage($model, 'next');
+ }
+/**
+ * Returns true if the given result set has the page number given by $page
+ *
+ * @param string $model Optional model name. Uses the default if none is specified.
+ * @param int $page The page number - if not set defaults to 1.
+ * @return boolean True if the given result set has the specified page number.
+ */
+ function hasPage($model = null, $page = 1) {
+ if (is_numeric($model)) {
+ $page = $model;
+ $model = null;
+ }
+ $paging = $this->params($model);
+ return $page <= $paging['pageCount'];
+ }
+/**
+ * Protected method
+ *
+ */
+ function __hasPage($model, $page) {
+ $params = $this->params($model);
+ if (!empty($params)) {
+ if ($params["{$page}Page"] == true) {
+ return true;
+ }
+ }
+ return false;
+ }
+/**
+ * Gets the default model of the paged sets
+ *
+ * @return string Model name or null if the pagination isn't initialized.
+ */
+ function defaultModel() {
+ if ($this->__defaultModel != null) {
+ return $this->__defaultModel;
+ }
+ if (empty($this->params['paging'])) {
+ return null;
+ }
+ list($this->__defaultModel) = array_keys($this->params['paging']);
+ return $this->__defaultModel;
+ }
+/**
+ * Returns a counter string for the paged result set
+ *
+ * @param mixed $options Options for the counter string. See #options for list of keys.
+ * @return string Counter string.
+ */
+ function counter($options = array()) {
+ if (is_string($options)) {
+ $options = array('format' => $options);
+ }
+
+ $options = array_merge(
+ array(
+ 'model' => $this->defaultModel(),
+ 'format' => 'pages',
+ 'separator' => ' of '
+ ),
+ $options);
+
+ $paging = $this->params($options['model']);
+ if ($paging['pageCount'] == 0) {
+ $paging['pageCount'] = 1;
+ }
+ $start = 0;
+ if ($paging['count'] >= 1) {
+ $start = (($paging['page'] - 1) * $paging['options']['limit']) + 1;
+ }
+ $end = $start + $paging['options']['limit'] - 1;
+ if ($paging['count'] < $end) {
+ $end = $paging['count'];
+ }
+
+ switch ($options['format']) {
+ case 'range':
+ if (!is_array($options['separator'])) {
+ $options['separator'] = array(' - ', $options['separator']);
+ }
+ $out = $start . $options['separator'][0] . $end . $options['separator'][1] . $paging['count'];
+ break;
+ case 'pages':
+ $out = $paging['page'] . $options['separator'] . $paging['pageCount'];
+ break;
+ default:
+ $replace = array(
+ '%page%' => $paging['page'],
+ '%pages%' => $paging['pageCount'],
+ '%current%' => $paging['current'],
+ '%count%' => $paging['count'],
+ '%start%' => $start,
+ '%end%' => $end
+ );
+ $out = str_replace(array_keys($replace), array_values($replace), $options['format']);
+ break;
+ }
+ return $this->output($out);
+ }
+/**
+ * Returns a set of numbers for the paged result set
+ * uses a modulus to decide how many numbers to show on each side of the current page (default: 8)
+ *
+ * @param mixed $options Options for the numbers, (before, after, model, modulus, separator)
+ * @return string numbers string.
+ */
+ function numbers($options = array()) {
+ if ($options === true) {
+ $options = array(
+ 'before' => ' | ', 'after' => ' | ',
+ 'first' => 'first', 'last' => 'last',
+ );
+ }
+
+ $options = array_merge(
+ array(
+ 'tag' => 'span',
+ 'before'=> null, 'after'=> null,
+ 'model' => $this->defaultModel(),
+ 'modulus' => '8', 'separator' => ' | ',
+ 'first' => null, 'last' => null,
+ ),
+ (array)$options);
+
+ $params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
+ unset($options['model']);
+
+ if ($params['pageCount'] <= 1) {
+ return false;
+ }
+
+ extract($options);
+ unset($options['tag'], $options['before'], $options['after'], $options['model'],
+ $options['modulus'], $options['separator'], $options['first'], $options['last']);
+
+ $out = '';
+
+ if ($modulus && $params['pageCount'] > $modulus) {
+ $half = intval($modulus / 2);
+ $end = $params['page'] + $half;
+
+ if ($end > $params['pageCount']) {
+ $end = $params['pageCount'];
+ }
+ $start = $params['page'] - ($modulus - ($end - $params['page']));
+ if ($start <= 1) {
+ $start = 1;
+ $end = $params['page'] + ($modulus - $params['page']) + 1;
+ }
+
+ if ($first && $start > 1) {
+ $offset = ($start <= (int)$first) ? $start - 1 : $first;
+ if ($offset < $start - 1) {
+ $out .= $this->first($offset, array('tag' => $tag, 'separator' => $separator));
+ } else {
+ $out .= $this->first($offset, array('tag' => $tag, 'after' => $separator, 'separator' => $separator));
+ }
+ }
+
+ $out .= $before;
+
+ for ($i = $start; $i < $params['page']; $i++) {
+ $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)) . $separator;
+ }
+
+ $out .= $this->Html->tag($tag, $params['page'], array('class' => 'current'));
+ if ($i != $params['pageCount']) {
+ $out .= $separator;
+ }
+
+ $start = $params['page'] + 1;
+ for ($i = $start; $i < $end; $i++) {
+ $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)). $separator;
+ }
+
+ if ($end != $params['page']) {
+ $out .= $this->Html->tag($tag, $this->link($i, array('page' => $end), $options));
+ }
+
+ $out .= $after;
+
+ if ($last && $end < $params['pageCount']) {
+ $offset = ($params['pageCount'] < $end + (int)$last) ? $params['pageCount'] - $end : $last;
+ if ($offset <= $last && $params['pageCount'] - $end > $offset) {
+ $out .= $this->last($offset, array('tag' => $tag, 'separator' => $separator));
+ } else {
+ $out .= $this->last($offset, array('tag' => $tag, 'before' => $separator, 'separator' => $separator));
+ }
+ }
+
+ } else {
+ $out .= $before;
+
+ for ($i = 1; $i <= $params['pageCount']; $i++) {
+ if ($i == $params['page']) {
+ $out .= $this->Html->tag($tag, $i, array('class' => 'current'));
+ } else {
+ $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
+ }
+ if ($i != $params['pageCount']) {
+ $out .= $separator;
+ }
+ }
+
+ $out .= $after;
+ }
+
+ return $this->output($out);
+ }
+/**
+ * Returns a first or set of numbers for the first pages
+ *
+ * @param mixed $first if string use as label for the link, if numeric print page numbers
+ * @param mixed $options
+ * @return string numbers string.
+ */
+ function first($first = '<< first', $options = array()) {
+ $options = array_merge(
+ array(
+ 'tag' => 'span',
+ 'after'=> null,
+ 'model' => $this->defaultModel(),
+ 'separator' => ' | ',
+ ),
+ (array)$options);
+
+ $params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
+ unset($options['model']);
+
+ if ($params['pageCount'] <= 1) {
+ return false;
+ }
+ extract($options);
+ unset($options['tag'], $options['after'], $options['model'], $options['separator']);
+
+ $out = '';
+
+ if (is_int($first) && $params['page'] > $first) {
+ if ($after === null) {
+ $after = '...';
+ }
+ for ($i = 1; $i <= $first; $i++) {
+ $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
+ if ($i != $first) {
+ $out .= $separator;
+ }
+ }
+ $out .= $after;
+ } elseif ($params['page'] > 1) {
+ $out = $this->Html->tag($tag, $this->link($first, array('page' => 1), $options)) . $after;
+ }
+ return $out;
+ }
+/**
+ * Returns a last or set of numbers for the last pages
+ *
+ * @param mixed $last if string use as label for the link, if numeric print page numbers
+ * @param mixed $options
+ * @return string numbers string.
+ */
+ function last($last = 'last >>', $options = array()) {
+ $options = array_merge(
+ array(
+ 'tag' => 'span',
+ 'before'=> null,
+ 'model' => $this->defaultModel(),
+ 'separator' => ' | ',
+ ),
+ (array)$options);
+
+ $params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
+ unset($options['model']);
+
+ if ($params['pageCount'] <= 1) {
+ return false;
+ }
+
+ extract($options);
+ unset($options['tag'], $options['before'], $options['model'], $options['separator']);
+
+ $out = '';
+ $lower = $params['pageCount'] - $last + 1;
+
+ if (is_int($last) && $params['page'] < $lower) {
+ if ($before === null) {
+ $before = '...';
+ }
+ for ($i = $lower; $i <= $params['pageCount']; $i++) {
+ $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
+ if ($i != $params['pageCount']) {
+ $out .= $separator;
+ }
+ }
+ $out = $before . $out;
+ } elseif ($params['page'] < $params['pageCount']) {
+ $out = $before . $this->Html->tag($tag, $this->link($last, array('page' => $params['pageCount']), $options));
+ }
+ return $out;
+ }
+}
+?>
diff --git a/cake/libs/view/helpers/rss.php b/cake/libs/view/helpers/rss.php
new file mode 100755
index 00000000..a7889a2e
--- /dev/null
+++ b/cake/libs/view/helpers/rss.php
@@ -0,0 +1,277 @@
+ tags
+ *
+ * @param array $attrib tag attributes
+ * @return string An RSS document
+ */
+ function document($attrib = array(), $content = null) {
+ if ($content === null) {
+ $content = $attrib;
+ $attrib = array();
+ }
+ if (!isset($attrib['version']) || empty($attrib['version'])) {
+ $attrib['version'] = $this->version;
+ }
+
+ return $this->elem('rss', $attrib, $content);
+ }
+/**
+ * Returns an RSS element
+ *
+ * @param array $attrib tag attributes
+ * @param mixed $elements Named array elements which are converted to tags
+ * @param mixed $content Content ( 's belonging to this channel
+ * @return string An RSS
+ */
+ function channel($attrib = array(), $elements = array(), $content = null) {
+ $view =& ClassRegistry::getObject('view');
+
+ if (!isset($elements['title']) && !empty($view->pageTitle)) {
+ $elements['title'] = $view->pageTitle;
+ }
+ if (!isset($elements['link'])) {
+ $elements['link'] = '/';
+ }
+ if (!isset($elements['description'])) {
+ $elements['description'] = '';
+ }
+ $elements['link'] = $this->url($elements['link'], true);
+
+ $elems = '';
+ foreach ($elements as $elem => $data) {
+ $attributes = array();
+ if (is_array($data)) {
+ if (strtolower($elem) == 'cloud') {
+ $attributes = $data;
+ $data = array();
+ } elseif (isset($data['attrib']) && is_array($data['attrib'])) {
+ $attributes = $data['attrib'];
+ unset($data['attrib']);
+ } else {
+ $innerElements = '';
+ foreach ($data as $subElement => $value) {
+ $innerElements .= $this->elem($subElement, array(), $value);
+ }
+ $data = $innerElements;
+ }
+ }
+ $elems .= $this->elem($elem, $attributes, $data);
+ }
+ return $this->elem('channel', $attrib, $elems . $content, !($content === null));
+ }
+/**
+ * Transforms an array of data using an optional callback, and maps it to a set
+ * of tags
+ *
+ * @param array $items The list of items to be mapped
+ * @param mixed $callback A string function name, or array containing an object
+ * and a string method name
+ * @return string A set of RSS elements
+ */
+ function items($items, $callback = null) {
+ if ($callback != null) {
+ $items = array_map($callback, $items);
+ }
+
+ $out = '';
+ $c = count($items);
+
+ for ($i = 0; $i < $c; $i++) {
+ $out .= $this->item(array(), $items[$i]);
+ }
+ return $out;
+ }
+/**
+ * Converts an array into an element and its contents
+ *
+ * @param array $attrib The attributes of the element
+ * @param array $elements The list of elements contained in this
+ * @return string An RSS element
+ */
+ function item($att = array(), $elements = array()) {
+ $content = null;
+
+ if (isset($elements['link']) && !isset($elements['guid'])) {
+ $elements['guid'] = $elements['link'];
+ }
+
+ foreach ($elements as $key => $val) {
+ $attrib = array();
+ switch ($key) {
+ case 'pubDate' :
+ $val = $this->time($val);
+ break;
+ case 'category' :
+ if (is_array($val) && !empty($val[0])) {
+ foreach ($val as $category) {
+ $attrib = array();
+ if (isset($category['domain'])) {
+ $attrib['domain'] = $category['domain'];
+ unset($category['domain']);
+ }
+ $categories[] = $this->elem($key, $attrib, $category);
+ }
+ $elements[$key] = join('', $categories);
+ continue 2;
+ } else if (is_array($val) && isset($val['domain'])) {
+ $attrib['domain'] = $val['domain'];
+ }
+ break;
+ case 'link':
+ case 'guid':
+ case 'comments':
+ if (is_array($val) && isset($val['url'])) {
+ $attrib = $val;
+ unset($attrib['url']);
+ $val = $val['url'];
+ }
+ $val = $this->url($val, true);
+ break;
+ case 'source':
+ if (is_array($val) && isset($val['url'])) {
+ $attrib['url'] = $this->url($val['url'], true);
+ $val = $val['title'];
+ } elseif (is_array($val)) {
+ $attrib['url'] = $this->url($val[0], true);
+ $val = $val[1];
+ }
+ break;
+ case 'enclosure':
+ if (is_string($val['url']) && is_file(WWW_ROOT . $val['url']) && file_exists(WWW_ROOT . $val['url'])) {
+ if (!isset($val['length']) && strpos($val['url'], '://') === false) {
+ $val['length'] = sprintf("%u", filesize(WWW_ROOT . $val['url']));
+ }
+ if (!isset($val['type']) && function_exists('mime_content_type')) {
+ $val['type'] = mime_content_type(WWW_ROOT . $val['url']);
+ }
+ }
+ $val['url'] = $this->url($val['url'], true);
+ $attrib = $val;
+ $val = null;
+ break;
+ }
+ $escape = true;
+ if (is_array($val) && isset($val['convertEntities'])) {
+ $escape = $val['convertEntities'];
+ unset($val['convertEntities']);
+ }
+ if (!is_null($val) && $escape) {
+ $val = h($val);
+ }
+ $elements[$key] = $this->elem($key, $attrib, $val);
+ }
+ if (!empty($elements)) {
+ $content = join('', $elements);
+ }
+ return $this->output($this->elem('item', $att, $content, !($content === null)));
+ }
+/**
+ * Converts a time in any format to an RSS time
+ *
+ * @param mixed $time
+ * @return string An RSS-formatted timestamp
+ * @see TimeHelper::toRSS
+ */
+ function time($time) {
+ return $this->Time->toRSS($time);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/view/helpers/session.php b/cake/libs/view/helpers/session.php
new file mode 100755
index 00000000..d78170ab
--- /dev/null
+++ b/cake/libs/view/helpers/session.php
@@ -0,0 +1,202 @@
+__active = false;
+ }
+ }
+/**
+ * Turn sessions on if 'Session.start' is set to false in core.php
+ *
+ * @param string $base
+ */
+ function activate($base = null) {
+ $this->__active = true;
+ }
+/**
+ * Used to read a session values set in a controller for a key or return values for all keys.
+ *
+ * In your view: $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 values from the session vars
+ * @access public
+ */
+ function read($name = null) {
+ if ($this->__active === true && $this->__start()) {
+ return parent::read($name);
+ }
+ return false;
+ }
+/**
+ * Used to check is a session key has been set
+ *
+ * In your view: $session->check('Controller.sessKey');
+ *
+ * @param string $name
+ * @return boolean
+ * @access public
+ */
+ function check($name) {
+ if ($this->__active === true && $this->__start()) {
+ return parent::check($name);
+ }
+ return false;
+ }
+/**
+ * Returns last error encountered in a session
+ *
+ * In your view: $session->error();
+ *
+ * @return string last error
+ * @access public
+ */
+ function error() {
+ if ($this->__active === true && $this->__start()) {
+ return parent::error();
+ }
+ return false;
+ }
+/**
+ * Used to render the message set in Controller::Session::setFlash()
+ *
+ * In your view: $session->flash('somekey');
+ * Will default to flash if no param is passed
+ *
+ * @param string $key The [Message.]key you are rendering in the view.
+ * @return string Will echo the value if $key is set, or false if not set.
+ * @access public
+ */
+ function flash($key = 'flash') {
+ if ($this->__active === true && $this->__start()) {
+ if (parent::check('Message.' . $key)) {
+ $flash = parent::read('Message.' . $key);
+
+ if ($flash['layout'] == 'default') {
+ if (!empty($flash['params']['class'])) {
+ $class = $flash['params']['class'];
+ } else {
+ $class = 'message';
+ }
+ $out = '' . $flash['message'] . '';
+ } elseif ($flash['layout'] == '' || $flash['layout'] == null) {
+ $out = $flash['message'];
+ } else {
+ $view =& ClassRegistry::getObject('view');
+ list($tmpVars, $tmpTitle) = array($view->viewVars, $view->pageTitle);
+ list($view->viewVars, $view->pageTitle) = array($flash['params'], '');
+ $out = $view->renderLayout($flash['message'], $flash['layout']);
+ list($view->viewVars, $view->pageTitle) = array($tmpVars, $tmpTitle);
+ }
+ echo($out);
+ parent::del('Message.' . $key);
+ return true;
+ }
+ }
+ return false;
+ }
+/**
+ * Used to check is a session is valid in a view
+ *
+ * @return boolean
+ * @access public
+ */
+ function valid() {
+ if ($this->__active === true && $this->__start()) {
+ return parent::valid();
+ }
+ }
+/**
+ * Override CakeSession::write().
+ * This method should not be used in a view
+ *
+ * @return boolean
+ * @access public
+ */
+ function write() {
+ trigger_error(__('You can not write to a Session from the view', true), E_USER_WARNING);
+ }
+/**
+ * Session id
+ *
+ * @return string Session id
+ * @access public
+ */
+ function id() {
+ return parent::id();
+ }
+/**
+ * Determine if Session has been started
+ * and attempt to start it if not
+ *
+ * @return boolean true if Session is already started, false if
+ * Session could not be started
+ * @access public
+ */
+ function __start() {
+ if (!parent::started()) {
+ parent::start();
+ }
+ return true;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/view/helpers/text.php b/cake/libs/view/helpers/text.php
new file mode 100755
index 00000000..a3ae2bee
--- /dev/null
+++ b/cake/libs/view/helpers/text.php
@@ -0,0 +1,344 @@
+\1', $considerHtml = false) {
+ if (empty($phrase)) {
+ return $text;
+ }
+
+ if (is_array($phrase)) {
+ $replace = array();
+ $with = array();
+
+ foreach ($phrase as $key => $value) {
+ $key = $value;
+ $value = $highlighter;
+ $key = '(' . $key . ')';
+ if ($considerHtml) {
+ $key = '(?![^<]+>)' . $key . '(?![^<]+>)';
+ }
+ $replace[] = '|' . $key . '|iu';
+ $with[] = empty($value) ? $highlighter : $value;
+ }
+
+ return preg_replace($replace, $with, $text);
+ } else {
+ $phrase = '(' . $phrase . ')';
+ if ($considerHtml) {
+ $phrase = '(?![^<]+>)' . $phrase . '(?![^<]+>)';
+ }
+
+ return preg_replace('|'.$phrase.'|iu', $highlighter, $text);
+ }
+ }
+/**
+ * Strips given text of all links (]+>|im', '', preg_replace('|<\/a>|im', '', $text));
+ }
+/**
+ * Adds links ( $value) {
+ $value = var_export($value, true);
+ $options .= "'$option' => $value, ";
+ }
+ $options .= ')';
+
+ $text = preg_replace_callback('#(?)((?:http|https|ftp|nntp)://[^ <]+)#i', create_function('$matches',
+ '$Html = new HtmlHelper(); $Html->tags = $Html->loadConfig(); return $Html->link($matches[0], $matches[0],' . $options . ');'), $text);
+
+ return preg_replace_callback('#(?)(?tags = $Html->loadConfig(); return $Html->link($matches[0], "http://" . strtolower($matches[0]),' . $options . ');'), $text);
+ }
+/**
+ * Adds email links ( '$value', ";
+ }
+ $options .= ')';
+
+ return preg_replace_callback('#([_A-Za-z0-9+-]+(?:\.[_A-Za-z0-9+-]+)*@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*)#',
+ create_function('$matches', '$Html = new HtmlHelper(); $Html->tags = $Html->loadConfig(); return $Html->link($matches[0], "mailto:" . $matches[0],' . $options . ');'), $text);
+ }
+/**
+ * Convert all links and email adresses to HTML links.
+ *
+ * @param string $text Text
+ * @param array $htmlOptions Array of HTML options.
+ * @return string The text with links
+ * @access public
+ */
+ function autoLink($text, $htmlOptions = array()) {
+ return $this->autoLinkEmails($this->autoLinkUrls($text, $htmlOptions), $htmlOptions);
+ }
+/**
+ * Truncates text.
+ *
+ * Cuts a string to the length of $length and replaces the last characters
+ * with the ending if the text is longer than length.
+ *
+ * @param string $text String to truncate.
+ * @param integer $length Length of returned string, including ellipsis.
+ * @param mixed $ending If string, will be used as Ending and appended to the trimmed string. Can also be an associative array that can contain the last three params of this method.
+ * @param boolean $exact If false, $text will not be cut mid-word
+ * @param boolean $considerHtml If true, HTML tags would be handled correctly
+ * @return string Trimmed string.
+ */
+ function truncate($text, $length = 100, $ending = '...', $exact = true, $considerHtml = false) {
+ if (is_array($ending)) {
+ extract($ending);
+ }
+ if ($considerHtml) {
+ if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
+ return $text;
+ }
+ $totalLength = mb_strlen($ending);
+ $openTags = array();
+ $truncate = '';
+ preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER);
+ foreach ($tags as $tag) {
+ if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) {
+ if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
+ array_unshift($openTags, $tag[2]);
+ } else if (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
+ $pos = array_search($closeTag[1], $openTags);
+ if ($pos !== false) {
+ array_splice($openTags, $pos, 1);
+ }
+ }
+ }
+ $truncate .= $tag[1];
+
+ $contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $tag[3]));
+ if ($contentLength + $totalLength > $length) {
+ $left = $length - $totalLength;
+ $entitiesLength = 0;
+ if (preg_match_all('/&[0-9a-z]{2,8};|[0-9]{1,7};|[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) {
+ foreach ($entities[0] as $entity) {
+ if ($entity[1] + 1 - $entitiesLength <= $left) {
+ $left--;
+ $entitiesLength += mb_strlen($entity[0]);
+ } else {
+ break;
+ }
+ }
+ }
+
+ $truncate .= mb_substr($tag[3], 0 , $left + $entitiesLength);
+ break;
+ } else {
+ $truncate .= $tag[3];
+ $totalLength += $contentLength;
+ }
+ if ($totalLength >= $length) {
+ break;
+ }
+ }
+
+ } else {
+ if (mb_strlen($text) <= $length) {
+ return $text;
+ } else {
+ $truncate = mb_substr($text, 0, $length - strlen($ending));
+ }
+ }
+ if (!$exact) {
+ $spacepos = mb_strrpos($truncate, ' ');
+ if (isset($spacepos)) {
+ if ($considerHtml) {
+ $bits = mb_substr($truncate, $spacepos);
+ preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER);
+ if (!empty($droppedTags)) {
+ foreach ($droppedTags as $closingTag) {
+ if (!in_array($closingTag[1], $openTags)) {
+ array_unshift($openTags, $closingTag[1]);
+ }
+ }
+ }
+ }
+ $truncate = mb_substr($truncate, 0, $spacepos);
+ }
+ }
+
+ $truncate .= $ending;
+
+ if ($considerHtml) {
+ foreach ($openTags as $tag) {
+ $truncate .= ''.$tag.'>';
+ }
+ }
+
+ return $truncate;
+ }
+/**
+ * Alias for truncate().
+ *
+ * @see TextHelper::truncate()
+ * @access public
+ */
+ function trim() {
+ $args = func_get_args();
+ return call_user_func_array(array(&$this, 'truncate'), $args);
+ }
+/**
+ * Extracts an excerpt from the text surrounding the phrase with a number of characters on each side determined by radius.
+ *
+ * @param string $text String to search the phrase in
+ * @param string $phrase Phrase that will be searched for
+ * @param integer $radius The amount of characters that will be returned on each side of the founded phrase
+ * @param string $ending Ending that will be appended
+ * @return string Modified string
+ * @access public
+ */
+ function excerpt($text, $phrase, $radius = 100, $ending = "...") {
+ if (empty($text) or empty($phrase)) {
+ return $this->truncate($text, $radius * 2, $ending);
+ }
+
+ $phraseLen = strlen($phrase);
+ if ($radius < $phraseLen) {
+ $radius = $phraseLen;
+ }
+
+ $pos = strpos(strtolower($text), strtolower($phrase));
+
+ $startPos = 0;
+ if ($pos > $radius) {
+ $startPos = $pos - $radius;
+ }
+
+ $textLen = strlen($text);
+
+ $endPos = $pos + $phraseLen + $radius;
+ if ($endPos >= $textLen) {
+ $endPos = $textLen;
+ }
+
+ $excerpt = substr($text, $startPos, $endPos - $startPos);
+ if ($startPos != 0) {
+ $excerpt = substr_replace($excerpt, $ending, 0, $phraseLen);
+ }
+
+ if ($endPos != $textLen) {
+ $excerpt = substr_replace($excerpt, $ending, -$phraseLen);
+ }
+
+ return $excerpt;
+ }
+/**
+ * Creates a comma separated list where the last two items are joined with 'and', forming natural English
+ *
+ * @param array $list The list to be joined
+ * @return string
+ * @access public
+ */
+ function toList($list, $and = 'and') {
+ $r = '';
+ $c = count($list) - 1;
+ foreach ($list as $i => $item) {
+ $r .= $item;
+ if ($c > 0 && $i < $c)
+ {
+ $r .= ($i < $c - 1 ? ', ' : " {$and} ");
+ }
+ }
+ return $r;
+ }
+/**
+ * Text-to-html parser, similar to Textile or RedCloth, only with a little different syntax.
+ *
+ * @param string $text String to "flay"
+ * @param boolean $allowHtml Set to true if if html is allowed
+ * @return string "Flayed" text
+ * @access public
+ * @todo Change this. We need a real Textile parser.
+ * @codeCoverageIgnoreStart
+ */
+ function flay($text, $allowHtml = false) {
+ trigger_error(__('(TextHelper::flay) Deprecated: the Flay library is no longer supported and will be removed in a future version.', true), E_USER_WARNING);
+ if (!class_exists('Flay')) {
+ uses('flay');
+ }
+ return Flay::toHtml($text, false, $allowHtml);
+ }
+/**
+ * @codeCoverageIgnoreEnd
+ */
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/view/helpers/time.php b/cake/libs/view/helpers/time.php
new file mode 100755
index 00000000..62792d90
--- /dev/null
+++ b/cake/libs/view/helpers/time.php
@@ -0,0 +1,538 @@
+serverOffset();
+ $gmtTime = $serverTime - $serverOffset;
+ $userTime = $gmtTime + $userOffset * (60*60);
+ return $userTime;
+ }
+/**
+ * Returns server's offset from GMT in seconds.
+ *
+ * @return int Offset
+ */
+ function serverOffset() {
+ return date('Z', time());
+ }
+/**
+ * Returns a UNIX timestamp, given either a UNIX timestamp or a valid strtotime() date string.
+ *
+ * @param string $dateString Datetime string
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return string Parsed timestamp
+ */
+ function fromString($dateString, $userOffset = null) {
+ if (empty($dateString)) {
+ return false;
+ }
+ if (is_integer($dateString) || is_numeric($dateString)) {
+ $date = intval($dateString);
+ } else {
+ $date = strtotime($dateString);
+ }
+ if ($userOffset !== null) {
+ return $this->convert($date, $userOffset);
+ }
+ return $date;
+ }
+/**
+ * Returns a nicely formatted date string for given Datetime string.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return string Formatted date string
+ */
+ function nice($dateString = null, $userOffset = null) {
+ if ($dateString != null) {
+ $date = $this->fromString($dateString, $userOffset);
+ } else {
+ $date = time();
+ }
+
+ $ret = date("D, M jS Y, H:i", $date);
+ return $this->output($ret);
+ }
+/**
+ * Returns a formatted descriptive date string for given datetime string.
+ *
+ * If the given date is today, the returned string could be "Today, 16:54".
+ * If the given date was yesterday, the returned string could be "Yesterday, 16:54".
+ * If $dateString's year is the current year, the returned string does not
+ * include mention of the year.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return string Described, relative date string
+ */
+ function niceShort($dateString = null, $userOffset = null) {
+ $date = $dateString ? $this->fromString($dateString, $userOffset) : time();
+
+ $y = $this->isThisYear($date) ? '' : ' Y';
+
+ if ($this->isToday($date)) {
+ $ret = sprintf(__('Today, %s',true), date("H:i", $date));
+ } elseif ($this->wasYesterday($date)) {
+ $ret = sprintf(__('Yesterday, %s',true), date("H:i", $date));
+ } else {
+ $ret = date("M jS{$y}, H:i", $date);
+ }
+
+ return $this->output($ret);
+ }
+/**
+ * Returns a partial SQL string to search for all records between two dates.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param string $end Datetime string or Unix timestamp
+ * @param string $fieldName Name of database field to compare with
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return string Partial SQL string.
+ */
+ function daysAsSql($begin, $end, $fieldName, $userOffset = null) {
+ $begin = $this->fromString($begin, $userOffset);
+ $end = $this->fromString($end, $userOffset);
+ $begin = date('Y-m-d', $begin) . ' 00:00:00';
+ $end = date('Y-m-d', $end) . ' 23:59:59';
+
+ $ret ="($fieldName >= '$begin') AND ($fieldName <= '$end')";
+ return $this->output($ret);
+ }
+/**
+ * Returns a partial SQL string to search for all records between two times
+ * occurring on the same day.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param string $fieldName Name of database field to compare with
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return string Partial SQL string.
+ */
+ function dayAsSql($dateString, $fieldName, $userOffset = null) {
+ $date = $this->fromString($dateString, $userOffset);
+ $ret = $this->daysAsSql($dateString, $dateString, $fieldName);
+ return $this->output($ret);
+ }
+/**
+ * Returns true if given datetime string is today.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return boolean True if datetime string is today
+ */
+ function isToday($dateString, $userOffset = null) {
+ $date = $this->fromString($dateString, $userOffset);
+ return date('Y-m-d', $date) == date('Y-m-d', time());
+ }
+/**
+ * Returns true if given datetime string is within this week
+ * @param string $dateString
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return boolean True if datetime string is within current week
+ */
+ function isThisWeek($dateString, $userOffset = null) {
+ $date = $this->fromString($dateString, $userOffset);
+ return date('W Y', $date) == date('W Y', time());
+ }
+/**
+ * Returns true if given datetime string is within this month
+ * @param string $dateString
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return boolean True if datetime string is within current month
+ */
+ function isThisMonth($dateString, $userOffset = null) {
+ $date = $this->fromString($dateString);
+ return date('m Y',$date) == date('m Y', time());
+ }
+/**
+ * Returns true if given datetime string is within current year.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @return boolean True if datetime string is within current year
+ */
+ function isThisYear($dateString, $userOffset = null) {
+ $date = $this->fromString($dateString, $userOffset);
+ return date('Y', $date) == date('Y', time());
+ }
+/**
+ * Returns true if given datetime string was yesterday.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return boolean True if datetime string was yesterday
+ */
+ function wasYesterday($dateString, $userOffset = null) {
+ $date = $this->fromString($dateString, $userOffset);
+ return date('Y-m-d', $date) == date('Y-m-d', strtotime('yesterday'));
+ }
+/**
+ * Returns true if given datetime string is tomorrow.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return boolean True if datetime string was yesterday
+ */
+ function isTomorrow($dateString, $userOffset = null) {
+ $date = $this->fromString($dateString, $userOffset);
+ return date('Y-m-d', $date) == date('Y-m-d', strtotime('tomorrow'));
+ }
+/**
+ * Returns the quart
+ * @param string $dateString
+ * @param boolean $range if true returns a range in Y-m-d format
+ * @return boolean True if datetime string is within current week
+ */
+ function toQuarter($dateString, $range = false) {
+ $time = $this->fromString($dateString);
+ $date = ceil(date('m', $time) / 3);
+
+ if ($range === true) {
+ $range = 'Y-m-d';
+ }
+
+ if ($range !== false) {
+ $year = date('Y', $time);
+
+ switch ($date) {
+ case 1:
+ $date = array($year.'-01-01', $year.'-03-31');
+ break;
+ case 2:
+ $date = array($year.'-04-01', $year.'-06-30');
+ break;
+ case 3:
+ $date = array($year.'-07-01', $year.'-09-30');
+ break;
+ case 4:
+ $date = array($year.'-10-01', $year.'-12-31');
+ break;
+ }
+ }
+ return $this->output($date);
+ }
+/**
+ * Returns a UNIX timestamp from a textual datetime description. Wrapper for PHP function strtotime().
+ *
+ * @param string $dateString Datetime string to be represented as a Unix timestamp
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return integer Unix timestamp
+ */
+ function toUnix($dateString, $userOffset = null) {
+ $ret = $this->fromString($dateString, $userOffset);
+ return $this->output($ret);
+ }
+/**
+ * Returns a date formatted for Atom RSS feeds.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return string Formatted date string
+ */
+ function toAtom($dateString, $userOffset = null) {
+ $date = $this->fromString($dateString, $userOffset);
+ $ret = date('Y-m-d\TH:i:s\Z', $date);
+ return $this->output($ret);
+ }
+/**
+ * Formats date for RSS feeds
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return string Formatted date string
+ */
+ function toRSS($dateString, $userOffset = null) {
+ $date = $this->fromString($dateString, $userOffset);
+ $ret = date("r", $date);
+ return $this->output($ret);
+ }
+/**
+ * Returns either a relative date or a formatted date depending
+ * on the difference between the current time and given datetime.
+ * $datetime should be in a strtotime - parsable format, like MySQL's datetime datatype.
+ *
+ * Options:
+ *
+ * - 'format' => a fall back format if the relative time is longer than the duration specified by end
+ * - 'end' => The end of relative time telling
+ * - 'userOffset' => Users offset from GMT (in hours)
+ *
+ * Relative dates look something like this:
+ * 3 weeks, 4 days ago
+ * 15 seconds ago
+ *
+ * Default date formatting is d/m/yy e.g: on 18/2/09
+ *
+ * The returned string includes 'ago' or 'on' and assumes you'll properly add a word
+ * like 'Posted ' before the function output.
+ *
+ * @param string $dateString Datetime string or Unix timestamp
+ * @param array $options Default format if timestamp is used in $dateString
+ * @return string Relative time string.
+ */
+ function timeAgoInWords($dateTime, $options = array()) {
+ $userOffset = null;
+ if (is_array($options) && isset($options['userOffset'])) {
+ $userOffset = $options['userOffset'];
+ }
+ $now = time();
+ if (!is_null($userOffset)) {
+ $now = $this->convert(time(), $userOffset);
+ }
+ $inSeconds = $this->fromString($dateTime, $userOffset);
+ $backwards = ($inSeconds > $now);
+
+ $format = 'j/n/y';
+ $end = '+1 month';
+
+ if (is_array($options)) {
+ if (isset($options['format'])) {
+ $format = $options['format'];
+ unset($options['format']);
+ }
+ if (isset($options['end'])) {
+ $end = $options['end'];
+ unset($options['end']);
+ }
+ } else {
+ $format = $options;
+ }
+
+ if ($backwards) {
+ $futureTime = $inSeconds;
+ $pastTime = $now;
+ } else {
+ $futureTime = $now;
+ $pastTime = $inSeconds;
+ }
+ $diff = $futureTime - $pastTime;
+
+ // If more than a week, then take into account the length of months
+ if ($diff >= 604800) {
+ $current = array();
+ $date = array();
+
+ list($future['H'], $future['i'], $future['s'], $future['d'], $future['m'], $future['Y']) = explode('/', date('H/i/s/d/m/Y', $futureTime));
+
+ list($past['H'], $past['i'], $past['s'], $past['d'], $past['m'], $past['Y']) = explode('/', date('H/i/s/d/m/Y', $pastTime));
+ $years = $months = $weeks = $days = $hours = $minutes = $seconds = 0;
+
+ if ($future['Y'] == $past['Y'] && $future['m'] == $past['m']) {
+ $months = 0;
+ $years = 0;
+ } else {
+ if ($future['Y'] == $past['Y']) {
+ $months = $future['m'] - $past['m'];
+ } else {
+ $years = $future['Y'] - $past['Y'];
+ $months = $future['m'] + ((12 * $years) - $past['m']);
+
+ if ($months >= 12) {
+ $years = floor($months / 12);
+ $months = $months - ($years * 12);
+ }
+
+ if ($future['m'] < $past['m'] && $future['Y'] - $past['Y'] == 1) {
+ $years --;
+ }
+ }
+ }
+
+ if ($future['d'] >= $past['d']) {
+ $days = $future['d'] - $past['d'];
+ } else {
+ $daysInPastMonth = date('t', $pastTime);
+ $daysInFutureMonth = date('t', mktime(0, 0, 0, $future['m'] - 1, 1, $future['Y']));
+
+ if (!$backwards) {
+ $days = ($daysInPastMonth - $past['d']) + $future['d'];
+ } else {
+ $days = ($daysInFutureMonth - $past['d']) + $future['d'];
+ }
+
+ if ($future['m'] != $past['m']) {
+ $months --;
+ }
+ }
+
+ if ($months == 0 && $years >= 1 && $diff < ($years * 31536000)) {
+ $months = 11;
+ $years --;
+ }
+
+ if ($months >= 12) {
+ $years = $years + 1;
+ $months = $months - 12;
+ }
+
+ if ($days >= 7) {
+ $weeks = floor($days / 7);
+ $days = $days - ($weeks * 7);
+ }
+ } else {
+ $years = $months = $weeks = 0;
+ $days = floor($diff / 86400);
+
+ $diff = $diff - ($days * 86400);
+
+ $hours = floor($diff / 3600);
+ $diff = $diff - ($hours * 3600);
+
+ $minutes = floor($diff / 60);
+ $diff = $diff - ($minutes * 60);
+ $seconds = $diff;
+ }
+ $relativeDate = '';
+ $diff = $futureTime - $pastTime;
+
+ if ($diff > abs($now - $this->fromString($end))) {
+ $relativeDate = sprintf(__('on %s',true), date($format, $inSeconds));
+ } else {
+ if ($years > 0) {
+ // years and months and days
+ $relativeDate .= ($relativeDate ? ', ' : '') . $years . ' ' . __n('year', 'years', $years, true);
+ $relativeDate .= $months > 0 ? ($relativeDate ? ', ' : '') . $months . ' ' . __n('month', 'months', $months, true) : '';
+ $relativeDate .= $weeks > 0 ? ($relativeDate ? ', ' : '') . $weeks . ' ' . __n('week', 'weeks', $weeks, true) : '';
+ $relativeDate .= $days > 0 ? ($relativeDate ? ', ' : '') . $days . ' ' . __n('day', 'days', $days, true) : '';
+ } elseif (abs($months) > 0) {
+ // months, weeks and days
+ $relativeDate .= ($relativeDate ? ', ' : '') . $months . ' ' . __n('month', 'months', $months, true);
+ $relativeDate .= $weeks > 0 ? ($relativeDate ? ', ' : '') . $weeks . ' ' . __n('week', 'weeks', $weeks, true) : '';
+ $relativeDate .= $days > 0 ? ($relativeDate ? ', ' : '') . $days . ' ' . __n('day', 'days', $days, true) : '';
+ } elseif (abs($weeks) > 0) {
+ // weeks and days
+ $relativeDate .= ($relativeDate ? ', ' : '') . $weeks . ' ' . __n('week', 'weeks', $weeks, true);
+ $relativeDate .= $days > 0 ? ($relativeDate ? ', ' : '') . $days . ' ' . __n('day', 'days', $days, true) : '';
+ } elseif (abs($days) > 0) {
+ // days and hours
+ $relativeDate .= ($relativeDate ? ', ' : '') . $days . ' ' . __n('day', 'days', $days, true);
+ $relativeDate .= $hours > 0 ? ($relativeDate ? ', ' : '') . $hours . ' ' . __n('hour', 'hours', $hours, true) : '';
+ } elseif (abs($hours) > 0) {
+ // hours and minutes
+ $relativeDate .= ($relativeDate ? ', ' : '') . $hours . ' ' . __n('hour', 'hours', $hours, true);
+ $relativeDate .= $minutes > 0 ? ($relativeDate ? ', ' : '') . $minutes . ' ' . __n('minute', 'minutes', $minutes, true) : '';
+ } elseif (abs($minutes) > 0) {
+ // minutes only
+ $relativeDate .= ($relativeDate ? ', ' : '') . $minutes . ' ' . __n('minute', 'minutes', $minutes, true);
+ } else {
+ // seconds only
+ $relativeDate .= ($relativeDate ? ', ' : '') . $seconds . ' ' . __n('second', 'seconds', $seconds, true);
+ }
+
+ if (!$backwards) {
+ $relativeDate = sprintf(__('%s ago', true), $relativeDate);
+ }
+ }
+ return $this->output($relativeDate);
+ }
+/**
+ * Alias for timeAgoInWords
+ *
+ * @param mixed $dateTime Datetime string (strtotime-compatible) or Unix timestamp
+ * @param mixed $options Default format string, if timestamp is used in $dateTime, or an array of options to be passed
+ * on to timeAgoInWords().
+ * @return string Relative time string.
+ * @see TimeHelper::timeAgoInWords
+ */
+ function relativeTime($dateTime, $options = array()) {
+ return $this->timeAgoInWords($dateTime, $options);
+ }
+/**
+ * Returns true if specified datetime was within the interval specified, else false.
+ *
+ * @param mixed $timeInterval the numeric value with space then time type. Example of valid types: 6 hours, 2 days, 1 minute.
+ * @param mixed $dateString the datestring or unix timestamp to compare
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return bool
+ */
+ function wasWithinLast($timeInterval, $dateString, $userOffset = null) {
+ $tmp = str_replace(' ', '', $timeInterval);
+ if (is_numeric($tmp)) {
+ $timeInterval = $tmp . ' ' . __('days', true);
+ }
+
+ $date = $this->fromString($dateString, $userOffset);
+ $interval = $this->fromString('-'.$timeInterval);
+
+ if ($date >= $interval && $date <= time()) {
+ return true;
+ }
+
+ return false;
+ }
+/**
+ * Returns gmt, given either a UNIX timestamp or a valid strtotime() date string.
+ *
+ * @param string $dateString Datetime string
+ * @return string Formatted date string
+ */
+ function gmt($string = null) {
+ if ($string != null) {
+ $string = $this->fromString($string);
+ } else {
+ $string = time();
+ }
+ $string = $this->fromString($string);
+ $hour = intval(date("G", $string));
+ $minute = intval(date("i", $string));
+ $second = intval(date("s", $string));
+ $month = intval(date("n", $string));
+ $day = intval(date("j", $string));
+ $year = intval(date("Y", $string));
+
+ $return = gmmktime($hour, $minute, $second, $month, $day, $year);
+ return $return;
+ }
+/**
+ * Returns a UNIX timestamp, given either a UNIX timestamp or a valid strtotime() date string.
+ *
+ * @param string $format date format string. defaults to 'd-m-Y'
+ * @param string $dateString Datetime string
+ * @param boolean $invalid flag to ignore results of fromString == false
+ * @param int $userOffset User's offset from GMT (in hours)
+ * @return string Formatted date string
+ */
+ function format($format = 'd-m-Y', $date, $invalid = false, $userOffset = null) {
+ $date = $this->fromString($date, $userOffset);
+ if ($date === false && $invalid !== false) {
+ return $invalid;
+ }
+ return date($format, $date);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/view/helpers/xml.php b/cake/libs/view/helpers/xml.php
new file mode 100755
index 00000000..6377a526
--- /dev/null
+++ b/cake/libs/view/helpers/xml.php
@@ -0,0 +1,163 @@
+Xml =& new Xml();
+ $this->Xml->options(array('verifyNs' => false));
+ }
+/**
+ * Returns an XML document header
+ *
+ * @param array $attrib Header tag attributes
+ * @return string XML header
+ */
+ function header($attrib = array()) {
+ if (Configure::read('App.encoding') !== null) {
+ $this->encoding = Configure::read('App.encoding');
+ }
+
+ if (is_array($attrib)) {
+ $attrib = array_merge(array('encoding' => $this->encoding), $attrib);
+ }
+ if (is_string($attrib) && strpos($attrib, 'xml') !== 0) {
+ $attrib = 'xml ' . $attrib;
+ }
+
+ return $this->output($this->Xml->header($attrib));
+ }
+/**
+ * Adds a namespace to any documents generated
+ *
+ * @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
+ * @deprecated
+ * @see Xml::addNs()
+ */
+ function addNs($name, $url = null) {
+ return $this->Xml->addNamespace($name, $url);
+ }
+/**
+ * Removes a namespace added in addNs()
+ *
+ * @param string $name The namespace name or URI
+ * @deprecated
+ * @see Xml::removeNs()
+ */
+ function removeNs($name) {
+ return $this->Xml->removeGlobalNamespace($name);
+ }
+/**
+ * Generates an XML element
+ *
+ * @param string $name The name of the XML element
+ * @param array $attrib The attributes of the XML element
+ * @param mixed $content XML element content
+ * @param boolean $endTag Whether the end tag of the element should be printed
+ * @return string XML
+ */
+ function elem($name, $attrib = array(), $content = null, $endTag = true) {
+ $namespace = null;
+ if (isset($attrib['namespace'])) {
+ $namespace = $attrib['namespace'];
+ unset($attrib['namespace']);
+ }
+ $cdata = false;
+ if (is_array($content) && isset($content['cdata'])) {
+ $cdata = true;
+ unset($content['cdata']);
+ }
+ if (is_array($content) && isset($content['value'])) {
+ $content = $content['value'];
+ }
+ $children = array();
+ if (is_array($content)) {
+ $children = $content;
+ $content = null;
+ }
+
+ $elem =& $this->Xml->createElement($name, $content, $attrib, $namespace);
+ foreach ($children as $child) {
+ $elem->createElement($child);
+ }
+ $out = $elem->toString(array('cdata' => $cdata, 'leaveOpen' => !$endTag));
+
+ if (!$endTag) {
+ $this->Xml =& $elem;
+ }
+ return $this->output($out);
+ }
+/**
+ * Create closing tag for current element
+ *
+ * @return string
+ */
+ function closeElem() {
+ $name = $this->Xml->name();
+ if ($parent =& $this->Xml->parent()) {
+ $this->Xml =& $parent;
+ }
+ return $this->output('' . $name . '>');
+ }
+/**
+ * Serializes a model resultset into XML
+ *
+ * @param mixed $data The content to be converted to XML
+ * @param array $options The data formatting options. For a list of valid options, see
+ * XmlNode::__construct().
+ * @return string A copy of $data in XML format
+ * @see XmlNode
+ */
+ function serialize($data, $options = array()) {
+ $options += array('attributes' => false, 'format' => 'attributes');
+ $data =& new Xml($data, $options);
+ return $data->toString($options + array('header' => false));
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/view/layouts/ajax.ctp b/cake/libs/view/layouts/ajax.ctp
new file mode 100755
index 00000000..ca3459af
--- /dev/null
+++ b/cake/libs/view/layouts/ajax.ctp
@@ -0,0 +1,25 @@
+
+
\ No newline at end of file
diff --git a/cake/libs/view/layouts/default.ctp b/cake/libs/view/layouts/default.ctp
new file mode 100755
index 00000000..3b6fac08
--- /dev/null
+++ b/cake/libs/view/layouts/default.ctp
@@ -0,0 +1,64 @@
+
+
+
+
+ charset(); ?>
+
+
+
+
+ meta('icon');
+
+ echo $html->css('cake.generic');
+
+ echo $scripts_for_layout;
+ ?>
+
+
+
+
+ link(__('CakePHP: the rapid development php framework', true), 'http://cakephp.org'); ?>
+
+
+
+ flash(); ?>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cake/libs/view/layouts/email/html/default.ctp b/cake/libs/view/layouts/email/html/default.ctp
new file mode 100755
index 00000000..a41315a3
--- /dev/null
+++ b/cake/libs/view/layouts/email/html/default.ctp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+ This email was sent using the CakePHP Framework
+
+
\ No newline at end of file
diff --git a/cake/libs/view/layouts/email/text/default.ctp b/cake/libs/view/layouts/email/text/default.ctp
new file mode 100755
index 00000000..4b0b62c4
--- /dev/null
+++ b/cake/libs/view/layouts/email/text/default.ctp
@@ -0,0 +1,28 @@
+
+
+
+This email was sent using the CakePHP Framework, http://cakephp.org.
+
diff --git a/cake/libs/view/layouts/flash.ctp b/cake/libs/view/layouts/flash.ctp
new file mode 100755
index 00000000..d896896e
--- /dev/null
+++ b/cake/libs/view/layouts/flash.ctp
@@ -0,0 +1,43 @@
+
+
+
+
+charset(); ?>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cake/libs/view/layouts/js/default.ctp b/cake/libs/view/layouts/js/default.ctp
new file mode 100755
index 00000000..d94dc903
--- /dev/null
+++ b/cake/libs/view/layouts/js/default.ctp
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/cake/libs/view/layouts/rss/default.ctp b/cake/libs/view/layouts/rss/default.ctp
new file mode 100755
index 00000000..518d9758
--- /dev/null
+++ b/cake/libs/view/layouts/rss/default.ctp
@@ -0,0 +1,16 @@
+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/cake/libs/view/layouts/xml/default.ctp b/cake/libs/view/layouts/xml/default.ctp
new file mode 100755
index 00000000..566ca215
--- /dev/null
+++ b/cake/libs/view/layouts/xml/default.ctp
@@ -0,0 +1,2 @@
+header(); ?>
+
\ No newline at end of file
diff --git a/cake/libs/view/media.php b/cake/libs/view/media.php
new file mode 100755
index 00000000..5be83784
--- /dev/null
+++ b/cake/libs/view/media.php
@@ -0,0 +1,249 @@
+ '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',
+ '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', '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', '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', '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_push($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();
+ @ob_end_clean();
+
+ while (!feof($handle) && connection_status() == 0 && !connection_aborted()) {
+ set_time_limit(0);
+ $buffer = fread($handle, $chunkSize);
+ echo $buffer;
+ @flush();
+ @ob_flush();
+ }
+ fclose($handle);
+ exit(0);
+ }
+ 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]);
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/view/pages/home.ctp b/cake/libs/view/pages/home.ctp
new file mode 100755
index 00000000..f0e3d9c6
--- /dev/null
+++ b/cake/libs/view/pages/home.ctp
@@ -0,0 +1,147 @@
+cakeError('error404');
+endif;
+?>
+
+
+ 0):
+ Debugger::checkSessionKey();
+endif;
+?>
+
+ ';
+ __('Your tmp directory is writable.');
+ echo '';
+ else:
+ echo '';
+ __('Your tmp directory is NOT writable.');
+ echo '';
+ endif;
+ ?>
+
+
+ ';
+ echo sprintf(__('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;
+ ?>
+
+
+
+
+
+To change its layout, create: APP/views/layouts/default.ctp.
+You can also add some CSS styles for your pages at: APP/webroot/css.');
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+ -
+
+ -
+
+ -
+
+ -
+
+ -
+
+ -
+
+ - irc.freenode.net #cakephp
+
+ -
+
+ -
+
+ -
+
+ -
+
+
\ No newline at end of file
diff --git a/cake/libs/view/scaffolds/edit.ctp b/cake/libs/view/scaffolds/edit.ctp
new file mode 100755
index 00000000..6705b4bf
--- /dev/null
+++ b/cake/libs/view/scaffolds/edit.ctp
@@ -0,0 +1,51 @@
+
+
+create();
+ echo $form->inputs($scaffoldFields, array('created', 'modified', 'updated'));
+ echo $form->end(__('Submit', true));
+?>
+
+
+
+action != 'add'):?>
+ - link(__('Delete', true), array('action' => 'delete', $form->value($modelClass.'.'.$primaryKey)), null, __('Are you sure you want to delete', true).' #' . $form->value($modelClass.'.'.$primaryKey)); ?>
+
+ - 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- " . $html->link(sprintf(__('List %s', true), Inflector::humanize($_details['controller'])), array('controller' => $_details['controller'], 'action' =>'index')) . "
\n";
+ echo "\t\t- " . $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/cake/libs/view/scaffolds/index.ctp b/cake/libs/view/scaffolds/index.ctp
new file mode 100755
index 00000000..c5b9e0e7
--- /dev/null
+++ b/cake/libs/view/scaffolds/index.ctp
@@ -0,0 +1,97 @@
+
+
+
+counter(array(
+ 'format' => 'Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%'
+));
+?>
+
+
+
+ sort($_field);?>
+
+
+
+\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\t\t\t" . $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\n\t\t\t" . ${$singularVar}[$modelClass][$_field] . " \n\t\t \n";
+ }
+ }
+
+ echo "\t\t\n";
+ echo "\t\t\t" . $html->link(__('View', true), array('action' => 'view', ${$singularVar}[$modelClass][$primaryKey])) . "\n";
+ echo "\t\t\t" . $html->link(__('Edit', true), array('action' => 'edit', ${$singularVar}[$modelClass][$primaryKey])) . "\n";
+ echo "\t\t\t" . $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 \n";
+ echo "\t\n";
+
+endforeach;
+echo "\n";
+?>
+
+
+
+prev('<< ' . __('previous', true), array(), null, array('class' => 'disabled')) . "\n";?>
+ | numbers() . "\n"?>
+next(__('next', true) .' >>', array(), null, array('class' => 'disabled')) . "\n";?>
+
+
+
+ - link('New '.$singularHumanName, array('action' => 'add')); ?>
+ $_data) {
+ foreach ($_data as $_alias => $_details) {
+ if ($_details['controller'] != $this->name && !in_array($_details['controller'], $done)) {
+ echo "\t\t- " . $html->link(sprintf(__('List %s', true), Inflector::humanize($_details['controller'])), array('controller' => $_details['controller'], 'action' => 'index')) . "
\n";
+ echo "\t\t- " . $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/cake/libs/view/scaffolds/view.ctp b/cake/libs/view/scaffolds/view.ctp
new file mode 100755
index 00000000..a497ca67
--- /dev/null
+++ b/cake/libs/view/scaffolds/view.ctp
@@ -0,0 +1,159 @@
+
+
+
+
+ $_details) {
+ if ($_field === $_details['foreignKey']) {
+ $isKey = true;
+ echo "\t\t- " . Inflector::humanize($_alias) . "
\n";
+ echo "\t\t- \n\t\t\t" . $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";
+ }
+}
+?>
+
+
+
+
+" .$html->link(sprintf(__('Edit %s', true), $singularHumanName), array('action' => 'edit', ${$singularVar}[$modelClass][$primaryKey])). " \n";
+ echo "\t\t- " .$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- " .$html->link(sprintf(__('List %s', true), $pluralHumanName), array('action' => 'index')). "
\n";
+ echo "\t\t- " .$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- " . $html->link(sprintf(__('List %s', true), Inflector::humanize($_details['controller'])), array('controller' => $_details['controller'], 'action' => 'index')) . "
\n";
+ echo "\t\t- " . $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/cake/libs/view/theme.php b/cake/libs/view/theme.php
new file mode 100755
index 00000000..97e2569a
--- /dev/null
+++ b/cake/libs/view/theme.php
@@ -0,0 +1,95 @@
+theme =& $controller->theme;
+
+ if (!empty($this->theme)) {
+ if (is_dir(WWW_ROOT . 'themed' . DS . $this->theme)) {
+ $this->themeWeb = 'themed/'. $this->theme .'/';
+ }
+ /* deprecated: as of 6128 the following properties are no longer needed */
+ $this->themeElement = 'themed'. DS . $this->theme . DS .'elements'. DS;
+ $this->themeLayout = 'themed'. DS . $this->theme . DS .'layouts'. DS;
+ $this->themePath = 'themed'. DS . $this->theme . DS;
+ }
+ }
+
+/**
+ * Return all possible paths to find view files in order
+ *
+ * @param string $plugin
+ * @return array paths
+ * @access private
+ */
+ function _paths($plugin = null, $cached = true) {
+ $paths = parent::_paths($plugin, $cached);
+
+ if (!empty($this->theme)) {
+ $count = count($paths);
+ for ($i = 0; $i < $count; $i++) {
+ $themePaths[] = $paths[$i] . 'themed'. DS . $this->theme . DS;
+ }
+ $paths = array_merge($themePaths, $paths);
+ }
+
+ if (empty($this->__paths)) {
+ $this->__paths = $paths;
+ }
+
+ return $paths;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/libs/view/view.php b/cake/libs/view/view.php
new file mode 100755
index 00000000..1ed8c01c
--- /dev/null
+++ b/cake/libs/view/view.php
@@ -0,0 +1,929 @@
+ tags) for the layout
+ *
+ * @var array
+ * @access private
+ */
+ var $__scripts = array();
+/**
+ * Holds an array of paths.
+ *
+ * @var array
+ */
+ var $__paths = array();
+/**
+ * Constructor
+ *
+ * @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.
+ *
+ * @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)
+ * Special params:
+ * cache - enable caching for this element accepts boolean or strtotime compatible string.
+ * Can also be an array
+ * if an array,'time' is used to specify duration of cache. 'key' can be used to
+ * create unique cache files.
+ *
+ * @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);
+
+ foreach ($paths as $path) {
+ if (file_exists($path . 'elements' . DS . $name . $this->ext)) {
+ $file = $path . 'elements' . DS . $name . $this->ext;
+ break;
+ } elseif (file_exists($path . 'elements' . DS . $name . '.thtml')) {
+ $file = $path . 'elements' . DS . $name . '.thtml';
+ break;
+ }
+ }
+
+ if (is_file($file)) {
+ $params = array_merge_recursive($params, $this->loaded);
+ $element = $this->_render($file, array_merge($this->viewVars, $params), $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
+ */
+ 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)) {
+ if (substr($viewFileName, -3) === 'ctp' || substr($viewFileName, -5) === 'thtml') {
+ $out = View::_render($viewFileName, $this->viewVars);
+ } else {
+ $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']) &&
+ (($this->cacheAction != false)) && (Configure::read('Cache.check') === true)
+ );
+
+ if ($isCached) {
+ $replace = array('', ' ');
+ $out = str_replace($replace, '', $out);
+ }
+ }
+ $this->hasRendered = true;
+ } else {
+ $out = $this->_render($viewFileName, $this->viewVars);
+ $msg = __("Error in view %s, got: %s
", true);
+ trigger_error(sprintf($msg, $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 - contains page title
+ * content_for_layout - contains rendered view file
+ * scripts_for_layout - contains scripts added to header
+ * cakeDebug - if debug is on, cake debug information is added.
+ *
+ * @param string $content_for_layout Content to render in a view, wrapped by the surrounding layout.
+ * @return mixed Rendered output, or false on error
+ */
+ function renderLayout($content_for_layout, $layout = null) {
+ $layoutFileName = $this->_getLayoutFileName($layout);
+ if (empty($layoutFileName)) {
+ return $this->output;
+ }
+
+ $debug = '';
+
+ if (isset($this->viewVars['cakeDebug']) && Configure::read() > 2) {
+ $params = array('controller' => $this->viewVars['cakeDebug']);
+ $debug = View::element('dump', $params, false);
+ unset($this->viewVars['cakeDebug']);
+ }
+
+ if ($this->pageTitle !== false) {
+ $pageTitle = $this->pageTitle;
+ } else {
+ $pageTitle = Inflector::humanize($this->viewPath);
+ }
+ $data_for_layout = array_merge($this->viewVars, array(
+ 'title_for_layout' => $pageTitle,
+ 'content_for_layout' => $content_for_layout,
+ 'scripts_for_layout' => join("\n\t", $this->__scripts),
+ 'cakeDebug' => $debug
+ ));
+
+ if (empty($this->loaded) && !empty($this->helpers)) {
+ $loadHelpers = true;
+ } else {
+ $loadHelpers = false;
+ $data_for_layout = array_merge($data_for_layout, $this->loaded);
+ }
+
+ $this->_triggerHelpers('beforeLayout');
+
+ if (substr($layoutFileName, -3) === 'ctp' || substr($layoutFileName, -5) === 'thtml') {
+ $this->output = View::_render($layoutFileName, $data_for_layout, $loadHelpers, true);
+ } else {
+ $this->output = $this->_render($layoutFileName, $data_for_layout, $loadHelpers);
+ }
+
+ if ($this->output === false) {
+ $this->output = $this->_render($layoutFileName, $data_for_layout);
+ $msg = __("Error in layout %s, got: %s
", true);
+ trigger_error(sprintf($msg, $layoutFileName, $this->output), E_USER_ERROR);
+ return false;
+ }
+
+ $this->_triggerHelpers('afterLayout');
+
+ return $this->output;
+ }
+/**
+ * Fire a callback on all loaded Helpers
+ *
+ * @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
+ *
+ * @param string $filename the cache file to include
+ * @param string $timeStart the page render start time
+ */
+ 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');
+ }
+ echo str_replace('', '', $out);
+ return true;
+ }
+ }
+ }
+/**
+ * Returns a list of variables available in the current View context
+ *
+ * @return array
+ * @access public
+ */
+ function getVars() {
+ return array_keys($this->viewVars);
+ }
+/**
+ * Returns the contents of the given View variable(s)
+ *
+ * @return array
+ * @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
+ * @param string $content
+ * @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
+ */
+ function entity() {
+ $assoc = ($this->association) ? $this->association : $this->model;
+ 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 unknown
+ */
+ 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;
+ }
+
+ foreach ($data as $name => $value) {
+ if ($name == 'title') {
+ $this->pageTitle = $value;
+ } else {
+ $this->viewVars[$name] = $value;
+ }
+ }
+ }
+/**
+ * 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
+ */
+ 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
+ * @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);
+
+ foreach (array_keys($loadedHelpers) as $helper) {
+ $camelBackedHelper = Inflector::variable($helper);
+ ${$camelBackedHelper} =& $loadedHelpers[$helper];
+ $this->loaded[$camelBackedHelper] =& ${$camelBackedHelper};
+ }
+
+ $this->_triggerHelpers('beforeRender');
+ }
+
+ 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->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
+ */
+ function &_loadHelpers(&$loaded, $helpers, $parent = null) {
+ if (empty($loaded)) {
+ $helpers[] = 'Session';
+ }
+
+ foreach ($helpers as $i => $helper) {
+ $options = array();
+
+ if (!is_int($i)) {
+ $options = $helper;
+ $helper = $i;
+ }
+ $plugin = $this->plugin;
+
+ if (strpos($helper, '.') !== false) {
+ list($plugin, $helper) = explode('.', $helper);
+ }
+ $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', 'themeWeb', '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 $action 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 = array($this->ext, '.ctp', '.thtml');
+ 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 = Configure::read('pluginPaths');
+ 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.
+ *
+ * @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 = array($this->ext, '.ctp', '.thtml');
+ 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');
+ }
+/**
+ * Return a misssing view error message
+ *
+ * @param string $viewFileName the filename that should exist
+ * @return cakeError
+ */
+ 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
+ * @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 = Configure::read('viewPaths');
+ $corePaths = array_flip(Configure::corePaths('view'));
+
+ if (!empty($plugin)) {
+ $count = count($viewPaths);
+ for ($i = 0; $i < $count; $i++) {
+ if (!isset($corePaths[$viewPaths[$i]])) {
+ $paths[] = $viewPaths[$i] . 'plugins' . DS . $plugin . DS;
+ }
+ }
+ $pluginPaths = Configure::read('pluginPaths');
+ $count = count($pluginPaths);
+
+ for ($i = 0; $i < $count; $i++) {
+ $paths[] = $pluginPaths[$i] . $plugin . DS . 'views' . DS;
+ }
+ }
+ $paths = array_merge($paths, $viewPaths);
+
+ if (empty($this->__paths)) {
+ $this->__paths = $paths;
+ }
+ return $paths;
+ }
+/**
+ * @deprecated
+ * @see View::element
+ */
+ function renderElement($name, $params = array(), $loadHelpers = false) {
+ return $this->element($name, $params, $loadHelpers);
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/cake/libs/xml.php b/cake/libs/xml.php
new file mode 100755
index 00000000..0d91825b
--- /dev/null
+++ b/cake/libs/xml.php
@@ -0,0 +1,1406 @@
+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)) {
+ $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) {
+ $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 (!empty($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) {
+ $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.');
+ $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) && count($this->children) > 0) {
+ 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 (count($this->namespaces) > 0) {
+ foreach ($this->namespaces as $key => $val) {
+ $val = str_replace('"', '\"', $val);
+ $d .= ' xmlns:' . $key . '="' . $val . '"';
+ }
+ }
+
+ $parent =& $this->parent();
+ if ($parent->name === '#document' && count($parent->namespaces) > 0) {
+ foreach ($parent->namespaces as $key => $val) {
+ $val = str_replace('"', '\"', $val);
+ $d .= ' xmlns:' . $key . '="' . $val . '"';
+ }
+ }
+
+ if (is_array($this->attributes) && count($this->attributes) > 0) {
+ 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 .= '' . $this->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;
+ $multi = null;
+
+ foreach ($this->children as $child) {
+ $key = $camelize ? Inflector::camelize($child->name) : $child->name;
+
+ 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 (isset($out[$child->name]) || isset($multi[$key])) {
+ if (!isset($multi[$key])) {
+ $multi[$key] = array($out[$child->name]);
+ unset($out[$child->name]);
+ }
+ $multi[$key][] = $value;
+ } else {
+ $out[$child->name] = $value;
+ }
+ continue;
+ } elseif (count($child->children) === 0 && $child->value == '') {
+ $value = $child->attributes;
+ if (isset($out[$child->name]) || isset($multi[$key])) {
+ if (!isset($multi[$key])) {
+ $multi[$key] = array($out[$child->name]);
+ unset($out[$child->name]);
+ }
+ $multi[$key][] = $value;
+ } elseif (!empty($value)) {
+ $out[$key] = $value;
+ } else {
+ $out[$child->name] = $value;
+ }
+ continue;
+ } else {
+ $value = $child->toArray($camelize);
+ }
+
+ if (!isset($out[$key])) {
+ $out[$key] = $value;
+ } else {
+ if (!is_array($out[$key]) || !isset($out[$key][0])) {
+ $out[$key] = array($out[$key]);
+ }
+ $out[$key][] = $value;
+ }
+ }
+
+ if (isset($multi)) {
+ $out = array_merge($out, $multi);
+ }
+ 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 private
+ */
+ 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().
+ * @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'
+ );
+ $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');
+ 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;
+ }
+ }
+ 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() {
+ if (is_resource($this->__parser)) {
+ xml_parser_free($this->__parser);
+ }
+ }
+/**
+ * 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];
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/basics.test.php b/cake/tests/cases/basics.test.php
new file mode 100755
index 00000000..7baeeb60
--- /dev/null
+++ b/cake/tests/cases/basics.test.php
@@ -0,0 +1,735 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases
+ * @since CakePHP(tm) v 1.2.0.4206
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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
+ *
+ * @access public
+ * @return void
+ */
+ function setUp() {
+ $this->_localePaths = Configure::read('localePaths');
+ Configure::write('localePaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'locale'));
+ $this->_language = Configure::read('Config.language');
+ }
+/**
+ * tearDown method
+ *
+ * @access public
+ * @return void
+ */
+ function tearDown() {
+ Configure::write('localePaths', $this->_localePaths);
+ Configure::write('Config.language', $this->_language);
+ }
+/**
+ * test the array_diff_key compatibility function.
+ *
+ * @return void
+ **/
+ 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'), '');
+
+ $_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'] = 'double.subdomain.example.com';
+ $this->assertEqual(env('HTTP_BASE'), '.subdomain.example.com');
+
+ $_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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ function testA() {
+ $result = a('this', 'that', 'bar');
+ $this->assertEqual(array('this', 'that', 'bar'), $result);
+ }
+/**
+ * Test aa()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ function testCache() {
+ $_cacheDisable = Configure::read('Cache.disable');
+
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ function testClearCache() {
+ 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'));
+ }
+/**
+ * test __()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ function testDebug() {
+ ob_start();
+ debug('this-is-a-test');
+ $result = ob_get_clean();
+ $pattern = '/.*\>(cake(\/|\\\)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 = '/.*\>(cake(\/|\\\)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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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()
+ *
+ * @access public
+ * @return void
+ */
+ 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');
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/console/cake.test.php b/cake/tests/cases/console/cake.test.php
new file mode 100755
index 00000000..6eda913f
--- /dev/null
+++ b/cake/tests/cases/console/cake.test.php
@@ -0,0 +1,501 @@
+
+ * Copyright 2005-2007, Cake Software Foundation, Inc.
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2007, Cake Software Foundation, Inc.
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.console
+ * @since CakePHP(tm) v 1.2.0.5432
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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();
+}
+/**
+ * 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;
+/**
+ * _initEnvironment method
+ *
+ * @access protected
+ * @return void
+ */
+ function _initEnvironment() {
+ }
+/**
+ * stderr method
+ *
+ * @access public
+ * @return void
+ */
+ function stderr($string) {
+ $this->stderr .= rtrim($string, ' ');
+ }
+/**
+ * stdout method
+ *
+ * @access public
+ * @return void
+ */
+ function stdout($string, $newline = true) {
+ if ($newline) {
+ $this->stdout .= rtrim($string, ' ') . "\n";
+ } else {
+ $this->stdout .= rtrim($string, ' ');
+ }
+ }
+/**
+ * _stop method
+ *
+ * @access protected
+ * @return void
+ */
+ function _stop($status = 0) {
+ $this->stopped = 'Stopped with status: ' . $status;
+ }
+}
+/**
+ * ShellDispatcherTest
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs
+ */
+class ShellDispatcherTest extends UnitTestCase {
+/**
+ * setUp method
+ *
+ * @access public
+ * @return void
+ */
+ function setUp() {
+ $this->_pluginPaths = Configure::read('pluginPaths');
+ $this->_shellPaths = Configure::read('shellPaths');
+
+ Configure::write('pluginPaths', array(
+ TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS
+ ));
+ Configure::write('shellPaths', array(
+ CORE_PATH ? CONSOLE_LIBS : ROOT . DS . CONSOLE_LIBS,
+ TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS
+ ));
+ }
+/**
+ * tearDown method
+ *
+ * @access public
+ * @return void
+ */
+ function tearDown() {
+ Configure::write('pluginPaths', $this->_pluginPaths);
+ Configure::write('shellPaths', $this->_shellPaths);
+ }
+/**
+ * testParseParams method
+ *
+ * @access public
+ * @return void
+ */
+ 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);
+ }
+/**
+ * testBuildPaths method
+ *
+ * @access public
+ * @return void
+ */
+ 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());
+ }
+/**
+ * testDispatch method
+ *
+ * @access public
+ * @return void
+ */
+ function testDispatch() {
+ $Dispatcher =& new TestShellDispatcher(array('sample'));
+ $this->assertPattern('/This is the main method called from SampleShell/', $Dispatcher->stdout);
+
+ $Dispatcher =& new TestShellDispatcher(array('test_plugin_two.example'));
+ $this->assertPattern('/This is the main method called from TestPluginTwo.ExampleShell/', $Dispatcher->stdout);
+
+ $Dispatcher =& new TestShellDispatcher(array('test_plugin_two.welcome', 'say_hello'));
+ $this->assertPattern('/This is the say_hello method called from TestPluginTwo.WelcomeShell/', $Dispatcher->stdout);
+ }
+/**
+ * testHelpCommand method
+ *
+ * @access public
+ * @return void
+ */
+ function testHelpCommand() {
+ $Dispatcher =& new TestShellDispatcher();
+
+ $expected = "/ CORE(\\\|\/)tests(\\\|\/)test_app(\\\|\/)plugins(\\\|\/)test_plugin(\\\|\/)vendors(\\\|\/)shells:";
+ $expected .= "\n\t example";
+ $expected .= "\n/";
+ $this->assertPattern($expected, $Dispatcher->stdout);
+
+ $expected = "/ CORE(\\\|\/)tests(\\\|\/)test_app(\\\|\/)plugins(\\\|\/)test_plugin_two(\\\|\/)vendors(\\\|\/)shells:";
+ $expected .= "\n\t example";
+ $expected .= "\n\t welcome";
+ $expected .= "\n/";
+ $this->assertPattern($expected, $Dispatcher->stdout);
+
+ $expected = "/ APP(\\\|\/)vendors(\\\|\/)shells:";
+ $expected .= "\n/";
+ $this->assertPattern($expected, $Dispatcher->stdout);
+
+ $expected = "/ ROOT(\\\|\/)vendors(\\\|\/)shells:";
+ $expected .= "\n/";
+ $this->assertPattern($expected, $Dispatcher->stdout);
+
+ $expected = "/ CORE(\\\|\/)console(\\\|\/)libs:";
+ $expected .= "\n\t acl";
+ $expected .= "\n\t api";
+ $expected .= "\n\t bake";
+ $expected .= "\n\t console";
+ $expected .= "\n\t i18n";
+ $expected .= "\n\t schema";
+ $expected .= "\n\t testsuite";
+ $expected .= "\n/";
+ $this->assertPattern($expected, $Dispatcher->stdout);
+
+ $expected = "/ CORE(\\\|\/)tests(\\\|\/)test_app(\\\|\/)vendors(\\\|\/)shells:";
+ $expected .= "\n\t sample";
+ $expected .= "\n/";
+ $this->assertPattern($expected, $Dispatcher->stdout);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/console/libs/acl.test.php b/cake/tests/cases/console/libs/acl.test.php
new file mode 100755
index 00000000..f27a6943
--- /dev/null
+++ b/cake/tests/cases/console/libs/acl.test.php
@@ -0,0 +1,128 @@
+_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
+ **/
+ function endCase() {
+ Configure::write('Acl.database', $this->_aclDb);
+ Configure::write('Acl.classname', $this->_aclClass);
+ }
+/**
+ * setUp 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';
+ }
+
+/**
+ * tearDown method
+ *
+ * @return void
+ * @access public
+ */
+ function endTest() {
+ ClassRegistry::flush();
+ }
+
+/**
+ * test that model.foreign_key output works when looking at acl rows
+ *
+ * @return void
+ **/
+ 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();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/console/libs/api.test.php b/cake/tests/cases/console/libs/api.test.php
new file mode 100755
index 00000000..b5568fce
--- /dev/null
+++ b/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.
+ *
+ * @access public
+ * @return void
+ */
+ 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)',
+ '7. header($status)',
+ '8. isAuthorized()',
+ '9. loadModel($modelClass = null, $id = null)',
+ '10. paginate($object = null, $scope = array(), $whitelist = array())',
+ '11. postConditions($data = array(), $op = null, $bool = \'AND\', $exclusive = false)',
+ '12. redirect($url, $status = null, $exit = true)',
+ '13. referer($default = null, $local = false)',
+ '14. render($action = null, $layout = null, $file = null)',
+ '15. set($one, $two = null)',
+ '16. setAction($action)',
+ '17. validate()',
+ '18. 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();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/console/libs/schema.test.php b/cake/tests/cases/console/libs/schema.test.php
new file mode 100755
index 00000000..063910db
--- /dev/null
+++ b/cake/tests/cases/console/libs/schema.test.php
@@ -0,0 +1,353 @@
+ 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 {
+
+ var $fixtures = array('core.article', 'core.user');
+/**
+ * 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
+ **/
+ 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), 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 . 'sql');
+
+ 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), 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
+ **/
+ 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->view();
+ }
+
+/**
+ * test dump() with sql file generation
+ *
+ * @return void
+ **/
+ function testDumpWithFileWriting() {
+ $file =& new File(APP . 'config' . DS . 'sql' . DS . 'i18n.php');
+ $contents = $file->read();
+ $file =& new File(TMP . 'tests' . DS . 'i18n.php');
+ $file->write($contents);
+
+ $this->Shell->params = array('name' => 'i18n');
+ $this->Shell->args = array('write');
+ $this->Shell->startup();
+ $this->Shell->Schema->path = TMP . 'tests';
+ $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();
+ $file->delete();
+ }
+
+/**
+ * test generate with snapshot generation
+ *
+ * @return void
+ */
+ 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
+ **/
+ 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
+ **/
+ 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 schema run create with no table args.
+ *
+ * @return void
+ **/
+ function testRunCreateNoArgs() {
+ $this->Shell->params = array(
+ 'connection' => 'test_suite',
+ 'name' => 'i18n',
+ 'path' => APP . 'config' . DS . 'sql'
+ );
+ $this->Shell->args = array('create');
+ $this->Shell->startup();
+ $this->Shell->setReturnValue('in', 'y');
+ $this->Shell->run();
+
+ $db =& ConnectionManager::getDataSource('test_suite');
+ $sources = $db->listSources();
+ $this->assertTrue(in_array($db->config['prefix'] . 'i18n', $sources));
+ }
+
+/**
+ * Test schema run create with no table args.
+ *
+ * @return void
+ **/
+ function testRunCreateWithTableArgs() {
+ $this->Shell->params = array(
+ 'connection' => 'test_suite',
+ 'name' => 'DbAcl',
+ 'path' => APP . 'config' . DS . 'sql'
+ );
+ $this->Shell->args = array('create', 'acos');
+ $this->Shell->startup();
+ $this->Shell->setReturnValue('in', 'y');
+ $this->Shell->run();
+
+ $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));
+ }
+
+/**
+ * test run update with a table arg.
+ *
+ * @return void
+ **/
+ function testRunUpdateWithTable() {
+ $this->Shell->params = array(
+ 'name' => 'SchemaShellTest',
+ 'connection' => 'test_suite',
+ 'f' => true
+ );
+ $this->Shell->args = array('update', 'articles');
+ $this->Shell->startup();
+ $this->Shell->setReturnValue('in', 'y');
+ $this->Shell->run();
+
+ $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);
+ }
+
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/console/libs/shell.test.php b/cake/tests/cases/console/libs/shell.test.php
new file mode 100755
index 00000000..b832088e
--- /dev/null
+++ b/cake/tests/cases/console/libs/shell.test.php
@@ -0,0 +1,390 @@
+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() {
+ $_back = array(
+ 'modelPaths' => Configure::read('modelPaths'),
+ 'pluginPaths' => Configure::read('pluginPaths'),
+ 'viewPaths' => Configure::read('viewPaths'),
+ );
+ Configure::write('pluginPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS));
+ Configure::write('modelPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS));
+ $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');
+
+ Configure::write('pluginPaths', $_back['pluginPaths']);
+ Configure::write('modelPaths', $_back['modelPaths']);
+ }
+/**
+ * testOut method
+ *
+ * @return void
+ * @access public
+ */
+ function testOut() {
+ $this->Shell->Dispatch->expectAt(0, 'stdout', array('Just a test', true));
+ $this->Shell->out('Just a test');
+
+ $this->Shell->Dispatch->expectAt(1, 'stdout', array("Just\na\ntest\n", true));
+ $this->Shell->out(array('Just', 'a', 'test'));
+ }
+/**
+ * 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');
+ }
+/**
+ * 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 === '\\', '%s 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();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/console/libs/tasks/extract.test.php b/cake/tests/cases/console/libs/tasks/extract.test.php
new file mode 100755
index 00000000..d393a527
--- /dev/null
+++ b/cake/tests/cases/console/libs/tasks/extract.test.php
@@ -0,0 +1,135 @@
+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 . 'extract_task_test';
+ $folder1 = $path . DS . 'locale';
+
+ new Folder($path, true);
+ new Folder($folder1, true);
+
+ $this->Task->interactive = false;
+
+ $this->Task->params['path'] = 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);
+
+ $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);
+
+ $Folder = new Folder($path);
+ $Folder->delete();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/console/libs/tasks/test.test.php b/cake/tests/cases/console/libs/tasks/test.test.php
new file mode 100755
index 00000000..548f2960
--- /dev/null
+++ b/cake/tests/cases/console/libs/tasks/test.test.php
@@ -0,0 +1,100 @@
+Dispatcher =& new TestTestTaskMockShellDispatcher();
+ $this->Task =& new MockTestTask($this->Dispatcher);
+ $this->Task->Dispatch =& $this->Dispatcher;
+ }
+/**
+ * tearDown method
+ *
+ * @return void
+ * @access public
+ */
+ function tearDown() {
+ ClassRegistry::flush();
+ }
+/**
+ * Test that file path generation doesn't continuously append paths.
+ *
+ * @access public
+ * @return void
+ */
+ function testFilePathGeneration () {
+ $file = TESTS . 'cases' . DS . 'models' . DS . 'my_class.test.php';
+
+ $this->Task->Dispatch->expectNever('stderr');
+ $this->Task->Dispatch->expectNever('_stop');
+
+ $this->Task->setReturnValueAt(0, 'in', 'y');
+ $this->Task->expectAt(0, 'createFile', array($file, '*'));
+ $this->Task->bake('Model', 'MyClass');
+
+ $this->Task->setReturnValueAt(1, 'in', 'y');
+ $this->Task->expectAt(1, 'createFile', array($file, '*'));
+ $this->Task->bake('Model', 'MyClass');
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/dispatcher.test.php b/cake/tests/cases/dispatcher.test.php
new file mode 100755
index 00000000..613c8949
--- /dev/null
+++ b/cake/tests/cases/dispatcher.test.php
@@ -0,0 +1,2182 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases
+ * @since CakePHP(tm) v 1.2.0.4206
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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
+ * @access protected
+ * @return void
+ */
+ 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
+ * @access public
+ * @return void
+ */
+ function cakeError($filename, $params) {
+ return array($filename, $params);
+ }
+/**
+ * _stop method
+ *
+ * @access protected
+ * @return void
+ */
+ function _stop() {
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ function index() {
+ return true;
+ }
+/**
+ * add method
+ *
+ * @access public
+ * @return void
+ */
+ function add() {
+ return true;
+ }
+/**
+ * admin_add method
+ *
+ * @param mixed $id
+ * @access public
+ * @return void
+ */
+ 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
+ * @access public
+ * @return void
+ */
+ function display($page = null) {
+ return $page;
+ }
+/**
+ * index method
+ *
+ * @access public
+ * @return void
+ */
+ function index() {
+ return true;
+ }
+/**
+ * protected method
+ *
+ * @access protected
+ * @return void
+ */
+ function _protected() {
+ return true;
+ }
+/**
+ * redirect method overriding
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ * @access public
+ * @return void
+ */
+ function display($page = null) {
+ return $page;
+ }
+/**
+ * index method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ function admin_index() {
+ return true;
+ }
+/**
+ * camelCased method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ function admin_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
+ *
+ * @access public
+ * @return void
+ */
+ function beforeFilter() {
+ if ($this->params['action'] == 'index') {
+ $this->params['action'] = 'view';
+ } else {
+ $this->params['action'] = 'change';
+ }
+ $this->params['pass'] = array('changed');
+ }
+/**
+ * index method
+ *
+ * @access public
+ * @return void
+ */
+ function index() {
+ return true;
+ }
+/**
+ * change method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ function index() {
+ $this->render();
+ }
+/**
+ * test_nocache_tags method
+ *
+ * @access public
+ * @return void
+ */
+ function test_nocache_tags() {
+ $this->render();
+ }
+/**
+ * view method
+ *
+ * @access public
+ * @return void
+ */
+ function view($id = null) {
+ $this->render('index');
+ }
+}
+/**
+ * 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
+ *
+ * @access public
+ * @return void
+ */
+ function index() {
+ return true;
+ }
+}
+/**
+ * DispatcherTest class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases
+ */
+class DispatcherTest extends CakeTestCase {
+/**
+ * setUp method
+ *
+ * @access public
+ * @return void
+ */
+ 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->_vendorPaths = Configure::read('vendorPaths');
+ $this->_pluginPaths = Configure::read('pluginPaths');
+ $this->_viewPaths = Configure::read('viewPaths');
+ $this->_controllerPaths = Configure::read('controllerPaths');
+ $this->_debug = Configure::read('debug');
+
+ Configure::write('controllerPaths', Configure::corePaths('controller'));
+ Configure::write('viewPaths', Configure::corePaths('view'));
+ }
+/**
+ * tearDown method
+ *
+ * @access public
+ * @return void
+ */
+ function endTest() {
+ $_GET = $this->_get;
+ $_POST = $this->_post;
+ $_FILES = $this->_files;
+ $_SERVER = $this->_server;
+ Configure::write('App', $this->_app);
+ Configure::write('Cache', $this->_cache);
+ Configure::write('vendorPaths', $this->_vendorPaths);
+ Configure::write('pluginPaths', $this->_pluginPaths);
+ Configure::write('viewPaths', $this->_viewPaths);
+ Configure::write('controllerPaths', $this->_controllerPaths);
+ Configure::write('debug', $this->_debug);
+ }
+/**
+ * testParseParamsWithoutZerosAndEmptyPost method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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);
+
+ }
+/**
+ * testBaseUrlAndWebrootWithModRewrite method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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);
+ }
+/**
+ * testBaseUrlAndWebrootWithBase method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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));
+
+ $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);
+ }
+/**
+ * testDispatchWithArray method
+ *
+ * @access public
+ * @return void
+ */
+ function testDispatchWithArray() {
+ $Dispatcher =& new TestDispatcher();
+ Configure::write('App.baseUrl','/index.php');
+ $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);
+ }
+/**
+ * testAdminDispatch method
+ *
+ * @access public
+ * @return void
+ */
+ function testAdminDispatch() {
+ $_POST = array();
+ $Dispatcher =& new TestDispatcher();
+ Configure::write('Routing.admin', '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));
+
+ $expected = 'TestDispatchPages';
+ $this->assertEqual($expected, $controller->name);
+
+ $expected = array('param' => 'value', 'param2' => 'value2');
+ $this->assertIdentical($expected, $controller->passedArgs);
+ $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
+ *
+ * @access public
+ * @return void
+ */
+ 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);
+
+ $expected = 'my_plugin';
+ $this->assertIdentical($expected, $controller->plugin);
+
+ $expected = 'SomePages';
+ $this->assertIdentical($expected, $controller->name);
+
+ $expected = 'some_pages';
+ $this->assertIdentical($expected, $controller->params['controller']);
+
+ $expected = array('0' => 'home', 'param'=>'value', 'param2'=>'value2');
+ $this->assertIdentical($expected, $controller->passedArgs);
+
+ $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
+ *
+ * @access public
+ * @return void
+ */
+ 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));
+
+ $expected = 'my_plugin';
+ $this->assertIdentical($expected, $controller->plugin);
+
+ $expected = 'OtherPages';
+ $this->assertIdentical($expected, $controller->name);
+
+ $expected = 'index';
+ $this->assertIdentical($expected, $controller->action);
+
+ $expected = array('param' => 'value', 'param2' => 'value2');
+ $this->assertIdentical($expected, $controller->passedArgs);
+
+ $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
+ *
+ * @access public
+ * @return void
+ */
+ function testAutomaticPluginControllerDispatch() {
+ $_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/add/param:value/param2:value2';
+
+ $controller = $Dispatcher->dispatch($url, array('return' => 1));
+
+ $expected = 'my_plugin';
+ $this->assertIdentical($controller->plugin, $expected);
+
+ $expected = 'MyPlugin';
+ $this->assertIdentical($controller->name, $expected);
+
+ $expected = 'add';
+ $this->assertIdentical($controller->action, $expected);
+
+ $expected = array('param' => 'value', 'param2' => 'value2');
+ $this->assertEqual($controller->params['named'], $expected);
+
+
+ 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));
+
+ $expected = $pluginUrl;
+ $this->assertIdentical($controller->plugin, $expected);
+
+ $expected = $plugin;
+ $this->assertIdentical($controller->name, $expected);
+
+ $expected = 'index';
+ $this->assertIdentical($controller->action, $expected);
+
+ $expected = $pluginUrl;
+ $this->assertEqual($controller->params['controller'], $expected);
+
+
+ Configure::write('Routing.admin', 'admin');
+
+ Router::reload();
+ $Dispatcher =& new TestDispatcher();
+ $Dispatcher->base = false;
+
+ $url = 'admin/my_plugin/add/5/param:value/param2:value2';
+ $controller = $Dispatcher->dispatch($url, array('return' => 1));
+
+ $expected = 'my_plugin';
+ $this->assertIdentical($controller->plugin, $expected);
+
+ $expected = 'MyPlugin';
+ $this->assertIdentical($controller->name, $expected);
+
+ $expected = 'admin_add';
+ $this->assertIdentical($controller->action, $expected);
+
+ $expected = array(0 => 5, 'param'=>'value', 'param2'=>'value2');
+ $this->assertEqual($controller->passedArgs, $expected);
+
+ Router::reload();
+
+ $Dispatcher =& new TestDispatcher();
+ $Dispatcher->base = false;
+
+ $controller = $Dispatcher->dispatch('admin/articles_test', array('return' => 1));
+
+ $expected = 'articles_test';
+ $this->assertIdentical($controller->plugin, $expected);
+
+ $expected = 'ArticlesTest';
+ $this->assertIdentical($controller->name, $expected);
+
+ $expected = 'admin_index';
+ $this->assertIdentical($controller->action, $expected);
+
+ $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
+ **/
+ function testAutomaticPluginDispatchWithShortAccess() {
+ $_POST = array();
+ $_SERVER['PHP_SELF'] = '/cake/repo/branches/1.2.x.x/index.php';
+
+ Router::reload();
+ Router::connect('/my_plugin/:controller/:action/*', array('plugin'=>'my_plugin'));
+
+ $Dispatcher =& new TestDispatcher();
+ $Dispatcher->base = false;
+
+ $url = 'my_plugin/my_plugin/add';
+ $controller = $Dispatcher->dispatch($url, array('return' => 1));
+ $this->assertFalse(isset($controller->params['pass'][0]));
+
+ $Dispatcher =& new TestDispatcher();
+ $Dispatcher->base = false;
+
+ $url = 'my_plugin/my_plugin/add/0';
+ $controller = $Dispatcher->dispatch($url, array('return' => 1));
+ $this->assertTrue(isset($controller->params['pass'][0]));
+
+ $Dispatcher =& new TestDispatcher();
+ $Dispatcher->base = false;
+
+ $url = 'my_plugin/add';
+ $controller = $Dispatcher->dispatch($url, array('return' => 1));
+
+ $this->assertFalse(isset($controller->params['pass'][0]));
+
+ $Dispatcher =& new TestDispatcher();
+ $Dispatcher->base = false;
+
+ $url = 'my_plugin/add/0';
+ $controller = $Dispatcher->dispatch($url, array('return' => 1));
+ $this->assertIdentical('0',$controller->params['pass'][0]);
+
+ $Dispatcher =& new TestDispatcher();
+ $Dispatcher->base = false;
+
+ $url = 'my_plugin/add/1';
+ $controller = $Dispatcher->dispatch($url, array('return' => 1));
+ $this->assertIdentical('1',$controller->params['pass'][0]);
+ }
+/**
+ * testAutomaticPluginControllerMissingActionDispatch method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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);
+ }
+/**
+ * undocumented function
+ *
+ * @return void
+ **/
+ function testTestPluginDispatch() {
+ $Dispatcher =& new TestDispatcher();
+ $_back = Configure::read('pluginPaths');
+ Configure::write('pluginPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS));
+ $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'));
+
+ Configure::write('pluginPaths', $_back);
+ }
+/**
+ * testChangingParamsFromBeforeFilter method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ function testStaticAssets() {
+ Router::reload();
+ $Configure = Configure::getInstance();
+ $Configure->__objects = null;
+
+ Configure::write('pluginPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS));
+ Configure::write('vendorPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors'. DS));
+
+ $Dispatcher =& new TestDispatcher();
+
+ Configure::write('debug', 0);
+ ob_start();
+ $Dispatcher->dispatch('/img/test.jpg');
+ $result = ob_get_clean();
+ $file = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'img' . DS . 'test.jpg');
+ $this->assertEqual($file, $result);
+
+
+ Configure::write('debug', 0);
+ $Dispatcher->params = $Dispatcher->parseParams('css/test_asset.css');
+
+ ob_start();
+ $Dispatcher->cached('css/test_asset.css');
+ $result = ob_get_clean();
+ $this->assertEqual('this is the test asset css file', $result);
+
+
+ ob_start();
+ $Dispatcher->cached('test_plugin/js/test_plugin/test.js');
+ $result = ob_get_clean();
+ $this->assertEqual('alert("Test App");', $result);
+
+
+ Configure::write('debug', 0);
+ $Dispatcher->params = $Dispatcher->parseParams('test_plugin/js/test_plugin/test.js');
+ ob_start();
+ $Dispatcher->cached('test_plugin/js/test_plugin/test.js');
+ $result = ob_get_clean();
+ $this->assertEqual('alert("Test App");', $result);
+
+
+ Configure::write('debug', 0);
+ $Dispatcher->params = $Dispatcher->parseParams('test_plugin/css/test_plugin_asset.css');
+ ob_start();
+ $Dispatcher->cached('test_plugin/css/test_plugin_asset.css');
+ $result = ob_get_clean();
+ $this->assertEqual('this is the test plugin asset css file', $result);
+
+
+ Configure::write('debug', 0);
+ $Dispatcher->params = $Dispatcher->parseParams('test_plugin/img/cake.icon.gif');
+ ob_start();
+ $Dispatcher->cached('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 . 'vendors' . DS . 'img' . DS . 'cake.icon.gif');
+ $this->assertEqual($file, $result);
+
+ header('Content-type: text/html');//reset the header content-type without page can render as plain text.
+ }
+/**
+ * testFullPageCachingDispatch method
+ *
+ * @access public
+ * @return void
+ */
+ 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'));
+
+ Configure::write('viewPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS));
+
+ $dispatcher =& new Dispatcher();
+ $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);
+ }
+/**
+ * testHttpMethodOverrides method
+ *
+ * @access public
+ * @return void
+ */
+ 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
+ */
+ 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
+ *
+ * @access public
+ * @return void
+ */
+ 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', 'SERVER_SOFTWARE' => 'Microsoft-IIS/5.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_ACCEPT' => '*/*', 'HTTP_ACCEPT_LANGUAGE' => 'en-us', 'HTTP_CONNECTION' => 'Keep-Alive', 'HTTP_HOST' => 'localhost', 'HTTP_USER_AGENT' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)', 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', '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', 'REQUEST_METHOD' => 'GET', '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('UNIQUE_ID' => 'VardGqn@17IAAAu7LY8AAAAK', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-us) AppleWebKit/523.10.5 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6', 'HTTP_ACCEPT' => 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5', 'HTTP_ACCEPT_LANGUAGE' => 'en-us', 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 'HTTP_CONNECTION' => 'keep-alive', '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('UNIQUE_ID' => '2A-v8sCoAQ8AAAc-2xUAAAAB', 'HTTP_ACCEPT_LANGUAGE' => 'en-us', 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 'HTTP_COOKIE' => 'CAKEPHP=jcbv51apn84kd9ucv5aj2ln3t3', 'HTTP_CONNECTION' => 'keep-alive', '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', 'SERVER_ADMIN' => 'you@example.com', 'SCRIPT_FILENAME' => '/Volumes/Home/htdocs/cake/repo/branches/1.2.x.x/app/webroot/index.php', 'REMOTE_PORT' => '53550', 'GATEWAY_INTERFACE' => 'CGI/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'REQUEST_METHOD' => 'GET', '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_METHOD' => 'GET', '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
+ *
+ * @access private
+ * @return void
+ */
+ 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
+ *
+ * @access private
+ * @return void
+ */
+ function __backupEnvironment() {
+ return array(
+ 'App' => Configure::read('App'),
+ 'GET' => $_GET,
+ 'POST' => $_POST,
+ 'SERVER'=> $_SERVER
+ );
+ }
+/**
+ * reloadEnvironment method
+ *
+ * @access private
+ * @return void
+ */
+ 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
+ * @access private
+ * @return void
+ */
+ 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
+ * @access private
+ * @return string
+ */
+ 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;
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/cache.test.php b/cake/tests/cases/libs/cache.test.php
new file mode 100755
index 00000000..37d5ec63
--- /dev/null
+++ b/cake/tests/cases/libs/cache.test.php
@@ -0,0 +1,235 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs
+ * @since CakePHP(tm) v 1.2.0.5432
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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'));
+
+ Cache::engine('File', array('path' => TMP . 'tests'));
+ }
+/**
+ * tearDown method
+ *
+ * @access public
+ * @return void
+ */
+ function tearDown() {
+ Configure::write('Cache.disable', $this->_cacheDisable);
+ Cache::config('default', $this->_defaultCacheConfig['settings']);
+ Cache::engine('File');
+ }
+/**
+ * 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'));
+ }
+/**
+ * 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('File'));
+
+ $result = Cache::config('tests', array('engine'=> 'File', 'path' => TMP . 'tests'));
+ $this->assertEqual($result['settings'], Cache::settings('File'));
+
+ Cache::config('sessions', $_cacheConfigSessions['settings']);
+ Cache::config('tests', $_cacheConfigTests['settings']);
+ }
+/**
+ * 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('File'));
+
+ Cache::config('sessions', $_cacheConfigSessions['settings']);
+ }
+/**
+ * testInitSettings method
+ *
+ * @access public
+ * @return void
+ */
+ function testInitSettings() {
+ Cache::engine('File', array('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);
+
+ Cache::engine('File');
+ }
+/**
+ * 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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/cache/apc.test.php b/cake/tests/cases/libs/cache/apc.test.php
new file mode 100755
index 00000000..5dd30c50
--- /dev/null
+++ b/cake/tests/cases/libs/cache/apc.test.php
@@ -0,0 +1,143 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.cache
+ * @since CakePHP(tm) v 1.2.0.5434
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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 UnitTestCase {
+/**
+ * skip method
+ *
+ * @access public
+ * @return void
+ */
+ function skip() {
+ $skip = true;
+ if (Cache::engine('Apc')) {
+ $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::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 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);
+
+ 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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/cache/file.test.php b/cake/tests/cases/libs/cache/file.test.php
new file mode 100755
index 00000000..89ee671b
--- /dev/null
+++ b/cake/tests/cases/libs/cache/file.test.php
@@ -0,0 +1,356 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.cache
+ * @since CakePHP(tm) v 1.2.0.5434
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+if (!class_exists('Cache')) {
+ require LIBS . 'cache.php';
+}
+if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) {
+ define('CAKEPHP_UNIT_TEST_EXECUTION', 1);
+}
+/**
+ * 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('File'));
+ $this->assertNotEqual($result, Cache::settings('File'));
+
+ $result = Cache::config('tests', array('engine'=> 'File', 'path' => TMP . 'tests'));
+ $this->assertEqual($result['settings'], Cache::settings('File'));
+ $this->assertNotEqual($result, Cache::settings('File'));
+ }
+/**
+ * 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::engine('File', array('serialize' => true));
+ $data = 'this is a test of the emergency broadcasting system';
+ $write = Cache::write('serialize_test', $data, 1);
+ $this->assertTrue($write);
+
+ Cache::engine('File', 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::engine('File', array('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'));
+
+ $result = Cache::engine('File', array('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::engine('File', array('path' => CACHE));
+ }
+/**
+ * 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::engine('File', array('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'));
+
+ $data = Cache::write('test_dir_map', $expected);
+ $data = Cache::read('test_dir_map');
+ Cache::delete('test_dir_map');
+ $this->assertEqual($expected, $data);
+ }
+/**
+ * testWriteQuotedString method
+ *
+ * @access public
+ * @return void
+ */
+ function testWriteQuotedString() {
+ Cache::engine('File', array('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::engine('File', 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'");
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/cache/memcache.test.php b/cake/tests/cases/libs/cache/memcache.test.php
new file mode 100755
index 00000000..8a48df86
--- /dev/null
+++ b/cake/tests/cases/libs/cache/memcache.test.php
@@ -0,0 +1,225 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.cache
+ * @since CakePHP(tm) v 1.2.0.5434
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+if (!class_exists('Cache')) {
+ require LIBS . 'cache.php';
+}
+/**
+ * MemcacheEngineTest class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs.cache
+ */
+/**
+ * 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 (Cache::engine('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_'));
+ }
+/**
+ * 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,
+ 'servers' => array('127.0.0.1'),
+ 'compress' => false,
+ 'engine' => 'Memcache'
+ );
+ $this->assertEqual($settings, $expecting);
+ }
+/**
+ * testSettings method
+ *
+ * @access public
+ * @return void
+ */
+ function testMultipleServers() {
+ $servers = array('127.0.0.1:11211', '127.0.0.1:11222');
+
+ $Cache =& Cache::getInstance();
+ $MemCache =& $Cache->_Engine['Memcache'];
+
+ $available = true;
+ foreach($servers as $server) {
+ list($host, $port) = explode(':', $server);
+ if (!@$MemCache->__Memcache->connect($host, $port)) {
+ $available = false;
+ }
+ }
+
+ if ($this->skipIf(!$available, '%s Need memcache servers at ' . implode(', ', $servers) . ' to run this test')) {
+ return;
+ }
+
+ unset($MemCache->__Memcache);
+ $MemCache->init(array('engine' => 'Memcache', 'servers' => $servers));
+
+ $servers = array_keys($MemCache->__Memcache->getExtendedStats());
+ $settings = Cache::settings();
+ $this->assertEqual($servers, $settings['servers']);
+ }
+/**
+ * testConnect method
+ *
+ * @access public
+ * @return void
+ */
+ function testConnect() {
+ $Cache =& Cache::getInstance();
+ $result = $Cache->_Engine['Memcache']->connect('127.0.0.1');
+ $this->assertTrue($result);
+ }
+/**
+ * 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::engine('Memcache', array('duration' => '+1 second'));
+ sleep(2);
+
+ $result = Cache::read('other_test');
+ $this->assertFalse($result);
+
+ Cache::engine('Memcache', array('duration' => '+31 day'));
+ $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);
+
+ $result = Cache::read('long_expiry_test');
+ $this->assertTrue($result);
+
+ Cache::engine('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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/cache/xcache.test.php b/cake/tests/cases/libs/cache/xcache.test.php
new file mode 100755
index 00000000..842cbfa7
--- /dev/null
+++ b/cake/tests/cases/libs/cache/xcache.test.php
@@ -0,0 +1,172 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.cache
+ * @since CakePHP(tm) v 1.2.0.5434
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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 ($result = Cache::engine('Xcache')) {
+ $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',
+ 'PHP_AUTH_USER' => 'user',
+ 'PHP_AUTH_PW' => 'password',
+ );
+ $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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/cake_log.test.php b/cake/tests/cases/libs/cake_log.test.php
new file mode 100755
index 00000000..05dc3945
--- /dev/null
+++ b/cake/tests/cases/libs/cake_log.test.php
@@ -0,0 +1,55 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs
+ * @since CakePHP(tm) v 1.2.0.5432
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Core', 'Log');
+/**
+ * CakeLogTest class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs
+ */
+class CakeLogTest extends CakeTestCase {
+/**
+ * testLogFileWriting method
+ *
+ * @access public
+ * @return void
+ */
+ function testLogFileWriting() {
+ @unlink(LOGS . 'error.log');
+ CakeLog::write(LOG_WARNING, 'Test warning');
+ $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');
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/cake_test_case.test.php b/cake/tests/cases/libs/cake_test_case.test.php
new file mode 100755
index 00000000..895eaaf4
--- /dev/null
+++ b/cake/tests/cases/libs/cake_test_case.test.php
@@ -0,0 +1,418 @@
+_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->Case =& new SubjectCakeTestCase();
+ $reporter =& new MockCakeHtmlReporter();
+ $this->Case->setReporter($reporter);
+ $this->Reporter = $reporter;
+ }
+/**
+ * tearDown
+ *
+ * @access public
+ * @return void
+ */
+ function tearDown() {
+ unset($this->Case);
+ unset($this->Reporter);
+ }
+/**
+ * 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));
+
+ $input = "\tMy link";
+ $pattern = array(
+ 'a' => array('id' => 'primary', 'href' => '/test.html', 'class' => 'active'),
+ 'assertTrue($this->Case->assertTags($input, $pattern));
+
+ $input = '';
+ $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));
+ }
+/**
+ * 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() {
+ $_back = array(
+ 'controller' => Configure::read('controllerPaths'),
+ 'view' => Configure::read('viewPaths'),
+ 'model' => Configure::read('modelPaths'),
+ 'plugin' => Configure::read('pluginPaths')
+ );
+ Configure::write('controllerPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'controllers' . DS));
+ Configure::write('viewPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS));
+ Configure::write('modelPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'models' . DS));
+ Configure::write('pluginPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS));
+
+ $result = $this->Case->testAction('/tests_apps/index', array('return' => 'view'));
+ $this->assertPattern('/This is the TestsAppsController index view/', $result);
+
+ $result = $this->Case->testAction('/tests_apps/index', array('return' => 'contents'));
+ $this->assertPattern('/This is the TestsAppsController index view/', $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->assertTrue(isset($result['params']['url']['output']));
+ $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']['output']));
+ $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);
+
+
+ Configure::write('modelPaths', $_back['model']);
+ Configure::write('controllerPaths', $_back['controller']);
+ Configure::write('viewPaths', $_back['view']);
+ Configure::write('pluginPaths', $_back['plugin']);
+ }
+/**
+ * testSkipIf
+ *
+ * @return void
+ **/
+ function testSkipIf() {
+ $this->assertTrue($this->Case->skipIf(true));
+ $this->assertFalse($this->Case->skipIf(false));
+ }
+/**
+ * testTestDispatcher
+ *
+ * @access public
+ * @return void
+ */
+ function testTestDispatcher() {
+ $_back = array(
+ 'controller' => Configure::read('controllerPaths'),
+ 'view' => Configure::read('viewPaths'),
+ 'plugin' => Configure::read('pluginPaths')
+ );
+ Configure::write('controllerPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'controllers' . DS));
+ Configure::write('viewPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS));
+ Configure::write('pluginPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS));
+
+ $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));
+
+ Configure::write('controllerPaths', $_back['controller']);
+ Configure::write('viewPaths', $_back['view']);
+ Configure::write('pluginPaths', $_back['plugin']);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/cake_test_fixture.test.php b/cake/tests/cases/libs/cake_test_fixture.test.php
new file mode 100755
index 00000000..d5844bc6
--- /dev/null
+++ b/cake/tests/cases/libs/cake_test_fixture.test.php
@@ -0,0 +1,289 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.cake.tests.libs
+ * @since CakePHP(tm) v 1.2.0.4667
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Core', 'DboSource');
+/**
+ * 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')
+ );
+}
+/**
+ * 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';
+}
+Mock::generate('DboSource', 'FixtureMockDboSource');
+/**
+ * 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');
+
+ $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 = null;
+ $Fixture->import = array('model' => 'FixtureImportTestModel', 'connection' => 'test_suite');
+ $Fixture->init();
+ $this->assertEqual(array_keys($Fixture->fields), array('id', 'name', 'created'));
+
+ $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 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->setReturnValue('insertMulti', true);
+ $this->criticDb->expectAtLeastOnce('insertMulti');
+
+ $return = $Fixture->insert($this->criticDb);
+ $this->assertTrue($this->criticDb->fullDebug);
+ $this->assertTrue($return);
+ }
+/**
+ * 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);
+ }
+/**
+ * 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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/class_registry.test.php b/cake/tests/cases/libs/class_registry.test.php
new file mode 100755
index 00000000..d40ed0e8
--- /dev/null
+++ b/cake/tests/cases/libs/class_registry.test.php
@@ -0,0 +1,314 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs
+ * @since CakePHP(tm) v 1.2.0.5432
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/code_coverage_manager.test.php b/cake/tests/cases/libs/code_coverage_manager.test.php
new file mode 100755
index 00000000..9a1e6c41
--- /dev/null
+++ b/cake/tests/cases/libs/code_coverage_manager.test.php
@@ -0,0 +1,655 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs
+ * @since CakePHP(tm) v 1.2.0.4206
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Core', 'CodeCoverageManager');
+require_once CAKE . 'tests' . DS . 'lib' . DS . 'cli_reporter.php';
+require_once CAKE . 'tests' . DS . 'lib' . DS . 'cake_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 (PHP_SAPI != 'cli') {
+ unset($_GET['group']);
+ CodeCoverageManager::start(substr(md5(microtime()), 0, 5), new CakeHtmlReporter());
+ CodeCoverageManager::report(false);
+ $this->assertError();
+
+ CodeCoverageManager::start('libs/'.basename(__FILE__), new CakeHtmlReporter());
+ CodeCoverageManager::report(false);
+ $this->assertError();
+
+ $path = LIBS;
+ if (strpos(LIBS, ROOT) === false) {
+ $path = ROOT.DS.LIBS;
+ }
+ App::import('Core', 'Folder');
+ $folder = new Folder();
+ $folder->cd($path);
+ $contents = $folder->ls();
+/**
+ * remove method
+ *
+ * @param mixed $var
+ * @access public
+ * @return void
+ */
+ function remove($var) {
+ return ($var != basename(__FILE__));
+ }
+ $contents[1] = array_filter($contents[1], "remove");
+
+ foreach ($contents[1] as $file) {
+ CodeCoverageManager::start('libs'.DS.$file, new CakeHtmlReporter());
+ CodeCoverageManager::report(false);
+ $this->assertNoErrors('libs'.DS.$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('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 CLIReporter;
+ $expected = $manager->__testObjectFileFromCaseFile('libs/set.test.php', false);
+ $this->assertIdentical(ROOT.DS.'cake'.DS.'libs'.DS.'set.php', $expected);
+ }
+/**
+ * testOfHtmlReport method
+ *
+ * @access public
+ * @return void
+ */
+ function testOfHtmlReport() {
+ $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,
+ );
+ $execCodeLines = range(0, 72);
+ $result = explode("", $report = $manager->reportCaseHtml($testObjectFile, $coverageData, $execCodeLines));
+
+ foreach ($result as $num => $line) {
+ $num++;
+ if (array_key_exists($num, $coverageData)) {
+ if ($coverageData[$num] == 1) {
+ $this->assertTrue(strpos($line, 'covered') !== false, $num.': '.$line." fails");
+ }
+
+ if (!array_key_exists($num, $execCodeLines) || $coverageData[$num] == -2) {
+ $this->assertTrue(strpos($line, 'ignored') !== false, $num.': '.$line." fails");
+ }
+
+ if ($coverageData[$num] == -1) {
+ $this->assertTrue(strpos($line, 'uncovered') !== false, $num.': '.$line." fails");
+ }
+ }
+ }
+ }
+/**
+ * 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 = <<
+ ?>
+
+}
+{{}}
+(())
+ @codeCoverageIgnoreStart
+ some
+ more
+ code
+ here
+ @codeCoverageIgnoreEnd
+HTML;
+ $result = $manager->__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();
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/configure.test.php b/cake/tests/cases/libs/configure.test.php
new file mode 100755
index 00000000..e29f5042
--- /dev/null
+++ b/cake/tests/cases/libs/configure.test.php
@@ -0,0 +1,540 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs
+ * @since CakePHP(tm) v 1.2.0.5432
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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');
+ Configure::write('Cache.disable', true);
+
+ $this->_debug = Configure::read('debug');
+ }
+/**
+ * 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);
+ }
+/**
+ * testListObjects method
+ *
+ * @access public
+ * @return void
+ */
+ function testListObjects() {
+ $result = Configure::listObjects('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 = Configure::listObjects('behavior');
+ $this->assertTrue(in_array('Tree', $result));
+
+ $result = Configure::listObjects('controller');
+ $this->assertTrue(in_array('Pages', $result));
+
+ $result = Configure::listObjects('component');
+ $this->assertTrue(in_array('Auth', $result));
+
+ $result = Configure::listObjects('view');
+ $this->assertTrue(in_array('Media', $result));
+
+ $result = Configure::listObjects('helper');
+ $this->assertTrue(in_array('Html', $result));
+
+ $result = Configure::listObjects('model');
+ $notExpected = array('AppModel', 'Behavior', 'ConnectionManager', 'DbAcl', 'Model', 'Schema');
+
+ foreach ($notExpected as $class) {
+ $this->assertFalse(in_array($class, $result));
+ }
+
+ $result = Configure::listObjects('file');
+ $this->assertFalse($result);
+
+ $result = Configure::listObjects('file', 'non_existing_configure');
+ $expected = array();
+ $this->assertEqual($result, $expected);
+
+ $result = Configure::listObjects('NonExistingType');
+ $this->assertFalse($result);
+ }
+/**
+ * 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() {
+ Configure::write('SomeName.someKey', 'myvalue');
+ $result = Configure::read('SomeName.someKey');
+ $this->assertEqual($result, 'myvalue');
+
+ Configure::write('SomeName.someKey', null);
+ $result = Configure::read('SomeName.someKey');
+ $this->assertEqual($result, null);
+ }
+/**
+ * testSetErrorReporting Level
+ *
+ * @return void
+ **/
+ function testSetErrorReportingLevel() {
+ 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);
+ }
+/**
+ * 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 === null);
+ }
+/**
+ * testStore method
+ *
+ * @access public
+ * @return void
+ */
+ function testStoreAndLoad() {
+ Configure::write('Cache.disable', false);
+
+ $expected = array('data' => 'value');
+ Configure::store('SomeExample', 'test', $expected);
+
+ Configure::load('test');
+ $config = Configure::read('SomeExample');
+ $this->assertEqual($config, $expected);
+
+ $expected = array('data' => array('first' => 'value', 'second' => 'value2'));
+ 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', '>='));
+ }
+/**
+ * testBuildPaths method
+ *
+ * @access public
+ * @return void
+ */
+ function testBuildPaths() {
+ Configure::buildPaths(array());
+ $models = Configure::read('modelPaths');
+ $this->assertTrue(!empty($models));
+ }
+}
+/**
+ * AppImportTest class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs
+ */
+class AppImportTest extends UnitTestCase {
+/**
+ * testClassLoading method
+ *
+ * @access public
+ * @return void
+ */
+ function testClassLoading() {
+ $file = App::import();
+ $this->assertTrue($file);
+
+ $file = App::import('Core', 'Model', false);
+ $this->assertTrue($file);
+
+ $file = App::import('Model', 'SomeRandomModelThatDoesNotExist', false);
+ $this->assertFalse($file);
+
+ $file = App::import('Model', 'AppModel', false);
+ $this->assertTrue($file);
+
+ $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);
+
+ $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);
+
+ $file = App::import('Component', 'RequestHandler');
+ $this->assertTrue($file);
+
+ $file = App::import('Helper', 'Form');
+ $this->assertTrue($file);
+
+ $file = App::import('Model', 'NonExistingModel');
+ $this->assertFalse($file);
+ }
+
+ $_back = Configure::read('pluginPaths');
+ Configure::write('pluginPaths', 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('Helper', 'TestPlugin.OtherHelper');
+ $this->assertTrue($result);
+ $this->assertTrue(class_exists('OtherHelperHelper'));
+
+ Configure::write('pluginPaths', $_back);
+ }
+/**
+ * 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', 'Socket');
+
+ $classes = array_flip(get_declared_classes());
+ $this->assertFalse(isset($classes['i18n']));
+ $this->assertFalse(isset($classes['Socket']));
+
+ $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', 'Socket'));
+ $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() {
+ Configure::write('pluginPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS));
+ Configure::write('vendorPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors'. DS));
+
+ 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');
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/component.test.php b/cake/tests/cases/libs/controller/component.test.php
new file mode 100755
index 00000000..e3f78b28
--- /dev/null
+++ b/cake/tests/cases/libs/controller/component.test.php
@@ -0,0 +1,521 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ * @since CakePHP(tm) v 1.2.0.5436
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Core', array('Component', 'Controller'));
+
+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');
+}
+/**
+ * ComponentTest class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ */
+class ComponentTest extends CakeTestCase {
+/**
+ * setUp method
+ *
+ * @access public
+ * @return void
+ */
+ function setUp() {
+ $this->_pluginPaths = Configure::read('pluginPaths');
+ Configure::write('pluginPaths', array(
+ TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS
+ ));
+ }
+/**
+ * tearDown method
+ *
+ * @access public
+ * @return void
+ */
+ function tearDown() {
+ Configure::write('pluginPaths', $this->_pluginPaths);
+ 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->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->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 a component being used more than once.
+ *
+ * @return void
+ */
+ function testMultipleComponentInitialize() {
+ $Controller =& new ComponentTestController();
+ $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->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->assertTrue(is_a($Controller->Session, 'SessionComponent'));
+ $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() {
+ $Controller =& new ComponentTestController();
+ $Controller->components = array('Orange' => array('setting' => array('itemx')));
+
+ $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->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->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() {
+ $this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController');
+
+ $Controller =& new ComponentTestController();
+ $Controller->uses = array();
+ $Controller->components = array('Session');
+ $Controller->constructClasses();
+
+ $this->assertEqual($Controller->components, array('Session' => '', 'Orange' => array('colour' => 'blood orange')));
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/components/acl.test.php b/cake/tests/cases/libs/controller/components/acl.test.php
new file mode 100755
index 00000000..755d7ae1
--- /dev/null
+++ b/cake/tests/cases/libs/controller/components/acl.test.php
@@ -0,0 +1,614 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller.components
+ * @since CakePHP(tm) v 1.2.0.5435
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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 {
+}
+/**
+ * Short description for class.
+ *
+ * @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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/components/auth.test.php b/cake/tests/cases/libs/controller/components/auth.test.php
new file mode 100755
index 00000000..481b2dd5
--- /dev/null
+++ b/cake/tests/cases/libs/controller/components/auth.test.php
@@ -0,0 +1,1266 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.cake.tests.cases.libs.controller.components
+ * @since CakePHP(tm) v 1.2.0.5347
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import(array('controller' . DS . 'components' . DS .'auth', 'controller' . DS . 'components' . DS .'acl'));
+App::import(array('controller' . DS . 'components' . DS . 'acl', 'model' . DS . 'db_acl'));
+App::import('Core', 'Xml');
+/**
+* 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('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() {
+ }
+/**
+ * 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('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);
+
+ ClassRegistry::addObject('view', new View($this->Controller));
+
+ $this->Controller->Session->del('Auth');
+ $this->Controller->Session->del('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->del('Auth');
+ $this->Controller->Session->del('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));
+ }
+/**
+ * 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->del('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->del('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->del('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->del('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->del('Auth');
+ }
+/**
+ * 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->del('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->del('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->del('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->del('Auth');
+ $this->Controller->Auth->startup($this->Controller);
+ $this->assertTrue($this->Controller->Session->check('Message.auth'));
+ }
+/**
+ * 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');
+
+ $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));
+ }
+/**
+ * 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');
+ }
+/**
+ * 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->del('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->del('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->del('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->del('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->del('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->del('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->del('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->del('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->del('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->del('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->del('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->del('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->del($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->del($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);
+
+ $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->del('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() {
+ $admin = Configure::read('Routing.admin');
+ Configure::write('Routing.admin', '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.admin', $admin);
+ }
+/**
+ * testAjaxLogin method
+ *
+ * @access public
+ * @return void
+ */
+ function testAjaxLogin() {
+ Configure::write('viewPaths', 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", $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'));
+ }
+}
+?>
diff --git a/cake/tests/cases/libs/controller/components/cookie.test.php b/cake/tests/cases/libs/controller/components/cookie.test.php
new file mode 100755
index 00000000..5d8d87aa
--- /dev/null
+++ b/cake/tests/cases/libs/controller/components/cookie.test.php
@@ -0,0 +1,439 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller.components
+ * @since CakePHP(tm) v 1.2.0.5435
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Core', array('Component', 'Controller', '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();
+ }
+/**
+ * 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->del('name');
+ $this->Controller->Cookie->del('version');
+ $this->Controller->Cookie->del('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->del('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->del('Encrytped_array');
+ $data = $this->Controller->Cookie->read('Encrytped_array');
+ $expected = array();
+ $this->assertEqual($data, $expected);
+
+ $this->Controller->Cookie->del('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->del('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']);
+ }
+/**
+ * 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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/components/email.test.php b/cake/tests/cases/libs/controller/components/email.test.php
new file mode 100755
index 00000000..8baf95e4
--- /dev/null
+++ b/cake/tests/cases/libs/controller/components/email.test.php
@@ -0,0 +1,670 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.cake.tests.cases.libs.controller.components
+ * @since CakePHP(tm) v 1.2.0.5347
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Component', 'Email');
+/**
+ * EmailTestComponent class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller.components
+ */
+class EmailTestComponent extends EmailComponent {
+/**
+ * smtpSend method override for testing
+ *
+ * @access public
+ * @return mixed
+ */
+ function smtpSend($data, $code = '250') {
+ return parent::__smtpSend($data, $code);
+ }
+/**
+ * Convenience setter method for testing.
+ *
+ * @access public
+ * @return void
+ */
+ function setConnectionSocket(&$socket) {
+ $this->__smtpConnection = $socket;
+ }
+/**
+ * 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 method for testing.
+ *
+ * @access public
+ * @return string
+ */
+ function strip($content, $message = false) {
+ return parent::__strip($content, $message);
+ }
+}
+/**
+ * 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('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));
+
+ $this->_viewPaths = Configure::read('viewPaths');
+ Configure::write('viewPaths', 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);
+ Configure::write('viewPaths', $this->_viewPaths);
+ $this->Controller->Session->del('Message');
+ restore_error_handler();
+ ClassRegistry::flush();
+ }
+/**
+ * testBadSmtpSend method
+ *
+ * @access public
+ * @return void
+ */
+ function testBadSmtpSend() {
+ $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->reset();
+ $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));
+ }
+/**
+ * testAuthenticatedSmtpSend method
+ *
+ * @access public
+ * @return void
+ */
+ function testAuthenticatedSmtpSend() {
+ $this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost');
+
+ $this->Controller->EmailTest->reset();
+ $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->reset();
+ $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';
+
+ $message = <<To: postmaster@localhost
+From: noreply@example.com
+Subject: Cake SMTP test
+Header:
+
+From: noreply@example.com
+Reply-To: noreply@example.com
+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() {
+ $this->Controller->EmailTest->reset();
+ $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';
+
+ $header = <<Controller->EmailTest->layout = 'default';
+ $this->Controller->EmailTest->template = 'default';
+
+ $text = <<
+
+
+
+ EmailTest
+
+
+
+ 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 = <<
+
+
+
+ EmailTest
+
+
+
+ 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 . "\n" . '';
+ $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));
+
+ return;
+
+ $text = <<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', 'wide', 'default'));
+ $this->assertEqual($this->Controller->Session->read('Message.email.message'), $this->__osFix($expect));
+ }
+/**
+ * testSmtpSendSocket method
+ *
+ * @access public
+ * @return void
+ */
+ function testSmtpSendSocket() {
+ $this->skipIf(!@fsockopen('localhost', 25), '%s No SMTP server running on localhost');
+
+ $this->Controller->EmailTest->reset();
+ $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->reset();
+ $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->assertTrue($this->Controller->EmailTest->send('This is the body of the message'));
+ }
+/**
+ * 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\nMy own html content
";
+
+ $result = $this->Controller->EmailTest->strip($content, true);
+ $expected = "Previous content\n--alt-\n text/html; utf-8\n 7bit\n\nMy 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);
+
+ }
+/**
+ * testMultibyte method
+ *
+ * @access public
+ * @return void
+ */
+ function testMultibyte() {
+ $this->Controller->EmailTest->reset();
+ $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 testSendAsIsNotIgnoredIfAttachmentsPresent() {
+ $this->Controller->EmailTest->reset();
+ $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);
+ }
+/**
+ * undocumented function
+ *
+ * @return void
+ * @access public
+ */
+ function testNoDoubleNewlinesInHeaders() {
+ $this->Controller->EmailTest->reset();
+ $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->subject = 'Test subject';
+ $this->Controller->EmailTest->additionalParams = 'X-additional-header';
+ $this->Controller->EmailTest->delivery = 'smtp';
+ $this->Controller->EmailTest->smtpOptions['host'] = 'blah';
+ $this->Controller->EmailTest->attachments = array('attachment1', 'attachment2');
+
+ $this->assertFalse($this->Controller->EmailTest->send('Should not work'));
+
+ $this->Controller->EmailTest->reset();
+
+ $this->assertNull($this->Controller->EmailTest->template);
+ $this->assertNull($this->Controller->EmailTest->to);
+ $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->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());
+ }
+/**
+ * osFix method
+ *
+ * @param string $string
+ * @access private
+ * @return string
+ */
+ function __osFix($string) {
+ return str_replace(array("\r\n", "\r"), "\n", $string);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/components/request_handler.test.php b/cake/tests/cases/libs/controller/components/request_handler.test.php
new file mode 100755
index 00000000..f0e638d4
--- /dev/null
+++ b/cake/tests/cases/libs/controller/components/request_handler.test.php
@@ -0,0 +1,542 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller.components
+ * @since CakePHP(tm) v 1.2.0.5435
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Core', array('Controller'));
+App::import('Component', array('RequestHandler'));
+
+Mock::generatePartial('RequestHandlerComponent', 'NoStopRequestHandler', array('_stop'));
+/**
+ * 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');
+ }
+}
+/**
+ * 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.
+ }
+ }
+/**
+ * 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');
+ }
+/**
+ * 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');
+ }
+/**
+ * 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_object($this->Controller->data));
+ $this->assertEqual(strtolower(get_class($this->Controller->data)), 'xml');
+ }
+/**
+ * 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_object($this->Controller->data));
+ $this->assertEqual(strtolower(get_class($this->Controller->data)), 'xml');
+ }
+/**
+ * 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));
+ }
+/**
+ * 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/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/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());
+ }
+/**
+ * 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->getReferrer(), 'localhost');
+ $_SERVER['HTTP_HOST'] = null;
+ $_SERVER['HTTP_X_FORWARDED_HOST'] = 'cakephp.org';
+ $this->assertEqual($this->RequestHandler->getReferrer(), '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();
+ $_paths = Configure::read('viewPaths');
+ $testDir = array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS);
+ Configure::write('viewPaths', array_merge($testDir, $_paths));
+
+ $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.');
+
+ Configure::write('viewPaths', $_paths);
+ unset($_SERVER['HTTP_X_REQUESTED_WITH']);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/components/security.test.php b/cake/tests/cases/libs/controller/components/security.test.php
new file mode 100755
index 00000000..f744f2dc
--- /dev/null
+++ b/cake/tests/cases/libs/controller/components/security.test.php
@@ -0,0 +1,1105 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller.components
+ * @since CakePHP(tm) v 1.2.0.5435
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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('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 setUp() {
+ $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 tearDown() {
+ Configure::write('Security.salt', $this->oldSalt);
+ $this->Controller->Session->del('_Token');
+ unset($this->Controller->Security);
+ unset($this->Controller->Component);
+ unset($this->Controller);
+ }
+/**
+ * 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('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['REQUEST_METHOD'] = 'POST';
+ $this->Controller->action = 'posted';
+ $this->Controller->Security->requireSecure('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('posted');
+ $this->Controller->Security->startup($this->Controller);
+ $this->assertTrue($this->Controller->failed);
+
+ $this->Controller->Session->write('_Token', 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', 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('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(
+ '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('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('deleted');
+ $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%3An%3A1%3A%7Bv%3A0%3B';
+ $fields .= 'f%3A11%3A%22Zbqry.inyvq%22%3B%7D';
+
+ $this->Controller->data = array(
+ 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
+ '_Token' => compact('key', 'fields')
+ );
+ $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
+ }
+/**
+ * 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%3An%3A0%3A%7B%7D';
+
+ $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%3An%3A0%3A%7B%7D';
+
+ $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%3An%3A0%3A%7B%7D';
+
+ $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%3An%3A2%3A%7Bv%3A0%3Bf%3A14%3A%';
+ $fields .= '22Nqqerffrf.0.vq%22%3Bv%3A1%3Bf%3A14%3A%22Nqqerffrf.1.vq%22%3B%7D';
+
+ $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%3An%3A0%3A%7B%7D';
+
+ $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%3An%3A0%3A%7B%7D';
+ $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%3An%3A1%3A%7Bv%3A0%';
+ $fields .= '3Bf%3A11%3A%22Zbqry.inyvq%22%3B%7D';
+
+ $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%3An%3A0%3A%7B%7D';
+
+ $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%3An%3A2%3A%7Bv%3A0%3Bf%3A12%3A';
+ $fields .= '%22Zbqry.uvqqra%22%3Bv%3A1%3Bf%3A18%3A%22Zbqry.bgure_uvqqra%22%3B%7D';
+
+ $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%3An%3A1%3A%7Bv%';
+ $fields .= '3A0%3Bf%3A12%3A%22Zbqry.uvqqra%22%3B%7D';
+
+ $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%3An%3A3%3A%7Bv%3A0%3Bf%3A11';
+ $fields .= '%3A%22Zbqry.inyvq%22%3Bv%3A1%3Bf%3A12%3A%22Zbqry2.inyvq%22%3Bv%3A2%';
+ $fields .= '3Bf%3A12%3A%22Zbqry3.inyvq%22%3B%7D';
+
+ $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%3An%3A4%3A%7Bv%3A0%3Bf%3A14%3A%2';
+ $fields .= '2Zbqry.0.uvqqra%22%3Bv%3A1%3Bf%3A13%3A%22Zbqry.0.inyvq%22%3Bv%3A2%3Bf%3';
+ $fields .= 'A14%3A%22Zbqry.1.uvqqra%22%3Bv%3A3%3Bf%3A13%3A%22Zbqry.1.inyvq%22%3B%7D';
+
+ $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%3An%3A4%3A%7Bv%3A0%3Bf%3A12%3A%2';
+ $fields .= '2Nqqerff.0.vq%22%3Bv%3A1%3Bf%3A17%3A%22Nqqerff.0.cevznel%22%3Bv%3A2%3Bf%';
+ $fields .= '3A12%3A%22Nqqerff.1.vq%22%3Bv%3A3%3Bf%3A17%3A%22Nqqerff.1.cevznel%22%3B%7D';
+
+ $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%3An%3A4%3A%7Bv%3A0%3Bf%3A12%3A%2';
+ $fields .= '2Nqqerff.0.vq%22%3Bv%3A1%3Bf%3A17%3A%22Nqqerff.0.cevznel%22%3Bv%3A2%3Bf%';
+ $fields .= '3A12%3A%22Nqqerff.1.vq%22%3Bv%3A3%3Bf%3A17%3A%22Nqqerff.1.cevznel%22%3B%7D';
+
+ $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',
+ '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',
+ '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);
+ }
+/**
+ * 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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/components/session.test.php b/cake/tests/cases/libs/controller/components/session.test.php
new file mode 100755
index 00000000..b79f043c
--- /dev/null
+++ b/cake/tests/cases/libs/controller/components/session.test.php
@@ -0,0 +1,372 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller.components
+ * @since CakePHP(tm) v 1.2.0.5436
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Core', array('Controller', 'Object'));
+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);
+ }
+/**
+ * testSessionInitialize method
+ *
+ * @access public
+ * @return void
+ */
+ function testSessionInitialize() {
+ $Session =& new SessionComponent();
+
+ $this->assertEqual($Session->__bare, 0);
+
+ $Session->initialize(new SessionTestController());
+ $this->assertEqual($Session->__bare, 0);
+
+ $sessionController =& new SessionTestController();
+ $sessionController->params['bare'] = 1;
+ $Session->initialize($sessionController);
+ $this->assertEqual($Session->__bare, 1);
+ }
+/**
+ * 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->del('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->del('Test');
+
+ $array = array('key1' => 'val1', 'key2' => 'val2', 'key3' => 'val3');
+ $this->assertTrue($Session->write('Test', $array));
+ $this->assertEqual($Session->read('Test'), $array);
+ $Session->del('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->del('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);
+ }
+/**
+ * testSessionDel method
+ *
+ * @access public
+ * @return void
+ */
+ function testSessionDel() {
+ $Session =& new SessionComponent();
+
+ $this->assertFalse($Session->del('Test'));
+
+ $Session->write('Test', 'some value');
+ $this->assertTrue($Session->del('Test'));
+
+ Configure::write('Session.start', false);
+ $Session =& new SessionComponent();
+ $Session->write('Test', 'some value');
+ $this->assertFalse($Session->del('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', 'layout' => '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', 'layout' => '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', 'layout' => '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', 'layout' => 'default', 'params' => array()));
+
+ $Session->del('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'));
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/controller.test.php b/cake/tests/cases/libs/controller/controller.test.php
new file mode 100755
index 00000000..8c7b3204
--- /dev/null
+++ b/cake/tests/cases/libs/controller/controller.test.php
@@ -0,0 +1,1148 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ * @since CakePHP(tm) v 1.2.0.5436
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+App::import('Core', 'Controller');
+App::import('Component', 'Security');
+App::import('Component', 'Cookie');
+/**
+ * AppController class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ */
+if (!class_exists('AppController')) {
+ /**
+ * AppController class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ */
+ class AppController extends Controller {
+ /**
+ * helpers property
+ *
+ * @var array
+ * @access public
+ */
+ var $helpers = array('Html', 'Javascript');
+ /**
+ * uses property
+ *
+ * @var array
+ * @access public
+ */
+ var $uses = array('ControllerPost');
+ /**
+ * components property
+ *
+ * @var array
+ * @access public
+ */
+ var $components = array('Cookie');
+ }
+} elseif (!defined('APP_CONTROLLER_EXISTS')) {
+ define('APP_CONTROLLER_EXISTS', true);
+}
+/**
+ * ControllerPost class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ */
+class ControllerPost extends CakeTestModel {
+/**
+ * name property
+ *
+ * @var string 'ControllerPost'
+ * @access public
+ */
+ var $name = 'ControllerPost';
+/**
+ * useTable property
+ *
+ * @var string 'posts'
+ * @access public
+ */
+ var $useTable = 'posts';
+/**
+ * invalidFields property
+ *
+ * @var array
+ * @access public
+ */
+ var $invalidFields = array('name' => '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('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() {
+ }
+}
+/**
+ * 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');
+/**
+ * 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);
+
+ $_back = array(
+ 'pluginPaths' => Configure::read('pluginPaths'),
+ );
+ Configure::write('pluginPaths', 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'));
+
+ Configure::write('pluginPaths', $_back['pluginPaths']);
+ 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');
+ }
+/**
+ * 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);
+ }
+/**
+ * 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));
+ }
+/**
+ * testFlash method
+ *
+ * @access public
+ * @return void
+ */
+ function testFlash() {
+ $Controller =& new Controller();
+ $Controller->flash('this should work', '/flash');
+ $result = $Controller->output;
+
+ $expected = '
+
+
+
+ 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);
+ }
+/**
+ * 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->pageTitle, 'someTitle');
+
+ $Controller->viewVars = array();
+ $expected = array('ModelName' => 'name', 'ModelName2' => 'name2');
+ $Controller->set(array('ModelName', 'ModelName2'), array('name', 'name2'));
+ $this->assertIdentical($Controller->viewVars, $expected);
+ }
+/**
+ * testRender method
+ *
+ * @access public
+ * @return void
+ */
+ function testRender() {
+ Configure::write('viewPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS, TEST_CAKE_CORE_INCLUDE_PATH . 'libs' . DS . 'view' . DS));
+
+ $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();
+ }
+/**
+ * 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($TestController->helpers, $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);
+
+ $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->assertTrue(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'] = '';
+ $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);
+ }
+/**
+ * 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);
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/controller_merge_vars.test.php b/cake/tests/cases/libs/controller/controller_merge_vars.test.php
new file mode 100755
index 00000000..b405d3ec
--- /dev/null
+++ b/cake/tests/cases/libs/controller/controller_merge_vars.test.php
@@ -0,0 +1,222 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ * @since CakePHP(tm) v 1.2.3
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
+ */
+if (!class_exists('AppController')) {
+ /**
+ * Test case AppController requred
+ *
+ * @package 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 {
+
+/**
+ * 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 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(
+ 'Javascript',
+ 'MergeVar' => array('format' => 'html', 'terse')
+ );
+ $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->assertTrue(isset($Controller->Session));
+ }
+}
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/pages_controller.test.php b/cake/tests/cases/libs/controller/pages_controller.test.php
new file mode 100755
index 00000000..66980e74
--- /dev/null
+++ b/cake/tests/cases/libs/controller/pages_controller.test.php
@@ -0,0 +1,86 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ * @since CakePHP(tm) v 1.2.0.5436
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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('Core', array('Controller', 'PagesController'));
+/**
+ * PagesControllerTest class
+ *
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ */
+class PagesControllerTest extends CakeTestCase {
+/**
+ * setUp method
+ *
+ * @access public
+ * @return void
+ */
+ function setUp() {
+ $this->_viewPaths = Configure::read('viewPaths');
+ }
+/**
+ * tearDown method
+ *
+ * @access public
+ * @return void
+ */
+ function tearDown() {
+ Configure::write('viewPaths', $this->_viewPaths);
+ }
+/**
+ * testDisplay method
+ *
+ * @access public
+ * @return void
+ */
+ function testDisplay() {
+ if ($this->skipIf(defined('APP_CONTROLLER_EXISTS'), '%s Need a non-existent AppController')) {
+ return;
+ }
+
+ Configure::write('viewPaths', 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');
+ $this->assertEqual($Pages->pageTitle, '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');
+ $this->assertEqual($Pages->pageTitle, 'Index');
+ }
+}
+?>
\ No newline at end of file
diff --git a/cake/tests/cases/libs/controller/scaffold.test.php b/cake/tests/cases/libs/controller/scaffold.test.php
new file mode 100755
index 00000000..72cab3eb
--- /dev/null
+++ b/cake/tests/cases/libs/controller/scaffold.test.php
@@ -0,0 +1,785 @@
+
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests
+ * @package cake
+ * @subpackage cake.tests.cases.libs.controller
+ * @since CakePHP(tm) v 1.2.0.5436
+ * @version $Revision$
+ * @modifiedby $LastChangedBy$
+ * @lastmodified $Date$
+ * @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' => 'post_id',
+ 'associationForeignKey' => 'tag_id',
+ 'joinTable' => 'posts_tags'
+ )
+ );
+}
+/**
+ * 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.posts_tag', '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);
+ }
+/**
+ * testGetViewFilename method
+ *
+ * @access public
+ * @return void
+ */
+ function testGetViewFilename() {
+ $_admin = Configure::read('Routing.admin');
+ Configure::write('Routing.admin', '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);
+
+ $_back = array(
+ 'viewPaths' => Configure::read('viewPaths'),
+ 'pluginPaths' => Configure::read('pluginPaths'),
+ );
+ Configure::write('viewPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS));
+ Configure::write('pluginPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS));
+
+ $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('viewPaths', $_back['viewPaths']);
+ Configure::write('pluginPaths', $_back['pluginPaths']);
+ Configure::write('Routing.admin', $_admin);
+ }
+/**
+ * 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);
+ //TODO: add testing for table generation
+ $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('/