From dc5b1c1f5b7f1e55e539fad3ba03d191b05ecdc2 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 24 Jan 2017 11:29:50 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=AF=BC=E5=85=A5=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 54 + README.md | 127 + app/appadmin/config/appadmin.php | 38 + app/appadmin/config/modules/Catalog.php | 14 + app/appadmin/config/modules/Cms.php | 16 + app/appadmin/config/modules/Customer.php | 16 + app/appadmin/config/modules/Fecadmin.php | 18 + app/appadmin/config/modules/Sales.php | 16 + .../base/AppadminbaseBlockEditInterface.php | 24 + .../base/AppadminbaseBlockInterface.php | 20 + app/appadmin/modules/AppadminbaseBlock.php | 619 ++++ .../modules/AppadminbaseBlockEdit.php | 253 ++ .../modules/Catalog/CatalogController.php | 28 + app/appadmin/modules/Catalog/Module.php | 30 + .../modules/Catalog/block/category/Image.php | 31 + .../modules/Catalog/block/category/Index.php | 318 ++ .../Catalog/block/category/Product.php | 485 +++ .../Catalog/block/productfavorite/Index.php | 236 ++ .../block/productinfo/Getproductcategory.php | 165 + .../Catalog/block/productinfo/Imageupload.php | 59 + .../Catalog/block/productinfo/Index.php | 354 +++ .../Catalog/block/productinfo/Manageredit.php | 510 ++++ .../Catalog/block/productinfo/index/Attr.php | 355 +++ .../Catalog/block/productreview/Index.php | 329 ++ .../block/productreview/Manageredit.php | 259 ++ .../Catalog/block/urlrewrite/Index.php | 239 ++ .../controllers/CategoryController.php | 79 + .../controllers/ProductfavoriteController.php | 37 + .../controllers/ProductinfoController.php | 60 + .../controllers/ProductreviewController.php | 64 + .../controllers/UrlrewriteController.php | 69 + .../modules/Catalog/helper/Product.php | 40 + .../modules/Catalog/views/category/index.php | 192 ++ .../modules/Catalog/views/category/info.php | 122 + .../Catalog/views/category/product.php | 232 ++ .../Catalog/views/productfavorite/index.php | 41 + .../productinfo/custom_option_edit_copy.php | 182 ++ .../Catalog/views/productinfo/index.php | 46 + .../Catalog/views/productinfo/manageredit.php | 509 ++++ .../Catalog/views/productreview/index.php | 41 + .../views/productreview/manageredit.php | 85 + .../Catalog/views/urlrewrite/index.php | 39 + app/appadmin/modules/Cms/CmsController.php | 28 + app/appadmin/modules/Cms/Module.php | 30 + .../modules/Cms/block/article/Index.php | 237 ++ .../modules/Cms/block/article/Manageredit.php | 184 ++ .../modules/Cms/block/staticblock/Index.php | 250 ++ .../Cms/block/staticblock/Manageredit.php | 164 + .../Cms/controllers/ArticleController.php | 69 + .../Cms/controllers/StaticblockController.php | 85 + .../modules/Cms/views/article/index.php | 41 + .../modules/Cms/views/article/manageredit.php | 61 + .../modules/Cms/views/staticblock/index.php | 41 + .../Cms/views/staticblock/manageredit.php | 70 + .../modules/Customer/CustomerController.php | 28 + app/appadmin/modules/Customer/Module.php | 30 + .../modules/Customer/block/account/Index.php | 226 ++ .../Customer/block/account/Manageredit.php | 187 ++ .../controllers/AccountController.php | 55 + .../modules/Customer/views/account/index.php | 41 + .../Customer/views/account/manageredit.php | 61 + app/appadmin/modules/Sales/Module.php | 30 + .../modules/Sales/SalesController.php | 28 + .../modules/Sales/block/coupon/Manager.php | 279 ++ .../Sales/block/coupon/Manageredit.php | 185 ++ .../modules/Sales/block/orderinfo/Manager.php | 293 ++ .../Sales/block/orderinfo/Manageredit.php | 50 + .../Sales/controllers/CouponController.php | 69 + .../Sales/controllers/OrderinfoController.php | 44 + .../modules/Sales/views/coupon/manager.php | 41 + .../Sales/views/coupon/manageredit.php | 61 + .../modules/Sales/views/orderinfo/manager.php | 41 + .../Sales/views/orderinfo/manageredit.php | 325 ++ app/appapi/config/appapi.php | 57 + app/appapi/config/modules/V1.php | 16 + app/appapi/modules/AppapiController.php | 47 + app/appapi/modules/AppapiModule.php | 18 + app/appapi/modules/V1/Module.php | 21 + .../controllers/article/IndexController.php | 44 + app/appfront/config/appfront.php | 84 + app/appfront/config/modules/Catalog.php | 110 + app/appfront/config/modules/Catalogsearch.php | 53 + app/appfront/config/modules/Checkout.php | 20 + app/appfront/config/modules/Cms.php | 44 + app/appfront/config/modules/Customer.php | 118 + app/appfront/config/modules/Payment.php | 20 + app/appfront/config/modules/Site.php | 14 + app/appfront/config/params.php | 20 + app/appfront/helper/Country.php | 78 + app/appfront/helper/Format.php | 37 + app/appfront/helper/Shipping.php | 23 + app/appfront/helper/mailer/Email.php | 250 ++ app/appfront/languages/de_DE/appfront.php | 11 + app/appfront/languages/en_US/appfront.php | 11 + app/appfront/languages/es_ES/appfront.php | 11 + app/appfront/languages/fr_FR/appfront.php | 12 + app/appfront/languages/it_IT/appfront.php | 11 + app/appfront/languages/nl_NL/appfront.php | 11 + app/appfront/languages/pt_PT/appfront.php | 11 + app/appfront/languages/ru_RU/appfront.php | 11 + app/appfront/languages/zh_CN/appfront.php | 34 + app/appfront/modules/AppfrontController.php | 96 + app/appfront/modules/AppfrontModule.php | 31 + app/appfront/modules/Catalog/Module.php | 41 + .../modules/Catalog/block/category/Index.php | 433 +++ .../modules/Catalog/block/category/Price.php | 30 + .../Catalog/block/category/index/Page.php | 0 .../Catalog/block/favoriteproduct/Add.php | 57 + .../Catalog/block/product/CustomOption.php | 118 + .../modules/Catalog/block/product/Index.php | 301 ++ .../modules/Catalog/block/product/Review.php | 82 + .../Catalog/block/reviewproduct/Add.php | 160 + .../Catalog/block/reviewproduct/Lists.php | 147 + .../controllers/CategoryController.php | 49 + .../controllers/FavoriteproductController.php | 50 + .../Catalog/controllers/ProductController.php | 79 + .../controllers/ReviewproductController.php | 76 + .../modules/Catalog/helpers/Price.php | 43 + .../modules/Catalog/helpers/Review.php | 68 + app/appfront/modules/Catalogsearch/Module.php | 41 + .../Catalogsearch/block/index/Index.php | 411 +++ .../controllers/IndexController.php | 48 + app/appfront/modules/Checkout/Module.php | 41 + .../modules/Checkout/block/cart/Index.php | 142 + .../modules/Checkout/block/onepage/Index.php | 442 +++ .../Checkout/block/onepage/Placeorder.php | 230 ++ .../Checkout/controllers/CartController.php | 195 ++ .../controllers/OnepageController.php | 67 + app/appfront/modules/Cms/Module.php | 30 + .../modules/Cms/block/article/Index.php | 63 + app/appfront/modules/Cms/block/home/Index.php | 84 + .../modules/Cms/block/widgets/Footer.php | 23 + .../modules/Cms/block/widgets/Head.php | 27 + .../modules/Cms/block/widgets/Headers.php | 35 + .../modules/Cms/block/widgets/Menu.php | 27 + .../Cms/controllers/ArticleController.php | 39 + .../Cms/controllers/HomeController.php | 69 + app/appfront/modules/Customer/Module.php | 41 + .../modules/Customer/block/LeftMenu.php | 55 + .../Customer/block/account/Forgotpassword.php | 85 + .../modules/Customer/block/account/Index.php | 39 + .../modules/Customer/block/account/Login.php | 78 + .../Customer/block/account/Register.php | 87 + .../Customer/block/account/Resetpassword.php | 75 + .../block/account/Resetpasswordsuccess.php | 29 + .../modules/Customer/block/address/Edit.php | 230 ++ .../modules/Customer/block/address/Index.php | 64 + .../modules/Customer/block/contacts/Index.php | 108 + .../Customer/block/editaccount/Index.php | 101 + .../account/forgotpassword/EmailBody.php | 47 + .../block/mailer/account/login/EmailBody.php | 43 + .../mailer/account/register/EmailBody.php | 44 + .../block/mailer/contacts/EmailBody.php | 37 + .../block/mailer/newsletter/EmailBody.php | 37 + .../Customer/block/newsletter/Index.php | 39 + .../modules/Customer/block/order/Index.php | 85 + .../modules/Customer/block/order/Reorder.php | 64 + .../modules/Customer/block/order/View.php | 49 + .../modules/Customer/block/point/Index.php | 25 + .../Customer/block/productfavorite/Index.php | 112 + .../Customer/block/productreview/Index.php | 74 + .../controllers/AccountController.php | 181 ++ .../controllers/AddressController.php | 66 + .../Customer/controllers/AjaxController.php | 87 + .../controllers/ContactsController.php | 55 + .../controllers/EditaccountController.php | 57 + .../controllers/NewsletterController.php | 51 + .../Customer/controllers/OrderController.php | 62 + .../Customer/controllers/PointController.php | 54 + .../controllers/ProductfavoriteController.php | 58 + .../controllers/ProductreviewController.php | 54 + app/appfront/modules/Payment/Module.php | 41 + .../modules/Payment/PaymentController.php | 45 + .../controllers/CheckmoneyController.php | 83 + .../controllers/paypal/ExpressController.php | 52 + .../controllers/paypal/StandardController.php | 52 + app/appfront/modules/Site/Module.php | 30 + .../Site/controllers/HelperController.php | 43 + app/appfront/theme/BaseAsset.php | 0 .../theme/base/front/assets/css/ie.css | 1 + .../theme/base/front/assets/css/ltie9.css | 0 .../base/front/assets/css/owl.carousel.css | 163 + .../theme/base/front/assets/css/style.css | 2671 +++++++++++++++++ .../assets/elevatezoom/jquery.elevatezoom.js | 1790 +++++++++++ .../base/front/assets/fancybox/blank.gif | Bin 0 -> 43 bytes .../assets/fancybox/fancybox_loading.gif | Bin 0 -> 6567 bytes .../assets/fancybox/fancybox_loading@2x.gif | Bin 0 -> 13984 bytes .../assets/fancybox/fancybox_overlay.png | Bin 0 -> 1003 bytes .../front/assets/fancybox/fancybox_sprite.png | Bin 0 -> 1362 bytes .../assets/fancybox/fancybox_sprite@2x.png | Bin 0 -> 6553 bytes .../fancybox/helpers/fancybox_buttons.png | Bin 0 -> 1080 bytes .../helpers/jquery.fancybox-buttons.css | 97 + .../helpers/jquery.fancybox-buttons.js | 122 + .../fancybox/helpers/jquery.fancybox-media.js | 201 ++ .../helpers/jquery.fancybox-thumbs.css | 55 + .../helpers/jquery.fancybox-thumbs.js | 165 + .../front/assets/fancybox/jquery.fancybox.css | 275 ++ .../front/assets/fancybox/jquery.fancybox.js | 2018 +++++++++++++ .../assets/fancybox/jquery.fancybox.pack.js | 46 + .../theme/base/front/assets/images/1.jpg | Bin 0 -> 22606 bytes .../theme/base/front/assets/images/16.jpg | Bin 0 -> 64143 bytes .../theme/base/front/assets/images/17.jpg | Bin 0 -> 56074 bytes .../theme/base/front/assets/images/18.jpg | Bin 0 -> 75759 bytes .../theme/base/front/assets/images/2.jpg | Bin 0 -> 15881 bytes .../theme/base/front/assets/images/3.jpg | Bin 0 -> 21107 bytes .../base/front/assets/images/AjaxLoader.gif | Bin 0 -> 1517 bytes .../theme/base/front/assets/images/Star_0.png | Bin 0 -> 1314 bytes .../theme/base/front/assets/images/Star_1.png | Bin 0 -> 1338 bytes .../theme/base/front/assets/images/Star_2.png | Bin 0 -> 1352 bytes .../theme/base/front/assets/images/Star_3.png | Bin 0 -> 1333 bytes .../theme/base/front/assets/images/Star_4.png | Bin 0 -> 1335 bytes .../theme/base/front/assets/images/Star_5.png | Bin 0 -> 1282 bytes .../front/assets/images/add_Close_hover.jpg | Bin 0 -> 3909 bytes .../front/assets/images/add_save_hover.jpg | Bin 0 -> 4197 bytes .../front/assets/images/bg_direction_nav2.png | Bin 0 -> 700 bytes .../base/front/assets/images/btn_trash.gif | Bin 0 -> 643 bytes .../base/front/assets/images/checkbox.png | Bin 0 -> 1488 bytes .../theme/base/front/assets/images/cur.png | Bin 0 -> 101 bytes .../theme/base/front/assets/images/en_.jpg | Bin 0 -> 263340 bytes .../theme/base/front/assets/images/en_a.jpg | Bin 0 -> 143414 bytes .../base/front/assets/images/favourite.png | Bin 0 -> 1778 bytes .../theme/base/front/assets/images/fb.png | Bin 0 -> 354 bytes .../base/front/assets/images/footer_fixed.png | Bin 0 -> 652 bytes .../base/front/assets/images/free_en.jpg | Bin 0 -> 7673 bytes .../base/front/assets/images/googole.png | Bin 0 -> 634 bytes .../base/front/assets/images/i_msg-error.gif | Bin 0 -> 1013 bytes .../front/assets/images/i_msg-success.gif | Bin 0 -> 1024 bytes .../theme/base/front/assets/images/jj.png | Bin 0 -> 922 bytes .../theme/base/front/assets/images/new.jpg | Bin 0 -> 8648 bytes .../base/front/assets/images/paypaltopay.jpg | Bin 0 -> 5932 bytes .../theme/base/front/assets/images/pinter.png | Bin 0 -> 757 bytes .../theme/base/front/assets/images/pp.png | Bin 0 -> 23357 bytes .../images/product_rating_big_blank_star.png | Bin 0 -> 678 bytes .../images/product_rating_big_full_star.png | Bin 0 -> 1864 bytes .../theme/base/front/assets/images/sammy.jpg | Bin 0 -> 83905 bytes .../base/front/assets/images/scart_step1.gif | Bin 0 -> 1951 bytes .../theme/base/front/assets/images/sign.png | Bin 0 -> 3246 bytes .../base/front/assets/images/signloading.gif | Bin 0 -> 771 bytes .../theme/base/front/assets/images/tag.png | Bin 0 -> 10225 bytes .../theme/base/front/assets/images/toptip.png | Bin 0 -> 2580 bytes .../base/front/assets/images/twitter.png | Bin 0 -> 538 bytes .../assets/images/validation_advice_bg.gif | Bin 0 -> 134 bytes .../theme/base/front/assets/images/vip.jpg | Bin 0 -> 8178 bytes .../base/front/assets/js/jquery-3.0.0.min.js | 4 + .../base/front/assets/js/jquery.lazyload.js | 241 ++ .../front/assets/js/jquery.lazyload.min.js | 3 + app/appfront/theme/base/front/assets/js/js.js | 77 + .../base/front/assets/js/owl.carousel.js | 1517 ++++++++++ .../base/front/assets/js/owl.carousel.min.js | 47 + .../onestepcheckout/images/ajax-loader.gif | Bin 0 -> 673 bytes .../images/button-background.png | Bin 0 -> 135 bytes .../assets/onestepcheckout/images/hm.png | Bin 0 -> 45178 bytes .../onestepcheckout/images/line-gradient.gif | Bin 0 -> 156 bytes .../images/onestepcheckout-error.jpg | Bin 0 -> 875 bytes .../images/onestepcheckout-numbers-1.gif | Bin 0 -> 613 bytes .../images/onestepcheckout-numbers-1.png | Bin 0 -> 1536 bytes .../images/onestepcheckout-numbers-2.gif | Bin 0 -> 856 bytes .../images/onestepcheckout-numbers-2.png | Bin 0 -> 1773 bytes .../images/onestepcheckout-numbers-3.gif | Bin 0 -> 876 bytes .../images/onestepcheckout-numbers-3.png | Bin 0 -> 1810 bytes .../images/onestepcheckout-numbers-4.gif | Bin 0 -> 845 bytes .../images/onestepcheckout-numbers-4.png | Bin 0 -> 1651 bytes .../images/onestepcheckout-place-order.png | Bin 0 -> 3023 bytes .../images/onestepcheckout-popup-footer.gif | Bin 0 -> 276 bytes .../images/onestepcheckout-popup-footer.png | Bin 0 -> 425 bytes .../images/onestepcheckout-popup-header.gif | Bin 0 -> 4497 bytes .../images/onestepcheckout-popup-header.png | Bin 0 -> 2334 bytes .../images/skin-magento/.svn/entries | 164 + .../onestepcheckout-numbers-1.png.svn-base | 9 + .../onestepcheckout-numbers-2.png.svn-base | 9 + .../onestepcheckout-numbers-3.png.svn-base | 9 + .../onestepcheckout-numbers-4.png.svn-base | 9 + .../onestepcheckout-numbers-1.png.svn-base | Bin 0 -> 256 bytes .../onestepcheckout-numbers-2.png.svn-base | Bin 0 -> 282 bytes .../onestepcheckout-numbers-3.png.svn-base | Bin 0 -> 300 bytes .../onestepcheckout-numbers-4.png.svn-base | Bin 0 -> 294 bytes .../onestepcheckout-numbers-1.png | Bin 0 -> 256 bytes .../onestepcheckout-numbers-2.png | Bin 0 -> 282 bytes .../onestepcheckout-numbers-3.png | Bin 0 -> 300 bytes .../onestepcheckout-numbers-4.png | Bin 0 -> 294 bytes .../onestepcheckout/onestepcheckout.css | 821 +++++ .../base/front/catalog/category/index.php | 154 + .../catalog/category/index/filter/attr.php | 38 + .../catalog/category/index/filter/price.php | 37 + .../category/index/filter/refineby.php | 12 + .../category/index/filter/subcategory.php | 10 + .../front/catalog/category/index/toolbar.php | 32 + .../base/front/catalog/category/price.php | 13 + .../base/front/catalog/product/index.php | 384 +++ .../catalog/product/index/buy_also_buy.php | 57 + .../catalog/product/index/custom_option.php | 235 ++ .../front/catalog/product/index/image.php | 133 + .../front/catalog/product/index/options.php | 74 + .../front/catalog/product/index/payment.php | 22 + .../front/catalog/product/index/price.php | 15 + .../front/catalog/product/index/review.php | 55 + .../catalog/product/index/tier_price.php | 31 + .../base/front/catalog/reviewproduct/add.php | 142 + .../front/catalog/reviewproduct/lists.php | 102 + .../base/front/catalogsearch/index/index.php | 146 + .../catalogsearch/index/index/toolbar.php | 22 + .../theme/base/front/checkout/cart/index.php | 322 ++ .../base/front/checkout/onepage/index.php | 409 +++ .../front/checkout/onepage/index/address.php | 111 + .../checkout/onepage/index/address_select.php | 98 + .../front/checkout/onepage/index/payment.php | 34 + .../checkout/onepage/index/review_order.php | 81 + .../front/checkout/onepage/index/shipping.php | 24 + .../theme/base/front/cms/article/index.php | 17 + .../theme/base/front/cms/home/index.php | 81 + .../theme/base/front/cms/home/index/price.php | 10 + .../base/front/cms/home/index/product.php | 38 + .../front/customer/account/forgotpassword.php | 109 + .../customer/account/forgotpasswordsubmit.php | 25 + .../base/front/customer/account/index.php | 78 + .../base/front/customer/account/login.php | 74 + .../base/front/customer/account/register.php | 210 ++ .../front/customer/account/resetpassword.php | 141 + .../customer/account/resetpasswordsuccess.php | 7 + .../base/front/customer/address/edit.php | 205 ++ .../base/front/customer/address/index.php | 76 + .../base/front/customer/contacts/index.php | 96 + .../base/front/customer/editaccount/index.php | 217 ++ .../theme/base/front/customer/leftmenu.php | 18 + .../base/front/customer/newsletter/error.php | 3 + .../base/front/customer/newsletter/index.php | 3 + .../theme/base/front/customer/order/index.php | 66 + .../base/front/customer/order/reorder.php | 0 .../theme/base/front/customer/order/view.php | 170 ++ .../front/customer/productfavorite/index.php | 98 + .../front/customer/productreview/index.php | 84 + .../base/front/layouts/category_view.php | 62 + .../theme/base/front/layouts/home.php | 65 + .../theme/base/front/layouts/main.php | 84 + .../base/front/layouts/one_step_checkout.php | 85 + .../theme/base/front/layouts/product_view.php | 67 + .../account/forgotpassword/body_en.php | 34 + .../account/forgotpassword/subject_en.php | 1 + .../mailer/customer/account/login/body_de.php | 0 .../mailer/customer/account/login/body_en.php | 12 + .../mailer/customer/account/login/body_es.php | 0 .../mailer/customer/account/login/body_fr.php | 0 .../mailer/customer/account/login/body_zh.php | 1 + .../customer/account/login/subject_de.php | 0 .../customer/account/login/subject_en.php | 1 + .../customer/account/login/subject_es.php | 1 + .../customer/account/login/subject_fr.php | 1 + .../customer/account/login/subject_zh.php | 1 + .../customer/account/register/body_de.php | 0 .../customer/account/register/body_en.php | 44 + .../customer/account/register/body_es.php | 1 + .../customer/account/register/body_fr.php | 0 .../customer/account/register/body_zh.php | 1 + .../customer/account/register/subject_de.php | 0 .../customer/account/register/subject_en.php | 1 + .../customer/account/register/subject_es.php | 0 .../customer/account/register/subject_fr.php | 0 .../customer/account/register/subject_zh.php | 1 + .../mailer/customer/contacts/body_de.php | 0 .../mailer/customer/contacts/body_en.php | 5 + .../mailer/customer/contacts/body_es.php | 0 .../mailer/customer/contacts/body_fr.php | 0 .../mailer/customer/contacts/body_zh.php | 0 .../mailer/customer/contacts/subject_de.php | 0 .../mailer/customer/contacts/subject_en.php | 1 + .../mailer/customer/contacts/subject_es.php | 0 .../mailer/customer/contacts/subject_fr.php | 0 .../mailer/customer/contacts/subject_zh.php | 0 .../mailer/customer/newsletter/body_en.php | 35 + .../mailer/customer/newsletter/subject_en.php | 1 + .../base/front/payment/checkmoney/success.php | 15 + .../theme/base/front/payment/success.php | 9 + .../theme/base/front/site/helper/error.php | 30 + .../theme/base/front/widgets/breadcrumbs.php | 28 + .../theme/base/front/widgets/flashmessage.php | 20 + .../theme/base/front/widgets/footer.php | 90 + .../theme/base/front/widgets/head.php | 16 + .../theme/base/front/widgets/header.php | 98 + .../theme/base/front/widgets/menu.php | 51 + .../theme/base/front/widgets/page.php | 37 + .../theme/base/front/widgets/scroll.php | 14 + .../theme/base/front/widgets/topsearch.php | 9 + app/appfront/widgets/Footer.php | 23 + app/appfront/widgets/Head.php | 27 + app/appfront/widgets/Headers.php | 36 + app/appfront/widgets/Menu.php | 27 + app/appfront/widgets/Page.php | 234 ++ app/apphtml5/config/apphtml5.php | 74 + app/apphtml5/config/modules/.gitignore | 1 + app/apphtml5/languages/.gitignore | 1 + app/apphtml5/modules/.gitignore | 0 app/apphtml5/theme/.gitignore | 0 app/appserver/config/appserver.php | 73 + app/appserver/config/modules/.gitignore | 1 + app/appserver/languages/.gitignore | 0 app/appserver/modules/.gitignore | 0 app/console/config/console.php | 20 + app/console/config/modules/Helper.php | 15 + app/console/config/modules/Product.php | 15 + app/console/modules/ConsoleController.php | 45 + app/console/modules/ConsoleModule.php | 20 + app/console/modules/Helper/Module.php | 28 + .../controllers/UrlrewriteController.php | 99 + app/console/modules/Product/Module.php | 28 + .../Product/controllers/IndexController.php | 31 + .../Product/controllers/PriceController.php | 72 + .../Product/controllers/SearchController.php | 99 + .../controllers/search/MongodbController.php | 26 + .../controllers/search/XunController.php | 26 + components/Store.php | 28 + composer.json | 42 + config/components/Store.php | 13 + config/components/XunSearch.php | 15 + config/fecshop.php | 27 + config/services/AdminUser.php | 12 + config/services/Cart.php | 27 + config/services/Category.php | 29 + config/services/Cms.php | 24 + config/services/Customer.php | 56 + config/services/Email.php | 66 + config/services/FecshopLang.php | 38 + config/services/Helper.php | 60 + config/services/Image.php | 24 + config/services/Order.php | 34 + config/services/Page.php | 144 + config/services/Payment.php | 37 + config/services/Product.php | 127 + config/services/Search.php | 47 + config/services/Shipping.php | 32 + config/services/Store.php | 29 + config/services/Systemhelper.php | 13 + config/services/Url.php | 25 + config/xunsearch/product.ini | 0 config/xunsearch/search.ini | 79 + initFecShop | 223 ++ interfaces/block/BlockCache.php | 19 + migrations/READ.me | 1 + .../log/m160607_052627_log_product_view.php | 46 + ...160608_062040_mongodb_log_product_view.php | 17 + .../m160608_061933_mongodb_url_write.php | 22 + models/mongodb/Category.php | 70 + models/mongodb/CategoryProduct.php | 45 + models/mongodb/FecshopServiceLog.php | 42 + models/mongodb/Newsletter.php | 82 + models/mongodb/Product.php | 108 + models/mongodb/Search.php | 75 + models/mongodb/UrlRewrite.php | 37 + models/mongodb/cms/Article.php | 41 + models/mongodb/cms/StaticBlock.php | 39 + models/mongodb/customer/Newsletter.php | 40 + models/mongodb/product/Favorite.php | 48 + models/mongodb/product/Review.php | 72 + models/mongodb/product/ViewLog.php | 39 + models/mongodb/url/UrlRewrite.php | 39 + models/mysqldb/Cart.php | 29 + models/mysqldb/Customer.php | 190 ++ models/mysqldb/Order.php | 29 + models/mysqldb/UrlRewrite.php | 28 + models/mysqldb/cart/Coupon.php | 25 + models/mysqldb/cart/CouponUsage.php | 30 + models/mysqldb/cart/Item.php | 28 + models/mysqldb/cms/Article.php | 28 + models/mysqldb/cms/StaticBlock.php | 28 + models/mysqldb/customer/Address.php | 30 + models/mysqldb/customer/CustomerLogin.php | 80 + models/mysqldb/customer/CustomerRegister.php | 122 + .../customer/CustomerResetPassword.php | 75 + models/mysqldb/order/Item.php | 28 + models/mysqldb/product/ViewLog.php | 32 + models/mysqldb/url/UrlRewrite.php | 28 + models/xunsearch/Search.php | 22 + services/AdminUser.php | 36 + services/Affiliate.php | 0 services/Application.php | 52 + services/Blog.php | 75 + services/Cart.php | 193 ++ services/Category.php | 134 + services/Cms.php | 27 + services/Coupon.php | 24 + services/Customer.php | 332 ++ services/Email.php | 189 ++ services/FecshopLang.php | 140 + services/Helper.php | 43 + services/Image.php | 209 ++ services/Order.php | 342 +++ services/Page.php | 25 + services/Payment.php | 132 + services/Point.php | 0 services/Product.php | 321 ++ services/Request.php | 135 + services/Search.php | 150 + services/Service.php | 172 ++ services/Shipping.php | 186 ++ services/Sitemap.php | 0 services/Store.php | 184 ++ services/Systemhelper.php | 25 + services/Url.php | 372 +++ services/Wholesale.php | 0 services/cart/Coupon.php | 436 +++ services/cart/Info.php | 80 + services/cart/Quote.php | 554 ++++ services/cart/QuoteItem.php | 326 ++ services/category/CategoryInterface.php | 20 + services/category/CategoryMongodb.php | 377 +++ services/category/CategoryMysqldb.php | 0 services/category/Image.php | 82 + services/category/Menu.php | 70 + services/category/Product.php | 130 + services/cms/Article.php | 90 + services/cms/StaticBlock.php | 125 + services/cms/article/ArticleInterface.php | 20 + services/cms/article/ArticleMongodb.php | 125 + services/cms/article/ArticleMysqldb.php | 174 ++ .../cms/staticblock/StaticBlockInterface.php | 20 + .../cms/staticblock/StaticBlockMongodb.php | 134 + .../cms/staticblock/StaticBlockMysqldb.php | 168 ++ services/customer/Address.php | 360 +++ services/customer/Affiliate.php | 24 + services/customer/Coupon.php | 24 + services/customer/DropShip.php | 24 + services/customer/Favorite.php | 24 + services/customer/Message.php | 24 + services/customer/Newsletter.php | 70 + services/customer/Order.php | 24 + services/customer/Point.php | 24 + services/customer/Review.php | 24 + services/customer/Wholesale.php | 24 + services/helper/AR.php | 98 + services/helper/Captcha.php | 132 + services/helper/Country.php | 932 ++++++ services/helper/Errors.php | 47 + services/helper/Log.php | 181 ++ services/helper/MobileDetect.php | 1363 +++++++++ services/helper/captcha/Elephant.ttf | Bin 0 -> 44820 bytes services/order/Item.php | 182 ++ services/page/Asset.php | 127 + services/page/Breadcrumbs.php | 84 + services/page/Currency.php | 209 ++ services/page/Footer.php | 51 + services/page/Menu.php | 128 + services/page/Message.php | 98 + services/page/Newsletter.php | 67 + services/page/StaticBlock.php | 53 + services/page/Theme.php | 117 + services/page/Translate.php | 47 + services/page/Widget.php | 157 + services/product/BestSell.php | 40 + services/product/BuyAlsoBuy.php | 0 services/product/Image.php | 162 + services/product/Info.php | 121 + services/product/Price.php | 278 ++ services/product/ProductInterface.php | 20 + services/product/ProductMongodb.php | 514 ++++ services/product/ProductMysqldb.php | 0 services/product/Review.php | 404 +++ services/product/ViewLog.php | 51 + services/product/viewLog/Db.php | 82 + services/product/viewLog/Mongodb.php | 87 + services/product/viewLog/Session.php | 72 + services/search/MongoSearch.php | 315 ++ services/search/SearchInterface.php | 24 + services/search/XunSearch.php | 243 ++ services/url/Category.php | 235 ++ services/url/Rewrite.php | 108 + services/url/rewrite/RewriteInterface.php | 24 + services/url/rewrite/RewriteMongodb.php | 157 + services/url/rewrite/RewriteMysqldb.php | 184 ++ shell/README.md | 42 + shell/computeProductFinalPrice.sh | 26 + shell/fullSearchSync.sh | 42 + shell/initDb.sh | 16 + shell/search/deleteXunSearchAllData.sh | 16 + shell/search/fullSearchSync.sh | 30 + shell/urlRewrite.sh | 53 + yii/Yii.php | 23 + yii/i18n/PhpMessageSource.php | 48 + yii/web/Request.php | 145 + yii/web/User.php | 59 + 578 files changed, 52386 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/appadmin/config/appadmin.php create mode 100644 app/appadmin/config/modules/Catalog.php create mode 100644 app/appadmin/config/modules/Cms.php create mode 100644 app/appadmin/config/modules/Customer.php create mode 100644 app/appadmin/config/modules/Fecadmin.php create mode 100644 app/appadmin/config/modules/Sales.php create mode 100644 app/appadmin/interfaces/base/AppadminbaseBlockEditInterface.php create mode 100644 app/appadmin/interfaces/base/AppadminbaseBlockInterface.php create mode 100644 app/appadmin/modules/AppadminbaseBlock.php create mode 100644 app/appadmin/modules/AppadminbaseBlockEdit.php create mode 100644 app/appadmin/modules/Catalog/CatalogController.php create mode 100644 app/appadmin/modules/Catalog/Module.php create mode 100644 app/appadmin/modules/Catalog/block/category/Image.php create mode 100644 app/appadmin/modules/Catalog/block/category/Index.php create mode 100644 app/appadmin/modules/Catalog/block/category/Product.php create mode 100644 app/appadmin/modules/Catalog/block/productfavorite/Index.php create mode 100644 app/appadmin/modules/Catalog/block/productinfo/Getproductcategory.php create mode 100644 app/appadmin/modules/Catalog/block/productinfo/Imageupload.php create mode 100644 app/appadmin/modules/Catalog/block/productinfo/Index.php create mode 100644 app/appadmin/modules/Catalog/block/productinfo/Manageredit.php create mode 100644 app/appadmin/modules/Catalog/block/productinfo/index/Attr.php create mode 100644 app/appadmin/modules/Catalog/block/productreview/Index.php create mode 100644 app/appadmin/modules/Catalog/block/productreview/Manageredit.php create mode 100644 app/appadmin/modules/Catalog/block/urlrewrite/Index.php create mode 100644 app/appadmin/modules/Catalog/controllers/CategoryController.php create mode 100644 app/appadmin/modules/Catalog/controllers/ProductfavoriteController.php create mode 100644 app/appadmin/modules/Catalog/controllers/ProductinfoController.php create mode 100644 app/appadmin/modules/Catalog/controllers/ProductreviewController.php create mode 100644 app/appadmin/modules/Catalog/controllers/UrlrewriteController.php create mode 100644 app/appadmin/modules/Catalog/helper/Product.php create mode 100644 app/appadmin/modules/Catalog/views/category/index.php create mode 100644 app/appadmin/modules/Catalog/views/category/info.php create mode 100644 app/appadmin/modules/Catalog/views/category/product.php create mode 100644 app/appadmin/modules/Catalog/views/productfavorite/index.php create mode 100644 app/appadmin/modules/Catalog/views/productinfo/custom_option_edit_copy.php create mode 100644 app/appadmin/modules/Catalog/views/productinfo/index.php create mode 100644 app/appadmin/modules/Catalog/views/productinfo/manageredit.php create mode 100644 app/appadmin/modules/Catalog/views/productreview/index.php create mode 100644 app/appadmin/modules/Catalog/views/productreview/manageredit.php create mode 100644 app/appadmin/modules/Catalog/views/urlrewrite/index.php create mode 100644 app/appadmin/modules/Cms/CmsController.php create mode 100644 app/appadmin/modules/Cms/Module.php create mode 100644 app/appadmin/modules/Cms/block/article/Index.php create mode 100644 app/appadmin/modules/Cms/block/article/Manageredit.php create mode 100644 app/appadmin/modules/Cms/block/staticblock/Index.php create mode 100644 app/appadmin/modules/Cms/block/staticblock/Manageredit.php create mode 100644 app/appadmin/modules/Cms/controllers/ArticleController.php create mode 100644 app/appadmin/modules/Cms/controllers/StaticblockController.php create mode 100644 app/appadmin/modules/Cms/views/article/index.php create mode 100644 app/appadmin/modules/Cms/views/article/manageredit.php create mode 100644 app/appadmin/modules/Cms/views/staticblock/index.php create mode 100644 app/appadmin/modules/Cms/views/staticblock/manageredit.php create mode 100644 app/appadmin/modules/Customer/CustomerController.php create mode 100644 app/appadmin/modules/Customer/Module.php create mode 100644 app/appadmin/modules/Customer/block/account/Index.php create mode 100644 app/appadmin/modules/Customer/block/account/Manageredit.php create mode 100644 app/appadmin/modules/Customer/controllers/AccountController.php create mode 100644 app/appadmin/modules/Customer/views/account/index.php create mode 100644 app/appadmin/modules/Customer/views/account/manageredit.php create mode 100644 app/appadmin/modules/Sales/Module.php create mode 100644 app/appadmin/modules/Sales/SalesController.php create mode 100644 app/appadmin/modules/Sales/block/coupon/Manager.php create mode 100644 app/appadmin/modules/Sales/block/coupon/Manageredit.php create mode 100644 app/appadmin/modules/Sales/block/orderinfo/Manager.php create mode 100644 app/appadmin/modules/Sales/block/orderinfo/Manageredit.php create mode 100644 app/appadmin/modules/Sales/controllers/CouponController.php create mode 100644 app/appadmin/modules/Sales/controllers/OrderinfoController.php create mode 100644 app/appadmin/modules/Sales/views/coupon/manager.php create mode 100644 app/appadmin/modules/Sales/views/coupon/manageredit.php create mode 100644 app/appadmin/modules/Sales/views/orderinfo/manager.php create mode 100644 app/appadmin/modules/Sales/views/orderinfo/manageredit.php create mode 100644 app/appapi/config/appapi.php create mode 100644 app/appapi/config/modules/V1.php create mode 100644 app/appapi/modules/AppapiController.php create mode 100644 app/appapi/modules/AppapiModule.php create mode 100644 app/appapi/modules/V1/Module.php create mode 100644 app/appapi/modules/V1/controllers/article/IndexController.php create mode 100644 app/appfront/config/appfront.php create mode 100644 app/appfront/config/modules/Catalog.php create mode 100644 app/appfront/config/modules/Catalogsearch.php create mode 100644 app/appfront/config/modules/Checkout.php create mode 100644 app/appfront/config/modules/Cms.php create mode 100644 app/appfront/config/modules/Customer.php create mode 100644 app/appfront/config/modules/Payment.php create mode 100644 app/appfront/config/modules/Site.php create mode 100644 app/appfront/config/params.php create mode 100644 app/appfront/helper/Country.php create mode 100644 app/appfront/helper/Format.php create mode 100644 app/appfront/helper/Shipping.php create mode 100644 app/appfront/helper/mailer/Email.php create mode 100644 app/appfront/languages/de_DE/appfront.php create mode 100644 app/appfront/languages/en_US/appfront.php create mode 100644 app/appfront/languages/es_ES/appfront.php create mode 100644 app/appfront/languages/fr_FR/appfront.php create mode 100644 app/appfront/languages/it_IT/appfront.php create mode 100644 app/appfront/languages/nl_NL/appfront.php create mode 100644 app/appfront/languages/pt_PT/appfront.php create mode 100644 app/appfront/languages/ru_RU/appfront.php create mode 100644 app/appfront/languages/zh_CN/appfront.php create mode 100644 app/appfront/modules/AppfrontController.php create mode 100644 app/appfront/modules/AppfrontModule.php create mode 100644 app/appfront/modules/Catalog/Module.php create mode 100644 app/appfront/modules/Catalog/block/category/Index.php create mode 100644 app/appfront/modules/Catalog/block/category/Price.php create mode 100644 app/appfront/modules/Catalog/block/category/index/Page.php create mode 100644 app/appfront/modules/Catalog/block/favoriteproduct/Add.php create mode 100644 app/appfront/modules/Catalog/block/product/CustomOption.php create mode 100644 app/appfront/modules/Catalog/block/product/Index.php create mode 100644 app/appfront/modules/Catalog/block/product/Review.php create mode 100644 app/appfront/modules/Catalog/block/reviewproduct/Add.php create mode 100644 app/appfront/modules/Catalog/block/reviewproduct/Lists.php create mode 100644 app/appfront/modules/Catalog/controllers/CategoryController.php create mode 100644 app/appfront/modules/Catalog/controllers/FavoriteproductController.php create mode 100644 app/appfront/modules/Catalog/controllers/ProductController.php create mode 100644 app/appfront/modules/Catalog/controllers/ReviewproductController.php create mode 100644 app/appfront/modules/Catalog/helpers/Price.php create mode 100644 app/appfront/modules/Catalog/helpers/Review.php create mode 100644 app/appfront/modules/Catalogsearch/Module.php create mode 100644 app/appfront/modules/Catalogsearch/block/index/Index.php create mode 100644 app/appfront/modules/Catalogsearch/controllers/IndexController.php create mode 100644 app/appfront/modules/Checkout/Module.php create mode 100644 app/appfront/modules/Checkout/block/cart/Index.php create mode 100644 app/appfront/modules/Checkout/block/onepage/Index.php create mode 100644 app/appfront/modules/Checkout/block/onepage/Placeorder.php create mode 100644 app/appfront/modules/Checkout/controllers/CartController.php create mode 100644 app/appfront/modules/Checkout/controllers/OnepageController.php create mode 100644 app/appfront/modules/Cms/Module.php create mode 100644 app/appfront/modules/Cms/block/article/Index.php create mode 100644 app/appfront/modules/Cms/block/home/Index.php create mode 100644 app/appfront/modules/Cms/block/widgets/Footer.php create mode 100644 app/appfront/modules/Cms/block/widgets/Head.php create mode 100644 app/appfront/modules/Cms/block/widgets/Headers.php create mode 100644 app/appfront/modules/Cms/block/widgets/Menu.php create mode 100644 app/appfront/modules/Cms/controllers/ArticleController.php create mode 100644 app/appfront/modules/Cms/controllers/HomeController.php create mode 100644 app/appfront/modules/Customer/Module.php create mode 100644 app/appfront/modules/Customer/block/LeftMenu.php create mode 100644 app/appfront/modules/Customer/block/account/Forgotpassword.php create mode 100644 app/appfront/modules/Customer/block/account/Index.php create mode 100644 app/appfront/modules/Customer/block/account/Login.php create mode 100644 app/appfront/modules/Customer/block/account/Register.php create mode 100644 app/appfront/modules/Customer/block/account/Resetpassword.php create mode 100644 app/appfront/modules/Customer/block/account/Resetpasswordsuccess.php create mode 100644 app/appfront/modules/Customer/block/address/Edit.php create mode 100644 app/appfront/modules/Customer/block/address/Index.php create mode 100644 app/appfront/modules/Customer/block/contacts/Index.php create mode 100644 app/appfront/modules/Customer/block/editaccount/Index.php create mode 100644 app/appfront/modules/Customer/block/mailer/account/forgotpassword/EmailBody.php create mode 100644 app/appfront/modules/Customer/block/mailer/account/login/EmailBody.php create mode 100644 app/appfront/modules/Customer/block/mailer/account/register/EmailBody.php create mode 100644 app/appfront/modules/Customer/block/mailer/contacts/EmailBody.php create mode 100644 app/appfront/modules/Customer/block/mailer/newsletter/EmailBody.php create mode 100644 app/appfront/modules/Customer/block/newsletter/Index.php create mode 100644 app/appfront/modules/Customer/block/order/Index.php create mode 100644 app/appfront/modules/Customer/block/order/Reorder.php create mode 100644 app/appfront/modules/Customer/block/order/View.php create mode 100644 app/appfront/modules/Customer/block/point/Index.php create mode 100644 app/appfront/modules/Customer/block/productfavorite/Index.php create mode 100644 app/appfront/modules/Customer/block/productreview/Index.php create mode 100644 app/appfront/modules/Customer/controllers/AccountController.php create mode 100644 app/appfront/modules/Customer/controllers/AddressController.php create mode 100644 app/appfront/modules/Customer/controllers/AjaxController.php create mode 100644 app/appfront/modules/Customer/controllers/ContactsController.php create mode 100644 app/appfront/modules/Customer/controllers/EditaccountController.php create mode 100644 app/appfront/modules/Customer/controllers/NewsletterController.php create mode 100644 app/appfront/modules/Customer/controllers/OrderController.php create mode 100644 app/appfront/modules/Customer/controllers/PointController.php create mode 100644 app/appfront/modules/Customer/controllers/ProductfavoriteController.php create mode 100644 app/appfront/modules/Customer/controllers/ProductreviewController.php create mode 100644 app/appfront/modules/Payment/Module.php create mode 100644 app/appfront/modules/Payment/PaymentController.php create mode 100644 app/appfront/modules/Payment/controllers/CheckmoneyController.php create mode 100644 app/appfront/modules/Payment/controllers/paypal/ExpressController.php create mode 100644 app/appfront/modules/Payment/controllers/paypal/StandardController.php create mode 100644 app/appfront/modules/Site/Module.php create mode 100644 app/appfront/modules/Site/controllers/HelperController.php create mode 100644 app/appfront/theme/BaseAsset.php create mode 100644 app/appfront/theme/base/front/assets/css/ie.css create mode 100644 app/appfront/theme/base/front/assets/css/ltie9.css create mode 100644 app/appfront/theme/base/front/assets/css/owl.carousel.css create mode 100644 app/appfront/theme/base/front/assets/css/style.css create mode 100644 app/appfront/theme/base/front/assets/elevatezoom/jquery.elevatezoom.js create mode 100644 app/appfront/theme/base/front/assets/fancybox/blank.gif create mode 100644 app/appfront/theme/base/front/assets/fancybox/fancybox_loading.gif create mode 100644 app/appfront/theme/base/front/assets/fancybox/fancybox_loading@2x.gif create mode 100644 app/appfront/theme/base/front/assets/fancybox/fancybox_overlay.png create mode 100644 app/appfront/theme/base/front/assets/fancybox/fancybox_sprite.png create mode 100644 app/appfront/theme/base/front/assets/fancybox/fancybox_sprite@2x.png create mode 100644 app/appfront/theme/base/front/assets/fancybox/helpers/fancybox_buttons.png create mode 100644 app/appfront/theme/base/front/assets/fancybox/helpers/jquery.fancybox-buttons.css create mode 100644 app/appfront/theme/base/front/assets/fancybox/helpers/jquery.fancybox-buttons.js create mode 100644 app/appfront/theme/base/front/assets/fancybox/helpers/jquery.fancybox-media.js create mode 100644 app/appfront/theme/base/front/assets/fancybox/helpers/jquery.fancybox-thumbs.css create mode 100644 app/appfront/theme/base/front/assets/fancybox/helpers/jquery.fancybox-thumbs.js create mode 100644 app/appfront/theme/base/front/assets/fancybox/jquery.fancybox.css create mode 100644 app/appfront/theme/base/front/assets/fancybox/jquery.fancybox.js create mode 100644 app/appfront/theme/base/front/assets/fancybox/jquery.fancybox.pack.js create mode 100644 app/appfront/theme/base/front/assets/images/1.jpg create mode 100644 app/appfront/theme/base/front/assets/images/16.jpg create mode 100644 app/appfront/theme/base/front/assets/images/17.jpg create mode 100644 app/appfront/theme/base/front/assets/images/18.jpg create mode 100644 app/appfront/theme/base/front/assets/images/2.jpg create mode 100644 app/appfront/theme/base/front/assets/images/3.jpg create mode 100644 app/appfront/theme/base/front/assets/images/AjaxLoader.gif create mode 100644 app/appfront/theme/base/front/assets/images/Star_0.png create mode 100644 app/appfront/theme/base/front/assets/images/Star_1.png create mode 100644 app/appfront/theme/base/front/assets/images/Star_2.png create mode 100644 app/appfront/theme/base/front/assets/images/Star_3.png create mode 100644 app/appfront/theme/base/front/assets/images/Star_4.png create mode 100644 app/appfront/theme/base/front/assets/images/Star_5.png create mode 100644 app/appfront/theme/base/front/assets/images/add_Close_hover.jpg create mode 100644 app/appfront/theme/base/front/assets/images/add_save_hover.jpg create mode 100644 app/appfront/theme/base/front/assets/images/bg_direction_nav2.png create mode 100644 app/appfront/theme/base/front/assets/images/btn_trash.gif create mode 100644 app/appfront/theme/base/front/assets/images/checkbox.png create mode 100644 app/appfront/theme/base/front/assets/images/cur.png create mode 100644 app/appfront/theme/base/front/assets/images/en_.jpg create mode 100644 app/appfront/theme/base/front/assets/images/en_a.jpg create mode 100644 app/appfront/theme/base/front/assets/images/favourite.png create mode 100644 app/appfront/theme/base/front/assets/images/fb.png create mode 100644 app/appfront/theme/base/front/assets/images/footer_fixed.png create mode 100644 app/appfront/theme/base/front/assets/images/free_en.jpg create mode 100644 app/appfront/theme/base/front/assets/images/googole.png create mode 100644 app/appfront/theme/base/front/assets/images/i_msg-error.gif create mode 100644 app/appfront/theme/base/front/assets/images/i_msg-success.gif create mode 100644 app/appfront/theme/base/front/assets/images/jj.png create mode 100644 app/appfront/theme/base/front/assets/images/new.jpg create mode 100644 app/appfront/theme/base/front/assets/images/paypaltopay.jpg create mode 100644 app/appfront/theme/base/front/assets/images/pinter.png create mode 100644 app/appfront/theme/base/front/assets/images/pp.png create mode 100644 app/appfront/theme/base/front/assets/images/product_rating_big_blank_star.png create mode 100644 app/appfront/theme/base/front/assets/images/product_rating_big_full_star.png create mode 100644 app/appfront/theme/base/front/assets/images/sammy.jpg create mode 100644 app/appfront/theme/base/front/assets/images/scart_step1.gif create mode 100644 app/appfront/theme/base/front/assets/images/sign.png create mode 100644 app/appfront/theme/base/front/assets/images/signloading.gif create mode 100644 app/appfront/theme/base/front/assets/images/tag.png create mode 100644 app/appfront/theme/base/front/assets/images/toptip.png create mode 100644 app/appfront/theme/base/front/assets/images/twitter.png create mode 100644 app/appfront/theme/base/front/assets/images/validation_advice_bg.gif create mode 100644 app/appfront/theme/base/front/assets/images/vip.jpg create mode 100644 app/appfront/theme/base/front/assets/js/jquery-3.0.0.min.js create mode 100644 app/appfront/theme/base/front/assets/js/jquery.lazyload.js create mode 100644 app/appfront/theme/base/front/assets/js/jquery.lazyload.min.js create mode 100644 app/appfront/theme/base/front/assets/js/js.js create mode 100644 app/appfront/theme/base/front/assets/js/owl.carousel.js create mode 100644 app/appfront/theme/base/front/assets/js/owl.carousel.min.js create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/ajax-loader.gif create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/button-background.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/hm.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/line-gradient.gif create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-error.jpg create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-numbers-1.gif create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-numbers-1.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-numbers-2.gif create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-numbers-2.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-numbers-3.gif create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-numbers-3.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-numbers-4.gif create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-numbers-4.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-place-order.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-popup-footer.gif create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-popup-footer.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-popup-header.gif create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/onestepcheckout-popup-header.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/entries create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/prop-base/onestepcheckout-numbers-1.png.svn-base create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/prop-base/onestepcheckout-numbers-2.png.svn-base create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/prop-base/onestepcheckout-numbers-3.png.svn-base create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/prop-base/onestepcheckout-numbers-4.png.svn-base create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/text-base/onestepcheckout-numbers-1.png.svn-base create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/text-base/onestepcheckout-numbers-2.png.svn-base create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/text-base/onestepcheckout-numbers-3.png.svn-base create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/.svn/text-base/onestepcheckout-numbers-4.png.svn-base create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/onestepcheckout-numbers-1.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/onestepcheckout-numbers-2.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/onestepcheckout-numbers-3.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/images/skin-magento/onestepcheckout-numbers-4.png create mode 100644 app/appfront/theme/base/front/assets/onestepcheckout/onestepcheckout.css create mode 100644 app/appfront/theme/base/front/catalog/category/index.php create mode 100644 app/appfront/theme/base/front/catalog/category/index/filter/attr.php create mode 100644 app/appfront/theme/base/front/catalog/category/index/filter/price.php create mode 100644 app/appfront/theme/base/front/catalog/category/index/filter/refineby.php create mode 100644 app/appfront/theme/base/front/catalog/category/index/filter/subcategory.php create mode 100644 app/appfront/theme/base/front/catalog/category/index/toolbar.php create mode 100644 app/appfront/theme/base/front/catalog/category/price.php create mode 100644 app/appfront/theme/base/front/catalog/product/index.php create mode 100644 app/appfront/theme/base/front/catalog/product/index/buy_also_buy.php create mode 100644 app/appfront/theme/base/front/catalog/product/index/custom_option.php create mode 100644 app/appfront/theme/base/front/catalog/product/index/image.php create mode 100644 app/appfront/theme/base/front/catalog/product/index/options.php create mode 100644 app/appfront/theme/base/front/catalog/product/index/payment.php create mode 100644 app/appfront/theme/base/front/catalog/product/index/price.php create mode 100644 app/appfront/theme/base/front/catalog/product/index/review.php create mode 100644 app/appfront/theme/base/front/catalog/product/index/tier_price.php create mode 100644 app/appfront/theme/base/front/catalog/reviewproduct/add.php create mode 100644 app/appfront/theme/base/front/catalog/reviewproduct/lists.php create mode 100644 app/appfront/theme/base/front/catalogsearch/index/index.php create mode 100644 app/appfront/theme/base/front/catalogsearch/index/index/toolbar.php create mode 100644 app/appfront/theme/base/front/checkout/cart/index.php create mode 100644 app/appfront/theme/base/front/checkout/onepage/index.php create mode 100644 app/appfront/theme/base/front/checkout/onepage/index/address.php create mode 100644 app/appfront/theme/base/front/checkout/onepage/index/address_select.php create mode 100644 app/appfront/theme/base/front/checkout/onepage/index/payment.php create mode 100644 app/appfront/theme/base/front/checkout/onepage/index/review_order.php create mode 100644 app/appfront/theme/base/front/checkout/onepage/index/shipping.php create mode 100644 app/appfront/theme/base/front/cms/article/index.php create mode 100644 app/appfront/theme/base/front/cms/home/index.php create mode 100644 app/appfront/theme/base/front/cms/home/index/price.php create mode 100644 app/appfront/theme/base/front/cms/home/index/product.php create mode 100644 app/appfront/theme/base/front/customer/account/forgotpassword.php create mode 100644 app/appfront/theme/base/front/customer/account/forgotpasswordsubmit.php create mode 100644 app/appfront/theme/base/front/customer/account/index.php create mode 100644 app/appfront/theme/base/front/customer/account/login.php create mode 100644 app/appfront/theme/base/front/customer/account/register.php create mode 100644 app/appfront/theme/base/front/customer/account/resetpassword.php create mode 100644 app/appfront/theme/base/front/customer/account/resetpasswordsuccess.php create mode 100644 app/appfront/theme/base/front/customer/address/edit.php create mode 100644 app/appfront/theme/base/front/customer/address/index.php create mode 100644 app/appfront/theme/base/front/customer/contacts/index.php create mode 100644 app/appfront/theme/base/front/customer/editaccount/index.php create mode 100644 app/appfront/theme/base/front/customer/leftmenu.php create mode 100644 app/appfront/theme/base/front/customer/newsletter/error.php create mode 100644 app/appfront/theme/base/front/customer/newsletter/index.php create mode 100644 app/appfront/theme/base/front/customer/order/index.php create mode 100644 app/appfront/theme/base/front/customer/order/reorder.php create mode 100644 app/appfront/theme/base/front/customer/order/view.php create mode 100644 app/appfront/theme/base/front/customer/productfavorite/index.php create mode 100644 app/appfront/theme/base/front/customer/productreview/index.php create mode 100644 app/appfront/theme/base/front/layouts/category_view.php create mode 100644 app/appfront/theme/base/front/layouts/home.php create mode 100644 app/appfront/theme/base/front/layouts/main.php create mode 100644 app/appfront/theme/base/front/layouts/one_step_checkout.php create mode 100644 app/appfront/theme/base/front/layouts/product_view.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/forgotpassword/body_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/forgotpassword/subject_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/body_de.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/body_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/body_es.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/body_fr.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/body_zh.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/subject_de.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/subject_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/subject_es.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/subject_fr.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/login/subject_zh.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/body_de.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/body_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/body_es.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/body_fr.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/body_zh.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/subject_de.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/subject_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/subject_es.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/subject_fr.php create mode 100644 app/appfront/theme/base/front/mailer/customer/account/register/subject_zh.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/body_de.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/body_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/body_es.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/body_fr.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/body_zh.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/subject_de.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/subject_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/subject_es.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/subject_fr.php create mode 100644 app/appfront/theme/base/front/mailer/customer/contacts/subject_zh.php create mode 100644 app/appfront/theme/base/front/mailer/customer/newsletter/body_en.php create mode 100644 app/appfront/theme/base/front/mailer/customer/newsletter/subject_en.php create mode 100644 app/appfront/theme/base/front/payment/checkmoney/success.php create mode 100644 app/appfront/theme/base/front/payment/success.php create mode 100644 app/appfront/theme/base/front/site/helper/error.php create mode 100644 app/appfront/theme/base/front/widgets/breadcrumbs.php create mode 100644 app/appfront/theme/base/front/widgets/flashmessage.php create mode 100644 app/appfront/theme/base/front/widgets/footer.php create mode 100644 app/appfront/theme/base/front/widgets/head.php create mode 100644 app/appfront/theme/base/front/widgets/header.php create mode 100644 app/appfront/theme/base/front/widgets/menu.php create mode 100644 app/appfront/theme/base/front/widgets/page.php create mode 100644 app/appfront/theme/base/front/widgets/scroll.php create mode 100644 app/appfront/theme/base/front/widgets/topsearch.php create mode 100644 app/appfront/widgets/Footer.php create mode 100644 app/appfront/widgets/Head.php create mode 100644 app/appfront/widgets/Headers.php create mode 100644 app/appfront/widgets/Menu.php create mode 100644 app/appfront/widgets/Page.php create mode 100644 app/apphtml5/config/apphtml5.php create mode 100644 app/apphtml5/config/modules/.gitignore create mode 100644 app/apphtml5/languages/.gitignore create mode 100644 app/apphtml5/modules/.gitignore create mode 100644 app/apphtml5/theme/.gitignore create mode 100644 app/appserver/config/appserver.php create mode 100644 app/appserver/config/modules/.gitignore create mode 100644 app/appserver/languages/.gitignore create mode 100644 app/appserver/modules/.gitignore create mode 100644 app/console/config/console.php create mode 100644 app/console/config/modules/Helper.php create mode 100644 app/console/config/modules/Product.php create mode 100644 app/console/modules/ConsoleController.php create mode 100644 app/console/modules/ConsoleModule.php create mode 100644 app/console/modules/Helper/Module.php create mode 100644 app/console/modules/Helper/controllers/UrlrewriteController.php create mode 100644 app/console/modules/Product/Module.php create mode 100644 app/console/modules/Product/controllers/IndexController.php create mode 100644 app/console/modules/Product/controllers/PriceController.php create mode 100644 app/console/modules/Product/controllers/SearchController.php create mode 100644 app/console/modules/Product/controllers/search/MongodbController.php create mode 100644 app/console/modules/Product/controllers/search/XunController.php create mode 100644 components/Store.php create mode 100644 composer.json create mode 100644 config/components/Store.php create mode 100644 config/components/XunSearch.php create mode 100644 config/fecshop.php create mode 100644 config/services/AdminUser.php create mode 100644 config/services/Cart.php create mode 100644 config/services/Category.php create mode 100644 config/services/Cms.php create mode 100644 config/services/Customer.php create mode 100644 config/services/Email.php create mode 100644 config/services/FecshopLang.php create mode 100644 config/services/Helper.php create mode 100644 config/services/Image.php create mode 100644 config/services/Order.php create mode 100644 config/services/Page.php create mode 100644 config/services/Payment.php create mode 100644 config/services/Product.php create mode 100644 config/services/Search.php create mode 100644 config/services/Shipping.php create mode 100644 config/services/Store.php create mode 100644 config/services/Systemhelper.php create mode 100644 config/services/Url.php create mode 100644 config/xunsearch/product.ini create mode 100644 config/xunsearch/search.ini create mode 100755 initFecShop create mode 100644 interfaces/block/BlockCache.php create mode 100644 migrations/READ.me create mode 100644 migrations/db/product/log/m160607_052627_log_product_view.php create mode 100644 migrations/mongodb/product/log/m160608_062040_mongodb_log_product_view.php create mode 100644 migrations/mongodb/urlwrite/m160608_061933_mongodb_url_write.php create mode 100644 models/mongodb/Category.php create mode 100644 models/mongodb/CategoryProduct.php create mode 100644 models/mongodb/FecshopServiceLog.php create mode 100644 models/mongodb/Newsletter.php create mode 100644 models/mongodb/Product.php create mode 100644 models/mongodb/Search.php create mode 100644 models/mongodb/UrlRewrite.php create mode 100644 models/mongodb/cms/Article.php create mode 100644 models/mongodb/cms/StaticBlock.php create mode 100644 models/mongodb/customer/Newsletter.php create mode 100644 models/mongodb/product/Favorite.php create mode 100644 models/mongodb/product/Review.php create mode 100644 models/mongodb/product/ViewLog.php create mode 100644 models/mongodb/url/UrlRewrite.php create mode 100644 models/mysqldb/Cart.php create mode 100644 models/mysqldb/Customer.php create mode 100644 models/mysqldb/Order.php create mode 100644 models/mysqldb/UrlRewrite.php create mode 100644 models/mysqldb/cart/Coupon.php create mode 100644 models/mysqldb/cart/CouponUsage.php create mode 100644 models/mysqldb/cart/Item.php create mode 100644 models/mysqldb/cms/Article.php create mode 100644 models/mysqldb/cms/StaticBlock.php create mode 100644 models/mysqldb/customer/Address.php create mode 100644 models/mysqldb/customer/CustomerLogin.php create mode 100644 models/mysqldb/customer/CustomerRegister.php create mode 100644 models/mysqldb/customer/CustomerResetPassword.php create mode 100644 models/mysqldb/order/Item.php create mode 100644 models/mysqldb/product/ViewLog.php create mode 100644 models/mysqldb/url/UrlRewrite.php create mode 100644 models/xunsearch/Search.php create mode 100644 services/AdminUser.php create mode 100644 services/Affiliate.php create mode 100644 services/Application.php create mode 100644 services/Blog.php create mode 100644 services/Cart.php create mode 100644 services/Category.php create mode 100644 services/Cms.php create mode 100644 services/Coupon.php create mode 100644 services/Customer.php create mode 100644 services/Email.php create mode 100644 services/FecshopLang.php create mode 100644 services/Helper.php create mode 100644 services/Image.php create mode 100644 services/Order.php create mode 100644 services/Page.php create mode 100644 services/Payment.php create mode 100644 services/Point.php create mode 100644 services/Product.php create mode 100644 services/Request.php create mode 100644 services/Search.php create mode 100644 services/Service.php create mode 100644 services/Shipping.php create mode 100644 services/Sitemap.php create mode 100644 services/Store.php create mode 100644 services/Systemhelper.php create mode 100644 services/Url.php create mode 100644 services/Wholesale.php create mode 100644 services/cart/Coupon.php create mode 100644 services/cart/Info.php create mode 100644 services/cart/Quote.php create mode 100644 services/cart/QuoteItem.php create mode 100644 services/category/CategoryInterface.php create mode 100644 services/category/CategoryMongodb.php create mode 100644 services/category/CategoryMysqldb.php create mode 100644 services/category/Image.php create mode 100644 services/category/Menu.php create mode 100644 services/category/Product.php create mode 100644 services/cms/Article.php create mode 100644 services/cms/StaticBlock.php create mode 100644 services/cms/article/ArticleInterface.php create mode 100644 services/cms/article/ArticleMongodb.php create mode 100644 services/cms/article/ArticleMysqldb.php create mode 100644 services/cms/staticblock/StaticBlockInterface.php create mode 100644 services/cms/staticblock/StaticBlockMongodb.php create mode 100644 services/cms/staticblock/StaticBlockMysqldb.php create mode 100644 services/customer/Address.php create mode 100644 services/customer/Affiliate.php create mode 100644 services/customer/Coupon.php create mode 100644 services/customer/DropShip.php create mode 100644 services/customer/Favorite.php create mode 100644 services/customer/Message.php create mode 100644 services/customer/Newsletter.php create mode 100644 services/customer/Order.php create mode 100644 services/customer/Point.php create mode 100644 services/customer/Review.php create mode 100644 services/customer/Wholesale.php create mode 100644 services/helper/AR.php create mode 100644 services/helper/Captcha.php create mode 100644 services/helper/Country.php create mode 100644 services/helper/Errors.php create mode 100644 services/helper/Log.php create mode 100644 services/helper/MobileDetect.php create mode 100644 services/helper/captcha/Elephant.ttf create mode 100644 services/order/Item.php create mode 100644 services/page/Asset.php create mode 100644 services/page/Breadcrumbs.php create mode 100644 services/page/Currency.php create mode 100644 services/page/Footer.php create mode 100644 services/page/Menu.php create mode 100644 services/page/Message.php create mode 100644 services/page/Newsletter.php create mode 100644 services/page/StaticBlock.php create mode 100644 services/page/Theme.php create mode 100644 services/page/Translate.php create mode 100644 services/page/Widget.php create mode 100644 services/product/BestSell.php create mode 100644 services/product/BuyAlsoBuy.php create mode 100644 services/product/Image.php create mode 100644 services/product/Info.php create mode 100644 services/product/Price.php create mode 100644 services/product/ProductInterface.php create mode 100644 services/product/ProductMongodb.php create mode 100644 services/product/ProductMysqldb.php create mode 100644 services/product/Review.php create mode 100644 services/product/ViewLog.php create mode 100644 services/product/viewLog/Db.php create mode 100644 services/product/viewLog/Mongodb.php create mode 100644 services/product/viewLog/Session.php create mode 100644 services/search/MongoSearch.php create mode 100644 services/search/SearchInterface.php create mode 100644 services/search/XunSearch.php create mode 100644 services/url/Category.php create mode 100644 services/url/Rewrite.php create mode 100644 services/url/rewrite/RewriteInterface.php create mode 100644 services/url/rewrite/RewriteMongodb.php create mode 100644 services/url/rewrite/RewriteMysqldb.php create mode 100644 shell/README.md create mode 100644 shell/computeProductFinalPrice.sh create mode 100644 shell/fullSearchSync.sh create mode 100644 shell/initDb.sh create mode 100644 shell/search/deleteXunSearchAllData.sh create mode 100644 shell/search/fullSearchSync.sh create mode 100644 shell/urlRewrite.sh create mode 100644 yii/Yii.php create mode 100644 yii/i18n/PhpMessageSource.php create mode 100644 yii/web/Request.php create mode 100644 yii/web/User.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..951ee51c9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,54 @@ +The Fecshop is free software. It is released under the terms of the following OSL v.3.0 License. + +Copyright (c) 2016, Terry +All rights reserved. + + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. + diff --git a/README.md b/README.md new file mode 100644 index 000000000..a6d071c0f --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +

+ + + +

+
+ +演示地址:http://fecshop.appfront.fancyecommerce.com/ + +Fecshop 全称为Fancy ECommerce Shop,是基于php Yii2框架之上开发的一款优秀的开源电商系统,遵循[OSL3.0协议](http://www.oschina.net/question/28_8527), +截止到2016-12-19号,除了支付部分,其他的基本都已经完成,关注fecshop的 +在等2-3个月,也就是明年2,3月份,版本就可以出来,2017年4,5月份在把手机web +做一下,预计到明年5月份,后台,pc前台,手机web前台 ,命令控制台 这几个入口 +基本可以完善,多谢大家关注和你们的Star,我会坚持把他写好。 +目前由于项目还没有完成,因此,下载安装只能安装部分代码, +而且数据库部分没有提供migrate,也就是说,目前fecshop还不可用, +等代码开发完成,我在整体核对一遍代码, +然后把文档落实,该项目是开源项目,从事外贸电商6年来, +用了不少开源电商系统,譬如magento,发现开源框架都有一定 +的诟病,在并发方面差,后期扩展,业务发展后期重构难, +尤其是现在的移动端的发展,多入口的电商模式占据主流, +性能方面的要求越来越高,Fecshop采用了nosql和mysql结合的方式, +关系型表放到mysql中,譬如优惠券,购物车,订单等, +非关系型数据表(非关系型代表不会出现多表强事务类型操作) +放到mongodb中,缓存用redis,搜索目前用的是mongodb的FullTextSearch功能, +支持一些主流语言的分词与搜索,不过目前不支持中文 切词 搜索, +后期扩展一下用ElasticSearch来进行搜索(ElasticSearch有中文插件,安装后支持中文分词)。 + + +作者QQ:2358269014 + +github: https://github.com/fancyecommerce/yii2_fecshop + +[![Latest Stable Version](https://poser.pugx.org/fancyecommerce/fecshop/v/stable)](https://packagist.org/packages/fancyecommerce/fecshop) [![Total Downloads](https://poser.pugx.org/fancyecommerce/fecshop/downloads)](https://packagist.org/packages/fancyecommerce/fecshop) [![Latest Unstable Version](https://poser.pugx.org/fancyecommerce/fecshop/v/unstable)](https://packagist.org/packages/fancyecommerce/fecshop) + +**Fecshop文档(撰写中)**:[Fecshop Doc Guide](http://www.fecshop.com/doc/fecshop-guide/cn-1.0/guide-index.html#) + + + +项目状态: + +> 项目已经开始,本项目由Terry筹划,预计到2017-05-01出来第一个正式版本。 + + + + + +1、安装Fecsop +------------ + +本部分为fecshop的核心代码部分, +是以yii2扩展的方式制作,因此,您安装入口库包`fecshop app advanced` +,通过`composer update`, 本部分代码会以依赖包的方式被加载安装。 + +点击这里,进入[安装 fecshop app advanced](https://github.com/fancyecommerce/yii2_fecshop_app_advanced) +页面。 + +2、架构特色 +----------- + +架构特色:参看详细介绍:[Fecshop 架构特色](http://www.fecshop.com/doc/fecshop-guide/cn-1.0/guide-fecshop-about-fecshop.html) + +中文博客:[yii2 教程](http://www.fancyecommerce.com). + +Fecshop 全称为Fancy ECommerce Shop,是一款优秀的开源电商系统,遵循[OSL3.0协议](http://www.oschina.net/question/28_8527), +目的是为了方便yii2用户快速的 +开发商城,Fecshop作为一款可以持续性发展的商城系统, +在框架层面有以下特性: + +1. 由于商城系统的复杂性,原始的框架MVC结构,显的有点力不从心,Fecshop框架 +加入了[Block层](fecshop-feature-block.md), +Controller层只负责调度, Model只负责数据库映射,中间的处理逻辑由block来完成,View层 +负责显示,这样各司其职, 以免造成controller文件过于庞大。 + +2. 加入[独立功能块](fecshop-feature-independent-block.md),有点类似Yii2的Widget,目的是为了让一些侧栏公用块 +可以通过配置的方式 +添加,同时,还可以具有设置缓存的功能,譬如侧栏的产品浏览记录, +newsletter等独立显示块可能在很多 +页面用到,通过独立功能块可以配置方便的载入。 + +3. 在Model层的上层加入[服务层Services](fecshop-services-abc.md),这样,Controller,Block,View 层,在原则上 +不能直接调用model,必须通过Services层以及子Services层,然后Services访问各个 +model,组织数据,事务处理等操作,将数据结果返回给上层,这种设计可以方便以后业务 +发展后,进而根据业务特点进行重构,或者以后如果出现新技术,新方式, +都重构成自己想要的样子,譬如, +将某个底层由mysql换成mongodb,或者为了应付高并发读写并且多事务性的功能部分, +进行分库分表的设计方式。 + +4. Fecshop[ 多模板系统](fecshop-feature-mutil-themes.md),Fecshop设置了多个模板路径,各个模板路径下的文件被加载 +的优先级不同,其中,Fecshop的模板路径下的文件最全面,但是优先级最低, +,第三方模板路径优先级其次,用户本地模板路径优先级最高, +用户可以通过 +复制相应路径下的view或者js,css文件到本地模板路径,存在于高优先级 +模板路径的文件会被优先加载,这样用户可以通过多模板系统的原理进行模板的 +制作,同时,不影响Fecshop模板的升级,如果Fecshop view文件升级后被修改, +那么用户可以比对本地模板文件与升级模板文件的代码的不同, +复制更改的代码到本地模板路径 +即可。第三方的模板路径的优先级介于本地模板路径和Fecshop +模板路径之间。 + +5. [重写机制](fecshop-feature-rewrite.md),Fecshop的功能基本都可以被用户重写,包括servies层,Modules, +Controller,Block,Views,View Layout, +以及Js Css Img等,都可以被用户重写,其中 Js,Css,Img,Views,View Layout + 是通过多模板 +路径优先级来实现的,其他的是通过配置文件的覆盖更改来实现重写,这样,用户 +就可以很方便重构Fecshop或者第三方的功能和模板。 + +6. 升级最小化干扰,Fecshop的核心文件是放到vendor/fancyecommerce/fecshop +路径下面,和第三方扩展,用户二次开发路径完全隔离开, +Fecshop可以通过composer进行核心功能的升级,用户只需要通过composer升级 +即可。 + +7. 快速高效,[Fecshop Servises](fecshop-services-abc.md)遵循Yii2的懒加载方式,只初始化使用到的组件服务, +缓存方面有整页缓存,block部分缓存,动态数据ajax加载等方式,让您的网站快速响应。 + +8. [Fecshop 多入口模式](fecshop-feature-mutil-entrances.md),分为 appadmin(后台), appfront(PC前端),apphtml5(手机web), +appserver(手机app服务),appapi(erp,或者其他接口对接), +不同的业务,不同的设备,进入不同的入口,各个入口共用服务层services, +但是modules部分独立,这样相互干扰最小,可以相互独立开发。 + +9. 后台封装化,fec_admin扩展可以快速的实现增删改查类型的表单列表, +方便用户快速的做增删改查。 + +鉴于以上特点,您可以下载安装fecshop,然后更改fecshop的模板和功能,扩展自己想要 +的功能,或者安装第三方开发好了的扩展或者模板,来快速的组建起来您的网站。 + + diff --git a/app/appadmin/config/appadmin.php b/app/appadmin/config/appadmin.php new file mode 100644 index 000000000..26952026f --- /dev/null +++ b/app/appadmin/config/appadmin.php @@ -0,0 +1,38 @@ +$modules, + /* only config in front web */ + //'bootstrap' => ['store'], + 'components' => [ + 'user' => [ + 'identityClass' => 'fecadmin\models\AdminUser', + 'enableAutoLogin' => true, + ], + + 'errorHandler' => [ + 'errorAction' => 'fecadmin/error', + ], + + 'urlManager' => [ + 'rules' => [ + '' => 'fecadmin/index/index', + ], + ], + ], + 'params' => [ + 'appName' => 'appadmin', + ], +]; diff --git a/app/appadmin/config/modules/Catalog.php b/app/appadmin/config/modules/Catalog.php new file mode 100644 index 000000000..318676591 --- /dev/null +++ b/app/appadmin/config/modules/Catalog.php @@ -0,0 +1,14 @@ + [ + 'class' => '\fecshop\app\appadmin\modules\Catalog\Module', + + ], +]; \ No newline at end of file diff --git a/app/appadmin/config/modules/Cms.php b/app/appadmin/config/modules/Cms.php new file mode 100644 index 000000000..3c9fda3d7 --- /dev/null +++ b/app/appadmin/config/modules/Cms.php @@ -0,0 +1,16 @@ + [ + 'class' => '\fecshop\app\appadmin\modules\Cms\Module', + 'params'=> [ + + ], + ], +]; diff --git a/app/appadmin/config/modules/Customer.php b/app/appadmin/config/modules/Customer.php new file mode 100644 index 000000000..8748df185 --- /dev/null +++ b/app/appadmin/config/modules/Customer.php @@ -0,0 +1,16 @@ + [ + 'class' => '\fecshop\app\appadmin\modules\Customer\Module', + 'params'=> [ + + ], + ], +]; diff --git a/app/appadmin/config/modules/Fecadmin.php b/app/appadmin/config/modules/Fecadmin.php new file mode 100644 index 000000000..77480ff37 --- /dev/null +++ b/app/appadmin/config/modules/Fecadmin.php @@ -0,0 +1,18 @@ + [ + 'class' => '\fecadmin\Module', + //'controllerMap' => [ + // 'login' => [ + // 'class' => 'appadmin\local\fecadmin\controllers\LoginController', + // ], + //], + ], +]; diff --git a/app/appadmin/config/modules/Sales.php b/app/appadmin/config/modules/Sales.php new file mode 100644 index 000000000..bcc01a697 --- /dev/null +++ b/app/appadmin/config/modules/Sales.php @@ -0,0 +1,16 @@ + [ + 'class' => '\fecshop\app\appadmin\modules\Sales\Module', + 'params'=> [ + + ], + ], +]; diff --git a/app/appadmin/interfaces/base/AppadminbaseBlockEditInterface.php b/app/appadmin/interfaces/base/AppadminbaseBlockEditInterface.php new file mode 100644 index 000000000..c86de5a56 --- /dev/null +++ b/app/appadmin/interfaces/base/AppadminbaseBlockEditInterface.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +interface AppadminbaseBlockEditInterface{ + + /** + * set Service ,like $this->_service = Yii::$service->cms->article; + */ + public function setService(); + /** + * config edit array + */ + public function getEditArr(); +} \ No newline at end of file diff --git a/app/appadmin/interfaces/base/AppadminbaseBlockInterface.php b/app/appadmin/interfaces/base/AppadminbaseBlockInterface.php new file mode 100644 index 000000000..bd37dbbdd --- /dev/null +++ b/app/appadmin/interfaces/base/AppadminbaseBlockInterface.php @@ -0,0 +1,20 @@ + + * @since 1.0 + */ +interface AppadminbaseBlockInterface{ + + + public function getSearchArr(); + public function getTableFieldArr(); +} \ No newline at end of file diff --git a/app/appadmin/modules/AppadminbaseBlock.php b/app/appadmin/modules/AppadminbaseBlock.php new file mode 100644 index 000000000..ba0914f33 --- /dev/null +++ b/app/appadmin/modules/AppadminbaseBlock.php @@ -0,0 +1,619 @@ + + * @since 1.0 + */ +class AppadminbaseBlock extends Object{ + /** + * parameter storage front passed. + */ + public $_param = []; + /** + * fecshop service + */ + public $_service; + /** + * default pages number + */ + public $_pageNum = 1; + /** + * collection default number displayed. + */ + public $_numPerPage = 50; + /** + * collection primary key. + */ + public $_primaryKey ; + + /** + * collection sort direction , the default value is 'desc'. + */ + public $_sortDirection = 'desc'; + /** + * collection sort field , the default value is primary key. + */ + public $_orderField ; + + public $_asArray = true; + /** + * current url with param,like http://xxx.com?p=3&sort=desc + */ + public $_currentParamUrl; + /** + * current url with no param,like http://xxx.com + */ + public $_currentUrlKey; + /** + * data edit url, if you not set value ,it will be equal to current url. + */ + public $_editUrl; + /** + * data delete url, if you not set value ,it will be equal to current url. + */ + public $_deleteUrl; + public $_currentUrl; + + + /** + * it will be execute during initialization ,the following object variables will be initialize. + * $_primaryKey , $_param , $_currentUrl , + * $_currentParamUrl , $_addUrl , $_editUrl, + * $_deleteUrl. + */ + public function init(){ + if(!($this instanceof AppadminbaseBlockInterface)){ + echo 'Managere must implements fecshop\app\appadmin\interfaces\base\AppadminbaseBlockInterface'; + exit; + } + $param = \fec\helpers\CRequest::param(); + $this->_primaryKey = $this->_service->getPrimaryKey(); + if(empty($param['pageNum'])) $param['pageNum'] = $this->_pageNum ; + if(empty($param['numPerPage'])) $param['numPerPage'] = $this->_numPerPage ; + if(empty($param['orderField'])) $param['orderField'] = $this->_primaryKey ; + if(empty($param['orderDirection'])) $param['orderDirection'] = $this->_sortDirection ; + if(is_array($param) && !empty($param)){ + $this->_param = array_merge($this->_param, $param) ; + } + $currentUrl = CUrl::getCurrentUrlNoParam(); + $this->_currentParamUrl = CUrl::getCurrentUrl(); + $this->_editUrl = $this->_editUrl ? $this->_editUrl : $currentUrl; + $this->_deleteUrl = $this->_deleteUrl ? $this->_deleteUrl : $currentUrl; + } + + /** + * generate pager form html, it is important to j-ui js framework, which will storage current request param as hidden way. + * @return $str|String , html format string. + */ + public function getPagerForm(){ + $str = ""; + if(is_array($this->_param) && !empty($this->_param)){ + foreach($this->_param as $k=>$v){ + if($k != "_csrf"){ + $str .=''; + } + } + } + return $str; + } + + /** + * @property $data|Array, it was return by defined function getSearchArr(); + * generate search section html, + */ + public function getSearchBarHtml($data){ + if(is_array($data) && !empty($data)){ + $r_data = []; + $i = 0; + foreach($data as $k=>$d){ + $type11 = $d['type']; + if($type11 == 'select'){ + $value = $d['value']; + $name = $d['name']; + $title = $d['title']; + $d['value'] = $this->getSearchBarSelectHtml($name,$value,$title); + }else if($type11 == 'chosen_select'){ + $i++; + $value = $d['value']; + $name = $d['name']; + $title = $d['title']; + $d['value'] = $this->getSearchBarChosenSelectHtml($name,$value,$title,$i); + } + $r_data[$k] = $d; + } + } + $searchBar = $this->getDbSearchBarHtml($r_data); + return $searchBar; + } + + /** + * @property $name|String , html code select name. + * @property $data|Array, select options key and value. + * @property $title|String , select title , as select default display. + * generate html select code . + * @return String, select html code. + */ + public function getSearchBarSelectHtml($name,$data,$title){ + if(is_array($data) && !empty($data)){ + $html_chosen_select = ''; + return $html_chosen_select; + }else{ + return ''; + } + } + /** + * @property $name|String , html code select name. + * @property $data|Array, select options key and value. + * @property $title|String , select title , as select default display. + * @property $id|Int , use for chosen select config, if you use this function muilt times , $id must be unique in each time + * for example ,first time use this function set $id = 1, next time ,you can set $id=2,because is must be unique in front html. + * generate html select code . + * @return String, chosen select html code. + */ + public function getSearchBarChosenSelectHtml($name,$data,$title,$id=1){ + if(is_array($data) && !empty($data)){ + + $html_chosen_select .= ' + '; + $html_chosen_select .= ''; + return $html_chosen_select; + }else{ + return ''; + } + } + /** + * custom html code at the end of Search Bar. + * your can rewrite this function in your block class. + */ + public function customSearchBarHtml(){ + return ''; + } + + /** + * @property $data|Array + */ + public function getDbSearchBarHtml($data){ + $searchBar = ''; + if(!empty($data)){ + $searchBar .= ''; + $searchBar .=' + '; + foreach($data as $d){ + $type = $d['type']; + $name = $d['name']; + $title = $d['title']; + $value = $d['value']; + if($d['type'] == 'select'){ + $searchBar .= ''; + }else if($d['type'] == 'chosen_select'){ + $searchBar .= ''; + }else if($d['type'] == 'inputtext'){ + $searchBar .= ''; + }else if($d['type'] == 'inputdate'){ + $searchBar .= ''; + }else if($d['type'] == 'inputdatefilter'){ + $value = $d['value']; + if(is_array($value)){ + foreach($value as $t=>$title){ + $searchBar .= ''; + } + } + }else if($d['type'] == 'inputfilter'){ + $value = $d['value']; + if(is_array($value)){ + foreach($value as $t=>$title){ + $searchBar .= ''; + } + } + } + } + $customSearchHtml = $this->customSearchBarHtml(); + $searchBar .= $customSearchHtml; + $searchBar .= ' +
+ '.$value.' + + '.$value.' + + '.$title.': + + '.$title.' + + '.$title.' + + '.$title.' +
+
+
    +
  • + +
+
'; + } + return $searchBar; + } + + + /** + * get search bar html code. + */ + public function getSearchBar(){ + $data = $this->getSearchArr(); + return $this->getSearchBarHtml($data); + } + + /** + * @property $searchArr|Array. + * generate where Array by $this->_param and $searchArr. + * foreach $searchArr , check each one if it is exist in this->_param. + */ + public function initDataWhere($searchArr){ + $where = []; + foreach($searchArr as $field){ + $type = $field['type']; + $name = $field['name']; + $lang = $field['lang']; + + $columns_type = isset($field['columns_type']) ? $field['columns_type'] : ''; + if($this->_param[$name] || $this->_param[$name.'_get'] || $this->_param[$name.'_lt']){ + if($type == 'inputtext' || $type == 'select' || $type == 'chosen_select'){ + if($columns_type == 'string'){ + if($lang){ + $langname = $name.'.'.\Yii::$service->fecshoplang->getDefaultLangAttrName($name) ; + $where[] = ['like', $langname, $this->_param[$name]]; + }else{ + $where[] = ['like', $name, $this->_param[$name]]; + } + }else if($columns_type == 'int'){ + $where[] = [$name => (int)$this->_param[$name]]; + }else if($columns_type == 'float'){ + $where[] = [$name => (float)$this->_param[$name]]; + }else if($columns_type == 'date'){ + $where[] = [$name => $this->_param[$name]]; + }else{ + $where[] = [$name => $this->_param[$name]]; + } + }else if($type == 'inputdatefilter'){ + $_gte = $this->_param[$name.'_gte']; + $_lt = $this->_param[$name.'_lt']; + if($columns_type == 'int'){ + $_gte = strtotime($_gte); + $_lt = strtotime($_lt); + } + if($_gte){ + $where[] = ['>=', $name, $_gte]; + } + if($_lt){ + $where[] = ['<', $name, $_lt]; + } + }else if($type == 'inputfilter'){ + $_gte = $this->_param[$name.'_gte']; + $_lt = $this->_param[$name.'_lt']; + if($columns_type == 'int'){ + $_gte = (int)$_gte; + $_lt = (int)$_lt; + }else if($columns_type == 'float'){ + $_gte = (float)$_gte; + $_lt = (float)$_lt; + } + if($_gte){ + $where[] = ['>=', $name, $_gte]; + } + if($_lt){ + $where[] = ['<', $name, $_lt]; + } + }else{ + $where[] = [$name => $this->_param[$name]]; + } + } + } + //var_dump($where); + return $where; + } + + /** + * get edit html bar, it contains add ,eidt ,delete button. + */ + public function getEditBar(){ + /* + if(!strstr($this->_currentParamUrl,"?")){ + $csvUrl = $this->_currentParamUrl."?type=export"; + }else{ + $csvUrl = $this->_currentParamUrl."&type=export"; + } +
  • line
  • +
  • 导出EXCEL
  • + */ + return ''; + } + /** + * list pager, it contains numPerPage , pageNum , totalNum. + */ + public function getToolBar($numCount,$pageNum,$numPerPage){ + return '
    + 显示 + + 条,共'.$numCount.'条 +
    + + '; + } + /** + * list table thead. + */ + public function getTableThead(){ + $table_th_bar = $this->getTableFieldArr(); + return $this->getTableTheadHtml($table_th_bar); + + } + + public function getTableTheadHtml($table_th_bar){ + $table_th_bar = $this->getTableTheadArrInit($table_th_bar); + $this->_param['orderField'] = $this->_param['orderField'] ? $this->_param['orderField'] : $this->_primaryKey; + $this->_param['orderDirection'] = $this->_param['orderDirection'] ; + foreach($table_th_bar as $k => $field){ + if($field['orderField'] == $this->_param['orderField']){ + $table_th_bar[$k]['class'] = $this->_param['orderDirection']; + } + } + $str = ''; + $str .= ''; + foreach($table_th_bar as $b){ + $width = $b['width']; + $label = $b['label']; + $orderField = $b['orderField']; + $class = isset($b['class']) ? $b['class'] : ''; + $align = isset($b['align']) ? 'align="'.$b['align'].'"' : ''; + $str .= ''.$label.''; + } + $str .= '编辑'; + $str .= ''; + return $str; + } + + public function getTableTheadArrInit($table_columns){ + + foreach($table_columns as $field){ + $d = [ + 'orderField' => $field['orderField'], + // 'label' => $this->_obj->getAttributeLabel($field['orderField']) , + 'width' => $field['width'], + 'align' => $field['align'], + ]; + $d['label'] = $field['label'] ? $field['label'] : ''; + if(empty($d['label'] )){ + $d['label'] = $field['orderField']; + } + $table_th_bar[] = $d; + } + return $table_th_bar; + + } + + /** + * list table body. + */ + public function getTableTbody(){ + $searchArr = $this->getSearchArr(); + if(is_array($searchArr) && !empty($searchArr)){ + $where = $this->initDataWhere($searchArr); + } + $filter = [ + 'numPerPage' => $this->_param['numPerPage'], + 'pageNum' => $this->_param['pageNum'], + 'orderBy' => [$this->_param['orderField'] => (($this->_param['orderDirection'] == 'asc') ? SORT_ASC : SORT_DESC )], + 'where' => $where, + 'asArray' => $this->_asArray, + ]; + $coll = $this->_service->coll($filter ); + $data = $coll['coll']; + $this->_param['numCount'] = $coll['count']; + return $this->getTableTbodyHtml($data); + } + + public function getTableTbodyHtml($data){ + $fileds = $this->getTableFieldArr(); + $str .= ''; + $csrfString = \fec\helpers\CRequest::getCsrfString(); + foreach($data as $one){ + $str .= ''; + $str .= ''; + foreach($fileds as $field){ + $orderField = $field['orderField']; + $display = $field['display']; + $val = $one[$orderField]; + $display_title = ''; + if($val){ + if(isset($field['display']) && !empty($field['display'])){ + $display = $field['display']; + $val = $display[$val] ? $display[$val] : $val; + $display_title = $val; + } + if(isset($field['convert']) && !empty($field['convert'])){ + $convert = $field['convert']; + foreach($convert as $origin =>$to){ + if(strstr($origin,'mongodate')){ + if(isset($val->sec)){ + $timestramp = $val->sec; + if($to == 'date'){ + $val = date('Y-m-d',$timestramp); + }else if($to == 'datetime'){ + $val = date('Y-m-d H:i:s',$timestramp); + }else if($to == 'int'){ + $val = $timestramp; + } + $display_title = $val; + } + }else if(strstr($origin,'date')){ + if($to == 'date'){ + $val = date('Y-m-d',strtotime($val)); + }else if($to == 'datetime'){ + $val = date('Y-m-d H:i:s',strtotime($val)); + }else if($to == 'int'){ + $val = strtotime($val); + } + $display_title = $val; + }else if($origin == 'int'){ + if($to == 'date'){ + $val = date('Y-m-d',$val); + }else if($to == 'datetime'){ + $val = date('Y-m-d H:i:s',$val); + }else if($to == 'int'){ + $val = $val; + } + $display_title = $val; + }else if($origin == 'string'){ + if($to == 'img'){ + + $t_width = isset($field['img_width']) ? $field['img_width'] : '100'; + $t_height = isset($field['img_height']) ? $field['img_height'] : '100'; + $val = '';; + } + } + } + } + if(isset($field['lang']) && !empty($field['lang'])){ + + $val = Yii::$service->fecshoplang->getDefaultLangAttrVal($val,$orderField); + } + } + $str .= ''.$val.''; + } + $str .= ' + 编辑 + 删除 + '; + $str .= ''; + } + return $str ; + + } + + /* + example : getTableFieldArr + detail refer function getTableTbodyHtml($data)。 + public function getTableFieldArr(){ + $table_th_bar = [ + [ + 'orderField' => '_id', # db columns + 'label' => 'ID', # db columns display in table head + 'width' => '40', # columns width in table list + 'align' => 'center',# columns position in table list td + + ], + [ + 'orderField' => 'keyword', + 'label' => '关键字', + 'width' => '110', + 'align' => 'left', + ], + # select 选择类型 display 对应的是一个数组,通过key 对应值 + # 一般是状态,譬如 1 对应激活,2对应关闭等。 + [ + 'orderField' => 'unit', + 'label' => '站点', + 'width' => '110', + 'align' => 'left', + 'display' => CConfig::param("channel_type"), # Array + ], + # 图片类型: + [ + 'orderField' => 'img', + 'label' => '图片', + 'width' => '110', + 'align' => 'left', + 'convert' => ['string' => 'img'], + 'img_width' => '100', # 图片宽度 + 'img_height' => '100', # 图片高度 + ], + [ + 'orderField' => 'created_at', + 'label' => '创建时间', + 'width' => '190', + 'align' => 'center', + //'convert' => ['datetime' =>'date'], + # 把 datetime(Y-m-d H:i:s) 转化成datetime(Y-m-d) + ], + + [ + 'orderField' => 'updated_at', + 'label' => '更新时间', + 'width' => '190', + 'align' => 'center', + 'convert' => ['datetime' =>'date'], # int date datetime 显示的转换 + ], + [ + 'orderField' => 'updated_at', + 'label' => '更新时间', + 'width' => '190', + 'align' => 'center', + 'convert' => ['datetime' =>'int'], # datetime格式转换成时间戳 + ], + ]; + return $table_th_bar ; + } + */ +} \ No newline at end of file diff --git a/app/appadmin/modules/AppadminbaseBlockEdit.php b/app/appadmin/modules/AppadminbaseBlockEdit.php new file mode 100644 index 000000000..4645bf2c1 --- /dev/null +++ b/app/appadmin/modules/AppadminbaseBlockEdit.php @@ -0,0 +1,253 @@ + + * @since 1.0 + */ +class AppadminbaseBlockEdit extends Object{ + + public $_param; + public $_primaryKey; + public $_one; + public $_service; + public $_textareas; + public $_lang_attr; + /** + * html input or text etc. , html name like: + */ + protected $_editFormData; + + public function init(){ + if(!($this instanceof AppadminbaseBlockEditInterface)){ + echo json_encode(array( + 'statusCode'=>'300', + 'message'=>'Manageredit must implements fecshop\app\appadmin\interfaces\base\AppadminbaseBlockEditInterface', + )); + exit; + } + $this->_editFormData = 'editFormData'; + $this->setService(); + $this->_param = CRequest::param(); + $this->_primaryKey = $this->_service->getPrimaryKey(); + $id = $this->_param[$this->_primaryKey]; + + $this->_one = $this->_service->getByPrimaryKey($id); + } + + + public function getEditBar($editArr=[]){ + $langs = Yii::$service->fecshoplang->getAllLangCode(); + $defaultLangCode = Yii::$service->fecshoplang->defaultLangCode; + if(empty($editArr)){ + $editArr = $this->getEditArr(); + } + $str = ''; + $langAndTextarea = ''; + if($this->_param[$this->_primaryKey]){ + $str = << +EOF; + } + foreach($editArr as $column){ + $name = $column['name']; + $require = $column['require'] ? 'required' : ''; + $label = $column['label'] ? $column['label'] : $this->_one->getAttributeLabel($name); + $display = isset($column['display']) ? $column['display'] : ''; + if(empty($display)){ + $display = ['type' => 'inputString']; + } + //var_dump($this->_one['id']); + $value = $this->_one[$name] ? $this->_one[$name] : $column['default']; + $display_type = isset($display['type']) ? $display['type'] : 'inputString'; + if($display_type == 'inputString'){ + $isLang = isset($display['lang']) ? $display['lang'] : false; + + if( $isLang && is_array($langs) && !empty($langs) ){ + $tabLangTitle = ''; + $tabLangInput = ''; + foreach($langs as $lang){ + + if($require && $defaultLangCode === $lang){ + $inputStringLangRequire = 'required'; + }else{ + $inputStringLangRequire = 0; + } + + $tabLangTitle .= '
  • '.$lang.'
  • '; + $langAttrName = Yii::$service->fecshoplang->getLangAttrName($name,$lang); + $tabLangInput .= '
    +

    + + +

    + +
    '; + } + $this->_lang_attr .=<< +
    +
    +
      + {$tabLangTitle} +
    +
    +
    +
    + {$tabLangInput} +
    +
    +
    +
    + +EOF; + + }else{ + $str .=<< + + +

    +EOF; + } + }else if($display_type == 'inputDate'){ + if($value && !is_numeric($value)){ + $value = strtotime($value); + } + $valueData = $value ? date("Y-m-d",$value) : ''; + $str .=<< + + +

    +EOF; + }else if($display_type == 'inputEmail'){ + $str .=<< + + +

    +EOF; + }else if($display_type == 'inputPassword'){ + $str .=<< + + +

    +EOF; + }else if($display_type == 'select'){ + $data = isset($display['data']) ? $display['data'] : ''; + //var_dump($data); + //echo $value; + $select_str = ''; + if(is_array($data)){ + $select_str .= << +EOF; + $select_str .=''; + foreach($data as $k => $v){ + if($value == $k){ + //echo $value."#".$k; + $select_str .=''; + }else{ + $select_str .=''; + } + + } + $select_str .= ''; + } + + $str .=<< + + {$select_str} +

    +EOF; + }else if($display_type == 'textarea'){ + $rows = isset($display['rows']) ? $display['rows'] : 15; + $cols = isset($display['cols']) ? $display['cols'] : 110; + $isLang = isset($display['lang']) ? $display['lang'] : false; + $uploadImgUrl = 'upimgurl="'.CUrl::getUrl('cms/staticblock/imageupload').'" upimgext="jpg,jpeg,gif,png"'; + $uploadFlashUrl = 'upflashurl="'.CUrl::getUrl('cms/staticblock/flashupload').'" upflashext="swf"'; + $uploadLinkUrl = 'uplinkurl="'.CUrl::getUrl('cms/staticblock/linkupload').'" uplinkext="zip,rar,txt"'; + $uploadMediaUrl = 'upmediaurl="'.CUrl::getUrl('cms/staticblock/mediaupload').'" upmediaext:"avi"="" '; + + + + + if( $isLang && is_array($langs) && !empty($langs) ){ + $tabLangTitle = ''; + $tabLangTextarea = ''; + foreach($langs as $lang){ + $langAttrName = Yii::$service->fecshoplang->getLangAttrName($name,$lang); + if($require && $defaultLangCode === $lang){ + $inputStringLangRequire = 'required'; + }else{ + $inputStringLangRequire = 0; + } + $tabLangTitle .= '
  • '.$lang.'
  • '; + $tabLangTextarea .= ' +
    +
    + '.$label.'['.$lang.']: +
    +
    + +
    +
    +
    +
    '; + + + + } + $this->_textareas .=<< +
    +
    +
      + {$tabLangTitle} +
    +
    +
    +
    + {$tabLangTextarea} +
    +
    +
    +
    + +EOF; + + }else{ + $this->_textareas .= << + {$label}: +
    + +
    + +EOF; + } + } + } + return $str; + } + + + + + +} \ No newline at end of file diff --git a/app/appadmin/modules/Catalog/CatalogController.php b/app/appadmin/modules/Catalog/CatalogController.php new file mode 100644 index 000000000..d9919b1a8 --- /dev/null +++ b/app/appadmin/modules/Catalog/CatalogController.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ +use Yii; +use yii\helpers\Url; +use fecadmin\FecadminbaseController; +class CatalogController extends FecadminbaseController +{ + + public function getViewPath() + { + return Yii::getAlias('@fecshop/app/appadmin/modules/Catalog/views') . DIRECTORY_SEPARATOR . $this->id; + } + +} + + +?> \ No newline at end of file diff --git a/app/appadmin/modules/Catalog/Module.php b/app/appadmin/modules/Catalog/Module.php new file mode 100644 index 000000000..543586494 --- /dev/null +++ b/app/appadmin/modules/Catalog/Module.php @@ -0,0 +1,30 @@ + + * @since 1.0 + */ +class Module extends \fec\AdminModule +{ + public function init() + { + + # 以下代码必须指定 + $this->controllerNamespace = __NAMESPACE__ . '\\controllers'; + $this->_currentDir = __DIR__ ; + $this->_currentNameSpace = __NAMESPACE__; + + # 指定默认的man文件 + $this->layout = "/main_ajax.php"; + parent::init(); + + } +} diff --git a/app/appadmin/modules/Catalog/block/category/Image.php b/app/appadmin/modules/Catalog/block/category/Image.php new file mode 100644 index 000000000..1e9140697 --- /dev/null +++ b/app/appadmin/modules/Catalog/block/category/Image.php @@ -0,0 +1,31 @@ + + * @since 1.0 + */ +class Image +{ + + public function upload(){ + foreach($_FILES as $FILE){ + + list($imgSavedRelativePath,$imgUrl,$imgPath) = Yii::$service->category->image->saveCategoryUploadImg($FILE); + } + echo json_encode([ + 'return_status' => 'success', + 'relative_path' => $imgSavedRelativePath, + 'img_url' => $imgUrl, + ]); + exit; + } + +} \ No newline at end of file diff --git a/app/appadmin/modules/Catalog/block/category/Index.php b/app/appadmin/modules/Catalog/block/category/Index.php new file mode 100644 index 000000000..e77ef42a8 --- /dev/null +++ b/app/appadmin/modules/Catalog/block/category/Index.php @@ -0,0 +1,318 @@ + + * @since 1.0 + */ +class Index extends AppadminbaseBlockEdit implements AppadminbaseBlockEditInterface +{ + /** + * init param function ,execute in construct + */ + public function init(){ + //$this->_saveUrl = CUrl::getUrl('catalog/category/managereditsave'); + parent::init(); + } + + + + public function remove(){ + $primaryKey = Yii::$service->category->getPrimaryKey(); + $primaryVal = Yii::$app->request->get($primaryKey); + if($primaryVal){ + Yii::$service->category->remove($primaryVal); + $errors = Yii::$service->helper->errors->get(); + if(!$errors ){ + echo json_encode(array( + "statusCode"=>"200", + "message"=>"delete success", + )); + exit; + }else{ + echo json_encode(array( + "statusCode"=>"300", + "message"=>$errors, + )); + exit; + } + } + echo json_encode(array( + "statusCode"=>"300", + "message"=>"您需要选择一个分类", + )); + exit; + } + + public function getCategoryTree($treeArr='',$level=0){ + if(!$treeArr){ + $treeArr = Yii::$service->category->getTreeArr(); + } + $id = Yii::$service->category->GetPrimaryKey(); + + if(!$level){ + $str = ' + + + +
    +
    + + + +
    + + +
    + + $refine_by_info, + ]; + $config = [ + 'view' => 'catalog/category/index/filter/refineby.php', + ]; + echo Yii::$service->page->widget->renderContent('category_product_filter_refine_by',$config,$parentThis); + ?> + $filter_category, + 'current_category'=> $name, + ]; + $config = [ + 'view' => 'catalog/category/index/filter/subcategory.php', + ]; + echo Yii::$service->page->widget->renderContent('category_product_filter_sub_category',$config,$parentThis); + ?> + $filter_info, + ]; + $config = [ + 'view' => 'catalog/category/index/filter/attr.php', + ]; + echo Yii::$service->page->widget->renderContent('category_product_filter_attr',$config,$parentThis); + ?> + $filter_price, + ]; + $config = [ + 'view' => 'catalog/category/index/filter/price.php', + ]; + echo Yii::$service->page->widget->renderContent('category_product_filter_price',$config,$parentThis); + ?> +
    +
    + + +registerJs($this->blocks['category_product_filter'],\yii\web\View::POS_END);//将编写的js代码注册到页面底部 ?> diff --git a/app/appfront/theme/base/front/catalogsearch/index/index/toolbar.php b/app/appfront/theme/base/front/catalogsearch/index/index/toolbar.php new file mode 100644 index 000000000..0c857e826 --- /dev/null +++ b/app/appfront/theme/base/front/catalogsearch/index/index/toolbar.php @@ -0,0 +1,22 @@ + +
    +
    + + + + Show Per Page: + + +
    + +
    +
    diff --git a/app/appfront/theme/base/front/checkout/cart/index.php b/app/appfront/theme/base/front/checkout/cart/index.php new file mode 100644 index 000000000..92600a14c --- /dev/null +++ b/app/appfront/theme/base/front/checkout/cart/index.php @@ -0,0 +1,322 @@ + +
    +
    + + +
    + +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     Product NameUnit PriceQtySubtotal 
    + + <?= $product_one['name'] ?> + + +

    + +

    + +
      + $val){ ?> + +
    • :
    • + + +
    + +
    + + + + + +
    + + + +
    +
    +
    + + + + + Remove item +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +

    Discount Codes

    +
    + +
    + + +
    +
    + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    + Item Subtotal: +
    + Shipping +
    + Coupon: + -
    + + + + + + + + + + + +
    + Grand Total + + +
    +
    +
    + + + + - OR - + + + +
    +
    +
    +
    + +
    +
    + +
    + Your Cart is empty, You Can Click Here to Home Page + + + +
    + +
    +
    + + \ No newline at end of file diff --git a/app/appfront/theme/base/front/checkout/onepage/index.php b/app/appfront/theme/base/front/checkout/onepage/index.php new file mode 100644 index 000000000..bb6aa7baf --- /dev/null +++ b/app/appfront/theme/base/front/checkout/onepage/index.php @@ -0,0 +1,409 @@ +
    +
    +
    + +
    +

    Checkout

    +

    Welcome to the checkout. Fill in the fields below to complete your purchase!

    + +
    +
    + $address_view_file, + ]; + //var_dump($address_list); + $addressParam = [ + 'cart_address_id' => $cart_address_id, + 'address_list' => $address_list, + 'customer_info' => $customer_info, + 'country_select' => $country_select, + 'state_html' => $state_html, + //'cart_address' => $cart_address, + //'payments' => $payments, + //'current_payment_mothod' => $current_payment_mothod, + ]; + ?> + page->widget->render($addressView,$addressParam); ?> + +
    + +
    +
    + 'checkout/onepage/index/shipping.php' + ]; + $shippingParam = [ + 'shippings' => $shippings, + ]; + ?> + page->widget->render($shippingView,$shippingParam); ?> +
    + + + 'checkout/onepage/index/payment.php' + ]; + $paymentParam = [ + 'payments' => $payments, + 'current_payment_mothod' => $current_payment_mothod, + ]; + ?> + page->widget->render($paymentView,$paymentParam); ?> + + +
    + +
    Coupon codes (optional)
    + + + + +
    + +
    +
    +
    + + +
    + +
    +
    + 'checkout/onepage/index/review_order.php' + ]; + $reviewOrderParam = [ + 'cart_info' => $cart_info, + 'currency_info' => $currency_info, + ]; + ?> + page->widget->render($reviewOrderView,$reviewOrderParam); ?> + +
    +
    + Place order now +
      Please wait, processing your order...
    +
    +
    +
     
    +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/app/appfront/theme/base/front/checkout/onepage/index/address.php b/app/appfront/theme/base/front/checkout/onepage/index/address.php new file mode 100644 index 000000000..3aff01220 --- /dev/null +++ b/app/appfront/theme/base/front/checkout/onepage/index/address.php @@ -0,0 +1,111 @@ + + + + + + +
    +
      +
    • +

      Billing address

      +
    • +
    • +
      + +
        +
      • +
        + + +
        +
        + + +
        +
      • +
      • +
        + + +
        + +
        +
        +
      • +
      • +
        + + +
        +
      • +
      • +
        + + +
        + +
        +
      • +
      • +
        + + +
        +
      • + +
      • +
        + + +
        +
      • +
      • + +
        +
        + +
        +
        +
      • +
      • +
        + + +
        + +
      • + user->isGuest){ ?> + + +
      • +
        + + +
        + +
      • + + +
      +
      +
    • + +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/checkout/onepage/index/address_select.php b/app/appfront/theme/base/front/checkout/onepage/index/address_select.php new file mode 100644 index 000000000..bd168be04 --- /dev/null +++ b/app/appfront/theme/base/front/checkout/onepage/index/address_select.php @@ -0,0 +1,98 @@ + + + + + + +
    +
      +
    • +

      Billing address

      +
    • +
    • +
      + + +
      +
    • + +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/checkout/onepage/index/payment.php b/app/appfront/theme/base/front/checkout/onepage/index/payment.php new file mode 100644 index 000000000..9838d1a6f --- /dev/null +++ b/app/appfront/theme/base/front/checkout/onepage/index/payment.php @@ -0,0 +1,34 @@ + + +
    +

    Payment method

    +
    +
    +
    + + $info){ ?> + +
    + type="radio"> + +
    +
    +
      +
    • + + + + + +
    • +
    • + +
    • +
    +
    + + +
    +
    +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/checkout/onepage/index/review_order.php b/app/appfront/theme/base/front/checkout/onepage/index/review_order.php new file mode 100644 index 000000000..2160a3f90 --- /dev/null +++ b/app/appfront/theme/base/front/checkout/onepage/index/review_order.php @@ -0,0 +1,81 @@ + + + + + +

    Review your order

    +
    + + + + + + + + + + + + + + + + + + + + +
    nameQtySubtotal
    + + 2121 + + + +

    + + + +

    + +
      + $val){ ?> + +
    • :
    • + + +
    + +
    + + + + + + + + + + + + + + + + + + + +
    Subtotal + +
    Shipping + +
    Discount + - +
    Grand total + +
    +
    + + \ No newline at end of file diff --git a/app/appfront/theme/base/front/checkout/onepage/index/shipping.php b/app/appfront/theme/base/front/checkout/onepage/index/shipping.php new file mode 100644 index 000000000..23615b194 --- /dev/null +++ b/app/appfront/theme/base/front/checkout/onepage/index/shipping.php @@ -0,0 +1,24 @@ + +
    +

    Shipping method

    +
    +
    + + + +
    +
    +
    + type="radio" id="s_method_flatrate_flatrate" value="" class="validate-one-required-by-name" name="shipping_method"> + +
    +
    + + +
    +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/cms/article/index.php b/app/appfront/theme/base/front/cms/article/index.php new file mode 100644 index 000000000..65b825da4 --- /dev/null +++ b/app/appfront/theme/base/front/cms/article/index.php @@ -0,0 +1,17 @@ + +
    +
    +

    +
    + +
    +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/cms/home/index.php b/app/appfront/theme/base/front/cms/home/index.php new file mode 100644 index 000000000..43f3d8c94 --- /dev/null +++ b/app/appfront/theme/base/front/cms/home/index.php @@ -0,0 +1,81 @@ + +
    + cms->staticblock->getStoreContentByIdentify('home-big-img','appfront') ?> +
    +

    BEST SELLERMORE

    +
    + 'cms/home/index/product.php', + ]; + echo Yii::$service->page->widget->renderContent('category_product_price',$config,$parentThis); + ?> +
    + +
    +
    +

    FEATURED PRODUCTSMORE

    +
    + 'cms/home/index/product.php', + ]; + echo Yii::$service->page->widget->renderContent('category_product_price',$config,$parentThis); + ?> +
    +
    + +
    + + +registerJs($this->blocks['owl_fecshop_slider'],\yii\web\View::POS_END);//将编写的js代码注册到页面底部 ?> diff --git a/app/appfront/theme/base/front/cms/home/index/price.php b/app/appfront/theme/base/front/cms/home/index/price.php new file mode 100644 index 000000000..e06288deb --- /dev/null +++ b/app/appfront/theme/base/front/cms/home/index/price.php @@ -0,0 +1,10 @@ +

    + + + + + + + + +

    \ No newline at end of file diff --git a/app/appfront/theme/base/front/cms/home/index/product.php b/app/appfront/theme/base/front/cms/home/index/product.php new file mode 100644 index 000000000..d0d0f676f --- /dev/null +++ b/app/appfront/theme/base/front/cms/home/index/product.php @@ -0,0 +1,38 @@ +
    +
    + + + + +
    +
    + + + + + diff --git a/app/appfront/theme/base/front/customer/account/forgotpassword.php b/app/appfront/theme/base/front/customer/account/forgotpassword.php new file mode 100644 index 000000000..ba32d93ce --- /dev/null +++ b/app/appfront/theme/base/front/customer/account/forgotpassword.php @@ -0,0 +1,109 @@ +
    +page->widget->render('flashmessage'); ?> + + +
    + + +registerJs($this->blocks['forgot_password'],\yii\web\View::POS_END);//将编写的js代码注册到页面底部 ?> + + + + diff --git a/app/appfront/theme/base/front/customer/account/forgotpasswordsubmit.php b/app/appfront/theme/base/front/customer/account/forgotpasswordsubmit.php new file mode 100644 index 000000000..0054d2f5b --- /dev/null +++ b/app/appfront/theme/base/front/customer/account/forgotpasswordsubmit.php @@ -0,0 +1,25 @@ +
    + +
    + We've sent a message to the email address + you have on file with us. + Please follow the instructions provided in the message to reset your password. +
    +
    +

    Didn't receive the mail from us? click here to retry

    + +

    Check your bulk or junk email folder.

    + +

    If you still can't find it, click support center for help

    +
    + +
    + Email address do not exist, please click here to re-enter! +
    +
    + + + + + +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/account/index.php b/app/appfront/theme/base/front/customer/account/index.php new file mode 100644 index 000000000..c01dd1890 --- /dev/null +++ b/app/appfront/theme/base/front/customer/account/index.php @@ -0,0 +1,78 @@ +
    + + +
    + 'fecshop\app\appfront\modules\Customer\block\LeftMenu', + 'view' => 'customer/leftmenu.php' + ]; + ?> + page->widget->render($leftMenu,$this); ?> +
    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/account/login.php b/app/appfront/theme/base/front/customer/account/login.php new file mode 100644 index 000000000..ada444cdc --- /dev/null +++ b/app/appfront/theme/base/front/customer/account/login.php @@ -0,0 +1,74 @@ +
    +page->widget->render('flashmessage'); ?> + + +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/account/register.php b/app/appfront/theme/base/front/customer/account/register.php new file mode 100644 index 000000000..2862d080c --- /dev/null +++ b/app/appfront/theme/base/front/customer/account/register.php @@ -0,0 +1,210 @@ +
    +page->widget->render('flashmessage'); ?> + + +
    + + +registerJs($this->blocks['customer_account_register'],\yii\web\View::POS_END);//将编写的js代码注册到页面底部 ?> + + + + diff --git a/app/appfront/theme/base/front/customer/account/resetpassword.php b/app/appfront/theme/base/front/customer/account/resetpassword.php new file mode 100644 index 000000000..acd81a30e --- /dev/null +++ b/app/appfront/theme/base/front/customer/account/resetpassword.php @@ -0,0 +1,141 @@ +
    +page->widget->render('flashmessage'); ?> + + + + + + + + registerJs($this->blocks['customer_account_reset'],\yii\web\View::POS_END);//将编写的js代码注册到页面底部 ?> + + + +
    + Your Reset Password Token is Expired, You can click here to retrieve it + +
    + +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/account/resetpasswordsuccess.php b/app/appfront/theme/base/front/customer/account/resetpasswordsuccess.php new file mode 100644 index 000000000..0b4a758e1 --- /dev/null +++ b/app/appfront/theme/base/front/customer/account/resetpasswordsuccess.php @@ -0,0 +1,7 @@ +
    + reset you account success, you can + click here + to login . + + +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/address/edit.php b/app/appfront/theme/base/front/customer/address/edit.php new file mode 100644 index 000000000..5bf6b05cb --- /dev/null +++ b/app/appfront/theme/base/front/customer/address/edit.php @@ -0,0 +1,205 @@ +
    + + +
    + 'fecshop\app\appfront\modules\Customer\block\LeftMenu', + 'view' => 'customer/leftmenu.php' + ]; + ?> + page->widget->render($leftMenu,$this); ?> +
    +
    +
    + + + \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/address/index.php b/app/appfront/theme/base/front/customer/address/index.php new file mode 100644 index 000000000..5bc22f160 --- /dev/null +++ b/app/appfront/theme/base/front/customer/address/index.php @@ -0,0 +1,76 @@ +
    + + +
    + 'fecshop\app\appfront\modules\Customer\block\LeftMenu', + 'view' => 'customer/leftmenu.php' + ]; + ?> + page->widget->render($leftMenu,$this); ?> +
    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/contacts/index.php b/app/appfront/theme/base/front/customer/contacts/index.php new file mode 100644 index 000000000..115fe9c2f --- /dev/null +++ b/app/appfront/theme/base/front/customer/contacts/index.php @@ -0,0 +1,96 @@ +
    + + +
    +
    +
    +

    Contacts

    +
    +
    + Email: + +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/editaccount/index.php b/app/appfront/theme/base/front/customer/editaccount/index.php new file mode 100644 index 000000000..46dab22fe --- /dev/null +++ b/app/appfront/theme/base/front/customer/editaccount/index.php @@ -0,0 +1,217 @@ +
    +page->widget->render('flashmessage'); ?> + + + +
    + 'fecshop\app\appfront\modules\Customer\block\LeftMenu', + 'view' => 'customer/leftmenu.php' + ]; + ?> + page->widget->render($leftMenu,$this); ?> +
    +
    +
    + + +registerJs($this->blocks['customer_account_info_update'],\yii\web\View::POS_END);//将编写的js代码注册到页面底部 ?> + + \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/leftmenu.php b/app/appfront/theme/base/front/customer/leftmenu.php new file mode 100644 index 000000000..d3c67c292 --- /dev/null +++ b/app/appfront/theme/base/front/customer/leftmenu.php @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/newsletter/error.php b/app/appfront/theme/base/front/customer/newsletter/error.php new file mode 100644 index 000000000..d95f17b2f --- /dev/null +++ b/app/appfront/theme/base/front/customer/newsletter/error.php @@ -0,0 +1,3 @@ +
    + +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/newsletter/index.php b/app/appfront/theme/base/front/customer/newsletter/index.php new file mode 100644 index 000000000..3307c9abf --- /dev/null +++ b/app/appfront/theme/base/front/customer/newsletter/index.php @@ -0,0 +1,3 @@ +
    + +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/order/index.php b/app/appfront/theme/base/front/customer/order/index.php new file mode 100644 index 000000000..7d89bc1ca --- /dev/null +++ b/app/appfront/theme/base/front/customer/order/index.php @@ -0,0 +1,66 @@ +
    +page->widget->render('flashmessage'); ?> + + + +
    + 'fecshop\app\appfront\modules\Customer\block\LeftMenu', + 'view' => 'customer/leftmenu.php' + ]; + ?> + page->widget->render($leftMenu,$this); ?> +
    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/order/reorder.php b/app/appfront/theme/base/front/customer/order/reorder.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/customer/order/view.php b/app/appfront/theme/base/front/customer/order/view.php new file mode 100644 index 000000000..952507f90 --- /dev/null +++ b/app/appfront/theme/base/front/customer/order/view.php @@ -0,0 +1,170 @@ + +
    + + +
    + 'fecshop\app\appfront\modules\Customer\block\LeftMenu', + 'view' => 'customer/leftmenu.php' + ]; + ?> + page->widget->render($leftMenu,$this); ?> +
    +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/productfavorite/index.php b/app/appfront/theme/base/front/customer/productfavorite/index.php new file mode 100644 index 000000000..b191c9ebd --- /dev/null +++ b/app/appfront/theme/base/front/customer/productfavorite/index.php @@ -0,0 +1,98 @@ +
    + + +
    + 'fecshop\app\appfront\modules\Customer\block\LeftMenu', + 'view' => 'customer/leftmenu.php' + ]; + ?> + page->widget->render($leftMenu,$this); ?> +
    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/customer/productreview/index.php b/app/appfront/theme/base/front/customer/productreview/index.php new file mode 100644 index 000000000..0b608a49a --- /dev/null +++ b/app/appfront/theme/base/front/customer/productreview/index.php @@ -0,0 +1,84 @@ +
    + + + +
    + 'fecshop\app\appfront\modules\Customer\block\LeftMenu', + 'view' => 'customer/leftmenu.php' + ]; + ?> + page->widget->render($leftMenu,$this); ?> +
    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/layouts/category_view.php b/app/appfront/theme/base/front/layouts/category_view.php new file mode 100644 index 000000000..13cd294a8 --- /dev/null +++ b/app/appfront/theme/base/front/layouts/category_view.php @@ -0,0 +1,62 @@ + + [ + 'position' => 'POS_END', + ], + 'js' =>[ + 'js/jquery-3.0.0.min.js', + 'js/jquery.lazyload.min.js', + 'js/js.js', + ], + ], +]; + +# css config +$cssOptions = [ + # css config 1. + [ + 'css' =>[ + 'css/style.css', + ], + ], +]; +\Yii::$service->page->asset->jsOptions = $jsOptions; +\Yii::$service->page->asset->cssOptions = $cssOptions; +\Yii::$service->page->asset->register($this); +?> +beginPage() ?> + + + +page->widget->render('head',$this); ?> + + +beginBody() ?> + + +
    + +
    + + page->widget->render('scroll',$this); ?> +endBody() ?> + + +endPage() ?> + diff --git a/app/appfront/theme/base/front/layouts/home.php b/app/appfront/theme/base/front/layouts/home.php new file mode 100644 index 000000000..7a1ac811b --- /dev/null +++ b/app/appfront/theme/base/front/layouts/home.php @@ -0,0 +1,65 @@ + + [ + 'position' => 'POS_END', + // 'condition'=> 'lt IE 9', + ], + 'js' =>[ + 'js/jquery-3.0.0.min.js', + 'js/jquery.lazyload.min.js', + 'js/owl.carousel.min.js', + 'js/js.js', + ], + ], +]; + +# css config +$cssOptions = [ + # css config 1. + [ + 'css' =>[ + 'css/style.css', + 'css/owl.carousel.css', + ], + ], +]; +\Yii::$service->page->asset->jsOptions = $jsOptions; +\Yii::$service->page->asset->cssOptions = $cssOptions; +\Yii::$service->page->asset->register($this); +?> +beginPage() ?> + + + +page->widget->render('head',$this); ?> + + +beginBody() ?> + + +
    + +
    + + page->widget->render('scroll',$this); ?> +endBody() ?> + + +endPage() ?> + diff --git a/app/appfront/theme/base/front/layouts/main.php b/app/appfront/theme/base/front/layouts/main.php new file mode 100644 index 000000000..ebfb796cf --- /dev/null +++ b/app/appfront/theme/base/front/layouts/main.php @@ -0,0 +1,84 @@ + + [ + 'position' => 'POS_END', + // 'condition'=> 'lt IE 9', + ], + 'js' =>[ + 'js/jquery-3.0.0.min.js', + 'js/jquery.lazyload.min.js', + 'js/owl.carousel.min.js', + 'js/js.js', + ], + ], + # js config 2 + [ + 'options' => [ + 'condition'=> 'lt IE 9', + ], + 'js' =>[ + 'js/ie9js.js' + ], + ], +]; + +# css config +$cssOptions = [ + # css config 1. + [ + 'css' =>[ + 'css/style.css', + 'css/ie.css', + ], + ], + + # css config 2. + [ + 'options' => [ + 'condition'=> 'lt IE 9', + ], + 'css' =>[ + 'css/ltie9.css', + ], + ], +]; +\Yii::$service->page->asset->jsOptions = $jsOptions; +\Yii::$service->page->asset->cssOptions = $cssOptions; +\Yii::$service->page->asset->register($this); +?> +beginPage() ?> + + + +page->widget->render('head',$this); ?> + + +beginBody() ?> + + +
    + +
    + + page->widget->render('scroll',$this); ?> +endBody() ?> + + +endPage() ?> + diff --git a/app/appfront/theme/base/front/layouts/one_step_checkout.php b/app/appfront/theme/base/front/layouts/one_step_checkout.php new file mode 100644 index 000000000..c1590b76f --- /dev/null +++ b/app/appfront/theme/base/front/layouts/one_step_checkout.php @@ -0,0 +1,85 @@ + + [ + 'position' => 'POS_END', + // 'condition'=> 'lt IE 9', + ], + 'js' =>[ + 'js/jquery-3.0.0.min.js', + 'js/jquery.lazyload.min.js', + 'js/owl.carousel.min.js', + 'js/js.js', + ], + ], + # js config 2 + [ + 'options' => [ + 'condition'=> 'lt IE 9', + ], + 'js' =>[ + 'js/ie9js.js' + ], + ], +]; + +# css config +$cssOptions = [ + # css config 1. + [ + 'css' =>[ + 'css/style.css', + 'css/ie.css', + 'onestepcheckout/onestepcheckout.css', + ], + ], + + # css config 2. + [ + 'options' => [ + 'condition'=> 'lt IE 9', + ], + 'css' =>[ + 'css/ltie9.css', + ], + ], +]; +\Yii::$service->page->asset->jsOptions = $jsOptions; +\Yii::$service->page->asset->cssOptions = $cssOptions; +\Yii::$service->page->asset->register($this); +?> +beginPage() ?> + + + +page->widget->render('head',$this); ?> + + +beginBody() ?> + + +
    + +
    + + page->widget->render('scroll',$this); ?> +endBody() ?> + + +endPage() ?> + diff --git a/app/appfront/theme/base/front/layouts/product_view.php b/app/appfront/theme/base/front/layouts/product_view.php new file mode 100644 index 000000000..1bf65318e --- /dev/null +++ b/app/appfront/theme/base/front/layouts/product_view.php @@ -0,0 +1,67 @@ + + [ + 'position' => 'POS_END', + ], + 'js' =>[ + 'js/jquery-3.0.0.min.js', + 'js/jquery.lazyload.min.js', + 'js/owl.carousel.min.js', + 'elevatezoom/jquery.elevatezoom.js', + 'fancybox/jquery.fancybox.pack.js', + 'js/js.js', + ], + ], +]; + +# css config +$cssOptions = [ + # css config 1. + [ + 'css' =>[ + 'css/style.css', + 'css/owl.carousel.css', + 'fancybox/jquery.fancybox.css', + ], + ], +]; +\Yii::$service->page->asset->jsOptions = $jsOptions; +\Yii::$service->page->asset->cssOptions = $cssOptions; +\Yii::$service->page->asset->register($this); +?> +beginPage() ?> + + + +page->widget->render('head',$this); ?> + + +beginBody() ?> + + +
    + +
    + + page->widget->render('scroll',$this); ?> +endBody() ?> + + +endPage() ?> + diff --git a/app/appfront/theme/base/front/mailer/customer/account/forgotpassword/body_en.php b/app/appfront/theme/base/front/mailer/customer/account/forgotpassword/body_en.php new file mode 100644 index 000000000..2d2400fd8 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/forgotpassword/body_en.php @@ -0,0 +1,34 @@ + +
    + + + + +
    + + + + + + + + + + +
    + + + +
    +

    Dear ,

    +

    There was recently a request to change the password for your account.

    +

    If you requested this password change, please click on the following link to reset your password:

    +

    If clicking the link does not work, please copy and paste the URL into your browser instead.

    +
    +

    If you did not make this request, you can ignore this message and your password will remain the same.

    +

    If you have any questions about your account or any other matter, please feel free to contact us at or by phone at .

    + +

    Thank you,

    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/forgotpassword/subject_en.php b/app/appfront/theme/base/front/mailer/customer/account/forgotpassword/subject_en.php new file mode 100644 index 000000000..1565dde02 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/forgotpassword/subject_en.php @@ -0,0 +1 @@ +Reset Your Password \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/body_de.php b/app/appfront/theme/base/front/mailer/customer/account/login/body_de.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/body_en.php b/app/appfront/theme/base/front/mailer/customer/account/login/body_en.php new file mode 100644 index 000000000..1cdb4fd22 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/login/body_en.php @@ -0,0 +1,12 @@ + +Get , message
    +Store:"en"
    +Email:
    +Mobile:
    +Content: \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/body_es.php b/app/appfront/theme/base/front/mailer/customer/account/login/body_es.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/body_fr.php b/app/appfront/theme/base/front/mailer/customer/account/login/body_fr.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/body_zh.php b/app/appfront/theme/base/front/mailer/customer/account/login/body_zh.php new file mode 100644 index 000000000..9b130d5db --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/login/body_zh.php @@ -0,0 +1 @@ +login body zh \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/subject_de.php b/app/appfront/theme/base/front/mailer/customer/account/login/subject_de.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/subject_en.php b/app/appfront/theme/base/front/mailer/customer/account/login/subject_en.php new file mode 100644 index 000000000..ff0664335 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/login/subject_en.php @@ -0,0 +1 @@ +you hava login your account on fecsop! \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/subject_es.php b/app/appfront/theme/base/front/mailer/customer/account/login/subject_es.php new file mode 100644 index 000000000..a4b6e5d26 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/login/subject_es.php @@ -0,0 +1 @@ +login subject es \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/subject_fr.php b/app/appfront/theme/base/front/mailer/customer/account/login/subject_fr.php new file mode 100644 index 000000000..d8260d620 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/login/subject_fr.php @@ -0,0 +1 @@ +login subject fr \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/login/subject_zh.php b/app/appfront/theme/base/front/mailer/customer/account/login/subject_zh.php new file mode 100644 index 000000000..1552a1c7f --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/login/subject_zh.php @@ -0,0 +1 @@ +login subject zh \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/body_de.php b/app/appfront/theme/base/front/mailer/customer/account/register/body_de.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/body_en.php b/app/appfront/theme/base/front/mailer/customer/account/register/body_en.php new file mode 100644 index 000000000..522c24dc8 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/register/body_en.php @@ -0,0 +1,44 @@ + +
    + + + + +
    + + + + + + + + + + + + +
    + + + +
    +

    Dear ,

    +

    Welcome to . To log in when visiting our site just click Login or My Account at the top of every page, and then enter your e-mail address and password.

    +

    + Use the following values when prompted to log in:
    + E-mail:
    + Password:

    +

    When you log in to your account, you will be able to do the following:

    +
      +
    • – Proceed through checkout faster when making a purchase
    • +
    • – Check the status of orders
    • +
    • – View past orders
    • +
    • – Make changes to your account information
    • +
    • – Change your password
    • +
    • – Store alternative addresses (for shipping to multiple family members and friends!)
    • +
    +

    If you have any questions about your account or any other matter, please feel free to contact us at or by phone at .

    +

    Thank you again,

    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/body_es.php b/app/appfront/theme/base/front/mailer/customer/account/register/body_es.php new file mode 100644 index 000000000..2520874d0 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/register/body_es.php @@ -0,0 +1 @@ +register body es \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/body_fr.php b/app/appfront/theme/base/front/mailer/customer/account/register/body_fr.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/body_zh.php b/app/appfront/theme/base/front/mailer/customer/account/register/body_zh.php new file mode 100644 index 000000000..559348ea4 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/register/body_zh.php @@ -0,0 +1 @@ +register body zh \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/subject_de.php b/app/appfront/theme/base/front/mailer/customer/account/register/subject_de.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/subject_en.php b/app/appfront/theme/base/front/mailer/customer/account/register/subject_en.php new file mode 100644 index 000000000..5e5119d85 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/register/subject_en.php @@ -0,0 +1 @@ +you hava register account on fecsop! \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/subject_es.php b/app/appfront/theme/base/front/mailer/customer/account/register/subject_es.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/subject_fr.php b/app/appfront/theme/base/front/mailer/customer/account/register/subject_fr.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/account/register/subject_zh.php b/app/appfront/theme/base/front/mailer/customer/account/register/subject_zh.php new file mode 100644 index 000000000..0648908d7 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/account/register/subject_zh.php @@ -0,0 +1 @@ +register subject zh \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/body_de.php b/app/appfront/theme/base/front/mailer/customer/contacts/body_de.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/body_en.php b/app/appfront/theme/base/front/mailer/customer/contacts/body_en.php new file mode 100644 index 000000000..d3f80435e --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/contacts/body_en.php @@ -0,0 +1,5 @@ +Get , message
    +Store:
    +Email:
    +Mobile:
    +Content: diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/body_es.php b/app/appfront/theme/base/front/mailer/customer/contacts/body_es.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/body_fr.php b/app/appfront/theme/base/front/mailer/customer/contacts/body_fr.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/body_zh.php b/app/appfront/theme/base/front/mailer/customer/contacts/body_zh.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/subject_de.php b/app/appfront/theme/base/front/mailer/customer/contacts/subject_de.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/subject_en.php b/app/appfront/theme/base/front/mailer/customer/contacts/subject_en.php new file mode 100644 index 000000000..bf4109df5 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/contacts/subject_en.php @@ -0,0 +1 @@ +Customer Contacts \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/subject_es.php b/app/appfront/theme/base/front/mailer/customer/contacts/subject_es.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/subject_fr.php b/app/appfront/theme/base/front/mailer/customer/contacts/subject_fr.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/contacts/subject_zh.php b/app/appfront/theme/base/front/mailer/customer/contacts/subject_zh.php new file mode 100644 index 000000000..e69de29bb diff --git a/app/appfront/theme/base/front/mailer/customer/newsletter/body_en.php b/app/appfront/theme/base/front/mailer/customer/newsletter/body_en.php new file mode 100644 index 000000000..3d917d537 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/newsletter/body_en.php @@ -0,0 +1,35 @@ + +
    + + + + +
    + + + + + + + + + + + + +
    + + + +
    +

    Dear ,

    +

    + Your subscribed email was successful, You can click Here to + + , Thank You. +

    + +

    Thank you again,

    +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/mailer/customer/newsletter/subject_en.php b/app/appfront/theme/base/front/mailer/customer/newsletter/subject_en.php new file mode 100644 index 000000000..002421f26 --- /dev/null +++ b/app/appfront/theme/base/front/mailer/customer/newsletter/subject_en.php @@ -0,0 +1 @@ +Subscribed Email Successful \ No newline at end of file diff --git a/app/appfront/theme/base/front/payment/checkmoney/success.php b/app/appfront/theme/base/front/payment/checkmoney/success.php new file mode 100644 index 000000000..0b70ca3cc --- /dev/null +++ b/app/appfront/theme/base/front/payment/checkmoney/success.php @@ -0,0 +1,15 @@ +
    +
    +
    + + Your Order Increment Id: + # + +

    + Please pay offline and contact customer service to change order payment status +

    + Thank You! +
    + +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/payment/success.php b/app/appfront/theme/base/front/payment/success.php new file mode 100644 index 000000000..facc3a0f0 --- /dev/null +++ b/app/appfront/theme/base/front/payment/success.php @@ -0,0 +1,9 @@ +
    +
    +
    + Order payment success, + Your Order Increment Id: +
    + +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/site/helper/error.php b/app/appfront/theme/base/front/site/helper/error.php new file mode 100644 index 000000000..b756eaa2d --- /dev/null +++ b/app/appfront/theme/base/front/site/helper/error.php @@ -0,0 +1,30 @@ +title = $name; +?> +
    +
    +
    + +

    title) ?>

    + +
    + +
    + +

    + The above error occurred while the Web server was processing your request. +

    +

    + Please contact us if you think this is a server error. Thank you. +

    + +
    +
    \ No newline at end of file diff --git a/app/appfront/theme/base/front/widgets/breadcrumbs.php b/app/appfront/theme/base/front/widgets/breadcrumbs.php new file mode 100644 index 000000000..13d57205f --- /dev/null +++ b/app/appfront/theme/base/front/widgets/breadcrumbs.php @@ -0,0 +1,28 @@ + + +page->breadcrumbs->getItems(); ?> + + + \ No newline at end of file diff --git a/app/appfront/theme/base/front/widgets/flashmessage.php b/app/appfront/theme/base/front/widgets/flashmessage.php new file mode 100644 index 000000000..39cf2334c --- /dev/null +++ b/app/appfront/theme/base/front/widgets/flashmessage.php @@ -0,0 +1,20 @@ +page->message->getCorrects(); ?> +page->message->getErrors(); ?> + +
    + + +
    +
    +
    + + + + +
    +
    +
    + + +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/widgets/footer.php b/app/appfront/theme/base/front/widgets/footer.php new file mode 100644 index 000000000..8e30fc763 --- /dev/null +++ b/app/appfront/theme/base/front/widgets/footer.php @@ -0,0 +1,90 @@ + + + \ No newline at end of file diff --git a/app/appfront/theme/base/front/widgets/head.php b/app/appfront/theme/base/front/widgets/head.php new file mode 100644 index 000000000..4ea1753bd --- /dev/null +++ b/app/appfront/theme/base/front/widgets/head.php @@ -0,0 +1,16 @@ + + +<?= \Yii::$app->view->title; ?> + + + + +head() ?> diff --git a/app/appfront/theme/base/front/widgets/header.php b/app/appfront/theme/base/front/widgets/header.php new file mode 100644 index 000000000..02e078677 --- /dev/null +++ b/app/appfront/theme/base/front/widgets/header.php @@ -0,0 +1,98 @@ + +
    + + + +
    +
    + +
    +
    +
    +
      + $langName){ ?> +
    • + +
    +
    +
    + +
    +
    +
    +
      + + +
    • + +
    +
    +
    +
    + +
    + + +
    + + + + 0 +
    +
    + + + + 0 +
    +
    + +
    +
    + + + + +
    + + + +
    +
    +
    + page->widget->render('topsearch',$this); ?> +
    + + +
    + +
    +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/widgets/menu.php b/app/appfront/theme/base/front/widgets/menu.php new file mode 100644 index 000000000..f23471009 --- /dev/null +++ b/app/appfront/theme/base/front/widgets/menu.php @@ -0,0 +1,51 @@ + + +
    + +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/widgets/page.php b/app/appfront/theme/base/front/widgets/page.php new file mode 100644 index 000000000..36faf59aa --- /dev/null +++ b/app/appfront/theme/base/front/widgets/page.php @@ -0,0 +1,37 @@ +
    + + < + + < + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + > + +
    + \ No newline at end of file diff --git a/app/appfront/theme/base/front/widgets/scroll.php b/app/appfront/theme/base/front/widgets/scroll.php new file mode 100644 index 000000000..554185efb --- /dev/null +++ b/app/appfront/theme/base/front/widgets/scroll.php @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/appfront/theme/base/front/widgets/topsearch.php b/app/appfront/theme/base/front/widgets/topsearch.php new file mode 100644 index 000000000..5ec1374e1 --- /dev/null +++ b/app/appfront/theme/base/front/widgets/topsearch.php @@ -0,0 +1,9 @@ +
    +
    +
    + +
    + + +
    +
    \ No newline at end of file diff --git a/app/appfront/widgets/Footer.php b/app/appfront/widgets/Footer.php new file mode 100644 index 000000000..624f161b3 --- /dev/null +++ b/app/appfront/widgets/Footer.php @@ -0,0 +1,23 @@ +store->currentLanguage; + return self::BLOCK_CACHE_PREFIX.'_'.$lang; + + } +} + + + diff --git a/app/appfront/widgets/Head.php b/app/appfront/widgets/Head.php new file mode 100644 index 000000000..36b133132 --- /dev/null +++ b/app/appfront/widgets/Head.php @@ -0,0 +1,27 @@ +store->currentStore; + $moduleId = Yii::$app->controller->module->id; + $controllerId = Yii::$app->controller->id; + $actionId = Yii::$app->controller->action->id; + $urlPathKey = $moduleId.'_'.$controllerId.'_'.$actionId; + return self::BLOCK_CACHE_PREFIX.'_'.$store.'_'.$urlPathKey; + + } +} + + + diff --git a/app/appfront/widgets/Headers.php b/app/appfront/widgets/Headers.php new file mode 100644 index 000000000..8b7795a26 --- /dev/null +++ b/app/appfront/widgets/Headers.php @@ -0,0 +1,36 @@ +url->getCurrentUrl(); + $logoutUrl = Yii::$service->url->getUrl('customer/account/logout',['rt'=>base64_encode($currentUrl)]); + + //$currentLang = + //$currency = Yii::$service->page->currency->getCurrentCurrency(); + return [ + 'logoutUrl' => $logoutUrl, + 'homeUrl' => Yii::$service->url->homeUrl(), + 'currentBaseUrl' => Yii::$service->url->getCurrentBaseUrl(), + 'currentStore' => Yii::$service->store->currentStore, + 'currentStoreLang' => Yii::$service->store->currentLangName, + 'stores' => Yii::$service->store->getStoresLang(), + 'currency' => Yii::$service->page->currency->getCurrencyInfo(), + 'currencys' => Yii::$service->page->currency->getCurrencys(), + ]; + } + + public function getCacheKey(){ + $lang = Yii::$service->store->currentStore; + $currency = Yii::$service->page->currency->getCurrentCurrency(); + return self::BLOCK_CACHE_PREFIX.'_'.$lang.'_'.$currency; + + } +} + + + diff --git a/app/appfront/widgets/Menu.php b/app/appfront/widgets/Menu.php new file mode 100644 index 000000000..9a08354ab --- /dev/null +++ b/app/appfront/widgets/Menu.php @@ -0,0 +1,27 @@ +page->menu->getMenuData(); + //var_dump($categoryArr); + return [ + 'categoryArr' => $categoryArr, + ]; + } + + public function getCacheKey(){ + $lang = Yii::$service->store->currentLanguage; + + return self::BLOCK_CACHE_PREFIX.'_'.$lang; + + } +} + + + diff --git a/app/appfront/widgets/Page.php b/app/appfront/widgets/Page.php new file mode 100644 index 000000000..6358294cb --- /dev/null +++ b/app/appfront/widgets/Page.php @@ -0,0 +1,234 @@ + + * @since 1.0 + */ +class Page +{ + public $pageNum; + public $numPerPage; + public $countTotal; + public $page; + + public function getLastData(){ + $spaceShowNum = 4; + $productNumPerPage = $this->numPerPage; + $countTotal = $this->countTotal; + $pageNum = $this->pageNum; + + $maxPageNum = ceil($countTotal/$productNumPerPage); + if($pageNum >$maxPageNum){ + $pageNum = $maxPageNum; + } + $firstSpaceShow = false; + $lastSpaceShow = false; + $frontPage = []; + $behindPage= []; + $endSpaceNum = $maxPageNum - $spaceShowNum +1 ; + $hiddenPageMaxCount = 2*$spaceShowNum+1; + $hiddenFrontStr = ''; + $hiddenBehindStr = ''; + if($maxPageNum <= $hiddenPageMaxCount){ + $c = $pageNum; + while($c > 1){ + $c = $c - 1; + if($c){ + $frontPage = array_merge([$c],$frontPage); + } + } + $c = $pageNum; + while($c < $maxPageNum){ + $c = $c + 1; + $behindPage[] = $c; + } + //var_dump($behindPage); + }else if(($pageNum > $spaceShowNum)&&($pageNum < $endSpaceNum)){ + $firstSpaceShow = true; + $lastSpaceShow = true; + $hiddenFrontStr = '...'; + $hiddenBehindStr= '...'; + $frontPage[] = $pageNum - 1; + $behindPage[]= $pageNum + 1; + $behindPage[]= $pageNum + 2; + }else if($pageNum == 1){ + $firstSpaceShow = false; + $lastSpaceShow = true; + $hiddenBehindStr = '...'; + $behindPage[]= $pageNum + 1; + $behindPage[]= $pageNum + 2; + $behindPage[]= $pageNum + 3; + $behindPage[]= $pageNum + 4; + }else if($pageNum == 2){ + $firstSpaceShow = false; + $lastSpaceShow = true; + $hiddenBehindStr = '...'; + $frontPage[] = $pageNum - 1; + $behindPage[]= $pageNum + 1; + $behindPage[]= $pageNum + 2; + $behindPage[]= $pageNum + 3; + }else if($pageNum == 3){ + $firstSpaceShow = false; + $lastSpaceShow = true; + $hiddenBehindStr = '...'; + $frontPage[] = $pageNum - 2; + $frontPage[] = $pageNum - 1; + $behindPage[]= $pageNum + 1; + $behindPage[]= $pageNum + 2; + }else if($pageNum == 4){ + $firstSpaceShow = false; + $lastSpaceShow = true; + $hiddenBehindStr = '...'; + $frontPage[] = $pageNum - 3; + $frontPage[] = $pageNum - 2; + $frontPage[] = $pageNum - 1; + $behindPage[]= $pageNum + 1; + $behindPage[]= $pageNum + 2; + }else if($pageNum == $endSpaceNum){ + $firstSpaceShow = true; + $lastSpaceShow = false; + $hiddenFrontStr = '...'; + $frontPage[]= $pageNum - 1; + $behindPage[]= $pageNum + 1; + $behindPage[]= $pageNum + 2; + $behindPage[]= $pageNum + 3; + + }else if($pageNum == ($endSpaceNum + 1)){ + $firstSpaceShow = true; + $lastSpaceShow = false; + $hiddenFrontStr = '...'; + $frontPage[]= $pageNum - 2; + $frontPage[]= $pageNum - 1; + $behindPage[]= $pageNum + 1; + $behindPage[]= $pageNum + 2; + }else if($pageNum == ($endSpaceNum + 2)){ + $firstSpaceShow = true; + $lastSpaceShow = false; + $hiddenFrontStr = '...'; + $frontPage[]= $pageNum - 3; + $frontPage[]= $pageNum - 2; + $frontPage[]= $pageNum - 1; + $behindPage[]= $pageNum + 1; + }else if($pageNum == ($endSpaceNum + 3)){ + $firstSpaceShow = true; + $lastSpaceShow = false; + $hiddenFrontStr = '...'; + $frontPage[]= $pageNum - 4; + $frontPage[]= $pageNum - 3; + $frontPage[]= $pageNum - 2; + $frontPage[]= $pageNum - 1; + + } + //Yii::$service->url->category->getFilterChooseAttrUrl($this->page,$val); + if($firstSpaceShow){ + $url = $this->getPageUrl($pageNum,1); + //Yii::$service->url->category->getFilterChooseAttrUrl($this->page,1); + $firstSpaceShow = [ + 'p' => 1, + 'url' => $url, + ]; + } + if($lastSpaceShow){ + $url = $this->getPageUrl($pageNum,$maxPageNum); + //Yii::$service->url->category->getFilterChooseAttrUrl($this->page,$maxPageNum); + $lastSpaceShow = [ + 'p' => $maxPageNum, + 'url' => $url, + ]; + } + $frontPageU = []; + //var_dump($frontPage); + if(is_array($frontPage) && !empty($frontPage)){ + foreach($frontPage as $p){ + $frontPageU[] = [ + 'p' => $p, + 'url' => $this->getPageUrl($pageNum,$p), + //Yii::$service->url->category->getFilterChooseAttrUrl($this->page,$p), + ]; + } + } + $behindPageU = []; + //var_dump($behindPage); + if(is_array($behindPage) && !empty($behindPage)){ + foreach($behindPage as $p){ + $behindPageU[] = [ + 'p' => $p, + 'url' => $this->getPageUrl($pageNum,$p), + //Yii::$service->url->category->getFilterChooseAttrUrl($this->page,$p), + ]; + } + } + $prevPage = ''; + $nextPage = ''; + if($pageNum > 1){ + $prevPage = $pageNum - 1; + $prevPage = [ + 'p' => $prevPage, + 'url' => $this->getPageUrl($pageNum,$prevPage), + //Yii::$service->url->category->getFilterChooseAttrUrl($this->page,$prevPage), + ]; + } + if($pageNum != $maxPageNum){ + $nextPage = $pageNum + 1; + $nextPage = [ + 'p' => $nextPage, + 'url' => $this->getPageUrl($pageNum,$nextPage), + //Yii::$service->url->category->getFilterChooseAttrUrl($this->page,$nextPage), + ]; + } + $currentPage = [ + 'p' => $pageNum, + ]; + //var_dump($frontPageU); + return [ + 'firstSpaceShow'=> $firstSpaceShow, + 'lastSpaceShow' => $lastSpaceShow, + 'frontPage' => $frontPageU, + 'behindPage' => $behindPageU, + 'currentPage' => $currentPage, + //'maxPageNum' => $maxPageNum, + 'prevPage' => $prevPage, + 'nextPage' => $nextPage, + 'hiddenFrontStr'=> $hiddenFrontStr, + 'hiddenBehindStr'=>$hiddenBehindStr, + ]; + } + + public function getPageUrl($currentPage,$showPage){ + $currentUrl = Yii::$service->url->getCurrentUrl(); + $pVal = Yii::$app->request->get('p'); + if($pVal){ + $currentPageStr = 'p='.$pVal; + $showPageStr = 'p='.$showPage; + $url = str_replace($currentPageStr,$showPageStr,$currentUrl); + }else{ + if(strstr($currentUrl,'?')){ + $url = $currentUrl.'&p='.$showPage; + }else{ + $url = $currentUrl.'?p='.$showPage; + } + } + return [ + 'url' => $url, + ]; + } +} + + + + + + + + + + + diff --git a/app/apphtml5/config/apphtml5.php b/app/apphtml5/config/apphtml5.php new file mode 100644 index 000000000..1f0233285 --- /dev/null +++ b/app/apphtml5/config/apphtml5.php @@ -0,0 +1,74 @@ +$modules, + /* only config in front web */ + 'bootstrap' => ['store'], + 'params' => [ + /* appfront base theme dir */ + 'appfrontBaseTheme' => '@fecshop/app/apphtml5/theme/base/front', + 'appfrontBaseLayoutName'=> 'main.php', + 'appName' => 'apphtml5', + ], + # language config. + 'components' => [ + 'i18n' => [ + 'translations' => [ + 'apphtml5' => [ + //'class' => 'yii\i18n\PhpMessageSource', + 'class' => 'fecshop\yii\i18n\PhpMessageSource', + 'basePaths' => [ + '@fecshop/app/apphtml5/languages', + '@apphtml5/languages', + ], + 'sourceLanguage' => 'en_US', # en_US Ҳ뷭룬ôԸijen_XX + ], + ], + ], + + 'user' => [ + 'identityClass' => 'fecadmin\models\AdminUser', + 'enableAutoLogin' => true, + ], + + 'errorHandler' => [ + 'errorAction' => 'site/error', + ], + + 'urlManager' => [ + 'rules' => [ + '' => 'cms/home/index', + ], + ], + + + 'request' => [ + 'class' => 'fecshop\yii\web\Request', + /* + 'enableCookieValidation' => true, + 'enableCsrfValidation' => true, + 'cookieValidationKey' => 'O1d232trde1x-M97_7QvwPo-5QGdkLMp#@#@', + 'noCsrfRoutes' => [ + 'catalog/product/addreview', + 'favorite/product/remark', + 'paypal/ipn/index', + 'paypal/ipn', + ], + */ + ], + ], + +]; diff --git a/app/apphtml5/config/modules/.gitignore b/app/apphtml5/config/modules/.gitignore new file mode 100644 index 000000000..b722e9e13 --- /dev/null +++ b/app/apphtml5/config/modules/.gitignore @@ -0,0 +1 @@ +!.gitignore \ No newline at end of file diff --git a/app/apphtml5/languages/.gitignore b/app/apphtml5/languages/.gitignore new file mode 100644 index 000000000..b722e9e13 --- /dev/null +++ b/app/apphtml5/languages/.gitignore @@ -0,0 +1 @@ +!.gitignore \ No newline at end of file diff --git a/app/apphtml5/modules/.gitignore b/app/apphtml5/modules/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/app/apphtml5/theme/.gitignore b/app/apphtml5/theme/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/app/appserver/config/appserver.php b/app/appserver/config/appserver.php new file mode 100644 index 000000000..a6b18387d --- /dev/null +++ b/app/appserver/config/appserver.php @@ -0,0 +1,73 @@ +$modules, + /* only config in front web */ + 'bootstrap' => ['store'], + 'params' => [ + /* appfront base theme dir */ + //'appfrontBaseTheme' => '@fecshop/app/appfront/theme/base/front', + //'appfrontBaseLayoutName'=> 'main.php', + 'appName' => 'appserver', + ], + # language config. + 'components' => [ + 'i18n' => [ + 'translations' => [ + 'appserver' => [ + //'class' => 'yii\i18n\PhpMessageSource', + 'class' => 'fecshop\yii\i18n\PhpMessageSource', + 'basePaths' => [ + '@fecshop/app/appserver/languages', + '@appserver/languages', + ], + 'sourceLanguage' => 'en_US', # en_US Ҳ뷭룬ôԸijen_XX + ], + ], + ], + + 'user' => [ + 'identityClass' => 'fecadmin\models\AdminUser', + 'enableAutoLogin' => true, + ], + + 'errorHandler' => [ + 'errorAction' => 'site/error', + ], + + 'urlManager' => [ + 'rules' => [ + '' => 'cms/home/index', + ], + ], + + 'request' => [ + 'class' => 'fecshop\yii\web\Request', + /* + 'enableCookieValidation' => true, + 'enableCsrfValidation' => true, + 'cookieValidationKey' => 'O1d232trde1x-M97_7QvwPo-5QGdkLMp#@#@', + 'noCsrfRoutes' => [ + 'catalog/product/addreview', + 'favorite/product/remark', + 'paypal/ipn/index', + 'paypal/ipn', + ], + */ + ], + ], + +]; diff --git a/app/appserver/config/modules/.gitignore b/app/appserver/config/modules/.gitignore new file mode 100644 index 000000000..b722e9e13 --- /dev/null +++ b/app/appserver/config/modules/.gitignore @@ -0,0 +1 @@ +!.gitignore \ No newline at end of file diff --git a/app/appserver/languages/.gitignore b/app/appserver/languages/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/app/appserver/modules/.gitignore b/app/appserver/modules/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/app/console/config/console.php b/app/console/config/console.php new file mode 100644 index 000000000..dea97e8f9 --- /dev/null +++ b/app/console/config/console.php @@ -0,0 +1,20 @@ + +$modules, + 'params' => [ + 'appName' => 'console', + ], +]; diff --git a/app/console/config/modules/Helper.php b/app/console/config/modules/Helper.php new file mode 100644 index 000000000..deac0ce3c --- /dev/null +++ b/app/console/config/modules/Helper.php @@ -0,0 +1,15 @@ + + [ + 'class' => '\fecshop\app\console\modules\Helper\Module', + ], +]; diff --git a/app/console/config/modules/Product.php b/app/console/config/modules/Product.php new file mode 100644 index 000000000..7c08eb2bf --- /dev/null +++ b/app/console/config/modules/Product.php @@ -0,0 +1,15 @@ + + [ + 'class' => '\fecshop\app\console\modules\Product\Module', + ], +]; diff --git a/app/console/modules/ConsoleController.php b/app/console/modules/ConsoleController.php new file mode 100644 index 000000000..e1acb5cf8 --- /dev/null +++ b/app/console/modules/ConsoleController.php @@ -0,0 +1,45 @@ + + + * @since 1.0 + */ +class ConsoleController extends FecController +{ + public $blockNamespace; + + /** + * get current block + * you can change $this->blockNamespace + */ + public function getBlock($blockName=''){ + if(!$blockName){ + $blockName = $this->action->id; + } + if(!$this->blockNamespace){ + $this->blockNamespace = Yii::$app->controller->module->blockNamespace; + } + if(!$this->blockNamespace){ + throw new \yii\web\HttpException(406,'blockNamespace is empty , you should config it in module->blockNamespace or controller blockNamespace '); + } + + $relativeFile = '\\'.$this->blockNamespace; + $relativeFile .= '\\'.$this->id.'\\'.ucfirst($blockName); + return new $relativeFile; + } + + +} diff --git a/app/console/modules/ConsoleModule.php b/app/console/modules/ConsoleModule.php new file mode 100644 index 000000000..8f7197f56 --- /dev/null +++ b/app/console/modules/ConsoleModule.php @@ -0,0 +1,20 @@ + + + * @since 1.0 + */ +class ConsoleModule extends \yii\base\Module +{ + +} diff --git a/app/console/modules/Helper/Module.php b/app/console/modules/Helper/Module.php new file mode 100644 index 000000000..883c7a6c7 --- /dev/null +++ b/app/console/modules/Helper/Module.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ +class Module extends ConsoleModule +{ + public $blockNamespace; + public function init() + { + # ´ָ + $nameSpace = __NAMESPACE__; + $this->controllerNamespace = $nameSpace . '\\controllers'; + $this->blockNamespace = $nameSpace . '\\block'; + parent::init(); + + } +} diff --git a/app/console/modules/Helper/controllers/UrlrewriteController.php b/app/console/modules/Helper/controllers/UrlrewriteController.php new file mode 100644 index 000000000..9385c8a17 --- /dev/null +++ b/app/console/modules/Helper/controllers/UrlrewriteController.php @@ -0,0 +1,99 @@ + + * @since 1.0 + */ +class UrlrewriteController extends Controller +{ + protected $_numPerPage = 50; + /** + * 得到当前的时间。 + */ + public function actionNowtime(){ + echo time(); + } + + /** + * 得到产品的页数。 + */ + public function actionProductpagenum(){ + $count = Yii::$service->product->collCount($filter); + echo ceil($count/$this->_numPerPage); + } + + /** + * 得到产品的总数。 + */ + public function actionProductcount(){ + $count = Yii::$service->product->collCount($filter); + echo $count; + } + /** + * 处理产品的重写 + */ + public function actionProduct($pageNum){ + $filter['numPerPage'] = $this->_numPerPage; + $filter['pageNum'] = $pageNum; + $filter['asArray'] = true; + $products = Yii::$service->product->coll($filter); + $product_ids = []; + foreach($products['coll'] as $one){ + Yii::$service->product->save($one); + } + + } + + + + + /** + * 得到分类的页数。 + */ + public function actionCategorypagenum(){ + $count = Yii::$service->category->collCount($filter); + echo ceil($count/$this->_numPerPage); + } + + /** + * 得到分类的总数。 + */ + public function actionCategorycount(){ + $count = Yii::$service->category->collCount($filter); + echo $count; + } + /** + * 处理分类的重写 + */ + public function actionCategory($pageNum){ + $filter['numPerPage'] = $this->_numPerPage; + $filter['pageNum'] = $pageNum; + $filter['asArray'] = true; + $categorys = Yii::$service->category->coll($filter); + $category_ids = []; + foreach($categorys['coll'] as $one){ + Yii::$service->category->save($one); + } + + } + /** + * 删除时间小于nowtime的 + */ + public function actionClearnoactive($nowtime){ + echo 'delete date gt '.$nowtime."\n"; + Yii::$service->url->rewrite->removeByUpdatedAt($nowtime); + } +} + + + diff --git a/app/console/modules/Product/Module.php b/app/console/modules/Product/Module.php new file mode 100644 index 000000000..a12870df9 --- /dev/null +++ b/app/console/modules/Product/Module.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ +class Module extends ConsoleModule +{ + public $blockNamespace; + public function init() + { + # ´ָ + $nameSpace = __NAMESPACE__; + $this->controllerNamespace = $nameSpace . '\\controllers'; + $this->blockNamespace = $nameSpace . '\\block'; + parent::init(); + + } +} diff --git a/app/console/modules/Product/controllers/IndexController.php b/app/console/modules/Product/controllers/IndexController.php new file mode 100644 index 000000000..78372423a --- /dev/null +++ b/app/console/modules/Product/controllers/IndexController.php @@ -0,0 +1,31 @@ + + * @since 1.0 + */ +class IndexController extends Controller +{ + + public function actionIndex(){ + echo 'xxxxx'; + //$article = Yii::$service->cms->article->coll(); + //foreach($article as $d){ + // var_dump($d); + //} + } + + + + +} \ No newline at end of file diff --git a/app/console/modules/Product/controllers/PriceController.php b/app/console/modules/Product/controllers/PriceController.php new file mode 100644 index 000000000..85954b471 --- /dev/null +++ b/app/console/modules/Product/controllers/PriceController.php @@ -0,0 +1,72 @@ + + * @since 1.0 + */ +class PriceController extends Controller +{ + protected $_numPerPage = 50; + + + /** + * 通过脚本,把产品的相应语言信息添加到相应的新表中。 + * 然后在相应语言搜索的时候,自动去相应的表中查数据。 + * 为什么需要搞这么多表呢?因为mongodb的全文搜索(fullSearch)索引,在一个表中只能有一个。 + * 这个索引可以是多个字段的组合索引, + * 因此,对于多语言的产品搜索就需要搞几个表了。 + * 下面的功能: + * 1. 将产品的name description price img score sku spu等信息更新过来。 + + */ + public function actionComputefinalprice($pageNum){ + $filter['numPerPage'] = $this->_numPerPage; + $filter['pageNum'] = $pageNum; + $filter['asArray'] = true; + $products = Yii::$service->product->coll($filter); + $product_ids = []; + foreach($products['coll'] as $one){ + //var_dump($one); + //exit; + $price = $one['price']; + $special_price = $one['special_price']; + $special_from = $one['special_from']; + $special_to = $one['special_to']; + $one['final_price'] = Yii::$service->product->price->getFinalPrice($price,$special_price,$special_from,$special_to); + Yii::$service->product->save($one); + } + + } + /** + * 得到产品的总数。 + */ + public function actionProductcount(){ + //$filter['select'] = ['_id']; + //$filter['where'][] = ['is_in_stock' => 1]; + //$filter['where'][] = ['status' => 1]; + $count = Yii::$service->product->collCount($filter); + echo $count; + } + public function actionProductpagenum(){ + //$filter['select'] = ['_id']; + //$filter['where'][] = ['is_in_stock' => 1]; + //$filter['where'][] = ['status' => 1]; + $count = Yii::$service->product->collCount($filter); + echo ceil($count/$this->_numPerPage); + } + + +} + + + diff --git a/app/console/modules/Product/controllers/SearchController.php b/app/console/modules/Product/controllers/SearchController.php new file mode 100644 index 000000000..5e49940b2 --- /dev/null +++ b/app/console/modules/Product/controllers/SearchController.php @@ -0,0 +1,99 @@ + + * @since 1.0 + */ +class SearchController extends Controller +{ + protected $_numPerPage = 50; + + /** + * 1.初始化mongodb表的索引。如果您已经有了text索引,然后想更改text索引 + * 您需要去mongodb的各个语言表中将text索引删除,或者将各个搜索表删除。 + * 当重新执行initmongoindex的时候就会重建text索引,否则会报错 + * 在mongodb中text可以是多个字段组合,但是只能有一个text索引。 + * 这也就是为什么要把各个语言分开成多个表的原因。 + * 2.初始化其他表的索引 + */ + public function actionInitindex(){ + Yii::$service->search->initFullSearchIndex(); + } + + public function actionNowtime(){ + echo time(); + } + + + + /** + * 通过脚本,把产品的相应语言信息添加到相应的新表中。 + * 然后在相应语言搜索的时候,自动去相应的表中查数据。 + * 为什么需要搞这么多表呢?因为mongodb的全文搜索(fullSearch)索引,在一个表中只能有一个。 + * 这个索引可以是多个字段的组合索引, + * 因此,对于多语言的产品搜索就需要搞几个表了。 + * 下面的功能: + * 1. 将产品的name description price img score sku spu等信息更新过来。 + * 2. 只会同步is_in_stock = 1 , status = 1 的产品。 + */ + public function actionSyncdata($pageNum){ + $filter['select'] = ['_id']; + $filter['where'][] = ['is_in_stock' => 1]; + $filter['where'][] = ['status' => 1]; + $filter['numPerPage'] = $this->_numPerPage; + $filter['pageNum'] = $pageNum; + $products = Yii::$service->product->coll($filter); + $product_ids = []; + foreach($products['coll'] as $p){ + $product_ids[] = $p['_id']; + } + //echo count($product_ids); + Yii::$service->search->syncProductInfo($product_ids,$this->_numPerPage); + + } + /** + * 得到产品的总数。 + */ + public function actionSynccount(){ + $filter['select'] = ['_id']; + $filter['where'][] = ['is_in_stock' => 1]; + $filter['where'][] = ['status' => 1]; + $count = Yii::$service->product->collCount($filter); + echo $count; + } + public function actionSyncpagenum(){ + $filter['select'] = ['_id']; + $filter['where'][] = ['is_in_stock' => 1]; + $filter['where'][] = ['status' => 1]; + $count = Yii::$service->product->collCount($filter); + echo ceil($count/$this->_numPerPage); + } + + + /** + * @property $nowtime | Int 当前时间的时间戳 + * 经过上面的批量更新,会更新updated_at字段,因此小于$nowtime的数据,会认为是无效 + * 的字段了,因为上面的更新是批量更新。 + */ + public function actionDeletenotactiveproduct($nowTimeStamp){ + Yii::$service->search->deleteNotActiveProduct($nowTimeStamp); + } + + + public function actionXundeleteallproduct($i){ + Yii::$service->search->xunSearch->xunDeleteAllProduct($this->_numPerPage,$i); + } +} + + + diff --git a/app/console/modules/Product/controllers/search/MongodbController.php b/app/console/modules/Product/controllers/search/MongodbController.php new file mode 100644 index 000000000..c354ca913 --- /dev/null +++ b/app/console/modules/Product/controllers/search/MongodbController.php @@ -0,0 +1,26 @@ + + * @since 1.0 + */ +class MongodbController extends Controller +{ + + + + +} + + + diff --git a/app/console/modules/Product/controllers/search/XunController.php b/app/console/modules/Product/controllers/search/XunController.php new file mode 100644 index 000000000..616d9a937 --- /dev/null +++ b/app/console/modules/Product/controllers/search/XunController.php @@ -0,0 +1,26 @@ + + * @since 1.0 + */ +class XunController extends Controller +{ + + + + +} + + + diff --git a/components/Store.php b/components/Store.php new file mode 100644 index 000000000..69638ce8f --- /dev/null +++ b/components/Store.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ +class Store extends Component implements BootstrapInterface +{ + + public function bootstrap($app){ + Yii::$service->store->bootstrap($app); + + } + + + +} \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..f6a4026f2 --- /dev/null +++ b/composer.json @@ -0,0 +1,42 @@ +{ + "name": "fancyecommerce/fecshop", + "description": "fancyecommerce yii2 fecshop ", + "keywords": [ + "yii2", + "fecshop" , + "fancyecommerce" , + "shop" , + "ecommerce" , + "cart" + ], + "homepage": "https://github.com/fancyecommerce/yii2_fecshop", + "type": "yii2-extension", + "license": "OSL-3", + "support": { + "source": "https://github.com/fancyecommerce/yii2_fecshop" + }, + "authors": [ + { + "name": "terry", + "email": "zqy234@126.com" + } + ], + "minimum-stability": "stable", + "require": { + "php": ">=5.4.0", + "yiisoft/yii2": ">=2.0.9" , + "fancyecommerce/fec_admin":"~1.3.6.2", + "yiisoft/yii2-mongodb": "~2.1.0" , + "skeeks/yii2-assets-auto-compress": "*", + "hightman/xunsearch": "*@beta", + "facebook/graph-sdk": ">=5.4.4" + }, + "autoload": { + "psr-4": { + "fecshop\\": "" + } + }, + "config": { + "process-timeout": 1800 + } +} diff --git a/config/components/Store.php b/config/components/Store.php new file mode 100644 index 000000000..84e4d59e8 --- /dev/null +++ b/config/components/Store.php @@ -0,0 +1,13 @@ + [ + 'class' => 'fecshop\components\Store', + ], +]; \ No newline at end of file diff --git a/config/components/XunSearch.php b/config/components/XunSearch.php new file mode 100644 index 000000000..16ab42740 --- /dev/null +++ b/config/components/XunSearch.php @@ -0,0 +1,15 @@ + [ + 'class' => 'hightman\xunsearch\Connection', // б + 'iniDirectory' => '@fecshop/config/xunsearch', // ini ļĿ¼Ĭϣ@vendor/hightman/xunsearch/app + 'charset' => 'utf-8', // ָĿʹõĬϱ룬Ĭϼʱ utf-8ɲָ + ], +]; diff --git a/config/fecshop.php b/config/fecshop.php new file mode 100644 index 000000000..87c24a64c --- /dev/null +++ b/config/fecshop.php @@ -0,0 +1,27 @@ + $components, + 'services' => $services, + 'params' => [ + + ], +]; diff --git a/config/services/AdminUser.php b/config/services/AdminUser.php new file mode 100644 index 000000000..76707265e --- /dev/null +++ b/config/services/AdminUser.php @@ -0,0 +1,12 @@ + [ + 'class' => 'fecshop\services\AdminUser', + ], +]; \ No newline at end of file diff --git a/config/services/Cart.php b/config/services/Cart.php new file mode 100644 index 000000000..0703384cb --- /dev/null +++ b/config/services/Cart.php @@ -0,0 +1,27 @@ + [ + 'class' => 'fecshop\services\Cart', + # 子服务 + 'childService' => [ + 'quote' => [ + 'class' => 'fecshop\services\cart\Quote', + ], + 'quoteItem' => [ + 'class' => 'fecshop\services\cart\QuoteItem', + ], + 'info' => [ + 'class' => 'fecshop\services\cart\Info', + ], + 'coupon' => [ + 'class' => 'fecshop\services\cart\Coupon', + ], + ], + ], +]; \ No newline at end of file diff --git a/config/services/Category.php b/config/services/Category.php new file mode 100644 index 000000000..7f8f2166e --- /dev/null +++ b/config/services/Category.php @@ -0,0 +1,29 @@ + [ + 'class' => 'fecshop\services\Category', + # 子服务 + 'childService' => [ + 'product' => [ + 'class' => 'fecshop\services\category\Product', + ], + 'menu' => [ + 'class' => 'fecshop\services\category\Menu', + //'rootCategoryId' => 0, + ], + 'image' => [ + 'class' => 'fecshop\services\category\Image', + 'imageFloder' => 'media/catalog/category', + //'allowImgType' => ['image/jpeg','image/gif','image/png'], + 'maxUploadMSize'=> 5, #MB + + ], + ], + ], +]; \ No newline at end of file diff --git a/config/services/Cms.php b/config/services/Cms.php new file mode 100644 index 000000000..a716c38c1 --- /dev/null +++ b/config/services/Cms.php @@ -0,0 +1,24 @@ + [ + 'class' => 'fecshop\services\Cms', + # 子服务 + 'childService' => [ + 'article' => [ + 'class' => 'fecshop\services\cms\Article', + 'storage' => 'mysqldb', # mysqldb or mongodb. + ], + + 'staticblock' => [ + 'class' => 'fecshop\services\cms\StaticBlock', + 'storage' => 'mongodb', # mysqldb or mongodb. + ], + ], + ], +]; \ No newline at end of file diff --git a/config/services/Customer.php b/config/services/Customer.php new file mode 100644 index 000000000..604740629 --- /dev/null +++ b/config/services/Customer.php @@ -0,0 +1,56 @@ + [ + 'class' => 'fecshop\services\Customer', + 'customer_register' => [ + 'min_name_length' => 2, # 注册账号的firstname, lastname的最小长度 + 'max_name_length' => 30, # 注册账号的firstname, lastname的最大长度 + 'min_pass_length' => 2, # 注册账号的密码的最小长度 + 'max_pass_length' => 30, # 注册账号的密码的最大长度 + ], + # 子服务 + 'childService' => [ + 'newsletter' => [ + 'class' => 'fecshop\services\customer\Newsletter', + ], + + + 'address' => [ + 'class' => 'fecshop\services\customer\Address', + ], + 'affiliate' => [ + 'class' => 'fecshop\services\customer\Affiliate', + ], + 'coupon' => [ + 'class' => 'fecshop\services\customer\Coupon', + ], + 'dropship' => [ + 'class' => 'fecshop\services\customer\Dropship', + ], + 'favorite' => [ + 'class' => 'fecshop\services\customer\Favorite', + ], + 'message' => [ + 'class' => 'fecshop\services\customer\Message', + ], + 'order' => [ + 'class' => 'fecshop\services\customer\Order', + ], + 'point' => [ + 'class' => 'fecshop\services\customer\Point', + ], + 'review' => [ + 'class' => 'fecshop\services\customer\Review', + ], + 'wholesale' => [ + 'class' => 'fecshop\services\customer\Wholesale', + ], + ], + ], +]; \ No newline at end of file diff --git a/config/services/Email.php b/config/services/Email.php new file mode 100644 index 000000000..55e95c116 --- /dev/null +++ b/config/services/Email.php @@ -0,0 +1,66 @@ + [ + 'class' => 'fecshop\services\Email', + 'mailerConfig' => [ + # 默认通用配置 + 'default' => [ + + 'class' => 'yii\swiftmailer\Mailer', + 'transport' => [ + 'class' => 'Swift_SmtpTransport', + 'host' => 'smtp.qq.com', + //'username' => '2358269014@qq.com', + //'password' => 'bjxpkyzfwkxnebai', + + 'username' => '372716335@qq.com', + 'password' => 'wffmbummgnhhcbbj', + + 'port' => '587', + 'encryption' => 'tls', + ], + 'messageConfig'=>[ + 'charset'=>'UTF-8', + ], + + + 'messageConfig'=>[ + 'charset'=>'UTF-8', + ], + ], + /* + # 登录账号 - 发送邮件的配置 + 'login' => 'default', + # 注册账号 - 发送邮件的配置 + 'register' => 'default', + # 找回密码 - 发送邮件的配置 + 'forgetPass' => 'default', + # 订阅邮件操作成功后 - 发送邮件的配置 + 'subscribeEmailSuccess' => 'default', + # 客户订阅邮件成功后,定期发送邮件给客户, - 发送邮件的配置 + 'subscribeEmail' => 'default', + # 客户评论产品后, - 发送邮件的配置 + 'reviewProduct' => 'default', + # 客户评论审核成功后, - 发送邮件的配置 + 'reviewProductAuditSuccess' => 'default', + # 客户评论产品后, - 发送邮件的配置 + 'contacts' => 'default', + + 'createNewOrder'=> [ # 创建新订单,但是不一定支付成功 + + ], + + 'paySuccessOrder'=> 'createNewOrder', # 支付成功发送的邮件配置,值为 createNewOrder 代表等于 createNewOrder的配置 + 'shippedOrder' => 'createNewOrder', # 订单发货后,发送的邮件配置,值为 createNewOrder 代表等于 createNewOrder的配置 + */ + ], + + + ], +]; \ No newline at end of file diff --git a/config/services/FecshopLang.php b/config/services/FecshopLang.php new file mode 100644 index 000000000..773ec5880 --- /dev/null +++ b/config/services/FecshopLang.php @@ -0,0 +1,38 @@ + [ + 'class' => 'fecshop\services\FecshopLang', + /* + 'allLangCode' => [ + 'en_US' => [ + 'code' => 'en', + ], + 'fr_FR' => [ + 'code' => 'fr', + ], + 'de_DE' => [ + 'code' => 'de', + ],, + 'es_ES' => [ + 'code' => 'es', + ],, + 'ru_RU' => [ + 'code' => 'ru', + ],, + 'pt_PT' => [ + 'code' => 'pt', + ],, + 'zh_CN' => [ + 'code' => 'zh', + ], + ], + 'defaultLangCode' => 'en', + */ + ], +]; \ No newline at end of file diff --git a/config/services/Helper.php b/config/services/Helper.php new file mode 100644 index 000000000..59bdc6693 --- /dev/null +++ b/config/services/Helper.php @@ -0,0 +1,60 @@ + [ + 'class' => 'fecshop\services\Helper', + # 子服务 + 'childService' => [ + 'ar' => [ + 'class' => 'fecshop\services\helper\AR', + ], + 'log' => [ + 'class' => 'fecshop\services\helper\Log', + 'log_config' => [ + # service log config + 'services' => [ + # if enable is false , all services will be close + 'enable' => false, + # print log info to db. + 'dbprint' => false, + # print log info to front html + 'htmlprint' => false, + # print log + 'htmlprintbyparam' => [ + # like :http://fecshop.appfront.fancyecommerce.com/cn/?servicelog=xxxxxxxx + 'enable' => false, + 'paramKey' => 'servicelog', + 'paramVal' => 'xxxxxxxx', + ], + ], + ], + ], + 'errors' => [ + 'class' => 'fecshop\services\helper\Errors', + ], + 'mobileDetect' => [ + 'class' => 'fecshop\services\helper\MobileDetect', + ], + 'captcha' => [ + 'class' => 'fecshop\services\helper\Captcha', + 'charset' => '023456789', //随机因子 + 'codelen' => 4, //验证码长度 + 'width' => 130,//宽度 + 'height' => 50, //高度 + 'fontsize' => 20, //子体大小 + 'case_sensitive'=> false , // 是否区分大小写,false代表不区分 + ], + + 'country' => [ + 'class' => 'fecshop\services\helper\Country', + //'default_country' => 'US', + ], + + ], + ], +]; \ No newline at end of file diff --git a/config/services/Image.php b/config/services/Image.php new file mode 100644 index 000000000..27d749fb9 --- /dev/null +++ b/config/services/Image.php @@ -0,0 +1,24 @@ + [ + 'class' => 'fecshop\services\Image', + /* + 'imageFloder' => 'media/upload', + 'maxUploadMSize' => 2, // MB + 'allowImgType' => [ + 'image/jpeg', + 'image/gif', + 'image/png', + 'image/jpg', + 'image/pjpeg', + ], + */ + + ], +]; \ No newline at end of file diff --git a/config/services/Order.php b/config/services/Order.php new file mode 100644 index 000000000..08385aa2d --- /dev/null +++ b/config/services/Order.php @@ -0,0 +1,34 @@ + [ + 'class' => 'fecshop\services\Order', + 'paymentStatus' => [ + 'pending' => 'pending', #未付款订单状态 + 'processing' => 'processing' # 已付款订单状态 + ], + + 'requiredAddressAttr' => [ # 必填的订单字段。 + 'first_name', + 'last_name', + 'email', + 'telephone', + 'street1', + 'country', + 'city', + 'state', + 'zip' + ], + # 子服务 + 'childService' => [ + 'item' => [ + 'class' => 'fecshop\services\order\Item', + ], + ], + ], +]; diff --git a/config/services/Page.php b/config/services/Page.php new file mode 100644 index 000000000..66c012b1a --- /dev/null +++ b/config/services/Page.php @@ -0,0 +1,144 @@ + [ + 'class' => 'fecshop\services\Page', + //'terry' => 'xxxx', + # 子服务 + 'childService' => [ + 'breadcrumbs' => [ + 'class' => 'fecshop\services\page\Breadcrumbs', + 'homeName' => 'Home', # if homeName => '', Home will not show in breadcrums. + 'ifAddHomeUrl' => true, # default true, if set false, home will not add url (a). + //'intervalSymbol'=> ' >> ' # default value:' > ' + ], + 'translate' => [ + 'class' => 'fecshop\services\page\Translate', + ], + + 'asset' => [ + 'class' => 'fecshop\services\page\Asset', + /* js and css config example: + 'jsOptions' => [ + # js config 1 + [ + 'options' => [ + 'position' => 'POS_END', + // 'condition'=> 'lt IE 9', + ], + 'js' =>[ + 'js/jquery-3.0.0.min.js', + 'js/js.js', + ], + ], + # js config 2 + [ + 'options' => [ + 'condition'=> 'lt IE 9', + ], + 'js' =>[ + 'js/ie9js.js' + ], + ], + ], + # css config + 'cssOptions' => [ + # css config 1. + [ + 'css' =>[ + 'css/style.css', + 'css/ie.css', + ], + ], + + # css config 2. + [ + 'options' => [ + 'condition'=> 'lt IE 9', + ], + 'css' =>[ + 'css/ltie9.css', + ], + ], + ], + */ + ], + + 'theme' => [ + 'class' => 'fecshop\services\page\Theme', + /** + ** if you set value in config , it can not active ,it will be set value in store bootstrap. + * it will be set in store service bootstrop + 'localThemeDir' => '@appfront/theme/terry/theme01', + # it will be set in store service bootstrop + 'thirdThemeDir' => [], + # init in @fecshop/app/appName/modules/AppfrontController.php + # it will be set value in appfront controller init, it can not effect if you set value to it. + #'fecshopThemeDir' => '', + */ + ], + 'widget' => [ + 'class' => 'fecshop\services\page\Widget', + /* config example: + 'widgetConfig' => [ + + 'head' => [ + #'class' => 'fecshop\app\appfront\modules\Cms\block\widgets\Head', + # 根据多模板的优先级,依次去模板找查找该文件,直到找到这个文件。 + 'view' => 'widgets/head.php', + ], + 'header' => [ + 'class' => 'fecshop\app\appfront\modules\Cms\block\widgets\Headers', + # 根据多模板的优先级,依次去模板找查找该文件,直到找到这个文件。 + 'view' => 'widgets/header.php', + 'cache' => [ + 'enable' => false, + //'timeout' => 4500, + ], + ], + ], + */ + ], + 'currency' => [ + 'class' => 'fecshop\services\page\Currency', + /* currency config example: + 'baseCurrecy' => 'USD', # 产品的价格都使用基础货币填写价格值。 + 'defaultCurrency' => 'USD', # 如果store不设置货币,就使用这个store默认货币 + 'currencys' => [ + 'USD' => [ + 'rate' => 1, + 'symbol' => '$', + ], + 'RMB' => [ + 'rate' => 6.3, + 'symbol' => '¥', + ], + ], + */ + ], + + 'newsletter' => [ + 'class' => 'fecshop\services\page\Newsletter', + ], + + 'staticblock' => [ + 'class' => 'fecshop\services\page\StaticBlock', + ], + + 'menu' => [ + 'class' => 'fecshop\services\page\Menu', + + ], + 'message' => [ + 'class' => 'fecshop\services\page\Message', + + ], + + ], + ], +]; \ No newline at end of file diff --git a/config/services/Payment.php b/config/services/Payment.php new file mode 100644 index 000000000..610fe674e --- /dev/null +++ b/config/services/Payment.php @@ -0,0 +1,37 @@ + [ + 'class' => 'fecshop\services\Payment', + 'paymentConfig' => [ + 'standard' => [ + 'check_money' => [ + 'label' => 'Check / Money Order', + //'image' => ['images/mastercard.png','common'] ,# ֧ҳʾͼƬ + 'supplement' => 'Off-line Money Payments', # + 'style' => '', # css + 'start_url' => '@homeUrl/payment/checkmoney/start', + 'success_redirect_url' => '@homeUrl/payment/checkmoney/success', + ], + 'paypal_standard' => [ + 'label' => 'PayPal Website Payments Standard', + 'image' => ['images/paypal_standard.png','common'], # ֧ҳʾͼƬ + 'supplement' => 'You will be redirected to the PayPal website when you place an order. ', # + 'start_url' => '@homeUrl/payment/paypal/standard/start', + 'IPN_url' => '@homeUrl/payment/paypal/standard/ipn', + 'success_redirect_url' => '@homeUrl/payment/paypal/standard/success', + ], + ], + + 'express' => [ + + ], + + ] + ] +]; \ No newline at end of file diff --git a/config/services/Product.php b/config/services/Product.php new file mode 100644 index 000000000..68dd5ef29 --- /dev/null +++ b/config/services/Product.php @@ -0,0 +1,127 @@ + [ + 'class' => 'fecshop\services\Product', + /* + 'customAttrGroup' => [ + 'dress_group' => [ + 'dresses-length' => [ + 'dbtype' => 'Int', + 'label'=>'裙长', + 'name'=>'dresses-length', + 'display'=>[ + 'type' => 'inputString', + 'lang' => true, + ], + 'require' => 1, + ], + 'style-status' => [ + 'dbtype' => 'Int', + 'label'=>'分类状态', + 'name'=>'status', + 'display'=>[ + 'type' => 'select', + 'data' => [ + 1 => '激活', + 2 => '关闭', + ] + ], + 'require' => 1, + 'default' => 1, + ], + ], + + 'computer_group' => [ + 'memory_capacity' => [ + 'dbtype' => 'String', + 'label'=>'Memory Capacity', + 'name'=>'memory_capacity', + 'display'=>[ + 'type' => 'inputString', + 'lang' => true, + ], + 'require' => 1, + ], + 'cpu' => [ + 'dbtype' => 'Int', + 'label'=>'CPU型号', + 'name'=>'cpu', + 'display'=>[ + 'type' => 'select', + 'data' => [ + 1 => 'i3', + 2 => 'i5', + 3 => 'i7', + ] + ], + 'require' => 1, + 'default' => 1, + ], + ], + + + ], + */ + # 子服务 + 'childService' => [ + 'image' => [ + 'class' => 'fecshop\services\product\Image', + 'imageFloder' => 'media/catalog/product', + //'allowImgType' => ['image/jpeg','image/gif','image/png'], + 'maxUploadMSize'=> 5, #MB + + ], + 'price' => [ + 'class' => 'fecshop\services\product\Price', + 'ifSpecialPriceGtPriceFinalPriceEqPrice' => true, # 设置为true后,如果产品的special_price > price, 则 special_price无效,价格为price + ], + 'review' => [ + 'class' => 'fecshop\services\product\Review', + 'filterByLang' => false, # 是否通过语言进行评论过滤? + ], + 'favorite' => [ + 'class' => 'fecshop\services\product\Favorite', + ], + 'info' => [ + 'class' => 'fecshop\services\product\Info', + + ], + /* #暂时没用 + + 'coll' => [ + 'class' => 'fecshop\services\product\Coll', + //'numPerPage' => 50, # default + //'pageNum' => 1, # default + //'orderBy' => ['_id' => SORT_DESC ], # default + //'allowMaxPageNum' => 200, # default + ], + 'bestSell' => [ + 'class' => 'fecshop\services\product\BestSell', + ], + 'viewLog' => [ + 'class' => 'fecshop\services\product\ViewLog', + 'childService' => [ + 'session' => [ + 'class' => 'fecshop\services\product\viewLog\Session', + ], + 'db' =>[ + 'class' => 'fecshop\services\product\viewLog\Db', + //'table' => '', # custom table, you must create this mysql table before you use it. + ], + 'mongodb' =>[ + 'class' => 'fecshop\services\product\viewLog\Mongodb', + 'collection' => '', + ], + ], + + ], + */ + ], + ], +]; \ No newline at end of file diff --git a/config/services/Search.php b/config/services/Search.php new file mode 100644 index 000000000..343441060 --- /dev/null +++ b/config/services/Search.php @@ -0,0 +1,47 @@ + [ + 'class' => 'fecshop\services\Search', + /* example: + 'filterAttr' => [ + 'color','size', # 在搜索页面侧栏的搜索过滤属性字段 + ], + */ + 'childService' => [ + 'mongoSearch' => [ + 'class' => 'fecshop\services\search\MongoSearch', + 'searchIndexConfig' => [ + 'name' => 10, + 'description' => 5, + ], + #more : https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages + /* example: + 'searchLang' => [ + 'en' => 'english', + 'fr' => 'french', + 'de' => 'german', + 'es' => 'spanish', + 'ru' => 'russian', + 'pt' => 'portuguese', + ], + */ + ], + 'xunSearch' => [ + 'class' => 'fecshop\services\search\XunSearch', + 'searchIndexConfig' => [ + 'name' => 10, + 'description' => 5, + ], + 'fuzzy' => true, # 是否开启模糊查询 + 'synonyms' => true, #是否开启同义词翻译 + 'searchLang' => ['zh'], + ], + ], + ] +]; \ No newline at end of file diff --git a/config/services/Shipping.php b/config/services/Shipping.php new file mode 100644 index 000000000..eccd56ad6 --- /dev/null +++ b/config/services/Shipping.php @@ -0,0 +1,32 @@ + [ + 'class' => 'fecshop\services\Shipping', + # Shipping的运费,是表格的形式录入,shippingCsvDir是存放运费表格的文件路径。 + 'shippingCsvDir' => '@common/config/shipping', + 'shippingConfig'=>[ + 'free_shipping'=>[ # 免运费 + 'label'=>'Free shipping( 7-20 work days)', + 'name' => 'HKBRAM', + 'cost' => 0, + ], + 'fast_shipping'=>[ + 'label'=>'Fast Shipping( 5-10 work days)', + 'name' => 'HKDHL', + 'cost' => 'csv' # 请将文件名字的命名写入 fast_shipping.csv + + ], + ], + # 该值必须在上面的配置 $shippingConfig中存在,如果不存在,则返回为空。 + 'defaultShippingMethod' => [ + 'enable' => true, # 如果值为true,那么用户在cart生成的时候,就会填写上默认的货运方式。 + 'shipping' => 'free_shipping', + ], + ] +]; \ No newline at end of file diff --git a/config/services/Store.php b/config/services/Store.php new file mode 100644 index 000000000..4f2816c2a --- /dev/null +++ b/config/services/Store.php @@ -0,0 +1,29 @@ + [ + 'class' => 'fecshop\services\Store', + 'stores' => [ + /* + 'demo.www.fecshop.com' => [ + 'language' => 'en', + 'themePackage' => 'default', + 'theme' => 'default', + 'currency' => 'USD', + ], + 'admin.fancyecommerce.com' => [ + 'language' => 'cn', + 'themePackage' => 'default', + 'theme' => 'default', + 'currency' => 'RMB', + ], + */ + ], + + ], +]; \ No newline at end of file diff --git a/config/services/Systemhelper.php b/config/services/Systemhelper.php new file mode 100644 index 000000000..2a284d631 --- /dev/null +++ b/config/services/Systemhelper.php @@ -0,0 +1,13 @@ + [ + 'class' => 'fecshop\services\Systemhelper', + ], +]; \ No newline at end of file diff --git a/config/services/Url.php b/config/services/Url.php new file mode 100644 index 000000000..2974e9e94 --- /dev/null +++ b/config/services/Url.php @@ -0,0 +1,25 @@ + [ + 'class' => 'fecshop\services\Url', + 'showScriptName'=> true, # if is show index.php in url. if set false ,you must config nginx rewrite + 'randomCount'=> 8, # if url key is exist in url write table , add a random string behide the url key, this param is define random String length + # 子服务 + 'childService' => [ + 'rewrite' => [ + 'class' => 'fecshop\services\url\Rewrite', + 'storage' => 'mongodb', + ], + 'category' => [ + 'class' => 'fecshop\services\url\Category', + + ], + ], + ], +]; \ No newline at end of file diff --git a/config/xunsearch/product.ini b/config/xunsearch/product.ini new file mode 100644 index 000000000..e69de29bb diff --git a/config/xunsearch/search.ini b/config/xunsearch/search.ini new file mode 100644 index 000000000..6f2063be8 --- /dev/null +++ b/config/xunsearch/search.ini @@ -0,0 +1,79 @@ +project.name = search +project.default_charset = UTF-8 +;服务端用默认值 +;server.index = 8383 +;server.search = 8384 + +[_id] +type = id + +[name] +type = title + +[description] +type = body + +[sku] +type = string +index = mixed + +[spu] +type = string +index = mixed + +[score] +type = numeric +index = none + +[url_key] +type = string +index = none + +[price] +type = numeric +index = none + +[cost_price] +type = numeric +index = none + +[special_price] +type = numeric +index = none + +[special_from] +type = numeric +index = none + +[special_to] +type = numeric +index = none + +[final_price] +type = numeric +index = none + +[image] +type = string +index = none + +[short_description] +type = string +index = none + +[created_at] +type = numeric +index = none + +[sync_updated_at] +type = numeric +index = none + +[color] +type = string +index = self + +[size] +type = string +index = self + \ No newline at end of file diff --git a/initFecShop b/initFecShop new file mode 100755 index 000000000..b9f671300 --- /dev/null +++ b/initFecShop @@ -0,0 +1,223 @@ +#!/usr/bin/env php + + * + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +if (!extension_loaded('openssl')) { + die('The OpenSSL PHP extension is required by Yii2.'); +} + +$params = getParams(); +$root = str_replace('\\', '/', __DIR__); +$root = "$root/../../.."; +$envs = require("$root/vendor/fancyecommerce/fecshop/environments/index.php"); +$envNames = array_keys($envs); + +echo "Yii Application Initialization Tool v1.0\n\n"; + +$envName = null; +if (empty($params['env']) || $params['env'] === '1') { + /* + echo "Which environment do you want the application to be initialized in?\n\n"; + foreach ($envNames as $i => $name) { + echo " [$i] $name\n"; + } + echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; + $answer = trim(fgets(STDIN)); + + if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) { + echo "\n Quit initialization.\n"; + exit(0); + } + + if (isset($envNames[$answer])) { + $envName = $envNames[$answer]; + } + */ + $envName = 'prod'; +} else { + $envName = $params['env']; +} + +if (!in_array($envName, $envNames)) { + $envsList = implode(', ', $envNames); + echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n"; + exit(2); +} + +$env = $envs[$envName]; + +if (empty($params['env'])) { + echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; + $answer = trim(fgets(STDIN)); + if (strncasecmp($answer, 'y', 1)) { + echo "\n Quit initialization.\n"; + exit(0); + } +} + +echo "\n Start initialization ...\n\n"; +$files = getFileList("$root/vendor/fancyecommerce/fecshop/environments/{$env['path']}"); +var_dump($files);exit; +if (isset($env['skipFiles'])) { + $skipFiles = $env['skipFiles']; + array_walk($skipFiles, function(&$value) use($env, $root) { $value = "$root/$value"; }); + $files = array_diff($files, array_intersect_key($env['skipFiles'], array_filter($skipFiles, 'file_exists'))); +} +$all = false; +foreach ($files as $file) { + if (!copyFile($root, "vendor/fancyecommerce/fecshop/environments/{$env['path']}/$file", $file, $all, $params)) { + break; + } +} + +$callbacks = ['setCookieValidationKey', 'setWritable', 'setExecutable', 'createSymlink']; +foreach ($callbacks as $callback) { + if (!empty($env[$callback])) { + $callback($root, $env[$callback]); + } +} + +echo "\n ... initialization completed.\n\n"; + +function getFileList($root, $basePath = '') +{ + $files = []; + $handle = opendir($root); + while (($path = readdir($handle)) !== false) { + if ($path === '.git' || $path === '.svn' || $path === '.' || $path === '..') { + continue; + } + $fullPath = "$root/$path"; + $relativePath = $basePath === '' ? $path : "$basePath/$path"; + if (is_dir($fullPath)) { + $files = array_merge($files, getFileList($fullPath, $relativePath)); + } else { + $files[] = $relativePath; + } + } + closedir($handle); + return $files; +} + +function copyFile($root, $source, $target, &$all, $params) +{ + if (!is_file($root . '/' . $source)) { + echo " skip $target ($source not exist)\n"; + return true; + } + if (is_file($root . '/' . $target)) { + if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { + echo " unchanged $target\n"; + return true; + } + if ($all) { + echo " overwrite $target\n"; + } else { + echo " exist $target\n"; + echo " ...overwrite? [Yes|No|All|Quit] "; + + + $answer = !empty($params['overwrite']) ? $params['overwrite'] : trim(fgets(STDIN)); + if (!strncasecmp($answer, 'q', 1)) { + return false; + } else { + if (!strncasecmp($answer, 'y', 1)) { + echo " overwrite $target\n"; + } else { + if (!strncasecmp($answer, 'a', 1)) { + echo " overwrite $target\n"; + $all = true; + } else { + echo " skip $target\n"; + return true; + } + } + } + } + file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); + return true; + } + echo " generate $target\n"; + @mkdir(dirname($root . '/' . $target), 0777, true); + file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); + return true; +} + +function getParams() +{ + $rawParams = []; + if (isset($_SERVER['argv'])) { + $rawParams = $_SERVER['argv']; + array_shift($rawParams); + } + + $params = []; + foreach ($rawParams as $param) { + if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { + $name = $matches[1]; + $params[$name] = isset($matches[3]) ? $matches[3] : true; + } else { + $params[] = $param; + } + } + return $params; +} + +function setWritable($root, $paths) +{ + foreach ($paths as $writable) { + if (is_dir("$root/$writable")) { + echo " chmod 0777 $writable\n"; + @chmod("$root/$writable", 0777); + } else { + echo "\n Error. Directory $writable does not exist. \n"; + } + } +} + +function setExecutable($root, $paths) +{ + foreach ($paths as $executable) { + echo " chmod 0755 $executable\n"; + @chmod("$root/$executable", 0755); + } +} + +function setCookieValidationKey($root, $paths) +{ + foreach ($paths as $file) { + echo " generate cookie validation key in $file\n"; + $file = $root . '/' . $file; + $length = 32; + $bytes = openssl_random_pseudo_bytes($length); + $key = strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.'); + $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($file)); + file_put_contents($file, $content); + } +} + +function createSymlink($root, $links) { + foreach ($links as $link => $target) { + echo " symlink " . $root . "/" . $target . " " . $root . "/" . $link . "\n"; + //first removing folders to avoid errors if the folder already exists + @rmdir($root . "/" . $link); + //next removing existing symlink in order to update the target + if (is_link($root . "/" . $link)) { + @unlink($root . "/" . $link); + } + @symlink($root . "/" . $target, $root . "/" . $link); + } +} + diff --git a/interfaces/block/BlockCache.php b/interfaces/block/BlockCache.php new file mode 100644 index 000000000..fadc64226 --- /dev/null +++ b/interfaces/block/BlockCache.php @@ -0,0 +1,19 @@ + + * @since 1.0 + */ +interface BlockCache{ + const BLOCK_CACHE_PREFIX = 'block_cache'; + public function getCacheKey(); + +} \ No newline at end of file diff --git a/migrations/READ.me b/migrations/READ.me new file mode 100644 index 000000000..cec6ad6ed --- /dev/null +++ b/migrations/READ.me @@ -0,0 +1 @@ +read me \ No newline at end of file diff --git a/migrations/db/product/log/m160607_052627_log_product_view.php b/migrations/db/product/log/m160607_052627_log_product_view.php new file mode 100644 index 000000000..9abc5caeb --- /dev/null +++ b/migrations/db/product/log/m160607_052627_log_product_view.php @@ -0,0 +1,46 @@ +execute($sql1); + + $sql2 = " + ALTER TABLE `log_product_view` ADD INDEX (`user_id` , `date_time` ) ; + "; + $this->execute($sql2); + + } + + public function down() + { + echo "m160607_052627_log_product_view cannot be reverted.\n"; + + return false; + } + + /* + // Use safeUp/safeDown to run migration code within a transaction + public function safeUp() + { + } + + public function safeDown() + { + } + */ +} diff --git a/migrations/mongodb/product/log/m160608_062040_mongodb_log_product_view.php b/migrations/mongodb/product/log/m160608_062040_mongodb_log_product_view.php new file mode 100644 index 000000000..e344b25ea --- /dev/null +++ b/migrations/mongodb/product/log/m160608_062040_mongodb_log_product_view.php @@ -0,0 +1,17 @@ +createIndex('log_product_view', $columns); + } + + public function down() + { + echo "m160608_062040_mongodb_log_product_view cannot be reverted.\n"; + + return false; + } +} diff --git a/migrations/mongodb/urlwrite/m160608_061933_mongodb_url_write.php b/migrations/mongodb/urlwrite/m160608_061933_mongodb_url_write.php new file mode 100644 index 000000000..9f7cd02b8 --- /dev/null +++ b/migrations/mongodb/urlwrite/m160608_061933_mongodb_url_write.php @@ -0,0 +1,22 @@ +createIndex('url_rewrite', $columns); + $columns = ['type']; + $this->createIndex('url_rewrite', $columns); + + } + + public function down() + { + echo "m160608_061933_mongodb_url_write cannot be reverted.\n"; + + return false; + } +} diff --git a/models/mongodb/Category.php b/models/mongodb/Category.php new file mode 100644 index 000000000..e27374d08 --- /dev/null +++ b/models/mongodb/Category.php @@ -0,0 +1,70 @@ + + * @since 1.0 + */ +class Category extends ActiveRecord +{ + + public static function collectionName() + { + return 'category'; + } + + public function attributes() + { + return [ + '_id', + 'parent_id', + 'name', + 'status', + 'url_key', + 'level', + 'thumbnail_image', + 'image', + 'filter_product_attr_selected', + 'filter_product_attr_unselected', + 'description', + 'menu_custom', + 'title', + 'meta_description', + 'meta_keywords', + 'include_in_menu', + 'is_feature', + 'available_sort_by', + 'default_sort_by', + 'theme', + 'active_from', + 'active_to', + 'created_at', + 'updated_at', + 'created_user_id', + //other + /* + category filter + category product + + + */ + ]; + } + + + + + + + + + +} \ No newline at end of file diff --git a/models/mongodb/CategoryProduct.php b/models/mongodb/CategoryProduct.php new file mode 100644 index 000000000..8681b3be9 --- /dev/null +++ b/models/mongodb/CategoryProduct.php @@ -0,0 +1,45 @@ + + * @since 1.0 + */ +class CategoryProduct extends ActiveRecord +{ + + + public static function collectionName() + { + return '{{%category_product}}'; + } + + + + + public function attributes() + { + return [ + '_id', + 'product_id', + 'category_id', + ]; + } + + + + + + + + + +} \ No newline at end of file diff --git a/models/mongodb/FecshopServiceLog.php b/models/mongodb/FecshopServiceLog.php new file mode 100644 index 000000000..337779abe --- /dev/null +++ b/models/mongodb/FecshopServiceLog.php @@ -0,0 +1,42 @@ + + * @since 1.0 + */ +class FecshopServiceLog extends ActiveRecord +{ + + + public static function collectionName() + { + return 'fecshop_service_log'; + } + + + + + public function attributes() + { + return [ + '_id', + 'service_file', + 'service_method', + 'service_method_argument', + 'begin_microtime', + 'end_microtime', + 'used_time', + 'process_date_time', + ]; + } + +} \ No newline at end of file diff --git a/models/mongodb/Newsletter.php b/models/mongodb/Newsletter.php new file mode 100644 index 000000000..394db66bf --- /dev/null +++ b/models/mongodb/Newsletter.php @@ -0,0 +1,82 @@ + + * @since 1.0 + */ + +class Newsletter extends ActiveRecord +{ + + + public static function collectionName() + { + return 'newsletter'; + } + + + public function attributes() + { + return [ + '_id', + 'email', + 'created_at', + 'send_mail_count' + ]; + } + + public function rules() + { + $parent_rules = parent::rules(); + $current_rules = [ + ['email', 'filter', 'filter' => 'trim'], + ['email', 'email'], + // ['email', 'validateEmail'], + ]; + return array_merge($parent_rules,$current_rules) ; + } + /* + public function validateEmail($attribute, $params){ + //$user = User::findByUsername($this->username) + if($this->_id){ + $one = Newsletter::find()->where('<>','_id',$this->_id) + ->andWhere('email' => $this->email) + ->one(); + if($one['id']){ + $this->addError($attribute,"the email is exist,you can not change!"); + } + }else{ + $one = Newsletter::find()->where('email' => $this->email) + ->one(); + if($one['id']){ + $this->addError($attribute,"the email is subscription by other"); + } + } + + } + */ + + public function beforeSave($insert) + { + if (parent::beforeSave($insert)) { + $now_date = CDate::getCurrentDateTime(); + if($insert == self::EVENT_BEFORE_INSERT) + $this->created_at = $now_date; + $this->updated_at = $now_date; + return true; + } else { + return false; + } + } + +} \ No newline at end of file diff --git a/models/mongodb/Product.php b/models/mongodb/Product.php new file mode 100644 index 000000000..f685f7e27 --- /dev/null +++ b/models/mongodb/Product.php @@ -0,0 +1,108 @@ + + * @since 1.0 + */ +class Product extends ActiveRecord +{ + public static $_customProductAttrs; + + /** + * 需要做的索引 [sku],[spu], + * [category_id,score],[category_id,created_at] + * [category_id,price] + */ + + + public static function collectionName() + { + return 'product_flat'; + } + + /** + * get custom product attrs. + */ + public static function addCustomProductAttrs($attrs){ + + self::$_customProductAttrs = $attrs; + } + + + public function attributes() + { + $origin = [ + '_id', + 'name', + 'spu', + 'sku', + 'weight', + 'score', + 'status', + 'qty', + 'is_in_stock', + 'visibility', + 'url_key', + //'url_path', + 'category', + 'price', + 'cost_price', + 'special_price', + 'special_from', + 'special_to', + 'tier_price', + 'final_price', # 算出来的最终价格。这个通过脚本赋值。 + 'new_product_from', + 'new_product_to', + 'freeshipping', + 'featured', + 'upc', + 'meta_title', + 'meta_keywords', + 'meta_description', + 'image', + 'sell_7_count', + 'sell_30_count', + 'sell_90_count', + 'description', + 'short_description', + 'custom_option', + 'remark', + 'created_at', + 'updated_at', + 'created_user_id', + 'attr_group', + 'reviw_rate_star_average', #评论平均评分 + 'review_count', #评论总数 + 'reviw_rate_star_average_lang', #(语言)评论平均评分 + 'review_count_lang', #(语言)评论总数 + 'favorite_count', # 产品被收藏的次数。 + 'relation_sku', # 相关产品 + 'buy_also_buy_sku', # 买了的还买了什么 + 'see_also_see_sku', # 看了的还看了什么 + + ]; + if(is_array(self::$_customProductAttrs) && !empty(self::$_customProductAttrs)){ + $origin = array_merge($origin,self::$_customProductAttrs); + } + return $origin; + } + + + + + + + + + +} \ No newline at end of file diff --git a/models/mongodb/Search.php b/models/mongodb/Search.php new file mode 100644 index 000000000..ded128d51 --- /dev/null +++ b/models/mongodb/Search.php @@ -0,0 +1,75 @@ + + * @since 1.0 + */ +class Search extends ActiveRecord +{ + /** + * ԣʹmodel֮ǰԣ򱨴 + */ + public static $_lang; + public static $_filterColumns; + + public static function collectionName() + { + if(self::$_lang){ + return 'full_search_product_'.self::$_lang; + }else{ + //throw new InvalidValueException('search class $_lang is empty, you must set search model class variable $_lang before use it'); + return 'full_search_product_no_lang'; + } + } + + + + public function attributes() + { + $origin = [ + '_id', + 'name', + 'spu', + 'sku', + 'score', + 'status', + 'is_in_stock', + 'url_key', + 'price', + 'cost_price', + 'special_price', + 'special_from', + 'special_to', + 'final_price', # ռ۸ͨűֵ + 'image', + 'short_description', + 'description', + 'created_at', + 'sync_updated_at', # ͬƷϢʱ + ]; + if(is_array(self::$_filterColumns) && !empty(self::$_filterColumns)){ + $origin = array_merge($origin,self::$_filterColumns); + $origin = array_unique($origin); + } + return $origin; + } + + + + + + + + + +} \ No newline at end of file diff --git a/models/mongodb/UrlRewrite.php b/models/mongodb/UrlRewrite.php new file mode 100644 index 000000000..4fbf0644c --- /dev/null +++ b/models/mongodb/UrlRewrite.php @@ -0,0 +1,37 @@ + + * @since 1.0 + */ + +class UrlRewrite extends ActiveRecord +{ + + + public static function collectionName() + { + return 'url_rewrite'; + } + + + public function attributes() + { + return [ + '_id', + 'type', + 'custom_url_key', + 'origin_url', + 'status' + ]; + } +} \ No newline at end of file diff --git a/models/mongodb/cms/Article.php b/models/mongodb/cms/Article.php new file mode 100644 index 000000000..3a89ad671 --- /dev/null +++ b/models/mongodb/cms/Article.php @@ -0,0 +1,41 @@ + + * @since 1.0 + */ +class Article extends ActiveRecord +{ + + + public static function collectionName() + { + return 'article'; + } + + + public function attributes() + { + return [ + '_id', + 'url_key', + 'title', + 'meta_keywords', + 'meta_description', + 'content', + 'status', + 'created_at', + 'updated_at', + 'created_user_id', + ]; + } +} \ No newline at end of file diff --git a/models/mongodb/cms/StaticBlock.php b/models/mongodb/cms/StaticBlock.php new file mode 100644 index 000000000..52a6d4a34 --- /dev/null +++ b/models/mongodb/cms/StaticBlock.php @@ -0,0 +1,39 @@ + + * @since 1.0 + */ +class StaticBlock extends ActiveRecord +{ + + + public static function collectionName() + { + return 'static_block'; + } + + + public function attributes() + { + return [ + '_id', + 'title', + 'identify', + 'status', + 'content', + 'created_at', + 'updated_at', + 'created_user_id', + ]; + } +} \ No newline at end of file diff --git a/models/mongodb/customer/Newsletter.php b/models/mongodb/customer/Newsletter.php new file mode 100644 index 000000000..73df42b27 --- /dev/null +++ b/models/mongodb/customer/Newsletter.php @@ -0,0 +1,40 @@ + + * @since 1.0 + */ +class Newsletter extends ActiveRecord +{ + const ENABLE_STATUS = 1; + const DISABLE_STATUS= 10; + + public static function collectionName() + { + return 'newsletter'; + } + + public function attributes() + { + return [ + '_id', + 'email', + 'created_at', + 'status', + + ]; + } + + public static function primaryKey(){ + return '_id'; + } +} \ No newline at end of file diff --git a/models/mongodb/product/Favorite.php b/models/mongodb/product/Favorite.php new file mode 100644 index 000000000..2b16f96a4 --- /dev/null +++ b/models/mongodb/product/Favorite.php @@ -0,0 +1,48 @@ + + * @since 1.0 + */ +class Favorite extends ActiveRecord +{ + + + public static function collectionName() + { + return 'favorite'; + } + + + public function attributes() + { + $origin = [ + '_id', + 'product_id', # Ʒid ַ + 'user_id', # ûid int + 'created_at', # ʱ int + 'updated_at', # ʱ int + 'store' # Store ǰstore + ]; + + return $origin; + } + + + + + + + + + +} \ No newline at end of file diff --git a/models/mongodb/product/Review.php b/models/mongodb/product/Review.php new file mode 100644 index 000000000..d2213ae40 --- /dev/null +++ b/models/mongodb/product/Review.php @@ -0,0 +1,72 @@ + + * @since 1.0 + */ +class Review extends ActiveRecord +{ + public static $_customAttrs; + # Ĭ״̬Ҳûۺ״̬ǰǿͻϢҪ˵ǰ£ͻϢҪ˵ĻACTIVE_STATUS + const NOACTIVE_STATUS = 10; + # ͨ״̬ + const ACTIVE_STATUS = 1; + # ˾ܾ״̬ + const REFUSE_STATUS = 2; + + public static function collectionName() + { + return 'review'; + } + # ֶ̬Ρ + public static function addCustomAttrs($attrs){ + self::$_customAttrs = $attrs; + } + + public function attributes($origin=false) + { + $origin = [ + '_id', + 'product_spu', + 'product_sku', + 'product_id', + 'rate_star', + 'name', + 'user_id', + 'ip', + 'summary', + 'review_content', # ۵ + 'review_date', # ۵ʱ + 'store', # store + 'lang_code', # + 'status', # ״̬ 10δˣ1ˡ + 'audit_user', # ˺ + 'audit_date', # ʱ + ]; + if($origin){ # ȡԭʼ + return $origin; + } + if(is_array(self::$_customAttrs) && !empty(self::$_customAttrs)){ + $origin = array_merge($origin,self::$_customAttrs); + } + return $origin; + } + + + + + + + + + +} \ No newline at end of file diff --git a/models/mongodb/product/ViewLog.php b/models/mongodb/product/ViewLog.php new file mode 100644 index 000000000..220a841c2 --- /dev/null +++ b/models/mongodb/product/ViewLog.php @@ -0,0 +1,39 @@ + + * @since 1.0 + */ +class ViewLog extends ActiveRecord +{ + public static $_collectionName; + + public static function collectionName() + { + return self::$_collectionName; + } + + public static function setCurrentCollectionName($name){ + self::$_collectionName = $name; + } + + + public function attributes() + { + return [ + '_id', 'date_time', + 'product_id', + 'sku', 'image' , + 'name', 'user_id' + ]; + } +} \ No newline at end of file diff --git a/models/mongodb/url/UrlRewrite.php b/models/mongodb/url/UrlRewrite.php new file mode 100644 index 000000000..93a520fb5 --- /dev/null +++ b/models/mongodb/url/UrlRewrite.php @@ -0,0 +1,39 @@ + + * @since 1.0 + */ + +class UrlRewrite extends ActiveRecord +{ + + + public static function collectionName() + { + return 'url_rewrite'; + } + + + public function attributes() + { + return [ + '_id', + 'type', + 'custom_url_key', + 'origin_url', + 'status', + 'updated_at', + 'created_at', + ]; + } +} \ No newline at end of file diff --git a/models/mysqldb/Cart.php b/models/mysqldb/Cart.php new file mode 100644 index 000000000..601952426 --- /dev/null +++ b/models/mysqldb/Cart.php @@ -0,0 +1,29 @@ + + * @since 1.0 + */ + +class Cart extends ActiveRecord +{ + + public static function tableName() + { + return 'sales_flat_cart'; + } + + + + + +} diff --git a/models/mysqldb/Customer.php b/models/mysqldb/Customer.php new file mode 100644 index 000000000..6e38768ce --- /dev/null +++ b/models/mysqldb/Customer.php @@ -0,0 +1,190 @@ + + * @since 1.0 + */ + +class Customer extends ActiveRecord implements IdentityInterface +{ + const STATUS_DELETED = 10; + const STATUS_ACTIVE = 1; + + public static function tableName() + { + return 'customer'; + } + + public function rules() + { + return [ + ['status', 'default', 'value' => self::STATUS_ACTIVE], + ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]], + ]; + } + + /** + * @inheritdoc + */ + # ͨid ҵidentity + public static function findIdentity($id) + { + return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]); + } + + /** + * @inheritdoc + */ + # ͨaccess_token ҵidentity + public static function findIdentityByAccessToken($token, $type = null) + { + return static::findOne(['access_token' => $token, 'status' => self::STATUS_ACTIVE]); + } + # access_token + public function generateAccessToken() + { + $this->access_token = Yii::$app->security->generateRandomString(); + } + + /** + * Finds user by username + * + * @param string $username + * @return static|null + */ + public static function findByEmail($email) + { + return static::findOne(['email' => $email, 'status' => self::STATUS_ACTIVE]); + } + + /** + * Finds user by password reset token + * + * @param string $token password reset token + * @return static|null + */ + # ˴ʹõ + public static function findByPasswordResetToken($token) + { + if (!static::isPasswordResetTokenValid($token)) { + return null; + } + + return static::findOne([ + 'password_reset_token' => $token, + 'status' => self::STATUS_ACTIVE, + ]); + } + + /** + * Finds out if password reset token is valid + * + * @param string $token password reset token + * @return boolean + */ + public static function isPasswordResetTokenValid($token) + { + if (empty($token)) { + return false; + } + + $timestamp = (int) substr($token, strrpos($token, '_') + 1); + $expire = Yii::$app->params['user.passwordResetTokenExpire']; + return $timestamp + $expire >= time(); + } + + /** + * @inheritdoc + */ + public function getId() + { + return $this->getPrimaryKey(); + } + + /** + * @inheritdoc + */ + public function getAuthKey() + { + return $this->auth_key; + } + + /** + * @inheritdoc + */ + public function validateAuthKey($authKey) + { + return $this->getAuthKey() === $authKey; + } + + /** + * Validates password + * + * @param string $password password to validate + * @return boolean if password provided is valid for current user + */ + public function validatePassword($password) + { + return Yii::$app->security->validatePassword($password, $this->password_hash); + } + + /** + * Generates password hash from password and sets it to the model + * + * @param string $password + */ + public function setPassword($password) + { + $this->password_hash = Yii::$app->security->generatePasswordHash($password); + } + + /** + * Generates "remember me" authentication key + */ + public function generateAuthKey() + { + $this->auth_key = Yii::$app->security->generateRandomString(); + } + + /** + * Generates new password reset token + */ + public function generatePasswordResetToken() + { + $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); + } + + /** + * Removes password reset token + */ + public function removePasswordResetToken() + { + $this->password_reset_token = null; + } + + +} diff --git a/models/mysqldb/Order.php b/models/mysqldb/Order.php new file mode 100644 index 000000000..32bdcb835 --- /dev/null +++ b/models/mysqldb/Order.php @@ -0,0 +1,29 @@ + + * @since 1.0 + */ + +class Order extends ActiveRecord +{ + + public static function tableName() + { + return 'sales_flat_order'; + } + + + + + +} diff --git a/models/mysqldb/UrlRewrite.php b/models/mysqldb/UrlRewrite.php new file mode 100644 index 000000000..8cb2943cd --- /dev/null +++ b/models/mysqldb/UrlRewrite.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ + +class UrlRewrite extends ActiveRecord +{ + + public static function tableName() + { + return 'url_rewrite'; + } + + + + +} diff --git a/models/mysqldb/cart/Coupon.php b/models/mysqldb/cart/Coupon.php new file mode 100644 index 000000000..40c0ccc46 --- /dev/null +++ b/models/mysqldb/cart/Coupon.php @@ -0,0 +1,25 @@ + + * @since 1.0 + */ +class Coupon extends ActiveRecord +{ + + public static function tableName() + { + return 'sales_coupon'; + } + + +} diff --git a/models/mysqldb/cart/CouponUsage.php b/models/mysqldb/cart/CouponUsage.php new file mode 100644 index 000000000..a63995bf9 --- /dev/null +++ b/models/mysqldb/cart/CouponUsage.php @@ -0,0 +1,30 @@ + + * @since 1.0 + */ +class CouponUsage extends ActiveRecord +{ + + public static function tableName() + { + return 'sales_coupon_usage'; + } + + +} + + + + + diff --git a/models/mysqldb/cart/Item.php b/models/mysqldb/cart/Item.php new file mode 100644 index 000000000..44345f7d7 --- /dev/null +++ b/models/mysqldb/cart/Item.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ + +class Item extends ActiveRecord +{ + + public static function tableName() + { + return 'sales_flat_cart_item'; + } + + + + +} diff --git a/models/mysqldb/cms/Article.php b/models/mysqldb/cms/Article.php new file mode 100644 index 000000000..2a0991312 --- /dev/null +++ b/models/mysqldb/cms/Article.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ + +class Article extends ActiveRecord +{ + + public static function tableName() + { + return 'article'; + } + + + + +} diff --git a/models/mysqldb/cms/StaticBlock.php b/models/mysqldb/cms/StaticBlock.php new file mode 100644 index 000000000..eb8d6021c --- /dev/null +++ b/models/mysqldb/cms/StaticBlock.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ + +class StaticBlock extends ActiveRecord +{ + + public static function tableName() + { + return 'static_block'; + } + + + + +} diff --git a/models/mysqldb/customer/Address.php b/models/mysqldb/customer/Address.php new file mode 100644 index 000000000..6cca12008 --- /dev/null +++ b/models/mysqldb/customer/Address.php @@ -0,0 +1,30 @@ + + * @since 1.0 + */ + +class Address extends ActiveRecord +{ + const STATUS_DELETED = 10; + const STATUS_ACTIVE = 1; + + public static function tableName() + { + return 'customer_address'; + } + + +} diff --git a/models/mysqldb/customer/CustomerLogin.php b/models/mysqldb/customer/CustomerLogin.php new file mode 100644 index 000000000..bfa8ed922 --- /dev/null +++ b/models/mysqldb/customer/CustomerLogin.php @@ -0,0 +1,80 @@ + + * @since 1.0 + */ +class CustomerLogin extends Model { + + public $email; + public $password; + //public $captcha; + private $_customer; + public function rules() + { + return [ + [['email', 'password'], 'required'], + ['email','email'], + ['password', 'validatePassword'], + ]; + } + + public function validatePassword($attribute,$params){ + + if (!$this->hasErrors()) { + $customer = $this->getCustomer(); + if (!$customer) { + $this->addError($attribute,'email is not exist'); + }else if(!$customer->validatePassword($this->password)){ + $this->addError($attribute,'user password is not correct'); + } + } + } + + + public function getCustomer(){ + if($this->_customer === null){ + $this->_customer = Customer::findByEmail($this->email); + } + return $this->_customer; + + } + /** + * @property $duration | Int + * 对于参数$duration: + * 1. 当不开启cookie时,$duration的设置是无效的,yii2只会从user组件Yii::$app->user->authTimeout + * 中读取过期时间 + * 2. 当开启cookie,$duration是有效的,会设置cookie的过期时间。 + * 如果不传递时间,默认使用 Yii::$app->session->timeout的值。 + * 总之,为了方便处理cookie和session的超时时间,统一使用 + * session的超时时间,这样做的好处为,可以让account 和 cart session的超时时间保持一致 + */ + public function login($duration = 0) + { + if(!$duration){ + if(Yii::$app->session->timeout){ + $duration = Yii::$app->session->timeout; + } + } + if ($this->validate()) { + //return \Yii::$app->user->login($this->getAdminUser(), $this->rememberMe ? 3600 * 24 * 30 : 0); + return \Yii::$app->user->login($this->getCustomer(), $duration); + } else { + return false; + } + } +} + + + + diff --git a/models/mysqldb/customer/CustomerRegister.php b/models/mysqldb/customer/CustomerRegister.php new file mode 100644 index 000000000..9404e873b --- /dev/null +++ b/models/mysqldb/customer/CustomerRegister.php @@ -0,0 +1,122 @@ + + * @since 1.0 + */ +class CustomerRegister extends Customer { + private $_admin_user; + + private $_rules; + + public function setCustomerRules($rules){ + $this->_rules = $rules; + } + + public function rules() + { + + + $parent_rules = parent::rules(); + $current_rules = [ + + ['email', 'filter', 'filter' => 'trim'], + ['email','email'], + ['email','validateEmail'], + + ['password', 'filter', 'filter' => 'trim'], + + ['firstname', 'filter', 'filter' => 'trim'], + ['lastname' , 'filter', 'filter' => 'trim'], + ['is_subscribed', 'validateIsSubscribed'], + // ['email', 'required'], + // ['email', 'email'], + // ['email', 'string', 'max' => 255], + // ['email', 'validateEmail'], + // ['code', 'string', 'min' => 5, 'max' => 5], + + // ['role', 'required'], + + // ['person', 'required'], + + // ['email', 'unique', 'targetClass' => '\fecadmin\models\AdminUser', 'message' => 'This email address has already been taken.'], + + // ['password', 'required'], + // ['password', 'string', 'min' => 6], + + ]; + + + $rules = array_merge($parent_rules,$current_rules) ; + if(is_array($this->_rules)){ + $rules = array_merge($rules,$this->_rules) ; + } + return $rules; + } + + public function validateIsSubscribed($attribute, $params){ + if($this->is_subscribed != 1){ + $this->is_subscribed = 2; + } + } + + + + public function validateEmail($attribute, $params){ + if($this->id){ + $one = Customer::find() + ->where(" id != :id AND email = :email ",[':id'=>$this->id,':email'=>$this->email]) + ->one(); + if($one['id']){ + $this->addError($attribute,"this email is exist!"); + } + + }else{ + $one = Customer::find() + ->where('email = :email',[':email' => $this->email]) + ->one(); + if($one['id']){ + $this->addError($attribute,"this email is exist!"); + } + } + } + + + + public function setPassword($password) + { + if($this->password){ + $this->password_hash = \Yii::$app->security->generatePasswordHash($password); + $this->password = ''; + } + } + + # 重写保存方法 + public function save($runValidation = true, $attributeNames = NULL){ + + + # 如果auth_key为空,则重置 + if(!$this->auth_key){ + $this->generateAuthKey(); + } + # 如果access_token为空,则重置 + if(!$this->access_token){ + $this->generateAccessToken(); + } + # 设置password + $this->setPassword($this->password); + parent::save($runValidation,$attributeNames); + } +} + + + + diff --git a/models/mysqldb/customer/CustomerResetPassword.php b/models/mysqldb/customer/CustomerResetPassword.php new file mode 100644 index 000000000..eb8093e1a --- /dev/null +++ b/models/mysqldb/customer/CustomerResetPassword.php @@ -0,0 +1,75 @@ + + * @since 1.0 + */ +class CustomerResetPassword extends Customer { + + public $username; + public $old_password; + public $new_password; + public $password_repeat; + private $_admin_user; + + public function rules() + { + return [ + [['old_password', 'new_password','password_repeat'], 'required'], + // ['username', 'validateLogin'], + ['new_password', 'validateNewPassword'], + ['old_password', 'validateOldPassword'], + ]; + } + + public function getAdminUser(){ + if($this->_admin_user === null){ + $this->_admin_user = Yii::$app->user->identity; + } + return $this->_admin_user; + } + + + public function updatePassword(){ + $AdminUser = $this->getAdminUser(); + $AdminUser->setPassword($this->new_password); + $AdminUser->save(); + } + + + public function validateNewPassword($attribute,$params){ + + if (!$this->hasErrors()) { + + if($this->new_password != $this->password_repeat){ + $this->addError($attribute, 'Password and PasswordRepeat is Inconsistent!'); + return; + } + + } + } + + public function validateOldPassword($attribute,$params){ + + if (!$this->hasErrors()) { + $username = $this->getAdminUser()->username; + $AdminUser = AdminUser::findByUsername($username); + if($AdminUser->validatePassword($this->old_password)){ + + }else{ + $this->addError($attribute, 'old password is not right!'); + } + } + } + + + +} diff --git a/models/mysqldb/order/Item.php b/models/mysqldb/order/Item.php new file mode 100644 index 000000000..8b3afc750 --- /dev/null +++ b/models/mysqldb/order/Item.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ + +class Item extends ActiveRecord +{ + + public static function tableName() + { + return 'sales_flat_order_item'; + } + + + + +} diff --git a/models/mysqldb/product/ViewLog.php b/models/mysqldb/product/ViewLog.php new file mode 100644 index 000000000..799418b73 --- /dev/null +++ b/models/mysqldb/product/ViewLog.php @@ -0,0 +1,32 @@ + + * @since 1.0 + */ + +class ViewLog extends ActiveRecord +{ + public static $_tableName; + + public static function tableName() + { + return self::$_tableName; + } + + public static function setCurrentTableName($tableName){ + self::$_tableName = $tableName; + } + + + +} diff --git a/models/mysqldb/url/UrlRewrite.php b/models/mysqldb/url/UrlRewrite.php new file mode 100644 index 000000000..c344fb9e1 --- /dev/null +++ b/models/mysqldb/url/UrlRewrite.php @@ -0,0 +1,28 @@ + + * @since 1.0 + */ + +class UrlRewrite extends ActiveRecord +{ + + public static function tableName() + { + return 'url_rewrite'; + } + + + + +} diff --git a/models/xunsearch/Search.php b/models/xunsearch/Search.php new file mode 100644 index 000000000..6e352a52e --- /dev/null +++ b/models/xunsearch/Search.php @@ -0,0 +1,22 @@ + + * @since 1.0 + */ + +class Search extends \hightman\xunsearch\ActiveRecord +{ + public static function projectName() { + return 'search'; // 这将使用 @app/config/another_name.ini 作为项目名 + } +} \ No newline at end of file diff --git a/services/AdminUser.php b/services/AdminUser.php new file mode 100644 index 000000000..9a230ec63 --- /dev/null +++ b/services/AdminUser.php @@ -0,0 +1,36 @@ + + * @since 1.0 + */ +class AdminUser extends Service +{ + + #Yii::$service->adminUser->getIdAndNameArrByIds($ids) + protected function actionGetIdAndNameArrByIds($ids){ + + $user_coll = \fecadmin\models\AdminUser::find()->asArray()->select(['id','username'])->where([ + 'in','id',$ids + ])->all(); + $users = []; + foreach($user_coll as $one){ + $users[$one['id']] = $one['username']; + } + return $users; + } + +} \ No newline at end of file diff --git a/services/Affiliate.php b/services/Affiliate.php new file mode 100644 index 000000000..e69de29bb diff --git a/services/Application.php b/services/Application.php new file mode 100644 index 000000000..e1b4ebf41 --- /dev/null +++ b/services/Application.php @@ -0,0 +1,52 @@ + + * @since 1.0 + */ +class Application +{ + public $childService; + public $_childService; + + + public function __construct($config = []) + { + Yii::$service = $this; + $this->childService = $config; + } + /** + * õservices õӷchildServiceʵ + */ + public function getChildService($childServiceName){ + if(!$this->_childService[$childServiceName]){ + $childService = $this->childService; + if(isset($childService[$childServiceName])){ + $service = $childService[$childServiceName]; + $this->_childService[$childServiceName] = Yii::createObject($service); + }else{ + throw new InvalidConfigException('Child Service ['.$childServiceName.'] is not find in '.get_called_class().', you must config it! '); + } + } + return $this->_childService[$childServiceName]; + } + + /** + * + */ + public function __get($attr){ + return $this->getChildService($attr); + + } + +} \ No newline at end of file diff --git a/services/Blog.php b/services/Blog.php new file mode 100644 index 000000000..b50d0af3c --- /dev/null +++ b/services/Blog.php @@ -0,0 +1,75 @@ + + * @since 1.0 + */ +class Blog extends Service +{ + + protected function actionGetCategoryMenu(){ + + + } + + protected function actionGetArticleList(){ + + + } + + protected function actionGetCategoryArticleList(){ + + + } + + protected function actionGetArticleById(){ + + + } + + + protected function actionSaveArticle(){ + + + } + + + protected function actionDeleteArticle(){ + + + } + + + protected function actionSaveCategory(){ + + + } + + protected function actionDeleteCategory(){ + + + } + + + protected function actionGetCategoryById(){ + + + } + + + + + +} \ No newline at end of file diff --git a/services/Cart.php b/services/Cart.php new file mode 100644 index 000000000..af55646c1 --- /dev/null +++ b/services/Cart.php @@ -0,0 +1,193 @@ + + * @since 1.0 + */ +class Cart extends Service +{ + + /** + * 将某个产品加入到购物车中 + * @property $item|Array + * $item = [ + * 'product_id' => 22222, + * 'custom_option_sku' => ['color'=>'red','size'=>'l'], + * 'qty' => 22, + * ]; + * 注意: $item['custom_option_sku'] 除了为上面的数组格式,还可以为字符串 + * 为字符串的时候,字符串标示的就是产品的custom option sku + */ + protected function actionAddProductToCart($item){ + $product = Yii::$service->product->getByPrimaryKey($item['product_id']); + $productValidate = Yii::$service->cart->info->validateProduct($item,$product); + if(!$productValidate){ + $get = Yii::$service->helper->errors->get(); + return false; + } + if(isset($item['custom_option_sku']) && !empty($item['custom_option_sku'])){ + if(is_array($item['custom_option_sku'])){ + $custom_option_sku = Yii::$service->cart->info->getCustomOptionSku($item,$product); + if(!$custom_option_sku){ + return false; + } + } + $item['custom_option_sku'] = $custom_option_sku; + } + $innerTransaction = Yii::$app->db->beginTransaction(); + try { + Yii::$service->cart->quoteItem->addItem($item); + $innerTransaction->commit(); + } catch (Exception $e) { + $innerTransaction->rollBack(); + } + return true; + + } + + # 得到购物车中产品的个数 + protected function actionGetCartItemQty(){ + return Yii::$service->cart->quote->getCartItemCount(); + + } + /** + * 得到购物车中的信息。 + */ + protected function actionGetCartInfo($shipping_method='',$country='',$region='*'){ + return Yii::$service->cart->quote->getCartInfo($shipping_method,$country,$region); + } + + + /** + * @property $item_id | Int 购物车产品表的id字段 + * 通过item id 将购物车中的某个产品的个数加一 + */ + protected function actionAddOneItem($item_id){ + $innerTransaction = Yii::$app->db->beginTransaction(); + try { + $status = Yii::$service->cart->quoteItem->addOneItem($item_id); + if(!$status){ + $innerTransaction->rollBack(); + return false; + } + Yii::$service->cart->quote->computeCartInfo(); + $innerTransaction->commit(); + return true; + } catch (Exception $e) { + $innerTransaction->rollBack(); + } + return false; + + } + /** + * @property $item_id | Int 购物车产品表的id字段 + * 通过item id 将购物车中的某个产品的个数减一 + */ + protected function actionLessOneItem($item_id){ + $innerTransaction = Yii::$app->db->beginTransaction(); + try { + $status = Yii::$service->cart->quoteItem->lessOneItem($item_id); + if(!$status){ + $innerTransaction->rollBack(); + return false; + } + Yii::$service->cart->quote->computeCartInfo(); + + $innerTransaction->commit(); + return true; + } catch (Exception $e) { + $innerTransaction->rollBack(); + } + return false; + } + + /** + * @property $item_id | Int 购物车产品表的id字段 + * 通过item id 删除购物车中的某个产品 + */ + protected function actionRemoveItem($item_id){ + $innerTransaction = Yii::$app->db->beginTransaction(); + try { + $status = Yii::$service->cart->quoteItem->removeItem($item_id); + if(!$status){ + $innerTransaction->rollBack(); + return false; + } + Yii::$service->cart->quote->computeCartInfo(); + $innerTransaction->commit(); + return true; + } catch (Exception $e) { + $innerTransaction->rollBack(); + } + return false; + } + /** + * @property $coupon_code 优惠卷码 + * @return boolean 优惠券使用成功则返回true,失败则返回false + */ + //protected function actionAddCoupon($coupon_code){ + + + //} + + + /** + * merge cart , if current cart currency is not equals to user cart currency when user login account. + */ + protected function actionMergeCartAfterUserLogin(){ + Yii::$service->cart->quote->mergeCartAfterUserLogin(); + + } + + + + + /** + * @property $address|Array + * save cart address.like,, customer name,tel,email,address ,,etc,,. + */ + protected function actionUpdateGuestCart($address,$shipping_method,$payment_method){ + Yii::$service->cart->quote->updateGuestCart($address,$shipping_method,$payment_method); + } + + protected function actionUpdateLoginCart($address_id,$shipping_method,$payment_method){ + return Yii::$service->cart->quote->updateLoginCart($address_id,$shipping_method,$payment_method); + } + + + /** + * clear cart product. + */ + protected function actionClearCart(){ + Yii::$service->cart->quote->clearCart(); + } + + + + /** + * add cart items by pending order Id + * 1. check if the order is exist ,and belong to current customer. + * 2. get all item sku and custom option. + * 3. add to cart like in product page ,click add to cart button. + */ + protected function actionAddItemsByPendingOrder($order_id){ + + + } + + +} \ No newline at end of file diff --git a/services/Category.php b/services/Category.php new file mode 100644 index 000000000..8c89d1722 --- /dev/null +++ b/services/Category.php @@ -0,0 +1,134 @@ + + * @since 1.0 + */ +class Category extends Service +{ + + public $storage = 'mongodb'; + protected $_category; + /** + * init function , 初始化category,使用哪一个category service + */ + public function init(){ + if($this->storage == 'mongodb'){ + $this->_category = new CategoryMongodb; + //}else if($this->storage == 'mysqldb'){ + //$this->_category = new CategoryMysqldb; + } + } + /** + * Get Url by article's url key. + */ + //public function getUrlByPath($urlPath){ + //return Yii::$service->url->getHttpBaseUrl().'/'.$urlKey; + //return Yii::$service->url->getUrlByPath($urlPath); + //} + /** + * 得到当前的category service 对应的主键名称,譬如如果是mongo,返回的是 _id + */ + protected function actionGetPrimaryKey(){ + return $this->_category->getPrimaryKey(); + } + /** + * @property $primaryKey | String or Int , 主键 + * 通过主键,得到category info + */ + protected function actionGetByPrimaryKey($primaryKey){ + return $this->_category->getByPrimaryKey($primaryKey); + } + + protected function actionCollCount($filter=''){ + return $this->_category->collCount($filter); + } + + + /** + * @property $filter|Array + * get artile collection by $filter + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + * 'where' => [ + * ['>','price','1'], + * ['<','price','10'], + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + * 通过上面个数的filter数组,得到过滤后的分类数据列表集合。 + */ + protected function actionColl($filter=''){ + return $this->_category->coll($filter); + } + /** + * 得到分类的树数组。 + * 数组中只有 id name(default language), child(子分类) 等数据。 + * 目前此函数仅仅用于后台对分类的编辑使用。 appadmin + */ + protected function actionGetTreeArr($rootCategoryId=0){ + return $this->_category->getTreeArr($rootCategoryId); + } + + /** + * @property $one|Array , save one data . 分类数组 + * @property $originUrlKey|String , 分类的在修改之前的url key.(在数据库中保存的url_key字段,如果没有则为空) + * 保存分类,同时生成分类的伪静态url(自定义url),如果按照name生成的url或者自定义的urlkey存在,系统则会增加几个随机数字字符串,来增加唯一性。 + */ + protected function actionSave($one,$originUrlKey='catalog/category/index'){ + return $this->_category->save($one,$originUrlKey); + } + /** + * @property $id | String 主键值 + * 通过主键值找到分类,并且删除分类在url rewrite表中的记录 + * 查看这个分类是否存在子分类,如果存在子分类,则删除所有的子分类,以及子分类在url rewrite表中对应的数据。 + */ + protected function actionRemove($id){ + return $this->_category->remove($id); + } + /** + * @property $parent_id|String + * 通过当前分类的parent_id字段(当前分类的上级分类id),得到所有的上级分类数组。 + * 里面包含的信息为:name,url_key。 + * 譬如一个分类为三级分类,将他的parent_id传递给这个函数,那么,他返回的数组信息为[一级分类的信息(name,url_key),二级分类的信息(name,url_key)]. + * 目前这个功能用于前端分类页面的面包屑导航。 + */ + protected function actionGetAllParentInfo($parent_id){ + return $this->_category->getAllParentInfo($parent_id); + } + /** + * @property $category_id|String 当前的分类_id + * @property $parent_id|String 当前的分类上级id parent_id + * 这个功能是点击分类后,在产品分类页面侧栏的子分类菜单导航,详细的逻辑如下: + * 1.如果level为一级,那么title部分为当前的分类,子分类为一级分类下的二级分类 + * 2.如果level为二级,那么将所有的二级分类列出,当前的二级分类,会列出来当前二级分类对应的子分类 + * 3.如果level为三级,那么将所有的二级分类列出。当前三级分类的所有姊妹分类(同一个父类)列出,当前三级分类如果有子分类,则列出 + * 4.依次递归。 + * 具体的显示效果,请查看appfront 对应的分类页面。 + */ + protected function actionGetFilterCategory($category_id,$parent_id){ + return $this->_category->getFilterCategory($category_id,$parent_id); + } + + + + + +} \ No newline at end of file diff --git a/services/Cms.php b/services/Cms.php new file mode 100644 index 000000000..1109f6b14 --- /dev/null +++ b/services/Cms.php @@ -0,0 +1,27 @@ + + * @since 1.0 + */ +class Cms extends Service +{ + /** + * cms storage db, you can set value: mysqldb,mongodb. + */ + public $storage = 'mysqldb'; + +} \ No newline at end of file diff --git a/services/Coupon.php b/services/Coupon.php new file mode 100644 index 000000000..6ebe70ea1 --- /dev/null +++ b/services/Coupon.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Coupon extends Service +{ + + + +} \ No newline at end of file diff --git a/services/Customer.php b/services/Customer.php new file mode 100644 index 000000000..0995d9395 --- /dev/null +++ b/services/Customer.php @@ -0,0 +1,332 @@ + + * @since 1.0 + */ +class Customer extends Service +{ + public $customer_register; + const USER_LOGIN_SUCCESS_REDIRECT_URL_KEY = 'usr_login_success_redirect_url'; + /** + * 注册用户名字的最小长度 + */ + protected function actionGetRegisterNameMinLength(){ + if(isset($this->customer_register['min_name_length'])){ + return $this->customer_register['min_name_length']; + } + } + /** + * 注册用户名字的最大长度 + */ + protected function actionGetRegisterNameMaxLength(){ + if(isset($this->customer_register['max_name_length'])){ + return $this->customer_register['max_name_length']; + } + } + /** + * 注册用户密码的最小长度 + */ + protected function actionGetRegisterPassMinLength(){ + if(isset($this->customer_register['min_pass_length'])){ + return $this->customer_register['min_pass_length']; + } + } + /** + * 注册用户密码的最大长度 + */ + protected function actionGetRegisterPassMaxLength(){ + if(isset($this->customer_register['max_pass_length'])){ + return $this->customer_register['max_pass_length']; + } + } + + /** + * @property $data|Array + * like :['email'=>'xxx@xxx.com','password'=>'xxxx'] + */ + protected function actionLogin($data){ + + $model = new CustomerLogin; + $model->email = $data['email']; + $model->password = $data['password']; + $loginStatus = $model->login(); + $errors = $model->errors; + if(empty($errors)){ + # 合并购物车数据 + Yii::$service->cart->mergeCartAfterUserLogin(); + }else{ + Yii::$service->helper->errors->add($errors); + } + return $loginStatus; + } + /** + * @property $data|Array + * register customer account + * ['email','firstname','lastname','password' + * + * ] + */ + protected function actionRegister($param){ + $model = new CustomerRegister; + $model->attributes = $param; + if($model->validate()){ + $model->created_at = time(); + $model->updated_at = time(); + return $model->save(); + }else{ + $errors = $model->errors; + Yii::$service->helper->errors->add($errors); + return false;; + } + } + + protected function actionIsRegistered($email){ + $customer = CustomerModel::findOne(['email' => $email]); + if($customer['email']){ + return true; + }else{ + return false; + } + } + + protected function actionSave($param){ + $primaryKey = $this->getPrimaryKey(); + $primaryVal = isset($param[$primaryKey]) ? $param[$primaryKey] : ''; + if($primaryVal){ + $model = $this->getByPrimaryKey($primaryVal); + if($model[$primaryKey]){ + unset($param[$primaryKey]); + $param['updated_at'] = time(); + $password = isset($param['password']) ? $param['password'] : ''; + if($password){ + $model->setPassword($password); + unset($param['password']); + } + $saveStatus = Yii::$service->helper->ar->save($model,$param); + if($saveStatus){ + return true; + }else{ + $errors = $model->errors; + Yii::$service->helper->errors->add($errors); + return false; + } + } + } + } + + /** + * @property $customerId|Int + * Get customer info by customerId, if customer id is empty, current customer id will be set, + * if current customer id is empty , false will be return . + */ + protected function actionViewInfo($customerId = ''){ + + + } + + /** + * @property $password|String + * @property $customerId|Int or String or Object + * change customer password. + * if $customer id is empty, it will be equals current customer id. + */ + protected function actionChangePassword($password,$identity){ + if(is_int($identity)){ + $customer_id = $identity; + $customerModel = CustomerModel::findIdentity($customer_id); + }else if(is_string($identity)){ + $email = $identity; + $customerModel = CustomerModel::findByEmail($email); + }else if(is_object($identity)){ + $customerModel = $identity; + } + $customerModel->updated_at = time(); + $customerModel->setPassword($password); + $customerModel->save(); + } + + protected function actionGetByPrimaryKey($val){ + if($val){ + $one = CustomerModel::findOne($val); + $primaryKey = $this->getPrimaryKey(); + if($one[$primaryKey]){ + return $one; + }else{ + return new CustomerModel; + } + } + } + /** + * @property $password|String + * @property $customerId|Int or String or Object + * change customer password. + * 更改密码,然后,清空token + */ + protected function actionChangePasswordAndClearToken($password,$identity){ + if(is_int($identity)){ + $customer_id = $identity; + $customerModel = CustomerModel::findIdentity($customer_id); + }else if(is_string($identity)){ + $email = $identity; + $customerModel = CustomerModel::findByEmail($email); + }else if(is_object($identity)){ + $customerModel = $identity; + }else{ + Yii::$service->helper->errors->add('identity is not right'); + return; + } + //echo $password;exit; + $customerModel->setPassword($password); + $customerModel->removePasswordResetToken(); + $customerModel->updated_at = time(); + $customerModel->save(); + return true; + } + + /** + * @property $customerId|Array + * ['firstname','lastname','password','customerId'] + */ + protected function actionChangeNameAndPassword($data){ + + } + + /** + * get current customer identify. + */ + protected function actionGetCurrentAccount(){ + return Yii::$app->user->identity->username; + + } + /** + * get CustomerModel by Email address + */ + protected function actionGetUserIdentityByEmail($email){ + $one = CustomerModel::findByEmail($email); + if($one['email']){ + return $one; + }else{ + return false; + } + } + + /** + * @property $identify|object(customer object) or String + * @return 生成的resetToken,如果生成失败返回false + * 用来找回密码,生成resetToken,返回 + */ + protected function actionGeneratePasswordResetToken($identify){ + if(is_string($identify)){ + $email = $identify; + $one = $this->getUserIdentityByEmail($email); + }else{ + $one = $identify; + } + if($one){ + + $one->generatePasswordResetToken(); + $one->updated_at = time(); + $one->save(); + return $one->password_reset_token; + } + return false; + } + + /** + * 通过PasswordResetToken 得到user + */ + protected function actionFindByPasswordResetToken($token){ + return CustomerModel::findByPasswordResetToken($token); + } + /** + * @property $url|String + * 在一些功能中,需要用户进行登录操作,等用户操作成功后,应该跳转到相应的页面中,这里通过session存储需要跳转到的url。 + * 某些页面 , 譬如评论页面,需要用户登录后才能进行登录操作,那么可以通过这个方法把url set 进去,登录成功 + * 后,页面不会跳转到账户中心,而是需要操作的页面中。 + */ + protected function actionSetLoginSuccessRedirectUrl($url){ + return Yii::$app->session->set($this::USER_LOGIN_SUCCESS_REDIRECT_URL_KEY,$url); + } + /** + * @property $url|String + * 在一些功能中,需要用户进行登录操作,等用户操作成功后,应该跳转到相应的页面中,这里通过session得到需要跳转到的url。 + */ + protected function actionGetLoginSuccessRedirectUrl(){ + $url = Yii::$app->session->get($this::USER_LOGIN_SUCCESS_REDIRECT_URL_KEY); + return $url ? $url : ''; + } + + protected function actionLoginSuccessRedirect($urlKey){ + $url = $this->getLoginSuccessRedirectUrl(); + + if($url){ + # 这个优先级最高 + # 在跳转之前,去掉这个session存储的值。跳转后,这个值必须失效。 + Yii::$app->session->remove($this::USER_LOGIN_SUCCESS_REDIRECT_URL_KEY); + //echo Yii::$app->session->get($this::USER_LOGIN_SUCCESS_REDIRECT_URL_KEY); + //exit; + Yii::$service->url->redirect($url); + + }else{ + Yii::$service->url->redirectByUrlKey($urlKey); + } + } + /** + * 得到status为删除状态的值 + */ + protected function actionGetStatusDeleted(){ + return CustomerModel::STATUS_DELETED; + } + /** + * 得到status为激活状态的值 + */ + protected function actionGetStatusActive(){ + return CustomerModel::STATUS_ACTIVE; + } + + protected function actionGetPrimaryKey(){ + return 'id'; + } + + protected function actionColl($filter=''){ + $query = CustomerModel::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + //var_dump($query->all());exit; + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + } + + protected function actionGetEmailByIds($user_ids){ + $arr = []; + if(is_array($user_ids) && !empty($user_ids)){ + $data = CustomerModel::find()->where([ + 'in','id',$user_ids + ])->all(); + if(is_array($data) && !empty($data)){ + foreach($data as $one){ + $arr[$one['id']] = $one['email']; + } + } + } + return $arr; + } + +} \ No newline at end of file diff --git a/services/Email.php b/services/Email.php new file mode 100644 index 000000000..372b38fd6 --- /dev/null +++ b/services/Email.php @@ -0,0 +1,189 @@ + + * @since 1.0 + */ +class Email extends Service +{ + public $mailerConfig; + public $defaultForm; + + protected $_mailer; # Array + protected $_mailer_from; #Array + protected $_from; + /** + * 得到MailConfig + */ + protected function getMailerConfig($key = 'default'){ + if(isset($this->mailerConfig[$key]) && $this->mailerConfig[$key]){ + if(is_array($this->mailerConfig[$key])){ + return $this->mailerConfig[$key]; + }else if(is_string($this->mailerConfig[$key])){ + return $this->getMailerConfig($this->mailerConfig[$key]); + } + } + return ''; + } + /** + * 默认的默认form。邮件from + */ + protected function defaultForm($mailerConfig){ + if(isset($mailerConfig['transport']['username'])){ + if(!empty($mailerConfig['transport']['username'])){ + return $mailerConfig['transport']['username']; + } + + } + return ; + } + /** + * @property $mailerConfig | Array or String mailer组件的配置,下面是例子, + 您可以使用在email service里面默认的配置,也可以动态配置他,下面是参数的例子: + 注意:如果自定义传递邮箱配置,不同的配置,要使用不同的configKey + [ + 'configKey' => [ + 'class' => 'yii\swiftmailer\Mailer', + 'transport' => [ + 'class' => 'Swift_SmtpTransport', + 'host' => 'smtp.qq.net', + 'username' => 'support@mail.com', + 'password' => 'xxxx', + 'port' => '587', + 'encryption' => 'tls', + ], + 'messageConfig'=>[ + 'charset'=>'UTF-8', + ], + ], + ] + * @return yii的mail组件、 + * + */ + protected function actionMailer($mailerConfigParam = ''){ + + if(!$mailerConfigParam){ + $key = 'default'; + }else if(is_array($mailerConfigParam)){ + $key_arr = array_keys($mailerConfigParam); + $key = $key_arr[0]; + }else if(is_string($mailerConfigParam)){ + $key = $mailerConfigParam; + }else{ + return; + } + if(!$key){ + return; + } + + //exit; + if(!$this->_mailer[$key]){ + $component_name = 'mailer_'.$key; + if(!$mailerConfigParam){ + $mailerConfig = $this->getMailerConfig(); + if(!is_array($mailerConfig) || empty($mailerConfig)){ + return; + } + Yii::$app->set($component_name,$mailerConfig); + }else if(is_array($mailerConfigParam)){ + $mailerConfig = $mailerConfigParam[$key]; + if(!is_array($mailerConfig) || empty($mailerConfig)){ + return; + } + $component_name .= 'custom_'; + Yii::$app->set($component_name,$mailerConfig); + }else if(is_string($mailerConfigParam)){ + $mailerConfig = $this->getMailerConfig($mailerConfigParam); + if(!is_array($mailerConfig) || empty($mailerConfig)){ + return; + } + Yii::$app->set($component_name,$mailerConfig); + + } + $this->_mailer_from[$key] = $this->defaultForm($mailerConfig); + $this->_mailer[$key] = Yii::$app->get($component_name); + } + $this->_from = $this->_mailer_from[$key]; + //var_dump($this->_mailer[$key]);exit; + return $this->_mailer[$key]; + } + + /** + * [ + 'to' => $to, + 'subject' => $subject, + 'htmlBody' => $htmlBody, + 'senderName'=> $senderName, + ] + */ + protected function actionSend($sendInfo,$mailerConfigParam=''){ + $to = isset($sendInfo['to']) ? $sendInfo['to'] : ''; + $subject = isset($sendInfo['subject']) ? $sendInfo['subject'] : ''; + $htmlBody = isset($sendInfo['htmlBody']) ? $sendInfo['htmlBody'] : ''; + $senderName = isset($sendInfo['senderName']) ? $sendInfo['senderName'] : ''; + /* + $this->mailer()->compose() + ->setFrom('support@fecshop.com') + ->setTo('2851823529@qq.com') + ->setSubject('111111Message subject222') + ->setHtmlBody('HTML content333333333df') + ->send(); + */ + if(!$subject){ + Yii::$service->helper->errors->add('email title is empty'); + return; + } + if(!$htmlBody){ + Yii::$service->helper->errors->add('email body is empty'); + return; + } + + $mailer = $this->mailer($mailerConfigParam); + if(!$mailer){ + //error + Yii::$service->helper->errors->add('compose is empty, you must check you email config'); + return; + } + + if(!$this->_from){ + //error + Yii::$service->helper->errors->add('email send from is empty'); + return; + }else{ + $from = $this->_from; + } + /* + echo $from; + echo '

    '; + echo $to;echo '

    '; + echo $subject;echo '

    '; + echo $htmlBody;echo '

    '; + var_dump($mailer); + */ + if($senderName){ + $setFrom = [$from => $senderName]; + }else{ + $setFrom = $from; + } + $mailer->compose() + ->setFrom($setFrom) + ->setTo($to) + ->setSubject($subject) + ->setHtmlBody($htmlBody) + ->send(); + + } + + +} diff --git a/services/FecshopLang.php b/services/FecshopLang.php new file mode 100644 index 000000000..23e2e8781 --- /dev/null +++ b/services/FecshopLang.php @@ -0,0 +1,140 @@ + + * @since 1.0 + */ +class Fecshoplang extends Service +{ + /** + * all languages + */ + public $allLangCode; + + /** + * default language + */ + public $defaultLangCode; + protected $_allLangCode; + + /** + * @property $attrName|String , attr name ,like : tilte , description ,name etc.. + * @property $langCode|String , language 2 code, like :en ,fr ,es, + * get language child language attr, like: title_fr + */ + protected function actionGetLangAttrName($attrName,$langCode){ + return $attrName.'_'.$langCode; + } + + protected function actionGetDefaultLangAttrName($attrName){ + return $attrName.'_'.$this->defaultLangCode; + } + /** + * 返回所有的,用于mongodb 生成fullSearch的语言名称。 + */ + /* + protected function actionGetAllmongoSearchLangName(){ + $arr = []; + foreach($this->allLangCode as $codeInfo){ + $mongoSearchLangName = $codeInfo['mongoSearchLangName']; + $arr[] = $mongoSearchLangName; + } + return $arr; + } + */ + + protected function actionGetAllLangCode(){ + if(!$this->_allLangCode){ + if(empty($this->allLangCode) || !is_array($this->allLangCode)){ + return []; + } + if($this->defaultLangCode){ + $this->_allLangCode[] = $this->defaultLangCode; + foreach($this->allLangCode as $codeInfo){ + $code = $codeInfo['code']; + if($this->defaultLangCode != $code){ + $this->_allLangCode[] = $code; + } + } + } + } + return $this->_allLangCode; + } + + /** + * @property $attrVal|Array , language attr array , like ['title_en' => 'xxxx','title_fr' => 'yyyy'] + * @property $attrName|String, attribute name ,like: title ,description. + * get default language attr value. + * example getDefaultLangAttrVal(['title_en'=>'xx','title_fr'=>'yy'],'title'); + */ + protected function actionGetDefaultLangAttrVal($attrVal,$attrName){ + $defaultLangAttrName = $this->getDefaultLangAttrName($attrName); + if(isset($attrVal[$defaultLangAttrName]) && !empty($attrVal[$defaultLangAttrName])){ + return $attrVal[$defaultLangAttrName]; + } + return ''; + } + + /** + * @property $attrVal|Array , language attr array , like ['title_en' => 'xxxx','title_fr' => 'yyyy'] + * @property $attrName|String, attribute name ,like: title ,description. + * @property $lang | String , language. + * if object or array attribute is a language attribute, you can get current + * language value by this function. + * if lang attribute in current store language is empty , default language attribute will be return. + * if attribute in default language value is empty, '' will be return. + * example getLangAttrVal(['title_en'=>'xx','title_fr'=>'yy'],'title','fr'); + */ + protected function actionGetLangAttrVal($attrVal,$attrName,$langCode){ + $langAttrName = $this->getLangAttrName($attrName,$langCode); + if(isset($attrVal[$langAttrName]) && !empty($attrVal[$langAttrName])){ + return $attrVal[$langAttrName]; + }else{ + $defaultLangAttrName = $this->getDefaultLangAttrName($attrName); + if(isset($attrVal[$defaultLangAttrName]) && !empty($attrVal[$defaultLangAttrName])){ + return $attrVal[$defaultLangAttrName]; + } + } + return ''; + } + + /** + * @property $attrVal|String 属性对应的值 一般是一个数组,里面包含各个语言的的属性值 + * @property $attrName|String 属性名称,譬如: name title + * @return 当前store 语言对应的值。 + */ + /* + protected function actionGetCurrentStoreAttrVal($attrVal,$attrName){ + $langCode = Yii::$service->store->currentLangCode ; + if($langCode){ + return $this->getLangAttrVal($attrVal,$attrName,$langCode); + } + } + */ + + /** + * @property $language|String like: en_US ,fr_FR,zh_CN + * @return String , like en ,fr ,es , if $language is not exist in $this->allLangCode + * empty will be return. + */ + protected function actionGetLangCodeByLanguage($language){ + if(isset($this->allLangCode[$language])){ + return $this->allLangCode[$language]['code']; + }else{ + return ''; + } + } + + +} \ No newline at end of file diff --git a/services/Helper.php b/services/Helper.php new file mode 100644 index 000000000..492f033ab --- /dev/null +++ b/services/Helper.php @@ -0,0 +1,43 @@ + + * @since 1.0 + */ +class Helper extends Service +{ + protected $_app_name; + + /** + * 得到当前的app入口的名字,譬如 appfront apphtml5 appserver等 + */ + public function getAppName(){ + return Yii::$app->params['appName']; + } + + + + + + + + + + + + + +} \ No newline at end of file diff --git a/services/Image.php b/services/Image.php new file mode 100644 index 000000000..013d18240 --- /dev/null +++ b/services/Image.php @@ -0,0 +1,209 @@ + + * @since 1.0 + */ +class Image extends Service +{ + + /** + * absolute image save floder + */ + public $imageFloder = 'media/upload'; + /** + * upload image max size (MB) + */ + public $maxUploadMSize = 2; + /** + * allow image type + */ + public $allowImgType = [ + 'image/jpeg', + 'image/gif', + 'image/png', + 'image/jpg', + 'image/pjpeg', + ]; + + protected $_maxUploadSize; + public $appbase; + /** + * 1.1 app front image Dir + */ + protected function actionGetImgDir($str='',$app='common'){ + if($appbase = $this->appbase){ + if(isset($appbase[$app]['basedir'])){ + if($str){ + return Yii::getAlias($appbase[$app]['basedir'].'/'.$str); + } + return Yii::getAlias($appbase[$app]['basedir']); + } + } + } + /** + * 1.2 app front image Url* + * example : image->getImgUrl('custom/logo.png','appfront'); ?> + * it will find image in @appimage/$app + */ + protected function actionGetImgUrl($str,$app='common'){ + //echo "$str,$app"; + if($appbase = $this->appbase){ + if(isset($appbase[$app]['basedomain'])){ + if($str){ + return $appbase[$app]['basedomain'].'/'.$str; + } + return $appbase[$app]['basedomain']; + } + } + return ; + } + /** + * 2.1 app front image base dir + */ + protected function actionGetBaseImgDir($app='common'){ + return $this->getImgDir('',$app); + } + /** + * 2.2 app front image base Url + */ + protected function actionGetBaseImgUrl($app='common'){ + return $this->getImgUrl('',$app); + } + + /** + * 设置上传图片的最大的size + */ + protected function actionSetMaxUploadSize($uploadSize){ + $this->_maxUploadSize = $uploadSize * 1024 * 1024; + } + /** + * 得到上传图片的最大的size + */ + protected function actionGetMaxUploadSize(){ + if(!$this->_maxUploadSize){ + if($this->maxUploadMSize){ + $this->_maxUploadSize = $this->maxUploadMSize * 1024 * 1024; + } + } + return $this->_maxUploadSize; + } + /** + * 得到保存图片所在相对根目录的url路径 + */ + protected function actionGetCurrentBaseImgUrl(){ + return $this->GetImgUrl($this->imageFloder,'common'); + } + /** + * 得到保存图片所在相对根目录的文件夹路径 + */ + protected function actionGetCurrentBaseImgDir(){ + return $this->GetImgDir($this->imageFloder,'common'); + } + /** + * 通过图片的相对路径得到产品图片的url + */ + protected function actionGetUrlByRelativePath($str){ + return $this->GetImgUrl($this->imageFloder.$str,'common'); + } + /** + * 通过图片的相对路径得到产品图片的绝对路径 + */ + protected function actionGetDirByRelativePath(){ + return $this->GetImgDir($this->imageFloder.$str,'common'); + } + + + /** + * @property $param_img_file | Array . + * upload image from web page , you can get image from $_FILE['XXX'] , + * $param_img_file is get from $_FILE['XXX']. + * return , if success ,return image saved relative file path , like '/b/i/big.jpg' + * if fail, reutrn false; + */ + protected function actionSaveUploadImg($FILE){ + + $size = $FILE['size']; + $file = $FILE['tmp_name']; + $name = $FILE['name']; + if($size > $this->getMaxUploadSize()){ + throw new InvalidValueException('upload image is to max than'. $this->getMaxUploadSize().' MB'); + }else if(!($img = getimagesize($file))){ + throw new InvalidValueException('file type is empty.'); + + }else if($img = getimagesize($file)){ + $imgType = $img['mime']; + + if(!in_array($imgType,$this->allowImgType)){ + throw new InvalidValueException('image type is not allow for '.$imgType); + } + } + // process image name. + $imgSavedRelativePath = $this->getImgSavedRelativePath($name); + $isMoved = @move_uploaded_file ( $file, $this->GetCurrentBaseImgDir().$imgSavedRelativePath); + if($isMoved){ + $imgUrl = $this->getUrlByRelativePath($imgSavedRelativePath); + $imgPath = $this->getDirByRelativePath($imgSavedRelativePath); + return [$imgSavedRelativePath,$imgUrl,$imgPath]; + }else{ + return false; + } + } + + /** + * get Image save file path, if floder is not exist, this function will create floder. + * if image file is exsit , image file name will be change to a not existed file name( by add radom string to file name ). + * return image saved relative path , like /a/d/advert.jpg + */ + protected function getImgSavedRelativePath($name){ + list($imgName,$imgType) = explode('.',$name); + if(!$imgName || !$imgType){ + throw new InvalidValueException('image file name and type is not correct'); + } + if(strlen($imgName) < 2){ + $imgName .= time(). mt_rand(100, 999); + } + $first_str = substr($imgName,0,1); + $two_str = substr($imgName,1,2); + + $imgSaveFloder = CDir::createFloder($this->GetCurrentBaseImgDir(),[$first_str,$two_str]); + if($imgSaveFloder){ + $imgName = $this->getUniqueImgNameInPath($imgSaveFloder,$imgName,$imgType); + $relative_floder = '/'.$first_str.'/'.$two_str.'/'; + return $relative_floder.$imgName; + } + return false; + + } + + /** + * @property $imgSaveFloder|String image save Floder absolute Path + * @property $name|String , image file name ,not contain image suffix. + * @property $imageType|String , image file suffix. like '.gif','jpg' + * return saved Image Name. + */ + + protected function getUniqueImgNameInPath($imgSaveFloder,$name,$imageType,$randStr=''){ + $imagePath = $imgSaveFloder.'/'.$name.$randStr.'.'.$imageType; + if(!file_exists($imagePath)){ + return $name.$randStr.'.'.$imageType;; + }else{ + $randStr = time().rand(10000,99999); + return $this->getUniqueImgNameInPath($imgSaveFloder,$name,$imageType,$randStr); + } + } +} \ No newline at end of file diff --git a/services/Order.php b/services/Order.php new file mode 100644 index 000000000..2b34dac88 --- /dev/null +++ b/services/Order.php @@ -0,0 +1,342 @@ + + * @since 1.0 + */ +class Order extends Service +{ + public $requiredAddressAttr; # 必填的订单字段。 + public $paymentStatus; # 订单支付状态。 + public $increment_id = 1000000000; + protected $checkout_type; + const CHECKOUT_TYPE_STANDARD = 'standard'; + const CHECKOUT_TYPE_EXPRESS = 'express'; + const CURRENT_ORDER_CREAMENT_ID = 'current_order_creament_id'; + + protected function actionSetCheckoutType($checkout_type){ + $arr = [self::CHECKOUT_TYPE_STANDARD,self::CHECKOUT_TYPE_EXPRESS]; + if(in_array($checkout_type,$arr)){ + $this->checkout_type = $checkout_type; + return true; + } + return false; + } + protected function actionGetCheckoutType(){ + return $this->checkout_type; + } + /** + * @property $billing | Array + * @return boolean + * 检查地址的必填。 + */ + protected function actionCheckRequiredAddressAttr($billing){ + //$this->requiredAddressAttr; + if(is_array($this->requiredAddressAttr) && !empty($this->requiredAddressAttr)){ + foreach($this->requiredAddressAttr as $attr){ + if(!isset($billing[$attr]) || empty($billing[$attr])){ + Yii::$service->helper->errors->add($attr.' can not empty'); + return false; + } + } + } + return true; + } + + + protected function actionGetPrimaryKey(){ + return 'order_id'; + } + /** + * @property $primaryKey | Int + * @return Object(MyOrder) + * 通过id找到cupon的对象 + */ + protected function actionGetByPrimaryKey($primaryKey){ + $one = MyOrder::findOne($primaryKey); + $primaryKey = $this->getPrimaryKey(); + if($one[$primaryKey]){ + return $one; + }else{ + return new MyOrder; + } + } + + protected function actionGetOrderInfoById($order_id){ + if(!$order_id){ + return ; + } + $one = MyOrder::findOne($order_id); + $primaryKey = $this->getPrimaryKey(); + if(!isset($one[$primaryKey]) || empty($one[$primaryKey])){ + return ; + } + $order_info = []; + foreach($one as $k=>$v){ + $order_info[$k] = $v; + } + $order_info['customer_address_state_name'] =Yii::$service->helper->country->getStateByContryCode($order_info['customer_address_country'],$order_info['customer_address_state']); + $order_info['customer_address_country_name'] = Yii::$service->helper->country->getCountryNameByKey($order_info['customer_address_country']); + $order_info['currency_symbol'] = Yii::$service->page->currency->getSymbol($order_info['order_currency_code']); + $order_info['products'] = Yii::$service->order->item->getByOrderId($order_id); + return $order_info; + } + + + /** + * @property $filter|Array + * @return Array; + * 通过过滤条件,得到coupon的集合。 + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionColl($filter=''){ + $query = MyOrder::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + $coll = $query->all(); + if(!empty($coll)){ + foreach($coll as $k => $one){ + $coll[$k] = $one; + } + } + return [ + 'coll' => $coll, + 'count'=> $query->count(), + ]; + } + /** + * @property $one|Array , save one data . + * @return Int 保存coupon成功后,返回保存的id。 + */ + protected function actionSave($one){ + $time = time(); + $primaryKey = $this->getPrimaryKey(); + $primaryVal = isset($one[$primaryKey]) ? $one[$primaryKey] : ''; + if($primaryVal){ + $model = MyOrder::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('coupon '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + + $model = new MyOrder; + $model->created_at = time(); + /* + if(isset(Yii::$app->user)){ + $user = Yii::$app->user; + if(isset($user->identity)){ + $identity = $user->identity; + $person_id = $identity['id']; + $model->created_person = $person_id; + } + } + */ + } + $model->updated_at = time(); + $saveStatus = Yii::$service->helper->ar->save($model,$one); + if(!$primaryVal){ + $primaryVal = Yii::$app->db->getLastInsertID(); + } + return $primaryVal; + } + + + /** + * @property $ids | Int or Array + * @return boolean + * 如果传入的是id数组,则删除多个 + * 如果传入的是Int,则删除一个 + * + */ + protected function actionRemove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + foreach($ids as $id){ + $model = MyOrder::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $model->delete(); + }else{ + Yii::$service->helper->errors->add("Coupon Remove Errors:ID $id is not exist."); + return false; + } + } + }else{ + $id = $ids; + $model = MyOrder::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $model->delete(); + }else{ + Yii::$service->helper->errors->add("Coupon Remove Errors:ID:$id is not exist."); + return false; + } + } + return true; + } + /** + * @property $increment_id | String , 订单号 + * @return Object (MyOrder),返回 MyOrder model + * 通过订单号,得到订单信息。 + */ + protected function actionGetByIncrementId($increment_id){ + $one = MyOrder::findOne(['increment_id' => $increment_id]); + $primaryKey = $this->getPrimaryKey(); + if($one[$primaryKey]){ + return $one; + } + } + + /** + * @property $address | Array 货运地址 + * @property $shipping_method | String 货运快递方式 + * @property $payment_method | Array 支付方式、 + * @return boolean 通过购物车的数据生成订单是否成功 + * 通过购物车中的产品信息,以及传递的货运地址,货运快递方式,支付方式生成订单。 + */ + protected function actionGenerateOrderByCart($address,$shipping_method,$payment_method){ + $cart = Yii::$service->cart->quote->getCurrentCart(); + if(!$cart){ + Yii::$service->helper->errors->add('current cart is empty'); + } + $currency_info = Yii::$service->page->currency->getCurrencyInfo(); + $currency_code = $currency_info['code']; + $currency_rate = $currency_info['rate']; + $cartInfo = Yii::$service->cart->getCartInfo($shipping_method,$weight,$country,$region); + $myOrder = new MyOrder; + $paymentStatus = $this->paymentStatus; + $myOrder['order_status'] = $paymentStatus['pending']; + $myOrder['store'] = $cartInfo['store']; + $myOrder['created_at'] = time(); + $myOrder['updated_at'] = time(); + $myOrder['items_count'] = $cartInfo['items_count']; + $myOrder['total_weight'] = $cartInfo['product_weight']; + $myOrder['order_currency_code'] = $currency_code; + $myOrder['order_to_base_rate'] = $currency_rate; + + $myOrder['grand_total'] = $cartInfo['grand_total']; + $myOrder['base_grand_total'] = $cartInfo['base_grand_total']; + $myOrder['subtotal'] = $cartInfo['product_total']; + $myOrder['base_subtotal'] = $cartInfo['base_product_total']; + $myOrder['subtotal_with_discount'] = $cartInfo['coupon_cost']; + $myOrder['base_subtotal_with_discount'] = $cartInfo['base_coupon_cost']; + $myOrder['shipping_total'] = $cartInfo['shipping_cost']; + $myOrder['base_shipping_total'] = $cartInfo['base_shipping_cost']; + + $myOrder['checkout_method'] = $this->getCheckoutType(); + if($address['customer_id']){ + $is_guest = 2; + }else{ + $is_guest = 1; + } + if(!Yii::$app->user->isGuest){ + $customer_id = Yii::$app->user->identity->id; + }else{ + $customer_id = ''; + } + $myOrder['customer_id'] = $customer_id; + $myOrder['customer_email'] = $address['email']; + $myOrder['customer_firstname'] = $address['first_name']; + $myOrder['customer_lastname'] = $address['last_name']; + $myOrder['customer_is_guest'] = $is_guest; + $myOrder['customer_telephone'] = $address['telephone']; + $myOrder['customer_address_country']= $address['country']; + $myOrder['customer_address_state'] = $address['state']; + $myOrder['customer_address_city'] = $address['city']; + $myOrder['customer_address_zip'] = $address['zip']; + $myOrder['customer_address_street1']= $address['street1']; + $myOrder['customer_address_street2']= $address['street2']; + + $myOrder['coupon_code'] = $cartInfo['coupon_code']; + $myOrder['payment_method'] = $payment_method; + $myOrder['shipping_method'] = $shipping_method; + $myOrder->save(); + $order_id = Yii::$app->db->getLastInsertId(); + $increment_id = $this->generateIncrementIdByOrderId($order_id); + $orderModel = $this->getByPrimaryKey($order_id); + if($orderModel[$this->getPrimaryKey()]){ + $orderModel['increment_id'] = $increment_id ; + $orderModel->save(); + Yii::$service->order->item->saveOrderItems($cartInfo['products'],$order_id,$cartInfo['store']); + $this->setSessionIncrementId($increment_id); + + return true; + } + return false; + } + /** + * @property $increment_id | String ,order订单号 + * 将生成的订单号写入session + */ + protected function actionSetSessionIncrementId($increment_id){ + + Yii::$app->session->set(self::CURRENT_ORDER_CREAMENT_ID,$increment_id); + } + /** + * 从session中取出来订单号 + */ + protected function actionGetSessionIncrementId(){ + return Yii::$app->session->get(self::CURRENT_ORDER_CREAMENT_ID); + } + /** + * 从session中销毁订单号 + */ + protected function actionRemoveSessionIncrementId(){ + return Yii::$app->session->remove(self::CURRENT_ORDER_CREAMENT_ID); + } + + /** + * @property $order_id | Int + * @return $increment_id | Int + * 通过 order_id 生成订单号。 + */ + protected function generateIncrementIdByOrderId($order_id){ + $increment_id = (int)$this->increment_id + (int)$order_id; + return $increment_id; + } + + /** + * get order list by customer account id. + */ + protected function actionGetCustomerOrderList($customer_id = ''){ + + + } + /** + * @property $order_id 订单id + * 订单支付成功后,更改订单的状态为支付成功状态。 + */ + protected function actionOrderPaySuccess($order_id){ + + + } + + + +} \ No newline at end of file diff --git a/services/Page.php b/services/Page.php new file mode 100644 index 000000000..4b9285c3d --- /dev/null +++ b/services/Page.php @@ -0,0 +1,25 @@ + + * @since 1.0 + */ +class Page extends Service +{ + + +} \ No newline at end of file diff --git a/services/Payment.php b/services/Payment.php new file mode 100644 index 000000000..a78555a13 --- /dev/null +++ b/services/Payment.php @@ -0,0 +1,132 @@ + + * @since 1.0 + */ +class Payment extends Service +{ + + public $paymentConfig; + protected $_currentPaymentMethod; + public function setPaymentMethod($payment_method){ + $this->_currentPaymentMethod = $payment_method; + } + public function getPaymentMethod(){ + return $this->_currentPaymentMethod; + } + /** + * @property $payment_method | String 支付方式。 + * @return 返回提交订单信息跳转到的第三方支付url,也就是第三方支付的url。 + */ + public function getStandardStartUrl($payment_method = ''){ + if(!$payment_method){ + $payment_method = $this->getPaymentMethod(); + } + if($payment_method){ + $paymentConfig = $this->paymentConfig; + if(isset($paymentConfig['standard'][$payment_method]['start_url'])){ + if(!empty($paymentConfig['standard'][$payment_method]['start_url'])){ + return $this->getUrl($paymentConfig['standard'][$payment_method]['start_url']); + } + } + } + } + /** + * @property $payment_method | String 支付方式。 + * @return 第三方支付成功后,返回到网站的url + */ + public function getStandardSuccessRedirectUrl($payment_method = ''){ + if(!$payment_method){ + $payment_method = $this->getPaymentMethod(); + } + if($payment_method){ + $paymentConfig = $this->paymentConfig; + if(isset($paymentConfig['standard'][$payment_method]['success_redirect_url'])){ + if(!empty($paymentConfig['standard'][$payment_method]['success_redirect_url'])){ + return $this->getUrl($paymentConfig['standard'][$payment_method]['success_redirect_url']); + } + } + } + } + /** + * @property $payment_method | String 支付方式。 + * @return 第三方网站发送ipn消息,告诉网站支付成功的url。 + */ + public function getStandardIpnUrl($payment_method = ''){ + if(!$payment_method){ + $payment_method = $this->getPaymentMethod(); + } + if($payment_method){ + $paymentConfig = $this->paymentConfig; + if(isset($paymentConfig['standard'][$payment_method]['IPN_url'])){ + if(!empty($paymentConfig['standard'][$payment_method]['IPN_url'])){ + return $this->getUrl($paymentConfig['standard'][$payment_method]['IPN_url']); + } + } + } + } + + protected function getUrl($url){ + $homeUrl = Yii::$service->url->homeUrl(); + $url = str_replace('@homeUrl',$homeUrl,$url); + return trim($url); + } + + /** + * @return Array 得到所有支付的数组。 + */ + public function getStandardPaymentArr(){ + $arr = []; + if( + isset($this->paymentConfig['standard']) && + is_array($this->paymentConfig['standard']) + ){ + foreach($this->paymentConfig['standard'] as $payment_type => $info){ + $label = $info['label']; + $imageUrl = ''; + if(is_array($info['image'])){ + list($iUrl,$l) = $info['image']; + if($iUrl){ + $imageUrl = Yii::$service->image->getImgUrl($iUrl,$l); + } + } + $supplement = $info['supplement']; + $arr[$payment_type] = [ + 'label' => $label, + 'imageUrl' => $imageUrl, + 'supplement' => $supplement, + ]; + } + } + return $arr; + } + + /** + * @property $shipping_method | String + * @return boolean 发货方式 + */ + protected function actionIfIsCorrectStandard($payment_method){ + $paymentConfig = $this->paymentConfig; + $standard = isset($paymentConfig['standard']) ? $paymentConfig['standard'] : ''; + if(isset($standard[$payment_method]) && !empty($standard[$payment_method])){ + return true; + }else{ + return false; + } + } + +} \ No newline at end of file diff --git a/services/Point.php b/services/Point.php new file mode 100644 index 000000000..e69de29bb diff --git a/services/Product.php b/services/Product.php new file mode 100644 index 000000000..b88bf52d2 --- /dev/null +++ b/services/Product.php @@ -0,0 +1,321 @@ + + * @since 1.0 + */ +class Product extends Service +{ + + public $storage = 'mongodb'; + public $customAttrGroup; + protected $_product; + protected $_defaultAttrGroup = 'default'; + + public function init(){ + if($this->storage == 'mongodb'){ + $this->_product = new ProductMongodb; + //}else if($this->storage == 'mysqldb'){ + //$this->_category = new CategoryMysqldb; + } + } + # Yii::$service->product->getCustomAttrGroup(); + /** + * 得到产品的所有的属性组。 + */ + protected function actionGetCustomAttrGroup(){ + $customAttrGroup = $this->customAttrGroup; + $arr = array_keys($customAttrGroup); + $arr[] = $this->_defaultAttrGroup; + return $arr; + } + /** + * @property $productAttrGroup|String + * 得到这个产品属性组里面的所有的产品属性, + * 注解:不同类型的产品,对应不同的属性组,譬如衣服有颜色尺码,电脑类型的有不同cpu型号等 + * 属性组,以及属性组对应的属性,是在Product Service config中配置的。 + */ + protected function actionGetGroupAttrInfo($productAttrGroup){ + $arr = []; + if($productAttrGroup == $this->_defaultAttrGroup){ + return []; + } + # 得到普通属性 + if(isset($this->customAttrGroup[$productAttrGroup]['general_attr']) + && is_array($this->customAttrGroup[$productAttrGroup]['general_attr']) + ){ + $arr = array_merge($arr,$this->customAttrGroup[$productAttrGroup]['general_attr']); + } + # 得到用于spu,细分sku的属性,譬如颜色尺码之类。 + if(isset($this->customAttrGroup[$productAttrGroup]['spu_attr']) + && is_array($this->customAttrGroup[$productAttrGroup]['spu_attr']) + ){ + $arr = array_merge($arr,$this->customAttrGroup[$productAttrGroup]['spu_attr']); + } + return $arr; + } + /** + * @property $productAttrGroup|String + * @return 一维数组 + * 得到这个产品属性组里面的 属性 + */ + protected function actionGetSpuAttr($productAttrGroup){ + $arr = []; + if($productAttrGroup == $this->_defaultAttrGroup){ + return []; + } + + # 得到用于spu,细分sku的属性,譬如颜色尺码之类。 + if(isset($this->customAttrGroup[$productAttrGroup]['spu_attr']) + && is_array($this->customAttrGroup[$productAttrGroup]['spu_attr']) + ){ + $arr = array_merge($arr,$this->customAttrGroup[$productAttrGroup]['spu_attr']); + } + return array_keys($arr); + } + # spu的属性目前不能超过两个选项,譬如衣服同一个spu + # 不同的sku 是通过颜色和尺码进行区分的。此时, color 和 size 被称为spu属性 + # 如果您有的产品可能有超过2个的spu属性,您可以通过拆分,将一个spu切分成多个spu + # 譬如:您的spu属性有三个: 颜色,尺码,材质。你可以把不同的材质设置成不同的spu + # 然后颜色和尺码作为spu属性,也就是同一个款式同一个材质的衣服,不同的颜色尺码是相同的spu,但是sku是不同的 + protected function actionIsCorrectSpuConfig($productAttrGroup){ + $spuAttrArr = $this->GetSpuAttr($productAttrGroup); + $count = count($spuAttrArr); + if( $count > 2 || $count <= 0 ){ + return false; + } + return true; + } + + protected function actionIsActive($status){ + return ($status == 1) ? true : false; + } + + protected function actionGetCustomOptionAttrInfo($productAttrGroup){ + $arr = []; + if($productAttrGroup == $this->_defaultAttrGroup){ + return []; + } + if(isset($this->customAttrGroup[$productAttrGroup]['custom_options']) + && is_array($this->customAttrGroup[$productAttrGroup]['custom_options']) + ){ + return $this->customAttrGroup[$productAttrGroup]['custom_options']; + } + } + + /** + * 得到默认的产品属性组。 + */ + protected function actionGetDefaultAttrGroup(){ + return $this->_defaultAttrGroup; + } + + /** + * 得到主键的名称 + */ + protected function actionGetPrimaryKey(){ + return $this->_product->getPrimaryKey(); + } + /** + * get Category model by primary key. + */ + protected function actionGetByPrimaryKey($primaryKey){ + return $this->_product->getByPrimaryKey($primaryKey); + } + + /** + * 通过sku查询产品 + */ + protected function actionGetBySku($sku){ + return $this->_product->getBySku($sku); + } + + /** + * 通过spu查询产品 + */ + protected function actionGetBySpu($spu){ + return $this->_product->getBySpu($spu); + } + + /** + * @property $filter|Array + * get artile collection by $filter + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + * where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionColl($filter=''){ + return $this->_product->coll($filter); + } + + protected function actionCollCount($filter=''){ + return $this->_product->collCount($filter); + } + + /** + * 通过where条件 和 查找的select 字段信息,得到产品的列表信息, + * 这里一般是用于前台的区块性的不分页的产品查找。 + * 结果数据没有进行进一步处理,需要前端获取数据后在处理。 + */ + protected function actionGetProducts($filter){ + return $this->_product->getProducts($filter); + } + /** + * @property $product_id_arr | Array + * @property $category_id | String + * 在给予的产品id数组$product_id_arr中,找出来那些产品属于分类 $category_id + * 该功能是后台分类编辑中,对应的分类产品列表功能 + * 也就是在当前的分类下,查看所有的产品,属于当前分类的产品,默认被勾选。 + */ + protected function actionGetCategoryProductIds($product_id_arr,$category_id){ + return $this->_product->getCategoryProductIds($product_id_arr,$category_id); + } + /** + * @property $one|Array , 产品数据数组 + * @property $originUrlKey|String , 分类的原来的url key ,也就是在前端,分类的自定义url。 + * 保存产品(插入和更新),以及保存产品的自定义url + * 如果提交的数据中定义了自定义url,则按照自定义url保存到urlkey中,如果没有自定义urlkey,则会使用name进行生成。 + */ + protected function actionSave($one,$originUrlKey='catalog/product/index'){ + return $this->_product->save($one,$originUrlKey); + } + /** + * @property $ids | Array or String + * 删除产品,如果ids是数组,则删除多个产品,如果是字符串,则删除一个产品 + * 在产品产品的同时,会在url rewrite表中删除对应的自定义url数据。 + */ + protected function actionRemove($ids){ + return $this->_product->remove($ids); + } + /** + * @property $category_id | String 分类的id的值 + * @property $addCateProductIdArr | Array 分类中需要添加的产品id数组,也就是给这个分类增加这几个产品。 + * @property $deleteCateProductIdArr | Array 分类中需要删除的产品id数组,也就是在这个分类下面去除这几个产品的对应关系。 + * 这个函数是后台分类编辑功能中使用到的函数,在分类中可以一次性添加多个产品,也可以删除多个产品,产品和分类是多对多的关系。 + */ + protected function actionAddAndDeleteProductCategory($category_id,$addCateProductIdArr,$deleteCateProductIdArr){ + return $this->_product->addAndDeleteProductCategory($category_id,$addCateProductIdArr,$deleteCateProductIdArr); + } + /** + *[ + * 'category_id' => 1, + * 'pageNum' => 2, + * 'numPerPage' => 50, + * 'orderBy' => 'name', + * 'where' => [ + * ['>','price',11], + * ['<','price',22], + * ], + * 'select' => ['xx','yy'], + * 'group' => '$spu', + * ] + * 得到分类下的产品,在这里需要注意的是: + * 1.同一个spu的产品,有很多sku,但是只显示score最高的产品,这个score可以通过脚本取订单的销量(最近一个月,或者 + * 最近三个月等等),或者自定义都可以。 + * 2.结果按照filter里面的orderBy排序 + * 3.由于使用的是mongodb的aggregate(管道)函数,因此,此函数有一定的限制,就是该函数 + * 处理后的结果不能大约32MB,因此,如果一个分类下面的产品几十万的时候可能就会出现问题, + * 这种情况可以用专业的搜索引擎做聚合工具。 + * 不过,对于一般的用户来说,这个不会成为瓶颈问题,一般一个分类下的产品不会出现几十万的情况。 + * 4.最后就得到spu唯一的产品列表(多个spu相同,sku不同的产品,只要score最高的那个) + */ + protected function actionGetFrontCategoryProducts($filter){ + return $this->_product->getFrontCategoryProducts($filter); + + } + /** + * @property $filter_attr | String 需要进行统计的字段名称 + * @propertuy $where | Array 搜索条件。这个需要些mongodb的搜索条件。 + * 得到的是个属性,以及对应的个数。 + * 这个功能是用于前端分类侧栏进行属性过滤。 + */ + protected function actionGetFrontCategoryFilter($filter_attr,$where){ + return $this->_product->getFrontCategoryFilter($filter_attr,$where); + } + /** + * 全文搜索 + * $filter Example: + * $filter = [ + * 'pageNum' => $this->getPageNum(), + * 'numPerPage' => $this->getNumPerPage(), + * 'where' => $this->_where, + * 'product_search_max_count' => Yii::$app->controller->module->params['product_search_max_count'], + * 'select' => $select, + * ]; + * 因为mongodb的搜索涉及到计算量,因此产品过多的情况下,要设置 product_search_max_count的值。减轻服务器负担 + * 因为对客户来说,前10页的产品已经足矣,后面的不需要看了,限定一下产品个数,减轻服务器的压力。 + * 多个spu,取score最高的那个一个显示。 + * 按照搜索的匹配度来进行排序,没有其他排序方式 + */ + //protected function actionFullTearchText($filter){ + // return $this->_product->fullTearchText($filter); + //} + + + /** + * @property $ids | Array + */ + protected function actionGetSkusByIds($ids){ + $skus = []; + $_id = $this->getPrimaryKey(); + if(!empty($ids) && is_array($ids)){ + $ids_ob_arr = []; + foreach($ids as $id){ + $ids_ob_arr[] = new \MongoId($id); + } + $filter = [ + 'where' => [ + ['in',$_id,$ids_ob_arr], + + ], + 'asArray' => true, + ]; + $coll = $this->coll($filter); + $data = $coll['coll']; + if(!empty($data) && is_array($data)){ + foreach($data as $one){ + $skus[$one[$_id]->{'$id'}] = $one['sku']; + } + } + } + return $skus; + } + /** + * @property $spu | String + * @property $avag_rate | Int 产品的总平均得分 + * @property $count | Int 产品的总评论数 + * @property $avag_lang_rate | 当前语言的总平均得分 + * @property $lang_count | 当前语言的总评论数 + */ + + protected function actionUpdateProductReviewInfo($spu,$avag_rate,$count,$lang_code,$avag_lang_rate,$lang_count){ + + return $this->_product->updateProductReviewInfo($spu,$avag_rate,$count,$lang_code,$avag_lang_rate,$lang_count); + } + +} + + diff --git a/services/Request.php b/services/Request.php new file mode 100644 index 000000000..ea3746a5c --- /dev/null +++ b/services/Request.php @@ -0,0 +1,135 @@ + + * @since 1.0 + */ +class Request extends \yii\web\Request +{ + /** + * rewrite yii\web\Request resolveRequestUri() + */ + protected function resolveRequestUri() + { + if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS + $requestUri = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $requestUri = $_SERVER['REQUEST_URI']; + if ($requestUri !== '' && $requestUri[0] !== '/') { + $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri); + } + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI + $requestUri = $_SERVER['ORIG_PATH_INFO']; + if (!empty($_SERVER['QUERY_STRING'])) { + $requestUri .= '?' . $_SERVER['QUERY_STRING']; + } + } else { + throw new InvalidConfigException('Unable to determine the request URI.'); + } + + /** + * Replace Code + * //return $requestUri; + * To: + */ + return $this->getRewriteUri($requestUri); + } + + /** + * get module request url by db ; + */ + protected function getRewriteUri($requestUri){ + $baseUrl = $this->getBaseUrl(); + $requestUriRelative = $requestUri; + if($baseUrl){ + $requestUriRelative = substr($requestUriRelative, strlen($baseUrl)); + } + + //echo $requestUriRelative;exit; + $urlKey = ''; + $urlParam = ''; + $urlParamSuffix = ''; + + if(strstr($requestUriRelative,"#")){ + list($urlNoSuffix,$urlParamSuffix)= explode("#",$requestUriRelative); + if(strstr($urlNoSuffix,"?")){ + list($urlKey,$urlParam)= explode("?",$urlNoSuffix); + } + }else if(strstr($requestUriRelative,"?")){ + list($urlKey,$urlParam)= explode("?",$requestUriRelative); + }else{ + $urlKey = $requestUriRelative; + } + if($urlParamSuffix){ + $urlParamSuffix = '#'.$urlParamSuffix; + } + if($originUrlPath = Yii::$app->url->getOriginUrl($urlKey)){ + if(strstr($originUrlPath,'?')){ + if($urlParam){ + $url = $originUrlPath.'&'.$urlParam.$urlParamSuffix; + }else{ + $url = $originUrlPath.$urlParamSuffix; + } + $this->setRequestParam($originUrlPath); + }else{ + if($urlParam){ + $url = $originUrlPath.'?'.$urlParam.$urlParamSuffix; + }else{ + $url = $originUrlPath.$urlParamSuffix; + } + } + return $baseUrl.$url; + }else{ + return $requestUri; + } + + } + + /** + * after get urlPath from db, if urlPath has get param , + * set the param to $_GET + */ + public function setRequestParam($originUrlPath){ + $arr = explode("?",$originUrlPath); + $yiiUrlParam = $arr[1]; + $arr = explode("&",$yiiUrlParam); + foreach($arr as $a){ + list($key,$val) = explode("=",$a); + $_GET[$key] = $val; + } + } + + /** + * mongodb url_rewrite collection columns: _id, type ,custom_url, yii_url, + * if selete date from UrlRewrite, return the yii url. + */ + protected function getOriginUrl($urlKey){ + $UrlData = UrlRewrite::find()->where([ + 'custom_url_key' => $urlKey, + ])->asArray()->one(); + if($UrlData['custom_url_key']){ + return $UrlData['origin_url']; + } + return ; + } + + + + +} \ No newline at end of file diff --git a/services/Search.php b/services/Search.php new file mode 100644 index 000000000..fdd6403b8 --- /dev/null +++ b/services/Search.php @@ -0,0 +1,150 @@ + + * @since 1.0 + */ +class Search extends Service +{ + //protected $_searchEngine; + /** + * 在搜索页面侧栏的搜索过滤属性字段 + */ + public $filterAttr; + + public function init(){ + //if($this->currentSearchEngine == 'MongoSearch'){ + // $this->_searchEngine = new MongoSearch; + //}else if($this->currentSearchEngine == 'XunSearch'){ + // $this->_searchEngine = new XunSearch; + //} + parent::init(); + } + + /** + * init search engine index + */ + protected function actionInitFullSearchIndex(){ + //exit; + $searchEngineList = $this->getAllChildServiceName(); + if(is_array($searchEngineList) && !empty($searchEngineList)){ + foreach($searchEngineList as $sE){ + $model = $this->{$sE}; + $model->initFullSearchIndex(); + } + } + } + /** + * @property $product_ids | Array 产品id数组 + * 批量处理,将所有产品 + */ + protected function actionSyncProductInfo($product_ids,$numPerPage=20){ + $searchEngineList = $this->getAllChildServiceName(); + if(is_array($searchEngineList) && !empty($searchEngineList)){ + foreach($searchEngineList as $sE){ + $model = $this->{$sE}; + $model->syncProductInfo($product_ids,$numPerPage); + } + } + } + /** + * 在批量更新脚本中,将no active 的产品,从搜索表中删除掉 + */ + protected function actionDeleteNotActiveProduct($nowTimeStamp){ + $searchEngineList = $this->getAllChildServiceName(); + if(is_array($searchEngineList) && !empty($searchEngineList)){ + foreach($searchEngineList as $sE){ + $model = $this->{$sE}; + $model->deleteNotActiveProduct($nowTimeStamp); + } + } + } + + /** + * 得到搜索的sku列表 + */ + protected function actionGetSearchProductColl($select,$where,$pageNum,$numPerPage,$product_search_max_count){ + $currentLangCode= Yii::$service->store->currentLangCode; + + if(!$currentLangCode){ + return ; + } + $searchEngineList = $this->getAllChildServiceName(); + if(is_array($searchEngineList) && !empty($searchEngineList)){ + foreach($searchEngineList as $sE){ + $service = $this->{$sE}; + $searchLang = $service->searchLang; + if(is_array($searchLang) && !empty($searchLang)){ + $searchLangCode = array_keys($searchLang); + # 如果当前store的语言,在当前的搜索引擎中支持,则会使用这个搜索,作为支持。 + + if(in_array($currentLangCode,$searchLangCode)){ + return $service->getSearchProductColl($select,$where,$pageNum,$numPerPage,$product_search_max_count); + } + } + } + } + } + + /** + * 得到搜索的sku列表侧栏的过滤 + * @property $filter_attr | Array + * @property $where | Array + */ + protected function actionGetFrontSearchFilter($filter_attr,$where){ + $currentLangCode= Yii::$service->store->currentLangCode; + if(!$currentLangCode){ + return ; + } + $searchEngineList = $this->getAllChildServiceName(); + if(is_array($searchEngineList) && !empty($searchEngineList)){ + foreach($searchEngineList as $sE){ + $service = $this->{$sE}; + $searchLang = $service->searchLang; + if(is_array($searchLang) && !empty($searchLang)){ + $searchLangCode = array_keys($searchLang); + # 如果当前store的语言,在当前的搜索引擎中支持,则会使用这个搜索,作为支持。 + + if(in_array($currentLangCode,$searchLangCode)){ + return $service->getFrontSearchFilter($filter_attr,$where); + } + } + } + } + } + + /** + * 通过product_id删除搜索数据 + * @property $product_id | \mongoId + */ + protected function actionRemoveByProductId($product_id){ + $searchEngineList = $this->getAllChildServiceName(); + if(is_array($searchEngineList) && !empty($searchEngineList)){ + foreach($searchEngineList as $sE){ + $service = $this->{$sE}; + $service->removeByProductId($product_id); + } + } + } + + + + + + + + +} diff --git a/services/Service.php b/services/Service.php new file mode 100644 index 000000000..2efd07494 --- /dev/null +++ b/services/Service.php @@ -0,0 +1,172 @@ + + * @since 1.0 + */ +class Service extends Object +{ + public $childService; + protected $_childService; + + protected $_beginCallTime; + protected $_beginCallCode; + protected $_callFuncLog; + /** + * + */ + public function __get($attr){ + return $this->getChildService($attr); + } + /** + * 通过call函数,去调用actionXxxx方法。 + */ + public function __call($originMethod,$arguments) { + if(isset($this->_callFuncLog[$originMethod])){ + $method = $this->_callFuncLog[$originMethod]; + }else{ + $method = 'action'.ucfirst($originMethod); + $this->_callFuncLog[$originMethod] = $method; + } + if(method_exists($this, $method)) { + $this->beginCall($originMethod,$arguments); + $return = call_user_func_array(array($this,$method),$arguments); + $this->endCall($originMethod,$arguments); + return $return; + }else{ + throw new InvalidCallException("fecshop service method is not exit. ".get_class($this)."::$method"); + } + } + + /** + * 得到services 里面配置的子服务childService的实例 + */ + protected function getChildService($childServiceName){ + if(!$this->_childService[$childServiceName]){ + $childService = $this->childService; + if(isset($childService[$childServiceName])){ + $service = $childService[$childServiceName]; + $this->_childService[$childServiceName] = Yii::createObject($service); + }else{ + throw new InvalidConfigException('Child Service ['.$childServiceName.'] is not find in '.get_called_class().', you must config it! '); + } + } + return $this->_childService[$childServiceName]; + } + + public function getAllChildServiceName(){ + $childService = $this->childService; + return array_keys($childService); + } + + /** + * 如果开启service log,则记录开始的时间。 + */ + protected function beginCall($originMethod,$arguments) { + if(Yii::$service->helper->log->isServiceLogEnable()){ + $this->_beginCallTime = microtime(true); + } + } + + /** + * @param $originMethod and $arguments,魔术方法传递的参数 + * 调用service后,调用endCall,目前用来记录log信息 + * 1. 如果service本身的调用,则不会记录,只会记录外部函数调用service + * 2. 同一次访问的service_uid 的值是一样的,这样可以把一次访问调用的serice找出来。 + */ + protected function endCall($originMethod,$arguments){ + if(Yii::$service->helper->log->isServiceLogEnable()){ + list($logTrace,$isCalledByThis) = $this->debugBackTrace(); + /** + * if function is called by $this ,not log it to mongodb. + */ + if($isCalledByThis){ + return; + } + $begin_microtime = $this->_beginCallTime; + $endCallTime = microtime(true); + $used_time = round(($endCallTime - $begin_microtime),4); + if(is_object($arguments)){ + $arguments = 'object'; + }else if(is_array($arguments)){ + $arguments = 'array'; + }else{ + $arguments = 'string or int or other'; + } + $serviceLogUid = Yii::$service->helper->log->getLogUid(); + $log_info = [ + 'service_uid' => $serviceLogUid, + 'current_url' => Yii::$service->url->getCurrentUrl(), + 'home_url' => Yii::$service->url->homeUrl(), + 'service_file' => get_class($this) , + 'service_method' => $originMethod , + 'service_method_argument' => $arguments , + 'begin_microtime' => $begin_microtime , + 'end_microtime' => $endCallTime , + 'used_time' => $used_time , + + 'process_date_time' => date('Y-m-d H:i:s') , + 'log_trace' => $logTrace, + ]; + + //Yii::$service->helper->log->fetchServiceLog($log_info); + Yii::$service->helper->log->printServiceLog($log_info); + } + } + + /** + * debug 追踪 + * 返回调用当前service的所有的文件。以及 行,类,类方法 。 + * 这几个方法将不会被记录:'__call','endCall','debugBackTrace','call_user_func_array' + * 如果$file 不存在,也不会记录。 + * @return Array, $arr里面存储执行的记录,$isCalledByThis 代表是否是当前的service内部方法调用, + * article 服务方法,在执行过程中,调用了一个内部的方法,追踪函数也会记录这个。 + */ + + protected function debugBackTrace(){ + $arr = []; + $isCalledByThis = false; + $d = debug_backtrace(); + $funcNotContainArr = [ + '__call','endCall','debugBackTrace','call_user_func_array' + ]; + $thisClass = get_class($this); + //echo '**'.$thisClass.'**'; + $i = 0; + $last_invoke_class = ''; + $last_sec_invoke_class = ''; + foreach($d as $e){ + $function = $e['function']; + $class = $e['class']; + //echo '**'.$class.'**'; + $file = $e['file']; + $line = $e['line']; + if( $file && !in_array($function,$funcNotContainArr)){ + $arr[] = $file.'('.$line.'),'.$class.'::'.$function.'()'; + $i++; + if($i===1){ + $last_invoke_class = $class; + }else if($i === 2){ + $last_sec_invoke_class = $class; + } + } + } + if($last_invoke_class === $last_sec_invoke_class){ + $isCalledByThis = true; + } + return [$arr,$isCalledByThis]; + } + +} \ No newline at end of file diff --git a/services/Shipping.php b/services/Shipping.php new file mode 100644 index 000000000..96ef27380 --- /dev/null +++ b/services/Shipping.php @@ -0,0 +1,186 @@ + + * @since 1.0 + */ +class Shipping extends Service +{ + public $shippingConfig; + public $shippingCsvDir; # 存放运费csv表格的文件路径。 + public $defaultShippingMethod; + + /** + * @property $method | String ,shipping_method key + * @return Array ,得到配置 + */ + protected function actionGetShippingMethod($shipping_method=''){ + $allmethod = $this->shippingConfig; + if($shipping_method){ + return isset($allmethod[$shipping_method]) ? $allmethod[$shipping_method] : ''; + }else{ + return $allmethod; + } + } + /** + * @property $shipping_method | String + * @return boolean 发货方式 + */ + protected function actionIfIsCorrect($shipping_method){ + $allmethod = $this->shippingConfig; + if(isset($allmethod[$shipping_method]) && !empty($allmethod[$shipping_method])){ + return true; + }else{ + return false; + } + } + /** + * @return string ,得到默认的运费方法 shipping_method key + * 配置中$this->shippingConfig 第一个参数就是默认 + */ + protected function actionGetDefaultShipping(){ + if($shippingMethod = $this->defaultShippingMethod){ + if(isset($shippingMethod['enable']) && $shippingMethod['enable']){ + $shipping = isset($shippingMethod['shipping']) ? $shippingMethod['shipping'] : ''; + if($shipping && $this->getShippingMethod($shipping)){ + return $shipping; + } + } + + } + return ''; + } + + # 通过方法,重量,国家,省,得到美元状态的运费金额 + /** + * @proeprty $shipping_method 货运方式的key + * @proeprty $weight 产品的总重量 + * @proeprty $country 货运国家 + * @proeprty $region 货运省份 + * @return Float 通过计算,得到在默认货币下的运费金额。 + */ + protected function actionGetShippingCostByCsvWeight($shipping_method,$weight,$country,$region='*'){ + $shippingArr = $this->getShippingByTableCsv($shipping_method); + $priceData = []; + if(isset($shippingArr[$country][$region])){ + $priceData = $shippingArr[$country][$region]; + }else if(isset($shippingArr[$country]['*'])){ + $priceData = $shippingArr[$country]['*']; + }else{ + $priceData = $shippingArr['*']['*']; + } + //var_dump($priceData); + $prev_weight = 0; + $prev_price = 0; + $last_price = 0; + if(is_array($priceData)){ + foreach($priceData as $data){ + $csv_weight = (float)$data[0]; + $csv_price = (float)$data[1]; + if($weight>=$csv_weight){ + $prev_weight = $csv_weight; + $prev_price = $csv_price; + continue; + }else{ + $last_price = $prev_price; + break; + } + } + if(!$last_price){ + $last_price = $csv_price; + } + return $last_price; + } + } + + /** + * @proeprty $shipping_method 货运方式的key + * @proeprty $weight 产品的总重量 + * @proeprty $country 货运国家 + * @return Array 当前货币下的运费的金额。 + * 运费是通过csv表格内容计算而来,如果cost==0,那么代表免邮的方式。 + * 该方法为:当前重量下,所有的运费方式对应的运费都计算出来,展示在下单页面,让用户选择。 + */ + protected function actionGetShippingCostWithSymbols($shipping_method,$weight,$country ='',$region='*'){ + //echo $weight; + $allmethod = $this->getShippingMethod(); + $m = $allmethod[$shipping_method]; + //var_dump($m );exit; + if(!empty($m) && is_array($m)){ + $cost = $m['cost']; + # csv方式 + if($cost === 'csv'){ + #通过 运费方式,重量,国家,得到美元的运费 + $usdCost = $this->getShippingCostByCsvWeight($shipping_method,$weight,$country,$region); + //echo $usdCost; + $currentCost = Yii::$service->page->currency->getCurrentCurrencyPrice($usdCost); + return [ + 'currCost' => $currentCost, + 'baseCost' => $usdCost, + ]; + # $cost = 0 代表为free shipping方式 + }else if($cost == 0){ + return [ + 'currCost' => number_format(0,2) , + 'baseCost' => 0, + ]; + } + } + } + + /** + * @property $shipping_method | String + * @return 得到货运方式的名字 + */ + protected static function actionGetShippingLabelByMethod($shipping_method){ + $s = $this->getShippingMethod($shipping_method); + return $s['label']; + } + + /** + * @property $shipping_method | String 货运方式的key + * @return Array ,通过csv表格,得到对应的运费数组信息 + */ + protected function getShippingByTableCsv($shipping_method){ + //$shippingCsvDir = '@common/config/shipping'; + $commonDir = Yii::getAlias($this->shippingCsvDir); + $csv = $commonDir."/".$shipping_method.".csv"; + $fp = fopen($csv, "r"); + $shippingArr = []; + $i = 0; + while(!feof($fp)) + { + if($i){ + $content = fgets($fp); + $arr = explode(",",$content); + $country = $arr[0]; + $Region = $arr[1]; + $Weight = $arr[3]; + $ShippingPrice = $arr[4]; + $shippingArr[$country][$Region][] = [$Weight,$ShippingPrice]; + } + $i++; + } + fclose($fp); + return $shippingArr; + } + + + + + + +} \ No newline at end of file diff --git a/services/Sitemap.php b/services/Sitemap.php new file mode 100644 index 000000000..e69de29bb diff --git a/services/Store.php b/services/Store.php new file mode 100644 index 000000000..b6169d84f --- /dev/null +++ b/services/Store.php @@ -0,0 +1,184 @@ + + * @since 1.0 + */ +class Store extends Service +{ + /** + * init by config file. + * all stores config . include : domain,language,theme,themePackage + */ + public $stores; + + /** + * current store language,for example: en_US,fr_FR + */ + public $currentLang; + /** + * current store language name + */ + public $currentLangName; + /** + * current store theme package + */ + //public $currentThemePackage = 'default'; + /** + * current store theme + */ + //public $currentTheme = 'default'; + /** + * 当前store的key,也就是当前的store + */ + public $currentStore; + /** + * current language code example : fr es cn ru. + */ + public $currentLangCode; + /** + * Bootstrap:init website, class property $currentLang ,$currentTheme and $currentStore. + * if you not config this ,default class property will be set. + * if current store_code is not config , InvalidValueException will be throw. + * class property $currentStore will be set value $store_code. + */ + protected function actionBootstrap($app){ + $host = explode('://' ,$app->getHomeUrl()); + $stores = $this->stores; + $init_compelte = 0; + if(is_array($stores) && !empty($stores)){ + foreach($stores as $store_code => $store){ + if($host[1] == $store_code){ + $this->html5DevideCheckAndRedirect($store_code,$store); + Yii::$service->store->currentStore = $store_code; + if(isset($store['language']) && !empty($store['language'])){ + Yii::$service->store->currentLang = $store['language']; + Yii::$service->store->currentLangCode = Yii::$service->fecshoplang->getLangCodeByLanguage($store['language']); + Yii::$service->store->currentLangName = $store['languageName']; + Yii::$service->page->translate->setLanguage($store['language']); + } + if(isset($store['theme']) && !empty($store['theme'])){ + Yii::$service->store->currentTheme = $store['theme']; + } + /** + * set local theme dir. + */ + if(isset($store['localThemeDir']) && $store['localThemeDir']){ + //Yii::$service->page->theme->localThemeDir = $store['localThemeDir']; + Yii::$service->page->theme->setLocalThemeDir($store['localThemeDir']); + } + /** + * set third theme dir. + */ + if(isset($store['thirdThemeDir']) && $store['thirdThemeDir']){ + //Yii::$service->page->theme->thirdThemeDir = $store['thirdThemeDir']; + Yii::$service->page->theme->setThirdThemeDir($store['thirdThemeDir']); + } + /** + * init store currency. + */ + if(isset($store['currency']) && !empty($store['currency'])){ + $currency = $store['currency']; + }else{ + $currency = ''; + } + Yii::$service->page->currency->initCurrency($currency); + /** + * current domian is config is store config. + */ + $init_compelte = 1; + break; + } + } + } + if(!$init_compelte){ + throw new InvalidValueException('this domain is not config in store component'); + } + + } + + /** + * mobile devide url redirect. + */ + protected function html5DevideCheckAndRedirect($store_code,$store){ + + if(!isset($store['mobile'])){ + return; + } + $mobileDetect = Yii::$service->helper->mobileDetect; + $enable = isset($store['mobile']['enable']) ? $store['mobile']['enable'] : false ; + if(!$enable){ + return; + } + $condition = isset($store['mobile']['condition']) ? $store['mobile']['condition'] : false ; + $redirectDomain = isset($store['mobile']['redirectDomain']) ? $store['mobile']['redirectDomain'] : false ; + if(is_array($condition) && !empty($condition) && !empty($redirectDomain)){ + if(in_array('phone',$condition) && in_array('tablet',$condition)){ + if($mobileDetect->isMobile()){ + $this->redirectMobile($store_code,$redirectDomain); + } + }else if(in_array('phone',$condition)){ + if( $mobileDetect->isMobile() && !$mobileDetect->isTablet() ){ + $this->redirectMobile($store_code,$redirectDomain); + } + }else if(in_array('tablet',$condition)){ + if( $mobileDetect->isTablet() ){ + $this->redirectMobile($store_code,$redirectDomain); + } + } + } + } + /** + * 设备满足什么条件的时候进行跳转。 + */ + protected function redirectMobile($store_code,$redirectDomain){ + $currentUrl = Yii::$service->url->getCurrentUrl(); + $redirectUrl = str_replace($store_code,$redirectDomain,$currentUrl); + header("Location:".$redirectUrl); + } + + + + + /** + * @property $attrVal|Array , language attr array , like ['title_en' => 'xxxx','title_fr' => 'yyyy'] + * @property $attrName|String, attribute name ,like: title ,description. + * if object or array attribute is a language attribute, you can get current + * language value by this function. + * if lang attribute in current store language is empty , default language attribute will be return. + * if attribute in default language value is empty, $attrVal will be return. + */ + protected function actionGetStoreAttrVal($attrVal,$attrName){ + $lang = $this->currentLangCode; + return Yii::$service->fecshoplang->getLangAttrVal($attrVal,$attrName,$lang); + } + + /** + * @return Array + * get all store info, one item in array format is: ['storeCode' => 'store language']. + */ + protected function actionGetStoresLang(){ + $stores = $this->stores; + $topLang = []; + foreach($stores as $storeCode=> $store){ + $languageName = $store['languageName']; + $topLang[$storeCode] = $languageName; + } + return $topLang; + } + + + + +} \ No newline at end of file diff --git a/services/Systemhelper.php b/services/Systemhelper.php new file mode 100644 index 000000000..5ee9a22b3 --- /dev/null +++ b/services/Systemhelper.php @@ -0,0 +1,25 @@ + + * @since 1.0 + */ +class Systemhelper extends Service +{ + public $controllerNameSpace ; + +} \ No newline at end of file diff --git a/services/Url.php b/services/Url.php new file mode 100644 index 000000000..5c7478764 --- /dev/null +++ b/services/Url.php @@ -0,0 +1,372 @@ + + * @since 1.0 + */ +class Url extends Service +{ + public $randomCount = 8; + public $showScriptName; + protected $_secure; + protected $_currentBaseUrl; + protected $_origin_url; + protected $_httpType; + protected $_httpBaseUrl; + protected $_httpsBaseUrl; + protected $_currentUrl; + /** + * About: 对于 \yii\helpers\CUrl 已经 封装了一些对url的操作,也就是基于yii2的url机制进行的 + * 但是对于前端并不适用,对于域名当首页http://xx.com这类url是没有问题,但是, + * 对于子目录当首页的时候就会出问题: 譬如:http://xx.com/zh , http://xx.com/es , http://xx.com/fr 这类得有一定目录的url,则不能满足要求 + * 另外前端页面为了seo要求,还会加入url自定义等要求,作为Yii2的url,已经不能满足要求, + * 因此这里重新封装,对于前端页面,请使用 Yii::$service->url. + * 对于admin部分,不会涉及到重写url和域名子目录作为htmlUrl的情况,因此admin部分还是可以用\yii\helpers\CUrl的。 + */ + /** + * save custom url to mongodb collection url_rewrite + * @param $str|String, example: fashion handbag women + * @param $originUrl|String , origin url ,example: /cms/home/index?id=5 + * @param $originUrlKey|String,origin url key, it can be empty ,or generate by system , or custom url key. + * @param $type|String, url rewrite type. + * @return rewrite Key. + */ + protected function actionSaveRewriteUrlKeyByStr($str,$originUrl,$originUrlKey,$type='system'){ + $str = trim($str); + $originUrl = $originUrl ? '/'.trim($originUrl,'/') : ''; + $originUrlKey = $originUrlKey ? '/'.trim($originUrlKey,'/') : ''; + if($originUrlKey){ + /** + * if originUrlKey and originUrl is exist in url rewrite collectons. + */ + $model = $this->find(); + $data_one = $model->where([ + 'custom_url_key' => $originUrlKey, + 'origin_url' => $originUrl, + ])->one(); + if(isset($data_one['custom_url_key'])){ + /** + * 只要进行了查询,就要更新一下rewrite url表的updated_at. + */ + $data_one->updated_at = time(); + $data_one->save(); + return $originUrlKey; + } + } + if($originUrlKey){ + $urlKey = $this->generateUrlByName($originUrlKey); + }else{ + $urlKey = $this->generateUrlByName($str); + } + if(strlen($urlKey)<=1){ + $urlKey .= $this->getRandom(); + } + if(strlen($urlKey)<=2){ + $urlKey .= '-'.$this->getRandom(); + } + $urlKey = $this->getRewriteUrlKey($urlKey,$originUrl); + $UrlRewrite = $this->findOne([ + 'origin_url' => $originUrl + ]); + if(!isset($UrlRewrite['origin_url'])){ + $UrlRewrite = $this->newModel(); + $UrlRewrite->created_at = time(); + } + $UrlRewrite->updated_at = time(); + $UrlRewrite->type = $type; + $UrlRewrite->custom_url_key = $urlKey; + $UrlRewrite->origin_url = $originUrl; + $UrlRewrite->save(); + return $urlKey; + } + + /** + * @property $url_key|String + * remove url rewrite data by $url_key,which is custom url key that saved in custom url modules,like articcle , product, category ,etc.. + */ + protected function actionRemoveRewriteUrlKey($url_key){ + $model = $this->findOne([ + 'custom_url_key' => $url_key, + ]); + if($model['custom_url_key']){ + $model->delete(); + } + + } + + /** + * get current url + */ + protected function actionGetCurrentUrl(){ + if(!$this->_currentUrl){ + $pageURL = 'http'; + if ($_SERVER["HTTPS"] == "on"){ + $pageURL .= "s"; + } + $pageURL .= "://"; + if ($_SERVER["SERVER_PORT"] != "80"){ + $pageURL .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"]; + }else{ + $pageURL .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"]; + } + $this->_currentUrl = $pageURL; + } + return $this->_currentUrl; + } + + protected function actionGetCurrentUrlNoParam(){ + $currentUrl = $this->getCurrentUrl(); + if(strstr($currentUrl,'?')){ + $currentUrl = substr($currentUrl,0,strpos($currentUrl,'?')); + } + return $currentUrl; + } + + /** + * @property $urlKey|String + * get $origin_url by $custom_url_key ,it is used for yii2 init, + * in (new fecshop\services\Request)->resolveRequestUri(), ## fecshop\services\Request is extend yii\web\Request + */ + protected function actionGetOriginUrl($urlKey){ + + return Yii::$service->url->rewrite->getOriginUrl($urlKey); + } + + /** + * @property $path|String, for example about-us.html, fashion-handbag/women.html + * genarate current store url by path. + * example: + * Yii::$service->url->getUrlByPath('cms/article/index?id=33'); + * Yii::$service->url->getUrlByPath('cms/article/index',['id'=>33]); + * Yii::$service->url->getUrlByPath('cms/article/index',['id'=>33],true); + */ + protected function actionGetUrl($path,$params=[],$https=false){ + $first_str = substr($path,0,1); + if($first_str == '/'){ + $jg = ''; + }else{ + $jg = '/'; + } + if($https){ + $baseUrl = $this->getHttpsBaseUrl(); + }else{ + $baseUrl = $this->getHttpBaseUrl(); + } + if(is_array($params) && !empty($params)){ + $arr = []; + foreach($params as $k => $v){ + $arr[] = $k.'='.$v; + } + return $baseUrl.$jg.$path.'?'.implode('&',$arr); + } + return $baseUrl.$jg.$path; + } + + /** + * get current base url , is was generate by http(or https ).'://'.store_code + */ + protected function actionGetCurrentBaseUrl(){ + if(!$this->_currentBaseUrl){ + $homeUrl = $this->homeUrl(); + if($this->showScriptName){ + $homeUrl .= '/index.php'; + } + if(!$this->_httpType) + $this->_httpType = $this->secure() ? 'https' : 'http'; + $this->_currentBaseUrl = str_replace("http",$this->_httpType,$homeUrl); + } + return $this->_currentBaseUrl; + } + + + /** + * get current home url , is was generate by 'http://'.store_code + */ + protected function actionHomeUrl(){ + return Yii::$app->getHomeUrl(); + } + + + + /** + * get http format base url. + */ + protected function getHttpBaseUrl(){ + if(!$this->_httpBaseUrl){ + $homeUrl = $this->homeUrl(); + if(strstr($homeUrl,'https://')){ + $this->_httpBaseUrl = str_replace('https://','http://',$homeUrl); + }else{ + $this->_httpBaseUrl = $homeUrl; + } + if($this->showScriptName){ + $this->_httpBaseUrl .= '/index.php'; + } + } + + return $this->_httpBaseUrl; + } + /** + * get https format base url. + */ + protected function getHttpsBaseUrl(){ + if(!$this->_httpsBaseUrl){ + $homeUrl = $this->homeUrl(); + if(strstr($homeUrl,'http://')){ + $this->_httpsBaseUrl = str_replace('http://','https://',$homeUrl); + }else{ + $this->_httpsBaseUrl = $homeUrl; + } + if($this->showScriptName){ + $this->_httpsBaseUrl .= '/index.php'; + } + } + return $this->_httpsBaseUrl; + } + + protected function newModel(){ + return Yii::$service->url->rewrite->newModel(); + } + protected function find(){ + return Yii::$service->url->rewrite->find(); + } + + + protected function findOne($where){ + return Yii::$service->url->rewrite->findOne($where); + } + + + + /** + * check current url type is http or https. https is secure url type. + */ + protected function secure(){ + if($this->_secure === null){ + $this->_secure = isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1) || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0; + } + return $this->_secure; + } + + /** + * get rewrite url key. + */ + protected function getRewriteUrlKey($urlKey,$originUrl){ + $model = $this->find(); + $data = $model->where([ + 'custom_url_key' => $urlKey, + ])->andWhere(['<>','origin_url',$originUrl]) + ->asArray()->one(); + if(isset($data['custom_url_key'])){ + $urlKey = $this->getRandomUrlKey($urlKey); + return $this->getRewriteUrlKey($urlKey,$originUrl); + }else{ + return $urlKey; + } + } + + + /** + * generate random string. + */ + protected function getRandom($length=''){ + if(!$length ){ + $length = $this->randomCount; + } + $str = null; + $strPol = "123456789"; + $max = strlen($strPol)-1; + for($i=0;$i<$length;$i++){ + $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数 + } + return $str; + + } + /** + * if url key is exist in url_rewrite table ,Behind url add some random string + */ + protected function getRandomUrlKey($url){ + if($this->_origin_url){ + $suffix = ''; + $o_url = $this->_origin_url; + if(strstr($this->_origin_url,'.')){ + list($o_url,$suffix) = explode('.',$this->_origin_url); + $randomStr = $this->getRandom(); + return $o_url.'-'.$randomStr.'.'.$suffix; + } + $randomStr = $this->getRandom(); + return $this->_origin_url.'-'.$randomStr; + } + } + + /** + * clear character that can not use for url. + */ + protected function generateUrlByName($name){ + $url = iconv('UTF-8', 'ASCII//TRANSLIT', $name); + $url = preg_replace("{[^a-zA-Z0-9_.| -]}", '', $url); + $url = strtolower(trim($url, '-')); + $url = preg_replace("{[_| -]+}", '-', $url); + $url = '/'.trim($url,'/'); + $this->_origin_url = $url; + return $url; + } + + /** + * @property $url|String 要处理的url , 一般是当前的url + * @property $removeUrlParamStr|String 在url中删除的部分,一般是某个key对应的某个val,譬如color=green + * @property $backToPage1|boolean 删除后,页数由原来的页数变成第一页? + */ + protected function actionRemoveUrlParamVal($url,$removeUrlParamStr,$backToPage1=true){ + + + $return_url = $url; + if(strstr($url,'?'.$removeUrlParamStr.'&')){ + $return_url = str_replace('?'.$removeUrlParamStr.'&','?',$url); + }else if(strstr($url,'?'.$removeUrlParamStr)){ + $return_url = str_replace('?'.$removeUrlParamStr,'',$url); + }else if(strstr($url,'&'.$removeUrlParamStr)){ + $return_url = str_replace('&'.$removeUrlParamStr,'',$url); + } + if($backToPage1){ + $pVal = Yii::$app->request->get('p'); + if($pVal){ + $originPUrl = 'p='.$pVal; + $afterPUrl = 'p=1'; + } + if($originPUrl){ + $return_url = str_replace($originPUrl,$afterPUrl,$return_url); + } + } + return $return_url; + } + /** + * url 跳转 + */ + protected function actionRedirect($url){ + if($url){ + //session_commit(); + header("Location: $url"); + } + } + protected function actionRedirectByUrlKey($urlKey,$params=[]){ + if($urlKey){ + $url = $this->getUrl($urlKey,$params); + //session_commit(); + header("Location: $url"); + } + } + +} \ No newline at end of file diff --git a/services/Wholesale.php b/services/Wholesale.php new file mode 100644 index 000000000..e69de29bb diff --git a/services/cart/Coupon.php b/services/cart/Coupon.php new file mode 100644 index 000000000..dc81c2dc6 --- /dev/null +++ b/services/cart/Coupon.php @@ -0,0 +1,436 @@ + + * @since 1.0 + */ +class Coupon extends Service +{ + + protected $_useCouponInit; # 是否初始化这个百年两 + protected $_customer_id; # 用户id + protected $_coupon_code; # 优惠卷码 + protected $_coupon_model; # 优惠券model + protected $_coupon_usage_model; # 优惠券使用次数记录model + + + protected function actionGetPrimaryKey(){ + + return 'coupon_id'; + } + /** + * @property $primaryKey | Int + * @return Object(MyCoupon) + * 通过id找到cupon的对象 + */ + protected function actionGetByPrimaryKey($primaryKey){ + $one = MyCoupon::findOne($primaryKey); + $primaryKey = $this->getPrimaryKey(); + if($one[$primaryKey]){ + return $one; + }else{ + return new MyCoupon; + } + } + + + + protected function actionGetCouponUsageModel($customer_id='',$coupon_id=''){ + + if(!$this->_coupon_usage_model){ + if(!$customer_id){ + $customer_id = $this->_customer_id; + } + if(!$coupon_id){ + $couponModel = $this->getCouponModel(); + $coupon_id = isset($couponModel['coupon_id']) ? $couponModel['coupon_id'] : ''; + } + if($customer_id && $coupon_id){ + $one = MyCouponUsage::findOne([ + 'customer_id' => $customer_id, + 'coupon_id' => $coupon_id, + ]); + if($one['customer_id']){ + $this->_coupon_usage_model = $one; + } + } + } + if($this->_coupon_usage_model){ + return $this->_coupon_usage_model; + } + } + + + protected function actionGetCouponModel($coupon_code = ''){ + + if(!$this->_coupon_model){ + if(!$coupon_code){ + $coupon_code = $this->_coupon_code; + } + $one = MyCoupon::findOne(['coupon_code' => $coupon_code]); + + if($one['coupon_code']){ + $this->_coupon_model = $one; + } + } + if($this->_coupon_model){ + return $this->_coupon_model; + } + } + + + + /** + * @property $filter|Array + * @return Array; + * 通过过滤条件,得到coupon的集合。 + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionColl($filter=''){ + $query = MyCoupon::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + $coll = $query->all(); + if(!empty($coll)){ + foreach($coll as $k => $one){ + $coll[$k] = $one; + } + } + //var_dump($one); + return [ + 'coll' => $coll, + 'count'=> $query->count(), + ]; + } + + /** + * @property $one|Array , save one data . + * @return Int 保存coupon成功后,返回保存的id。 + */ + protected function actionSave($one){ + $time = time(); + $primaryKey = $this->getPrimaryKey(); + $primaryVal = isset($one[$primaryKey]) ? $one[$primaryKey] : ''; + if($primaryVal){ + $model = MyCoupon::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('coupon '.$this->getPrimaryKey().' is not exist'); + return; + }else{ + $o_one = MyCoupon::find() + ->where(['coupon_code' =>$one['coupon_code']]) + ->andWhere(['!=',$primaryKey,$primaryVal]) + ->one() + ; + if($o_one[$primaryKey]){ + Yii::$service->helper->errors->add('coupon_code must be unique'); + return; + } + } + }else{ + $o_one = MyCoupon::find() + ->where(['coupon_code' =>$one['coupon_code']]) + ->one() + ; + if($o_one[$primaryKey]){ + Yii::$service->helper->errors->add('coupon_code must be unique'); + return; + } + $model = new MyCoupon; + $model->created_at = time(); + if(isset(Yii::$app->user)){ + $user = Yii::$app->user; + if(isset($user->identity)){ + $identity = $user->identity; + $person_id = $identity['id']; + $model->created_person = $person_id; + } + } + } + $model->updated_at = time(); + $saveStatus = Yii::$service->helper->ar->save($model,$one); + if(!$primaryVal){ + $primaryVal = Yii::$app->db->getLastInsertID(); + } + return $primaryVal; + } + + + /** + * @property $ids | Int or Array + * @return boolean + * 如果传入的是id数组,则删除多个 + * 如果传入的是Int,则删除一个coupon + * + */ + protected function actionRemove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + foreach($ids as $id){ + $model = MyCoupon::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $model->delete(); + }else{ + Yii::$service->helper->errors->add("Coupon Remove Errors:ID $id is not exist."); + return false; + } + } + }else{ + $id = $ids; + $model = MyCoupon::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $model->delete(); + }else{ + Yii::$service->helper->errors->add("Coupon Remove Errors:ID:$id is not exist."); + return false; + } + } + return true; + } + + /** + * @property $coupon_code | String 优惠卷码 + * 初始化对象变量,这个函数是在使用优惠券 和 取消优惠券的时候, + * 调用相应方法前,通过这个函数初始化类变量 + * $_useCouponInit # 是否初始化过,如果初始化过了,则不会执行 + * $_customer_id # 用户id + * $_coupon_code # 优惠卷码 + * $_coupon_model # 优惠券model + * $_coupon_usage_model # 优惠券使用次数记录model + * $ + */ + + protected function useCouponInit($coupon_code){ + if(!$this->_useCouponInit){ + # $this->_customer_id + if(Yii::$app->user->isGuest){ + $this->_customer_id = ''; + }else{ + if(Yii::$app->user->identity->id){ + $this->_customer_id = Yii::$app->user->identity->id; + } + } + # $this->getCouponUsageModel(); + # $this->getCouponModel(); + + # $this->_coupon_code + $this->_coupon_code = $coupon_code; + + $this->_useCouponInit = 1; + } + } + /** + * + */ + # $this->getCouponUsageModel(); + # $this->getCouponModel(); + protected function couponIsActive(){ + + if($this->_customer_id){ + if($couponModel = $this->getCouponModel()){ + $expiration_date = $couponModel['expiration_date']; + # 未过期 + if($expiration_date > time()){ + $couponUsageModel = $this->getCouponUsageModel(); + $times_used = 0; + if($couponUsageModel['times_used']){ + $times_used = $couponUsageModel['times_used']; + } + $users_per_customer = $couponModel['users_per_customer']; + # 次数限制 + if($times_used < $users_per_customer){ + return true; + }else{ + Yii::$service->helper->errors->add("The coupon has exceeded the maximum number of uses"); + } + }else{ + Yii::$service->helper->errors->add("coupon is expired"); + + } + }else{ + Yii::$service->helper->errors->add("coupon is not exist"); + + } + } + } + + protected function actionGetDiscount($coupon_code,$dc_price){ + $discount_cost = 0; + $this->useCouponInit($coupon_code); + if($this->couponIsActive()){ + $couponModel = $this->getCouponModel(); + $type = $couponModel['type']; + $conditions = $couponModel['conditions']; + $discount = $couponModel['discount']; + //echo $conditions.'##'.$dc_price;;exit; + if($conditions <= $dc_price){ + + if($type == 1){ # 百分比 + $base_discount_cost = $discount/100 * $dc_price; + }else if($type == 2){ # 直接折扣 + $base_discount_cost = $dc_price - $discount; + } + $curr_discount_cost = Yii::$service->page->currency->getCurrentCurrencyPrice($base_discount_cost); + } + } + return [ + 'baseCost' => $base_discount_cost, + 'currCost' => $curr_discount_cost + ]; + } + + /** + * @property $type | String add or cancel + * @return boolean + * 增加或者减少优惠券使用的次数 + */ + + protected function updateCouponUse($type){ + if($this->_customer_id){ + $c_model = $this->getCouponModel(); + if($c_model){ + if($type == 'add'){ + $cu_model = $this->getCouponUsageModel(); + if(!$cu_model){ + $cu_model = new MyCouponUsage; + $cu_model->times_used = 1; + $cu_model->customer_id = $this->_customer_id; + $cu_model->coupon_id = $c_model['coupon_id']; + }else{ + $cu_model->times_used += 1; + } + $cu_model->save(); + $times_used = $c_model->times_used ? $c_model->times_used : 0; + $c_model->times_used = $times_used + 1; + $c_model->save(); + return true; + + }else if($type == 'cancel'){ + + $couponModel = $this->getCouponModel(); + + $cu_model = $this->getCouponUsageModel(); + + if($cu_model){ + + $cu_model->times_used -= 1; + $times_used = $c_model->times_used ? $c_model->times_used : 0; + $times_used = $times_used - 1; + if($cu_model->times_used >= 0 && $times_used >= 0){ + + $cu_model->save(); + $c_model->times_used = $times_used; + $c_model->save(); + return true; + } + } + + } + } + } + } + + /** + * @property $coupon_code | String 优惠卷码 + * 检查当前购物车中是否存在优惠券,如果存在,则覆盖当前的优惠券 + * 如果当前购物车没有使用优惠券,则检查优惠券是否可以使用 + * 如果优惠券可以使用,则使用优惠券进行打折。更新购物车信息。 + */ + + protected function actionAddCoupon($coupon_code){ + $this->useCouponInit($coupon_code); + if($this->couponIsActive()){ + $couponModel= $this->getCouponModel(); + $type = $couponModel['type']; + $conditions = $couponModel['conditions']; + $discount = $couponModel['discount']; + # 判断购物车金额是否满足条件 + $cartProduct = Yii::$service->cart->quoteItem->getCartProductInfo(); + + $product_total = isset($cartProduct['product_total']) ? $cartProduct['product_total'] : 0; + if($product_total){ + //var_dump($product_total); + $dc_price = Yii::$service->page->currency->getDefaultCurrencyPrice($product_total); + + if($dc_price > $conditions){ + # 事务更新购物侧的coupon 和优惠券的使用情况。 + $innerTransaction = Yii::$app->db->beginTransaction(); + try { + $set_status = Yii::$service->cart->quote->setCartCoupon($coupon_code); + $up_status = $this->updateCouponUse('add'); + if($set_status && $up_status){ + $innerTransaction->commit(); + return true; + } + $innerTransaction->rollBack(); + } catch (Exception $e) { + $innerTransaction->rollBack(); + } + }else{ + Yii::$service->helper->errors->add('The coupon can be used if the product amount in the shopping cart is more than '.$conditions.' dollars'); + + } + } + } + } + + # 取消优惠券 + # $this->getCouponUsageModel(); + # $this->getCouponModel(); + # $this->useCouponInit($coupon_code); + protected function actionCancelCoupon($coupon_code){ + + $this->useCouponInit($coupon_code); + if($this->_customer_id){ + $couponModel = $this->getCouponModel($coupon_code); + if($couponModel){ + //$couponModel = $this->getCouponModel($coupon_code); + //$couponUsageModel = $this->getCouponUsageModel($customer_id,$coupon_id); + $innerTransaction = Yii::$app->db->beginTransaction(); + try { + + $up_status = $this->updateCouponUse('cancel'); + $cancel_status = Yii::$service->cart->quote->cancelCartCoupon($coupon_code); + //echo $up_status.'##'.$set_status; + //echo 555; + if($up_status && $cancel_status){ + $innerTransaction->commit(); + return true; + } + $innerTransaction->rollBack(); + } catch (Exception $e) { + $innerTransaction->rollBack(); + } + } + } + } + +} \ No newline at end of file diff --git a/services/cart/Info.php b/services/cart/Info.php new file mode 100644 index 000000000..f4be0205a --- /dev/null +++ b/services/cart/Info.php @@ -0,0 +1,80 @@ + + * @since 1.0 + */ +class Info extends Service +{ + + public function validateProduct($item,$product){ + $qty = $item['qty']; + $product_id = $item['product_id']; + # 验证提交产品数据 + # 验证产品是否存在 + if(!$product['sku']){ + Yii::$service->helper->errors->add('this product is not exist'); + return false; + } + # 验证库存 是否库存满足? + $canSale = Yii::$service->product->info->productIsCanSale($product,$qty); + if(!$canSale){ + return false; + } + # 验证产品是否 + if($product['status'] != 1){ + Yii::$service->helper->errors->add('product is not active'); + return false; + } + return true; + } + + public function getCustomOptionSku($item,$product){ + $qty = $item['qty']; + $custom_option_sku = $item['custom_option_sku']; + $product_id = $item['product_id']; + + $co_sku = ''; + if($custom_option_sku){ + $product_custom_option = $product['custom_option']; + $co_sku = Yii::$service->product->info->getProductCOSku($custom_option_sku,$product_custom_option); + + if($co_sku){ + return $co_sku; + } + } + + + } + + + public function productIsCanSale($product,$qty){ + if($product['is_in_stock'] != 1){ + Yii::$service->helper->errors->add('this product is stock out'); + return false; + } + if($qty > $product['qty']){ + Yii::$service->helper->errors->add('product qty add to cart is gt product stock count'); + return false; + } + + + } + + + + +} \ No newline at end of file diff --git a/services/cart/Quote.php b/services/cart/Quote.php new file mode 100644 index 000000000..8825733d2 --- /dev/null +++ b/services/cart/Quote.php @@ -0,0 +1,554 @@ + + * @since 1.0 + */ +class Quote extends Service +{ + const SESSION_CART_ID = 'current_session_cart_id'; + protected $_cart_id; + protected $_cart; + protected $_shipping_cost; + /** + * 存储购物车的信息。 + */ + protected $cartInfo; + + /** + * @return Int 得到cart_id + * Cart的session的超时时间由session组件决定。 + */ + public function getCartId(){ + if(!$this->_cart_id){ + $cart_id = Yii::$app->session->get(self::SESSION_CART_ID); + $this->_cart_id = $cart_id; + } + return $this->_cart_id; + } + /** + * @property $address|Array 地址信息数组 + * @property $shipping_method | String 货运方式 + * @property $payment_method | String 支付方式 + * @property boolean + * 更新游客购物车信息,用户下次下单 或者 重新下单,可以不需要重新填写货运地址信息。 + */ + public function updateGuestCart($address,$shipping_method,$payment_method){ + $cart = $this->getCurrentCart(); + if($cart){ + $cart->customer_firstname = $address['first_name']; + $cart->customer_lastname = $address['last_name']; + $cart->customer_email = $address['email']; + $cart->customer_telephone = $address['telephone']; + $cart->customer_address_street1 = $address['street1']; + $cart->customer_address_street2 = $address['street2']; + $cart->customer_address_country = $address['country']; + $cart->customer_address_city = $address['city']; + $cart->customer_address_state = $address['state']; + $cart->customer_address_zip = $address['zip']; + + $cart->shipping_method = $shipping_method; + $cart->payment_method = $payment_method; + return $cart->save(); + } + } + + /** + * @property $address_id | int 用户customer address id + * @property $shipping_method 货运方式 + * @property $payment_method 支付方式 + * @property boolean + * 登录用户的cart信息,进行更新,更新cart的$address_id,$shipping_method,$payment_method。 + * 用途:对于登录用户,create new address(在下单页面),新创建的address会被保存, + * 然后需要把address_id更新到cart中。 + * 对于 shipping_method 和 payment_method,保存到cart中,下次进入下单页面,会被记录 + * 下次登录用户进行下单,进入下单页面,会自动填写。 + */ + public function updateLoginCart($address_id,$shipping_method,$payment_method){ + $cart = $this->getCurrentCart(); + if($cart && $address_id){ + + $cart->customer_address_id = $address_id; + $cart->shipping_method = $shipping_method; + $cart->payment_method = $payment_method; + return $cart->save(); + } + } + /** + * @return Object + * 得到当前的cart,如果当前的cart不存在, + * 则返回为空(注意,这个就是 getCurrentCart() 和 getCart()两个函数的区别) + */ + public function getCurrentCart(){ + if(!$this->_cart){ + $cart_id = $this->getCartId(); + if($cart_id){ + $one = MyCart::findOne(['cart_id' => $cart_id]); + if($one['cart_id']){ + $this->_cart = $one; + } + } + } + return $this->_cart; + } + /** + * 如果当前的Cart不存在,则创建Cart + * 如果当前的cart存在,则查询,如果查询得到cart,则返回,如果查询不到,则重新创建 + * 设置$this->_cart 为 上面新建或者查询得到的cart对象。 + */ + public function getCart(){ + if(!$this->_cart){ + $cart_id = $this->getCartId(); + if(!$cart_id){ + $this->createCart(); + }else{ + $one = MyCart::findOne(['cart_id' => $cart_id]); + if($one['cart_id']){ + $this->_cart = $one; + }else{ + # 如果上面查询为空,则创建cart + $this->createCart(); + } + } + } + return $this->_cart; + } + /** + * @property $cart | MyCart Object + * 设置$this->_cart 为 当前传递的$cart对象。 + */ + public function setCart($cart){ + $this->_cart = $cart; + } + /** + * 得到购物车中产品的个数。头部的ajax请求一般访问这个 + */ + public function getCartItemCount(){ + $items_count = 0; + if($cart_id = $this->getCartId()){ + $one = MyCart::findOne(['cart_id' => $cart_id]); + if($one['items_count']){ + $items_count = $one['items_count']; + } + } + return $items_count; + } + /** + * @property $item_qty | Int ,当$item_qty 不等于null时,代表 + * 已经知道购物车中产品的个数,不需要去cart_item表中查询。 + * 譬如清空购物车操作,直接就知道产品个数肯定为零。 + * 当购物车的产品变动后,更新cart表的产品总数 + */ + public function computeCartInfo($item_qty = null){ + if($item_qty === null){ + $item_qty = Yii::$service->cart->quoteItem->getItemQty(); + } + $cart = $this->getCart(); + $cart->items_count = $item_qty; + $cart->save(); + return true; + } + /** + * 得到购物车中 产品的总数 + */ + public function getCartItemsCount(){ + $cart = $this->getCart(); + return $cart->items_count; + } + /** + * 返回当前的购物车Db对象 + */ + /* + public function getMyCart(){ + if(!$this->_my_cart){ + if($cart_id = $this->getCartId()){ + if(!$this->_my_cart){ + $this->_my_cart = MyCart::findOne(['cart_id'=>$cart_id]); + } + }else{ + $this->createCart(); + } + } + return $this->_my_cart; + } + */ + + /** + * @property $cart_id | int + * 设置cart_id类变量以及session中记录当前cartId的值 + * Cart的session的超时时间由session组件决定。 + */ + protected function actionSetCartId($cart_id){ + $this->_cart_id = $cart_id; + + Yii::$app->session->set(self::SESSION_CART_ID,$cart_id); + } + /** + * 清空购物车。只删除购物车中的产品,但是购物车中的信息保留。 + */ + protected function actionClearCart(){ + Yii::$service->cart->quoteItem->removeItemByCartId(); + //Yii::$app->session->remove(self::SESSION_CART_ID); + } + /** + * 初始化创建cart信息, + * 在用户的第一个产品加入购物车时,会在数据库中创建购物车 + */ + protected function actionCreateCart(){ + $myCart = new MyCart; + $myCart->store = Yii::$service->store->currentStore; + $myCart->created_at = time(); + $myCart->updated_at = time(); + if(!Yii::$app->user->isGuest){ + $identity = Yii::$app->user->identity; + $id = $identity['id']; + $firstname = $identity['firstname']; + $lastname = $identity['lastname']; + $email = $identity['email']; + $myCart->customer_id = $id; + $myCart->customer_email = $email; + $myCart->customer_firstname = $firstname; + $myCart->customer_lastname = $lastname; + $myCart->customer_is_guest = 2; + }else{ + $myCart->customer_is_guest = 1; + } + $myCart->remote_ip = \fec\helpers\CFunc::get_real_ip(); + $myCart->app_name = Yii::$service->helper->getAppName(); + if($defaultShippingMethod = Yii::$service->shipping->getDefaultShipping()){ + $myCart->shipping_method = $defaultShippingMethod; + } + $myCart->save(); + $cart_id = Yii::$app->db->getLastInsertId(); + $this->setCartId($cart_id); + $this->setCart(MyCart::findOne($cart_id)); + } + /* + public function addCustomerDefautAddressToCart(){ + if(!Yii::$app->user->isGuest){ + $cart = $this->getCart(); + # 购物车没有customer address id,则 + # 使用登录用户的默认address + $identity = Yii::$app->user->identity; + //echo $cart['customer_id'] ; + //echo "##"; + //echo $identity['id']; + //exit; + if($cart['customer_id'] == $identity['id']){ + if(!isset($cart['customer_address_id']) || empty($cart['customer_address_id'])){ + $defaultAddress = Yii::$service->customer->address->getDefaultAddress(); + if(is_array($defaultAddress) && !empty($defaultAddress)){ + $cart->customer_telephone = isset($defaultAddress['telephone']) ? $defaultAddress['telephone'] : ''; + $cart->customer_email= isset($defaultAddress['email']) ? $defaultAddress['email'] : ''; + $cart->customer_firstname= isset($defaultAddress['first_name']) ? $defaultAddress['first_name'] : ''; + $cart->customer_lastname= isset($defaultAddress['last_name']) ? $defaultAddress['last_name'] : ''; + $cart->customer_address_id= isset($defaultAddress['address_id']) ? $defaultAddress['address_id'] : ''; + $cart->customer_address_country= isset($defaultAddress['country']) ? $defaultAddress['country'] : ''; + $cart->customer_address_state= isset($defaultAddress['state']) ? $defaultAddress['state'] : ''; + $cart->customer_address_city= isset($defaultAddress['city']) ? $defaultAddress['city'] : ''; + $cart->customer_address_zip= isset($defaultAddress['zip']) ? $defaultAddress['zip'] : ''; + $cart->customer_address_street1= isset($defaultAddress['street1']) ? $defaultAddress['street1'] : ''; + $cart->customer_address_street2= isset($defaultAddress['street2']) ? $defaultAddress['street2'] : ''; + $cart->save(); + $this->setCart($cart); + + } + + } + } + } + } + */ + + /** + * 购物车数据中是否含有address_id,address_id,是登录用户才会有的。 + */ + + public function hasAddressId(){ + $cart = $this->getCart(); + $address_id = $cart['customer_address_id']; + if($address_id){ + return true; + } + } + /** + * 得到购物车中的用户地址信息 + * + */ + public function getCartAddress(){ + $email = ''; + $first_name = ''; + $last_name = ''; + if(!Yii::$app->user->isGuest){ + $identity = Yii::$app->user->identity; + $email = isset($identity['email']) ? $identity['email'] : ''; + $first_name = isset($identity['first_name']) ? $identity['first_name'] : ''; + $last_name = isset($identity['last_name']) ? $identity['last_name'] : ''; + } + $cart = $this->getCurrentCart(); + $customer_email = isset($cart['customer_email']) ? $cart['customer_email'] : ''; + $customer_firstname = isset($cart['customer_firstname']) ? $cart['customer_firstname'] : ''; + $customer_lastname = isset($cart['customer_lastname']) ? $cart['customer_lastname'] : ''; + $customer_telephone = isset($cart['customer_telephone']) ? $cart['customer_telephone'] : ''; + $customer_address_country = isset($cart['customer_address_country']) ? $cart['customer_address_country'] : ''; + $customer_address_state = isset($cart['customer_address_state']) ? $cart['customer_address_state'] : ''; + $customer_address_city = isset($cart['customer_address_city']) ? $cart['customer_address_city'] : ''; + $customer_address_zip = isset($cart['customer_address_zip']) ? $cart['customer_address_zip'] : ''; + $customer_address_street1 = isset($cart['customer_address_street1']) ? $cart['customer_address_street1'] : ''; + $customer_address_street2 = isset($cart['customer_address_street2']) ? $cart['customer_address_street2'] : ''; + + $customer_email = $customer_email ? $customer_email : $email; + $customer_firstname = $customer_firstname ? $customer_firstname : $first_name; + $customer_lastname = $customer_lastname ? $customer_lastname : $last_name; + return [ + 'first_name' => $customer_firstname, + 'last_name' => $customer_lastname, + 'email' => $customer_email, + 'telephone' => $customer_telephone, + 'country' => $customer_address_country, + 'state' => $customer_address_state, + 'city' => $customer_address_city, + 'zip' => $customer_address_zip, + 'street1' => $customer_address_street1, + 'street2' => $customer_address_street2, + + ]; + } + + /** + * @property $shipping_method | String 传递的货运方式 + * @property $country | String 货运国家 + * @property $region | String 省市 + * @return boolean OR array ,如果存在问题返回false + * 如果没有问题,返回购物车的信息。 + * 对于可选参数,如果不填写,就是返回当前的购物车的数据。 + * 对于填写了参数,返回的是填写参数后的数据,这个一般是用户选择了了货运方式,国家等,然后 + * 实时的计算出来数据反馈给用户,但是,用户选择的数据并没有进入cart表 + */ + public function getCartInfo($shipping_method='',$country='',$region='*'){ + //echo 333;exit; + $cartInfoKey = $shipping_method.'-shipping-'.$country.'-country-'.$region.'-region'; + if(!isset($this->cartInfo[$cartInfoKey])){ + $cart_id = $this->getCartId(); + if(!$cart_id){ + return false; + } + $cart = $this->getCart(); + $items_qty = $cart['items_count']; + if($items_qty <= 0){ + return false; + } + $coupon_code = $cart['coupon_code']; + if(!$shipping_method){ + $shipping_method = $cart['shipping_method']; + } + $cart_product_info = Yii::$service->cart->quoteItem->getCartProductInfo(); + if(is_array($cart_product_info)){ + $product_weight = $cart_product_info['product_weight']; + $products = $cart_product_info['products']; + $product_total = $cart_product_info['product_total']; + $base_product_total = $cart_product_info['base_product_total']; + if($products && $product_total){ + $shippingCost = $this->getShippingCost($shipping_method,$product_weight,$country,$region); + $currShippingCost = $shippingCost['currCost']; + $baseShippingCost = $shippingCost['baseCost']; + //echo 333; + //var_dump([$base_product_total,$product_total]); + //exit; + //echo $coupon_code;exit; + $couponCost = $this->getCouponCost($base_product_total,$coupon_code); + + $baseDiscountCost = $couponCost['currCost']; + $currDiscountCost = $couponCost['baseCost']; + + $curr_grand_total = $product_total + $currShippingCost - $currDiscountCost; + $base_grand_total = $base_product_total + $baseShippingCost - $baseDiscountCost; + + + $this->cartInfo[$cartInfoKey] = [ + 'store' => $cart['store'], # store nme + 'items_count' => $cart['items_count'], # 购物车中的产品总数 + 'coupon_code' => $coupon_code, # coupon卷码 + + 'grand_total' => $base_grand_total, # 当前货币总金额 + 'shipping_cost' => $currShippingCost, # 当前货币,运费 + 'coupon_cost' => $currDiscountCost, # 当前货币,优惠券优惠金额 + 'product_total' => $product_total, # 当前货币,购物车中产品的总金额 + + 'base_grand_total' => $base_grand_total, # 基础货币总金额 + 'base_shipping_cost' => $baseShippingCost, # 基础货币,运费 + 'base_coupon_cost' => $baseDiscountCost, # 基础货币,优惠券优惠金额 + 'base_product_total' => $base_product_total, # 基础货币,购物车中产品的总金额 + + + 'products' => $products, #产品信息。 + 'product_weight'=> $product_weight, #产品的总重量。 + ]; + } + + } + } + return $this->cartInfo[$cartInfoKey]; + } + + /** + * @property $shippingCost | Array ,example: + * [ + * 'currCost' => 33.22, #当前货币的运费金额 + * 'baseCost' => 26.44, #基础货币的运费金额 + * ]; + * 设置快递运费金额。 + */ + + public function setShippingCost($shippingCost){ + $this->_shipping_cost = $shippingCost; + } + + /** + * @property $shipping_method | String 货运方式 + * @property $weight | Float 产品重量 + * @property $country | String 国家 + * @property $region | String 省/市 + * @return $this->_shipping_cost | Array ,format: + * [ + * 'currCost' => 33.22, #当前货币的运费金额 + * 'baseCost' => 26.44, #基础货币的运费金额 + * ]; + * 得到快递运费金额。 + */ + public function getShippingCost($shipping_method='',$weight='',$country='',$region=''){ + if(!$region){ + $region='*'; + } + if(!$this->_shipping_cost){ + //echo "$shipping_method,$weight,$country,$region"; + $shippingCost = Yii::$service->shipping->getShippingCostWithSymbols($shipping_method,$weight,$country,$region); + $this->_shipping_cost = $shippingCost; + //if(isset($shippingCost['currentCost'])){ + // $this->_shipping_cost = $shippingCost['currentCost']; + //} + } + return $this->_shipping_cost; + } + /** + * 得到优惠券的折扣金额 + * @return Array , example: + * [ + * 'baseCost' => $base_discount_cost, # 基础货币的优惠金额 + * 'currCost' => $curr_discount_cost # 当前货币的优惠金额 + * ] + */ + public function getCouponCost($base_product_total,$coupon_code){ + //echo '###'; var_dump($product_total);exit; + //list($base_product_total,$product_total) = $product_total; + //$dc_price = Yii::$service->page->currency->getDefaultCurrencyPrice($product_total); + $dc_discount = Yii::$service->cart->coupon->getDiscount($coupon_code,$base_product_total); + //var_dump($dc_discount);exit; + return $dc_discount; + } + /** + * @property $coupon_code | String + * 设置购物车的优惠券 + */ + public function setCartCoupon($coupon_code){ + $cart = $this->getCart(); + $cart->coupon_code = $coupon_code; + $cart->save(); + return true; + } + /** + * @property $coupon_code | String + * 取消购物车的优惠券 + */ + public function cancelCartCoupon($coupon_code){ + $cart = $this->getCart(); + $cart->coupon_code = null; + $cart->save(); + return true; + } + /** + * 当用户登录账号后,将用户未登录时的购物车和用户账号中保存 + * 的购物车信息进行合并。 + */ + public function mergeCartAfterUserLogin(){ + if(!Yii::$app->user->isGuest){ + $identity = Yii::$app->user->identity; + $customer_id = $identity['id']; + $email = $identity->email; + $customer_firstname = $identity->firstname; + $customer_lastname = $identity->lastname; + $customer_cart = $this->getCartByCustomerId($customer_id); + $cart_id = $this->getCartId(); + if(!$customer_cart){ + if($cart_id){ + $cart = $this->getCart(); + if($cart){ + $cart['customer_email'] = $email ; + $cart['customer_id'] = $customer_id ; + $cart['customer_firstname'] = $customer_firstname ; + $cart['customer_lastname'] = $customer_lastname ; + $cart['customer_is_guest'] = 2; + $cart->save(); + } + } + }else{ + $cart = $this->getCart(); + if(!$cart || !$cart_id){ + $cart_id = $customer_cart['cart_id']; + $this->setCartId($cart_id); + }else{ + # 将无用户产品(当前)和 购物车中的产品(登录用户对应的购物车)进行合并。 + $new_cart_id = $customer_cart['cart_id']; + if($cart['coupon_code']){ + # 如果有优惠券则取消,以登录用户的购物车的优惠券为准。 + Yii::$service->cart->coupon->cancelCoupon($cart['coupon_code']); + } + # 将当前购物车产品表的cart_id 改成 登录用户对应的cart_id + if($new_cart_id && $cart_id && ($new_cart_id != $cart_id)){ + Yii::$service->cart->quoteItem->updateCartId($new_cart_id,$cart_id); + # 当前的购物车删除掉 + $cart->delete(); + # 设置当前的cart_id + $this->setCartId($new_cart_id); + # 设置当前的cart + $this->setCart($customer_cart); + # 重新计算购物车中产品的个数 + $this->computeCartInfo(); + } + } + } + } + } + /** + * @property $customer_id | int + * @return MyCart Object。 + * 通过用户的customer_id,在cart表中找到对应的购物车 + */ + public function getCartByCustomerId($customer_id){ + if($customer_id){ + $one = MyCart::findOne(['customer_id' => $customer_id]); + if($one['cart_id']){ + return $one; + } + } + } + + + + + + +} \ No newline at end of file diff --git a/services/cart/QuoteItem.php b/services/cart/QuoteItem.php new file mode 100644 index 000000000..181977c26 --- /dev/null +++ b/services/cart/QuoteItem.php @@ -0,0 +1,326 @@ + + * @since 1.0 + */ +class QuoteItem extends Service +{ + protected $_my_cart_item; # 购物车cart item 对象 + protected $_cart_product_info; + + /** + * @property $item | Array, example: + * $item = [ + * 'product_id' => 22222, + * 'custom_option_sku' => red-xxl, + * 'qty' => 22, + * ]; + * 将某个产品加入到购物车中。在添加到cart_item表后,更新 + * 购物车中产品的总数。 + */ + public function addItem($item){ + $cart_id = Yii::$service->cart->quote->getCartId(); + if(!$cart_id){ + Yii::$service->cart->quote->createCart(); + $cart_id = Yii::$service->cart->quote->getCartId(); + } + # 查看是否存在此产品,如果存在,则相加个数 + $item_one = MyCartItem::find()->where([ + 'cart_id' => $cart_id, + 'product_id'=> $item['product_id'], + 'custom_option_sku' => $item['custom_option_sku'], + ])->one(); + if($item_one['cart_id']){ + $item_one->qty = $item['qty'] + $item_one['qty']; + $item_one->save(); + # 重新计算购物车的数量 + Yii::$service->cart->quote->computeCartInfo(); + }else{ + $item_one = new MyCartItem; + $item_one->store = Yii::$service->store->currentStore; + $item_one->cart_id = $cart_id; + $item_one->created_at = time(); + $item_one->updated_at = time(); + $item_one->product_id = $item['product_id']; + $item_one->qty = $item['qty']; + $item_one->custom_option_sku= $item['custom_option_sku']; + $item_one->save(); + # 重新计算购物车的数量 + Yii::$service->cart->quote->computeCartInfo(); + } + } + /** + * @property $item | Array, example: + * $item = [ + * 'product_id' => 22222, + * 'custom_option_sku' => red-xxl, + * 'qty' => 22, + * ]; + * @return boolean; + * 将购物车中的某个产品更改个数,更改后的个数就是上面qty的值。 + */ + public function changeItemQty($item){ + $cart_id = Yii::$service->cart->quote->getCartId(); + # 查看是否存在此产品,如果存在,则更改 + $item_one = MyCartItem::find()->where([ + 'cart_id' => $cart_id, + 'product_id'=> $item['product_id'], + 'custom_option_sku' => $item['custom_option_sku'], + ])->one(); + if($item_one['cart_id']){ + $item_one->qty = $item['qty']; + $item_one->save(); + # 重新计算购物车的数量 + Yii::$service->cart->quote->computeCartInfo(); + return true; + }else{ + Yii::$service->helper->errors->add('This product is not available in the shopping cart'); + return false; + } + } + /** + * 通过quoteItem表,计算得到所有产品的总数 + * 得到购物车中产品的总数,不要使用这个函数,这个函数的作用: + * 在购物车中产品有变动后,使用这个函数得到产品总数,更新购物车中 + * 的产品总数。 + */ + public function getItemQty(){ + $cart_id = Yii::$service->cart->quote->getCartId(); + $item_qty = 0; + if($cart_id){ + $data = MyCartItem::find()->where([ + 'cart_id' => $cart_id + ])->all(); + if(is_array($data) && !empty($data)){ + foreach($data as $one){ + $item_qty += $one['qty']; + } + } + } + return $item_qty; + } + /** + * @return Array , foramt: + * [ + * 'products' => $products, # 产品详细信息,详情参看代码中的$products。 + * 'product_total' => $product_total, # 产品的当前货币总额 + * 'base_product_total' => $base_product_total,# 产品的基础货币总额 + * 'product_weight'=> $product_weight, # 蟾皮的总重量、 + * ] + * 得到当前购物车的产品信息,具体参看上面的example array。 + */ + public function getCartProductInfo(){ + $cart_id = Yii::$service->cart->quote->getCartId(); + $products = []; + $product_total = 0; + $product_weight = 0; + if($cart_id){ + if(!isset($this->_cart_product_info[$cart_id])){ + $data = MyCartItem::find()->where([ + 'cart_id' => $cart_id + ])->all(); + if(is_array($data) && !empty($data)){ + foreach($data as $one){ + $product_id = $one['product_id']; + $product_one = Yii::$service->product->getByPrimaryKey($product_id); + if($product_one['_id']){ + $qty = $one['qty']; + $custom_option_sku = $one['custom_option_sku']; + $product_price_arr = Yii::$service->product->price->getCartPriceByProductId($product_id,$qty,$custom_option_sku,2); + $curr_product_price= isset($product_price_arr['curr_price']) ? $product_price_arr['curr_price'] : 0; + $base_product_price= isset($product_price_arr['base_price']) ? $product_price_arr['base_price'] : 0; + $product_price = isset($curr_product_price['value']) ? $curr_product_price['value'] : 0; + + $product_row_price = $product_price * $qty; + $product_total += $product_row_price; + + $base_product_row_price = $base_product_price * $qty; + $base_product_total += $base_product_row_price; + + $p_wt = $product_one['weight'] * $qty; + $product_weight += $p_wt; + $productSpuOptions = $this->getProductSpuOptions($product_one); + $products[] = [ + 'item_id' => $one['item_id'], + 'product_id' => $product_id , + 'sku' => $product_one['sku'], + 'name' => Yii::$service->store->getStoreAttrVal($product_one['name'],'name'), + 'qty' => $qty , + 'custom_option_sku' => $custom_option_sku , + 'product_price' => $product_price , + 'product_row_price' => $product_row_price , + + 'base_product_price' => $base_product_price , + 'base_product_row_price' => $base_product_row_price , + + 'product_name' => $product_one['name'], + 'product_weight' => $p_wt, + 'product_row_weight'=> $p_wt * $qty, + 'product_url' => $product_one['url_key'], + 'product_image' => $product_one['image'], + 'custom_option' => $product_one['custom_option'], + 'spu_options' => $productSpuOptions, + ]; + //var_dump($product_one['image']);exit; + } + } + $this->_cart_product_info[$cart_id] = [ + 'products' => $products, + 'product_total' => $product_total, + 'base_product_total' => $base_product_total, + 'product_weight'=> $product_weight, + ]; + } + } + return $this->_cart_product_info[$cart_id]; + } + } + /** + * @property $productOb | Object,类型:\fecshop\models\mongodb\Product + * 得到产品的spu对应的属性以及值。 + * 概念 - spu options:当多个产品是同一个spu,但是不同的sku的时候,他们的产品表里面的 + * spu attr 的值是不同的,譬如对应鞋子,size 和 color 就是spu attr,对于同一款鞋子,他们 + * 是同一个spu,对于尺码,颜色不同的鞋子,是不同的sku,他们的spu attr 就是 color 和 size。 + */ + protected function getProductSpuOptions($productOb){ + $custom_option_info_arr = []; + if(isset($productOb['attr_group']) && !empty($productOb['attr_group'])){ + $productAttrGroup = $productOb['attr_group']; + $attrInfo = Yii::$service->product->getGroupAttrInfo($productAttrGroup); + if(is_array($attrInfo) && !empty($attrInfo)){ + $attrs = array_keys($attrInfo); + \fecshop\models\mongodb\Product::addCustomProductAttrs($attrs); + } + $productOb = Yii::$service->product->getByPrimaryKey($productOb['_id']->{'$id'}); + $spuArr = Yii::$service->product->getSpuAttr($productAttrGroup); + if(is_array($spuArr) && !empty($spuArr)){ + foreach($spuArr as $spu_attr){ + if(isset($productOb[$spu_attr]) && !empty($productOb[$spu_attr])){ + $custom_option_info_arr[$spu_attr] = $productOb[$spu_attr]; + } + } + } + } + return $custom_option_info_arr ; + } + /** + * @property $item_id | Int , quoteItem表的id + * @return boolean + * 将这个item_id对应的产品个数+1. + */ + public function addOneItem($item_id){ + $cart_id = Yii::$service->cart->quote->getCartId(); + if($cart_id){ + $one = MyCartItem::find()->where([ + 'cart_id' => $cart_id, + 'item_id' => $item_id, + ])->one(); + if($one['item_id']){ + $one['qty'] = $one['qty'] + 1; + $one->save(); + # 重新计算购物车的数量 + Yii::$service->cart->quote->computeCartInfo(); + return true; + } + } + return false; + } + /** + * @property $item_id | Int , quoteItem表的id + * @return boolean + * 将这个item_id对应的产品个数-1. + */ + public function lessOneItem($item_id){ + $cart_id = Yii::$service->cart->quote->getCartId(); + if($cart_id){ + $one = MyCartItem::find()->where([ + 'cart_id' => $cart_id, + 'item_id' => $item_id, + ])->one(); + if($one['item_id']){ + if($one['qty'] > 1){ + $one['qty'] = $one['qty'] - 1; + $one->save(); + # 重新计算购物车的数量 + Yii::$service->cart->quote->computeCartInfo(); + return true; + } + } + } + return false; + } + /** + * @property $item_id | Int , quoteItem表的id + * @return boolean + * 将这个item_id对应的产品删除 + */ + public function removeItem($item_id){ + $cart_id = Yii::$service->cart->quote->getCartId(); + if($cart_id){ + $one = MyCartItem::find()->where([ + 'cart_id' => $cart_id, + 'item_id' => $item_id, + ])->one(); + if($one['item_id']){ + $one->delete(); + # 重新计算购物车的数量 + Yii::$service->cart->quote->computeCartInfo(); + return true; + } + } + return false; + } + /** + * @property $cart_id | int 购物车id + * 删除购物车中的所有产品。 + * 注意:清空购物车并不是清空所有信息,仅仅是清空用户购物车中的产品。 + */ + public function removeItemByCartId($cart_id=''){ + if(!$cart_id){ + $cart_id = Yii::$service->cart->quote->getCartId(); + } + if($cart_id){ + $items = MyCartItem::deleteAll([ + 'cart_id' => $cart_id, + //'item_id' => $item_id, + ]); + # 重新计算购物车的数量 + Yii::$service->cart->quote->computeCartInfo(0); + } + return true; + } + /** + * @property $new_cart_id | int 更新后的cart_id + * @property $cart_id | int 更新前的cart_id + * 删除购物车中的所有产品。 + * 这里仅仅更改cart表的cart_id, 而不会做其他任何事情。 + */ + public function updateCartId($new_cart_id,$cart_id){ + if($cart_id && $new_cart_id){ + MyCartItem::updateAll( + ['cart_id'=>$new_cart_id], # $attributes + 'cart_id = '.$cart_id # $condition + ); + # 重新计算购物车的数量 + //Yii::$service->cart->quote->computeCartInfo(); + return true; + } + return false; + } + + +} \ No newline at end of file diff --git a/services/category/CategoryInterface.php b/services/category/CategoryInterface.php new file mode 100644 index 000000000..76cbb89f9 --- /dev/null +++ b/services/category/CategoryInterface.php @@ -0,0 +1,20 @@ + + * @since 1.0 + */ +interface CategoryInterface{ + + public function getByPrimaryKey($primaryKey); + public function coll($filter); + public function save($one,$originUrlKey); + public function remove($ids); +} \ No newline at end of file diff --git a/services/category/CategoryMongodb.php b/services/category/CategoryMongodb.php new file mode 100644 index 000000000..b611e8c50 --- /dev/null +++ b/services/category/CategoryMongodb.php @@ -0,0 +1,377 @@ + + * @since 1.0 + */ +class CategoryMongodb implements CategoryInterface +{ + public $numPerPage = 20; + /** + * 返回主键。 + */ + public function getPrimaryKey(){ + return '_id'; + } + /** + * 通过主键,得到Category对象。 + */ + public function getByPrimaryKey($primaryKey){ + if($primaryKey){ + return Category::findOne($primaryKey); + }else{ + return new Category; + } + } + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + * 'where' => [ + * ['>','price','1'], + * ['<','price','10'], + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function coll($filter=''){ + $query = Category::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + } + + /** + * 得到总数 + */ + public function collCount($filter=''){ + $query = Category::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return $query->count(); + } + + /** + * @property $one|Array , save one data . 分类数组 + * @property $originUrlKey|String , 分类的在修改之前的url key.(在数据库中保存的url_key字段,如果没有则为空) + * 保存分类,同时生成分类的伪静态url(自定义url),如果按照name生成的url或者自定义的urlkey存在,系统则会增加几个随机数字字符串,来增加唯一性。 + */ + public function save($one,$originUrlKey='catalog/category/index'){ + $currentDateTime = \fec\helpers\CDate::getCurrentDateTime(); + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + if($primaryVal){ + $model = Category::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('Category '.$this->getPrimaryKey().' is not exist'); + return; + } + $parent_id = $model['parent_id']; + }else{ + $model = new Category; + $model->created_at = time(); + $model->created_user_id = \fec\helpers\CUser::getCurrentUserId(); + $primaryVal = new \MongoId; + $model->{$this->getPrimaryKey()} = $primaryVal; + $parent_id = $one['parent_id']; + } + # 增加分类的级别字段level,从1级级别开始依次类推。 + if($parent_id === '0'){ + $model['level'] = 1; + }else{ + $parent_model = Category::findOne($parent_id); + if($parent_level = $parent_model['level']){ + $model['level'] = $parent_level + 1; + } + } + $model->updated_at = time(); + unset($one['_id']); + + $saveStatus = Yii::$service->helper->ar->save($model,$one); + $originUrl = $originUrlKey.'?'.$this->getPrimaryKey() .'='. $primaryVal; + $originUrlKey = isset($one['url_key']) ? $one['url_key'] : ''; + $defaultLangTitle = Yii::$service->fecshoplang->getDefaultLangAttrVal($one['name'],'name'); + $urlKey = Yii::$service->url->saveRewriteUrlKeyByStr($defaultLangTitle,$originUrl,$originUrlKey); + $model->url_key = $urlKey; + $model->save(); + return true; + } + + + /** + * @property $id | String 主键值 + * 通过主键值找到分类,并且删除分类在url rewrite表中的记录 + * 查看这个分类是否存在子分类,如果存在子分类,则删除所有的子分类,以及子分类在url rewrite表中对应的数据。 + */ + public function remove($id){ + if(!$id){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + + $model = Category::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + Yii::$service->url->removeRewriteUrlKey($url_key); + $model->delete(); + $this->removeChildCate($id); + }else{ + Yii::$service->helper->errors->add("Category Remove Errors:ID:$id is not exist."); + return false; + } + + return true; + } + + protected function removeChildCate($id){ + $data = Category::find()->where(['parent_id'=>$id])->all(); + if(!empty($data)){ + foreach($data as $one){ + $idVal = $one['_id']->{'$id'}; + if($this->hasChildCategory($idVal)){ + $this->removeChildCate($idVal); + } + $url_key = $one['url_key']; + Yii::$service->url->removeRewriteUrlKey($url_key); + $one->delete(); + } + } + } + /** + * 得到分类的树数组。 + * 数组中只有 id name(default language), child(子分类) 等数据。 + * 目前此函数仅仅用于后台对分类的编辑使用。 appadmin + */ + public function getTreeArr($rootCategoryId = '',$lang=''){ + $arr = []; + if(!$lang){ + $lang = Yii::$service->fecshoplang->defaultLangCode; + } + if(!$rootCategoryId){ + $where = ['parent_id' => '0']; + }else{ + $where = ['parent_id' => $rootCategoryId]; + } + $categorys = Category::find()->asArray()->where($where)->all(); + //var_dump($categorys);exit; + $idKey= $this->getPrimaryKey(); + if(!empty($categorys)){ + foreach($categorys as $cate){ + $idVal = $cate[$idKey]->{'$id'}; + $arr[$idVal] = [ + $idKey => $idVal, + 'name' => Yii::$service->fecshoplang->getLangAttrVal($cate['name'],'name',$lang), + ]; + //echo $arr[$idVal]['name']; + + if($this->hasChildCategory($idVal)){ + $arr[$idVal]['child'] = $this->getTreeArr($idVal,$lang); + } + } + } + return $arr; + } + protected function hasChildCategory($idVal){ + $one = Category::find()->asArray()->where(['parent_id'=>$idVal])->one(); + if(!empty($one)){ + return true; + } + return false; + } + /** + * @property $parent_id|String + * 通过当前分类的parent_id字段(当前分类的上级分类id),得到所有的上级信息数组。 + * 里面包含的信息为:name,url_key。 + * 譬如一个分类为三级分类,将他的parent_id传递给这个函数,那么,他返回的数组信息为[一级分类的信息(name,url_key),二级分类的信息(name,url_key)]. + * 目前这个功能用于前端分类页面的面包屑导航。 + */ + public function getAllParentInfo($parent_id){ + if($parent_id){ + $parentModel = Category::findOne($parent_id); + $parent_parent_id = $parentModel['parent_id']; + $parent_category = []; + if($parent_parent_id !== '0'){ + $parent_category[] = [ + 'name' => $parentModel['name'], + 'url_key'=>$parentModel['url_key'], + ]; + $parent_category = array_merge($this->getAllParentInfo($parent_parent_id),$parent_category); + }else{ + $parent_category[] = [ + 'name' => $parentModel['name'], + 'url_key'=>$parentModel['url_key'], + ]; + } + return $parent_category; + } + } + + protected function getParentCategory($parent_id){ + if($parent_id === '0'){ + return []; + } + $category = Category::find()->asArray()->where(['_id' => new \MongoId($parent_id)])->one(); + if(isset($category['_id']) && !empty($category['_id']) ){ + $currentUrlKey = $category['url_key']; + $currentName = $category['name']; + $currentId = $category['_id']->{'$id'}; + + $currentCategory[] = [ + '_id' => $currentId, + 'name' => $currentName, + 'url_key' => $currentUrlKey, + 'parent_id' => $category['parent_id'], + ]; + $parentCategory = $this->getParentCategory($category['parent_id']); + + return array_merge($parentCategory,$currentCategory); + + }else{ + return []; + } + } + /** + * @property $category_id|String 当前的分类_id + * @property $parent_id|String 当前的分类上级id parent_id + * 这个功能是点击分类后,在产品分类页面侧栏的子分类菜单导航,详细的逻辑如下: + * 1.如果level为一级,那么title部分为当前的分类,子分类为一级分类下的二级分类 + * 2.如果level为二级,那么将所有的二级分类列出,当前的二级分类,会列出来当前二级分类对应的子分类 + * 3.如果level为三级,那么将所有的二级分类列出。当前三级分类的所有姊妹分类(同一个父类)列出,当前三级分类如果有子分类,则列出 + * 4.依次递归。 + * 具体的显示效果,请查看appfront 对应的分类页面。 + */ + public function getFilterCategory($category_id,$parent_id){ + $returnData = []; + $primaryKey = $this->getPrimaryKey(); + $currentCategory = Category::findOne($category_id); + $currentUrlKey = $currentCategory['url_key']; + $currentName = $currentCategory['name']; + $currentId = $currentCategory['_id']->{'$id'}; + $returnData['current'] = [ + '_id' => $currentId, + 'name' => $currentName, + 'url_key' => $currentUrlKey, + 'parent_id' => $currentCategory['parent_id'], + ]; + if($currentCategory['parent_id']){ + $allParent = $this->getParentCategory($currentCategory['parent_id']); + $allParent[] = $returnData['current']; + $data = $this->getAllParentCate($allParent); + }else{ + $data = $this->getOneLevelCateChild($returnData['current']); + } + return $data; + } + + protected function getOneLevelCateChild($category){ + //'_id' => $currentId, + //'name' => $currentName, + //'url_key' => $currentUrlKey, + //$category['current'] = true; + //$data[0] = $category; + $_id = $category['_id']; + $name = $category['name']; + $url_key = $category['url_key']; + $cate = Category::find()->asArray()->where([ + 'parent_id' => $_id, + ])->all(); + if(is_array($cate) && !empty($cate)){ + foreach($cate as $one){ + $c_id = $one['_id']->{'$id'}; + $data[$c_id] = [ + 'name' => $one['name'], + 'url_key' => $one['url_key'], + 'parent_id' => $one['parent_id'], + ]; + } + } + return $data; + } + + + protected function getAllParentCate($allParent){ + //var_dump($allParent);exit; + $d = $allParent; + $data = []; + if(is_array($allParent) && !empty($allParent)){ + foreach($allParent as $k => $category){ + unset($d[$k]); + $category_id = $category['_id']; + $parent_id = $category['parent_id']; + if($parent_id){ + $cate = Category::find()->asArray()->where([ + 'parent_id' => $parent_id, + ])->all(); + //var_dump($cate); + //echo '$$$$$$$$$$'; + if(is_array($cate) && !empty($cate)){ + //echo '**********'; + foreach($cate as $one){ + $c_id = $one['_id']->{'$id'}; + $data[$c_id] = [ + 'name' => $one['name'], + 'url_key' => $one['url_key'], + 'parent_id' => $one['parent_id'], + ]; + //echo $category_id; + //echo '&&&'.$c_id; + if(($c_id == $category_id) && !empty($d)){ + $data[$c_id]['child'] = $this->getAllParentCate($d); + } + if(($c_id == $category_id) && empty($d)){ + $child_cate = $this->getChildCate($c_id); + $data[$c_id]['current'] = true; + if(!empty($child_cate)){ + $data[$c_id]['child'] = $child_cate; + } + } + } + } + break; + } + } + } + return $data; + } + + protected function getChildCate($category_id){ + //echo $category_id; + $data = Category::find()->asArray()->where([ + 'parent_id' => $category_id, + ])->all(); + $arr = []; + if(is_array($data) && !empty($data)){ + foreach($data as $one){ + $currentUrlKey = $one['url_key']; + $currentName = $one['name']; + $currentId = $one['_id']->{'$id'}; + + $arr[$currentId] = [ + //'_id' => $currentId, + 'name' => $currentName, + 'url_key' => $currentUrlKey, + 'parent_id' => $one['parent_id'], + ]; + } + } + return $arr; + } + + +} + + diff --git a/services/category/CategoryMysqldb.php b/services/category/CategoryMysqldb.php new file mode 100644 index 000000000..e69de29bb diff --git a/services/category/Image.php b/services/category/Image.php new file mode 100644 index 000000000..27ddb8dfc --- /dev/null +++ b/services/category/Image.php @@ -0,0 +1,82 @@ + + * @since 1.0 + */ +class Image extends Service +{ + /** + * absolute image save floder + */ + public $imageFloder = 'media/catalog/category'; + /** + * upload image max size + */ + public $maxUploadMSize; + /** + * allow image type + */ + public $allowImgType = [ + 'image/jpeg', + 'image/gif', + 'image/png', + 'image/jpg', + 'image/pjpeg', + ]; + + /** + * õͼƬԸĿ¼url· + */ + protected function actionGetBaseUrl(){ + return Yii::$service->image->GetImgUrl($this->imageFloder,'common'); + } + /** + * õͼƬԸĿ¼ļ· + */ + protected function actionGetBaseDir(){ + return Yii::$service->image->GetImgDir($this->imageFloder,'common'); + } + /** + * ͨͼƬ·õƷͼƬurl + */ + protected function actionGetUrl($str){ + return Yii::$service->image->GetImgUrl($this->imageFloder.$str,'common'); + } + /** + * ͨƷͼƬ·õƷͼƬľ· + */ + protected function actionGetDir(){ + return Yii::$service->image->GetImgDir($this->imageFloder.$str,'common'); + } + + + /** + * @property $param_img_file | Array . + * upload image from web page , you can get image from $_FILE['XXX'] , + * $param_img_file is get from $_FILE['XXX']. + * return , if success ,return image saved relative file path , like '/b/i/big.jpg' + * if fail, reutrn false; + */ + protected function actionSaveCategoryUploadImg($FILE){ + Yii::$service->image->imageFloder = $this->imageFloder; + Yii::$service->image->allowImgType = $this->allowImgType; + if($this->maxUploadMSize){ + Yii::$service->image->setMaxUploadSize($this->maxUploadMSize); + } + return Yii::$service->image->saveUploadImg($FILE); + } + + +} \ No newline at end of file diff --git a/services/category/Menu.php b/services/category/Menu.php new file mode 100644 index 000000000..7391c9a18 --- /dev/null +++ b/services/category/Menu.php @@ -0,0 +1,70 @@ + + * @since 1.0 + */ +class Menu extends Service +{ + public $rootCategoryId = '0'; + + /** + * @property $parentId|Int + * 得到分类的目录信息 + * + */ + protected function actionGetCategoryMenuArr($parentId=''){ + $arr = []; + if(!$parentId) + $parentId = $this->rootCategoryId; + $data = Category::find()->asArray()->select([ + '_id','parent_id','name','url_key','menu_custom' + ])->where([ + 'parent_id' => $parentId + ])->all(); + + if(is_array($data) && !empty($data)){ + foreach($data as $category){ + $categoryOne = [ + '_id' => $category['_id']->{'$id'}, + 'name' => Yii::$service->store->getStoreAttrVal($category['name'],'name'), + 'menu_custom'=> Yii::$service->store->getStoreAttrVal($category['menu_custom'],'menu_custom'), + 'url' => Yii::$service->url->getUrl($category['url_key']), + ]; + $childMenu = $this->getCategoryMenuArr($category['_id']->{'$id'}); + if($childMenu){ + $categoryOne['childMenu'] = $childMenu; + } + $arr[] = $categoryOne; + } + return $arr; + } + return ''; + } + + /** + * @property $categoryId|Array + * check if cateogry has child . + */ + protected function hasChild($categoryId){ + $one = Category::find()->asArray()->where([ + 'parent_id' => $categoryId + ])->one(); + if($one['_id']) + return true; + return false; + } +} \ No newline at end of file diff --git a/services/category/Product.php b/services/category/Product.php new file mode 100644 index 000000000..d0509514f --- /dev/null +++ b/services/category/Product.php @@ -0,0 +1,130 @@ + + * @since 1.0 + */ +class Product extends Service +{ + + public $pageNum = 1; + public $numPerPage = 50; + public $allowedNumPerPage ; + + /** + * @property $filter | Array example: + * [ + 'category_id' => 1, + 'pageNum' => 2, + 'numPerPage' => 50, + 'orderBy' => 'name', + 'where' => [ + ['>','price',11], + ['<','price',22], + ], + ] + * 通过搜索条件得到当类下的产品。 + */ + + protected function actionColl($filter){ + $category_id = isset($filter['category_id']) ? $filter['category_id'] : ''; + if(!$category_id){ + Yii::$service->helper->errors->add('category id is empty'); + return ; + }else{ + unset($filter['category_id']); + $filter['where'][] = ['category' => $category_id]; + } + if(!isset($filter['pageNum']) || !$filter['pageNum']){ + $filter['pageNum'] = 1; + } + if(!isset($filter['numPerPage']) || !$filter['numPerPage']){ + $filter['numPerPage'] = $this->numPerPage ; + } + if(isset($filter['orderBy']) && !empty($filter['orderBy'])){ + if(!is_array($filter['orderBy'])){ + Yii::$service->helper->errors->add('orderBy must be array'); + return; + } + } + return Yii::$service->product->coll($filter); + } + /** + * @property $filter | Array 和上面的函数 actionColl($filter) 类似。 + * + */ + protected function actionGetFrontList($filter){ + $filter['group'] = '$spu'; + $coll = Yii::$service->product->getFrontCategoryProducts($filter); + $collection = $coll['coll']; + $count = $coll['count']; + + $arr = $this->convertToCategoryInfo($collection); + return [ + 'coll' => $arr, + 'count'=> $count, + ]; + } + /** + * 将service取出来的数据,处理一下,然后前端显示。 + */ + protected function actionConvertToCategoryInfo($collection){ + $arr = []; + $defaultImg = Yii::$service->product->image->defautImg(); + if(is_array($collection) && !empty($collection)){ + foreach($collection as $one){ + if(is_array($one['name']) && !empty($one['name'])){ + $name = Yii::$service->store->getStoreAttrVal($one['name'],'name'); + }else{ + $name = $one['name']; + } + $image = $one['image']; + $url_key = $one['url_key']; + if(isset($image['main']['image']) && !empty($image['main']['image'])){ + $image = $image['main']['image']; + }else{ + $image = $defaultImg; + } + list($price,$special_price) = $this->getPrices($one['price'],$one['special_price'],$one['special_from'],$one['special_to']); + $arr[] = [ + 'name' => $name, + 'sku' => $one['sku'], + 'image' => $image, + 'price' => $price, + 'special_price' => $special_price, + 'url' => Yii::$service->url->getUrl($url_key), + ]; + } + } + return $arr; + } + + /** + * 处理,得到产品价格信息 + */ + protected function getPrices($price,$special_price,$special_from,$special_to){ + if(Yii::$service->product->price->specialPriceisActive($price,$special_price,$special_from,$special_to)){ + return [$price,$special_price]; + } + return [$price,0]; + } + + + + + + +} \ No newline at end of file diff --git a/services/cms/Article.php b/services/cms/Article.php new file mode 100644 index 000000000..de7f3a1cf --- /dev/null +++ b/services/cms/Article.php @@ -0,0 +1,90 @@ + + * @since 1.0 + */ +class Article extends Service +{ + public $storage = 'mongodb'; + protected $_article; + + public function init(){ + if($this->storage == 'mongodb'){ + $this->_article = new ArticleMongodb; + }else if($this->storage == 'mysqldb'){ + $this->_article = new ArticleMysqldb; + } + } + /** + * Get Url by article's url key. + */ + //public function getUrlByPath($urlPath){ + //return Yii::$service->url->getHttpBaseUrl().'/'.$urlKey; + //return Yii::$service->url->getUrlByPath($urlPath); + //} + /** + * get artile's primary key. + */ + protected function actionGetPrimaryKey(){ + return $this->_article->getPrimaryKey(); + } + /** + * get artile model by primary key. + */ + protected function actionGetByPrimaryKey($primaryKey){ + return $this->_article->getByPrimaryKey($primaryKey); + } + + + + /** + * @property $filter|Array + * get artile collection by $filter + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionColl($filter=''){ + return $this->_article->coll($filter); + } + + /** + * @property $one|Array , save one data . + * @property $originUrlKey|String , article origin url key. + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + protected function actionSave($one,$originUrlKey){ + return $this->_article->save($one,$originUrlKey); + } + + protected function actionRemove($ids){ + return $this->_article->remove($ids); + } + +} \ No newline at end of file diff --git a/services/cms/StaticBlock.php b/services/cms/StaticBlock.php new file mode 100644 index 000000000..502d1e9f7 --- /dev/null +++ b/services/cms/StaticBlock.php @@ -0,0 +1,125 @@ + + * @since 1.0 + */ +class Staticblock extends Service +{ + public $storage = 'mongodb'; + protected $_static_block; + /** + * init static block db. + */ + public function init(){ + if($this->storage == 'mongodb'){ + $this->_static_block = new StaticBlockMongodb; + }else if($this->storage == 'mysqldb'){ + $this->_static_block = new StaticBlockMysqldb; + } + } + /** + * get store static block content by identify + * example cms->staticblock->getStoreContentByIdentify('home-big-img','appfront') ?> + */ + protected function actionGetStoreContentByIdentify($identify,$app='common'){ + $staticBlock = $this->_static_block->getByIdentify($identify); + $content = $staticBlock['content']; + //echo '###'; + //var_dump($content); + //echo '###'; + $storeContent = Yii::$service->store->getStoreAttrVal($content,'content'); + //echo $storeContent; + $_params_ = $this->getStaticBlockVariableArr($app); + ob_start(); + ob_implicit_flush(false); + extract($_params_, EXTR_OVERWRITE); + foreach($_params_ as $k => $v){ + $key = '{{'.$k.'}}'; + if(strstr($storeContent,$key)){ + $storeContent = str_replace($key,$v,$storeContent); + } + } + echo $storeContent; + return ob_get_clean(); + } + /** + * staticblock中的变量,可以通过{{homeUlr}},来获取下面的值。 + */ + protected function getStaticBlockVariableArr($app){ + return [ + 'homeUrl' => Yii::$service->url->homeUrl(), + 'imgBaseUrl' => Yii::$service->image->getBaseImgUrl($app), + ]; + } + + /** + * get artile's primary key. + */ + protected function actionGetPrimaryKey(){ + return $this->_static_block->getPrimaryKey(); + } + /** + * get artile model by primary key. + */ + protected function actionGetByPrimaryKey($primaryKey){ + return $this->_static_block->getByPrimaryKey($primaryKey); + } + + + + /** + * @property $filter|Array + * get artile collection by $filter + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionColl($filter=''){ + return $this->_static_block->coll($filter); + } + + /** + * @property $one|Array , save one data . + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + protected function actionSave($one){ + return $this->_static_block->save($one); + } + + protected function actionRemove($ids){ + return $this->_static_block->remove($ids); + } + + +} + + + + + diff --git a/services/cms/article/ArticleInterface.php b/services/cms/article/ArticleInterface.php new file mode 100644 index 000000000..e88bd47da --- /dev/null +++ b/services/cms/article/ArticleInterface.php @@ -0,0 +1,20 @@ + + * @since 1.0 + */ +interface ArticleInterface{ + + public function getByPrimaryKey($primaryKey); + public function coll($filter); + public function save($one,$originUrlKey); + public function remove($ids); +} \ No newline at end of file diff --git a/services/cms/article/ArticleMongodb.php b/services/cms/article/ArticleMongodb.php new file mode 100644 index 000000000..62532f764 --- /dev/null +++ b/services/cms/article/ArticleMongodb.php @@ -0,0 +1,125 @@ + + * @since 1.0 + */ +class ArticleMongodb implements ArticleInterface +{ + public $numPerPage = 20; + + public function getPrimaryKey(){ + return '_id'; + } + + public function getByPrimaryKey($primaryKey){ + if($primaryKey){ + return Article::findOne($primaryKey); + }else{ + return new Article; + } + } + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + * 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function coll($filter=''){ + $query = Article::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + } + + /** + * @property $one|Array + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + public function save($one,$originUrlKey){ + $currentDateTime = \fec\helpers\CDate::getCurrentDateTime(); + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + if($primaryVal){ + $model = Article::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('article '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + $model = new Article; + $model->created_at = time(); + $model->created_user_id = \fec\helpers\CUser::getCurrentUserId(); + $primaryVal = new \MongoId; + $model->{$this->getPrimaryKey()} = $primaryVal; + } + $model->updated_at = time(); + unset($one['_id']); + $saveStatus = Yii::$service->helper->ar->save($model,$one); + $originUrl = $originUrlKey.'?'.$this->getPrimaryKey() .'='. $primaryVal; + $originUrlKey = isset($one['url_key']) ? $one['url_key'] : ''; + $defaultLangTitle = Yii::$service->fecshoplang->getDefaultLangAttrVal($one['title'],'title'); + $urlKey = Yii::$service->url->saveRewriteUrlKeyByStr($defaultLangTitle,$originUrl,$originUrlKey); + $model->url_key = $urlKey; + $model->save(); + return true; + } + + /** + * remove article + */ + public function remove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + foreach($ids as $id){ + $model = Article::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + Yii::$service->url->removeRewriteUrlKey($url_key); + $model->delete(); + }else{ + //throw new InvalidValueException("ID:$id is not exist."); + Yii::$service->helper->errors->add("Article Remove Errors:ID $id is not exist."); + return false; + } + } + }else{ + $id = $ids; + $model = Article::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + Yii::$service->url->removeRewriteUrlKey($url_key); + $model->delete(); + }else{ + Yii::$service->helper->errors->add("Article Remove Errors:ID:$id is not exist."); + return false; + } + } + return true; + } +} + + diff --git a/services/cms/article/ArticleMysqldb.php b/services/cms/article/ArticleMysqldb.php new file mode 100644 index 000000000..f8c6782ba --- /dev/null +++ b/services/cms/article/ArticleMysqldb.php @@ -0,0 +1,174 @@ + + * @since 1.0 + */ +class ArticleMysqldb implements ArticleInterface +{ + public $numPerPage = 20; + /** + * language attribute. + */ + protected $_lang_attr = [ + 'title', + 'meta_description', + 'content', + 'meta_keywords', + ]; + + public function getPrimaryKey(){ + return 'id'; + } + + public function getByPrimaryKey($primaryKey){ + if($primaryKey){ + $one = Article::findOne($primaryKey); + foreach($this->_lang_attr as $attrName){ + if(isset($one[$attrName])){ + $one[$attrName] = unserialize($one[$attrName]); + } + } + return $one; + }else{ + return new Article; + } + + } + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function coll($filter=''){ + + $query = Article::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + $coll = $query->all(); + if(!empty($coll)){ + foreach($coll as $k => $one){ + foreach($this->_lang_attr as $attr){ + $one[$attr] = $one[$attr] ? unserialize($one[$attr]) : ''; + } + $coll[$k] = $one; + } + } + //var_dump($one); + return [ + 'coll' => $coll, + 'count'=> $query->count(), + ]; + } + + /** + * @property $one|Array + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + public function save($one,$originUrlKey){ + $currentDateTime = \fec\helpers\CDate::getCurrentDateTime(); + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + if($primaryVal){ + $model = Article::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('article '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + $model = new Article; + $model->created_at = time(); + $model->created_user_id = \fec\helpers\CUser::getCurrentUserId(); + } + $model->updated_at = time(); + foreach($this->_lang_attr as $attrName){ + if(is_array($one[$attrName]) && !empty($one[$attrName]) ){ + $one[$attrName] = serialize($one[$attrName]); + } + } + + $saveStatus = Yii::$service->helper->ar->save($model,$one); + if(!$primaryVal){ + $primaryVal = Yii::$app->db->getLastInsertID(); + } + + $originUrl = $originUrlKey.'?'.$this->getPrimaryKey() .'='. $primaryVal; + $originUrlKey = isset($one['url_key']) ? $one['url_key'] : ''; + $defaultLangTitle = Yii::$service->fecshoplang->getDefaultLangAttrVal($one['title'],'title'); + $urlKey = Yii::$service->url->saveRewriteUrlKeyByStr($defaultLangTitle,$originUrl,$originUrlKey); + $model->url_key = $urlKey; + $model->save(); + + return true; + } + + public function remove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + $innerTransaction = Yii::$app->db->beginTransaction(); + try { + foreach($ids as $id){ + $model = Article::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + Yii::$service->url->removeRewriteUrlKey($url_key); + $model->delete(); + }else{ + + //throw new InvalidValueException("ID:$id is not exist."); + Yii::$service->helper->errors->add("Article Remove Errors:ID $id is not exist."); + $innerTransaction->rollBack(); + return false; + } + } + $innerTransaction->commit(); + } catch (Exception $e) { + Yii::$service->helper->errors->add("Article Remove Errors: transaction rollback"); + $innerTransaction->rollBack(); + return false; + } + }else{ + $id = $ids; + $model = Article::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $innerTransaction = Yii::$app->db->beginTransaction(); + try { + $url_key = $model['url_key']; + Yii::$service->url->removeRewriteUrlKey($url_key); + $model->delete(); + $innerTransaction->commit(); + } catch (Exception $e) { + Yii::$service->helper->errors->add("Article Remove Errors: transaction rollback"); + $innerTransaction->rollBack(); + } + }else{ + Yii::$service->helper->errors->add("Article Remove Errors:ID:$id is not exist."); + return false; + } + } + return true; + + } + +} \ No newline at end of file diff --git a/services/cms/staticblock/StaticBlockInterface.php b/services/cms/staticblock/StaticBlockInterface.php new file mode 100644 index 000000000..88f33e4d9 --- /dev/null +++ b/services/cms/staticblock/StaticBlockInterface.php @@ -0,0 +1,20 @@ + + * @since 1.0 + */ +interface StaticBlockInterface{ + + public function getByPrimaryKey($primaryKey); + public function coll($filter); + public function save($one); + public function remove($ids); +} \ No newline at end of file diff --git a/services/cms/staticblock/StaticBlockMongodb.php b/services/cms/staticblock/StaticBlockMongodb.php new file mode 100644 index 000000000..111b7e711 --- /dev/null +++ b/services/cms/staticblock/StaticBlockMongodb.php @@ -0,0 +1,134 @@ + + * @since 1.0 + */ +class StaticBlockMongodb implements StaticBlockInterface +{ + public $numPerPage = 20; + + public function getPrimaryKey(){ + return '_id'; + } + + public function getByPrimaryKey($primaryKey){ + if($primaryKey){ + return StaticBlock::findOne($primaryKey); + }else{ + return new StaticBlock; + } + } + + public function getByIdentify($identify){ + return StaticBlock::find()->asArray()->where([ + 'identify' => $identify + ])->one(); + } + + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function coll($filter=''){ + $query = StaticBlock::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + } + + /** + * @property $one|Array + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + public function save($one){ + $currentDateTime = \fec\helpers\CDate::getCurrentDateTime(); + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + if(!($this->validateIdentify($one))){ + Yii::$service->helper->errors->add('StaticBlock: identify存在,您必须定义一个唯一的identify '); + return; + } + if($primaryVal){ + $model = StaticBlock::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('StaticBlock '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + $model = new StaticBlock; + $model->created_at = time(); + $model->created_user_id = \fec\helpers\CUser::getCurrentUserId(); + $primaryVal = new \MongoId; + $model->{$this->getPrimaryKey()} = $primaryVal; + } + + $model->updated_at = time(); + unset($one['_id']); + $saveStatus = Yii::$service->helper->ar->save($model,$one); + return true; + } + + protected function validateIdentify($one){ + $identify = $one['identify']; + $id = $this->getPrimaryKey(); + $primaryVal = isset($one[$id]) ? $one[$id] : ''; + $where = ['identify' => $identify]; + $query = StaticBlock::find()->asArray(); + $query->where(['identify' => $identify]); + if($primaryVal){ + $query->andWhere([$id => ['$ne'=> new \MongoId($primaryVal)]]); + } + $one = $query->one(); + + if(!empty($one)){ + return false; + } + return true; + } + + /** + * remove Static Block + */ + public function remove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + foreach($ids as $id){ + $model = StaticBlock::findOne($id); + $model->delete(); + } + }else{ + $id = $ids; + $model = StaticBlock::findOne($id); + $model->delete(); + } + return true; + } +} + + diff --git a/services/cms/staticblock/StaticBlockMysqldb.php b/services/cms/staticblock/StaticBlockMysqldb.php new file mode 100644 index 000000000..b5555741f --- /dev/null +++ b/services/cms/staticblock/StaticBlockMysqldb.php @@ -0,0 +1,168 @@ + + * @since 1.0 + */ +class StaticBlockMysqldb implements StaticBlockInterface +{ + public $numPerPage = 20; + /** + * language attribute. + */ + protected $_lang_attr = [ + 'title', + 'content', + ]; + + public function getPrimaryKey(){ + return 'id'; + } + + public function getByPrimaryKey($primaryKey){ + if($primaryKey){ + $one = StaticBlock::findOne($primaryKey); + foreach($this->_lang_attr as $attrName){ + if(isset($one[$attrName])){ + $one[$attrName] = unserialize($one[$attrName]); + } + } + return $one; + }else{ + return new StaticBlock; + } + + } + + public function getByIdentify($identify){ + $one = StaticBlock::find()->asArray()->where([ + 'identify' => $identify + ])->one(); + foreach($this->_lang_attr as $attrName){ + if(isset($one[$attrName])){ + $one[$attrName] = unserialize($one[$attrName]); + } + } + return $one; + } + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function coll($filter=''){ + + $query = StaticBlock::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + $coll = $query->all(); + if(!empty($coll)){ + foreach($coll as $k => $one){ + foreach($this->_lang_attr as $attr){ + $one[$attr] = $one[$attr] ? unserialize($one[$attr]) : ''; + } + $coll[$k] = $one; + } + } + //var_dump($one); + return [ + 'coll' => $coll, + 'count'=> $query->count(), + ]; + } + + /** + * @property $one|Array + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + public function save($one){ + $currentDateTime = \fec\helpers\CDate::getCurrentDateTime(); + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + if(!($this->validateIdentify($one))){ + Yii::$service->helper->errors->add('StaticBlock: identify存在,您必须定义一个唯一的identify '); + return; + } + if($primaryVal){ + $model = StaticBlock::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('static block '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + $model = new StaticBlock; + $model->created_at = time(); + $model->created_user_id = \fec\helpers\CUser::getCurrentUserId(); + } + $model->updated_at = time(); + foreach($this->_lang_attr as $attrName){ + if(is_array($one[$attrName]) && !empty($one[$attrName]) ){ + $one[$attrName] = serialize($one[$attrName]); + } + } + + $saveStatus = Yii::$service->helper->ar->save($model,$one); + if(!$primaryVal){ + $primaryVal = Yii::$app->db->getLastInsertID(); + } + + return true; + } + + protected function validateIdentify($one){ + $identify = $one['identify']; + $id = $this->getPrimaryKey(); + $primaryVal = isset($one[$id]) ? $one[$id] : ''; + $where = ['identify' => $identify]; + $query = StaticBlock::find()->asArray(); + $query->where(['identify' => $identify]); + if($primaryVal){ + $query->andWhere(['<>',$id,$primaryVal]); + } + $one = $query->one(); + if(!empty($one)){ + return false; + } + return true; + } + + public function remove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + foreach($ids as $id){ + $model = StaticBlock::findOne($id); + $model->delete(); + } + }else{ + $id = $ids; + foreach($ids as $id){ + $model = StaticBlock::findOne($id); + $model->delete(); + } + } + return true; + + } + +} \ No newline at end of file diff --git a/services/customer/Address.php b/services/customer/Address.php new file mode 100644 index 000000000..c4d528bbc --- /dev/null +++ b/services/customer/Address.php @@ -0,0 +1,360 @@ + + * @since 1.0 + */ +class Address extends Service +{ + protected $currentCountry; + protected $currentState; + + protected function actionGetPrimaryKey(){ + + return 'address_id'; + } + /** + * @property $primaryKey | Int + * @return Object(MyCoupon) + * 通过id找到cupon的对象 + */ + protected function actionGetByPrimaryKey($primaryKey){ + $one = MyAddress::findOne($primaryKey); + $primaryKey = $this->getPrimaryKey(); + if($one[$primaryKey]){ + return $one; + }else{ + return new MyAddress; + } + } + + protected function actionGetAddressByIdAndCustomerId($address_id,$customer_id){ + $primaryKey = $this->getPrimaryKey(); + $one = MyAddress::findOne([ + $primaryKey => $address_id, + 'customer_id' => $customer_id, + ]); + + if($one[$primaryKey]){ + return $one; + }else{ + return false; + } + } + + + /** + * @property $filter|Array + * @return Array; + * 通过过滤条件,得到coupon的集合。 + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionColl($filter=''){ + $query = MyAddress::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + $coll = $query->all(); + if(!empty($coll)){ + foreach($coll as $k => $one){ + $coll[$k] = $one; + } + } + //var_dump($one); + return [ + 'coll' => $coll, + 'count'=> $query->count(), + ]; + } + + protected function actionCurrentAddress(){ + + + } + + /** + * + * + */ + protected function actionCurrentAddressList(){ + $arr = []; + if(!Yii::$app->user->isGuest){ + $identity = Yii::$app->user->identity; + $customer_id = $identity['id']; + if($customer_id ){ + $filter = [ + 'numPerPage' => 30, + 'pageNum' => 1, + 'orderBy' => ['updated_at' => SORT_DESC, ], + 'where' => [ + ['customer_id' => $customer_id], + ], + 'asArray' => true, + ]; + $coll = $this->coll($filter); + $ii = 0; + if(is_array($coll['coll']) && !empty($coll['coll'])){ + foreach($coll['coll'] as $one){ + $address_id = $one['address_id']; + $first_name = $one['first_name']; + $last_name = $one['last_name']; + $email = $one['email']; + $telephone = $one['telephone']; + $street1 = $one['street1']; + $street2 = $one['street2']; + $is_default = $one['is_default']; + $city = $one['city']; + + //$state = Yii::$service->helper->country->getStateByContryCode($one['country'],$one['state']); + $state = $one['state']; + $zip = $one['zip']; + $country = Yii::$service->helper->country->getCountryNameByKey($one['country']); + $str = $first_name.' '.$last_name.' '.$email.' '. + $street1.' '.$street2.' '.$city.' '.$state.' '.$country.' '. + $zip.' '.$telephone; + if($is_default == 1){ + $ii = 1; + } + $arr[$address_id] = [ + 'address' => $str, + 'is_default'=>$is_default, + ]; + } + if(!$ii){ + # 如果没有默认的地址,则取第一个当默认 + foreach($arr as $k=>$v){ + $arr[$k]['is_default'] = 1; + break; + } + } + } + } + } + return $arr; + } + + /** + * @property $one|Array , save one data . + * @return Int 保存coupon成功后,返回保存的id。 + * example $one = [ + 'first_name' => '', + 'last_name' => '', + 'email' => '', + 'company' => '', + 'telephone' => '', + 'fax' => '', + 'street1' => '', + 'street2' => '', + 'city' => '', + 'state' => '', + 'zip' => '', + 'country' => '', + 'customer_id' => '', + 'is_default' => '', + + ]; + */ + protected function actionSave($one){ + $time = time(); + $primaryKey = $this->getPrimaryKey(); + $primaryVal = isset($one[$primaryKey]) ? $one[$primaryKey] : ''; + if($primaryVal){ + $model = MyAddress::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('address '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + + $model = new MyAddress; + $model->created_at = time(); + if(isset(Yii::$app->user)){ + $user = Yii::$app->user; + if(isset($user->identity)){ + $identity = $user->identity; + $person_id = $identity['id']; + //$model->created_person = $person_id; + } + } + } + $model->updated_at = time(); + $saveStatus = Yii::$service->helper->ar->save($model,$one); + + if(!$primaryVal){ + $primaryVal = Yii::$app->db->getLastInsertID(); + } + if($one['is_default'] == 1){ + $customer_id = $one['customer_id']; + MyAddress::updateAll( + ['is_default'=>2], # $attributes + 'customer_id = '.$customer_id.' and '.$primaryKey.' != ' .$primaryVal # $condition + //[':customer_id' => $customer_id] + ); + } + return $primaryVal; + } + + + /** + * @property $ids | Int or Array + * @return boolean + * 如果传入的是id数组,则删除多个 + * 如果传入的是Int,则删除一个coupon + * + */ + protected function actionRemove($ids,$customer_id=''){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + foreach($ids as $id){ + $model = MyAddress::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + if($customer_id){ + if($model['customer_id'] == $customer_id){ + + $this->removeCartAddress($model['customer_id'],$id); + $model->delete(); + }else{ + Yii::$service->helper->errors->add("remove address is not current customer address"); + } + }else{ + $this->removeCartAddress($model['customer_id'],$id); + $model->delete(); + + } + + }else{ + Yii::$service->helper->errors->add("address Remove Errors:ID $id is not exist."); + return false; + } + } + }else{ + $id = $ids; + $model = MyAddress::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + if($customer_id){ + if($model['customer_id'] == $customer_id){ + $this->removeCartAddress($model['customer_id'],$id); + $model->delete(); + }else{ + Yii::$service->helper->errors->add("remove address is not current customer address"); + + } + }else{ + $this->removeCartAddress($model['customer_id'],$id); + $model->delete(); + } + + }else{ + Yii::$service->helper->errors->add("Address Remove Errors:ID:$id is not exist."); + return false; + } + } + # 查看是否有默认地址?如果该用户存在记录,但是没有默认地址, + # 则查找用户是否存在非默认地址,如果存在,则取一个设置为默认地址 + if($customer_id){ + $addressOne = MyAddress::find()->asArray() + ->where(['customer_id' => $customer_id,'is_default' => 1]) + ->one(); + if(!$addressOne['address_id']){ + $assOne = MyAddress::find() + ->where(['customer_id' => $customer_id]) + ->one(); + if($assOne['address_id']){ + $assOne->is_default = 1; + $assOne->updated_at = time(); + $assOne->save(); + } + } + + } + return true; + } + + # 删除购物车中的address部分。 + protected function removeCartAddress($customer_id,$address_id){ + $cart = Yii::$service->cart->quote->getCartByCustomerId($customer_id); + if(isset($cart['customer_address_id']) && !empty($cart['customer_address_id'])){ + if($cart['customer_address_id'] == $address_id){ + $cart->customer_address_id = ''; + $cart->save(); + } + } + } + + /** + * @property $customer_id | int 用户的id + * @return Array Or '' + * 得到customer的默认地址。 + */ + /* + protected function actionGetDefaultAddress($customer_id = ''){ + if(!$customer_id){ + $identity = Yii::$app->user->identity; + $customer_id = $identity['id']; + } + if($customer_id ){ + $addressOne = MyAddress::find()->asArray() + ->where(['customer_id' => $customer_id,'is_default' => 1]) + ->one(); + if($addressOne['address_id']){ + return $addressOne; + }else{ + $assOne = MyAddress::find()->asArray() + ->where(['customer_id' => $customer_id]) + ->one(); + if($assOne['address_id']){ + return $assOne; + } + } + } + } + */ + + + + + + + + + + + + + + + + + + + + + +} \ No newline at end of file diff --git a/services/customer/Affiliate.php b/services/customer/Affiliate.php new file mode 100644 index 000000000..b0aefe405 --- /dev/null +++ b/services/customer/Affiliate.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Affiliate extends Service +{ + +} \ No newline at end of file diff --git a/services/customer/Coupon.php b/services/customer/Coupon.php new file mode 100644 index 000000000..7caffa17a --- /dev/null +++ b/services/customer/Coupon.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Coupon extends Service +{ + +} \ No newline at end of file diff --git a/services/customer/DropShip.php b/services/customer/DropShip.php new file mode 100644 index 000000000..aa556b40b --- /dev/null +++ b/services/customer/DropShip.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class DropShip extends Service +{ + +} \ No newline at end of file diff --git a/services/customer/Favorite.php b/services/customer/Favorite.php new file mode 100644 index 000000000..095880dca --- /dev/null +++ b/services/customer/Favorite.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Favorite extends Service +{ + +} \ No newline at end of file diff --git a/services/customer/Message.php b/services/customer/Message.php new file mode 100644 index 000000000..9466bfb07 --- /dev/null +++ b/services/customer/Message.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Message extends Service +{ + +} \ No newline at end of file diff --git a/services/customer/Newsletter.php b/services/customer/Newsletter.php new file mode 100644 index 000000000..5a24224e5 --- /dev/null +++ b/services/customer/Newsletter.php @@ -0,0 +1,70 @@ + + * @since 1.0 + */ +class Newsletter extends Service +{ + /** + * @property $emailAddress | String + * @return boolean + * 检查邮件是否之前被订阅过 + */ + protected function emailIsExist($emailAddress){ + $primaryKey = NewsletterModel::primaryKey(); + $one = NewsletterModel::findOne(['email' => $emailAddress]); + if($one[$primaryKey]){ + return true; + } + return false; + } + + /** + * @property $emailAddress | String + * @return boolean + * 订阅邮件 + */ + protected function actionSubscribe($emailAddress){ + if(!$emailAddress){ + Yii::$service->helper->errors->add('email address is empty'); + return; + } + if($this->emailIsExist($emailAddress)){ + Yii::$service->helper->errors->add('Your email address has subscribe'); + return; + } + $newsletterModel = new NewsletterModel; + $newsletterModel->email = $emailAddress; + $newsletterModel->created_at = time(); + $newsletterModel->status = NewsletterModel::ENABLE_STATUS; + $newsletterModel->save(); + return true; + } + + + + + + + + + + + +} \ No newline at end of file diff --git a/services/customer/Order.php b/services/customer/Order.php new file mode 100644 index 000000000..8724415db --- /dev/null +++ b/services/customer/Order.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Order extends Service +{ + +} \ No newline at end of file diff --git a/services/customer/Point.php b/services/customer/Point.php new file mode 100644 index 000000000..614c438fc --- /dev/null +++ b/services/customer/Point.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Point extends Service +{ + +} \ No newline at end of file diff --git a/services/customer/Review.php b/services/customer/Review.php new file mode 100644 index 000000000..3b4526322 --- /dev/null +++ b/services/customer/Review.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Review extends Service +{ + +} \ No newline at end of file diff --git a/services/customer/Wholesale.php b/services/customer/Wholesale.php new file mode 100644 index 000000000..24853c5f5 --- /dev/null +++ b/services/customer/Wholesale.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +class Wholesale extends Service +{ + +} \ No newline at end of file diff --git a/services/helper/AR.php b/services/helper/AR.php new file mode 100644 index 000000000..f0a5c4084 --- /dev/null +++ b/services/helper/AR.php @@ -0,0 +1,98 @@ + + * @since 1.0 + */ +class AR extends Service +{ + public $numPerPage=20; + public $pageNum=1; + + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + * 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function getCollByFilter($query,$filter){ + $select = isset($filter['select']) ? $filter['select'] : ''; + $asArray = isset($filter['asArray']) ? $filter['asArray'] : true; + $numPerPage = isset($filter['numPerPage']) ? $filter['numPerPage'] : $this->numPerPage; + $pageNum = isset($filter['pageNum']) ? $filter['pageNum'] : $this->pageNum; + $orderBy = isset($filter['orderBy']) ? $filter['orderBy'] : ''; + $where = isset($filter['where']) ? $filter['where'] : ''; + + if($asArray) + $query->asArray(); + if(is_array($select) && !empty($select)) + $query->select($select); + if($where){ + if(is_array($where)){ + $i = 0; + foreach($where as $w){ + $i++; + if($i==1){ + $query->where($w); + }else{ + $query->andWhere($w); + } + } + } + } + + $offset = ($pageNum -1 ) * $numPerPage; + $query->limit($numPerPage)->offset($offset); + if($orderBy) + $query->orderBy($orderBy); + return $query; + } + + + public function save($model,$one,$serialize=false){ + if(!$model){ + Yii::$service->helper->errors->add('ActiveRecord Save Error: $model is empty'); + return; + } + $attributes = $model->attributes(); + if(is_array($attributes) && !empty($attributes)){ + foreach($attributes as $attr){ + if(isset($one[$attr])){ + if($serialize && is_array($one[$attr])){ + $model[$attr] = serialize($one[$attr]); + }else{ + $model[$attr] = $one[$attr]; + } + } + } + return $model->save(); + }else{ + Yii::$service->helper->errors->add('$attribute is empty or is not array'); + return; + } + } + + +} \ No newline at end of file diff --git a/services/helper/Captcha.php b/services/helper/Captcha.php new file mode 100644 index 000000000..3799b295c --- /dev/null +++ b/services/helper/Captcha.php @@ -0,0 +1,132 @@ + + * @since 1.0 + */ +class Captcha extends Service +{ + public $charset = 'abcdefghkmnprstuvwxyzABCDEFGHKMNPRSTUVWXYZ0123456789';// + public $codelen = 4;//֤볤 + public $width = 130;// + public $height = 50;//߶ + public $fontsize = 20;//ָС + public $case_sensitive = false; + private $fontcolor ;//ָɫ + private $code;//֤ + private $img;//ͼԴ + private $font;//ָ + private $_sessionKey = 'captcha_session_key'; + + /** + * 1. ͼƬ + public function actionCaptcha(){ + Yii::$service->helper->captcha->doimg(); + exit; + } + 2. ͼƬ +

    + ֤룺 + + +

    + 3. ֤֤ɹtrue ʧܷfalse + $code = '' // codeûݹֵ + Yii::$service->helper->captcha->validateCaptcha($code) + + * + */ + //췽ʼ + public function __construct() { + $this->font = dirname(__FILE__).'/captcha/Elephant.ttf';//ע·ҪдԣʾͼƬ + //echo $this->font;exit; + } + // + private function createCode() { + $_len = strlen($this->charset)-1; + for ($i=0;$i<$this->codelen;$i++) { + $this->code .= $this->charset[mt_rand(0,$_len)]; + } + } + //ɱ + private function createBg() { + $this->img = imagecreatetruecolor($this->width, $this->height); + $color = imagecolorallocate($this->img, mt_rand(157,255), mt_rand(157,255), mt_rand(157,255)); + imagefilledrectangle($this->img,0,$this->height,$this->width,0,$color); + } + // + private function createFont() { + $_x = $this->width / $this->codelen; + + for ($i=0;$i<$this->codelen;$i++) { + if(!$this->fontcolor){ + $fontcolor = imagecolorallocate($this->img,mt_rand(0,156),mt_rand(0,156),mt_rand(0,156)); + }else{ + $fontcolor = $this->fontcolor; + } + imagettftext($this->img,$this->fontsize,mt_rand(-30,30),$_x*$i+mt_rand(1,5),$this->height / 1.4,$fontcolor,$this->font,$this->code[$i]); + } + } + //ѩ + private function createLine() { + // + for ($i=0;$i<6;$i++) { + $color = imagecolorallocate($this->img,mt_rand(0,156),mt_rand(0,156),mt_rand(0,156)); + imageline($this->img,mt_rand(0,$this->width),mt_rand(0,$this->height),mt_rand(0,$this->width),mt_rand(0,$this->height),$color); + } + //ѩ + for ($i=0;$i<100;$i++) { + $color = imagecolorallocate($this->img,mt_rand(200,255),mt_rand(200,255),mt_rand(200,255)); + imagestring($this->img,mt_rand(1,5),mt_rand(0,$this->width),mt_rand(0,$this->height),'*',$color); + } + } + // + private function outPut() { + header('Content-type:image/jpg'); + imagepng($this->img); + imagedestroy($this->img); + } + // + public function doimg() { + $this->createBg(); + $this->createCode(); + $this->createLine(); + $this->createFont(); + $this->outPut(); + $this->setSessionCode(); + } + + public function setSessionCode(){ + $code = $this->getCode($this->code); + \Yii::$app->session->set($this->_sessionKey,$code); + } + //ȡ֤ + public function getCode($code) { + if(!$this->case_sensitive){ + return strtolower($code); + }else{ + return $this->code; + } + } + + public function validateCaptcha($captchaData){ + $captchaData = $this->getCode($captchaData); + $sessionCaptchaData = \Yii::$app->session->get($this->_sessionKey); + return ($captchaData === $sessionCaptchaData) ? true : false ; + } + +} \ No newline at end of file diff --git a/services/helper/Country.php b/services/helper/Country.php new file mode 100644 index 000000000..ee809ab19 --- /dev/null +++ b/services/helper/Country.php @@ -0,0 +1,932 @@ + + * @since 1.0 + */ +class Country extends Service +{ + + public $default_country; + + public function getDefaultCountry(){ + if(!$this->default_country){ + $this->default_country = 'US'; + } + return $this->default_country; + } + + + public function getStateOptionsByContryCode($CountryCode,$selected=''){ + if(!$CountryCode){ + $CountryCode = $this->getDefaultCountry(); + } + $stateArr = $this->getStateByContryCode($CountryCode); + $str = ''; + if(is_array($stateArr) && !empty($stateArr)){ + if($selected){ + foreach($stateArr as $code=>$name){ + if($selected == $code || strtolower($selected) == strtolower($name)){ + $str .= ''; + }else{ + $str .= ''; + } + } + }else{ + foreach($stateArr as $code=>$name){ + $str .= ''; + } + } + } + return $str; + } + + + //得到所有国家的option + public function getAllCountryOptions($name="country",$class="country",$current = ''){ + $all_country_array = $this->getAllCountryArray(); + if($name && $class){ + $str = '"; + } + return $str; + + } + + + public function getCountryNameByKey($key){ + $all_country = $this->getAllCountryArray(); + return isset($all_country[$key]) ? $all_country[$key] : $key; + } + + + + public static function getCountryOptionsHtml($selectd = ''){ + if(!$selectd){ + $selectd = $this->getDefaultCountry(); + } + + $all_country = $this->getAllCountryArray(); + $str = ''; + foreach($all_country as $key=>$value){ + if($selectd && ($selectd == $key)){ + $str .= ''; + }else{ + $str .= ''; + } + } + return $str; + } + + + + + + + /** + * @property $countryCode |String 国家简码 + * @property $stateCode | String 省市简码 + * @return String OR Array 如果不传递省市简码,那么返回的是该国家对应的省市数组 + * 如果传递省市简码,传递的是省市的名称 + */ + + public function getStateByContryCode($countryCode,$stateCode=''){ + $countryStates = $this->getCountryStateArr(); + $returnStateArr = []; + $returnStateName = ''; + if($countryCode){ + if($stateCode){ + if(isset($countryStates[$countryCode][$stateCode]) && !empty($countryStates[$countryCode][$stateCode])){ + $returnStateName = $countryStates[$countryCode][$stateCode]; + } + return $returnStateName ? $returnStateName : $stateCode; + + }else{ + if(isset($countryStates[$countryCode]) && !empty($countryStates[$countryCode]) && is_array($countryStates[$countryCode])){ + $returnStateArr = $countryStates[$countryCode]; + } + return $returnStateArr; + } + } + } + + /** + * @return Array ,得到所有国家的数组 + * 格式:['国家简码' => '国家全称'] + */ + public static function getAllCountryArray(){ + return [ + "AF"=>"Afghanistan", + "AX"=>"Åland Islands", + "AL"=>"Albania", + "DZ"=>"Algeria", + "AS"=>"American Samoa", + "AD"=>"Andorra", + "AO"=>"Angola", + "AI"=>"Anguilla", + "AQ"=>"Antarctica", + "AG"=>"Antigua and Barbuda", + "AR"=>"Argentina", + "AM"=>"Armenia", + "AW"=>"Aruba", + "AU"=>"Australia", + "AT"=>"Austria", + "AZ"=>"Azerbaijan", + "BS"=>"Bahamas", + "BH"=>"Bahrain", + "BD"=>"Bangladesh", + "BB"=>"Barbados", + "BY"=>"Belarus", + "BE"=>"Belgium", + "BZ"=>"Belize", + "BJ"=>"Benin", + "BM"=>"Bermuda", + "BT"=>"Bhutan", + "BO"=>"Bolivia", + "BA"=>"Bosnia and Herzegovina", + "BW"=>"Botswana", + "BV"=>"Bouvet Island", + "BR"=>"Brazil", + "IO"=>"British Indian Ocean Territory", + "VG"=>"British Virgin Islands", + "BN"=>"Brunei", + "BG"=>"Bulgaria", + "BF"=>"Burkina Faso", + "BI"=>"Burundi", + "KH"=>"Cambodia", + "CM"=>"Cameroon", + "CA"=>"Canada", + "CV"=>"Cape Verde", + "KY"=>"Cayman Islands", + "CF"=>"Central African Republic", + "TD"=>"Chad", + "CL"=>"Chile", + "CN"=>"China", + "CX"=>"Christmas Island", + "CC"=>"Cocos [Keeling] Islands", + "CO"=>"Colombia", + "KM"=>"Comoros", + "CG"=>"Congo - Brazzaville", + "CD"=>"Congo - Kinshasa", + "CK"=>"Cook Islands", + "CR"=>"Costa Rica", + "CI"=>"Côte d’Ivoire", + "HR"=>"Croatia", + "CU"=>"Cuba", + "CY"=>"Cyprus", + "CZ"=>"Czech Republic", + "DK"=>"Denmark", + "DJ"=>"Djibouti", + "DM"=>"Dominica", + "DO"=>"Dominican Republic", + "EC"=>"Ecuador", + "EG"=>"Egypt", + "SV"=>"El Salvador", + "GQ"=>"Equatorial Guinea", + "ER"=>"Eritrea", + "EE"=>"Estonia", + "ET"=>"Ethiopia", + "FK"=>"Falkland Islands", + "FO"=>"Faroe Islands", + "FJ"=>"Fiji", + "FI"=>"Finland", + "FR"=>"France", + "GF"=>"French Guiana", + "PF"=>"French Polynesia", + "TF"=>"French Southern Territories", + "GA"=>"Gabon", + "GM"=>"Gambia", + "GE"=>"Georgia", + "DE"=>"Germany", + "GH"=>"Ghana", + "GI"=>"Gibraltar", + "GR"=>"Greece", + "GL"=>"Greenland", + "GD"=>"Grenada", + "GP"=>"Guadeloupe", + "GU"=>"Guam", + "GT"=>"Guatemala", + "GG"=>"Guernsey", + "GN"=>"Guinea", + "GW"=>"Guinea-Bissau", + "GY"=>"Guyana", + "HT"=>"Haiti", + "HM"=>"Heard Island and McDonald Islands", + "HN"=>"Honduras", + "HK"=>"Hong Kong SAR China", + "HU"=>"Hungary", + "IS"=>"Iceland", + "IN"=>"India", + "ID"=>"Indonesia", + "IR"=>"Iran", + "IQ"=>"Iraq", + "IE"=>"Ireland", + "IM"=>"Isle of Man", + "IL"=>"Israel", + "IT"=>"Italy", + "JM"=>"Jamaica", + "JP"=>"Japan", + "JE"=>"Jersey", + "JO"=>"Jordan", + "KZ"=>"Kazakhstan", + "KE"=>"Kenya", + "KI"=>"Kiribati", + "KW"=>"Kuwait", + "KG"=>"Kyrgyzstan", + "LA"=>"Laos", + "LV"=>"Latvia", + "LB"=>"Lebanon", + "LS"=>"Lesotho", + "LR"=>"Liberia", + "LY"=>"Libya", + "LI"=>"Liechtenstein", + "LT"=>"Lithuania", + "LU"=>"Luxembourg", + "MO"=>"Macau SAR China", + "MK"=>"Macedonia", + "MG"=>"Madagascar", + "MW"=>"Malawi", + "MY"=>"Malaysia", + "MV"=>"Maldives", + "ML"=>"Mali", + "MT"=>"Malta", + "MH"=>"Marshall Islands", + "MQ"=>"Martinique", + "MR"=>"Mauritania", + "MU"=>"Mauritius", + "YT"=>"Mayotte", + "MX"=>"Mexico", + "FM"=>"Micronesia", + "MD"=>"Moldova", + "MC"=>"Monaco", + "MN"=>"Mongolia", + "ME"=>"Montenegro", + "MS"=>"Montserrat", + "MA"=>"Morocco", + "MZ"=>"Mozambique", + "MM"=>"Myanmar [Burma]", + "NA"=>"Namibia", + "NR"=>"Nauru", + "NP"=>"Nepal", + "NL"=>"Netherlands", + "AN"=>"Netherlands Antilles", + "NC"=>"New Caledonia", + "NZ"=>"New Zealand", + "NI"=>"Nicaragua", + "NE"=>"Niger", + "NG"=>"Nigeria", + "NU"=>"Niue", + "NF"=>"Norfolk Island", + "MP"=>"Northern Mariana Islands", + "KP"=>"North Korea", + "NO"=>"Norway", + "OM"=>"Oman", + "PK"=>"Pakistan", + "PW"=>"Palau", + "PS"=>"Palestinian Territories", + "PA"=>"Panama", + "PG"=>"Papua New Guinea", + "PY"=>"Paraguay", + "PE"=>"Peru", + "PH"=>"Philippines", + "PN"=>"Pitcairn Islands", + "PL"=>"Poland", + "PT"=>"Portugal", + "PR"=>"Puerto Rico", + "QA"=>"Qatar", + "RE"=>"R¨¦union", + "RO"=>"Romania", + "RU"=>"Russia", + "RW"=>"Rwanda", + "BL"=>"Saint Barth¨¦lemy", + "SH"=>"Saint Helena", + "KN"=>"Saint Kitts and Nevis", + "LC"=>"Saint Lucia", + "MF"=>"Saint Martin", + "PM"=>"Saint Pierre and Miquelon", + "VC"=>"Saint Vincent and the Grenadines", + "WS"=>"Samoa", + "SM"=>"San Marino", + "ST"=>"São Tomé and Príncipe", + "SA"=>"Saudi Arabia", + "SN"=>"Senegal", + "RS"=>"Serbia", + "SC"=>"Seychelles", + "SL"=>"Sierra Leone", + "SG"=>"Singapore", + "SK"=>"Slovakia", + "SI"=>"Slovenia", + "SB"=>"Solomon Islands", + "SO"=>"Somalia", + "ZA"=>"South Africa", + "GS"=>"South Georgia and the South Sandwich Islands", + "KR"=>"South Korea", + "ES"=>"Spain", + "LK"=>"Sri Lanka", + "SD"=>"Sudan", + "SR"=>"Suriname", + "SJ"=>"Svalbard and Jan Mayen", + "SZ"=>"Swaziland", + "SE"=>"Sweden", + "CH"=>"Switzerland", + "SY"=>"Syria", + "TW"=>"Taiwan", + "TJ"=>"Tajikistan", + "TZ"=>"Tanzania", + "TH"=>"Thailand", + "TL"=>"Timor-Leste", + "TG"=>"Togo", + "TK"=>"Tokelau", + "TO"=>"Tonga", + "TT"=>"Trinidad and Tobago", + "TN"=>"Tunisia", + "TR"=>"Turkey", + "TM"=>"Turkmenistan", + "TC"=>"Turks and Caicos Islands", + "TV"=>"Tuvalu", + "UG"=>"Uganda", + "UA"=>"Ukraine", + "AE"=>"United Arab Emirates", + "GB"=>"United Kingdom", + "US"=>"United States", + "UY"=>"Uruguay", + "UM"=>"U.S. Minor Outlying Islands", + "VI"=>"U.S. Virgin Islands", + "UZ"=>"Uzbekistan", + "VU"=>"Vanuatu", + "VA"=>"Vatican City", + "VE"=>"Venezuela", + "VN"=>"Vietnam", + "WF"=>"Wallis and Futuna", + "EH"=>"Western Sahara", + "YE"=>"Yemen", + "ZM"=>"Zambia", + "ZW"=>"Zimbabwe", + ]; + + } + + /** + * 得到国家和省市数组 + * 格式为: [ + * 国家简码 => + * [ + * 省/市简码 => 省/市名称, + * 省/市简码 => 省/市名称, + * 省/市简码 => 省/市名称, + * ] + * ] + * ] + * 在选择国家后,省市的信息会以ajax的形式带出,存在以下列表的国家,会以下拉选择条 + * 的方式显示,如果不存在以下列表,则显示inputtext输入框,如果您想要某个国家的省市以 + * 下拉条的方式选择,可以在下面的函数里面添加对应的国家和省市信息,添加后 + * 选择国家后,省市会以下拉条的方式供用户选择,而不是inputtext填写省市信息。 + */ + public function getCountryStateArr(){ + $data = [ + 'US' => [ + 'AL' => 'Alabama', + 'AK' => 'Alaska', + 'AS' => 'American Samoa', + 'AZ' => 'Arizona', + 'AR' => 'Arkansas', + 'AF' => 'Armed Forces Africa', + 'AA' => 'Armed Forces Americas', + 'AC' => 'Armed Forces Canada', + 'AE' => 'Armed Forces Europe', + 'AM' => 'Armed Forces Middle East', + 'AP' => 'Armed Forces Pacific', + 'CA' => 'California', + 'CO' => 'Colorado', + 'CT' => 'Connecticut', + 'DE' => 'Delaware', + 'DC' => 'District of Columbia', + 'FM' => 'Federated States Of Micronesia', + 'FL' => 'Florida', + 'GA' => 'Georgia', + 'GU' => 'Guam', + 'HI' => 'Hawaii', + 'ID' => 'Idaho', + 'IL' => 'Illinois', + 'IN' => 'Indiana', + 'IA' => 'Iowa', + 'KS' => 'Kansas', + 'KY' => 'Kentucky', + 'LA' => 'Louisiana', + 'ME' => 'Maine', + 'MH' => 'Marshall Islands', + 'MD' => 'Maryland', + 'MA' => 'Massachusetts', + 'MI' => 'Michigan', + 'MN' => 'Minnesota', + 'MS' => 'Mississippi', + 'MO' => 'Missouri', + 'MT' => 'Montana', + 'NE' => 'Nebraska', + 'NV' => 'Nevada', + 'NH' => 'New Hampshire', + 'NJ' => 'New Jersey', + 'NM' => 'New Mexico', + 'NY' => 'New York', + 'NC' => 'North Carolina', + 'ND' => 'North Dakota', + 'MP' => 'Northern Mariana Islands', + 'OH' => 'Ohio', + 'OK' => 'Oklahoma', + 'OR' => 'Oregon', + 'PW' => 'Palau', + 'PA' => 'Pennsylvania', + 'PR' => 'Puerto Rico', + 'RI' => 'Rhode Island', + 'SC' => 'South Carolina', + 'SD' => 'South Dakota', + 'TN' => 'Tennessee', + 'TX' => 'Texas', + 'UT' => 'Utah', + 'VT' => 'Vermont', + 'VI' => 'Virgin Islands', + 'VA' => 'Virginia', + 'WA' => 'Washington', + 'WV' => 'West Virginia', + 'WI' => 'Wisconsin', + 'WY' => 'Wyoming', + ], + 'CA' => [ + 'AB' => 'Alberta', + 'BC' => 'British Columbia', + 'MB' => 'Manitoba', + 'NL' => 'Newfoundland and Labrador', + 'NB' => 'New Brunswick', + 'NS' => 'Nova Scotia', + 'NT' => 'Northwest Territories', + 'NU' => 'Nunavut', + 'ON' => 'Ontario', + 'PE' => 'Prince Edward Island', + 'QC' => 'Quebec', + 'SK' => 'Saskatchewan', + 'YT' => 'Yukon Territory', + ], + 'DE' => [ + 'NDS' => 'Niedersachsen', + 'BAW' => 'Baden-Württemberg', + 'BAY' => 'Bayern', + 'BER' => 'Berlin', + 'BRG' => 'Brandenburg', + 'BRE' => 'Bremen', + 'HAM' => 'Hamburg', + 'HES' => 'Hessen', + 'MEC' => 'Mecklenburg-Vorpommern', + 'NRW' => 'Nordrhein-Westfalen', + 'RHE' => 'Rheinland-Pfalz', + 'SAR' => 'Saarland', + 'SAS' => 'Sachsen', + 'SAC' => 'Sachsen-Anhalt', + 'SCN' => 'Schleswig-Holstein', + 'THE' => 'Thüringen', + ], + 'AT' => [ + 'WI' => 'Wien', + 'NO' => 'Niederösterreich', + 'OO' => 'Oberösterreich', + 'SB' => 'Salzburg', + 'KN' => 'Kärnten', + 'ST' => 'Steiermark', + 'TI' => 'Tirol', + 'BL' => 'Burgenland', + 'VB' => 'Voralberg', + ], + 'CH' => [ + 'AG' => 'Aargau', + 'AI' => 'Appenzell Innerrhoden', + 'AR' => 'Appenzell Ausserrhoden', + 'BE' => 'Bern', + 'BL' => 'Basel-Landschaft', + 'BS' => 'Basel-Stadt', + 'FR' => 'Freiburg', + 'GE' => 'Genf', + 'GL' => 'Glarus', + 'GR' => 'Graubünden', + 'JU' => 'Jura', + 'LU' => 'Luzern', + 'NE' => 'Neuenburg', + 'NW' => 'Nidwalden', + 'OW' => 'Obwalden', + 'SG' => 'St. Gallen', + 'SH' => 'Schaffhausen', + 'SO' => 'Solothurn', + 'SZ' => 'Schwyz', + 'TG' => 'Thurgau', + 'TI' => 'Tessin', + 'UR' => 'Uri', + 'VD' => 'Waadt', + 'VS' => 'Wallis', + 'ZG' => 'Zug', + 'ZH' => 'Zürich', + ], + 'ES' => [ + 'A Coruсa' => 'A Coruña', + 'Alava' => 'Alava', + 'Albacete' => 'Albacete', + 'Alicante' => 'Alicante', + 'Almeria' => 'Almeria', + 'Asturias' => 'Asturias', + 'Avila' => 'Avila', + 'Badajoz' => 'Badajoz', + 'Baleares' => 'Baleares', + 'Barcelona' => 'Barcelona', + 'Burgos' => 'Burgos', + 'Caceres' => 'Caceres', + 'Cadiz' => 'Cadiz', + 'Cantabria' => 'Cantabria', + 'Castellon' => 'Castellon', + 'Ceuta' => 'Ceuta', + 'Ciudad Real' => 'Ciudad Real', + 'Cordoba' => 'Cordoba', + 'Cuenca' => 'Cuenca', + 'Girona' => 'Girona', + 'Granada' => 'Granada', + 'Guadalajara' => 'Guadalajara', + 'Guipuzcoa' => 'Guipuzcoa', + 'Huelva' => 'Huelva', + 'Huesca' => 'Huesca', + 'Jaen' => 'Jaen', + 'La Rioja' => 'La Rioja', + 'Las Palmas' => 'Las Palmas', + 'Leon' => 'Leon', + 'Lleida' => 'Lleida', + 'Lugo' => 'Lugo', + 'Madrid' => 'Madrid', + 'Malaga' => 'Malaga', + 'Melilla' => 'Melilla', + 'Murcia' => 'Murcia', + 'Navarra' => 'Navarra', + 'Ourense' => 'Ourense', + 'Palencia' => 'Palencia', + 'Pontevedra' => 'Pontevedra', + 'Salamanca' => 'Salamanca', + 'Santa Cruz de Tenerife' => 'Santa Cruz de Tenerife', + 'Segovia' => 'Segovia', + 'Sevilla' => 'Sevilla', + 'Soria' => 'Soria', + 'Tarragona' => 'Tarragona', + 'Teruel' => 'Teruel', + 'Toledo' => 'Toledo', + 'Valencia' => 'Valencia', + 'Valladolid' => 'Valladolid', + 'Vizcaya' => 'Vizcaya', + 'Zamora' => 'Zamora', + 'Zaragoza' => 'Zaragoza', + ], + 'FR' => [ + '1' => 'Ain', + '2' => 'Aisne', + '3' => 'Allier', + '4' => 'Alpes-de-Haute-Provence', + '5' => 'Hautes-Alpes', + '6' => 'Alpes-Maritimes', + '7' => 'Ardèche', + '8' => 'Ardennes', + '9' => 'Ariège', + '10' => 'Aube', + '11' => 'Aude', + '12' => 'Aveyron', + '13' => 'Bouches-du-Rhône', + '14' => 'Calvados', + '15' => 'Cantal', + '16' => 'Charente', + '17' => 'Charente-Maritime', + '18' => 'Cher', + '19' => 'Corrèze', + '2A' => 'Corse-du-Sud', + '2B' => 'Haute-Corse', + '21' => 'Côte-d\'Or', + '22' => 'Côtes-d\'Armor', + '23' => 'Creuse', + '24' => 'Dordogne', + '25' => 'Doubs', + '26' => 'Drôme', + '27' => 'Eure', + '28' => 'Eure-et-Loir', + '29' => 'Finistère', + '30' => 'Gard', + '31' => 'Haute-Garonne', + '32' => 'Gers', + '33' => 'Gironde', + '34' => 'Hérault', + '35' => 'Ille-et-Vilaine', + '36' => 'Indre', + '37' => 'Indre-et-Loire', + '38' => 'Isère', + '39' => 'Jura', + '40' => 'Landes', + '41' => 'Loir-et-Cher', + '42' => 'Loire', + '43' => 'Haute-Loire', + '44' => 'Loire-Atlantique', + '45' => 'Loiret', + '46' => 'Lot', + '47' => 'Lot-et-Garonne', + '48' => 'Lozère', + '49' => 'Maine-et-Loire', + '50' => 'Manche', + '51' => 'Marne', + '52' => 'Haute-Marne', + '53' => 'Mayenne', + '54' => 'Meurthe-et-Moselle', + '55' => 'Meuse', + '56' => 'Morbihan', + '57' => 'Moselle', + '58' => 'Nièvre', + '59' => 'Nord', + '60' => 'Oise', + '61' => 'Orne', + '62' => 'Pas-de-Calais', + '63' => 'Puy-de-Dôme', + '64' => 'Pyrénées-Atlantiques', + '65' => 'Hautes-Pyrénées', + '66' => 'Pyrénées-Orientales', + '67' => 'Bas-Rhin', + '68' => 'Haut-Rhin', + '69' => 'Rhône', + '70' => 'Haute-Saône', + '71' => 'Saône-et-Loire', + '72' => 'Sarthe', + '73' => 'Savoie', + '74' => 'Haute-Savoie', + '75' => 'Paris', + '76' => 'Seine-Maritime', + '77' => 'Seine-et-Marne', + '78' => 'Yvelines', + '79' => 'Deux-Sèvres', + '80' => 'Somme', + '81' => 'Tarn', + '82' => 'Tarn-et-Garonne', + '83' => 'Var', + '84' => 'Vaucluse', + '85' => 'Vendée', + '86' => 'Vienne', + '87' => 'Haute-Vienne', + '88' => 'Vosges', + '89' => 'Yonne', + '90' => 'Territoire-de-Belfort', + '91' => 'Essonne', + '92' => 'Hauts-de-Seine', + '93' => 'Seine-Saint-Denis', + '94' => 'Val-de-Marne', + '95' => 'Val-d\'Oise', + ], + 'RO' => [ + 'AB' => 'Alba', + 'AR' => 'Arad', + 'AG' => 'Argeş', + 'BC' => 'Bacău', + 'BH' => 'Bihor', + 'BN' => 'Bistriţa-Năsăud', + 'BT' => 'Botoşani', + 'BV' => 'Braşov', + 'BR' => 'Brăila', + 'B' => 'Bucureşti', + 'BZ' => 'Buzău', + 'CS' => 'Caraş-Severin', + 'CL' => 'Călăraşi', + 'CJ' => 'Cluj', + 'CT' => 'Constanţa', + 'CV' => 'Covasna', + 'DB' => 'Dâmboviţa', + 'DJ' => 'Dolj', + 'GL' => 'Galaţi', + 'GR' => 'Giurgiu', + 'GJ' => 'Gorj', + 'HR' => 'Harghita', + 'HD' => 'Hunedoara', + 'IL' => 'Ialomiţa', + 'IS' => 'Iaşi', + 'IF' => 'Ilfov', + 'MM' => 'Maramureş', + 'MH' => 'Mehedinţi', + 'MS' => 'Mureş', + 'NT' => 'Neamţ', + 'OT' => 'Olt', + 'PH' => 'Prahova', + 'SM' => 'Satu-Mare', + 'SJ' => 'Sălaj', + 'SB' => 'Sibiu', + 'SV' => 'Suceava', + 'TR' => 'Teleorman', + 'TM' => 'Timiş', + 'TL' => 'Tulcea', + 'VS' => 'Vaslui', + 'VL' => 'Vâlcea', + 'VN' => 'Vrancea', + ], + 'FI' => [ + 'Lappi' => 'Lappi', + 'Pohjois-Pohjanmaa' => 'Pohjois-Pohjanmaa', + 'Kainuu' => 'Kainuu', + 'Pohjois-Karjala' => 'Pohjois-Karjala', + 'Pohjois-Savo' => 'Pohjois-Savo', + 'Etelä-Savo' => 'Etelä-Savo', + 'Etelä-Pohjanmaa' => 'Etelä-Pohjanmaa', + 'Pohjanmaa' => 'Pohjanmaa', + 'Pirkanmaa' => 'Pirkanmaa', + 'Satakunta' => 'Satakunta', + 'Keski-Pohjanmaa' => 'Keski-Pohjanmaa', + 'Keski-Suomi' => 'Keski-Suomi', + 'Varsinais-Suomi' => 'Varsinais-Suomi', + 'Etelä-Karjala' => 'Etelä-Karjala', + 'Päijät-Häme' => 'Päijät-Häme', + 'Kanta-Häme' => 'Kanta-Häme', + 'Uusimaa' => 'Uusimaa', + 'Itä-Uusimaa' => 'Itä-Uusimaa', + 'Kymenlaakso' => 'Kymenlaakso', + 'Ahvenanmaa' => 'Ahvenanmaa', + ], + 'EE' => [ + 'EE-37' => 'Harjumaa', + 'EE-39' => 'Hiiumaa', + 'EE-44' => 'Ida-Virumaa', + 'EE-49' => 'Jõgevamaa', + 'EE-51' => 'Järvamaa', + 'EE-57' => 'Läänemaa', + 'EE-59' => 'Lääne-Virumaa', + 'EE-65' => 'Põlvamaa', + 'EE-67' => 'Pärnumaa', + 'EE-70' => 'Raplamaa', + 'EE-74' => 'Saaremaa', + 'EE-78' => 'Tartumaa', + 'EE-82' => 'Valgamaa', + 'EE-84' => 'Viljandimaa', + 'EE-86' => 'Võrumaa', + ], + 'LV' => [ + 'LV-DGV' => 'Daugavpils', + 'LV-JEL' => 'Jelgava', + 'Jēkabpils' => 'Jēkabpils', + 'LV-JUR' => 'Jūrmala', + 'LV-LPX' => 'Liepāja', + 'LV-LE' => 'Liepājas novads', + 'LV-REZ' => 'Rēzekne', + 'LV-RIX' => 'Rīga', + 'LV-RI' => 'Rīgas novads', + 'Valmiera' => 'Valmiera', + 'LV-VEN' => 'Ventspils', + 'Aglonas novads' => 'Aglonas novads', + 'LV-AI' => 'Aizkraukles novads', + 'Aizputes novads' => 'Aizputes novads', + 'Aknīstes novads' => 'Aknīstes novads', + 'Alojas novads' => 'Alojas novads', + 'Alsungas novads' => 'Alsungas novads', + 'LV-AL' => 'Alūksnes novads', + 'Amatas novads' => 'Amatas novads', + 'Apes novads' => 'Apes novads', + 'Auces novads' => 'Auces novads', + 'Babītes novads' => 'Babītes novads', + 'Baldones novads' => 'Baldones novads', + 'Baltinavas novads' => 'Baltinavas novads', + 'LV-BL' => 'Balvu novads', + 'LV-BU' => 'Bauskas novads', + 'Beverīnas novads' => 'Beverīnas novads', + 'Brocēnu novads' => 'Brocēnu novads', + 'Burtnieku novads' => 'Burtnieku novads', + 'Carnikavas novads' => 'Carnikavas novads', + 'Cesvaines novads' => 'Cesvaines novads', + 'Ciblas novads' => 'Ciblas novads', + 'LV-CE' => 'Cēsu novads', + 'Dagdas novads' => 'Dagdas novads', + 'LV-DA' => 'Daugavpils novads', + 'LV-DO' => 'Dobeles novads', + 'Dundagas novads' => 'Dundagas novads', + 'Durbes novads' => 'Durbes novads', + 'Engures novads' => 'Engures novads', + 'Garkalnes novads' => 'Garkalnes novads', + 'Grobiņas novads' => 'Grobiņas novads', + 'LV-GU' => 'Gulbenes novads', + 'Iecavas novads' => 'Iecavas novads', + 'Ikšķiles novads' => 'Ikšķiles novads', + 'Ilūkstes novads' => 'Ilūkstes novads', + 'Inčukalna novads' => 'Inčukalna novads', + 'Jaunjelgavas novads' => 'Jaunjelgavas novads', + 'Jaunpiebalgas novads' => 'Jaunpiebalgas novads', + 'Jaunpils novads' => 'Jaunpils novads', + 'LV-JL' => 'Jelgavas novads', + 'LV-JK' => 'Jēkabpils novads', + 'Kandavas novads' => 'Kandavas novads', + 'Kokneses novads' => 'Kokneses novads', + 'Krimuldas novads' => 'Krimuldas novads', + 'Krustpils novads' => 'Krustpils novads', + 'LV-KR' => 'Krāslavas novads', + 'LV-KU' => 'Kuldīgas novads', + 'Kārsavas novads' => 'Kārsavas novads', + 'Lielvārdes novads' => 'Lielvārdes novads', + 'LV-LM' => 'Limbažu novads', + 'Lubānas novads' => 'Lubānas novads', + 'LV-LU' => 'Ludzas novads', + 'Līgatnes novads' => 'Līgatnes novads', + 'Līvānu novads' => 'Līvānu novads', + 'LV-MA' => 'Madonas novads', + 'Mazsalacas novads' => 'Mazsalacas novads', + 'Mālpils novads' => 'Mālpils novads', + 'Mārupes novads' => 'Mārupes novads', + 'Naukšēnu novads' => 'Naukšēnu novads', + 'Neretas novads' => 'Neretas novads', + 'Nīcas novads' => 'Nīcas novads', + 'LV-OG' => 'Ogres novads', + 'Olaines novads' => 'Olaines novads', + 'Ozolnieku novads' => 'Ozolnieku novads', + 'LV-PR' => 'Preiļu novads', + 'Priekules novads' => 'Priekules novads', + 'Priekuļu novads' => 'Priekuļu novads', + 'Pārgaujas novads' => 'Pārgaujas novads', + 'Pāvilostas novads' => 'Pāvilostas novads', + 'Pļaviņu novads' => 'Pļaviņu novads', + 'Raunas novads' => 'Raunas novads', + 'Riebiņu novads' => 'Riebiņu novads', + 'Rojas novads' => 'Rojas novads', + 'Ropažu novads' => 'Ropažu novads', + 'Rucavas novads' => 'Rucavas novads', + 'Rugāju novads' => 'Rugāju novads', + 'Rundāles novads' => 'Rundāles novads', + 'LV-RE' => 'Rēzeknes novads', + 'Rūjienas novads' => 'Rūjienas novads', + 'Salacgrīvas novads' => 'Salacgrīvas novads', + 'Salas novads' => 'Salas novads', + 'Salaspils novads' => 'Salaspils novads', + 'LV-SA' => 'Saldus novads', + 'Saulkrastu novads' => 'Saulkrastu novads', + 'Siguldas novads' => 'Siguldas novads', + 'Skrundas novads' => 'Skrundas novads', + 'Skrīveru novads' => 'Skrīveru novads', + 'Smiltenes novads' => 'Smiltenes novads', + 'Stopiņu novads' => 'Stopiņu novads', + 'Strenču novads' => 'Strenču novads', + 'Sējas novads' => 'Sējas novads', + 'LV-TA' => 'Talsu novads', + 'LV-TU' => 'Tukuma novads', + 'Tērvetes novads' => 'Tērvetes novads', + 'Vaiņodes novads' => 'Vaiņodes novads', + 'LV-VK' => 'Valkas novads', + 'LV-VM' => 'Valmieras novads', + 'Varakļānu novads' => 'Varakļānu novads', + 'Vecpiebalgas novads' => 'Vecpiebalgas novads', + 'Vecumnieku novads' => 'Vecumnieku novads', + 'LV-VE' => 'Ventspils novads', + 'Viesītes novads' => 'Viesītes novads', + 'Viļakas novads' => 'Viļakas novads', + 'Viļānu novads' => 'Viļānu novads', + 'Vārkavas novads' => 'Vārkavas novads', + 'Zilupes novads' => 'Zilupes novads', + 'Ādažu novads' => 'Ādažu novads', + 'Ērgļu novads' => 'Ērgļu novads', + 'Ķeguma novads' => 'Ķeguma novads', + 'Ķekavas novads' => 'Ķekavas novads', + ], + 'LT' => [ + 'LT-AL' => 'Alytaus Apskritis', + 'LT-KU' => 'Kauno Apskritis', + 'LT-KL' => 'Klaipėdos Apskritis', + 'LT-MR' => 'Marijampolės Apskritis', + 'LT-PN' => 'Panevėžio Apskritis', + 'LT-SA' => 'Šiaulių Apskritis', + 'LT-TA' => 'Tauragės Apskritis', + 'LT-TE' => 'Telšių Apskritis', + 'LT-UT' => 'Utenos Apskritis', + 'LT-VL' => 'Vilniaus Apskritis', + ], + + ]; + return $data; + } + + + +} \ No newline at end of file diff --git a/services/helper/Errors.php b/services/helper/Errors.php new file mode 100644 index 000000000..3992358d5 --- /dev/null +++ b/services/helper/Errors.php @@ -0,0 +1,47 @@ + + * @since 1.0 + */ +class Errors extends Service +{ + protected $_errors = false ; + public $status = true; + + public function add($errros){ + if($errros){ + $this->_errors[] = $errros; + } + } + + public function get($arrayFormat=false){ + if($this->_errors){ + $errors = $this->_errors; + $this->_errors = false; + if(!$arrayFormat){ + return implode('|',$errors); + }else{ + return $errors; + } + + } + return false; + } + + +} \ No newline at end of file diff --git a/services/helper/Log.php b/services/helper/Log.php new file mode 100644 index 000000000..801065de8 --- /dev/null +++ b/services/helper/Log.php @@ -0,0 +1,181 @@ + + * @since 1.0 + */ +class Log extends Service +{ + public $log_config; + protected $_serviceContent; + protected $_serviceUid; + protected $_isServiceLog; + protected $_isServiceLogDbPrint; + protected $_isServiceLogHtmlPrint; + protected $_isServiceLogDbPrintByParam; + + + + /** + * Log:get log uuid . + */ + public function getLogUid(){ + if(!$this->_serviceUid){ + $this->_serviceUid = $this->guid(); + } + return $this->_serviceUid; + } + + + + /** + * ServiceLog:是否开启service log + */ + public function isServiceLogEnable(){ + if($this->_isServiceLog === null){ + if( + isset($this->log_config['services']['enable']) + && $this->log_config['services']['enable'] + ){ + $this->_isServiceLog = true; + }else{ + $this->_isServiceLog = false; + } + } + return $this->_isServiceLog; + } + + /** + * ServiceLog:保存serviceLog + */ + public function printServiceLog($log_info){ + if($this->isServiceLogDbPrint()){ + FecshopServiceLog::getCollection()->save($log_info); + } + if($this->isServiceLogHtmlPrint() || $this->isServiceLogDbPrintByParam()){ + $str = '
    #################################
    '; + foreach($log_info as $k=>$v){ + if(is_array($v)){ + $v = implode('
    ',$v); + $str .= " + + "; + }else{ + $str .= " + + "; + } + } + $str .= '
    $k$v
    $k$v

    #################################

    '; + echo $str ; + } + } + + /** + * ServiceLog:if service log db print is enable. + */ + protected function isServiceLogDbPrint(){ + if($this->_isServiceLogDbPrint === null){ + if( + isset($this->log_config['services']['enable']) + && $this->log_config['services']['enable'] + && isset($this->log_config['services']['dbprint']) + && $this->log_config['services']['dbprint'] + ){ + $this->_isServiceLogDbPrint = true; + }else{ + $this->_isServiceLogDbPrint = false; + } + } + return $this->_isServiceLogDbPrint; + + } + /** + * ServiceLog:在前台打印servicelog是否开启 + */ + protected function isServiceLogHtmlPrint(){ + if($this->_isServiceLogHtmlPrint === null){ + if( + isset($this->log_config['services']['enable']) + && $this->log_config['services']['enable'] + && isset($this->log_config['services']['htmlprint']) + && $this->log_config['services']['htmlprint'] + ){ + $this->_isServiceLogHtmlPrint = true; + }else{ + $this->_isServiceLogHtmlPrint = false; + } + } + return $this->_isServiceLogHtmlPrint; + } + + /** + * ServiceLog:通过参数,在前台打印servicelog是否开启 + */ + protected function isServiceLogDbPrintByParam(){ + if($this->_isServiceLogDbPrintByParam === null){ + $this->_isServiceLogDbPrintByParam = false; + if( + isset($this->log_config['services']['enable']) + && $this->log_config['services']['enable'] + && isset($this->log_config['services']['htmlprintbyparam']['enable']) + && $this->log_config['services']['htmlprintbyparam']['enable'] + && isset($this->log_config['services']['htmlprintbyparam']['paramVal']) + && ($paramVal = $this->log_config['services']['htmlprintbyparam']['paramVal']) + && isset($this->log_config['services']['htmlprintbyparam']['paramKey']) + && ($paramKey = $this->log_config['services']['htmlprintbyparam']['paramKey']) + + ){ + if(CRequest::param($paramKey) == $paramVal){ + $this->_isServiceLogDbPrintByParam = true; + } + } + } + return $this->_isServiceLogDbPrintByParam; + } + + + /** + * generate uuid . + */ + protected function guid(){ + if (function_exists('com_create_guid')){ + return com_create_guid(); + }else{ + mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up. + $charid = strtoupper(md5(uniqid(rand(), true))); + $hyphen = chr(45);// "-" + $uuid = //chr(123)// "{" + substr($charid, 0, 8).$hyphen + .substr($charid, 8, 4).$hyphen + .substr($charid,12, 4).$hyphen + .substr($charid,16, 4).$hyphen + .substr($charid,20,12) + //.chr(125)// "}" + ; + return $uuid; + } + } + + + + + + +} \ No newline at end of file diff --git a/services/helper/MobileDetect.php b/services/helper/MobileDetect.php new file mode 100644 index 000000000..2685f75a5 --- /dev/null +++ b/services/helper/MobileDetect.php @@ -0,0 +1,1363 @@ + + * Nick Ilyin + * + * Original author: Victor Stanciu + * + * @license Code and contributions have 'MIT License' + * More details: https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE.txt + * + * @link Homepage: http://mobiledetect.net + * GitHub Repo: https://github.com/serbanghita/Mobile-Detect + * Google Code: http://code.google.com/p/php-mobile-detect/ + * README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md + * HOWTO: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples + * + * @version 2.8.13 + */ + +namespace fecshop\services\helper; +use Yii; +class MobileDetect +{ + /** + * Mobile detection type. + * + * @deprecated since version 2.6.9 + */ + const DETECTION_TYPE_MOBILE = 'mobile'; + + /** + * Extended detection type. + * + * @deprecated since version 2.6.9 + */ + const DETECTION_TYPE_EXTENDED = 'extended'; + + /** + * A frequently used regular expression to extract version #s. + * + * @deprecated since version 2.6.9 + */ + const VER = '([\w._\+]+)'; + + /** + * Top-level device. + */ + const MOBILE_GRADE_A = 'A'; + + /** + * Mid-level device. + */ + const MOBILE_GRADE_B = 'B'; + + /** + * Low-level device. + */ + const MOBILE_GRADE_C = 'C'; + + /** + * Stores the version number of the current release. + */ + const VERSION = '2.8.13'; + + /** + * A type for the version() method indicating a string return value. + */ + const VERSION_TYPE_STRING = 'text'; + + /** + * A type for the version() method indicating a float return value. + */ + const VERSION_TYPE_FLOAT = 'float'; + + /** + * A cache for resolved matches + * @var array + */ + protected $cache = array(); + + /** + * The User-Agent HTTP header is stored in here. + * @var string + */ + protected $userAgent = null; + + /** + * HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE. + * @var array + */ + protected $httpHeaders = array(); + + /** + * The matching Regex. + * This is good for debug. + * @var string + */ + protected $matchingRegex = null; + + /** + * The matches extracted from the regex expression. + * This is good for debug. + * @var string + */ + protected $matchesArray = null; + + /** + * The detection type, using self::DETECTION_TYPE_MOBILE or self::DETECTION_TYPE_EXTENDED. + * + * @deprecated since version 2.6.9 + * + * @var string + */ + protected $detectionType = self::DETECTION_TYPE_MOBILE; + + /** + * HTTP headers that trigger the 'isMobile' detection + * to be true. + * + * @var array + */ + protected static $mobileHeaders = array( + + 'HTTP_ACCEPT' => array('matches' => array( + // Opera Mini; @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/ + 'application/x-obml2d', + // BlackBerry devices. + 'application/vnd.rim.html', + 'text/vnd.wap.wml', + 'application/vnd.wap.xhtml+xml' + )), + 'HTTP_X_WAP_PROFILE' => null, + 'HTTP_X_WAP_CLIENTID' => null, + 'HTTP_WAP_CONNECTION' => null, + 'HTTP_PROFILE' => null, + // Reported by Opera on Nokia devices (eg. C3). + 'HTTP_X_OPERAMINI_PHONE_UA' => null, + 'HTTP_X_NOKIA_GATEWAY_ID' => null, + 'HTTP_X_ORANGE_ID' => null, + 'HTTP_X_VODAFONE_3GPDPCONTEXT' => null, + 'HTTP_X_HUAWEI_USERID' => null, + // Reported by Windows Smartphones. + 'HTTP_UA_OS' => null, + // Reported by Verizon, Vodafone proxy system. + 'HTTP_X_MOBILE_GATEWAY' => null, + // Seen this on HTC Sensation. SensationXE_Beats_Z715e. + 'HTTP_X_ATT_DEVICEID' => null, + // Seen this on a HTC. + 'HTTP_UA_CPU' => array('matches' => array('ARM')), + ); + + /** + * List of mobile devices (phones). + * + * @var array + */ + protected static $phoneDevices = array( + 'iPhone' => '\biPhone\b|\biPod\b', // |\biTunes + 'BlackBerry' => 'BlackBerry|\bBB10\b|rim[0-9]+', + 'HTC' => 'HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)|APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200|ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\bEVO\b|T-Mobile G1|Z520m', + 'Nexus' => 'Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 6', + // @todo: Is 'Dell Streak' a tablet or a phone? ;) + 'Dell' => 'Dell.*Streak|Dell.*Aero|Dell.*Venue|DELL.*Venue Pro|Dell Flash|Dell Smoke|Dell Mini 3iX|XCD28|XCD35|\b001DL\b|\b101DL\b|\bGS01\b', + 'Motorola' => 'Motorola|DROIDX|DROID BIONIC|\bDroid\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955|A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611|MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863|ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317|XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800|XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925', + 'Samsung' => 'Samsung|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310|GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510|GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K|GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010|GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100|GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210|GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250|GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420|GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700|GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103|GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800|GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210|GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350|GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660|GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230|GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600|SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930|SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730|SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351|SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430|SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740|SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157|SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657|SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817|SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220|SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225|SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105|SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200|SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717|SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917|SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500|SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777|SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229|SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409|SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609|SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749|SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200|SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497|SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10|SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700|SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700|SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220|SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550|SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920|SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535|SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510|GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582|GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K|SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K', + 'LG' => '\bLG\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200|MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK|E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690|MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740|VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802)', + 'Sony' => 'SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i|C5303|C6902|C6903|C6906|C6943|D2533', + 'Asus' => 'Asus.*Galaxy|PadFone.*Mobile', + // http://www.micromaxinfo.com/mobiles/smartphones + // Added because the codes might conflict with Acer Tablets. + 'Micromax' => 'Micromax.*\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\b', + // @todo Complete the regex. + 'Palm' => 'PalmSource|Palm', // avantgo|blazer|elaine|hiptop|plucker|xiino ; + 'Vertu' => 'Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature', // Just for fun ;) + // http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA (PANTECH) + // Most of the VEGA devices are legacy. PANTECH seem to be newer devices based on Android. + 'Pantech' => 'PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L|IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S|IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995|IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790', + // http://www.fly-phone.com/devices/smartphones/ ; Included only smartphones. + 'Fly' => 'IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250', + // http://fr.wikomobile.com + 'Wiko' => 'KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA|LENNY|BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM', + 'iMobile' => 'i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)', + // Added simvalley mobile just for fun. They have some interesting devices. + // http://www.simvalley.fr/telephonie---gps-_22_telephonie-mobile_telephones_.html + 'SimValley' => '\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\b', + // Wolfgang - a brand that is sold by Aldi supermarkets. + // http://www.wolfgangmobile.com/ + 'Wolfgang' => 'AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q', + 'Alcatel' => 'Alcatel', + 'Nintendo' => 'Nintendo 3DS', + // http://en.wikipedia.org/wiki/Amoi + 'Amoi' => 'Amoi', + // http://en.wikipedia.org/wiki/INQ + 'INQ' => 'INQ', + // @Tapatalk is a mobile app; http://support.tapatalk.com/threads/smf-2-0-2-os-and-browser-detection-plugin-and-tapatalk.15565/#post-79039 + 'GenericPhone' => 'Tapatalk|PDA;|SAGEM|\bmmp\b|pocket|\bpsp\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\bwap\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser', + ); + + /** + * List of tablet devices. + * + * @var array + */ + protected static $tabletDevices = array( + 'iPad' => 'iPad|iPad.*Mobile', // @todo: check for mobile friendly emails topic. + 'NexusTablet' => 'Android.*Nexus[\s]+(7|9|10)|^.*Android.*Nexus(?:(?!Mobile).)*$', + 'SamsungTablet' => 'SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113|GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L|SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-I9205|GT-P5200|GT-P5210|GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X|GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X|SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237P|SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T', // SCH-P709|SCH-P729|SM-T2558 - Samsung Mega - treat them like a regular phone. + // http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html + 'Kindle' => 'Kindle|Silk.*Accelerated|Android.*\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI)\b', + // Only the Surface tablets with Windows RT are considered mobile. + // http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx + 'SurfaceTablet' => 'Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)', + // http://shopping1.hp.com/is-bin/INTERSHOP.enfinity/WFS/WW-USSMBPublicStore-Site/en_US/-/USD/ViewStandardCatalog-Browse?CatalogCategoryID=JfIQ7EN5lqMAAAEyDcJUDwMT + 'HPTablet' => 'HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10', + // Watch out for PadFone, see #132. + // http://www.asus.com/de/Tablets_Mobile/Memo_Pad_Products/ + 'AsusTablet' => '^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\bK00F\b|\bK00C\b|\bK00E\b|\bK00L\b|TX201LA|ME176C|ME102A|\bM80TA\b|ME372CL|ME560CG|ME372CG|ME302KL', + 'BlackBerryTablet' => 'PlayBook|RIM Tablet', + 'HTCtablet' => 'HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410', + 'MotorolaTablet' => 'xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617', + 'NookTablet' => 'Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2', + // http://www.acer.ro/ac/ro/RO/content/drivers + // http://www.packardbell.co.uk/pb/en/GB/content/download (Packard Bell is part of Acer) + // http://us.acer.com/ac/en/US/content/group/tablets + // http://www.acer.de/ac/de/DE/content/models/tablets/ + // Can conflict with Micromax and Motorola phones codes. + 'AcerTablet' => 'Android.*; \b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\b|W3-810|\bA3-A10\b|\bA3-A11\b', + // http://eu.computers.toshiba-europe.com/innovation/family/Tablets/1098744/banner_id/tablet_footerlink/ + // http://us.toshiba.com/tablets/tablet-finder + // http://www.toshiba.co.jp/regza/tablet/ + 'ToshibaTablet' => 'Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO', + // http://www.nttdocomo.co.jp/english/service/developer/smart_phone/technical_info/spec/index.html + // http://www.lg.com/us/tablets + 'LGTablet' => '\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\b', + 'FujitsuTablet' => 'Android.*\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\b', + // Prestigio Tablets http://www.prestigio.com/support + 'PrestigioTablet' => 'PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C|PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD', + // http://support.lenovo.com/en_GB/downloads/default.page?# + 'LenovoTablet' => 'Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)', + // http://www.dell.com/support/home/us/en/04/Products/tab_mob/tablets + 'DellTablet' => 'Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7', + // http://www.yarvik.com/en/matrix/tablets/ + 'YarvikTablet' => 'Android.*\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152|TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211|TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\b', + 'MedionTablet' => 'Android.*\bOYO\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB', + 'ArnovaTablet' => 'AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2', + // http://www.intenso.de/kategorie_en.php?kategorie=33 + // @todo: http://www.nbhkdz.com/read/b8e64202f92a2df129126bff.html - investigate + 'IntensoTablet' => 'INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004', + // IRU.ru Tablets http://www.iru.ru/catalog/soho/planetable/ + 'IRUTablet' => 'M702pro', + 'MegafonTablet' => 'MegaFon V9|\bZTE V9\b|Android.*\bMT7A\b', + // http://www.e-boda.ro/tablete-pc.html + 'EbodaTablet' => 'E-Boda (Supreme|Impresspeed|Izzycomm|Essential)', + // http://www.allview.ro/produse/droseries/lista-tablete-pc/ + 'AllViewTablet' => 'Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)', + // http://wiki.archosfans.com/index.php?title=Main_Page + 'ArchosTablet' => '\b(101G9|80G9|A101IT)\b|Qilive 97R|Archos5|\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\b', + // http://www.ainol.com/plugin.php?identifier=ainol&module=product + 'AinolTablet' => 'NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark', + // @todo: inspect http://esupport.sony.com/US/p/select-system.pl?DIRECTOR=DRIVER + // Readers http://www.atsuhiro-me.net/ebook/sony-reader/sony-reader-web-browser + // http://www.sony.jp/support/tablet/ + 'SonyTablet' => 'Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321|EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP612', + // http://www.support.philips.com/support/catalog/worldproducts.jsp?userLanguage=en&userCountry=cn&categoryid=3G_LTE_TABLET_SU_CN_CARE&title=3G%20tablets%20/%20LTE%20range&_dyncharset=UTF-8 + 'PhilipsTablet' => '\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\b', + // db + http://www.cube-tablet.com/buy-products.html + 'CubeTablet' => 'Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT', + // http://www.cobyusa.com/?p=pcat&pcat_id=3001 + 'CobyTablet' => 'MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010', + // http://www.match.net.cn/products.asp + 'MIDTablet' => 'M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800|MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733', + // http://www.msi.com/support + // @todo Research the Windows Tablets. + 'MSITablet' => 'MSI \b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\b', + // @todo http://www.kyoceramobile.com/support/drivers/ + // 'KyoceraTablet' => null, + // @todo http://intexuae.com/index.php/category/mobile-devices/tablets-products/ + // 'IntextTablet' => null, + // http://pdadb.net/index.php?m=pdalist&list=SMiT (NoName Chinese Tablets) + // http://www.imp3.net/14/show.php?itemid=20454 + 'SMiTTablet' => 'Android.*(\bMID\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)', + // http://www.rock-chips.com/index.php?do=prod&pid=2 + 'RockChipTablet' => 'Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A', + // http://www.fly-phone.com/devices/tablets/ ; http://www.fly-phone.com/service/ + 'FlyTablet' => 'IQ310|Fly Vision', + // http://www.bqreaders.com/gb/tablets-prices-sale.html + 'bqTablet' => '(bq)?.*(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris E10)|Maxwell.*Lite|Maxwell.*Plus', + // http://www.huaweidevice.com/worldwide/productFamily.do?method=index&directoryId=5011&treeId=3290 + // http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index&directoryId=3372&treeId=0&tb=1&type=software (including legacy tablets) + 'HuaweiTablet' => 'MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim', + // Nec or Medias Tab + 'NecTablet' => '\bN-06D|\bN-08D', + // Pantech Tablets: http://www.pantechusa.com/phones/ + 'PantechTablet' => 'Pantech.*P4100', + // Broncho Tablets: http://www.broncho.cn/ (hard to find) + 'BronchoTablet' => 'Broncho.*(N701|N708|N802|a710)', + // http://versusuk.com/support.html + 'VersusTablet' => 'TOUCHPAD.*[78910]|\bTOUCHTAB\b', + // http://www.zync.in/index.php/our-products/tablet-phablets + 'ZyncTablet' => 'z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900', + // http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/ + 'PositivoTablet' => 'TB07STA|TB10STA|TB07FTA|TB10FTA', + // https://www.nabitablet.com/ + 'NabiTablet' => 'Android.*\bNabi', + 'KoboTablet' => 'Kobo Touch|\bK080\b|\bVox\b Build|\bArc\b Build', + // French Danew Tablets http://www.danew.com/produits-tablette.php + 'DanewTablet' => 'DSlide.*\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\b', + // Texet Tablets and Readers http://www.texet.ru/tablet/ + 'TexetTablet' => 'NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020|TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A|TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD|TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE', + // Avoid detecting 'PLAYSTATION 3' as mobile. + 'PlaystationTablet' => 'Playstation.*(Portable|Vita)', + // http://www.trekstor.de/surftabs.html + 'TrekstorTablet' => 'ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab', + // http://www.pyleaudio.com/Products.aspx?%2fproducts%2fPersonal-Electronics%2fTablets + 'PyleAudioTablet' => '\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\b', + // http://www.advandigital.com/index.php?link=content-product&jns=JP001 + // because of the short codenames we have to include whitespaces to reduce the possible conflicts. + 'AdvanTablet' => 'Android.* \b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\b ', + // http://www.danytech.com/category/tablet-pc + 'DanyTechTablet' => 'Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1', + // http://www.galapad.net/product.html + 'GalapadTablet' => 'Android.*\bG1\b', + // http://www.micromaxinfo.com/tablet/funbook + 'MicromaxTablet' => 'Funbook|Micromax.*\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\b', + // http://www.karbonnmobiles.com/products_tablet.php + 'KarbonnTablet' => 'Android.*\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\b', + // http://www.myallfine.com/Products.asp + 'AllFineTablet' => 'Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide', + // http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr= + 'PROSCANTablet' => '\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K|PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082|PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\b', + // http://www.yonesnav.com/products/products.php + 'YONESTablet' => 'BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026', + // http://www.cjshowroom.com/eproducts.aspx?classcode=004001001 + // China manufacturer makes tablets for different small brands (eg. http://www.zeepad.net/index.html) + 'ChangJiaTablet' => 'TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106|TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205|TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503', + // http://www.gloryunion.cn/products.asp + // http://www.allwinnertech.com/en/apply/mobile.html + // http://www.ptcl.com.pk/pd_content.php?pd_id=284 (EVOTAB) + // @todo: Softwiner tablets? + // aka. Cute or Cool tablets. Not sure yet, must research to avoid collisions. + 'GUTablet' => 'TX-A1301|TX-M9002|Q702|kf026', // A12R|D75A|D77|D79|R83|A95|A106C|R15|A75|A76|D71|D72|R71|R73|R77|D82|R85|D92|A97|D92|R91|A10F|A77F|W71F|A78F|W78F|W81F|A97F|W91F|W97F|R16G|C72|C73E|K72|K73|R96G + // http://www.pointofview-online.com/showroom.php?shop_mode=product_listing&category_id=118 + 'PointOfViewTablet' => 'TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945|TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10', + // http://www.overmax.pl/pl/katalog-produktow,p8/tablety,c14/ + // @todo: add more tests. + 'OvermaxTablet' => 'OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)', + // http://hclmetablet.com/India/index.php + 'HCLTablet' => 'HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync', + // http://www.edigital.hu/Tablet_es_e-book_olvaso/Tablet-c18385.html + 'DPSTablet' => 'DPS Dream 9|DPS Dual 7', + // http://www.visture.com/index.asp + 'VistureTablet' => 'V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10', + // http://www.mijncresta.nl/tablet + 'CrestaTablet' => 'CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989', + // MediaTek - http://www.mediatek.com/_en/01_products/02_proSys.php?cata_sn=1&cata1_sn=1&cata2_sn=309 + 'MediatekTablet' => '\bMT8125|MT8389|MT8135|MT8377\b', + // Concorde tab + 'ConcordeTablet' => 'Concorde([ ]+)?Tab|ConCorde ReadMan', + // GoClever Tablets - http://www.goclever.com/uk/products,c1/tablet,c5/ + 'GoCleverTablet' => 'GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76|TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2|TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042', + // Modecom Tablets - http://www.modecom.eu/tablets/portal/ + 'ModecomTablet' => 'FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702|FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003', + // Vonino Tablets - http://www.vonino.eu/tablets + 'VoninoTablet' => '\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS|Android.*\bQ8\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\b', + // ECS Tablets - http://www.ecs.com.tw/ECSWebSite/Product/Product_Tablet_List.aspx?CategoryID=14&MenuID=107&childid=M_107&LanID=0 + 'ECSTablet' => 'V07OT2|TM105A|S10OT1|TR10CS1', + // Storex Tablets - http://storex.fr/espace_client/support.html + // @note: no need to add all the tablet codes since they are guided by the first regex. + 'StorexTablet' => 'eZee[_\']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab', + // Generic Vodafone tablets. + 'VodafoneTablet' => 'SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7', + // French tablets - Essentiel B http://www.boulanger.fr/tablette_tactile_e-book/tablette_tactile_essentiel_b/cl_68908.htm?multiChoiceToDelete=brand&mc_brand=essentielb + // Aka: http://www.essentielb.fr/ + 'EssentielBTablet' => 'Smart[ \']?TAB[ ]+?[0-9]+|Family[ \']?TAB2', + // Ross & Moor - http://ross-moor.ru/ + 'RossMoorTablet' => 'RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711', + // i-mobile http://product.i-mobilephone.com/Mobile_Device + 'iMobileTablet' => 'i-mobile i-note', + // http://www.tolino.de/de/vergleichen/ + 'TolinoTablet' => 'tolino tab [0-9.]+|tolino shine', + // AudioSonic - a Kmart brand + // http://www.kmart.com.au/webapp/wcs/stores/servlet/Search?langId=-1&storeId=10701&catalogId=10001&categoryId=193001&pageSize=72¤tPage=1&searchCategory=193001%2b4294965664&sortBy=p_MaxPrice%7c1 + 'AudioSonicTablet' => '\bC-22Q|T7-QC|T-17B|T-17P\b', + // AMPE Tablets - http://www.ampe.com.my/product-category/tablets/ + // @todo: add them gradually to avoid conflicts. + 'AMPETablet' => 'Android.* A78 ', + // Skk Mobile - http://skkmobile.com.ph/product_tablets.php + 'SkkTablet' => 'Android.* (SKYPAD|PHOENIX|CYCLOPS)', + // Tecno Mobile (only tablet) - http://www.tecno-mobile.com/index.php/product?filterby=smart&list_order=all&page=1 + 'TecnoTablet' => 'TECNO P9', + // JXD (consoles & tablets) - http://jxd.hk/products.asp?selectclassid=009008&clsid=3 + 'JXDTablet' => 'Android.*\b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603|S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\b', + // i-Joy tablets - http://www.i-joy.es/en/cat/products/tablets/ + 'iJoyTablet' => 'Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7|Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst|Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam|Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)', + // http://www.intracon.eu/tablet + 'FX2Tablet' => 'FX2 PAD7|FX2 PAD10', + // http://www.xoro.de/produkte/ + // @note: Might be the same brand with 'Simply tablets' + 'XoroTablet' => 'KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790|PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032|TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151', + // http://www1.viewsonic.com/products/computing/tablets/ + 'ViewsonicTablet' => 'ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a', + // http://www.odys.de/web/internet-tablet_en.html + 'OdysTablet' => 'LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\bXELIO\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10', + // http://www.captiva-power.de/products.html#tablets-en + 'CaptivaTablet' => 'CAPTIVA PAD', + // IconBIT - http://www.iconbit.com/products/tablets/ + 'IconbitTablet' => 'NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S', + // http://www.teclast.com/topic.php?channelID=70&topicID=140&pid=63 + 'TeclastTablet' => 'T98 4G|\bP80\b|\bX90HD\b|X98 Air|X98 Air 3G|\bX89\b|P80 3G|\bX80h\b|P98 Air|\bX89HD\b|P98 3G|\bP90HD\b|P89 3G|X98 3G|\bP70h\b|P79HD 3G|G18d 3G|\bP79HD\b|\bP89s\b|\bA88\b|\bP10HD\b|\bP19HD\b|G18 3G|\bP78HD\b|\bA78\b|\bP75\b|G17s 3G|G17h 3G|\bP85t\b|\bP90\b|\bP11\b|\bP98t\b|\bP98HD\b|\bG18d\b|\bP85s\b|\bP11HD\b|\bP88s\b|\bA80HD\b|\bA80se\b|\bA10h\b|\bP89\b|\bP78s\b|\bG18\b|\bP85\b|\bA70h\b|\bA70\b|\bG17\b|\bP18\b|\bA80s\b|\bA11s\b|\bP88HD\b|\bA80h\b|\bP76s\b|\bP76h\b|\bP98\b|\bA10HD\b|\bP78\b|\bP88\b|\bA11\b|\bA10t\b|\bP76a\b|\bP76t\b|\bP76e\b|\bP85HD\b|\bP85a\b|\bP86\b|\bP75HD\b|\bP76v\b|\bA12\b|\bP75a\b|\bA15\b|\bP76Ti\b|\bP81HD\b|\bA10\b|\bT760VE\b|\bT720HD\b|\bP76\b|\bP73\b|\bP71\b|\bP72\b|\bT720SE\b|\bC520Ti\b|\bT760\b|\bT720VE\b|T720-3GE|T720-WiFi', + // Onda - http://www.onda-tablet.com/buy-android-onda.html?dir=desc&limit=all&order=price + 'OndaTablet' => '\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811|V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819|V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\b[\s]+', + 'JaytechTablet' => 'TPC-PA762', + 'BlaupunktTablet' => 'Endeavour 800NG|Endeavour 1010', + // http://www.digma.ru/support/download/ + // @todo: Ebooks also (if requested) + 'DigmaTablet' => '\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\b', + // http://www.evolioshop.com/ro/tablete-pc.html + // http://www.evolio.ro/support/downloads_static.html?cat=2 + // @todo: Research some more + 'EvolioTablet' => 'ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\bEvotab\b|\bNeura\b', + // @todo http://www.lavamobiles.com/tablets-data-cards + 'LavaTablet' => 'QPAD E704|\bIvoryS\b|E-TAB IVORY', + // https://www.celkonmobiles.com/?_a=categoryphones&sid=2 + 'CelkonTablet' => 'CT695|CT888|CT[\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\bCT-1\b', + // http://www.wolderelectronics.com/productos/manuales-y-guias-rapidas/categoria-2-miTab + 'WolderTablet' => 'miTab \b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT|EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND|BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\b', + // http://www.mi.com/en + 'MiTablet' => '\bMI PAD\b|\bHM NOTE 1W\b', + // http://www.nbru.cn/index.html + 'NibiruTablet' => 'Nibiru M1|Nibiru Jupiter One', + // http://navroad.com/products/produkty/tablety/ + 'NexoTablet' => 'NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI', + // http://www.datawind.com/ubislate/ + 'UbislateTablet' => 'UbiSlate[\s]?7C', + // http://www.pocketbook-int.com/ru/support + 'PocketBookTablet' => 'Pocketbook', + // http://www.tesco.com/direct/hudl/ + 'Hudl' => 'Hudl HT7S3', + // http://www.telstra.com.au/home-phone/thub-2/ + 'TelstraTablet' => 'T-Hub2', + 'GenericTablet' => 'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002|\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab|\bM6pro\b|CT1020W|arc 10HD|\bJolla\b' + ); + + /** + * List of mobile Operating Systems. + * + * @var array + */ + protected static $operatingSystems = array( + 'AndroidOS' => 'Android', + 'BlackBerryOS' => 'blackberry|\bBB10\b|rim tablet os', + 'PalmOS' => 'PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino', + 'SymbianOS' => 'Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\bS60\b', + // @reference: http://en.wikipedia.org/wiki/Windows_Mobile + 'WindowsMobileOS' => 'Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Window Mobile|Windows Phone [0-9.]+|WCE;', + // @reference: http://en.wikipedia.org/wiki/Windows_Phone + // http://wifeng.cn/?r=blog&a=view&id=106 + // http://nicksnettravels.builttoroam.com/post/2011/01/10/Bogus-Windows-Phone-7-User-Agent-String.aspx + // http://msdn.microsoft.com/library/ms537503.aspx + 'WindowsPhoneOS' => 'Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;', + 'iOS' => '\biPhone.*Mobile|\biPod|\biPad', + // http://en.wikipedia.org/wiki/MeeGo + // @todo: research MeeGo in UAs + 'MeeGoOS' => 'MeeGo', + // http://en.wikipedia.org/wiki/Maemo + // @todo: research Maemo in UAs + 'MaemoOS' => 'Maemo', + 'JavaOS' => 'J2ME/|\bMIDP\b|\bCLDC\b', // '|Java/' produces bug #135 + 'webOS' => 'webOS|hpwOS', + 'badaOS' => '\bBada\b', + 'BREWOS' => 'BREW', + ); + + /** + * List of mobile User Agents. + * + * @var array + */ + protected static $browsers = array( + // @reference: https://developers.google.com/chrome/mobile/docs/user-agent + 'Chrome' => '\bCrMo\b|CriOS|Android.*Chrome/[.0-9]* (Mobile)?', + 'Dolfin' => '\bDolfin\b', + 'Opera' => 'Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR/[0-9.]+|Coast/[0-9.]+', + 'Skyfire' => 'Skyfire', + 'IE' => 'IEMobile|MSIEMobile', // |Trident/[.0-9]+ + 'Firefox' => 'fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile', + 'Bolt' => 'bolt', + 'TeaShark' => 'teashark', + 'Blazer' => 'Blazer', + // @reference: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3 + 'Safari' => 'Version.*Mobile.*Safari|Safari.*Mobile|MobileSafari', + // http://en.wikipedia.org/wiki/Midori_(web_browser) + //'Midori' => 'midori', + 'Tizen' => 'Tizen', + 'UCBrowser' => 'UC.*Browser|UCWEB', + 'baiduboxapp' => 'baiduboxapp', + 'baidubrowser' => 'baidubrowser', + // https://github.com/serbanghita/Mobile-Detect/issues/7 + 'DiigoBrowser' => 'DiigoBrowser', + // http://www.puffinbrowser.com/index.php + 'Puffin' => 'Puffin', + // http://mercury-browser.com/index.html + 'Mercury' => '\bMercury\b', + // http://en.wikipedia.org/wiki/Obigo_Browser + 'ObigoBrowser' => 'Obigo', + // http://en.wikipedia.org/wiki/NetFront + 'NetFront' => 'NF-Browser', + // @reference: http://en.wikipedia.org/wiki/Minimo + // http://en.wikipedia.org/wiki/Vision_Mobile_Browser + 'GenericBrowser' => 'NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger', + ); + + /** + * Utilities. + * + * @var array + */ + protected static $utilities = array( + // Experimental. When a mobile device wants to switch to 'Desktop Mode'. + // http://scottcate.com/technology/windows-phone-8-ie10-desktop-or-mobile/ + // https://github.com/serbanghita/Mobile-Detect/issues/57#issuecomment-15024011 + // https://developers.facebook.com/docs/sharing/best-practices + 'Bot' => 'Googlebot|facebookexternalhit|AdsBot-Google|Google Keyword Suggestion|Facebot|YandexBot|bingbot|ia_archiver|AhrefsBot|Ezooms|GSLFbot|WBSearchBot|Twitterbot|TweetmemeBot|Twikle|PaperLiBot|Wotbox|UnwindFetchor', + 'MobileBot' => 'Googlebot-Mobile|AdsBot-Google-Mobile|YahooSeeker/M1A1-R2D2', + 'DesktopMode' => 'WPDesktop', + 'TV' => 'SonyDTV|HbbTV', // experimental + 'WebKit' => '(webkit)[ /]([\w.]+)', + // @todo: Include JXD consoles. + 'Console' => '\b(Nintendo|Nintendo WiiU|Nintendo 3DS|PLAYSTATION|Xbox)\b', + 'Watch' => 'SM-V700', + ); + + /** + * All possible HTTP headers that represent the + * User-Agent string. + * + * @var array + */ + protected static $uaHttpHeaders = array( + // The default User-Agent string. + 'HTTP_USER_AGENT', + // Header can occur on devices using Opera Mini. + 'HTTP_X_OPERAMINI_PHONE_UA', + // Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/ + 'HTTP_X_DEVICE_USER_AGENT', + 'HTTP_X_ORIGINAL_USER_AGENT', + 'HTTP_X_SKYFIRE_PHONE', + 'HTTP_X_BOLT_PHONE_UA', + 'HTTP_DEVICE_STOCK_UA', + 'HTTP_X_UCBROWSER_DEVICE_UA' + ); + + /** + * The individual segments that could exist in a User-Agent string. VER refers to the regular + * expression defined in the constant self::VER. + * + * @var array + */ + protected static $properties = array( + + // Build + 'Mobile' => 'Mobile/[VER]', + 'Build' => 'Build/[VER]', + 'Version' => 'Version/[VER]', + 'VendorID' => 'VendorID/[VER]', + + // Devices + 'iPad' => 'iPad.*CPU[a-z ]+[VER]', + 'iPhone' => 'iPhone.*CPU[a-z ]+[VER]', + 'iPod' => 'iPod.*CPU[a-z ]+[VER]', + //'BlackBerry' => array('BlackBerry[VER]', 'BlackBerry [VER];'), + 'Kindle' => 'Kindle/[VER]', + + // Browser + 'Chrome' => array('Chrome/[VER]', 'CriOS/[VER]', 'CrMo/[VER]'), + 'Coast' => array('Coast/[VER]'), + 'Dolfin' => 'Dolfin/[VER]', + // @reference: https://developer.mozilla.org/en-US/docs/User_Agent_Strings_Reference + 'Firefox' => 'Firefox/[VER]', + 'Fennec' => 'Fennec/[VER]', + // http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/ie/hh869301(v=vs.85).aspx + 'IE' => array('IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'Trident/[0-9.]+;.*rv:[VER]'), + // http://en.wikipedia.org/wiki/NetFront + 'NetFront' => 'NetFront/[VER]', + 'NokiaBrowser' => 'NokiaBrowser/[VER]', + 'Opera' => array( ' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]' ), + 'Opera Mini' => 'Opera Mini/[VER]', + 'Opera Mobi' => 'Version/[VER]', + 'UC Browser' => 'UC Browser[VER]', + 'MQQBrowser' => 'MQQBrowser/[VER]', + 'MicroMessenger' => 'MicroMessenger/[VER]', + 'baiduboxapp' => 'baiduboxapp/[VER]', + 'baidubrowser' => 'baidubrowser/[VER]', + 'Iron' => 'Iron/[VER]', + // @note: Safari 7534.48.3 is actually Version 5.1. + // @note: On BlackBerry the Version is overwriten by the OS. + 'Safari' => array( 'Version/[VER]', 'Safari/[VER]' ), + 'Skyfire' => 'Skyfire/[VER]', + 'Tizen' => 'Tizen/[VER]', + 'Webkit' => 'webkit[ /][VER]', + + // Engine + 'Gecko' => 'Gecko/[VER]', + 'Trident' => 'Trident/[VER]', + 'Presto' => 'Presto/[VER]', + + // OS + 'iOS' => ' \bi?OS\b [VER][ ;]{1}', + 'Android' => 'Android [VER]', + 'BlackBerry' => array('BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'), + 'BREW' => 'BREW [VER]', + 'Java' => 'Java/[VER]', + // @reference: http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/08/29/introducing-the-ie9-on-windows-phone-mango-user-agent-string.aspx + // @reference: http://en.wikipedia.org/wiki/Windows_NT#Releases + 'Windows Phone OS' => array( 'Windows Phone OS [VER]', 'Windows Phone [VER]'), + 'Windows Phone' => 'Windows Phone [VER]', + 'Windows CE' => 'Windows CE/[VER]', + // http://social.msdn.microsoft.com/Forums/en-US/windowsdeveloperpreviewgeneral/thread/6be392da-4d2f-41b4-8354-8dcee20c85cd + 'Windows NT' => 'Windows NT [VER]', + 'Symbian' => array('SymbianOS/[VER]', 'Symbian/[VER]'), + 'webOS' => array('webOS/[VER]', 'hpwOS/[VER];'), + ); + + /** + * Construct an instance of this class. + * + * @param array $headers Specify the headers as injection. Should be PHP _SERVER flavored. + * If left empty, will use the global _SERVER['HTTP_*'] vars instead. + * @param string $userAgent Inject the User-Agent header. If null, will use HTTP_USER_AGENT + * from the $headers array instead. + */ + public function __construct( + array $headers = null, + $userAgent = null + ) { + $this->setHttpHeaders($headers); + $this->setUserAgent($userAgent); + } + + /** + * Get the current script version. + * This is useful for the demo.php file, + * so people can check on what version they are testing + * for mobile devices. + * + * @return string The version number in semantic version format. + */ + public static function getScriptVersion() + { + return self::VERSION; + } + + /** + * Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers. + * + * @param array $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract + * the headers. The default null is left for backwards compatibilty. + */ + public function setHttpHeaders($httpHeaders = null) + { + // use global _SERVER if $httpHeaders aren't defined + if (!is_array($httpHeaders) || !count($httpHeaders)) { + $httpHeaders = $_SERVER; + } + + // clear existing headers + $this->httpHeaders = array(); + + // Only save HTTP headers. In PHP land, that means only _SERVER vars that + // start with HTTP_. + foreach ($httpHeaders as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $this->httpHeaders[$key] = $value; + } + } + } + + /** + * Retrieves the HTTP headers. + * + * @return array + */ + public function getHttpHeaders() + { + return $this->httpHeaders; + } + + /** + * Retrieves a particular header. If it doesn't exist, no exception/error is caused. + * Simply null is returned. + * + * @param string $header The name of the header to retrieve. Can be HTTP compliant such as + * "User-Agent" or "X-Device-User-Agent" or can be php-esque with the + * all-caps, HTTP_ prefixed, underscore seperated awesomeness. + * + * @return string|null The value of the header. + */ + public function getHttpHeader($header) + { + // are we using PHP-flavored headers? + if (strpos($header, '_') === false) { + $header = str_replace('-', '_', $header); + $header = strtoupper($header); + } + + // test the alternate, too + $altHeader = 'HTTP_' . $header; + + //Test both the regular and the HTTP_ prefix + if (isset($this->httpHeaders[$header])) { + return $this->httpHeaders[$header]; + } elseif (isset($this->httpHeaders[$altHeader])) { + return $this->httpHeaders[$altHeader]; + } + + return null; + } + + public function getMobileHeaders() + { + return self::$mobileHeaders; + } + + /** + * Get all possible HTTP headers that + * can contain the User-Agent string. + * + * @return array List of HTTP headers. + */ + public function getUaHttpHeaders() + { + return self::$uaHttpHeaders; + } + + /** + * Set the User-Agent to be used. + * + * @param string $userAgent The user agent string to set. + * + * @return string|null + */ + public function setUserAgent($userAgent = null) + { + // Invalidate cache due to #375 + $this->cache = array(); + + if (false === empty($userAgent)) { + return $this->userAgent = $userAgent; + } else { + $this->userAgent = null; + foreach ($this->getUaHttpHeaders() as $altHeader) { + if (false === empty($this->httpHeaders[$altHeader])) { // @todo: should use getHttpHeader(), but it would be slow. (Serban) + $this->userAgent .= $this->httpHeaders[$altHeader] . " "; + } + } + + return $this->userAgent = (!empty($this->userAgent) ? trim($this->userAgent) : null); + + } + } + + /** + * Retrieve the User-Agent. + * + * @return string|null The user agent if it's set. + */ + public function getUserAgent() + { + return $this->userAgent; + } + + /** + * Set the detection type. Must be one of self::DETECTION_TYPE_MOBILE or + * self::DETECTION_TYPE_EXTENDED. Otherwise, nothing is set. + * + * @deprecated since version 2.6.9 + * + * @param string $type The type. Must be a self::DETECTION_TYPE_* constant. The default + * parameter is null which will default to self::DETECTION_TYPE_MOBILE. + */ + public function setDetectionType($type = null) + { + if ($type === null) { + $type = self::DETECTION_TYPE_MOBILE; + } + + if ($type !== self::DETECTION_TYPE_MOBILE && $type !== self::DETECTION_TYPE_EXTENDED) { + return; + } + + $this->detectionType = $type; + } + + public function getMatchingRegex() + { + return $this->matchingRegex; + } + + public function getMatchesArray() + { + return $this->matchesArray; + } + + /** + * Retrieve the list of known phone devices. + * + * @return array List of phone devices. + */ + public static function getPhoneDevices() + { + return self::$phoneDevices; + } + + /** + * Retrieve the list of known tablet devices. + * + * @return array List of tablet devices. + */ + public static function getTabletDevices() + { + return self::$tabletDevices; + } + + /** + * Alias for getBrowsers() method. + * + * @return array List of user agents. + */ + public static function getUserAgents() + { + return self::getBrowsers(); + } + + /** + * Retrieve the list of known browsers. Specifically, the user agents. + * + * @return array List of browsers / user agents. + */ + public static function getBrowsers() + { + return self::$browsers; + } + + /** + * Retrieve the list of known utilities. + * + * @return array List of utilities. + */ + public static function getUtilities() + { + return self::$utilities; + } + + /** + * Method gets the mobile detection rules. This method is used for the magic methods $detect->is*(). + * + * @deprecated since version 2.6.9 + * + * @return array All the rules (but not extended). + */ + public static function getMobileDetectionRules() + { + static $rules; + + if (!$rules) { + $rules = array_merge( + self::$phoneDevices, + self::$tabletDevices, + self::$operatingSystems, + self::$browsers + ); + } + + return $rules; + + } + + /** + * Method gets the mobile detection rules + utilities. + * The reason this is separate is because utilities rules + * don't necessary imply mobile. This method is used inside + * the new $detect->is('stuff') method. + * + * @deprecated since version 2.6.9 + * + * @return array All the rules + extended. + */ + public function getMobileDetectionRulesExtended() + { + static $rules; + + if (!$rules) { + // Merge all rules together. + $rules = array_merge( + self::$phoneDevices, + self::$tabletDevices, + self::$operatingSystems, + self::$browsers, + self::$utilities + ); + } + + return $rules; + } + + /** + * Retrieve the current set of rules. + * + * @deprecated since version 2.6.9 + * + * @return array + */ + public function getRules() + { + if ($this->detectionType == self::DETECTION_TYPE_EXTENDED) { + return self::getMobileDetectionRulesExtended(); + } else { + return self::getMobileDetectionRules(); + } + } + + /** + * Retrieve the list of mobile operating systems. + * + * @return array The list of mobile operating systems. + */ + public static function getOperatingSystems() + { + return self::$operatingSystems; + } + + /** + * Check the HTTP headers for signs of mobile. + * This is the fastest mobile check possible; it's used + * inside isMobile() method. + * + * @return bool + */ + public function checkHttpHeadersForMobile() + { + + foreach ($this->getMobileHeaders() as $mobileHeader => $matchType) { + if (isset($this->httpHeaders[$mobileHeader])) { + if (is_array($matchType['matches'])) { + foreach ($matchType['matches'] as $_match) { + if (strpos($this->httpHeaders[$mobileHeader], $_match) !== false) { + return true; + } + } + + return false; + } else { + return true; + } + } + } + + return false; + + } + + /** + * Magic overloading method. + * + * @method boolean is[...]() + * @param string $name + * @param array $arguments + * @return mixed + * @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is' + */ + public function __call($name, $arguments) + { + // make sure the name starts with 'is', otherwise + if (substr($name, 0, 2) !== 'is') { + throw new BadMethodCallException("No such method exists: $name"); + } + + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + $key = substr($name, 2); + + return $this->matchUAAgainstKey($key); + } + + /** + * Find a detection rule that matches the current User-agent. + * + * @param null $userAgent deprecated + * @return boolean + */ + protected function matchDetectionRulesAgainstUA($userAgent = null) + { + // Begin general search. + foreach ($this->getRules() as $_regex) { + if (empty($_regex)) { + continue; + } + + if ($this->match($_regex, $userAgent)) { + return true; + } + } + + return false; + } + + /** + * Search for a certain key in the rules array. + * If the key is found the try to match the corresponding + * regex against the User-Agent. + * + * @param string $key + * + * @return boolean + */ + protected function matchUAAgainstKey($key) + { + // Make the keys lowercase so we can match: isIphone(), isiPhone(), isiphone(), etc. + $key = strtolower($key); + if (false === isset($this->cache[$key])) { + + // change the keys to lower case + $_rules = array_change_key_case($this->getRules()); + + if (false === empty($_rules[$key])) { + $this->cache[$key] = $this->match($_rules[$key]); + } + + if (false === isset($this->cache[$key])) { + $this->cache[$key] = false; + } + } + + return $this->cache[$key]; + } + + /** + * Check if the device is mobile. + * Returns true if any type of mobile device detected, including special ones + * @param null $userAgent deprecated + * @param null $httpHeaders deprecated + * @return bool + */ + public function isMobile($userAgent = null, $httpHeaders = null) + { + + if ($httpHeaders) { + $this->setHttpHeaders($httpHeaders); + } + + if ($userAgent) { + $this->setUserAgent($userAgent); + } + + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + if ($this->checkHttpHeadersForMobile()) { + return true; + } else { + return $this->matchDetectionRulesAgainstUA(); + } + + } + + /** + * Check if the device is a tablet. + * Return true if any type of tablet device is detected. + * + * @param string $userAgent deprecated + * @param array $httpHeaders deprecated + * @return bool + */ + public function isTablet($userAgent = null, $httpHeaders = null) + { + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + foreach (self::$tabletDevices as $_regex) { + if ($this->match($_regex, $userAgent)) { + return true; + } + } + + return false; + } + + /** + * This method checks for a certain property in the + * userAgent. + * @todo: The httpHeaders part is not yet used. + * + * @param string $key + * @param string $userAgent deprecated + * @param string $httpHeaders deprecated + * @return bool|int|null + */ + public function is($key, $userAgent = null, $httpHeaders = null) + { + // Set the UA and HTTP headers only if needed (eg. batch mode). + if ($httpHeaders) { + $this->setHttpHeaders($httpHeaders); + } + + if ($userAgent) { + $this->setUserAgent($userAgent); + } + + $this->setDetectionType(self::DETECTION_TYPE_EXTENDED); + + return $this->matchUAAgainstKey($key); + } + + /** + * Some detection rules are relative (not standard), + * because of the diversity of devices, vendors and + * their conventions in representing the User-Agent or + * the HTTP headers. + * + * This method will be used to check custom regexes against + * the User-Agent string. + * + * @param $regex + * @param string $userAgent + * @return bool + * + * @todo: search in the HTTP headers too. + */ + public function match($regex, $userAgent = null) + { + $match = (bool) preg_match(sprintf('#%s#is', $regex), (false === empty($userAgent) ? $userAgent : $this->userAgent), $matches); + // If positive match is found, store the results for debug. + if ($match) { + $this->matchingRegex = $regex; + $this->matchesArray = $matches; + } + + return $match; + } + + /** + * Get the properties array. + * + * @return array + */ + public static function getProperties() + { + return self::$properties; + } + + /** + * Prepare the version number. + * + * @todo Remove the error supression from str_replace() call. + * + * @param string $ver The string version, like "2.6.21.2152"; + * + * @return float + */ + public function prepareVersionNo($ver) + { + $ver = str_replace(array('_', ' ', '/'), '.', $ver); + $arrVer = explode('.', $ver, 2); + + if (isset($arrVer[1])) { + $arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions. + } + + return (float) implode('.', $arrVer); + } + + /** + * Check the version of the given property in the User-Agent. + * Will return a float number. (eg. 2_0 will return 2.0, 4.3.1 will return 4.31) + * + * @param string $propertyName The name of the property. See self::getProperties() array + * keys for all possible properties. + * @param string $type Either self::VERSION_TYPE_STRING to get a string value or + * self::VERSION_TYPE_FLOAT indicating a float value. This parameter + * is optional and defaults to self::VERSION_TYPE_STRING. Passing an + * invalid parameter will default to the this type as well. + * + * @return string|float The version of the property we are trying to extract. + */ + public function version($propertyName, $type = self::VERSION_TYPE_STRING) + { + if (empty($propertyName)) { + return false; + } + + // set the $type to the default if we don't recognize the type + if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) { + $type = self::VERSION_TYPE_STRING; + } + + $properties = self::getProperties(); + + // Check if the property exists in the properties array. + if (true === isset($properties[$propertyName])) { + + // Prepare the pattern to be matched. + // Make sure we always deal with an array (string is converted). + $properties[$propertyName] = (array) $properties[$propertyName]; + + foreach ($properties[$propertyName] as $propertyMatchString) { + + $propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString); + + // Identify and extract the version. + preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match); + + if (false === empty($match[1])) { + $version = ($type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]); + + return $version; + } + + } + + } + + return false; + } + + /** + * Retrieve the mobile grading, using self::MOBILE_GRADE_* constants. + * + * @return string One of the self::MOBILE_GRADE_* constants. + */ + public function mobileGrade() + { + $isMobile = $this->isMobile(); + + if ( + // Apple iOS 4-7.0 – Tested on the original iPad (4.3 / 5.0), iPad 2 (4.3 / 5.1 / 6.1), iPad 3 (5.1 / 6.0), iPad Mini (6.1), iPad Retina (7.0), iPhone 3GS (4.3), iPhone 4 (4.3 / 5.1), iPhone 4S (5.1 / 6.0), iPhone 5 (6.0), and iPhone 5S (7.0) + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) >= 4.3 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) >= 4.3 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) >= 4.3 || + + // Android 2.1-2.3 - Tested on the HTC Incredible (2.2), original Droid (2.2), HTC Aria (2.1), Google Nexus S (2.3). Functional on 1.5 & 1.6 but performance may be sluggish, tested on Google G1 (1.5) + // Android 3.1 (Honeycomb) - Tested on the Samsung Galaxy Tab 10.1 and Motorola XOOM + // Android 4.0 (ICS) - Tested on a Galaxy Nexus. Note: transition performance can be poor on upgraded devices + // Android 4.1 (Jelly Bean) - Tested on a Galaxy Nexus and Galaxy 7 + ( $this->version('Android', self::VERSION_TYPE_FLOAT)>2.1 && $this->is('Webkit') ) || + + // Windows Phone 7.5-8 - Tested on the HTC Surround (7.5), HTC Trophy (7.5), LG-E900 (7.5), Nokia 800 (7.8), HTC Mazaa (7.8), Nokia Lumia 520 (8), Nokia Lumia 920 (8), HTC 8x (8) + $this->version('Windows Phone OS', self::VERSION_TYPE_FLOAT) >= 7.5 || + + // Tested on the Torch 9800 (6) and Style 9670 (6), BlackBerry? Torch 9810 (7), BlackBerry Z10 (10) + $this->is('BlackBerry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 6.0 || + // Blackberry Playbook (1.0-2.0) - Tested on PlayBook + $this->match('Playbook.*Tablet') || + + // Palm WebOS (1.4-3.0) - Tested on the Palm Pixi (1.4), Pre (1.4), Pre 2 (2.0), HP TouchPad (3.0) + ( $this->version('webOS', self::VERSION_TYPE_FLOAT) >= 1.4 && $this->match('Palm|Pre|Pixi') ) || + // Palm WebOS 3.0 - Tested on HP TouchPad + $this->match('hp.*TouchPad') || + + // Firefox Mobile 18 - Tested on Android 2.3 and 4.1 devices + ( $this->is('Firefox') && $this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 18 ) || + + // Chrome for Android - Tested on Android 4.0, 4.1 device + ( $this->is('Chrome') && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 4.0 ) || + + // Skyfire 4.1 - Tested on Android 2.3 device + ( $this->is('Skyfire') && $this->version('Skyfire', self::VERSION_TYPE_FLOAT) >= 4.1 && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 ) || + + // Opera Mobile 11.5-12: Tested on Android 2.3 + ( $this->is('Opera') && $this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11.5 && $this->is('AndroidOS') ) || + + // Meego 1.2 - Tested on Nokia 950 and N9 + $this->is('MeeGoOS') || + + // Tizen (pre-release) - Tested on early hardware + $this->is('Tizen') || + + // Samsung Bada 2.0 - Tested on a Samsung Wave 3, Dolphin browser + // @todo: more tests here! + $this->is('Dolfin') && $this->version('Bada', self::VERSION_TYPE_FLOAT) >= 2.0 || + + // UC Browser - Tested on Android 2.3 device + ( ($this->is('UC Browser') || $this->is('Dolfin')) && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 ) || + + // Kindle 3 and Fire - Tested on the built-in WebKit browser for each + ( $this->match('Kindle Fire') || + $this->is('Kindle') && $this->version('Kindle', self::VERSION_TYPE_FLOAT) >= 3.0 ) || + + // Nook Color 1.4.1 - Tested on original Nook Color, not Nook Tablet + $this->is('AndroidOS') && $this->is('NookTablet') || + + // Chrome Desktop 16-24 - Tested on OS X 10.7 and Windows 7 + $this->version('Chrome', self::VERSION_TYPE_FLOAT) >= 16 && !$isMobile || + + // Safari Desktop 5-6 - Tested on OS X 10.7 and Windows 7 + $this->version('Safari', self::VERSION_TYPE_FLOAT) >= 5.0 && !$isMobile || + + // Firefox Desktop 10-18 - Tested on OS X 10.7 and Windows 7 + $this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 10.0 && !$isMobile || + + // Internet Explorer 7-9 - Tested on Windows XP, Vista and 7 + $this->version('IE', self::VERSION_TYPE_FLOAT) >= 7.0 && !$isMobile || + + // Opera Desktop 10-12 - Tested on OS X 10.7 and Windows 7 + $this->version('Opera', self::VERSION_TYPE_FLOAT) >= 10 && !$isMobile + ){ + return self::MOBILE_GRADE_A; + } + + if ( + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT)<4.3 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT)<4.3 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT)<4.3 || + + // Blackberry 5.0: Tested on the Storm 2 9550, Bold 9770 + $this->is('Blackberry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 5 && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT)<6 || + + //Opera Mini (5.0-6.5) - Tested on iOS 3.2/4.3 and Android 2.3 + ($this->version('Opera Mini', self::VERSION_TYPE_FLOAT) >= 5.0 && $this->version('Opera Mini', self::VERSION_TYPE_FLOAT) <= 7.0 && + ($this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 || $this->is('iOS')) ) || + + // Nokia Symbian^3 - Tested on Nokia N8 (Symbian^3), C7 (Symbian^3), also works on N97 (Symbian^1) + $this->match('NokiaN8|NokiaC7|N97.*Series60|Symbian/3') || + + // @todo: report this (tested on Nokia N71) + $this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11 && $this->is('SymbianOS') + ){ + return self::MOBILE_GRADE_B; + } + + if ( + // Blackberry 4.x - Tested on the Curve 8330 + $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) <= 5.0 || + // Windows Mobile - Tested on the HTC Leo (WinMo 5.2) + $this->match('MSIEMobile|Windows CE.*Mobile') || $this->version('Windows Mobile', self::VERSION_TYPE_FLOAT) <= 5.2 || + + // Tested on original iPhone (3.1), iPhone 3 (3.2) + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) <= 3.2 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) <= 3.2 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) <= 3.2 || + + // Internet Explorer 7 and older - Tested on Windows XP + $this->version('IE', self::VERSION_TYPE_FLOAT) <= 7.0 && !$isMobile + ){ + return self::MOBILE_GRADE_C; + } + + // All older smartphone platforms and featurephones - Any device that doesn't support media queries + // will receive the basic, C grade experience. + return self::MOBILE_GRADE_C; + } +} + + + + + diff --git a/services/helper/captcha/Elephant.ttf b/services/helper/captcha/Elephant.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2763d31491f0f2b9b74374ec26aa7f74f680bc13 GIT binary patch literal 44820 zcmbTfcYqwll|Nq9Ju^MsGu@MO>d85$osGTP)V-^fw8{b&A)$mo5*ZOpHW`d1>~U_MzF|tL~ZAN_Y3qZ?u!TYr3mmz3_RVN(du_ z1avu~(J5;;lxvIK`5WQLZ{fM-)B`)t{K{ER-ir_(Mu@hadj7ehV*xc0(yfN`kL)>f z?}5{8pEJPs|AO!Tw)cz+_cRmhFGNWHD}-*iW#8@{yB^_tB-G90NW`u6r1)ci*8HY~YfhI49-`?ki^LHHB zarjL7eEo?CJ@^>>UOc$t!0z!qufYf&gx~AGeCDBZ&TTjwaQ{cIgFgTE%(Hi&`H4F| zy@b$b$^GwwWB-D0MS3LASL1KO5v0Atu@i6sA&O#ktX@yiGOF_D@;NJ8QtDf*k; zzrpW*h->P33K9At2@8n99+T!R7oTPPn17l=4lbIJRR-4`7bh$lV zpFa=`g(G4#7EdHo=}b14FBD7VO0`yRG+XU~&fw7S$mp0fzG`A}YI;UiX6IJVuUWfp zVf}`Un>KIRy6uD$Pg*>A`zbrn&Qo{o-m`b#Y5Pw<~t{so6Gyy)Uf z-goI`mtS$^Rad|NnrpAS{)QWGy7?CLfm_iBf#x4kyZG@t(LddF_dWNbPat&v0}np* z@TVU6^rN5o?B^bP{PSP<eFBQXY|b1zwym)J^S3Zpa0GaFMjuX zFMa>zS6+SX^*4U-Ci>xzkPiO^t~LoY(<2?a6nz#wj=qeZL|;WqxQaL69r!T*5cNFu z0`)Wc>!MTii(xS)ro_Q$EJno)F-y!9^TqNpIkr3b;vcCcm_sC@d(mU)i*S{%pl9GJ z>+vb-RlW^Z`4L>jA$rB2D8f}b|KlpV;3{ZoY3Y}}X$X_|rMs85EnT=YvSdN1%OdpN zkKX;bcK+SV-@Wvm{cqp)_FZpZ_x2~=zVGdWZ=djX>8Cz8OQv=eIvJe;?>iCj>GI)${aXF>zrDN$2OmQBpsUgQq1|ohSLhma3%U_~9NiBiw4xi(RWKhnqmQ8L z(4FW3bT@h!eFHs%J_YwX6@38i`7N{?eI0!hJ&&G6&!KOlpQ1hJyXZUU1@s8oi++x7 zMc+r?LocCy=q>aPx*qLEr=io)0dxjBi2ey3LT8~f(b?!6bRIevosTX+KSPJnMd(6w zF?t`m1bqP{~H*U=m32k3|BRhY@oqpzXQ!Cd?^x)NseTJ$1% z3g+Z1)Q=GdPZN3KpCAq>3|z4ESMt8(TY8W7Eq!B2Cdae~u7hxZrXWY~1O21_Ki*IH zKYsH(Q1jSF4kcms&PV78fj65t*2om5;^O0?E_uVctvhZEoO08)(~1RAobv9Q6d%K< zh*OVEpTF;hsZ%E5=wozpcGu&KJ{8a3U>!XE!0z6WnfOe6hxj;SfM((iJDyy+eCK1j zlqgzn7!;?)C#~qzEvLxQsP=Pc+;0m%dhGI@J8n3>rPff{^}k=T+`B8BKkYGn>c->l z2&aJn6asBU{hk&#o?CC@H;^JCUw>+s6I3Wih9F6OmYm`Zs3 z&(|vUF!eS(nYj8)x8n>v+ja6};1vFtcj6OM-iZf_pG-egynos|`3ZPFSiCRwP~kyG z=AjJrhT)m#49{F}`uXq*f9Icm!3C$E4yF!Omu{zSprgouCeSSUf^@)O=+uPCg4Z{( zYW#9P_RkiS@ky>zP?Bx7A=L_kZ(NBXc9nIUPO>o*LmOg~Hrnrtl{+)D1Cu^^bcRQx z(&)NTy3mlb4Srn~$CAkb*=6F#8OG;BG9BTi0l+l_crg#dhkq}Tx0e=GS^!G}cM~$$ z$P3IiJm5_JS@L4*Ji7=-;iUT4saBG$RwwHD@x`O8Hg@|9Rc&Os85%*Y%v)i~lX)7k7+be3to?lktW%xv^F$ zUC^2NIQ|byG`C+mc;U!Kwvo*bxWM`bmwrY+LH!9?fCDGdMs%gLkqza-gJC(X%a$Eh zggi1=cXP_aa*A`Z$3C>awdLb2K?m24Ba zXK)%Di0G$P`mShQueBV<{D6Mq;JrB+J3?}Bi_jv02PMd`nr$W1L3zcRPJ3q#Tysqx!PDlK*`(tWL!tztGmHtM{9fy zY@JG_Rle+#ROQiD%P|UEn@Xpfb(f%H+%PMxc3tIrXs&7Fc3&?NUU$3ke>Ki8IDF^L zhJ#yEaaTC%3#3nGlAgd}pm3a8MPrv-~r+01H{Djr| z-m~BNtKDjT=Lc`ROZsDltKG1CH511vw2n0)+M$}7-qpa|F-=XuG=Y*geE-qin>X+N z^vjri?!T?%T0eZtY_$W3!YBoZ_bb%DqH(lC8gr(c=|n1#ZloIN$<$j|H zn@yiGUgO63YA~n-cs6jziY1>_Fwo=aGz#*(Swd!FcC~+KEWZevk9^VC#10~g79A^R z*wZ31sp-CWjDZ=4&#H9B)i+`!+vyaDagRTswdddHYKa#LHJvFo?sOMLHzV6DcFMcY zEI5W7g2iT9<>1Zxd^Bq}I!8HQq~x~FM5t71z2L}Y9mbR5oG>r)_J)-<2Q#8Q!CmVK zUd6?2Vj*avC`;XC64wfZE|;J%^&E9JNLxsM~1VWF-;P!159ceR? z7|dpe26NfLZJB{~Hajo?zg&8E=_Y(BO~HM@0#_L9!6H7Z*!{q6daw08aC0cQ_Ft&i zR2>z8(K;16z$&bbWA!vI8{E8XCOZ8nlP<$Brct6aeI2HV!lqvP`tA2T`RxyW^c#OS z*y4$3S}-zJ^3JpPj1T_ikJo+hH~)U~hVu^<%ctBrJ^#Lg#q##s$=Cp!}TG(Xk3+U>UQKwOZUrwm=6|wFMSB-x&kROM1ez#}#eDUE6J2xw zH)Ha&Jv0U1iX{$T@V-*iqsJIj2jde<_{~xzWS{}Aee_Ten#5j zi%7&`wSn8!NxTuWl2Z^$Wj33vC_$F1n1gzo$7V=GD&lKrt?mFG9+8osL>p-JIAyI<=~@ zp^++&76)cGp0-fB@R7AUziaZ1C-1Ii9K4>Fj@-=*Uzs}L>bZ?O>!C_9Q5+s`j_xk; zXY7%7)z{i}6y+`Cgzh6SS_jPYXJE8b=t1dHPui2AxFnb2(p)Bx4rKD_e5RA`WM)&d z>620?rB6?tp1wGBar(y8jp9!zMHL?qfTc2* zs(4f!M)b~Ei+B5(C-l&f5ynOufJ5Qg-|FmbzdFSt0-B~?WPo}eG z-tOAJb+BK4VH?V66?23%rS@}zAN=9>MfA#7@C#JF+F1p zC=M5^0CG2)Oa>`{1WO*(?W4Y=8o>H}XX9g6NMC-zWbPC>9k#fwf9D z+rSr|C@3tKa|P2!n}kd76!vU7clZ)~^ZDoNJ(-xfo8kk6`MaN;fB5Oi+2_A;(0}U1 zEu2r=n@5FU&NcPLw>ASuxPc?yNBsgKdDly)`dFXIXO7sjY}S-DkJ-oZb~{I5yWZl6 zHLFT6&q`IdFs8?nMK44wIASSV#w=?shb)&_K4AHrFqfU@UyVb7sZ40x)>hxKGKor0unlu@h z`7Lbx)ZzJxD00OEXV+y>EFIc<=#jTRc<{!_Vtciech9vZR!>bj^dQbRn%od@bA;zN znJhOKqnGc!;+E6an5``rELvat=8hc12rldz4X*` zKLXt3Uph>Eiw>a-@P95J$OrSGd{}38V_#H6N$g9dFcKlyU|{_*(I1t>s32QdRc)%8 z0LE#37FfUUHh^^l9pDjY7-V0rN9lGNOf8sOh5FcEzCD&}Nj!&4BJk=c zczXmlxZ1V8y6%NmV2=AOmi-It(8m&ym;vx|I5suCaPHRR_dd0MPhzA{Uu|~!!dbsr zXPK|;zxhk}Z?-v8eN`#5GvOH7)I9U5(S29W-Tlr`HI}ceD!Wpg#hTuA_7D}#Ub6R` zj}qw~08F(6@k!#FMJ2n-0*cuH*vf9^>;@HK!GH);Lg?#Qpk-SDx(527tN-2yUwC#I zg;A)rbP?v^UM>_xImr|8^)aQ-Ku~4S-oureT2!Is{|zqb6}O*mt<~$&>He&pvO ze!klgsR)k$V)a+_Pp$q1^k`=3@6;&pt_fuI!_qEe!k9Fsya{j8o600I$xMo^vO+WI z;~PzfsTF9ITVt)Yt?jKtt;<@UYdzEAT2)rb`#7cQ;7!#)m6Dpgz|wh|HX(P*km1e2 zn7fq;2F!d5ZPKGNcGL3=U%n9e6bK2ps{|cZ|b16qZN?i>+z6G5x%_LgUK}GJ&&Q0hyv;>>f zkp(3a<)uJLP)2O5q@NH*Of5F)otEd;d0YBGOP2FtK0ry{s$dgjN#G?)1|6pgJ7Lsk zVaPDSJroCqiugt3SnmllubFAUFFms#e04!bIovH-ljVJ(xcCfuzl1jh?t4@ZWdAfY8*> zfZ}O%Sel)7KID9ndd~TEmp*|dOko$eVCjh4g_KZ)4GN0c1Ta}(LrNO)A)^bKjfgUt zQ-)A55snyAQX(ig4YB}!9#EP{6OfZol!z7aEYum$j*%cHVK|7Vr7e_dKZtYG24m@{ zJ0LLW>G?{{Mmmi*ZQaB~I8P#G?f!Zw8nd~WXAZs|5kmfKH)(d%+(P2-fBma#U&7_F zGZeT@I-M`oa8biYo?R0O=yX;qHu5fac0XtW@6r-2Q$&MS&?wp`jdliyhXfXYZ!o|O z!59TLonncYZ;H*fambrxnQzgwmZ_} zT~EZ7QGG;jJ}%(zpS*u2vuV?)q9_A{CtO*MhKf?0jhYh`e3NngSbe)d;qKRP_d;x? z^~8z%NlEk@z^BAQWu~e~E~6>DVSH?3)(|ot9B%D3;_lrf4_pI0@!z1KB518t<;@Nu zXz?M3L9(!dnKsaf75Grl8We+-pcGse)CYNs1exH98_Tqt2ReXsEfPSaO*u)ysC)pS zr=IKM@sOCDElw1r?!e4##t)G5owzWU()H!X1B3-gz&7 z?yk?BTNQ>}X2#5>#@h9<yzI&|3D6*tenF6w8Qc^Mn zs8nP;+tgeK}AI(5>-Qn|q)hXdj{PK^1dc6Psc95K2-I`d;3)qRpO z>{!*kZDN;!)F2Q_=z$V>bg8r&8_hf=SR5fHr=T3C1VXG5@Ux21!5OIvk0pv10>(Uo zNp&U!-r`930~Tj66i>)Zo|6+!*1{8hloJd=>BJ}PC5`%|fFQ@S5H=(PBKio%Pb62h z_9{=NZ7as>`kz4?T5rwCr_HP`k)aL_l_=T|^4RsQBnb86?$ak;vW4zGLm76CYa{)S zk{BfNcN|FwFO(tTehtW66aABPfDSkUu|PdA8Q2&&HE?#|%D{&L_XR#5_*USxz%K)T z4p=cXhz4qbRRO)-<_P$sVFz6**2|SdJuOvoLPD`Y{=;hHl_<>?ahi^@eq&&a1!1ri zQMpo*iX{@%kP`q}NKix5(TgNNsImy?t)raN=MN0gjUE;Tm8?duTwod*#^r%TXUwrqg z;bbkD&Re2hjaIZLR=(xJz`?X6ReI96*0IqofxMm14le;i#c~Y5h z1c68>jTWmk>Z}6yRZ>ZCWxeopaf*=xHey%>Mfip>h1|$!L_^rpGGb%Pi@if(OQe&+ zsZgodY2j8#E)H?BSmb3(h?OlAa1-IBMG%_KBCrcUrw&AeG##P}7HM!y4hgh=+~tAP znQGY}k_KTvNLxVMk5JCVxa!Rn-xaCB^JSQ;K5US&ibPbLgdIj6KcY>QJeX)fnIo;};f zjLMqW!Wcc?k+3D583|=`@ybZ3-O3GgLefY}kmI>y_)^nhnyA97%ATP1RxqauMhJIm z|9~|ix{E=0dq_)Vxs~h*lei+4Ml%f7B6&nPHcB-9WI$!ZW7zXsX)?i~eFu^*d%4R8 zGx^XiY)i#5E@L;6b2+(0aSm@3pIFI>E4Y;Kz@K7=FRNRHUf#^;70>CO&4taE*utjjWnNV1OEKXS9 zBqpr-0zGndY|kzdjwWmavQ4YK)^1}K$88F6dj06 zci*+dYayTt6Ut^deD=e)<9!}(%&XEVQtJ+sh?>&VhV0{`f1wt;#fli!>8+3;Wnz&E zex>9U4FKP}Yw+i-M!#6MJ)m?m)-*oxEyF%M7Kzr+o)ZnSS}eW zVS6bG}C>T7Z(f^#V7L4FuoM;CC6J)0gKcNm|~M+<;auNDL}KR z$Pyn1Q~XE;KVNbs1Z%f}$AFgPIKjjfz!N8^QHC(eNzR+6m{Z-tr{cr)WO?)}jZ z?tA!^*U#8kIq?H?d#}8NyluOxht1>QYb8(_%}50Wff5hp`J6?uT2!L~y)~j_$Ou!A zGge$!PP+J9ST-I-=)~Oipex~6)!-cCJrS5LP{5p^%RoapNT3ch7dSuOWBjGp?>jvg z9NauPz77cKlVIouBfwVbL#Zv zL^>2QS^0LE%``$@Ru~vm_FlbZiqieylMlW6gZobTB&N1qbv>Eo^$+JF6|0k}zMstY z(tpyoz-$kqX>_?XZ)EDZQhjPNh9;Js7^2D)G|AUhHwH~uAi2wowqx&-^9X)>7&$wVQQ zHkYGHxWGy2C`4Wq8iHX4vyh;}G|SR#Dw&oNNkI;j*#Ku|C5{z_I4nCg*iW)|M7e3& zOtS~X8`A!c1_Oy`K`f#d3WN}b7WyV$Jn;&_LsY~6I_jQ>c5Z%{k6Ia<%@A69#ir_L z>F_mkb9e9UwDyJUkiCp-+cJ`!Zcbfv)y6kZz3f132=8&7^8T%jM7~yTm5T4%d0k+z zn&|$`wti2sl1SCVrIGW19#E4`2US~Nm%-EQjCVqYbdGlxb>s9Juskd_&Ksg3$%& z-#uiG%}i}Nm@ea3B^uK#2wwVksCf3m=n`m3%39+-CZWV*;0xF|0V#3B#UZT17_GKg z+?7BfS#a?(YkIld;$=^Sm-RgH{9vv?F4UnB`Ot)hU`9<*tE1FR6^wNe_lF6hp@lXHlm^k! zJU^8Z#}gw=d8nDJ;D|)#-S=#tsc?qHG=7mythm=n&$299XITc1FhZw$r=J$ zjiWoIZHzB!wGR1xmB5hF8DNKUN+!pqJBrWC1yo`w20hm?W{f^UuAJTg=FVs_=NevL z(I1F9is9mz%(9}X~4`8Vgg%%(bxQI5-HX&ixxW8;oP^P88L#tmvh;T{KoJ=w(j9D zO2!K#s}qxlS0}}2x0Xvf&hN~osv_6@gDIFP&zyy8xYLN~!HtGRHG;|IzA32;I)ggm zg6Q2-+Y5)jettwft@Am=Z^%1Zb~UhS#d^=eO$3_~x)y-06|_Yff`VpSE7B5M6}>wn zvRR-j_kXiZcw#QLE?vm{-XAN~w&gE7M$>%- zH<~dLOdx)j$<_YQs-f!@Pe4z>5&!(1^PmG^m4xbrxzGtx-M~P|$>ne=b{;^tnRD79 zZ(u~M9pWsE(P=i>?1*zXO)kXACMPd*J$<3^0gMZzOY_5s-cb1j6bT?7(0gjYINHQe zr%a4d)P->?J(iQ7XjqS6{IBP%{!+_I{if@Nup|D-{_X=N>dSii`}^@4(~&voXJ<*q z-==+#<(QCCe2f-+QJWOD2{DOF3O*}s*Lj@L81D=Oki^FXi6Nkj@T|&*YOe@e0`ZAO z(L>}Q)$WyXFt%PXLps%}_tIl&n_B#VzwOhRbliMVy1Lo8a-uTJ={UV_zAZNS`QJ>;W6EH~L~j?Fq+wZ}~=Az)8P@O1K-BE8+AEZsQxF5U#k} zLhk`>hZq>yZ7{Md=;<_an6hDKO7f%xheTV2u+?N|osoDJIsEasLE;U9Bx?nM7aJp_^hS=}#?)mD&xB@z#Y08BoEhIuCuoKdNi0 z1vO+o`jsXyL2ehBymT|&OFOnGyVoI%)zbM1#yU4Ft*bT8sJpRKbO!S6(OCV)Qx>i> z=)Xs^RfkFNIYPCe?A8HGA(_oikJd&6jG};EU7y@_ZOlg&YNkAH(hHiNi3SEW(enQ1Wo&W>FZ=_95L4v|klSP28 zW^>*^*qf8xPR^Z^1&C{fiDDqs(DJk@XLj;7Ej$gX0St+zGvJlz2i0v&LoqKC2UTGM zs(|hT+n@>|Ll)y`E)ft$WnHK;qPE>}-u&jEL5K)uP#M@3OkpQws!ndd@I{@Nhy;xk z9gRg})E~~eP7;N>H#1YiFJ%J|-Ln2-tuu4&Az>}s9FG?_D9R~J5J!?-O^Af7)U0vsT$2I3Baq6^;8l(OP86fDPasE)>yDR+`&M(mUNem zNw*vdaI!nZ%LRbfs#fef391QKy@!{`q`+io(*gq`S_Dinfq0}WuGJ(?OK)Oq{fKx^ ztq`X5;GMdJHxgkfHX^oJilUs+GEQMfKM z=%n1f%S%x=7^x#4(I%(n%v^KMp=(mE-ndzq4HvAw>_}0UU38ljHptm&#q8!;hBk>j zS+F3xNi8gac^bp1MhM^vnI&Ma72R}2H6TvP#kDFvhN0NtB^%Gb*?ZD+g(WcJu=Qy)@Vf3_A_k~izO%M^L-N_Per<_G@=7P>ZqM8T2EG)UGGEm+O*@P3& z?b0GLgi?hK!);~5Ah*G%h$XIIQ`$aW*=FM==M^;0D>EcAFwbsdW-KD(;^(u4YGVUG zFK>47^z8ib@Va%g@j^jl#Riu7247*Z#7+v*EG)ul zGh5ad+Wv^a)5+GR8Shka{$zqh{|tO~9Y}u@9hAnAFIiKn6|PPz@gxf_9qXeN-oh%p znXRI@kvGx|$_W;y3(5zfA_3xZm3pogl5-)RmR(TTLD-GJP*vDLKnR9dPBW;Sr^Q2o z`7{b1O@M(7YxisxgpolN_tIj(i+!E#G+^2>IX1N)7|`AQE*}8x91O-nEH(xC?u#B^ z$eymow`K}+Lq9JiJdk|0VsEPSy=*Q~@GPNH+Oy1;zt^(auo zB#9wt9!~Gr?>kVr#~>@cL2*fy3ZbE`8Qh!Yrv40vRE*~ zr$k}h12cA&`K?@OYsM-6FO1(E1Nt3N`Tobi_eCiHT)&djVF?oOIj}ilWFpmMfT}S+ z%Dep;?eXpkevLtWDDZ2~2>f1~u_O1ZYzjA3{lm|K9hgNoNn62V8W@czXqHu4O^yT? zz!8Se5w36fOyflO#+e}oDjAJVBk3ArumphFAc5x(#MvDSwk2O(Msw=n?fyTy+RkM8VbCIn7dD0MR(H1aVm649E?-6gMs_f~jAB4Km2fLnI3ZNe7sP4R=&7ojl;#oCa4acY z%gUY54&Q{^aZuHkWcP)>M)Pz{o%J*OG<6QqNUUVns7gBP?qP?cBV5<&t8-7L)WdIi z7@-NpwKZO0$YR+!0^DQ+i0sNWUYZl2p_xA9CmISe{5{14pg;eK1Lxd%*I9?|d0rQd zi!mDORE+<-{WssdfB(%l?H4v0ThmJ2(H4a*Olw*N^`K+_bIC`Y1pRP9-u5(U{KCSG z1?mFR1^EjKbRr&0R(V=+4-jJtWhRhli}M^^GG!pT?1r+!lEXPr?UYK!L{$z}dD)m( zsjm7qnwlYcc1&eaqI2L0nig{NNdlcgt2EUzIpTM_oLHNLWplX#qoh5(ga>AINlzqV zq-jQsIw848b^j0#0p)EW5kt-JGL|M$XgEG z3QK>$-@(6!D)hb5NDb96xFfjc_t^tD69o9cNkO-ul=Q4r!$OlSnS6|$F&cdqOS1*_ z3JIUoY6?Ng=O@wKzCTU^WGXjnGppk7Wqxkg)f@(d!F8&bLShn>1}qUDhbmk188ZHCTVNP#adI0fG5E!NV(xUaHdRpGo1`D`Z5J1ODih1IEmHxiEL zvyIs7f?%`8JTG_$hs1!>;|~=J>-2u2ZsI|ylV5msR7O~~l;i*`8{;pCW~lUX}2Q$&BO%DU(GkuRzlRA~-{SwvzW zTes5jCNYM-`q1n-byN2ro{6EUW{Zdp=5Qz^hFHoSjbJZ1A|+DU%*@-Gv~-`!X0r(g z7D`dC4iM>W>S2P>mfoX$FqQZ;wGK3rQCld`7DQS*$vnJ)x6L(EXSW zx=&;sx~vX>h=i8&R#l*yIRUpx4YaDjiUD91FBwP`#el&a(dk#um<5aYP}1rTF67KJ zw%Y0-?|18Af#Hi-?=o4Wm5MRqJCQ%)EA;&020q_1gPu_=zz9x)IX#-}=n0a)-v^8tq_tQ=f8LR9fSeIKkjRDyUUDTH8j_ zAq;WaJ+RjatTA}u0+-hn#0vqaX=iJa&F6X0#Dx5zE!Vm|E?4(U0iS7ngUuKCd<0wu z>A4qTP~8Qv8yie|-nwPIk9S~;MaM_?{~|$bBq&`Q=RWgvpV_7Ya-B1?j?nPufE>Ik zkL{C&4PHyx=gs=daRp|_&nS9^Q}jB{8yCC=y+N-_85j?gWcVG0rkpCXDV^-_^G%cL znk+MpCNG3Kgr~F>6+|s*k_5V`mn&15rJpP7`6a4~>bstl7a{@Nw_>+~UGyW}=nLS5hqJ&Wd*T_*(o?9|SY3R=wTmGk?WRXs1p z<6n1Qciu4h3l=|3T{-zE^{C;CE`(N zdqKH~=#pU3X+v9_GgH@HHc;KLp*nEiuBjR4R;cu3X4ZN4S{6p^3+swkhI1u*sUTjF z+dF6QC=^p)Cg^T~tT9O=>IQ z5a&!gkitU!GfMMe8|z3o9Z|o}0~sUP7iE2(M1qDyHz!-c;9KclmPi9BY9Le-Mmw+^ z9DELj3~H-Cq@%-ZH`P_4+R({p2|4g04D^$`$v_8T@YBEGG3A2ZF#6VNu-n=ec4XlNs=S#|#wSiV-RUm24Lvinkm)adr zO}S=h`D;+fh*QHngIhngOF;h3id1I|I5I2EcuRqE^Rxz0z`ID_TtST8tmp;#aU{j~bj?%nC>uk@~wIIaa5v)1Ak zXrYZcx1z5kZn;72%gb6{9wT?c_~@O0ksz{3W=Lx>(QDyZzLOUFBz@6ZywYpo>$e~~ z9paNI(ig48Ezm;v65)l&ioU2(2a3>tLG8<{^IBi({p+aNro7sh^;&P^8eJ;?XN!qm zi{x=FNN=@kywYotKCT7nt=8feXrWO~ogqy&dwn6j)mpuJ{B<-8uJ(4T)>~LNOlaHa z-|2s}nCP|09@m2OR=dV4y%zc7T9DpqEpCAp8f~kj4e2dtyZBNaf0gf-}*lCN!E+M<1IFhh(!!hUc+Vh2Etv)?fm`7f@ zK4*_4lZArUi|qCKl!T_#J(GF~5)E=`Ivuc-YGiwLCd9rGq}6*T*%ZM+I)K>m282@Yf!)3G&Hnv!|?FN52P9p z@NKr>F8-xcsW5da#ICAnvs5y$)@qbj!a2^xJECF60a2|q$5w5$)#D9R%W*4|Y4TQ@ zXXP{vIR)ZJkZD*(!Y~V9eqnkvA4v_7^!H*&LuQJ2b|ib8hP@9}GfYC2u=b{x0wje| z9{l<8NU0cV^80L~iPlD^W&CsP;%58CsZDpEn`rpw9iM=deK_GX3HXJ;P%c@F`JYb} zrt8KJTOVLQ89aB*q3c-g!^ZC4d`*kpo|P6NQ)Qs&(yyUn^*2zlYDQ~eRr}@|-E7Uv zW9+OGcKKU@!4 zs$zbqbsu*Z*GddDf~QNonWFybn*8+Y^y*?`&7IrJ1I7vFPHtm1mkg!Lktm*u#V@{f z!%TXlQOLF_j&%){!2~zQ)mO) zFHMmnWO;ir9Ut(;MR#`G;XE*# zC~tkq<%p)YSzWO_{y=VBd1m)aGn`Ln+g1}}PG`hMY)yK)G_;N_j7(4LTh)o=b4hQS z;}}z>5cd1$>cPqU@Veiz$w0Ic8Z_kmkyZ#aZU&u6C8#q%69`X8EZ|-Q)wzhk`M#F~ zrMQ`ooOaqr$P*vb-pRf(rKL8t5fF9*D#|i8PXy9#C2(dUtjFXlAfHhWnkblv3qIKc zn-3U+f)poE73!^3t|aVABw5WMkrEcDN$;(W?(rNU@KKcm1r+2oR4bAE66{HUzvo>w z7#rF!Igo24!;yr;pcAuGwS`suS6^Ir;eqvco5ew>4?bnz{xR?LNPW5lTbjiKW|7uc z#q#*Bt;&IGuAaSQ%mNGJLowLO80rxK@$3VwD4}ztDX6YxqT=X)wQYmdYGRC4s!%2Z z^?i&uYO3O#K{VQ|k@kR2j-)vmQda?;>~64b*mB28%cdb-t!g3)3@iv?D>aIy*%)p8 z5u5-&q9?;Z0pO52cneVEObTU5fK7b|24i4RvYLB1zG6YMG(b1J*~1A?3T)wMK39zU zjTAO=V$?=;yZsV|d-hziefzHK&fC8xlCe2!gN;&PcC=m|OIvi}gx_tC3@4<{X|4go z&ed}Uw7??sEE27vC`#g)qF#xJd~X#JubAh6Hd`PYYxddfqq7d@M!zp21~%qXQ**Pv z?W0((jB--5B~()KREpm|h2_}*H_OXDo|g<81!;}SM?g|lV)MOKBS1(sq(syqc+FtJ z$YW7elsy~XqjgVV_FW}SUiA=NRzvMT=%cGtdm9qO`->$E4A>VU%xN+oWSU&{ntp|4 zq%ZvS{rCTxJiA+QQtbXJ=oDy-X^SI(xxtyD-;yimios~eW)f`R_w&h#Jw6@djHJDO zf68YL+VF|5-G2KUuYK(H*YGy&O22;K=0My)2?Fe-=?vgl$N`35U?YXFCzmn_oVz^W zh(%(evv`LXGf_6(a56k#!ru`-`otFeV;zuv-mBjgP2VubE$f|k_=m; zn5>*?NtiXzMfK?~|!s0K;h z@PMQm7oix1l%uFKq9UgiX>&+TEyIYF+aZvURj=~0XUGlo;a}f>-){&l#nl)-|cU_0R((~<2C#C zUu{YGUC-}WxM0K9%hpcP^QVIK$l}+jFGDWfi>5FwJvKlM=-Q^Xd4L}fWL_3Fn>L%b z@!N#GroHCV_|t^*P3N00;x7`eHC=1Ifxkic6#f+T6n=_&5x+>iNx!KRhdmg>3V6t~ zdAvfQR20gk@u`ZSbOt%4RN*GWN*)&Rhr$qRF>{X6u%TlpK@vqYgbaegWB|oOC5#S} zIl;jHBM=IYO~D2TxqO0YHpYjBr^Xu6cte=-NeQ3elpL_Tw`QYA7{1TW>ZkrUH9AI= ztM(v@r>}jMBd#PWON0d^;H*Wmt`P_yhrN*1&|nP`O$4OI^{PNXuy94v(*r>#h$517 zzSg?cZnhZ!vsnz~%{hmOwz)p-g~Yuh%bVao3UIsjIUzJf8#(^+cJnI8-(iRS6E-_V znOFJOQ$Ie(KmS7~h0SK{vOPZ+Jjn)8Mw5wxMJadMSQDnqANr6P(@yI>RtGtODd&x~ z#8-)Xh8}~u*88BY)lBMIX-sgdS*vSxs7XPvB48#SOPv>VhD?D|)`^red51~xivCOt z_TWa-R0@BVdQ`>0Ny%-Y6<99>1u$m2x`N1{AXsw>+in9GE)VCe!^fJ$l^b|Y!)8ON zL^zoFMJ5nTr$T`Ywcnpf1;eRK5O%T%z&qF$*t|+t2fj0azx^xe#tN=b6}qCU==Ebz z@{`pYP?MP+ibchEu2fD`Tf;*YWn_$1Mp~>?fn8a0O%8T0OOypAN|tom;Ea>iXR}dd z8u25r8HU}6r}YM#-5b!0(NS-_dcM=jpJ=`kwn6fop<#i78nf5(=WR#N zx~V&B9JA2`ELl9d3=Lo0R?w>1}(ahgHwlw5*{jk@@uC}Yc(?)V( z2icF~zqRcr@CA6r$acTtnz*hWHN!STWZ{xdH!=qcMI9ES5pp(U3l5CCU(j}@7~f-{ zx}P~_w~k+e#;h#;5>}A@1#;d|RDv9DO>cFV0*a7=ZGZ#dC^57Ei%A({-eh+m(Gzo* z@@}z`$Fd1>ydvzRDU!|0K@F-2ULcQZn1F^F1qn@w020->!eIe_1%PCdCYeHY4Fi}M ztv1#})}LN>nq(c77k>NDh5JWsBePq(|N0*6{xz2zfK{k^gZ6>ek@cwK>-lw!kDvVT zD`RWTYwCARb{`Pqr>Vx4Cc9gzLFmJO``g0b{`R*c-?;yUg?HXrcmWE}A)2S| zqXW^re!TBy^wD0VuCN@bBM~Bp8hy~BP&Y#$heRLLr~m{7@zNi_d?i3`#?iylKJX-K zmb#^(V;}`PIyf?tR%#7asa07imlib3RmtgKJNYp!ePti(=VqiaB-CL2xB=Ff`200K z5Ol|Cd_3q*rE7d9`2Y3x<>75r*S_nVqgl2jTec+2vMfuoY?9@u194`Okc4>%Gn9}fooK%{aCtP$v^Duk8EzTc(&0fHC@pQ;+tQF%Xy{ETmqN+~ zYQ5juN3xv|Zu`gkUSR*cwaz|!SbLv6tiATyj)ulqacjomDNgqkW1cE@ z#P2WmhkP(^Tu~e-Gc*i`gNv)1>y9}c4XyKo2Tc!}pE2=aH=MYi@`$r6ub6%k|2LEL zzz^DC7ZDAekLxVBc5ZrqPl-<)R8g$MyVIVs5znnS+cI+;|28I}{&OE;hvqtHGW0=v zE)!8%+_d0_wf_} zocLK|ezO8iT^)c#pCT5bVz4R9@e}#gB-WYJwiGc5!?dfQ&7oSQNEA%$7M7#(b>)rq z?z%cziLB`F3^+UXB^_1KPK*!M+z0X( z(e8rKdATV)3>zGdaOPHj-k)9G;@4GT@A(6Z2c z)Xi(OpII^~XPurWQKu=MOA~udH$bJO8VcpxOcSgJGp$oTAI6rI(=^XJJ?%ozX{E65qx=*hU{M$n`Cs6!8&zsQ zce*bSI@7Re9fGzTOWA)RVo{WJsuU6Hk0^~pCas=C>8JVheEKF$%351p+k&$wn%eei zWp28!e=t3Nz+#*4n;)I8r2Eqr>bLdF)gC^Ql2W=cBA6*{K%<5F2>ES(pFizi;ve^K z_h0MZ@4wqW>3`1uvcK5hor<@)8vEy0*ALE}+tS^ro?vNoSzXfXV=U@A4>ym9ew|^u zR=Gy=hBdf<5ZwjOjZxR4M`7zZs8&`2DHD~L|1jEmG>N=2w%9V8Ss0&ovPEB`uW`aj zzb>~}TO&20gw~phC;A&Y7KY1V1Q-?`{E@h)Y3Gu@i#3{u6;Rsu;t2$KvlxMXE zwGpFn>3y-LsKOXoOXsA!s=dA%*pBd9Y_-1HXswd2@mi?HRwEm!Ml!H{@~lz;!{Wu| z)l^hdt_x5RRD&Su5}IuJF92Lq*KfkCa-LoJp)xPI(&u%;c!QexDhaY$CO#O-rhOr;} zJxTz5q6s(9ETvo0%VOqeQM5Q}X}4<0WOfhO+sxgKrbPqx?2>G%(PXbOo3aC@;%r)H zT4GM?Q+f~v>gwBKQp{l+>d#Upk}ySV8OH$T1~zCO_Cw;tbEtf4S;N!uQ@?z5xG9W0 z!cZV21Y(I~l?z9k+OZZb*kHtQsDeOhMuIYTv-_m z;Bb7zde*$56*wT(=5SP(RjKErRyU9O%Cr7Uc22!q?CWey^*ODTmyTq&OD-Jd=NARyP_>;=oM(<~Z)zXx<%3feOX*ms)?RE1_|$___6mE&wc|BI^BdEh`i0Bez-epl zCAm=sQ!&qK6esx1jvA{bkgbM!j0TOd!C0-Aa4k_qxhDY25gAW;F=b43JZA-i#FPp> zBsixEmee_wD!s&zlF%pe%T1kCo)!wLb1Wg)l-Ay^t*)sFD$@Qv0e?+Ekrs5@=Cmg6 zylBZlvZOPXxO?josrR`A)Zr?7?MuGbs_Mo4e7JZ~U~SK)z2OB#S^wJZ&6u|EElPir z1CUFaArreIJ!EXbD9{=&_O(UZ(rruH#@nuKySweVHba}SB^!pCWJybLx;SoWDef(n zjY=4{NvN$%7jk0?=4&epx!pyXrm|v7QM!%qdr)`Y)mEV*O07;l@RNlgf*<%!W=Za_ zqMNne7R}=mvR??vJ+0zYKUvI9$28A`Jksiap{q-nD$6zZFg?#IXBi?mngDYWOGYL_4TGJ zHI~jhHqBcct*dLR!Mz9GBIyQCFthKjhq~kS$(CTStgK8i4)?D*HsPrpyyc2yq&`~j z^@pu`-S}MZIC$4L^><-^gC%?yX~L4N#_MPZON~vT!GT5d<~OYhON-}bdls3}^XFRH zLRrH6JJe)xxUwc!F)UPA29zE&=m7KzC40=_?NP+-tVoe!@=S8rtyEtD#(%xc5^Fl$`uJM)>U+8`W(G3X)r<~+fy4d zLe7*~Ig;&LY)p?NEv+Hk1YkaOb;%!q?h(uLg_%zV$`Me*cRYwV200H8mW9oo92H7vB30{b-Gq?(hMsi7Anmc zRE7aXA$fJ4Q497=^`@U)ZEtwz`z?0GQe0e7-`(g6`5MBJIkWU+jTOz2a9&ULij}om zhcVr;JX+Igx7oSQN-F~aTe((aP=clP?xW#UD&%o_eHD(1rnW#qfwrP6<*mfA3!y+e zJQS1UIo?E5rK8A+!;@wD>Z;mWJ;p8>bK11@l=Pq&#e9%WYKm~qTEl&tvDlE2*+%Yb ztfT`&2v|Mq>)P4cD@G~jj8`f%|KShjvNoqX_LTX>7tN*3E_dSs)BT?NOf{>bZKF2R zmn*((sv2r(%ko&$ffRpqh7`590LGlL;V93kDPncSLTjg9597M3SNajsi&j>)l$-zX z6cTKzsH(G@U;NYkrkZ7mj-`IneU(UXVQaF#%7hzV6j<*5K)DC^OY*L9fWm24WsS95 zG1i#N&9J}j;=@$XYUMj_#KkihocO;}ZLrtQ8k}bP=O^pGak;xGX< z40s;Ry|4K>=DVe^d3AAmepz{?8|K!_{dMN%rW$=HUDIUA!a8oc9A8;0>_n$zQ%Y(z zMhtdxSB7jgKByvNxsk5+Su(azF$_OtI$)xeuN}y5<#N9zJ|_N@@TK9m+LILq3w3Q* zrcQd%gVnP2p)*O*csapliV__>1C%3PAtSOisiGqJ>RA%B6t=mA1nnPV6}`4^?A*C+ z+oAHFD*4Kh=<2--C$7Jm|E#+13k9(mOxG3+x=uZi7n^DHn>Ky@;dSeD8ZoCksf=Rw z?8Kcaec1VLcf}gL{$woO(b1ia`HQp75QYa}y(p|Rb(@>@VST{tk5MYxk#WKDM+j$; zDaMURT}e$d1g%LO2DoR38S=tVc*+qM-bNEs9_$e2_YiicgEotp3XJDW+ zEqQ``WO?cXJ|i}Q*G-bv~v7^46Pjh*rkS+ zabN3$*plG$ZH=@b-DnR7LfCRM1&VQdhYebfreYgg!>GoQDThxN3Zy*&iwzq%5Oj$# zHa~?He&({6ThCr`ISZN};G}+fH)l3gda|`X*1L6Bj`9^+Ri>KwA5SO98fZ$#7i_}i zk;qYV;FMqZzUB0PT>;4=mHSHEX;;XC zNnHu=hwymB(<;t+z6hYO_6i1hjO{baXXG4)R6a?pjxI6kt8uVe&GIqzXH8uFDLp-a zd3eM>o!_$Q%%fhxdwNyyzBXNMFP_D2BTe&|^=SlGk*hPZ@6}n%GP=!y*lEn-76ks# z@5Sj|Z`x*5bUG_vZ)TOIFEXVU>8l@DjmGG)R8sxzdbzwSK{4HcSWbm{1%V8BkirD7b*EB@4 zohc(u{Nj9BgUMcwt38dZyRC_K8lpP4RIW1{v{92em`rJU$(`}mnY=MKWgJngN$i?@ zhpLJj)VksAhk_9=tSxzcSx`~nB_uWk#i_^X6QHbmpl*82)aXnHTr(|<yHqXWSnNMGmU&k4|HR(AxwPMURHyJA`Vf`DcYn#!i z&*HLzW{E;`ku;I*`+hZmmfB}Mtjd?ixRcvvn@(E z!@BHcS|xv+SS>arX{;w16MB++=xF*9R*{5p)Q&DCyZEZj*DKjuue$28%lGV=ciE~{ zE3+db>6P;=XHuNpT)M$NQOe4b6|T!0n>xKWp`2SZLmxY}LF&1#w zsH$h?l{9flTfna>7-2(OsA`>Z+A^;<_ZwrUv!JgT@tMx7uc@kYHU^6fbEX5wefm!X zuBeA{XQ@l*e&W(dq1EZ|{EJ$hg@7NkE~xrLk^iYKXdxh4P>}vi|0z9;o?g}aQm>{w zP48>^W=d&c;Tj^okMIiIOaBl?Y22}C(JBTWmJK+*k{^!6NQ_Fwn(tGGTDKMVwTkJ& zWwVD?o&(^mfM`+T&!^E-FO4bj3XB)?MO9aD9svUfV3 zwnCWv83{hypvLoA5sx~;breRpS!XE<;rgf3i%iEQEZ&P9+9r{wy;$3*yE4@fK)8gSgsbM}V!l-!)&C1j1>Ge;?<(-a8SZ)@1QdeSo#o3KBt=5>AYM7Vv zXFz1xKOK*n*8S6Y!ptCFHjyJfOOjvpF~8M#`lr*HDVusmgu`^^dGy14JU!ECsbM%j zYM7Ua&ynyxcKc~zX5#6mzvsim)iC%f#Tf;O?;C4PSD$XDLfB)0oeCRHDY2~z-WnA1 zUNLUZ7^^tV(8?@h6?x+-ryP*ZkEuLo8JBg^e)$L3A$O$BRxQM(T8pHRrsa9ofDs1c zSW_V`?Kkh;Yrb$)wiwo}Gdw2VtdtJQ-$iX^(^17%q{lTaCVkdsGTMr8k8`m_leY0K zdwQ6>tA@RXUOJbwd`Ai2@eE-BCXBIY3rxU5;pT^sT-aX2JekEsqT7c3thm@7d)((W zlvooj*7J3#V)i)Bm4_Ww*!ArX|r@WT`ujRJ<`|cYtke12o9zFJ0>AcjjhZUw6)m=Y|CwHZM$qY z+P-KzXfxYa}V z?kp{FWb0rw2*)Dr4hLncu`OGtH)`rQTXlwNShbLFy1dj;tz?P<#TmO|v}YVJktD>w zg}M<#Ems)Nkx^SH2G5TR_e4Q)DWC|Y7D%Ytr!SMrUzQ@>vd?r=;l8LPxI60P)ux@N zUMV$oc3z?DHqX?(`D>$mnyCjoFXgZC%HJ!c8n4IqV}mYHBUa*N(AZd?PL(^V;?VQ( zS9Qd-0WNE{8A@|;ttqZ=HhanGb@|Oz-e|C)HsLpWqyAdTM9ui{p%6FS3Q;Wef3b1H z5^iprg(qrc#-MlbZ=W>(@(2w$ZQvbhSHej1I>U*j!W zzpZFXU?}dlz3;Adt}Hq$*w$Nd_tu?X{qiPnc$8+9eTmmbZxobE!)$*-yT z)SSAe-n&R`IfK(Jhi5GT@YF;J8c)wnWX-s>h1(9_5C?_UY_+b~TwPh+SgmBWCB-%^ zl#c44V4;ngi>NvpssmGNjJlFcv;?fh)j43T&}jh^)%B+k7l5(ce)2aLD9|y*;dRvl zq=osbFU#MeJj;~59QMwcBd#xK?&3=|)-KzbY4FY&>BId7TE5=kwCfLcC%4$8sheTb z>_X`-XaqX^z0T-Jv}swQQj*+FZF%J=?d+T4ptt!h^rP6aX9;BP+tX=ZwbvgpD%sL@ zW479Bs;SME+Kt9qrO{lguf}mSi?t+L;jQsUTIz?!C7aN6fXPC25uVoz z`#y8WBkogy2+5mzlqIvBud_Ebj8YN6UV zWBbO+_B{)RB;|5H+rGK-{0-Y{T8BgBO=lPE-pGGcm44nEbT+GoZ_;Zc-V!!^gWmFX zipbB(=VO%erLA9~J7C?$tk>#gjlpDS>9W1ng~ckiAWL-v;ekz&!d8lM^uKAPYtn+FR`FHYEL}(7o)?k-!D=!Z<*CiMGd#marb>-y^ zb&-0{Kq#B2YYsJ>lZ|&QS`_cdO3i^TZ+%m!+35&1)%&`GnVw**-ci<29}D)*Z(F=5 z(Yjzh_yCEy^f~!K>{%PBJ?+I6K2YY=8YMk$=a!B7E{)ct>4GvfE>^-W%g=D>Vls(6 zC2INM@3Dj6WYHgt2E$;6Gg0TfD{=4riN%|@ZV;tlbR^8Km56KM^Qzpd8% zJWuDB)PHX%KAwr=--oJ``l(5}lqwa8;_{Cuh8R7{FR}9Z8Kx+|q=j%c=_rMixpWn5 zn)sy((&KMX+L${kpQNB{C7b*oaEHk*e-~%;?jeW#Jm@>fEk8u{^7j$8lRDu#5XW_J zH}YS36U|qCOYQQB+>6RZsE+?yeAzUe0r41y@9;6E$(59JV< zo~Fm~ z*M#)1;Bt$ybGf<9g)(veIZtPDoJZtuwiEeVj5Y=&-fLn|6XJPPZ5^kPmQ|=DIZ>~q=n|Y=L!Mg*w zj|A`dH|kCB&PP#Wn0uD%OYrO+YLMPR+c*wwL$t|hzMx*U z2Gs31>H~GfUx4%?_5xlcI~_;gc%1AUm*6kznC{Q5lm-#kAl1oVLH)t5wkRXoDu?Cw z1i!)S!e8dQXkXmM@=lZwY4dO4M}2UcVkda~9(3|lw-$dl!#xhS4`um0{$30>UML&d zbD7+OZv*+`d@2#7w;}f~@_oCqgMJ}BOHOVR_!9B8TfT{dD%*?jJ9B`UdvLfww}!&i*j)0Olr)oxo$5`>`K&3Y>$SmLVo9 za@8!Jx`Fp0J*!BUV^{$HNg75-6OF+C9h_^k=3c-&n{lLg`=ogu?0RH#D4GuXB{|TgGgXN(S z@pOdiz~kTyhfh%n zxHQc5fOJa`dst9LfZsuw66DGa|9#+IiO53MR>*(ss6AC z!L@8FUBKUhpUwE* z9|c~67Sf#i3F@dBE6f(43AN1OzXjZdT4>HahIb<(Eyh&d-y+gtOyQcu9R#A$$2+x=z#t5~Er)`3h?Vu!a=Q=-S?4S-{3-Ed5If4E>ND0(w z7=A74D}gVl9r#}elYrE=TlhPGe}x8o0zBLYd;u*g0S?%J$IvzsxgUcY3DTeqFy&t0 z5#)qpco_HsYL+Q2)S3GOa?&Ys#TbGA2=dUGdlRiL2}%R-E%2QE2Z4u?E~5>21TnL} z5y(9!BTg}36p8>j{$%bs^y?g^33w7^VSGYR4pIvA7T|M;Aq9E^@GZp2^r)Z&fxp08 z{Ip461o%^=o(z6r4kz##Aiq|${{+JRh0UAb=}=Nu+M-MQa@(ha&zU{GL|(sVcag*3j~1PvgD zF5vU%qXrN%3H%QF+W~xooq`euzJQj=VPb+37nD`PKM(i;da8lkhp3~0+_%vOay-qz zBgpvx`tuPQME+L;Pa@8Fz!Bg_h?D&Z;FG8k_Ma{A5Ok}!e&>U-T41NZ5#UD%lL37g z_z}XeKMZ^lrN|(MhXk$?^b~LsF>siezy$C~l#%IUf-(a92<ke|1tWuQPgP$_}|FesED7#91xTd;Cu9l z@UIe22WSQ22?F0jjjTXFb2j`Z;adT&4%15blR$1KE0OvNAh$rKBm^ZauuD+l!f&Hh zBA=`9ZV>(>h=(aFg#QrmBrOGgfIP3ty$y>R?2iEd06we&m+lml5g@mdRk=T+>>PuQ z#zZ`0xtBp71AP$4?~dhOM}GL}2$0hqL%IXNw-FnM83w+I)-wjqEESa1K>lXW##>8) zAA(C~gYt;LjKC3rcLF~||G64*t^%G!%&Wo4Fp%F}ji)1m9s#~in2De+7ND*?$Q6ef z6Hk-C!{FX(j7PPC(hfWh&a6g>T7V~zb511-d;@u7$}sRxC3qN2_qt4Lk@Oi|;*eNJU;3QIEJX=uWK<-sId>Hs7 z_|E=GCOobozK_3Ag2K}Yn zR=o9+c#AP9{8`}tL9SlPt;32{{Wp$3v)$WR%3gt0{8d=7e+)U)c8EsagxNdnr0Xw% z`w{K}cnwQj{5IYO_XOO#5Q&$;eUWey8V>Ijk6}FwzcoT+gXFx#i_IelN9|Z1+b<(3 z)4`?S?tuF#jB6qeC*E-l6IE;_awEL^S45RZi9E-MybhvjjCtUGN7RaZwRXW> zLlnPJl0cO%VSq~H4iQTker)v5P6+9N;Llxq71@kpCej;cwhmB79!sZKO|a&XN&HF18)}{CmI5ehN5sQ z;Slc-@-*~3+?zzhcs7jk3?q$Ulx_G9xF_I#OXzOl{BQ$s;ML;W;l2s?GTggFOUmF< zaI4`^)+KkteIE{KE?olm9inAOYs5k{ItKSeq7{f|<*h_xeMDz3B3gqu)+~oZe$L&6 zrh~Y~@!mMnAOD!>eEhxu^tFh4E#h8_JWm*i)*+wkw-aqZxQ+J_ZJH$7+y;mEw}8IY zO|%W~Y%hhondluR zF1bGM*O5<7=lup+B##TfiSq+{BpluZ-q_?Pgx}2hk$;l+GyRD07m4u4^M0nkC;WUn zfkk0Gd`V;Dc$8A%XPYObkU=xuF6eyzQqu7KXhq6E-p}Ee2!EBJt9h#u;kPi|;1Kky z@_r6~EBlK?{*|xf{Y*b7{34&qxAT6cKlh+7mQ1$#`p?_3Yhs5l< z8+PsV?U>j(vE!17wUNOs6WceOw{_RjiS-w6Id4bdXW{SY#EzXv!553fq6J!WKI|Fu zux!`Ht?QSbcX`c2zR_4D8jV0oxs7(vg>)Wm!8~{iP0)7QfL~i_7i`q+627&-9aw>$ z{`^7mVU?4_f35KKi%`1|!w&ok3CcFaGXdXnx*YMcFAK^Am}U6r)VF6sEJKdA(8Wl5 zBYazFC)Vk+-&-PbJoEJQn1=A)1<1oTyt$3mAs)^fmvXyEcP?Vz0@@CQTaU8tLOA|4 zfxn#hOMq(;^7PU!1${m8wMCTk^rxr)9u;xz%$I@r6oHF=BGzWiuV%;luk(RxXCwHt z9xqD$#yaO*~BN_oZV{9+-_#Se>&g( ze?P;N3cVx#<*vls?o9vQ$P=MXhQ1IsSkPBPIS7(9oct#X=CwA=MoY1KQU*!21JYd= zRbb6jiFI2Qq=nU}l^W+_4SwWk}*MGfiSv zIR_J(Zt8)?S(@hJG*Uli<%5_N&&SFf7cpUWz6f%OVN5TUVD`KeU+Z$LeMT{dUWwW0 z7*-9dX$_qNeYJ74kMr?OtVK&(C))f*+C-aC?-!!|Y{PeP5!(Guw5N;F`Y)x+(3-BG zE1|)8HAbl2bS*6HT#q&2jdT-6saxn)`V8GhdoUL6#YnZEK1W}mFVdGV8h)9+g7NBW zSOeclcR_OXzvv$NI^|_X>92e+hftzoK8$5&8}N zmi_~y;P2@*I!dq8AL$Kxi=Zb=C+IKqHoZen)01>OT^BU?aUF!$;DIi#%izF0Riy@- z!D1+a3V^|&GiVGL0}Zrr^0^CW;rYJFlgk26NT##KCbfY+X|i-7EnMFBBy?b(yJ@fE zQJrpZ(l9;%4Up6SxgO1A1~*Qo_Y6Fm&8i+){Tkdbc@|uH&-kP-Ffgfue`)@=KQMr1 G_J0Aepy_`A literal 0 HcmV?d00001 diff --git a/services/order/Item.php b/services/order/Item.php new file mode 100644 index 000000000..e10194259 --- /dev/null +++ b/services/order/Item.php @@ -0,0 +1,182 @@ + + * @since 1.0 + */ +class Item extends Service +{ + /** + * @property $order_id | Int + * @return Array + * 通过order_id 得到所有的items + */ + protected function actionGetByOrderId($order_id){ + $items = MyOrderItem::find()->asArray()->where([ + 'order_id' => $order_id, + ])->all(); + foreach($items as $k=>$one){ + $product_id = $one['product_id']; + $product_one = Yii::$service->product->getByPrimaryKey($product_id); + + $productSpuOptions = $this->getProductSpuOptions($product_one); + //var_dump($productSpuOptions); + $items[$k]['spu_options'] = $productSpuOptions; + $items[$k]['custom_option'] = $product_one['custom_option']; + $items[$k]['custom_option_info'] = $this->getProductOptions($items[$k]); + $items[$k]['image'] = $this->getProductImage($product_one,$one); + + } + return $items ; + } + /** + * @property $product_one | Object, product model + * @property $item_one | Array , order item + * 得到产品的图片。 + */ + public function getProductImage($product_one,$item_one){ + $custom_option = $product_one['custom_option']; + $custom_option_sku = $item_one['custom_option_sku']; + $image = ''; + # 设置图片 + if(isset($product_one['image']['main']['image'])){ + $image = $product_one['image']['main']['image']; + } + $custom_option_image = isset($custom_option[$custom_option_sku]['image']) ? $custom_option[$custom_option_sku]['image'] : ''; + if($custom_option_image){ + $image = $custom_option_image; + } + if(!$image){ + $image = $item_one['image'] ; + } + return $image; + } + /** + * @property $item_one | Array , order item + * 通过$item_one 的$item_one['custom_option_sku'],$item_one['custom_option'] , $item_one['spu_options'] + * @return Array + * 将spu的选择属性和自定义属性custom_option 组合起来,返回一个统一的数组 + */ + public function getProductOptions($item_one){ + $custom_option_sku = $item_one['custom_option_sku']; + $custom_option_info_arr = []; + $custom_option = isset($item_one['custom_option']) ? $item_one['custom_option'] : ''; + if(isset($custom_option[$custom_option_sku]) && !empty($custom_option[$custom_option_sku])){ + $custom_option_info = $custom_option[$custom_option_sku]; + foreach($custom_option_info as $attr=>$val){ + if(!in_array($attr,['qty','sku','price','image'])){ + $attr = str_replace('_',' ',$attr); + $attr = ucfirst($attr); + $custom_option_info_arr[$attr] = $val; + } + } + } + $spu_options = isset($item_one['spu_options']) ? $item_one['spu_options'] : ''; + if(is_array($spu_options) && !empty($spu_options)){ + foreach($spu_options as $label => $val){ + $custom_option_info_arr[$label] = $val; + } + } + return $custom_option_info_arr; + } + + /** + * @property $productOb | Object,类型:\fecshop\models\mongodb\Product + * 得到产品的spu对应的属性以及值。 + * 概念 - spu options:当多个产品是同一个spu,但是不同的sku的时候,他们的产品表里面的 + * spu attr 的值是不同的,譬如对应鞋子,size 和 color 就是spu attr,对于同一款鞋子,他们 + * 是同一个spu,对于尺码,颜色不同的鞋子,是不同的sku,他们的spu attr 就是 color 和 size。 + */ + protected function getProductSpuOptions($productOb){ + $custom_option_info_arr = []; + if(isset($productOb['attr_group']) && !empty($productOb['attr_group'])){ + $productAttrGroup = $productOb['attr_group']; + $attrInfo = Yii::$service->product->getGroupAttrInfo($productAttrGroup); + if(is_array($attrInfo) && !empty($attrInfo)){ + $attrs = array_keys($attrInfo); + \fecshop\models\mongodb\Product::addCustomProductAttrs($attrs); + } + $productOb = Yii::$service->product->getByPrimaryKey($productOb['_id']->{'$id'}); + $spuArr = Yii::$service->product->getSpuAttr($productAttrGroup); + if(is_array($spuArr) && !empty($spuArr)){ + foreach($spuArr as $spu_attr){ + if(isset($productOb[$spu_attr]) && !empty($productOb[$spu_attr])){ + $custom_option_info_arr[$spu_attr] = $productOb[$spu_attr]; + } + } + } + } + return $custom_option_info_arr ; + } + + /** + * @property $items | Array , example: + * $itmes = [ + * [ + * 'item_id' => $one['item_id'], + * 'product_id' => $product_id , + * 'sku' => $product_one['sku'], + * 'name' => Yii::$service->store->getStoreAttrVal($product_one['name'],'name'), + * 'qty' => $qty , + * 'custom_option_sku' => $custom_option_sku , + * 'product_price' => $product_price , + * 'product_row_price' => $product_row_price , + * + * 'base_product_price' => $base_product_price , + * 'base_product_row_price' => $base_product_row_price , + * + * 'product_name' => $product_one['name'], + * 'product_weight' => $p_wt, + * 'product_row_weight'=> $p_wt * $qty, + * 'product_url' => $product_one['url_key'], + * 'product_image' => $product_one['image'], + * 'custom_option' => $product_one['custom_option'], + * 'spu_options' => $productSpuOptions, + * ] + * ]; + * @property $order_id | Int + * 保存订单的item信息 + */ + protected function actionSaveOrderItems($items,$order_id,$store){ + if(is_array($items) && !empty($items) && $order_id && $store){ + foreach($items as $item){ + $myOrderItem = new MyOrderItem; + $myOrderItem['order_id'] = $order_id; + $myOrderItem['store'] = $store; + $myOrderItem['created_at'] = time(); + $myOrderItem['updated_at'] = time(); + $myOrderItem['product_id'] = $item['product_id']; + $myOrderItem['sku'] = $item['sku']; + $myOrderItem['name'] = $item['name']; + $myOrderItem['custom_option_sku'] = $item['custom_option_sku']; + $myOrderItem['image'] = isset($item['product_image']['main']['image']) ? $item['product_image']['main']['image'] : '' ; + $myOrderItem['weight'] = $item['product_weight']; + $myOrderItem['qty'] = $item['qty']; + $myOrderItem['row_weight'] = $item['product_row_weight']; + $myOrderItem['price'] = $item['product_price']; + $myOrderItem['base_price'] = $item['product_row_price']; + $myOrderItem['row_total'] = $item['product_row_price']; + $myOrderItem['base_row_total'] = $item['base_product_row_price']; + $myOrderItem['redirect_url'] = $item['product_url']; + $myOrderItem->save(); + } + } + } + + +} \ No newline at end of file diff --git a/services/page/Asset.php b/services/page/Asset.php new file mode 100644 index 000000000..c8b13e118 --- /dev/null +++ b/services/page/Asset.php @@ -0,0 +1,127 @@ + + * @since 1.0 + extends AssetBundle + */ +class Asset extends Service +{ + public $cssOptions; + public $jsOptions; + /** + * 在模板路径下的相对文件夹。 + * 譬如模板路径为@fecshop/app/theme/base/front + * 那么js,css路径默认为@fecshop/app/theme/base/front/assets + */ + public $defaultDir = 'assets'; + /** + * 文件路径默认放到模板路径下面的assets里面 + */ + protected function actionRegister($view){ + $assetArr = []; + $themeDir = Yii::$service->page->theme->getThemeDirArr(); + if( is_array($themeDir) && !empty($themeDir)){ + if( is_array($this->jsOptions) && !empty($this->jsOptions)){ + foreach($this->jsOptions as $jsOption){ + if( isset($jsOption['js']) && is_array($jsOption['js']) && !empty($jsOption['js'])){ + + foreach($jsOption['js'] as $jsPath){ + foreach($themeDir as $dir){ + $dir = $dir.'/'.$this->defaultDir.'/'; + $jsAbsoluteDir = $dir.$jsPath; + if(file_exists($jsAbsoluteDir)){ + $assetArr[$dir]['jsOptions'][] = [ + 'js' => $jsPath, + 'options' => $this->initOptions($jsOption['options']), + ]; + break; + } + } + } + } + } + } + + if( is_array($this->cssOptions) && !empty($this->cssOptions)){ + foreach($this->cssOptions as $cssOption){ + if( isset($cssOption['css']) && is_array($cssOption['css']) && !empty($cssOption['css'])){ + foreach($cssOption['css'] as $cssPath){ + foreach($themeDir as $dir){ + $dir = $dir.'/'.$this->defaultDir.'/'; + $cssAbsoluteDir = $dir.$cssPath; + if(file_exists($cssAbsoluteDir)){ + $assetArr[$dir]['cssOptions'][] = [ + 'css' => $cssPath, + 'options' => $this->initOptions($cssOption['options']), + ]; + break; + } + } + } + } + } + } + } + if(!empty($assetArr)){ + foreach($assetArr as $fileDir=>$as){ + $cssConfig = $as['cssOptions']; + $jsConfig = $as['jsOptions']; + $publishDir = $view->assetManager->publish($fileDir); + if(!empty($jsConfig) && is_array($jsConfig)){ + foreach($jsConfig as $c){ + $view->registerJsFile($publishDir[1].'/'.$c['js'],$c['options']); + } + } + if(!empty($cssConfig) && is_array($cssConfig)){ + foreach($cssConfig as $c){ + $view->registerCssFile($publishDir[1].'/'.$c['css'],$c['options']); + } + } + } + } + } + + + protected function initOptions($options){ + if(isset($options['position'])){ + if($options['position'] == 'POS_HEAD'){ + $options['position'] = \yii\web\View::POS_HEAD; + }else if($options['position'] == 'POS_END'){ + $options['position'] = \yii\web\View::POS_END; + } + } + return $options; + } +} + + + + + + + + + + + + diff --git a/services/page/Breadcrumbs.php b/services/page/Breadcrumbs.php new file mode 100644 index 000000000..3c6020ba3 --- /dev/null +++ b/services/page/Breadcrumbs.php @@ -0,0 +1,84 @@ + + * @since 1.0 + */ +class Breadcrumbs extends Service +{ + public $homeName = 'Home'; + public $ifAddHomeUrl = true; + public $active = true; + protected $_items; + + public function init(){ + if($this->homeName){ + $items['name'] = $this->homeName; + if($this->ifAddHomeUrl) + $items['url'] = CUrl::getHomeUrl(); + $this->addItems($items); + } + } + + /** + * property $items|Array. add $items to $this->_items. + * $items format example. + * $items = ['name'=>'fashion handbag','url'=>'http://www.xxx.com']; + */ + protected function actionAddItems($items){ + if($this->active){ + $this->_items[] = $items; + } + } + + + protected function actionGetItems(){ + if($this->active){ + if(is_array($this->_items) && !empty($this->_items)){ + return $this->_items; + }else{ + return []; + } + } + } + + /** + * generate Breadcrumbs html ,before generate , you should use addItems function to add breadcrumbs items. + */ + /* + protected function actionGenerateHtml(){ + $arr = []; + if($this->_items){ + foreach($this->_items as $item){ + $name = isset($item['name']) ? $item['name'] : ''; + $url = isset($item['url']) ? $item['url'] : ''; + if($name){ + if($url){ + $arr[] = ''.$name.''; + }else{ + $arr[] = ''.$name.''; + } + } + } + } + return $arr; + //if(!empty($arr)) + // return implode($this->intervalSymbol,$arr); + } + */ + +} \ No newline at end of file diff --git a/services/page/Currency.php b/services/page/Currency.php new file mode 100644 index 000000000..75a3535f8 --- /dev/null +++ b/services/page/Currency.php @@ -0,0 +1,209 @@ + + * @since 1.0 + */ +class Currency extends Service +{ + + const CURRENCY_CURRENT = 'currency_current'; + + /** + * 该变量用于:在配置文件中,配置所有的货币参数。 + * 格式如下: + * [ + * 'USD' => [ + * 'rate' => 1, + * 'symbol' => '$', + * ], + * 'RMB' => [ + * 'rate' => 6.3, + * 'symbol' => 'гд', + * ], + * ] + * + */ + public $currencys; + /** + * 基础货币,产品的价格,填写的都是基础货币的价格。 + * 该值需要在配置文件中进行配置 + */ + public $baseCurrecy ; + /** + * 网站的默认货币,需要注意的是,默认货币不要和基础货币混淆,举例: + * 后台产品统一使用的美元填写产品价格,但是我的网站前端的默认货币为人民币。 + * 该值需要在配置文件中进行配置 + */ + public $defaultCurrency = 'USD'; + /** + * 当前的货币简码 + */ + protected $_currentCurrencyCode; + /** + * 根据配置,保存所有货币的配置信息。 + */ + protected $_currencys; + + + /** + * @property $currencyCode | string 货币简码,譬如USD,RMB等 + * @return Array + * 如果不传递参数,得到所有的货币 + * 如果传递参数,得到的是当前货币的信息。 + */ + protected function actionGetCurrencys($currencyCode=''){ + if(!$this->_currencys){ + foreach($this->currencys as $code => $info){ + $this->_currencys[$code] = [ + 'code' => $code , + 'rate' => $info['rate'] , + 'symbol' => $info['symbol'] , + ]; + } + } + if($currencyCode) + return $this->_currencys[$currencyCode]; + return $this->_currencys; + } + /** + * 得到当前货币的符号,譬如¥ $ 等。 + * 如果当前的货币在配置中找不到,则会强制改成默认货币 + */ + protected function actionGetCurrentSymbol(){ + if(isset($this->currencys[$this->getCurrentCurrency()]['symbol'])){ + return $this->currencys[$this->getCurrentCurrency()]['symbol']; + } + } + + /** + * @property $currencyCode | 货币简码 + * 得到货币的符号,譬如¥ $ 等。 + */ + protected function actionGetSymbol($currencyCode){ + if(isset($this->currencys[$currencyCode]['symbol'])){ + return $this->currencys[$currencyCode]['symbol']; + } + } + /** + * property $price|Float ,默认货币的价格 + * Get current currency price. price format is two decimal places, + * if current currency is not find in object variable $currencys(maybe change config in online shop,but current user session is effective), + * current currency will set defaultCurrency, origin price will be return. + * 通过传递默认货币的价格,得到当前货币的价格。 + */ + protected function actionGetCurrentCurrencyPrice($price){ + + + if(isset($this->currencys[$this->getCurrentCurrency()]['rate'])){ + $rate = $this->currencys[$this->getCurrentCurrency()]['rate']; + if($rate) + return ceil($price * $rate * 100)/100; + } + /** + * 如果上面出现错误,当前的货币在货币配置中找不到,则会使用默认货币 + * 这种情况可能出现在货币配置调整的过程中,找不到则会被强制改成默认货币。 + */ + $this->setCurrentCurrency($this->baseCurrecy); + return $price; + } + /** + * @property $current_price | Float 当前货币下的价格 + * @return 默认货币下的价格 + * 通过当前的货币价格得到默认货币的价格,这是一个反推的过程, + * 需要特别注意的是:这种反推方法换算得到的默认货币的价格,和原来的默认货币价格, + * 可能有0.01的误差,因为默认货币换算成当前货币的算法为小数点后两位进一法得到的。 + */ + protected function actionGetDefaultCurrencyPrice($current_price){ + if(isset($this->currencys[$this->getCurrentCurrency()]['rate'])){ + $rate = $this->currencys[$this->getCurrentCurrency()]['rate']; + if($rate) + return ceil($current_price / $rate * 100)/100; + } + } + /** + * @property $currencyCode | 货币简码 + * 初始化货币信息,在service Store bootstrap(Yii::$app->store->bootstrap()), 中会被调用 + * 1. 如果 $this->defaultCurrency 和 $this->baseCurrecy 没有设置,将会报错。 + * 2. 如果 传递参数$currencyCode为空,则会使用默认货币 + */ + protected function actionInitCurrency($currencyCode=''){ + if(!$this->defaultCurrency){ + throw new InvalidConfigException('defautlt currency must config'); + } + if(!$this->baseCurrecy){ + throw new InvalidConfigException('base currency must config'); + } + if(!$this->getCurrentCurrency()){ + if(!$currencyCode){ + $currencyCode = $this->defaultCurrency; + } + $this->setCurrentCurrency($currencyCode); + } + + } + /** + * @property $currencyCode | String , 货币简码,如果参数$currencyCode为空,则取当前的货币简码 + * @return Array + * 得到货币的详细信息,数据格式如下: + * [ + * 'code' => $code , + * 'rate' => $rate , + * 'symbol' => $symbol , + * ] + */ + protected function actionGetCurrencyInfo($currencyCode = ''){ + if(!$currencyCode) + $currencyCode = $this->getCurrentCurrency(); + return $this->getCurrencys($currencyCode); + } + /** + * 得到当前的货币。 + */ + protected function actionGetCurrentCurrency(){ + + if(!$this->_currentCurrencyCode) + $this->_currentCurrencyCode = CSession::get(self::CURRENCY_CURRENT); + return $this->_currentCurrencyCode; + } + /** + * @property $currencyCode | String, 当前的货币简码 + * 设置当前的货币。 + */ + protected function actionSetCurrentCurrency($currencyCode){ + if(!$this->isCorrectCurrency($currencyCode)){ + $currencyCode = $this->defaultCurrency; + } + if($currencyCode){ + CSession::set(self::CURRENCY_CURRENT,$currencyCode); + return true; + } + + } + /** + * @property $currency | String 货币简码 + * @return boolean + * 检测当前传递的货币简码,是否在配置中存在,如果存在则返回true + */ + protected function isCorrectCurrency($currencyCode){ + if(isset($this->currencys[$currencyCode])){ + return true; + }else{ + return false; + } + } + +} \ No newline at end of file diff --git a/services/page/Footer.php b/services/page/Footer.php new file mode 100644 index 000000000..e48614106 --- /dev/null +++ b/services/page/Footer.php @@ -0,0 +1,51 @@ + + * @since 1.0 + */ +class Footer extends Service +{ + //public $textTerms; + const TEXT_TERMS = 'footer_text_terms'; + const COPYRIGHT = 'footer_copyright'; + const FOLLOW_USE = 'footer_follow_us'; + const PAYMENT_IMG = 'footer_payment_img'; + + + public function getTextTerms(){ + Yii::$service->page->staticblock->get(self::TEXT_TERMS); + } + + + public function getCopyRight(){ + Yii::$service->page->staticblock->get(self::COPYRIGHT); + } + + + public function followUs(){ + Yii::$service->page->staticblock->get(self::FOLLOW_USE); + } + + + public function getPaymentImg(){ + Yii::$service->page->staticblock->get(self::PAYMENT_IMG); + } + + + +} diff --git a/services/page/Menu.php b/services/page/Menu.php new file mode 100644 index 000000000..80ac9d438 --- /dev/null +++ b/services/page/Menu.php @@ -0,0 +1,128 @@ + + * @since 1.0 + */ +class Menu extends Service +{ + /** + * whether display HOME in the menu. + */ + public $displayHome; + /** + * custom menu that display in the front of product category. + */ + public $frontCustomMenu; + /** + * custom menu that display in the behind of product category. + */ + public $behindCustomMenu; + + protected $_homeUrl; + + + /** + * return menu array data, contains: + * home,frontCustomMenu,productCategory,behindCustomMenu. + * example: + [ + [ + 'name' => 'my custom menu', + 'urlPath' => '/my-custom-menu.html', + 'childMenu' => [ + 'name' => 'my custom menu', + 'urlPath' => '/my-custom-menu.html', + ], + ], + [ + ..., + ] + ] + */ + protected function actionGetMenuData(){ + + $this->_homeUrl = CUrl::getHomeUrl(); + $arr = []; + if($displayHome = $this->displayHome){ + $enable = isset($displayHome['enable']) ?$displayHome['enable'] : ''; + $display = isset($displayHome['display']) ?$displayHome['display'] : ''; + if($enable && $display){ + $arr[] = [ + 'name' => $display, + 'url' => Yii::$service->url->homeUrl(), + ]; + } + } + $first_custom_menu = $this->customMenuInit($this->frontCustomMenu); + if(is_array($first_custom_menu) && !empty($first_custom_menu)){ + foreach($first_custom_menu as $m){ + $arr[] = $m; + } + } + $categoryMenuArr = $this->getProductCategoryMenu(); + //var_dump($categoryMenuArr); + if(is_array($categoryMenuArr) && !empty($categoryMenuArr)){ + foreach($categoryMenuArr as $a){ + $arr[] = $a; + } + } + + $behind_custom_menu = $this->customMenuInit($this->behindCustomMenu); + if(is_array($behind_custom_menu) && !empty($behind_custom_menu)){ + foreach($behind_custom_menu as $m){ + $arr[] = $m; + } + } + + //var_dump($arr); + return $arr; + } + + protected function customMenuInit($customMenu){ + $cMenu = []; + if(is_array($customMenu) && !empty($customMenu)){ + foreach($customMenu as $k=>$menu){ + $name = $menu['name']; + if(is_array($name)){ + $name = Yii::$service->store->getStoreAttrVal($name,'name'); + $menu['name'] = $name; + } + $urlPath = $menu['urlPath']; + $menu['url'] = Yii::$service->url->getUrl($urlPath); + $cMenu[$k] = $menu; + if(isset($menu['childMenu'])){ + $cMenu[$k]['childMenu'] = $this->customMenuInit($menu['childMenu']); + } + } + } + return $cMenu; + } + + /** + * get product category array as menu. + */ + protected function getProductCategoryMenu(){ + return Yii::$service->category->menu->getCategoryMenuArr(); + } + +} + + + + diff --git a/services/page/Message.php b/services/page/Message.php new file mode 100644 index 000000000..04e9b7083 --- /dev/null +++ b/services/page/Message.php @@ -0,0 +1,98 @@ + + * @since 1.0 + */ +class Message extends Service +{ + protected $_correctName = 'correct_message'; + protected $_errorName = 'error_message'; + /** + * 增加 correct message + * @property $message | String + */ + protected function actionAddCorrect($message){ + + if(empty($message)){ + return; + } + if(is_string($message)){ + $message = [$message]; + } + $correct = $this->getCorrects(); + if(is_array($correct) && is_array($message)){ + $message = array_merge($correct,$message); + } + return Yii::$app->session->setFlash($this->_correctName,$message); + } + /** + * 增加 error message + * @property $message | String + */ + protected function actionAddError($message){ + + if(empty($message)){ + return; + } + if(is_string($message)){ + $message = [$message]; + } + $error = $this->getErrors(); + if(is_array($error) && is_array($message)){ + $message = array_merge($error,$message); + } + return Yii::$app->session->setFlash($this->_errorName,$message); + } + /** + * 从service->helper_errors中取出来错误信息,加入到 + * service->page->message 中。 + */ + protected function actionAddByHelperErrors(){ + $errors = Yii::$service->helper->errors->get(true); + if($errors){ + if(is_array($errors) && !empty($errors)){ + foreach($errors as $error){ + if(is_array($error) && !empty($error)){ + foreach($error as $er){ + Yii::$service->page->message->addError($er); + } + } + } + } + } + return true; + } + /** + * 获取 correct message + * @return Array + */ + protected function actionGetCorrects(){ + return Yii::$app->session->getFlash($this->_correctName); + } + /** + * 获取 error message + * @return Array + */ + protected function actionGetErrors(){ + return Yii::$app->session->getFlash($this->_errorName); + } + + +} + +?> \ No newline at end of file diff --git a/services/page/Newsletter.php b/services/page/Newsletter.php new file mode 100644 index 000000000..cb6bc2b47 --- /dev/null +++ b/services/page/Newsletter.php @@ -0,0 +1,67 @@ + + * @since 1.0 + */ +class Newsletter extends Service +{ + + /** + * newsletter subscription + */ + protected function actionSubscription($email){ + $mongoNewsletter = new MongoNewsletter(); + $mongoNewsletter->attributes = [ + 'email' => $email, + ]; + if($mongoNewsletter->validate()){ + $one = MongoNewsletter::find()->where('email' => $email) + ->one(); + if($one['id']){ + return [ + 'code' => 300, + 'description' => 'subscription email is exist', + ]; + }else{ + $mongoNewsletter->save(); + return [ + 'code' => 200, + 'description' => 'subscription email success' + ]; + } + + }else{ + return [ + 'code' => 300, + 'description' => 'subscription email format is not correct' + ]; + } + } + + /** + * @property $filter|array + * get subscription email collection + */ + protected function actionGetSubscriptionList($filter){ + + + } + + +} \ No newline at end of file diff --git a/services/page/StaticBlock.php b/services/page/StaticBlock.php new file mode 100644 index 000000000..597bb63d9 --- /dev/null +++ b/services/page/StaticBlock.php @@ -0,0 +1,53 @@ + + * @since 1.0 + */ +class StaticBlock extends Service +{ + /** + * @property $key|Array + * + */ + public function getByKey($key,$lang=''){ + if(!$lang) + $lang = Yii::$service->store->currentLanguage; + if(!$lang) + throw new InvalidValueException('language is empty'); + + } + + /** + * @property $_id | Int + * get StaticBlock one data by $_id. + */ + public function getById($_id){ + + + } + + /** + * @property $filter | Array + * get StaticBlock collections by $filter . + */ + public function getStaticBlockList($filter){ + + + } + +} \ No newline at end of file diff --git a/services/page/Theme.php b/services/page/Theme.php new file mode 100644 index 000000000..20d309b15 --- /dev/null +++ b/services/page/Theme.php @@ -0,0 +1,117 @@ + + * @since 1.0 + */ +class Theme extends Service +{ + /** + * user current theme dir. Highest priority + * + */ + public $localThemeDir; + /** + * $thirdThemeDir | Array + * user current theme dir.Second priority. + * array[0] priority is higher than array[1], + */ + public $thirdThemeDir; + /** + * fecshop theme dir. lower priority + */ + public $fecshopThemeDir ; + /** + * current layout file path. + */ + public $layoutFile; + /** + * array that contains mutil theme dir. + */ + protected $_themeDirArr; + + + protected function actionGetThemeDirArr(){ + if(!$this->_themeDirArr){ + $arr = []; + if($localThemeDir = Yii::getAlias($this->localThemeDir)){ + $arr[] = $localThemeDir; + } + + $thirdThemeDirArr = $this->thirdThemeDir; + if(!empty($thirdThemeDirArr) && is_array($thirdThemeDirArr)){ + foreach($thirdThemeDirArr as $theme){ + $arr[] = Yii::getAlias($theme); + } + } + $arr[] = Yii::getAlias($this->fecshopThemeDir); + $this->_themeDirArr = $arr; + } + return $this->_themeDirArr; + } + + /** + * find theme file by mutil theme ,if not find view file and $throwError=true, it will throw InvalidValueException. + */ + protected function actionGetViewFile($view,$throwError=true){ + $view = trim($view); + if(substr($view,0,1) == '@'){ + return Yii::getAlias($view); + } + $relativeFile = ''; + $module = Yii::$app->controller->module; + if($module && $module->id){ + $relativeFile = $module->id.'/'; + } + $relativeFile .= Yii::$app->controller->id.'/'.$view.'.php'; + $absoluteDir = Yii::$service->page->theme->getThemeDirArr(); + foreach($absoluteDir as $dir){ + if($dir){ + $file = $dir.'/'.$relativeFile; + if(file_exists($file)){ + return $file; + } + } + } + /* not find view file */ + if($throwError){ + $notExistFile = []; + foreach($absoluteDir as $dir){ + if($dir){ + $file = $dir.'/'.$relativeFile; + $notExistFile[] = $file; + } + } + throw new InvalidValueException('view file is not exist in'.implode(',',$notExistFile)); + }else{ + return false; + } + } + + protected function actionSetLocalThemeDir($dir){ + $this->localThemeDir = $dir; + } + + protected function actionSetThirdThemeDir($dir){ + $this->thirdThemeDir = $dir; + } + + + + + +} \ No newline at end of file diff --git a/services/page/Translate.php b/services/page/Translate.php new file mode 100644 index 000000000..5830507c7 --- /dev/null +++ b/services/page/Translate.php @@ -0,0 +1,47 @@ + + * @since 1.0 + */ +class Translate extends Service +{ + /** + * current i18n category. it will set in controller init . + * example: fecshop\app\appfront\modules\AppfrontController + * code: Yii::$service->page->translate->category = 'appfront'; + */ + public $category; + + /** + * Yii::$service->page->translate->__('Hello, {username}!', ['username' => $username]); + */ + public function __($text,$arr=[]){ + if(!$this->category){ + return $text; + }else{ + return Yii::t($this->category, $text ,$arr); + } + } + + protected function actionSetLanguage($language){ + Yii::$app->language = $language; + } + + + +} \ No newline at end of file diff --git a/services/page/Widget.php b/services/page/Widget.php new file mode 100644 index 000000000..1cc581315 --- /dev/null +++ b/services/page/Widget.php @@ -0,0 +1,157 @@ + + * @since 1.0 + */ +class Widget extends Service +{ + public $defaultObMethod = 'getLastData'; + public $widgetConfig; + /* + @property configKey String or Array + + Array example: + [ + # class 选填 + 'class' => 'fec\block\TestMenu', + # view 为 必填 , view可以用两种方式 + # view 1 使用绝对地址的方式 + 'view' => '@fec/views/testmenu/index.php', + OR + # view 2 使用相对地址,通过当前模板进行查找 + 'view' => 'cms/home/index.php', + + # 下面为选填 + 'method'=> 'getLastData', + 'terry1'=> 'My1', + 'terry2'=> 'My2', + ] + */ + protected function actionRender($configKey,$parentThis=''){ + $config = ''; + if(is_array($configKey)){ + $config = $configKey; + $configKey = ''; + }else{ + if(isset($this->widgetConfig[$configKey])){ + $config = $this->widgetConfig[$configKey]; + }else{ + throw new InvalidValueException(" config key: '$configKey', can not find in ".'Yii::$service->page->widget->widgetConfig'.", you must config it before use it."); + } + } + + return $this->renderContent($configKey,$config,$parentThis); + } + + protected function actionRenderContentHtml($configKey,$config,$parentThis=''){ + if( !isset($config['view']) || empty($config['view']) + ){ + throw new InvalidConfigException('view and class must exist in array config!'); + } + $params = []; + $view = $config['view']; + unset($config['view']); + $viewFile = $this->getViewFile($view); + if( !isset($config['class']) || empty($config['class'])){ + if($parentThis){ + $params['parentThis'] = $parentThis; + } + return Yii::$app->view->renderFile($viewFile, $params); + } + if(isset($config['method']) && !empty($config['method'])){ + $method = $config['method']; + unset($config['method']); + }else{ + $method = $this->defaultObMethod; + } + $ob = Yii::createObject($config); + $params = $ob->$method(); + if($parentThis){ + $params['parentThis'] = $parentThis; + } + return Yii::$app->view->renderFile($viewFile, $params); + } + + protected function actionRenderContent($configKey,$config,$parentThis=''){ + if(isset($config['cache']['enable']) && $config['cache']['enable']){ + if(!isset($config['class']) || !$config['class']){ + throw new InvalidConfigException('in widget ['.$configKey.'],you enable cache ,you must config widget class .'); + }else if($ob = new $config['class']){ + if($ob instanceof BlockCache){ + $cacheKey = $ob->getCacheKey(); + if(!($content = CCache::get($cacheKey))){ + $cache = $config['cache']; + $timeout = isset($cache['timeout']) ? $cache['timeout'] : 0; + unset($config['cache']); + $content = $this->renderContentHtml($configKey,$config,$parentThis); + CCache::set($cacheKey,$content,$timeout); + } + return $content; + }else{ + throw new InvalidConfigException($config['class'].' must implete fecshop\interfaces\block\BlockCache when you use block cache .'); + } + } + } + $content = $this->renderContentHtml($configKey,$config,$parentThis); + return $content; + + } + + + + + /** + * find theme file by mutil theme ,if not find view file and $throwError=true, it will throw InvalidValueException. + */ + protected function getViewFile($view,$throwError=true){ + $view = trim($view); + if(substr($view,0,1) == '@'){ + return Yii::getAlias($view); + } + $absoluteDir = Yii::$service->page->theme->getThemeDirArr(); + + foreach($absoluteDir as $dir){ + if($dir){ + $file = $dir.'/'.$view; + //echo $file."
    "; + if(file_exists($file)){ + + return $file; + } + } + } + + /* not find view file */ + if($throwError){ + $notExistFile = []; + foreach($absoluteDir as $dir){ + if($dir){ + $file = $dir.'/'.$view; + $notExistFile[] = $file; + } + } + throw new InvalidValueException('view file is not exist in'.implode(',',$notExistFile)); + }else{ + return false; + } + } + + +} \ No newline at end of file diff --git a/services/product/BestSell.php b/services/product/BestSell.php new file mode 100644 index 000000000..86c51eca0 --- /dev/null +++ b/services/product/BestSell.php @@ -0,0 +1,40 @@ + + * @since 1.0 + */ +class BestSell extends Service +{ + + /** + * õȫƷIJƷ + */ + + protected function actionGetCategoryProduct() + { + return 'category best sell product'; + } + + /** + * õȫƷIJƷ + */ + protected function actionGetProduct(){ + + + } + + + + +} \ No newline at end of file diff --git a/services/product/BuyAlsoBuy.php b/services/product/BuyAlsoBuy.php new file mode 100644 index 000000000..e69de29bb diff --git a/services/product/Image.php b/services/product/Image.php new file mode 100644 index 000000000..3af1a816d --- /dev/null +++ b/services/product/Image.php @@ -0,0 +1,162 @@ + + * @since 1.0 + */ +class Image extends Service +{ + /** + * absolute image save floder + */ + public $imageFloder = 'media/catalog/product'; + /** + * upload image max size + */ + public $maxUploadMSize; + /** + * allow image type + */ + public $allowImgType = [ + 'image/jpeg', + 'image/gif', + 'image/png', + 'image/jpg', + 'image/pjpeg', + ]; + + public $defaultImg = '/default.jpg'; + public $waterImg = 'product_water.jpg'; + protected $_defaultImg; + protected $_md5WaterImgPath; + + /** + * 得到保存产品图片所在相对根目录的url路径 + */ + protected function actionGetBaseUrl(){ + return Yii::$service->image->GetImgUrl($this->imageFloder,'common'); + } + /** + * 得到保存产品图片所在相对根目录的文件夹路径 + */ + protected function actionGetBaseDir(){ + return Yii::$service->image->GetImgDir($this->imageFloder,'common'); + } + /** + * 通过产品图片的相对路径得到产品图片的url + */ + protected function actionGetUrl($str){ + return Yii::$service->image->GetImgUrl($this->imageFloder.$str,'common'); + } + /** + * 通过产品图片的相对路径得到产品图片的绝对路径 + */ + protected function actionGetDir($str){ + return Yii::$service->image->GetImgDir($this->imageFloder.$str,'common'); + } + + + /** + * @property $param_img_file | Array . + * upload image from web page , you can get image from $_FILE['XXX'] , + * $param_img_file is get from $_FILE['XXX']. + * return , if success ,return image saved relative file path , like '/b/i/big.jpg' + * if fail, reutrn false; + */ + protected function actionSaveProductUploadImg($FILE){ + Yii::$service->image->imageFloder = $this->imageFloder; + Yii::$service->image->allowImgType = $this->allowImgType; + if($this->maxUploadMSize){ + Yii::$service->image->setMaxUploadSize($this->maxUploadMSize); + } + return Yii::$service->image->saveUploadImg($FILE); + } + + + protected function actionDefautImg(){ + if(!$this->_defaultImg){ + $this->_defaultImg = $this->getUrl($this->defaultImg); + } + return $this->_defaultImg; + } + + /** + * $imgResize 可以为数组 [230,230] 代表生成的图片为230*230,如果宽度或者高度不够,则会用白色填充 + * 如果 $imgResize设置为 230, 则宽度不变,高度按照原始图的比例计算出来。 + */ + protected function actionGetResize($imageVal,$imgResize,$isWatered=false){ + /* + list($width,$height) = $imgResize; + if(!$width && !$height){ + throw new InvalidValueException('resize img width and height can not empty'); + } + if($width && !$height){ + $height = $width; + } + if(!$width && $height){ + $width = $height; + } + $imgResize = [$width , $height]; + */ + $originImgPath = $this->getDir($imageVal); + $waterImgPath = ''; + if($isWatered){ + $waterImgPath = $this->getDir('/'.$this->waterImg); + } + list($newPath,$newUrl) = $this->getProductNewPath($imageVal,$imgResize,$waterImgPath); + if(!file_exists($newPath)){ + \fec\helpers\CImage::saveResizeMiddleWaterImg($originImgPath,$newPath,$imgResize,$waterImgPath); + } + return $newUrl; + } + + protected function getProductNewPath($imageVal,$imgResize,$waterImgPath){ + if(!$this->_md5WaterImgPath){ + if(!$waterImgPath){ + $waterImgPath = 'defaultWaterPath'; + } + //echo $waterImgPath;exit; + $this->_md5WaterImgPath = md5($waterImgPath); + } + + $baseDir = '/cache/'.$this->_md5WaterImgPath; + if(is_array($imgResize)){ + list($width,$height) = $imgResize; + }else{ + $width = $imgResize; + $height = '0'; + } + + + $imageArr = explode('/',$imageVal); + $dirArr = ['cache',$this->_md5WaterImgPath,$width,$height]; + foreach($imageArr as $igf){ + if($igf && !strstr($igf,'.')){ + $dirArr[] = $igf; + } + } + \fec\helpers\CDir::createFloder($this->getBaseDir(),$dirArr); + $newPath = $this->getBaseDir().$baseDir .'/'.$width.'/'.$height.$imageVal; + $newUrl = $this->getBaseUrl().$baseDir .'/'.$width.'/'.$height.$imageVal; + return [$newPath,$newUrl]; + } + + + + + + + + +} \ No newline at end of file diff --git a/services/product/Info.php b/services/product/Info.php new file mode 100644 index 000000000..90063a417 --- /dev/null +++ b/services/product/Info.php @@ -0,0 +1,121 @@ + + * @since 1.0 + */ +class Info extends Service +{ + /** + * @property $custome_option | Array + * $custome_option = [ + "my_color" => "red", + "my_size" => "M", + "my_size2" => "M2", + "my_size3" => "L3" + ] + * 通过custom的各个值,生成custom option sku + */ + public function getCustomOptionSkuByValue($custome_option){ + $str = ''; + $arr = []; + if(is_array($custome_option) && !empty($custome_option)){ + foreach($custome_option as $k=>$v){ + $arr[] = str_replace(' ','*',$v); + } + } + return implode('-',$arr); + } + /** + * @property $custom_option | Array 前台传递的custom option 一维数组。 + * @property $product_custom_option | Array 数据库中存储的产品custom_option的值 + * 验证前台传递的custom option 是否正确。 + */ + public function validateProductCustomOption($custom_option,$product_custom_option){ + if(empty($product_custom_option) && empty($custom_option)){ + return true; # 都为空,说明不需要验证。 + } + if($custom_option){ + $co_sku = $this->getCustomOptionSkuByValue($custom_option); + //$product_custom_option = $product['custom_option']; + if(!is_array($product_custom_option)){ + Yii::$service->helper->errors->add('this product custom option is error'); + return; + } + foreach($product_custom_option as $p_sku => $option){ + if($p_sku == $co_sku){ + return true; + } + } + } + Yii::$service->helper->errors->add('this product custom option can not find in this product'); + return false; + } + /** + * 通过返回的值,得到product custom option 的sku key + */ + /** + * 通过前台传递的custom option 得到customOptionSku + */ + public function getProductCOSku($custom_option_sku,$product_custom_option){ + if(is_array($product_custom_option) && !empty($product_custom_option)){ + foreach($product_custom_option as $co_sku => $info){ + $bool = true; + if(is_array($info) && !empty($info)){ + foreach($info as $k=>$v){ + if(isset($custom_option_sku[$k]) && ($custom_option_sku[$k] != $v)){ + $bool = false; + break; + } + } + if($bool){ + return $co_sku; + } + } + + } + } + } + + /** + * @property $product | Object 产品对象 + * @property $sale_qty| 想要购买的个数 + * 验证当前产品,是否是可以出售的。 + */ + public static function productIsCanSale($product,$sale_qty){ + $is_in_stock = $product['is_in_stock']; + $qty = $product['qty']; + if($is_in_stock == 1){ + if($qty >= $sale_qty){ + return true; + } + } + + } + + + + + + + + + + + + +} \ No newline at end of file diff --git a/services/product/Price.php b/services/product/Price.php new file mode 100644 index 000000000..72e8fa75b --- /dev/null +++ b/services/product/Price.php @@ -0,0 +1,278 @@ + + * @since 1.0 + */ +class Price extends Service +{ + /** + * 当产品的special_price 大于 price 的时候,是否以 price 为准。 + */ + public $ifSpecialPriceGtPriceFinalPriceEqPrice; + + protected $_currencyInfo; + + /** + * @property $price | Float 产品的价格 + * 得到当前货币状态下的产品的价格信息。 + */ + protected function actionFormatPrice($price){ + $currencyInfo = $this->getCurrentInfo(); + $price = $price * $currencyInfo['rate']; + $price = ceil($price*100)/100; + return [ + 'code' => $currencyInfo['code'], + 'symbol' => $currencyInfo['symbol'], + 'value' => $price, + ]; + } + + protected function actionFormatSamplePrice($price){ + $currencyInfo = $this->getCurrentInfo(); + $price = $price * $currencyInfo['rate']; + $price = ceil($price*100)/100; + return $currencyInfo['symbol'].$price; + } + /** + * 得到单个产品的最终价格。支持tier price 如果是tier price 需要把qty 以及tier Price传递过来 + * @property $price | Float 产品的价格 + * @property $special_price | Float 产品的特价 + * @property $special_from | Int 产品的特检开始时间 + * @property $special_to | Int 产品的特检结束时间 + * @property $qty | Int 产品的个数,这个用于一次性购买多个产品的优惠,这些是用于批发客户 + * @property $tier_price | Array ,Example: + * $tier_price = [ + * ['qty'=>2,'price'=>33], + * ['qty'=>4,'price'=>30], + * ]; + * + * @return Float + */ + protected function actionGetFinalPrice( + $price , $special_price, + $special_from , $special_to, + $qty='' , $tier_price=[] + ){ + if($this->specialPriceisActive($price,$special_price,$special_from,$special_to)){ + $return_price = $special_price; + }else{ + $return_price = $price; + } + if($qty > 1){ + $return_price = $this->getTierPrice($qty,$tier_price,$return_price); + } + return $return_price; + } + + /** + * @property $productId | String + * @property $qty | Int + * @property $custom_option_sku | String + @property $format | Int , 返回的价格的格式,0代表为美元格式,1代表为当前货币格式,2代表美元和当前货币格式都有 + * 通过产品以及个数,custonOptionSku 得到产品的最终价格 + */ + protected function actionGetCartPriceByProductId($productId,$qty,$custom_option_sku,$format = 1){ + $product = Yii::$service->product->getByPrimaryKey($productId); + $custom_option_price = 0; + $status = isset($product['status']) ? $product['status'] : 0; + + if($product['price'] && Yii::$service->product->isActive($status)){ + $price = $product['price']; + $special_price = isset($product['special_price']) ? $product['special_price'] : 0; + $special_from = isset($product['special_from']) ? $product['special_from'] : ''; + $special_to = isset($product['special_to']) ? $product['special_to'] : ''; + $tier_price = isset($product['tier_price']) ? $product['tier_price'] : []; + $custom_option = isset($product['custom_option']) ? $product['custom_option'] : ''; + + if(!empty($custom_option) && $custom_option_sku && isset($custom_option[$custom_option_sku])){ + if($co = $custom_option[$custom_option_sku]){ + $custom_option_price = isset($co['price']) ? $co['price'] : 0; + } + } + return $this->getCartPrice( + $price , $special_price, + $special_from , $special_to, + $qty , $custom_option_price, + $tier_price , $format + ); + } + + } + + # 产品加入购物车,得到相应个数的最终价格。 + /** + * + * @property $format | Int , 返回的价格的格式,0代表为美元格式,1代表为当前货币格式,2代表美元和当前货币格式都有 + */ + protected function actionGetCartPrice( + $price , $special_price, + $special_from , $special_to, + $qty='' , $custom_option_price, + $tier_price=[] , $format = 1 + ){ + if($this->specialPriceisActive($price,$special_price,$special_from,$special_to)){ + $return_price = $special_price; + }else{ + $return_price = $price; + } + + if($qty > 1){ + $return_price = $this->getTierPrice($qty,$tier_price,$return_price); + + } + $return_price = $return_price + $custom_option_price; + if($format == 1){ + $format_price = $this->formatPrice($return_price); + return $format_price ; + }else if($format == 2){ + $format_price = $this->formatPrice($return_price); + return [ + 'base_price' => $return_price, + 'curr_price' => $format_price, + ]; + }else{ + return $return_price; + } + + + } + + + /** + * @property $qty | Int + * @property $price | Float 一个产品的单价(如果有特价,那么这个值是一个产品的特价) + * @property $tier_price | Array , example: + * $tier_price = [ + * ['qty'=>2,'price'=>33], + * ['qty'=>4,'price'=>30], + * ]; + * 传递过来的tier_price 数组,必须是按照qty进行排序好了的数组 + */ + protected function actionGetTierPrice($qty,$tier_price_arr,$price){ + if($qty <= 1){ + return $price; + } + $t_price = $price; + if(is_array($tier_price_arr) && !empty($tier_price_arr)){ + + foreach($tier_price_arr as $one){ + + $t_qty = $one['qty']; + $t_price = $one['price']; + + if($t_qty < $qty){ + $parent_price = $t_price; + continue; + }else{ + if($parent_price){ + return $parent_price; + }else{ + return $price; + } + } + + } + } + return $t_price; + } + /** + * 判断产品的special_price是否有效,下面几种情况会无效 + * 1. $special_price为空 + * 2. 产品的$special_price 大于 $price,并且,ifSpecialPriceGtPriceFinalPriceEqPrice设置为true + * 3. 当前的时间不在 特价时间范围内 + * @property $price | Float 产品的价格 + * @property $special_price | Float 产品的特价 + * @property $special_from | Int 产品的特检开始时间 + * @property $special_to | Int 产品的特检结束时间 + * @return boolean + + */ + protected function actionSpecialPriceisActive($price,$special_price,$special_from,$special_to){ + if(!$special_price){ + return false; + } + if($this->ifSpecialPriceGtPriceFinalPriceEqPrice){ + if($special_price > $price){ + return false; + } + } + $nowTimeStamp = time(); + if($special_from){ + if($special_from > $nowTimeStamp){ + return false; + } + } + if($special_to){ + if($special_to < $nowTimeStamp){ + return false; + } + } + return true; + } + /** + * 得到当前的货币信息,并保存到对象属性中,方便多次调用 + */ + protected function getCurrentInfo(){ + if(!$this->_currencyInfo){ + $this->_currencyInfo = Yii::$service->page->currency->getCurrencyInfo(); + } + return $this->_currencyInfo; + } + + /** + * 通过该函数,得到产品的价格信息,如果特价是active的,则会有特价信息。 + * @property $price | Float 产品的价格 + * @property $special_price | Float 产品的特价 + * @property $special_from | Int 产品的特检开始时间 + * @property $special_to | Int 产品的特检结束时间 + * @return $return | Array 产品的价格信息 + */ + protected function actionGetCurrentCurrencyProductPriceInfo($price,$special_price,$special_from,$special_to){ + $price_info = $this->formatPrice($price); + $return['price'] = [ + 'symbol' => $price_info['symbol'], + 'value' => $price_info['value'], + 'code' => $price_info['code'], + ]; + $specialIsActive = $this->specialPriceisActive($price,$special_price,$special_from,$special_to); + if($specialIsActive){ + $special_price_info = Yii::$service->product->price->formatPrice($special_price); + $return['special_price'] = [ + 'symbol' => $special_price_info['symbol'], + 'value' => $special_price_info['value'], + 'code' => $special_price_info['code'], + ]; + } + return $return; + } + + + + + + + + + + + + + + +} + + diff --git a/services/product/ProductInterface.php b/services/product/ProductInterface.php new file mode 100644 index 000000000..bf931ce25 --- /dev/null +++ b/services/product/ProductInterface.php @@ -0,0 +1,20 @@ + + * @since 1.0 + */ +interface ProductInterface{ + + public function getByPrimaryKey($primaryKey); + public function coll($filter); + public function save($one,$originUrlKey); + public function remove($ids); +} \ No newline at end of file diff --git a/services/product/ProductMongodb.php b/services/product/ProductMongodb.php new file mode 100644 index 000000000..839a3ebb0 --- /dev/null +++ b/services/product/ProductMongodb.php @@ -0,0 +1,514 @@ + + * @since 1.0 + */ +class ProductMongodb implements ProductInterface +{ + public $numPerPage = 20; + + public function getPrimaryKey(){ + return '_id'; + } + + public function getByPrimaryKey($primaryKey){ + if($primaryKey){ + return Product::findOne($primaryKey); + }else{ + return new Product; + } + } + /** + * @property $sku|Array + * @property $returnArr|boolean 返回的数据是否是数组格式,如果设置为 + * false,则返回的是对象数据 + * @return Array or Object + * 通过sku 获取产品,一个产品 + */ + public function getBySku($sku,$returnArr=true){ + if($sku){ + if($returnArr){ + return Product::find()->asArray() + ->where(['sku' => $sku]) + ->one() + ; + }else{ + return Product::findOne(['sku' => $sku]); + } + } + } + + /** + * @property $spu|Array + * @property $returnArr|boolean 返回的数据是否是数组格式,如果设置为 + * false,则返回的是对象数据 + * @return Array or Object + * 通过spu 获取产品数组 + */ + public function getBySpu($spu,$returnArr=true){ + if($spu){ + if($returnArr){ + return Product::find()->asArray() + ->where(['spu' => $spu]) + ->all(); + }else{ + return Product::find() + ->where(['spu' => $spu]) + ->all(); + } + } + } + + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + * 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function coll($filter=''){ + $query = Product::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + } + /** + * 得到总数 + */ + public function collCount($filter=''){ + $query = Product::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return $query->count(); + } + + /** + * @property $product_id_arr | Array + * @property $category_id | String + * 在给予的产品id数组$product_id_arr中,找出来那些产品属于分类 $category_id + * 该功能是后台分类编辑中,对应的分类产品列表功能 + * 也就是在当前的分类下,查看所有的产品,属于当前分类的产品,默认被勾选。 + */ + public function getCategoryProductIds($product_id_arr,$category_id){ + $id_arr = []; + if(is_array($product_id_arr) && !empty($product_id_arr)){ + $query = Product::find()->asArray(); + $mongoIds = []; + foreach($product_id_arr as $id){ + $mongoIds[] = new \MongoId($id); + } + //var_dump($mongoIds); + $query->where(['in',$this->getPrimaryKey(),$mongoIds]); + $query->andWhere(['category'=>$category_id]); + $data = $query->all(); + if(is_array($data) && !empty($data) ){ + foreach($data as $one){ + $id_arr[] = $one[$this->getPrimaryKey()]; + } + } + } + //echo '####'; + //var_dump($id_arr); + //echo '####'; + return $id_arr; + } + + /** + * @property $one|Array , 产品数据数组 + * @property $originUrlKey|String , 产品的原来的url key ,也就是在前端,分类的自定义url。 + * 保存产品(插入和更新),以及保存产品的自定义url + * 如果提交的数据中定义了自定义url,则按照自定义url保存到urlkey中,如果没有自定义urlkey,则会使用name进行生成。 + */ + public function save($one,$originUrlKey='catalog/product/index'){ + + if(!$this->initSave($one)){ + return; + } + $currentDateTime = \fec\helpers\CDate::getCurrentDateTime(); + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + if($primaryVal){ + $model = Product::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('Product '.$this->getPrimaryKey().' is not exist'); + return; + } + #验证sku 是否重复 + $product_one = Product::find()->asArray()->where([ + '<>',$this->getPrimaryKey(),(new \MongoId($primaryVal)) + ])->andWhere([ + 'sku' => $one['sku'], + ])->one(); + if($product_one['sku']){ + Yii::$service->helper->errors->add('Product Sku 已经存在,请使用其他的sku'); + return; + } + }else{ + $model = new Product; + $model->created_at = time(); + $model->created_user_id = \fec\helpers\CUser::getCurrentUserId(); + $primaryVal = new \MongoId; + $model->{$this->getPrimaryKey()} = $primaryVal; + #验证sku 是否重复 + $product_one = Product::find()->asArray()->where([ + 'sku' => $one['sku'], + ])->one(); + if($product_one['sku']){ + Yii::$service->helper->errors->add('Product Sku 已经存在,请使用其他的sku'); + return; + } + } + $model->updated_at = time(); + /** + * 计算出来产品的最终价格。 + */ + $one['final_price']= Yii::$service->product->price->getFinalPrice($one['price'],$one['special_price'],$one['special_from'],$one['special_to']); + $one['score'] = (int)$one['score']; + unset($one['_id']); + /** + * 保存产品 + */ + $saveStatus = Yii::$service->helper->ar->save($model,$one); + /** + * 自定义url部分 + */ + if($originUrlKey){ + $originUrl = $originUrlKey.'?'.$this->getPrimaryKey() .'='. $primaryVal; + $originUrlKey = isset($one['url_key']) ? $one['url_key'] : ''; + $defaultLangTitle = Yii::$service->fecshoplang->getDefaultLangAttrVal($one['name'],'name'); + $urlKey = Yii::$service->url->saveRewriteUrlKeyByStr($defaultLangTitle,$originUrl,$originUrlKey); + $model->url_key = $urlKey; + $model->save(); + } + /** + * 更新产品信息到搜索表。 + */ + $product_ids = [$model->{$this->getPrimaryKey()} ]; + Yii::$service->search->syncProductInfo($product_ids); + return true; + } + /** + * @property $one|Array + * 对保存的数据进行数据验证 + * sku spu 默认语言name , 默认语言description不能为空。 + */ + protected function initSave($one){ + if(!isset($one['sku']) || empty($one['sku'])){ + Yii::$service->helper->errors->add(' sku 必须存在 '); + return false; + } + if(!isset($one['spu']) || empty($one['spu'])){ + Yii::$service->helper->errors->add(' spu 必须存在 '); + return false; + } + $defaultLangName = \Yii::$service->fecshoplang->getDefaultLangAttrName('name'); + if(!isset($one['name'][$defaultLangName]) || empty($one['name'][$defaultLangName])){ + Yii::$service->helper->errors->add(' name '.$defaultLangName.' 不能为空 '); + return false; + } + $defaultLangDes = \Yii::$service->fecshoplang->getDefaultLangAttrName('description'); + if(!isset($one['description'][$defaultLangDes]) || empty($one['description'][$defaultLangDes])){ + Yii::$service->helper->errors->add(' description '.$defaultLangDes.'不能为空 '); + return false; + } + return true; + } + + /** + * @property $ids | Array or String + * 删除产品,如果ids是数组,则删除多个产品,如果是字符串,则删除一个产品 + * 在产品产品的同时,会在url rewrite表中删除对应的自定义url数据。 + */ + public function remove($ids){ + if(empty($ids)){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids)){ + foreach($ids as $id){ + $model = Product::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + # 删除在重写url里面的数据。 + Yii::$service->url->removeRewriteUrlKey($url_key); + # 删除在搜索表(各个语言)里面的数据 + Yii::$service->search->removeByProductId($model[$this->getPrimaryKey()]); + $model->delete(); + //$this->removeChildCate($id); + }else{ + Yii::$service->helper->errors->add("Product Remove Errors:ID:$id is not exist."); + return false; + } + } + }else{ + $id = $ids; + $model = Product::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + # 删除在重写url里面的数据。 + Yii::$service->url->removeRewriteUrlKey($url_key); + # 删除在搜索里面的数据 + Yii::$service->search->removeByProductId($model[$this->getPrimaryKey()]); + $model->delete(); + //$this->removeChildCate($id); + }else{ + Yii::$service->helper->errors->add("Product Remove Errors:ID:$id is not exist."); + return false; + } + } + return true; + } + + /** + * @property $category_id | String 分类的id的值 + * @property $addCateProductIdArr | Array 分类中需要添加的产品id数组,也就是给这个分类增加这几个产品。 + * @property $deleteCateProductIdArr | Array 分类中需要删除的产品id数组,也就是在这个分类下面去除这几个产品的对应关系。 + * 这个函数是后台分类编辑功能中使用到的函数,在分类中可以一次性添加多个产品,也可以删除多个产品,产品和分类是多对多的关系。 + */ + public function addAndDeleteProductCategory($category_id,$addCateProductIdArr,$deleteCateProductIdArr){ + # 在 addCategoryIdArr 查看哪些产品,分类id在product中已经存在, + $idKey = $this->getPrimaryKey(); + //var_dump($addCateProductIdArr); + if(is_array($addCateProductIdArr) && !empty($addCateProductIdArr) && $category_id){ + $addCateProductIdArr = array_unique($addCateProductIdArr); + foreach($addCateProductIdArr as $product_id){ + if(!$product_id){ + continue; + } + $product = Product::findOne($product_id); + if(!$product[$idKey]){ + continue; + } + $category = $product->category; + $category = ($category && is_array($category)) ? $category : []; + //echo $category_id; + if(!in_array($category_id,$category)){ + //echo $category_id; + $category[] = $category_id; + $product->category = $category; + $product->save(); + } + } + } + + if(is_array($deleteCateProductIdArr) && !empty($deleteCateProductIdArr) && $category_id){ + $deleteCateProductIdArr = array_unique($deleteCateProductIdArr); + foreach($deleteCateProductIdArr as $product_id){ + if(!$product_id){ + continue; + } + $product = Product::findOne($product_id); + if(!$product[$idKey]){ + continue; + } + $category = $product->category; + if(in_array($category_id,$category)){ + $arr = []; + foreach($category as $c){ + if($category_id != $c){ + $arr[] = $c; + } + } + $product->category = $arr; + $product->save(); + } + } + } + } + + /** + * 通过where条件 和 查找的select 字段信息,得到产品的列表信息, + * 这里一般是用于前台的区块性的不分页的产品查找。 + * 结果数据没有进行进一步处理,需要前端获取数据后在处理。 + */ + public function getProducts($filter){ + $where = $filter['where']; + if(empty($where)) + return []; + $select = $filter['select']; + $query = Product::find()->asArray(); + $query->where($where); + if(is_array($select) && !empty($select)){ + $query->select($select); + } + return $query->all(); + } + /** + *[ + * 'category_id' => 1, + * 'pageNum' => 2, + * 'numPerPage' => 50, + * 'orderBy' => 'name', + * 'where' => [ + * ['>','price',11], + * ['<','price',22], + * ], + * 'select' => ['xx','yy'], + * 'group' => '$spu', + * ] + * 得到分类下的产品,在这里需要注意的是: + * 1.同一个spu的产品,有很多sku,但是只显示score最高的产品,这个score可以通过脚本取订单的销量(最近一个月,或者 + * 最近三个月等等),或者自定义都可以。 + * 2.结果按照filter里面的orderBy排序 + * 3.由于使用的是mongodb的aggregate(管道)函数,因此,此函数有一定的限制,就是该函数 + * 处理后的结果不能大约32MB,因此,如果一个分类下面的产品几十万的时候可能就会出现问题, + * 这种情况可以用专业的搜索引擎做聚合工具。 + * 不过,对于一般的用户来说,这个不会成为瓶颈问题,一般一个分类下的产品不会出现几十万的情况。 + * 4.最后就得到spu唯一的产品列表(多个spu相同,sku不同的产品,只要score最高的那个) + */ + + public function getFrontCategoryProducts($filter){ + $where = $filter['where']; + if(empty($where)) + return []; + $orderBy = $filter['orderBy']; + $pageNum = $filter['pageNum']; + $numPerPage = $filter['numPerPage']; + $select = $filter['select']; + $group['_id'] = $filter['group']; + $project = []; + foreach($select as $column){ + $project[$column] = 1; + $group[$column] = ['$first' => '$'.$column]; + } + $pipelines = [ + [ + '$match' => $where, + ], + [ + '$sort' => [ + 'score' => -1 + ] + ], + [ + '$project' => $project + ], + [ + '$group' => $group, + ], + [ + '$sort' => $orderBy, + ], + ]; + //var_dump($orderBy); + $product_data = Product::getCollection()->aggregate($pipelines); + $product_total_count = count($product_data); + $pageOffset = ($pageNum - 1) * $numPerPage; + $products = array_slice($product_data, $pageOffset, $numPerPage); + return [ + 'coll' => $products, + 'count' => $product_total_count, + ]; + } + /** + * @property $filter_attr | String 需要进行统计的字段名称 + * @propertuy $where | Array 搜索条件。这个需要些mongodb的搜索条件。 + * 得到的是个属性,以及对应的个数。 + * 这个功能是用于前端分类侧栏进行属性过滤。 + */ + public function getFrontCategoryFilter($filter_attr,$where){ + if(empty($where)) + return []; + $group['_id'] = '$'.$filter_attr; + $group['count'] = ['$sum'=> 1]; + $project = [$filter_attr => 1]; + $pipelines = [ + [ + '$match' => $where, + ], + [ + '$project' => $project + ], + [ + '$group' => $group, + ], + ]; + $filter_data = Product::getCollection()->aggregate($pipelines); + return $filter_data; + } + + + /* + $project = []; + $group['_id'] = $filter['group']; + foreach($select as $column){ + $project[$column] = 1; + if($column != '_id'){ + $group[$column] = ['$first' => '$'.$column]; + }else{ + $group['mongo_id'] = ['$first' => '$'.$column]; + + } + + } + + $pipelines = [ + [ + '$match' => $where, + ], + + [ + '$project' => $project + ], + [ + '$group' => $group, + ], + + ]; + //var_dump($pipelines); + //exit; + $data = Product::getCollection()->aggregate($pipelines); + + */ + //var_dump($where);exit; + //var_dump($productIds ); + //$data = Product::getCollection() + // ->fullTextSearch($searchText,[],['_id'],['limit'=>$product_search_max_count]) + // ; + //exit; + + public function updateProductReviewInfo($spu,$avag_rate,$count,$lang_code,$avag_lang_rate,$lang_count){ + $data = Product::find()->where([ + 'spu' => $spu + ])->all(); + if(!empty($data) && is_array($data)){ + $attrName = 'reviw_rate_star_average_lang'; + $review_star_lang = Yii::$service->fecshoplang->getLangAttrName($attrName,$lang_code); + $attrName = 'review_count_lang'; + $review_count_lang = Yii::$service->fecshoplang->getLangAttrName($attrName,$lang_code); + + foreach($data as $one){ + $one['reviw_rate_star_average'] = $avag_rate; + $one['review_count'] = $count; + $a = $one['reviw_rate_star_average_lang']; + $a[$review_star_lang] = $avag_lang_rate ; + $b = $one['review_count_lang']; + $b[$review_count_lang] = $lang_count ; + $one['reviw_rate_star_average_lang'] = $a; + $one['review_count_lang'] = $b; + $one->save() ; + } + } + } +} + + diff --git a/services/product/ProductMysqldb.php b/services/product/ProductMysqldb.php new file mode 100644 index 000000000..e69de29bb diff --git a/services/product/Review.php b/services/product/Review.php new file mode 100644 index 000000000..186bd6985 --- /dev/null +++ b/services/product/Review.php @@ -0,0 +1,404 @@ + + * @since 1.0 + */ +class Review extends Service +{ + + public $filterByLang; + + /** + * 得到review noactive status,默认状态 + */ + protected function actionNoActiveStatus(){ + return ReviewModel::NOACTIVE_STATUS; + } + + /** + * 得到review active status 审核通过的状态 + */ + protected function actionActiveStatus(){ + return ReviewModel::ACTIVE_STATUS; + } + /** + * 得到review refuse status 审核拒绝的状态 + */ + protected function actionRefuseStatus(){ + return ReviewModel::REFUSE_STATUS; + } + + + /** + * @property $arr | Array + * 初始化review model的属性,因为每一个产品的可能添加的评论字段不同。 + */ + protected function actionInitReviewAttr($arr){ + if(!empty($arr) && is_array($arr)){ + $ReviewModel = new ReviewModel; + $attr_arr = $ReviewModel->attributes(true); + $arr_keys = array_keys($arr); + $attrs = array_diff($arr_keys,$attr_arr); + ReviewModel::addCustomAttrs($attrs); + } + } + + public function getPrimaryKey(){ + return '_id'; + } + + /** + * @property $spu | String. + * 通过spu找到评论总数。 + */ + protected function actionGetCountBySpu($spu){ + $where = [ + 'product_spu' => $spu + ]; + + if($this->filterByLang && ($currentLangCode = Yii::$service->store->currentLangCode)){ + $where['lang_code'] = $currentLangCode; + } + $count = ReviewModel::find()->asArray()->where($where)->count(); + return $count ? $count : 0; + } + /** + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['review_date' => SORT_DESC], + * where' => [ + * ['spu' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + * 通过spu找到评论listing + */ + protected function actionGetListBySpu($filter){ + + + if($this->filterByLang && ($currentLangCode = Yii::$service->store->currentLangCode)){ + $filter['where'][] = ['lang_code' => $currentLangCode ]; + } + $query = ReviewModel::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + + } + + + + /** + * @property $review_data | Array + * + * 增加评论 前台增加评论调用的函数。 + */ + protected function actionAddReview($review_data){ + //$this->initReviewAttr($review_data); + $model = new ReviewModel; + if(isset($review_data[$this->getPrimaryKey()])){ + unset($review_data[$this->getPrimaryKey()]); + } + + $review_data['status'] = ReviewModel::NOACTIVE_STATUS; + + $review_data['store'] = Yii::$service->store->currentStore; + $review_data['lang_code'] = Yii::$service->store->currentLangCode; + $review_data['review_date'] = time(); + if(!Yii::$app->user->isGuest){ + $identity = Yii::$app->user->identity; + $user_id = $identity['id']; + $review_data['user_id'] = $user_id ; + } + + $review_data['ip'] = \fec\helpers\CFunc::get_real_ip(); + $saveStatus = Yii::$service->helper->ar->save($model,$review_data); + + return true; + } + + /** + * @property $review_data | Array + * 保存评论 + */ + protected function actionUpdateReview($review_data){ + //$this->initReviewAttr($review_data); + $model = ReviewModel::findOne([$this->getPrimaryKey()=> $review_data[$this->getPrimaryKey()]]); + unset($review_data[$this->getPrimaryKey()]); + $saveStatus = Yii::$service->helper->ar->save($model,$review_data); + return true; + } + + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => [$this->getPrimaryKey() => SORT_DESC, 'sku' => SORT_ASC ], + * 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + * 查看review 的列表 + */ + protected function actionList($filter){ + $query = ReviewModel::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + } + /** + * @property $_id | String + * 后台编辑 通过评论id找到评论 + * 注意:因为每个产品的评论可能加入了新的字段,因此不能使用ActiveRecord的方式取出来, + * 使用下面的方式可以把字段都取出来。 + */ + protected function actionGetByReviewId($_id){ + + return ReviewModel::getCollection()->findOne([$this->getPrimaryKey() => $_id]); + + } + + + + /** + * get artile model by primary key. + */ + protected function actionGetByPrimaryKey($primaryKey){ + if($primaryKey){ + return ReviewModel::findOne($primaryKey); + }else{ + return new ReviewModel; + } + } + + + + /** + * @property $filter|Array + * get artile collection by $filter + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => [$this->getPrimaryKey() => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionColl($filter=''){ + return $this->list($filter); + } + + /** + * @property $one|Array , save one data . + * @property $originUrlKey|String , article origin url key. + * 评论,后台审核评论的保存方法。 + * 保存后,把评论信息更新到产品表中。 + */ + protected function actionSave($one){ + $currentDateTime = \fec\helpers\CDate::getCurrentDateTime(); + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + $one['status'] = (int)$one['status']; + $one['rate_star'] = (int)$one['rate_star']; + + if($primaryVal){ + $model = ReviewModel::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('ReviewModel '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + $model = new ReviewModel; + $model->created_admin_user_id = \fec\helpers\CUser::getCurrentUserId(); + $primaryVal = new \MongoId; + $model->{$this->getPrimaryKey()} = $primaryVal; + } + //$review_data['status'] = ReviewModel::ACTIVE_STATUS; + $model->review_date = time(); + unset($one[$this->getPrimaryKey()]); + $saveStatus = Yii::$service->helper->ar->save($model,$one); + $model->save(); + # 更新评论信息到产品表中。 + $this->updateProductSpuReview($model['product_spu'],$model['lang_code']); + return true; + } + + protected function actionRemove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + foreach($ids as $id){ + $model = ReviewModel::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $product_spu = $model['product_spu']; + $model->delete(); + # 更新评论信息到产品表中。 + $this->updateProductSpuReview($product_spu,$model['lang_code']); + }else{ + //throw new InvalidValueException("ID:$id is not exist."); + Yii::$service->helper->errors->add("Review Remove Errors:ID $id is not exist."); + return false; + } + } + }else{ + $id = $ids; + $model = ReviewModel::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $model->delete(); + }else{ + Yii::$service->helper->errors->add("Review Remove Errors:ID:$id is not exist."); + return false; + } + } + return true; + } + /** + * @property $ids | Array + * 通过 $ids 数组,批量审核通过评论 + */ + protected function actionAuditReviewByIds($ids){ + if(is_array($ids) && !empty($ids)){ + $identity = Yii::$app->user->identity; + $user_id = $identity['id']; + foreach($ids as $id){ + $model = ReviewModel::findOne($id); + if($model[$this->getPrimaryKey()]){ + $model->audit_user = $user_id; + $model->audit_date = time(); + $model->status = ReviewModel::ACTIVE_STATUS; + $model->save(); + # 更新评论信息到产品表中。 + $this->updateProductSpuReview($model['product_spu'],$model['lang_code']); + } + } + } + + } + + /** + * @property $ids | Array + * 通过 $ids 数组,批量审核评论拒绝 + */ + protected function actionAuditRejectedReviewByIds($ids){ + if(is_array($ids) && !empty($ids)){ + $identity = Yii::$app->user->identity; + $user_id = $identity['id']; + foreach($ids as $id){ + $model = ReviewModel::findOne($id); + if($model[$this->getPrimaryKey()]){ + $model->audit_user = $user_id; + $model->audit_date = time(); + $model->status = ReviewModel::REFUSE_STATUS; + $model->save(); + # 更新评论的信息到产品表 + $this->updateProductSpuReview($model['product_spu'],$model['lang_code']); + } + } + } + + } + /** + * @property $spu | String + * 当评论保存,更新评论的总数,平均评分信息到产品表的所有spu + */ + protected function actionUpdateProductSpuReview($spu,$lang_code){ + $filter = [ + 'where' => [ + ['product_spu' => $spu], + ['status' => ReviewModel::ACTIVE_STATUS], + ], + ]; + $coll = $this->coll($filter); + + $count = $coll['count']; + $data = $coll['coll']; + $rate_total = 0; + $rate_lang_total = 0; + $lang_count = 0; + if(!empty($data) && is_array($data)){ + foreach($data as $one){ + $rate_total += $one['rate_star']; + if($lang_code == $one['lang_code']){ + $rate_lang_total += $one['rate_star']; + $lang_count++; + } + } + } + if($count == 0){ + $avag_rate = 0; + }else{ + $avag_rate = ceil($rate_total/$count); + } + if($lang_count == 0){ + $avag_lang_rate = 0; + }else{ + $avag_lang_rate = ceil($rate_lang_total/$lang_count); + } + + Yii::$service->product->updateProductReviewInfo($spu,$avag_rate,$count,$lang_code,$avag_lang_rate,$lang_count); + return true; + } + + /** + * @property $filter|Array + * get artile collection by $filter + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => [$this->getPrimaryKey() => SORT_DESC, 'sku' => SORT_ASC ], + 'where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionGetReviewsByUserId($filter){ + $query = ReviewModel::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + + } + + + + + + +} \ No newline at end of file diff --git a/services/product/ViewLog.php b/services/product/ViewLog.php new file mode 100644 index 000000000..1becc43d0 --- /dev/null +++ b/services/product/ViewLog.php @@ -0,0 +1,51 @@ + + * @since 1.0 + */ +class ViewLog extends Service +{ + /** + * data: + $product = [ + 'id' => 44, + 'sku' => 'ttt', + 'image' => '/xx/tt/dfas/dsd.jpg', + 'name' => 'xxxxx', + 'user_id' => 22, # 如果选填,则通过user组件,选择当前的用户id + ]; + + #use mongodb save product view log history + Yii::$service->product->viewLog->mongodb->setHistory($product); + + #use mongodb get product view log history + $d = Yii::$service->product->viewLog->mongodb->getHistory(); + + #use mysql save product view log history + Yii::$service->product->viewLog->db->setHistory($product); + + #use mysql get product view log history + $d = Yii::$service->product->viewLog->db->getHistory(); + + + #use session save product view log history + Yii::$service->product->viewLog->session->setHistory($product); + + #use session get product view log history + $history = Yii::$service->product->viewLog->session->getHistory(); + + */ +} \ No newline at end of file diff --git a/services/product/viewLog/Db.php b/services/product/viewLog/Db.php new file mode 100644 index 000000000..25d2c192b --- /dev/null +++ b/services/product/viewLog/Db.php @@ -0,0 +1,82 @@ + + * @since 1.0 + */ +class Db extends Service +{ + + public $table; + public $_defaultTable = 'log_product_view'; + public $_maxProductCount = 10; + + # init function + public function init(){ + if(!$this->table) + $this->table = $this->_defaultTable; + DbViewLog::setCurrentTableName($this->table); + } + + + /** + * get product history log + */ + public function getHistory($user_id='',$count = '') + { + if(!$count) + $count = $this->_maxProductCount; + if(!$user_id) + $user_id = CUser::getCurrentUserId(); + if(!$user_id) + return ; + $coll = DbViewLog::find()->where([ + 'user_id' => $user_id, + ]) + ->asArray() + ->orderBy(['date_time' => SORT_DESC]) + ->limit($count) + ->all(); + return $coll; + + } + + /** + * save product visit log + */ + public function setHistory($productOb){ + $DbViewLog = new DbViewLog; + if(isset($productOb['user_id']) && $productOb['user_id']){ + $DbViewLog->user_id = $productOb['user_id']; + }else if($currentUser = CUser::getCurrentUserId()){ + $DbViewLog->user_id = $currentUser; + }else{ + // if not give user_id, can not save history + return; + } + $DbViewLog->date_time = CDate::getCurrentDateTime(); + $DbViewLog->product_id = $productOb['id']; + $DbViewLog->sku = $productOb['sku']; + $DbViewLog->image = $productOb['image']; + $DbViewLog->name = $productOb['name']; + $DbViewLog->save(); + } + + + + +} \ No newline at end of file diff --git a/services/product/viewLog/Mongodb.php b/services/product/viewLog/Mongodb.php new file mode 100644 index 000000000..895e28578 --- /dev/null +++ b/services/product/viewLog/Mongodb.php @@ -0,0 +1,87 @@ + + * @since 1.0 + */ +class Mongodb extends Service +{ + + public $collection; + public $_defaultCollection = 'log_product_view'; + public $_maxProductCount = 10; + + # init + public function init(){ + if(!$this->collection) + $this->collection = $this->_defaultCollection; + MongodbViewLog::setCurrentCollectionName($this->collection); + } + + + /** + * get product history log + */ + public function getHistory($user_id='',$count = '') + { + if(!$count) + $count = $this->_maxProductCount; + if(!$user_id) + $user_id = CUser::getCurrentUserId(); + if(!$user_id) + return ; + $coll = MongodbViewLog::find()->where([ + 'user_id' => $user_id, + ]) + ->asArray() + ->orderBy(['date_time' => SORT_DESC]) + ->limit($count) + ->all(); + return $coll; + + } + + /** + * save product history log + */ + public function setHistory($productOb){ + + $arr = [ + 'date_time' => CDate::getCurrentDateTime(), + 'product_id' => $productOb['id'], + 'sku' => $productOb['sku'], + 'image' => $productOb['image'], + 'name' => $productOb['name'], + ]; + + if(isset($productOb['user_id']) && $productOb['user_id']){ + $arr['user_id'] = $productOb['user_id']; + }else if($currentUser = CUser::getCurrentUserId()){ + $arr['user_id'] = $currentUser; + }else{ + // if not give user_id, can not save history + return; + } + + $MongodbViewLog = MongodbViewLog::getCollection(); + $MongodbViewLog->save($arr); + + } + + + + +} \ No newline at end of file diff --git a/services/product/viewLog/Session.php b/services/product/viewLog/Session.php new file mode 100644 index 000000000..ca14092a2 --- /dev/null +++ b/services/product/viewLog/Session.php @@ -0,0 +1,72 @@ + + * @since 1.0 + */ +class Session extends Service +{ + public $type; + public $_defaultType = 'session'; + public $_sessionKey = 'services_product_viewlog_history'; + public $_maxProductCount = 10; + + + /** + * get product history log. + */ + public function getHistory() + { + $history = CSession::get($this->_sessionKey); + return $history ? $history : ''; + } + + /** + * save product history log. + */ + public function setHistory($productOb){ + + $logArr = [ + 'date_time' => CDate::getCurrentDateTime(), + 'product_id'=> $productOb['id'], + 'sku' => $productOb['sku'], + 'image' => $productOb['image'], + 'name' => is_array($productOb['name']) ? serialize($productOb['name']) : $productOb['name'], + ]; + if(isset($productOb['user_id']) && $productOb['user_id']){ + $logArr['user_id'] = $productOb['user_id']; + }else{ + $logArr['user_id'] = CUser::getCurrentUserId(); + } + + + if(!($session_history = CSession::get($this->_sessionKey))){ + $session_history = []; + }else if(($count = count($session_history)) >= $this->_maxProductCount){ + $unsetMaxKey = $count - $this->_maxProductCount ; + for($i=0;$i<=$unsetMaxKey;$i++){ + array_shift($session_history); + } + } + $session_history[] = $logArr; + CSession::set($this->_sessionKey,$session_history); + + } + + + + +} \ No newline at end of file diff --git a/services/search/MongoSearch.php b/services/search/MongoSearch.php new file mode 100644 index 000000000..9ac578c57 --- /dev/null +++ b/services/search/MongoSearch.php @@ -0,0 +1,315 @@ + + * @since 1.0 + */ +class MongoSearch extends Service implements SearchInterface +{ + public $searchIndexConfig; + public $searchLang; + public function init(){ + /** + * 初始化search model 的属性,将需要过滤的属性添加到search model的类属性中。 + * $searchModel = new Search; + * $searchModel->attributes(); + * 上面的获取的属性,就会有下面添加的属性了。 + * 将产品同步到搜索表的时候,就会把这些字段也添加进去 + */ + $filterAttr = Yii::$service->search->filterAttr; + if(is_array($filterAttr) && !empty($filterAttr)){ + Search::$_filterColumns = $filterAttr; + } + } + + /** + * 创建索引 + */ + protected function actionInitFullSearchIndex(){ + + $config1 = []; + $config2 = []; + //var_dump($this->searchIndexConfig);exit; + if(is_array($this->searchIndexConfig) && (!empty($this->searchIndexConfig))){ + foreach($this->searchIndexConfig as $column => $weight){ + $config1[$column] = 'text'; + $config2['weights'][$column] = (int)$weight; + } + } + + //$langCodes = Yii::$service->fecshoplang->allLangCode; + if(!empty($this->searchLang) && is_array($this->searchLang)){ + foreach($this->searchLang as $langCode => $mongoSearchLangName){ + /** + * 如果语言不存在,譬如中文,mongodb的fullSearch是不支持中文的, + * 这种情况是不能搜索的。 + * 能够进行搜索的语言列表:https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages + */ + if($mongoSearchLangName){ + Search::$_lang = $langCode; + $searchModel = new Search; + $colltionM = $searchModel::getCollection(); + $config2['default_language'] = $mongoSearchLangName; + $colltionM->mongoCollection->ensureIndex($config1,$config2); + } + } + } + /* + $searchModel::getCollection()->ensureIndex( + [ + 'name' => 'text', + 'description' => 'text', + ], + [ + + 'weights' => [ + 'name' => 10, + 'description' => 5, + ], + 'default_language'=>$store, + ] + ); + */ + } + /** + * @property $product_ids | Array ,里面的子项是MongoId类型。 + * 将产品表的数据同步到各个语言对应的搜索表中。 + */ + protected function actionSyncProductInfo($product_ids,$numPerPage){ + if(is_array($product_ids) && !empty($product_ids)){ + $productPrimaryKey = Yii::$service->product->getPrimaryKey(); + $searchModel = new Search; + $filter['select'] = $searchModel->attributes(); + $filter['asArray'] = true; + $filter['where'][] = ['in',$productPrimaryKey,$product_ids]; + $filter['numPerPage']= $numPerPage; + $filter['pageNum'] = 1; + $coll = Yii::$service->product->coll($filter); + if(is_array($coll['coll']) && !empty($coll['coll'])){ + foreach($coll['coll'] as $one){ + //$langCodes = Yii::$service->fecshoplang->allLangCode; + //if(!empty($langCodes) && is_array($langCodes)){ + // foreach($langCodes as $langCodeInfo){ + $one_name = $one['name']; + $one_description = $one['description']; + $one_short_description = $one['short_description']; + if(!empty($this->searchLang) && is_array($this->searchLang)){ + foreach($this->searchLang as $langCode => $mongoSearchLangName){ + Search::$_lang = $langCode; + $searchModel = Search::findOne(['_id' => $one['_id']]); + if(!$searchModel['_id']){ + $searchModel = new Search; + } + $one['name'] = Yii::$service->fecshoplang->getLangAttrVal($one_name,'name',$langCode); + $one['description'] = Yii::$service->fecshoplang->getLangAttrVal($one_description,'description',$langCode); + $one['short_description'] = Yii::$service->fecshoplang->getLangAttrVal($one_short_description,'short_description',$langCode); + $one['sync_updated_at'] = time(); + Yii::$service->helper->ar->save($searchModel,$one); + if($errors = Yii::$service->helper->errors->get()){ + # 报错。 + echo $errors; + //return false; + } + } + } + } + } + } + return true; + } + + + /** + * 批量更新过程中,被更新的产品都会更新字段sync_updated_at + * 删除xunSearch引擎中sync_updated_at小于$nowTimeStamp的字段 + */ + protected function actionDeleteNotActiveProduct($nowTimeStamp){ + echo "begin delete Mongodb Search Date \n"; + //$langCodes = Yii::$service->fecshoplang->allLangCode; + //if(!empty($langCodes) && is_array($langCodes)){ + // foreach($langCodes as $langCodeInfo){ + if(!empty($this->searchLang) && is_array($this->searchLang)){ + foreach($this->searchLang as $langCode => $mongoSearchLangName){ + Search::$_lang = $langCode; + # 更新时间方式删除。 + Search::deleteAll([ + '<','sync_updated_at',(int)$nowTimeStamp + ]); + # 不存在更新时间的直接删除掉。 + Search::deleteAll([ + 'sync_updated_at' => [ + '?exists' => false, + ] + ]); + } + } + + } + + protected function actionRemoveByProductId($product_id){ + //echo 1;exit; + if(!empty($this->searchLang) && is_array($this->searchLang)){ + foreach($this->searchLang as $langCode => $mongoSearchLangName){ + Search::$_lang = $langCode; + Search::deleteAll([ + '_id' => $product_id, + ]); + } + } + return true; + } + + /** + * 得到搜索的产品列表 + */ + protected function actionGetSearchProductColl($select,$where,$pageNum,$numPerPage,$product_search_max_count){ + $filter = [ + 'pageNum' => $pageNum, + 'numPerPage' => $numPerPage, + 'where' => $where, + 'product_search_max_count' => $product_search_max_count, + 'select' => $select, + ]; + //var_dump($filter);exit; + $collection = $this->fullTearchText($filter); + $collection['coll'] = Yii::$service->category->product->convertToCategoryInfo($collection['coll']); + return $collection; + } + + /** + * 全文搜索 + * $filter Example: + * $filter = [ + * 'pageNum' => $this->getPageNum(), + * 'numPerPage' => $this->getNumPerPage(), + * 'where' => $this->_where, + * 'product_search_max_count' => Yii::$app->controller->module->params['product_search_max_count'], + * 'select' => $select, + * ]; + * 因为mongodb的搜索涉及到计算量,因此产品过多的情况下,要设置 product_search_max_count的值。减轻服务器负担 + * 因为对客户来说,前10页的产品已经足矣,后面的不需要看了,限定一下产品个数,减轻服务器的压力。 + * 多个spu,取score最高的那个一个显示。 + * 按照搜索的匹配度来进行排序,没有其他排序方式 + */ + protected function fullTearchText($filter){ + $where = $filter['where']; + $product_search_max_count = $filter['product_search_max_count'] ? $filter['product_search_max_count'] : 1000; + + $select = $filter['select']; + $pageNum = $filter['pageNum']; + $numPerPage = $filter['numPerPage']; + $orderBy = $filter['orderBy']; + # + /** + * 说明:1.'search_score'=>['$meta'=>"textScore" ,这个是text搜索为了排序, + * 详细参看:https://docs.mongodb.com/manual/core/text-search-operators/ + * 2. sort排序:search_score是全文搜索匹配后的得分,score是product表的一个字段,这个字段可以通过销售量或者其他作为参考设置。 + */ + Search::$_lang = Yii::$service->store->currentLangCode; + //$search_data = Search::getCollection(); + + //$mongodb = Yii::$app->mongodb; + //$search_data = $mongodb->getCollection('full_search_product_en') + + $search_data = Search::getCollection()->find($where,['search_score'=>['$meta'=>"textScore" ],'id' => 1 ,'spu'=> 1,'score' => 1,]) + ->sort( ['search_score'=> [ '$meta'=> 'textScore' ],'score' => -1] ) + ->limit($product_search_max_count) + ; + /** + * 通过下面的数组,在spu相同的多个sku产品,只显示一个,因为上面已经排序, + * 因此,spu相同的sku产品,显示的是score最高的一个。 + */ + $data = []; + foreach($search_data as $one){ + if(!isset($data[$one['spu']])){ + $data[$one['spu']] = $one; + } + } + $count = count($data); + $offset = ($pageNum -1)*$numPerPage; + $limit = $numPerPage; + $productIds = []; + foreach($data as $d){ + $productIds[] = $d['_id']; + } + $productIds = array_slice($productIds, $offset, $limit); + if(!empty($productIds)){ + $query = Product::find()->asArray() + ->select($select) + ->where(['_id'=> ['$in'=>$productIds]]) + ; + $data = $query->all(); + /** + * 下面的代码的作用:将结果按照上面in查询的顺序进行数组的排序,使结果和上面的搜索结果排序一致(_id)。 + */ + $s_data = []; + foreach($data as $one){ + $_id = $one['_id']->{'$id'}; + $s_data[$_id] = $one; + } + $return_data = []; + foreach($productIds as $product_id){ + $return_data[] = $s_data[$product_id->{'$id'}]; + } + return [ + 'coll' => $return_data , + 'count'=> $count, + ]; + } + + } + + + /** + * @property $filter_attr | String 需要进行统计的字段名称 + * @propertuy $where | Array 搜索条件。这个需要些mongodb的搜索条件。 + * 得到的是个属性,以及对应的个数。 + * 这个功能是用于前端分类侧栏进行属性过滤。 + */ + protected function actionGetFrontSearchFilter($filter_attr,$where){ + if(empty($where)) + return []; + $group['_id'] = '$'.$filter_attr; + $group['count'] = ['$sum'=> 1]; + $project = [$filter_attr => 1]; + $pipelines = [ + [ + '$match' => $where, + ], + [ + '$project' => $project + ], + [ + '$group' => $group, + ], + ]; + Search::$_lang = Yii::$service->store->currentLangCode; + $filter_data = Search::getCollection()->aggregate($pipelines); + return $filter_data; + } +} + + + + + + + + + + + diff --git a/services/search/SearchInterface.php b/services/search/SearchInterface.php new file mode 100644 index 000000000..52ef05823 --- /dev/null +++ b/services/search/SearchInterface.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +interface SearchInterface{ + + //protected function actionInitFullSearchIndex(); + +} + + + + + + diff --git a/services/search/XunSearch.php b/services/search/XunSearch.php new file mode 100644 index 000000000..01ea1da57 --- /dev/null +++ b/services/search/XunSearch.php @@ -0,0 +1,243 @@ + + * @since 1.0 + */ +class XunSearch extends Service implements SearchInterface +{ + public $searchIndexConfig; + public $searchLang; + public $fuzzy = false; + public $synonyms = false; + /** + * 初始化xunSearch索引 + */ + protected function actionInitFullSearchIndex(){ + return; + } + /** + * 将产品信息同步到xunSearch引擎中 + */ + protected function actionSyncProductInfo($product_ids,$numPerPage){ + + if(is_array($product_ids) && !empty($product_ids)){ + $productPrimaryKey = Yii::$service->product->getPrimaryKey(); + $XunSearchModel = new XunSearchModel; + $filter['select'] = $XunSearchModel->attributes(); + $filter['asArray'] = true; + $filter['where'][] = ['in',$productPrimaryKey,$product_ids]; + $filter['numPerPage']= $numPerPage; + $filter['pageNum'] = 1; + $coll = Yii::$service->product->coll($filter); + if(is_array($coll['coll']) && !empty($coll['coll'])){ + foreach($coll['coll'] as $one){ + $one_name = $one['name']; + $one_description = $one['description']; + $one_short_description = $one['short_description']; + if(!empty($this->searchLang) && is_array($this->searchLang)){ + foreach($this->searchLang as $langCode){ + $XunSearchModel = new XunSearchModel(); + $XunSearchModel->_id = $one['_id']->{'$id'}; + $one['name'] = Yii::$service->fecshoplang->getLangAttrVal($one_name,'name',$langCode); + $one['description'] = Yii::$service->fecshoplang->getLangAttrVal($one_description,'description',$langCode); + $one['short_description'] = Yii::$service->fecshoplang->getLangAttrVal($one_short_description,'short_description',$langCode); + $one['sync_updated_at'] = time(); + //echo $one['sync_updated_at']."\n"; + $serialize = true; + Yii::$service->helper->ar->save($XunSearchModel,$one,$serialize); + if($errors = Yii::$service->helper->errors->get()){ + # 报错。 + echo $errors; + //return false; + } + + } + } + } + } + } + return true; + } + protected function actionDeleteNotActiveProduct($nowTimeStamp){ + return; + } + /** + * 删除在xunSearch的所有搜索数据, + * 当您的产品有很多产品被删除了,但是在xunsearch 存在某些异常没有被删除 + * 您希望也被删除掉,那么,你可以通过这种方式批量删除掉产品 + * 然后重新跑一边同步脚本 + */ + protected function actionXunDeleteAllProduct($numPerPage,$i){ + //var_dump($index); + $dbName = XunSearchModel::projectName(); + # 删除索引 + Yii::$app->xunsearch->getDatabase($dbName)->getIndex()->clean(); + //$index = Yii::$app->xunsearch->getDatabase($dbName)->index; + + echo "begin delete Xun Search Date \n"; + $nowTimeStamp = (int) $nowTimeStamp; + $XunSearchData = XunSearchModel::find() + ->limit($numPerPage) + ->offset(($i-1)*$numPerPage) + ->all(); + foreach($XunSearchData as $one){ + $one->delete(); + } + } + + /** + * 得到搜索的sku列表 + */ + protected function actionGetSearchProductColl($select,$where,$pageNum,$numPerPage,$product_search_max_count){ + + $collection = $this->fullTearchText($select,$where,$pageNum,$numPerPage,$product_search_max_count); + + $collection['coll'] = Yii::$service->category->product->convertToCategoryInfo($collection['coll']); + //var_dump($collection); + //exit; + return $collection; + + } + + protected function fullTearchText($select,$where,$pageNum,$numPerPage,$product_search_max_count){ + $XunSearchQuery = XunSearchModel::find()->asArray(); + $XunSearchQuery->fuzzy($this->fuzzy); + $XunSearchQuery->synonyms($this->synonyms); + + + if(is_array($where) && !empty($where)){ + if(isset($where['$text']['$search']) && $where['$text']['$search']){ + $XunSearchQuery->where($where['$text']['$search']); + }else{ + return []; + } + foreach($where as $k => $v){ + if($k != '$text'){ + $XunSearchQuery->andWhere([$k => $v]); + } + } + } + $XunSearchQuery->orderBy( ['score' => SORT_DESC] ); + $XunSearchQuery->limit($product_search_max_count); + $XunSearchQuery->offset(0); + $search_data = $XunSearchQuery->all(); + + $data = []; + foreach($search_data as $one){ + if(!isset($data[$one['spu']])){ + $data[$one['spu']] = $one; + } + } + + $count = count($data); + $offset = ($pageNum -1)*$numPerPage; + $limit = $numPerPage; + $productIds = []; + foreach($data as $d){ + $productIds[] = new \MongoId($d['_id']); + } + + $productIds = array_slice($productIds, $offset, $limit); + + if(!empty($productIds)){ + $query = Product::find()->asArray() + ->select($select) + ->where(['_id'=> ['$in'=>$productIds]]) + ; + $data = $query->all(); + /** + * 下面的代码的作用:将结果按照上面in查询的顺序进行数组的排序,使结果和上面的搜索结果排序一致(_id)。 + */ + $s_data = []; + foreach($data as $one){ + $_id = $one['_id']->{'$id'}; + $s_data[$_id] = $one; + } + $return_data = []; + foreach($productIds as $product_id){ + $return_data[] = $s_data[$product_id->{'$id'}]; + } + return [ + 'coll' => $return_data , + 'count'=> $count, + ]; + } + + } + + + + + /** + * 得到搜索的sku列表侧栏的过滤 + */ + protected function actionGetFrontSearchFilter($filter_attr,$where){ + //var_dump($where); + $dbName = XunSearchModel::projectName(); + $_search = Yii::$app->xunsearch->getDatabase($dbName)->getSearch(); + $text = isset($where['$text']['$search']) ? $where['$text']['$search'] : ''; + if(!$text){ + return []; + } + $sh = ''; + foreach($where as $k => $v){ + if($k != '$text'){ + if(!$sh){ + $sh = ' AND '.$k.':'.$v; + }else{ + $sh .= ' AND '.$k.':'.$v; + } + } + } + echo $sh; + + + $docs = $_search->setQuery($text.$sh) + ->setFacets([$filter_attr]) + ->setFuzzy($this->fuzzy) + ->setAutoSynonyms($this->synonyms) + ->search(); + $filter_attr_counts = $_search->getFacets($filter_attr); + $count_arr = []; + if(is_array($filter_attr_counts) && !empty($filter_attr_counts)){ + foreach($filter_attr_counts as $k => $v){ + $count_arr[] = [ + '_id' => $k, + 'count' => $v, + ]; + } + } + return $count_arr; + } + /** + * 通过product_id删除搜索数据 + */ + protected function actionRemoveByProductId($product_id){ + if(is_object($product_id)){ + $product_id = $product_id->{'$id'}; + $model = XunSearchModel::findOne($product_id); + $model->delete(); + } + + } + + +} + + + diff --git a/services/url/Category.php b/services/url/Category.php new file mode 100644 index 000000000..d67dee57b --- /dev/null +++ b/services/url/Category.php @@ -0,0 +1,235 @@ + + * @since 1.0 + */ +class Category extends Service +{ + /** + * ֵתurlʽַurl + */ + protected function actionAttrValConvertUrlStr($strVal){ + if($strVal){ + if(!preg_match("/^[A-Za-z0-9-_ &]+$/",$strVal)){ + throw new InvalidValueException('"'.$strVal .'":contain special str , you can only contain special string [A-Za-z0-9-_ &]'); + } + $convert = $this->strUrlRelation(); + foreach($convert as $originStr => $nStr){ + $strVal = str_replace($originStr,$nStr,$strVal); + } + return $strVal; + } + } + /** + * urlʽַתֵڽurlõӦֵ + */ + protected function actionUrlStrConvertAttrVal($urlStr){ + $convert = $this->strUrlRelation(); + foreach($convert as $originStr => $nStr){ + $urlStr = str_replace($nStr,$originStr,$urlStr); + } + return $urlStr; + } + + protected function strUrlRelation(){ + return [ + ' ' => '!', + '&' => '@', + ]; + } + /** + * + */ + /** + * ڷԣõѡԵurl + * @property $attrUrlStr|String Եurlַ + * @property $val|String ԶӦֵδurlֵ + * @property $p|String urlʾҳIJһpʾ + * @property $pageBackToOne|boolean Ƿpҳعһҳ + */ + protected function actionGetFilterChooseAttrUrl($attrUrlStr,$val,$p='p',$pageBackToOne=true){ + + $val = $this->attrValConvertUrlStr($val); + $str = $attrUrlStr.'='.$val; + $currentRequestVal = Yii::$app->request->get($attrUrlStr); + $originPUrl = ''; + if($pageBackToOne && $p){ + $pVal = Yii::$app->request->get($p); + if($pVal){ + $originPUrl = $p.'='.$pVal; + $afterPUrl = $p.'=1'; + } + } + + if($currentRequestVal){ + $originAttrUrlStr = $attrUrlStr.'='.$currentRequestVal; + $currentUrl = Yii::$service->url->getCurrentUrl(); + if($originAttrUrlStr == $str){ + //return str_replace($originAttrUrlStr,$str,$currentUrl); + $url = $currentUrl; + if(strstr($currentUrl,'?'.$originAttrUrlStr.'&')){ + $url = str_replace('?'.$originAttrUrlStr.'&','?',$currentUrl); + }else if(strstr($currentUrl,'?'.$originAttrUrlStr)){ + $url = str_replace('?'.$originAttrUrlStr,'',$currentUrl); + }else if(strstr($currentUrl,'&'.$originAttrUrlStr)){ + $url = str_replace('&'.$originAttrUrlStr,'',$currentUrl); + } + if($originPUrl){ + $url = str_replace($originPUrl,$afterPUrl,$url); + } + return [ + 'url' => $url, + 'selected' => true, + ]; + }else{ + if($originPUrl){ + $currentUrl = str_replace($originPUrl,$afterPUrl,$currentUrl); + } + return [ + 'url' => str_replace($originAttrUrlStr,$str,$currentUrl), + 'selected' => false, + ]; + } + return str_replace($originAttrUrlStr,$str,$currentUrl); + }else{ + $currentUrl = Yii::$service->url->getCurrentUrl(); + if(strstr($currentUrl,'?')){ + if($originPUrl){ + $currentUrl = str_replace($originPUrl,$afterPUrl,$currentUrl); + } + return [ + 'url' => $currentUrl.'&'.$str, + 'selected' => false + ]; + }else{ + if($originPUrl){ + $currentUrl = str_replace($originPUrl,$afterPUrl,$currentUrl); + } + return [ + 'url' => $currentUrl.'?'.$str, + 'selected' => false + ]; + } + } + + } + + /** + * õurl + * @property $arr|Array sortֶκֵ dirֶκֵ + * @property $p|String urlʾҳIJһpʾ + * @property $pageBackToOne|boolean Ƿpҳعһҳ + */ + protected function actionGetFilterSortAttrUrl($arr,$p='',$pageBackToOne=true){ + $sort = $arr['sort']['key']; + $sortVal = $arr['sort']['val']; + $dir = $arr['dir']['key']; + $dirVal = $arr['dir']['val']; + + $originPUrl = ''; + if($pageBackToOne && $p){ + $pVal = Yii::$app->request->get($p); + if($pVal){ + $originPUrl = $p.'='.$pVal; + $afterPUrl = $p.'=1'; + } + } + + $sortVal = $this->attrValConvertUrlStr($sortVal); + $sortStr = $sort.'='.$sortVal; + $currentSortVal = Yii::$app->request->get($sort); + + $dirVal = $this->attrValConvertUrlStr($dirVal); + $dirStr = $dir.'='.$dirVal; + $currentDirVal = Yii::$app->request->get($dir); + + $str = $sortStr.'&'.$dirStr; + if($currentSortVal && $currentDirVal){ + $originAttrUrlStr = $sort.'='.$currentSortVal.'&'.$dir.'='.$currentDirVal; + $currentUrl = Yii::$service->url->getCurrentUrl(); + + if($originAttrUrlStr == $str){ + //return str_replace($originAttrUrlStr,$str,$currentUrl); + $url = $currentUrl; + if(strstr($currentUrl,'?'.$originAttrUrlStr.'&')){ + $url = str_replace('?'.$originAttrUrlStr.'&','?',$currentUrl); + }else if (strstr($currentUrl,'?'.$originAttrUrlStr)){ + $url = str_replace('?'.$originAttrUrlStr,'',$currentUrl); + }else if(strstr($currentUrl,'&'.$originAttrUrlStr)){ + $url = str_replace('&'.$originAttrUrlStr,'',$currentUrl); + } + if($originPUrl){ + $url = str_replace($originPUrl,$afterPUrl,$url); + } + return [ + 'url' => $url, + 'selected' => true, + ]; + }else{ + if($originPUrl){ + $currentUrl = str_replace($originPUrl,$afterPUrl,$currentUrl); + } + return [ + 'url' => str_replace($originAttrUrlStr,$str,$currentUrl), + 'selected' => false, + ]; + } + return str_replace($originAttrUrlStr,$str,$currentUrl); + }else{ + $currentUrl = Yii::$service->url->getCurrentUrl(); + if(strstr($currentUrl,'?')){ + if($originPUrl){ + $currentUrl = str_replace($originPUrl,$afterPUrl,$currentUrl); + } + return [ + 'url' => $currentUrl.'&'.$str, + 'selected' => false + ]; + }else{ + if($originPUrl){ + $currentUrl = str_replace($originPUrl,$afterPUrl,$currentUrl); + } + return [ + 'url' => $currentUrl.'?'.$str, + 'selected' => false + ]; + } + } + + } + + /** + * õѡԵurl + */ + /* + protected function actionGetFilterUnChooseAttrUrl($attrUrlStr,$val){ + $val = $this->attrValConvertUrlStr($val); + $str = $attrUrlStr.'='.$val; + $currentUrl = Yii::$service->url->getCurrentUrl(); + $currentUrl = str_replace($str,'',$currentUrl); + return $currentUrl ; + } + */ + + + + + + +} \ No newline at end of file diff --git a/services/url/Rewrite.php b/services/url/Rewrite.php new file mode 100644 index 000000000..dc3f015bf --- /dev/null +++ b/services/url/Rewrite.php @@ -0,0 +1,108 @@ + + * @since 1.0 + */ +class Rewrite extends Service +{ + public $storage = 'mongodb'; + protected $_urlRewrite; + + + public function init(){ + if($this->storage == 'mongodb'){ + $this->_urlRewrite = new RewriteMongodb; + }else if($this->storage == 'mysqldb'){ + $this->_urlRewrite = new RewriteMysqldb; + } + } + + protected function actionGetOriginUrl($urlKey){ + return $this->_urlRewrite->getOriginUrl($urlKey); + } + + /** + * get artile's primary key. + */ + protected function actionGetPrimaryKey(){ + return $this->_urlRewrite->getPrimaryKey(); + } + /** + * get artile model by primary key. + */ + protected function actionGetByPrimaryKey($primaryKey){ + return $this->_urlRewrite->getByPrimaryKey($primaryKey); + } + + //public function getById($id){ + // return $this->_article->getById($id); + //} + + /** + * @property $filter|Array + * get artile collection by $filter + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + * where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + protected function actionColl($filter=''){ + return $this->_urlRewrite->coll($filter); + } + + /** + * @property $one|Array , save one data . + * @property $originUrlKey|String , article origin url key. + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + protected function actionSave($one){ + return $this->_urlRewrite->save($one); + } + + protected function actionRemove($ids){ + return $this->_urlRewrite->remove($ids); + } + + protected function actionRemoveByUpdatedAt($time){ + return $this->_urlRewrite->removeByUpdatedAt($time); + } + + + protected function actionFind(){ + return $this->_urlRewrite->find(); + } + + protected function actionFindOne($where){ + return $this->_urlRewrite->findOne($where); + } + + protected function actionNewModel(){ + return $this->_urlRewrite->newModel(); + } + +} \ No newline at end of file diff --git a/services/url/rewrite/RewriteInterface.php b/services/url/rewrite/RewriteInterface.php new file mode 100644 index 000000000..50e7cf7aa --- /dev/null +++ b/services/url/rewrite/RewriteInterface.php @@ -0,0 +1,24 @@ + + * @since 1.0 + */ +interface RewriteInterface{ + + public function getByPrimaryKey($primaryKey); + public function coll($filter); + public function save($one); + public function remove($ids); + public function find(); + public function findOne($where); + public function newModel(); + +} \ No newline at end of file diff --git a/services/url/rewrite/RewriteMongodb.php b/services/url/rewrite/RewriteMongodb.php new file mode 100644 index 000000000..4a844fae1 --- /dev/null +++ b/services/url/rewrite/RewriteMongodb.php @@ -0,0 +1,157 @@ + + * @since 1.0 + */ +class RewriteMongodb implements RewriteInterface +{ + public $numPerPage = 20; + + + public function getOriginUrl($urlKey){ + $UrlData = UrlRewrite::find()->where([ + 'custom_url_key' => $urlKey, + ])->asArray()->one(); + if($UrlData['custom_url_key']){ + return $UrlData['origin_url']; + } + return ; + } + + public function getPrimaryKey(){ + return '_id'; + } + + public function getByPrimaryKey($primaryKey){ + if($primaryKey){ + return UrlRewrite::findOne($primaryKey); + }else{ + return new UrlRewrite; + } + + } + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function coll($filter=''){ + + $query = UrlRewrite::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + return [ + 'coll' => $query->all(), + 'count'=> $query->count(), + ]; + } + + /** + * @property $one|Array + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + public function save($one){ + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + if($primaryVal){ + $model = UrlRewrite::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('UrlRewrite '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + $model = new UrlRewrite; + } + unset($one['_id']); + $saveStatus = Yii::$service->helper->ar->save($model,$one); + + return true; + } + + public function remove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + foreach($ids as $id){ + $model = UrlRewrite::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + $model->delete(); + }else{ + //throw new InvalidValueException("ID:$id is not exist."); + Yii::$service->helper->errors->add("UrlRewrite Remove Errors:ID $id is not exist."); + return false; + } + } + }else{ + $id = $ids; + $model = UrlRewrite::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + $model->delete(); + }else{ + Yii::$service->helper->errors->add("UrlRewrite Remove Errors:ID:$id is not exist."); + return false; + } + } + return true; + + } + + public function removeByUpdatedAt($time){ + if($time){ + UrlRewrite::deleteAll([ + '$or' => [ + [ + 'updated_at' => [ + '$lt' => (int)$time, + ], + ], + [ + 'updated_at' => [ + '$exists' => false + ] + ] + ] + + ]); + echo "delete complete \n"; + } + + } + + + public function find(){ + return UrlRewrite::find(); + } + + public function findOne($where){ + return UrlRewrite::findOne($where); + } + + public function newModel(){ + return new UrlRewrite; + } + +} \ No newline at end of file diff --git a/services/url/rewrite/RewriteMysqldb.php b/services/url/rewrite/RewriteMysqldb.php new file mode 100644 index 000000000..8136f9585 --- /dev/null +++ b/services/url/rewrite/RewriteMysqldb.php @@ -0,0 +1,184 @@ + + * @since 1.0 + */ +class RewriteMysqldb implements RewriteInterface +{ + public $numPerPage = 20; + /** + * language attribute. + */ + protected $_lang_attr = [ + + ]; + + public function getOriginUrl($urlKey){ + $UrlData = UrlRewrite::find()->where([ + 'custom_url_key' => $urlKey, + ])->asArray()->one(); + if($UrlData['custom_url_key']){ + return $UrlData['origin_url']; + } + return ; + } + + public function getPrimaryKey(){ + return 'id'; + } + + public function getByPrimaryKey($primaryKey){ + if($primaryKey){ + $one = UrlRewrite::findOne($primaryKey); + if(!empty($this->_lang_attr)){ + foreach($this->_lang_attr as $attrName){ + if(isset($one[$attrName])){ + $one[$attrName] = unserialize($one[$attrName]); + } + } + } + return $one; + }else{ + return new UrlRewrite; + } + + } + /* + * example filter: + * [ + * 'numPerPage' => 20, + * 'pageNum' => 1, + * 'orderBy' => ['_id' => SORT_DESC, 'sku' => SORT_ASC ], + * where' => [ + ['>','price',1], + ['<=','price',10] + * ['sku' => 'uk10001'], + * ], + * 'asArray' => true, + * ] + */ + public function coll($filter=''){ + + $query = UrlRewrite::find(); + $query = Yii::$service->helper->ar->getCollByFilter($query,$filter); + $coll = $query->all(); + if(!empty($coll)){ + foreach($coll as $k => $one){ + if(!empty($this->_lang_attr)){ + foreach($this->_lang_attr as $attr){ + $one[$attr] = $one[$attr] ? unserialize($one[$attr]) : ''; + } + } + $coll[$k] = $one; + } + } + //var_dump($one); + return [ + 'coll' => $coll, + 'count'=> $query->count(), + ]; + } + + /** + * @property $one|Array + * save $data to cms model,then,add url rewrite info to system service urlrewrite. + */ + public function save($one){ + $primaryVal = isset($one[$this->getPrimaryKey()]) ? $one[$this->getPrimaryKey()] : ''; + if($primaryVal){ + $model = UrlRewrite::findOne($primaryVal); + if(!$model){ + Yii::$service->helper->errors->add('UrlRewrite '.$this->getPrimaryKey().' is not exist'); + return; + } + }else{ + $model = new UrlRewrite; + } + unset($one['_id']); + $saveStatus = Yii::$service->helper->ar->save($model,$one); + + return true; + } + + public function remove($ids){ + if(!$ids){ + Yii::$service->helper->errors->add('remove id is empty'); + return false; + } + if(is_array($ids) && !empty($ids)){ + $innerTransaction = Yii::$service->db->beginTransaction(); + try { + foreach($ids as $id){ + $model = UrlRewrite::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $url_key = $model['url_key']; + $model->delete(); + }else{ + + //throw new InvalidValueException("ID:$id is not exist."); + Yii::$service->helper->errors->add("UrlRewrite Remove Errors:ID $id is not exist."); + $innerTransaction->rollBack(); + return false; + } + } + $innerTransaction->commit(); + } catch (Exception $e) { + Yii::$service->helper->errors->add("UrlRewrite Remove Errors: transaction rollback"); + $innerTransaction->rollBack(); + return false; + } + }else{ + $id = $ids; + $model = UrlRewrite::findOne($id); + if(isset($model[$this->getPrimaryKey()]) && !empty($model[$this->getPrimaryKey()]) ){ + $innerTransaction = Yii::$service->db->beginTransaction(); + try { + $url_key = $model['url_key']; + $model->delete(); + $innerTransaction->commit(); + } catch (Exception $e) { + Yii::$service->helper->errors->add("UrlRewrite Remove Errors: transaction rollback"); + $innerTransaction->rollBack(); + } + }else{ + Yii::$service->helper->errors->add("UrlRewrite Remove Errors:ID:$id is not exist."); + return false; + } + } + return true; + + } + + public function removeByUpdatedAt($time){ + if($time){ + UrlRewrite::deleteAll([ + '<','updated_at',$time + ]); + } + } + + public function find(){ + return UrlRewrite::find(); + } + + public function findOne($where){ + return UrlRewrite::findOne($where); + } + + public function newModel(){ + return new UrlRewrite; + } + +} \ No newline at end of file diff --git a/shell/README.md b/shell/README.md new file mode 100644 index 000000000..07eb54d52 --- /dev/null +++ b/shell/README.md @@ -0,0 +1,42 @@ +脚本说明 +======== + +### 1. fullSearchSync.sh + +此脚本是初始化产品数据到相应的搜索引擎中的表中 +目前只有mongodb的fullTextSearch 和Xun Search +其中mongodb fullTextSearch支持大多数国外字母系列语言 +Xun Search 只支持中文, + +本脚本做的事情: + +1.1 初始化搜索引擎索引 + +1.2 循环遍历产品数据,将产品数据拷贝到相应的搜索表中 +同时更新sync_updated_at字段时间戳。 + +1.3 时间戳小于sync_updated_at的代表没有被批量处理 +,说明这个产品存在问题(产品被删除,noactive 无库存等) +会被最后的脚本,从搜索表中清空。 + +### 2.initDb.sh + +本脚本为第一次安装的时候,初始化数据库。 + +### 3.computeProductFinalPrice.sh + +本脚本为计算每一个产品的final_price的值, +3.1 同时会同步信息到搜索表,但是不会执行删除操作(譬如某个产品 +删除了,这个脚本是不会检测,同步删除的,如果要删除需要执行 fullSearchSync.sh脚本。) +3.2 url rewrite的产品数据也会同步,同样不会执行删除url rewrite里面的数据( +如果要清除残留的url rewrite数据,请执行urlRewrite.sh脚本)。 + + +### 4.urlRewrite.sh脚本 + +4.1 将产品的url自定义重新跑一次。包括分类,产品等 +4.2 将urlRewrite表中残留的重新数据,但是在产品和分类中不存在的清空。 + + + + diff --git a/shell/computeProductFinalPrice.sh b/shell/computeProductFinalPrice.sh new file mode 100644 index 000000000..e09e3cc15 --- /dev/null +++ b/shell/computeProductFinalPrice.sh @@ -0,0 +1,26 @@ +#!/bin/sh +Cur_Dir=$(cd `dirname $0`; pwd) +# get product all count. +count=`$Cur_Dir/../../../../yii product/price/productcount` +pagenum=`$Cur_Dir/../../../../yii product/price/productpagenum` + +echo "There are $count products to process" +echo "There are $pagenum pages to process" +echo "##############ALL BEGINING###############"; +for (( i=1; i<=$pagenum; i++ )) +do + $Cur_Dir/../../../../yii product/price/computefinalprice $i + echo "Page $i done" +done + +###### 1.Sync Section End + + + + +echo "##############ALL COMPLETE###############"; + + + + + diff --git a/shell/fullSearchSync.sh b/shell/fullSearchSync.sh new file mode 100644 index 000000000..65780b570 --- /dev/null +++ b/shell/fullSearchSync.sh @@ -0,0 +1,42 @@ +#!/bin/sh +Cur_Dir=$(cd `dirname $0`; pwd) +# init full search collection indexes. +$Cur_Dir/../../../../yii product/search/initindex +# get now update timestamp. +nowtime=`$Cur_Dir/../../../../yii product/search/nowtime` + +###### 1.Sync Section : Sync Product Serach Collection +# get product all count. +count=`$Cur_Dir/../../../../yii product/search/synccount` +pagenum=`$Cur_Dir/../../../../yii product/search/syncpagenum` + + +echo "There are $count products to process" +echo "There are $pagenum pages to process" +echo "##############ALL BEGINING###############"; +for (( i=1; i<=$pagenum; i++ )) +do + $Cur_Dir/../../../../yii product/search/syncdata $i + echo "Page $i done" +done +#delete all search data that sync_updated_at $gt $nowtime. +$Cur_Dir/../../../../yii product/search/deletenotactiveproduct $nowtime + +###### delete xunsearch +echo "There are $pagenum pages to check if is delete in xunSearch" +echo "##############ALL BEGINING###############"; +for (( i=1; i<=$pagenum; i++ )) +do + $Cur_Dir/../../../../yii product/search/xundeletenotactiveproduct $nowtime $i + echo "Page $i done" +done + + + + +echo "##############ALL COMPLETE###############"; + + + + + diff --git a/shell/initDb.sh b/shell/initDb.sh new file mode 100644 index 000000000..485267ee0 --- /dev/null +++ b/shell/initDb.sh @@ -0,0 +1,16 @@ +#!/bin/sh +#processDate=$1 +#Cur_Dir=$(pwd) +Cur_Dir=$(cd `dirname $0`; pwd) +#fec_admin +$Cur_Dir/../../../../yii migrate --migrationPath=@fecadmin/migrations + +#db +$Cur_Dir/../../../../yii migrate --migrationPath=@fecshop/migrations/db/product/log + +#mongodb +$Cur_Dir/../../../../yii mongodb-migrate --migrationPath=@fecshop/migrations/mongodb/urlwrite +$Cur_Dir/../../../../yii mongodb-migrate --migrationPath=@fecshop/migrations/mongodb/product/log + + + diff --git a/shell/search/deleteXunSearchAllData.sh b/shell/search/deleteXunSearchAllData.sh new file mode 100644 index 000000000..cb29a1c1e --- /dev/null +++ b/shell/search/deleteXunSearchAllData.sh @@ -0,0 +1,16 @@ +#!/bin/sh +Cur_Dir=$(cd `dirname $0`; pwd) + +count=`$Cur_Dir/../../../../../yii product/search/synccount` +pagenum=`$Cur_Dir/../../../../../yii product/search/syncpagenum` + +###### delete xunsearch +echo "There are $pagenum pages to check if is delete in xunSearch" +echo "##############ALL BEGINING###############"; +for (( i=1; i<=$pagenum; i++ )) +do + $Cur_Dir/../../../../../yii product/search/xundeleteallproduct $i + echo "Page $i done" +done + + diff --git a/shell/search/fullSearchSync.sh b/shell/search/fullSearchSync.sh new file mode 100644 index 000000000..76b5bf2f3 --- /dev/null +++ b/shell/search/fullSearchSync.sh @@ -0,0 +1,30 @@ +#!/bin/sh +Cur_Dir=$(cd `dirname $0`; pwd) +# init full search collection indexes. +$Cur_Dir/../../../../../yii product/search/initindex +# get now update timestamp. +nowtime=`$Cur_Dir/../../../../../yii product/search/nowtime` + +###### 1.Sync Section : Sync Product Serach Collection +# get product all count. +count=`$Cur_Dir/../../../../../yii product/search/synccount` +pagenum=`$Cur_Dir/../../../../../yii product/search/syncpagenum` + + +echo "There are $count products to process" +echo "There are $pagenum pages to process" +echo "##############ALL BEGINING###############"; +for (( i=1; i<=$pagenum; i++ )) +do + $Cur_Dir/../../../../../yii product/search/syncdata $i + echo "Page $i done" +done +# ()delete all search data that sync_updated_at $gt $nowtime. +$Cur_Dir/../../../../../yii product/search/deletenotactiveproduct $nowtime + +echo "##############ALL COMPLETE###############"; + + + + + diff --git a/shell/urlRewrite.sh b/shell/urlRewrite.sh new file mode 100644 index 000000000..ec5faede9 --- /dev/null +++ b/shell/urlRewrite.sh @@ -0,0 +1,53 @@ +#!/bin/sh +Cur_Dir=$(cd `dirname $0`; pwd) +# init full search collection indexes. +$Cur_Dir/../../../../yii product/search/initindex +# get now update timestamp. +nowtime=`$Cur_Dir/../../../../yii helper/urlrewrite/nowtime` + +###### 1.Sync Section : Sync Product Serach Collection +# get product all count. +count=`$Cur_Dir/../../../../yii helper/urlrewrite/productcount` +pagenum=`$Cur_Dir/../../../../yii helper/urlrewrite/productpagenum` + + +echo "There are $count products to process" +echo "There are $pagenum products pages to process" +echo "##############ALL BEGINING###############"; +for (( i=1; i<=$pagenum; i++ )) +do + $Cur_Dir/../../../../yii helper/urlrewrite/product $i + echo "Page $i done" +done + + +#Category + +count=`$Cur_Dir/../../../../yii helper/urlrewrite/categorycount` +pagenum=`$Cur_Dir/../../../../yii helper/urlrewrite/categorypagenum` + + +echo "There are $count categorys to process" +echo "There are $pagenum categorys pages to process" +echo "##############ALL BEGINING###############"; +for (( i=1; i<=$pagenum; i++ )) +do + $Cur_Dir/../../../../yii helper/urlrewrite/category $i + echo "Page $i done" +done + + +#delete all search data that sync_updated_at $gt $nowtime. +$Cur_Dir/../../../../yii helper/urlrewrite/clearnoactive $nowtime + +###### 1.Sync Section End + + + + +echo "##############ALL COMPLETE###############"; + + + + + diff --git a/yii/Yii.php b/yii/Yii.php new file mode 100644 index 000000000..f9558aa3b --- /dev/null +++ b/yii/Yii.php @@ -0,0 +1,23 @@ + + * @since 1.0 + */ +class Yii extends \yii\BaseYii +{ + public static $service; + +} + +spl_autoload_register(['Yii', 'autoload'], true, true); +Yii::$classMap = require($dir.'/classes.php'); +Yii::$container = new yii\di\Container(); diff --git a/yii/i18n/PhpMessageSource.php b/yii/i18n/PhpMessageSource.php new file mode 100644 index 000000000..20407bd19 --- /dev/null +++ b/yii/i18n/PhpMessageSource.php @@ -0,0 +1,48 @@ + + * @since 1.0 + */ +class PhpMessageSource extends YiiPhpMessageSource +{ + public $basePaths = []; + protected function loadMessages($category, $language) + { + $message_merge = []; + foreach($this->basePaths as $base){ + $this->basePath = $base; + $messageFile = $this->getMessageFilePath($category, $language); + $messages = $this->loadMessagesFromFile($messageFile); + + $fallbackLanguage = substr($language, 0, 2); + $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2); + + if ($language !== $fallbackLanguage) { + $messages = $this->loadFallbackMessages($category, $fallbackLanguage, $messages, $messageFile); + } elseif ($language === $fallbackSourceLanguage) { + $messages = $this->loadFallbackMessages($category, $this->sourceLanguage, $messages, $messageFile); + } else { + if ($messages === null) { + Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__); + } + } + if(is_array($messages)){ + $message_merge = array_merge($message_merge,$messages); + } + } + //var_dump($message_merge);exit; + return (array) $message_merge; + } + + +} diff --git a/yii/web/Request.php b/yii/web/Request.php new file mode 100644 index 000000000..421ead217 --- /dev/null +++ b/yii/web/Request.php @@ -0,0 +1,145 @@ + + * @since 1.0 + */ +class Request extends \yii\web\Request +{ + /** + * rewrite yii\web\Request resolveRequestUri() + */ + protected function resolveRequestUri() + { + if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS + $requestUri = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $requestUri = $_SERVER['REQUEST_URI']; + if ($requestUri !== '' && $requestUri[0] !== '/') { + $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri); + } + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI + $requestUri = $_SERVER['ORIG_PATH_INFO']; + if (!empty($_SERVER['QUERY_STRING'])) { + $requestUri .= '?' . $_SERVER['QUERY_STRING']; + } + } else { + throw new InvalidConfigException('Unable to determine the request URI.'); + } + + /** + * Replace Code + * //return $requestUri; + * To: + */ + return $this->getRewriteUri($requestUri); + } + + /** + * get module request url by db ; + */ + protected function getRewriteUri($requestUri){ + $baseUrl = $this->getBaseUrl(); + $requestUriRelative = $requestUri; + if($baseUrl){ + $requestUriRelative = substr($requestUriRelative, strlen($baseUrl)); + } + + //echo $requestUriRelative;exit; + $urlKey = ''; + $urlParam = ''; + $urlParamSuffix = ''; + + if(strstr($requestUriRelative,"#")){ + list($urlNoSuffix,$urlParamSuffix)= explode("#",$requestUriRelative); + if(strstr($urlNoSuffix,"?")){ + list($urlKey,$urlParam)= explode("?",$urlNoSuffix); + } + }else if(strstr($requestUriRelative,"?")){ + list($urlKey,$urlParam)= explode("?",$requestUriRelative); + }else{ + $urlKey = $requestUriRelative; + } + if($urlParamSuffix){ + $urlParamSuffix = '#'.$urlParamSuffix; + } + //echo $urlKey;exit; + if(Yii::$service->url->showScriptName){ + $urlKey = str_replace('/index.php','',$urlKey); + $originUrlPath = Yii::$service->url->getOriginUrl($urlKey); + }else{ + $originUrlPath = Yii::$service->url->getOriginUrl($urlKey); + } + //echo $urlKey; + //echo $originUrlPath; + //exit; + if($originUrlPath){ + if(strstr($originUrlPath,'?')){ + if($urlParam){ + $url = $originUrlPath.'&'.$urlParam.$urlParamSuffix; + }else{ + $url = $originUrlPath.$urlParamSuffix; + } + $this->setRequestParam($originUrlPath); + }else{ + if($urlParam){ + $url = $originUrlPath.'?'.$urlParam.$urlParamSuffix; + }else{ + $url = $originUrlPath.$urlParamSuffix; + } + } + return $baseUrl.$url; + }else{ + return $requestUri; + } + + } + + /** + * after get urlPath from db, if urlPath has get param , + * set the param to $_GET + */ + public function setRequestParam($originUrlPath){ + $arr = explode("?",$originUrlPath); + $yiiUrlParam = $arr[1]; + $arr = explode("&",$yiiUrlParam); + foreach($arr as $a){ + list($key,$val) = explode("=",$a); + $_GET[$key] = $val; + } + } + + /** + * mongodb url_rewrite collection columns: _id, type ,custom_url, yii_url, + * if selete date from UrlRewrite, return the yii url. + */ + /* + protected function getOriginUrl($urlKey){ + $UrlData = UrlRewrite::find()->where([ + 'custom_url_key' => $urlKey, + ])->asArray()->one(); + if($UrlData['custom_url_key']){ + return $UrlData['origin_url']; + } + return ; + } + */ + + + +} \ No newline at end of file diff --git a/yii/web/User.php b/yii/web/User.php new file mode 100644 index 000000000..25e9b6bef --- /dev/null +++ b/yii/web/User.php @@ -0,0 +1,59 @@ + + * @since 1.0 + */ +class User extends \yii\web\User +{ + /** + * 重写该方法的作用为:当用户登录账户设置为cookie的时候 + * 购物车信息,通过cookie获取的用户id,在session中设置cart_id. + */ + protected function loginByCookie() + { + $data = $this->getIdentityAndDurationFromCookie(); + if (isset($data['identity'], $data['duration'])) { + $identity = $data['identity']; + $duration = $data['duration']; + if ($this->beforeLogin($identity, true, $duration)) { + $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0); + $id = $identity->getId(); + $ip = Yii::$app->getRequest()->getUserIP(); + Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__); + $this->afterLogin($identity, true, $duration); + /** + * 如果user组件配置enableAutoLogin = true + * 当session失效后,就会调用当前方法,cookie获取信息后,重新使用session登录 + * 因此,在账号重新恢复登录状态后,当前账户的购物车也要恢复。 + * 下面的代码就是在cookie恢复登录状态后,通过当前账户的id,搜索出来购物车信息 + * 然后把对应的购物车的cart_id,保存到cookie中。 + * + */ + + $customer_cart = Yii::$service->cart->quote->getCartByCustomerId($id); + $cart_id = isset($customer_cart['cart_id']) ? $customer_cart['cart_id'] : ''; + //echo $cart_id; + if($cart_id){ + Yii::$service->cart->quote->setCartId($cart_id); + } + //Yii::$service->cart->mergeCartAfterUserLogin(); + + } + } + } + + + +} + +