87 Multiple Choice items (85 scored, 2 unscored)
120 minutes to complete the exam
Based on Magento Community Edition 1.9 and Magento Enterprise Edition 1.14
Magento Certified Developer Plus exam: For sections 11 & 12 combined a score of 7 or higher AND meet the overall passing score of 48 or higher
5% 1-Basics (config, events, cron, translations, theme, locating template/layout file)
6% 2-Request Flow
6% 3-Rendering
11% 4-Working with Database
8% 5-Entity-Attribute-Value (EAV) Model
6% 6-Adminhtml (ACL, cache, grids, forms, system config)
8% 7-Catalog (product types, indexing, prices, categories, catalog rules, tax)
13% 8-Checkout (inventory, add to cart, totals, cart rules, payment methods, shipping methods, multishipping)
9% 9-Sales and Customers (order, refund, partial invoice/shipping/refund, cancel, customers)
11% 10-Advanced features (widgets, API)
11% 11-Enterprise Edition (target rules, reward points, website restrictions, full page cache, payment bridge)
8% 12-Challenge Questions
run:
- events collection
- app request, response (optional)
- config
- run app
baseInit:
- error handler + default timezone
- load base config - /etc/*.xml (config.xml, local.xml)
- cache - new core/cache by , request_processors x send response here if in cache _initModules:
- loadModules (declared, modulesConfiguration)
- applyAllUpdates (unless new install)
- loadDb
- saveCache x code and db are ready run:
- load area (global events)
- initCurrentStore (cookie,get) |
- initRequest (pathinfo) | unless new install
- applyAllDataUpdates |
- front dispatch
<web>
<routers>
<admin>
<area>admin</area>
<class>Mage_Core_Controller_Varien_Router_Admin</class>
x <web><routers>
- admin, frontend. CollectRoutes from <admin><routers>
and <frontend><routers>
init:
- events
controller_front_init_before
-- prepend routers here. - routers (from
<config><web><routers>
) + collectRoutes ([frontName] => modules[])router.collectRoutes(router_key, router_area)
- router key -- only routes with
<use>
== router_area are collected. e.g. use=admin, use=standard - router area -- load routes from area, e.g. frontend/routers, admin/routers ! admin/routers -- registers concrete route . in modules reference concrete route to add more controllers to chain
- router key -- only routes with
- event
controller_front_init_routers
-- append routers here. CMS adds router here
dispatch:
- _checkBaseUrl (unless admin)
- rewrite (db + config)
- loop
- send response (headers, exceptions, body)
- events (2)
dispatch:
- preDispatch
- call action
- postDispatch preDispatch:
- rewrite (+forward)
- cookie + session start
- load area (config, events, design, translate)
- events (3) postDispatch:
- events (3)
- app addEventArea
- translator init (setConfig: locale code, store id, design package name (exceptions: regexp user agent -> custom package, skin, template, layout, default)) initDesign (only front):
- design = design package singleton
- design config (/app/design/{area}/{package}/{theme}/etc/theme.xml -> tree {area}/{package}/{theme}/...) ! theme.xml: layout/updates here
- design fallback (inheritance: parent = package/theme, legacy: theme default, 'default')
- design_change (store, date): package/theme -> design package packageName, theme
getLayoutFilename, getTemplateFilename, getLocaleFilename -> getFilename
getFilename:
- getFallbackScheme - first no update, then parents
- _fallback(file, params, fallback themes)
- return found validateFile: getBaseDir.file
- or renderFilename base/default
storeConfig:
- design/package/name
- design/theme/default
translate_inline - allowed and active admin/front - only devAllowed
- update addHandle
- addActionLayoutHandles STORE_$code, THEME_$area_$package_$layout, $route_$controller_$action
action loadLayoutUpdates:
- event
- update load update load:
- loadCache
- merge[]
- saveCache fetchFileLayoutUpdates:
- getFileLayoutUpdatesXml (once) area, package, theme, store id - load all layout files
- event
- update files (config $area/layout/updates, theme layout/updates, local.xml) ! frontend/layout/updates/(convert_shopby module="Convert_Shopby" - to disable output)/file='convert/shopby.xml'
- append all layout files xmls merge[] handle: builds _updates array of all matched handle contents
- fetchPackageLayoutUpdates (handle)
- packageLayout handle
- fetchRecursiveUpdates + addUpdate handle text content
- packageLayout handle
- fetchDbLayoutUpdates (handle)
- getUpdateString handle from core_layout_update, core_layout_link by handle, store id, area, package, theme
- fetchRecursiveUpdates + addUpdate
action generateLayoutXml:
- event
- layout generateXml
- - ignore block and its references (but keep if acl isAllowed)
- setXml
action generateLayoutBlocks:
- events (2)
- layout generateBlocks (recursive)
- ignore marked
- generateBlock
- type, name -> createBlock, _prepareLayout, event
- insert before/after/append parent->child
- insert anonymous no suffix - _blocks[ANONYMOUS_0]->_blocks[root.child0], nameInLayout=root.child0, alias=root.child0, _children[root.child0]
- insert anonymous with suffix _blocks[ANONYMOUS_1]->_blocks[root.suffix], nameinLayout=root.suffix, alias=root.suffix, _children[root.suffix]
- insert name no suffix _blocks[head], alias=head, _children[head]
- insert name with suffix _blocks[footer.before], _children[footer_before]
- template, output
- generateAction <arg1_array> value0 value1 </arg1_array> true false false Translatable string _blocks[root].doSomething( array(key1 => value1, key2 => value2), // simple assoc helper(catalog/category)->getStoreCategories(true, false, false), // helper result json_decode(helper(module)->returnJson()) array(b => array(c => helper(core)->__(Translatable string))) );
layout xml: simplexml_element layout simplexml_element block name=root simplexml_element block name=head ... simplexml_element block name=core_profiler
_blocks[root] = block_object(type=page/html,nameInLayout=root,layout=obj,template=page/3columns.phtml,_children=[obj]) _blocks[head] = block_object(type=page/html_head,nameInLayout=head,layout=obj,parentBlock=obj,blockAlias=head) ... _output[root] = toHtml
- renderTitles - $this->_title('foo')->_title('bar') => head: foo / bar
- events (2)
- layout getOutput
- translate inline { {{original}} {{.}} {{.}} {{.*}} }
- response appendBody
send response events: Mage_Core_Controller_Varien_Front::dispatch - 2 events Mage_Core_Controller_Response_Http::sendResponse - event
items[type/name], type - js_css, skin_css, js, skin_js; rss, link_rel
addCss(styles.css) addJsCss(js-slider/slider.css) addItem(skin_css, lucky.css, 'whatsthis?', 'lt IE 7', 'feel_lucky') addItem(rss, http://feed.mikle.com/support/rss/, 'title=RSS') addLinkRel(http://non.skin/style.css, 'rel=stylesheet type=text/css')
_items["skin_css/styles.css"] = [type skin_css, name styles.css] _items["js_css/js-slider/slider.css"] = [type js_css, name js-slider/slider.css] _items["skin_css/lucky.css"] = [type skin_css, name lucky.css, params 'whatsthis?', if 'lt IE 7', cond 'feel_lucky'] _items["rss/http://feed.mikle.com/support/rss/"] = [type rss, name http://feed.mikle.com/support/rss/, params "title=RSS"] _items["link_rel/http://non.skin/style.css"] = [type link_rel, name http://non.skin/style.css, params "rel=stylesheet type=text/css"]
getCssJsHtml: lines['']['skin_css']['']['styles.css'] = 'styles.css' lines['']['js_css']['']['js-slider/slider.css'] = 'js-slider/slider.css' if $feel_lucky: lines['lt IE 7']['skin_css']['whatsthis?']['lucky.css'] = 'lucky.css' lines['']['other'][] = lines['']['other'][] =
prepare css (+merge), prepare js (+merge), prepare other _prepareStaticAndSkinElements:
- designPackage getSkinUrl or getFilename
- if merged, single file per group, else separate files
caching, parent/children, sort, child groups, messages block, surround frame, helpers (url, date, escape), inline translate
- set layout events (2)
- to html events (2) absolute path - Mage::getDir('design') -> core/config_options::getDir() -> getDesignDir()
model - define resource model when constructed resource model (entity) - define resource prefix (for read/write connections) / main table entity init(main table, id field). mymodule/maintable: resource prefix = mymodule, resource model = maintable e.g. connections = {resourcePrefix}{connection} = mymodule_read, mymodule_write collection - define model and resource model when constructed migrations: all resources with setup child -> module name -> module/resource_name/migrations...
install/upgrade/rollback/uninstall, php/sql install-{$version}, upgrade-{$version} {$connection->model}-install-{$version}, mysql4-upgrade-{$version}
class core/resource_setup utility methods:
- run (multi sql)
- getTable, tableExists
- getTableRow, deleteTableRow, updateTableRow
- setConfigData, deleteConfigData
- getIdxName, getFkName
core/resource_setup_query_modifier
Mage_Eav_Model_Entity_Setup
transactions tables - create, drop, describe, ... + batch columns - add, modify, drop, ... indeces foreign keys select - Varien_Db_Select inserts - 5 methods update, delete query, multiQuery fetch - 6 methods quote - 5 methods startSetup, endSetup dql cache - 6 methods prepare condition, value sql fragments - check, case, ifnull, concat, length, least, greatest, substring, std. deviation date format, add, sub, to/from unix timestamp getTableName generate name - index, fk enable/disable keys (indexing) selects by range insert/update/delete from select table checksum order rand for update primary key name decode binary create table from select drop trigger change autoincrement
- getModelName name or [name, suffix] + event
getResourceModel(convert_acme/flat) => resourceModel = global/models/convert_acme/resourceModel, getModel(resourceModel/flat) load:
- beforeLoad + events (2): generic, tailored (_eventPrefix, _getEventData, _eventObject)
- resource load
- afterLoad + events (2) generic, tailored
- set orig data, mark no changes save:
- delete if isDeleted
- check _hasModelChanged
- resource.begin transaction
- before save (can _dataSaveAllowed), set is new + events (2)
model_save_before
,{prefix}_save_before
- resource save
- after save - cleanModelCache + events (2)
- resource commit
- afterCommitCallback + events (2)
model_save_commit_after
,{prefix}_save_commit_after
- commit, rollback
- commit callbacks
- format date
- serialize/unserialize field
- get read/write adapter
- _prepareDataForTable -- keeps only data for columns that exist in table
-
- serializableFields, uniqueFields, ...
- getTable name or [name, suffix], name can be full model/entity or short entity (current model)
- getMainTable
- load:
- getLoadSelect, fetchRow, setData
- unserializeFields
- afterLoad
- save:
- delete if isDeleted
- serialize fields
- before save
- check unique
- detect mode update/insert (model getId() and optionally isObjectNew())
- have id, not new - normally update, but if no auto-increment decide insert/update using select id exists (explicit id)
- no id or is new - insert, set last insert id
- unserialize
- after save
_getLoadSelect
: getReadAdapter()->select() = Varien_Db_Adapter_Pdo_Mysql->select() = new Varien_Db_Select
connection instanceof Zend_Db_Adapter_Abstract
Zend_Db_Adapter_Abstract::select() - new Zend_Db_Select
eav/resource_entity_abstract - eav
core/resource_transaction: - order, invoice, creditmemo, shipment, quote submitOrder
- addObject, addObject, ... - models with getResource method
- addCommitCallbacksave, addCommitCallback, ...
- save - save[], callback, commit
- delete - delete[], callback, commit Varien_Db_Adapter_Interface: insertMultiple, insertArray (model->getResource() | Mage::getSingleton('core/resource'))->getConnection('default_write')
- ! add/get filter
- curPage, pageSize
- ! count - loaded items, getSize - all collection, get last page number count($collection) = load() + count(items) $collection->getSize() = smart COUNT() SQL
- get items/first/last/by id
- get column values/items by column
- add item, remove item by key
- get all ids
- load, load data, clear
- walk, each
- set data to all
- set order
- set item object class, get new empty item
- to xml/array/option array/option hash
- cache key/tags/lifetime
- distinct
- render filters/orders/limit
addFilter('name', 'Roman', 'and') # noop setOrder('position', 'asc')
- connection (Zend_Db_Adapter_Abstract)
- select (Zend_Db_Select), get select sql,
- add bind param
- id field name
- init cache (external cacher) - prefix, hash select
- ! get cur page - loads collection! fixes out of bounds
- ! get select count sql - used by getSize
- order
- ! add field to filter
- fetch item
- get/reset data
- add filter to map
- print log query
addFilter('name', 'Roman', 'and') # select->where addFilter('name', 'Roman', 'or') # select->orWhere addFilter('', 'stores.id is not null', 'string') # value as is addFilter('name', ['eq' => 'Roman'], 'public')
addFieldToFilter('name', 'Roman') ! addFieldToFilter('name', ['eq' => 'Roman']) -- one of: eq, neq, like, nlike, in, nin, is, notnull, null, gt, lt, gteq, ... ! addFieldToFilter('name', [ -- nested OR ['eq' => 'Roman'], // OR ['null' => 'anyvalue'], ]) addFieldToFilter(['first_name', 'email', 'roman_friend'], ['Roman', '[email protected]', ['notnull' => 'anyvalue']]) # OR
see \Varien_Db_Adapter_Pdo_Mysql::prepareSqlCondition
- from,to
- one of: eq, neq, like, nlike, in, nin, is, notnull, null, gt, lt, gteq, lteq, finset, regexp, from, to, seq, sneq
- nested OR(queries[])
addOrder('position', 'asc') === setOrder unshiftOrder('position', 'asc')
- model, resource model
- main table, get table
- events (2 + 2)
core_collection_abstract_load_before
,core_collection_abstract_load_after
only if _eventPrefix and _eventObject defined:{prefix}_load_before
,{prefix}_load_after
- ! add/remove field/expression field to select
- ! join (table, cond, cols). join('some/table', 'a = b', '*'); join(['alias' => 'some/table'], 'a = b', ['alias' => 'col'])
- reset items data changed
- save -- each item[].save
- mage app cache -- initCache must be called to work
- initial fields to select (id)
addFieldToSelect('count(entity_id)', 'cnt') addExpressionFieldToSelect('cnt', 'COUNT({{entity_id}})', 'entity_id')
initCache --> _cacheConf:
- ['prefix'] -- influences cache id
- ['tags']
- ['object'] == _getCacheInstance getData:
- _renderFilters, _renderOrders, _renderLimit
- _prepareSelect -- stupid
- ! _fetchAll: -- try to load cache, if miss - load and save to cache
- _canUseCache -- enabled in admin AND
_cacheConf
configured - _loadCache -- use _getSelectCacheId --
_cacheConf['prefix'] + md5(select)
- ...
- _saveCache -- tags =
'mage', 'collection_data' + _cacheConf[tags]
-- describe dependencies to refresh your collection, e.g. category model
- _canUseCache -- enabled in admin AND
- _afterLoadData
- addAttributeToSelect -- + joinType
- addAttributeToFilter
- addAttributeToSort
- addEntityTypeToSelect -- dummy?
- addStaticField
- addExpressionAttributeToSelect
- groupByAttribute
- joinAttribute
- joinField -- regular table
- joinTable
- save / delete
- importFromArray / exportToArray
- select batch insert, batch split, float fix
- debug, DQL cache, hostinfo, check DDL in transaction, query hook, FK_, delete/update/insert/create from select, dates
- Varien_DB_Adapter_Interface
- Varien_Db_Select
- dsn, exec
- connection, profiler, crud, select, quote.
- Zend_Db_Select
- delete/update/insert from select (adapter)
- order rand (adapter)
- exists
- reset left join
- distinct
- from('name', '*'), from(['alias' => 'table'], ['alias' => 'col'])
- union([select1, select2])
- join/joinInner, joinLeft, joinRight,
- joinFull = left + right, nulls missing
- joinCross = cartesian product, no condition
- joinNatural = same name in both columns, no condition
- where, orWhere
- group
- having, orHaving
- limit, limitPage
- getPart, reset(part)
- order
- query(mode, [bind])
resource model must provide entity type id ✔, id field ✔ attributes (source/backend/frontend model), system, user-defined
resource model, entity, is different - Mage_Core_Model_Resource_Abstract -> Mage_Eav_Model_Entity_Abstract (✔)
- load, save, delete methods entity collection - Varien_Data_Collection -> Varien_Data_Collection_Db -> Mage_Eav_Model_Entity_Collection_Abstract (✔)
- join attribute tables setup class - Mage_Core_Model_Resource_Setup -> Mage_Eav_Model_Entity_Setup (✔)
- add/update attribute
- modify attribute values
- set/get type
- get/add attribute
- unset attributes
- get attributes by code/id/table
- is attribute static
- entity table
- entity id field
- validate
- set new increment id
- check attribute unique value
- is partial save/load
- save attribute
- get default attributes
- add attribute to filter == add field to filter
- add/remove attribute to select
- add attribute to sort
- add entity type to select
- add static field
- add expression attribute to select
- group by attribute
- join attribute
- join field
- join table
- set page
- import from array/export to array
- get loaded ids
Zend_Cache_Core:
- clean
- remove
- load
- test
- ...
Zend_Cache_Backend_Interface:
- load - single entry
- test - single entry
- save - single entry
- remove - single key
- clean - all/by tag
flush magento cache:
- flushSystem - by tag MAGE - added when saving anything in Magento
flush cache storage:
- flushAll -> all
_isAllowed - controllers, some blocks (custom check) Mage_Adminhtml_Controller_Action::preDispatch:
- check form key on post, secret key
- check _isAllowed
- set default session locale event adminhtml:controller_action_predispatch -> admin/observer::actionPreDispatchAdmin:
- check open actions
- handle post login -> admin/session::login -> admin/user::login -> admin/resource_acl::loadAcl -> redirect on success
- redirect to adminhtml/index/login otherwise
admin/resource_acl::loadAcl acl = admin/acl == Zend_Acl
- admin/config::loadAclResources into admin/acl resources -> read all adminhtml, add recursively all children, prepending parent/ path, completes each resource with children Zend_Acl::add('all', parent=null)
- load roles from admin_role -> into admin/acl roles
- load rules! e.g. acl->_rules->byResourceId->[admin/system/config/sales]->byRoleId->[G31]->allPrivileges->type=DENY
admin/resource_acl -> have resources loaded from config, load roles from db, load rules from db
admin_role - union (roles - parent_id=0, user_id=0; assigned users - parent_id=role, user_id=user) admin_rule join admin_assert - role-access to each resource (allow/deny)
layouts: main.xml, report.xml, catalog.xml, customer.xml, promo.xml
...- buttons (adminhtml/widget_button), area (footer)
- header text
- event
adminhtml_widget_container_html_before
- template widget/grid/container.phtml [header][buttons] // [grid]
- add button (createUrl), back button
- 'grid' auto child
- header css class, header width
- id
- current url
- creates button html
- global icon
- data properties: sortable, var_name_filter, grid_header, use_ajax, additional_javascript, collapsed, js: row_click_callback, checkbox_check_callback, row_init_callback
- _extensible properties: {headers,filter,pager}Visibility, countTotals, countSubTotals, {varName,default}{Limit,Page,Sort,Dir,Filter}, massActionIdField (id, query_id)
- extensibleMethods: addColumn, addExportType (url,label), addRssList (url,label), setTotals
- multipleRows: row item->children
- template widget/grid.phtml
- columns
- limit, page, sort, dir, dir
- totals, subtotals, rss
- export types: csv, xml, excel
- main buttons: reset filter, search
- prepare mass action
- collection
- ajax
- grid header (concrete)
- jsObjectName: {id}JsObject = varienGrid
- filter: prepareCollection: prepare pager, set order, set filter values
- use_select_all, use_ajax, error_text, form_field_name
- addItem: items-actions (label, url, selected, additional, confirm), additional action block (create block by name or from config form-like) -- set new status, assign to customer group
- get javascript: {jsObjectName} = new varienGridMassaction
- selected json: params(internal_massaction)=1,2,3,..., items, gridIds, useAjax,
- data properties: type, header, index, renderer, filter, width, align, html_decorators (nobr), frame_callback, editable, sortable, dir, css_class, header_html_property, totals_label, filter_index, filter_condition_callback
- get renderer - class or by type adminhtml/widget_grid_column_renderer_* by type (text, date, datetime, ...)
- get filter - false, class or by type
- column filter or by type adminhtml/widget_grid_column_filter_*
- css: align, column class, editable
- get row field + decorators, frame callback?
- get row export field
- column getter
- render header
- render + column editable input
- render export
- render property (width etc.)
- render header
- get condition - for select by current value --> collection.addFieldToFilter(column, condition)
- _toHtml
- get escaped value
- template widget/form/container.phtml
- 'form' auto child
- data: form_action_url
- prop: _formInitScripts, _formScripts, _objectId (id, sitemap_id) for delete button and url
- buttons header, footer: back, reset, delete, save
- form action url
- template widget/form.phtml [form_html][form_after]
- data: dest_element_id, show_global_icon
- element types
- Form.set{Element,Fieldset,FieldsetElement}Renderer
- set fieldset (attributes, to fieldset) - to mass add dynamic eav attributes (customer, category)
- extensions:
- prepare form, set form
- init form values
- get additional element types
- get additional element html
- data: attribute_code, is_visible
- source: all options
- frontend: input type, input renderer class, label, class
Varien_Data_Form:
- set readonly
- add type (type, class name)
- add field (id, type, config, after) - creates from types by name
- add fieldset
- add column?
- add element - simply adds instance
- toHtml
elements: date, label, reset, textarea, multiselect, submit, radios, select, radio, multiline, text, checkbox, checkboxes, note, file, link, image, imagefile, hidden, button, password, column, gallery, time
- get sections
- get tabs
- loads modules' system.xml
- apply extends
- event
adminhtml_init_system_config
- system.xml/sections, system.xml/tabs
- acl: system/config/{section}
- default - first allowed section
<frontend_type> - form field types + registered with fieldset in adminhtml system_config_form:: _getAdditionalElementTypes:
- export
- import
- allowspecific
- image
- file
blocks: left - adminhtml/system_config_tabs content - adminhtml/system_config_edit .form - adminhtml/system_config_form
adminhtml/config_data:
virtual downloadable = virtual + link simple = virtual + weight grouped = add 3 products with 1 click. No variations, separate options, no total qty, no group price, no sku control, separate items in cart bundle = add 1 composite product, consisting of many selected variations. options independent. sku dynamic/fixed, weight dynamic/fixed, price dynamic/fixed. each qty x total qty configurable = add 1 composite product, consisting of 1 selected variation. all options are connected
custom product - create? index? store custom data? existing:
- calculation?
- parent-child?
- shared tables and specific?
model/product.getTypeInstance (singleton|ornot).model/product_type.factory
MyModule Custom Product Type mymodule/product_type_mytype mymodule/product_type_mytype_price 1 1 1 0 mymodule/catalog_product_price_mytype mymodule/catalogindex_data_mytype- type instance.isSalable
- enabled and data(is_salable) <-- by inventory>
- not composite. why?
- product.isAvailable = above + ability to skip in admin when create/edit order?
- product.isSalable = above + 2
events
- product.getIsSalable -- stupid, doesn't check enabled
- type instance.getIsSalable
- data(is_salable) <-- by inventory>
- fallback above
-- sturdy data: .type_id = 'mytype' .has_options = 1 -- flimsy data: .category_id (product.setCategoryId(something); product.getCategory() -- loads as defined) .category_ids .website_ids .store_ids .has_options .product_options .type_has_options .required_options (bool) .cart_qty .parent_product_id .stick_within_parent .options_validation_fail events:
-
catalog_model_product_duplicate
-
catalog_product_is_salable_before
-
catalog_product_is_salable_after
-
catalog_product_delete_after_done
-
catalog_product_validate_before
-
catalog_product_validate_after
-
is super
-factory - product_type -price factory
.setConfig(from_file) .setProduct() - not always! only if not singleton .beforeSave() .save() -- after save .getSetAttributes() .isSalable() .isComposite() via _isComposite .hasOptions() .hasRequiredOptions() .canConfigure() via _canConfigure .canUseQtyDecimals() via config .getSku() .getWeight() .isVirtual() .processBuyRequest() -- convert buyRequest back to options to configure!
- when configure .checkProductConfiguration() -- return errors when configuring added product. not to extend
- when configure .assignProductToOption()
- quote_item_collection._afterLoad > _assignProducts > process each item option[] ._prepareProduct() -- buy request
- prepareForCartAdvanced -- return cart candidates .checkProductBuyState() -- check product has all required options. read product.getCustomOption, @throws
- quote_item.checkData() -- checks if product in cart is healthy .getProductsToPurchaseByReqGroups
- cataloginventory/stock_item.getStockQty .getOrderOptions() .getSearchableData() - delegate to product_option by default .getRelationInfo() - [table, parent_field_name, child_field_name, where] - used in:
- fulltext search to get attributes of product+children
- product flat indexer - insert children into flat .getChildrenIds()
- stock per website, when changing parent inventory to IN_STOCK, at least one child must be IN_STOCK, or will not set
- when apply catalog rule, mark active quotes with children => trigger_recollect=1 .getParentIdsByChild()
- indexer
- inventory stock change
checkout_cart_configure helper catalog/product_view.prepareAndRender -- pass buyRequests helper catalog/product.prepareProductOptions: - product.processBuyRequest - type.processBuyRequest -- add specific options to buy request - type.checkProductConfiguration - type.prepareForCart -> prepareForCartAdvanced - _prepareProduct - product.setPreconfiguredValues (some options, qty)
product.getPreconfiguredValues
- from type.processBuyRequest -- convert buyRequest back to options!
- get price
- get base price -- min: group price, tier price, special price
- get final price = base price + event 'catalog_product_get_final_price' + options prices -- regular price on product view + price of added product accounting for qty
- unleass there's shortcut -- product.setCalculatedProductPrice() by
- calculatePrice -- unused?
- get child final price -- only for bundle
- total: address subtotal
- _get group price -- price by customer group
- _get tier price -- price by qty
- calculate price
- catalogindex/data_abstract.getFinalPrice() - special price (from-to) vs catalogrule price
- _calculate special price
- addStoreFilter
- addWebsiteFilter
- addCategoryFilter
- setVisibility
- addPriceDataFieldFilter (%s < %s, [final_price, price])
- get price fixed -> as defined -- product.getPriceType = Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_PARENT dynamic - 0 -- product.getPriceType = Mage_Catalog_Model_Product_Type_Abstract::CALCULATE_CHILD
- get final price base + options + selections
- get final price - same + selection extra
is_delete is_require .saveOptions()
- set option, set product
- validateUserValue(buy_options) -- check required, max chars, dropdown single selection, file max width/height, parse date
- prepareForCart -- format selection for storing: concatenated ids for dropdown, format date
product_option_value: (e.g. pepper, mustard, ketchup)
- qty
- product -- child for configurable
- related_products
- super_product_config[product_id] -- parent
- super_product_config[product_type]
- bundle: []super_group_{id} -- qty, show only if item salable
- options
- reset_count, id
- info_buyRequest - serialized
- product_type (of parent + parent product id)
- option_ids -- keys of product_type._prepareOptions
- option_{id} = {value} -- values of product_type._prepareOptions
- product_qty_{product_id}
- attributes = serialize attributes
- product_qty_{simple_id} = 1
- simple_product = object
- option_ids, {ids,ids,..}
- option_{id}, {value}, option_{id}, {value}, ...
- option_{id}, {value}, option_{id}, {value}, ... -- copy product options from parent to simple
- parent_product_id = {parent_id}
- bundle_option_id
- bundle_selection_ids
- bundle_option_ids
- bundle_selection_attributes
- bundle_identity
- can configure, is saleable --> add to cart btn/view details/out of stock
- has required options --> build link to product
- hasOptions - options price jsonConfig
- getSetAttributes - show additional data (visible on front)
- is salable - display availability, show buy btn
- is salable and has options - display product options
- is composite - cataloginventory.getStockQty - as is or sum
- getProductsToPurchaseByReqGroups - cataloginventory.getStockQty - only for composite
quote.addProduct()
quote.addProductAdvanced() -- full process mode; create quote_items, add qty
type.prepareForCartAdvanced() = type._prepareProduct()
type._prepareOptions () -- return options [option_id] = option
event sales_quote_product_add_after
event checkout_cart_product_add_after
collect totals, collect rates
cart.save
event checkout_cart_add_product_complete
- last added product id
- quote id
- cart was updated
- no cart redirect
-links_purchased_separately -> has options, has required options, link selection required when prepare product -price model.get final price: base price + . + options
db:
- downloadable/link
- downloadable/sample
- downloadable/file
options:
- is_downloadable = true
- real_product_type = downloadable
- price model.get final price
- composite
- associated products
- product form:
- []super_group_{id} -- qty, show only if item salable
- custom options:
- attributes
- configurable attributes (table product_super_attribute_id)
- custom options:
- bundle_selection_ids
- selection_qty_{selection_id}
- entity_id, website_id, customer_group_id, tax_class_id --- base prices w/o options
- price - as entered in admin
- final_price - as a group member, my base price is this --- display in catalog grid --- if I buy just a bit, I get my group price + options
- group_price - (price,final_price) + min options --- optional
- min_price - (price,final_price) + min options
- max_price - (price,final_price) + max options --- if I'll buy a lot, I'll get tier_price
- tier_price - best I can get + min options --- optional
getProductAttributes = getAttributesUsedInProductListing = resource.getAttributesUsedInListing:
- crosssell
resource catalog/product_indexer_price.getTypeIndexers catalog/product_type.getTypesByPriority indexer:
-
set type id
-
set is compisite
cataloginventory_stock cataloginventory/indexer_stock ┠catalog_product_attribute catalog/product_indexer_eav ┗catalog_product_price catalog/product_indexer_price catalog_url catalog/indexer_url catalog_category_product catalog/category_indexer_product catalogsearch_fulltext catalogsearch/indexer_fulltext
1 column: mode manual and missed auto update event 2 column: has lost index events
- logEvent - create index_event, process[].register,matchEvent -> indexer.register,matchEvent,_registerEvent e.g. before delete store, stock item after save, before delete eav attribute
- processEntityAction - logEvent + 2 events + indexEvent (process[].safeProcessEvent,matchEvent,processEvent,matchEvent -> indexer.processEvent,matchEvent,_processEvent)
- indexEvents - process unprocessed. 2 events + process[].indexEvents, indexer.matchEntityAndType, process._processEventsCollection,processEvent -> indexer.processEvent,matchEvent,_processEvent i.e. process existing unprocessed row events collection
processEntityAction - log + register + process, full suite logEvent - only insert, no register/process indexEvents - only process new, created by log, e.g. recursively created events after indexes worked once. call at the end
indexer_abstract:
- _registerEvent - write data to event object
- _processEvent - do actual work, delegate to resource
global: end_index_events_* - index/indexer.indexEvents end_process_event_* adminhtml: after_reindex_process_*
(index/indexer)->logEvent(entity, entityType = 'catalog_product', eventType = 'delete') save row in index_event registerEvent index/index -> []indexer(price,url,tags,...).register abstract_indexer[].register matchEvent, _registerEvent... PriceProcessIndexer.register matchEvent _registerEvent -- specific to price process indexer write some generic metadata to event object productTypePriceIndexer[].registerEvent()
(index/indexer)->processEntityAction(entity, entityType = 'catalog_product', eventType = 'save') every matched indexers - register: addProcessId(), addNewData(). each process_id => insert index_process_event, status every matched indexers - safeProcessEvent: lock file var/index_process_{$id}.lock processEvent
process: index/process (index_process) - index management page - indexer_code, status, mode, started at, ended at process.getIndexer(indexer_code)
<global>
<index>
<indexer>
<myindexercode>
<model></model> <!-- Mage_Index_Model_Indexer_Abstract -->
- name, description, is visible
- _matchedEntities [entity, actions[]]
- reindexAll -> resource.reindexAll
- register
- registerEvent
priceIndexer._registerEvent - change process status or write some ids, let product type price indexers take part (mostly useless) priceIndexer._processEvent - reindexProductIds() or reindexAll() or callEventHandler() # TODO - research product_type_price_indexers role here
catalog/product_indexer_price mymodule/proPimcore duct_indexer_pricemodel catalog/product_indexer_price -> resource catalog/product_indexer_price _registerEvent: write some data to event depending on changes:
- product_type_id={id}, reindex_price=1 -- if (options|website|relations|prices) changed
- reindex_price_parent_ids -- if deleted a product and parents found in
catalog/product_relation
- reindex_price_product_ids={mass_ids}
- id
- catalog_product_price_reindex_all=true
for each product type, product_type_<price_indexer>.registerEvent --- each type indexer add own metadata. why call all?
_processEvent:
resource.reindexProductIds
- save product? catalog_product_save_commit_after -> catalogrule observer applyAllRulesToProduct -> processEntityAction(product, reindex_price) resource.reindexAll
- import products: convert_adapter_product.finish -> processEntityAction(import, save)
dynamic resource.{entity}_{type}() or resource.{type}
catalogProductSave() -- if (options|website|relations|prices) changed
! productTypeIndexer.reindexEntity(product_id) -- parent and all children
collect
product_price_indexer_final_(idx|tmp)
in multiple steps move toproduct_price_indexer_(idx|tmp)
copy index data by product_id to main tableproduct_index_price
catalogProductDelete() -- if deleted a product and parents found incatalog/product_relation
! productTypeIndexer.reindexEntity(parents_ids) catalogProductMassAction() if affected > 30% of all products, reindex all -> type_indexer[].reindexAll else type_indexer[].reindexEntity(ids)
- registerEvent -- for logEvent and processEntityAction
- reindexEntity(ids) -- one or multiple
- reindexAll
model catalog/product_indexer_flat -> singleton catalog/product_flat_indexer -> resource catalog/product_flat_indexer on product save, mass action on eav attribute save, delete on store save, delete on store group save on import products
eav attributes shown in flat category:
- system attributes:
- hardcoded - 'status', 'required_options', 'tax_class_id', 'weight'
- from **(
global/catalog/product/flat/attribute_nodes
) =>frontend/product/collection/attributes
: -- why reference node path in other node???? <url_key/> <special_price/> <special_from_date/> <special_to_date/> <short_description/> <small_image/> <image_label/> <thumbnail_label/> <small_image_label/> <tax_class_id/> <news_from_date/> <news_to_date/> <created_at/> <updated_at/>
used_for_sort_by
used_in_product_listing
is_used_for_promo_rules
is_filterable
by default DISABLED inglobal/catalog/product/flat/add_filterable_attributes
mage_catalog_model_resource_product_flat_indexer:
- updateStaticAttributes
- getAllAttributes -- backend_type == 'static'
- updateEavAttributes:
- getAllAttributes -- backend_type <> 'static'
- updateAttribute
- isAvailable - enabled and indexer not running and no lock file
- isBuilt - table not empty
-
getTreeModel, getTreeModelInstance
-
move - 2 events before, 2 events after, event
category_move
, indexer.processEntityAction, clean cache by tagcatalog_category_tree_move_before
,catalog_category_tree_move_after
catalog_category_move_before
,catalog_category_move_after
-
getProductCollection
-
getStoreIds
-
getPathIds, getUrlPath, getLevel
-
getParentId, getParentIds
-
getParentCategory
-
getParentDesignCategory - Category
-
isInRootCategoryList
-
getAllChildren - = by default joined ids ("2,3,4...") + parent id, recursive, only active
-
getChildren - joined ids ("2,3,4..."), only immediate, only active
-
hasChildren - recursive, only active
-
getAnchorsAbove - array of ids - parents that are anchor
-
getProductCount - only directly assigned, all
-
getCategories - tree|collection|Category[]. recursive, only active, only include_in_menu, rewrites joined
-
getParentCategories - Category[], only active
-
getChildrenCategories - collection|Category[], only active, only immediate, include_in_menu*
-
getChildrenCategoriesWithInactive - collection|Category[], all immediate, include_in_menu*
count($collection) = load() + count(items) $collection->getSize() = smart COUNT() SQL count($array) count(tree) = count(nodes) -- immediate or recursive?
- checkId
- getAllChildren - array of ids+my id
- getChildren* - array of ids, by default recursive, only active
- getChildrenAmount - by default only active (or only inactive), recursive
- getCategories* - tree|collection|array
- getParentCategories*
- getChildrenCategories
- getParentDesignCategory
- getChildrenCategoriesWithInactive*
- changeParent
- findWhereAttributeIs
- getChildren* - only active, by default recusive
- getChildrenCategoriesWithInactive* - returns Category collection
- getCategories* - by default node_tree (or collection) + parent id, recursive, by default (active+include_in_menu), by default with rewrites. node tree|collection
- Varien_Data_Tree > Varien_Data_Tree_Dbp > resource catalog/category_tree
- Varien_Data_Tree_Node_Collection - actual node storage
- Varien_Data_Tree_Node - holds tree, parent and children collection
- - attributes to load
- getChildrenCategories* - collection, only immediate, only active, with rewrites
- verifyIds
- getAnchorsAbove
- getChildren* - by default active, by default recusive
- getChildrenCategoriesWithInactive* - returns Category[]
- getCategories* - resursive, by default active, only include_in_menu, rewrites always joined. array|collection
- categories can be excluded by event 'catalog_category_tree_init_inactive_category_ids'
- attributes can be added to select in event 'catalog_category_flat_loadnodes_before'
- getChildrenCategories* - Category[], only immediate, only active, only include_in_menu, with rewrites
invalidate cache -- by default block_html, can add more in <related_cache_types>, e.g. amasty collections data
model.applyAll - full rework, recreate matching products, reindex rule prices, reindex all prices, clear cache resource.applyAllRules - only reindex rule prices
- product_type_indexer.prepareFinalPrice "prepare_catalog_product_price_index_table"
- alter configurable price "catalog_product_type_configurable_price"
- before save product - mark which rules is already matched. why?
- after save product - model.applyAllRulesToProduct -- add/delete to matched, reindex
- resource.applyToProduct
- resource.applyAllRules
- invalidateCache
- product import before finish - add/delete to matched, only imported
- product import after - resource.applyAllRules - reindex all catalogrule prices
- after delete catalog attribute - remove from conditions, model.applyAll if found (full rework)
- after saving attribute, uncheckign 'use in promo rules' - remove from conditions, model.applyAll if found (full rework)
- catalogrule_product -- matched products to rules, used for indexing prices
- catalogrule_product_price -- joined when indexing final price ? catalogrule_group_website
- for every rule, call resource.updateRuleProductData(rule) -- update matched products
- clean all matched products by rule
- for each store, insert matched products by conditions
- resource.applyAllRules() -- reindex catalogrule prices for date period
- invalidateCache
- price indexer.reindexAll -- our event listener will join catalogrule prices table
ancestor for:
- catalogrule
- salesrule
- enterprise targetrule
- enterprise reminder
- enterprise customersegment methods:
- {get,set}Conditions
- {get,set}Actions
- getConditionsInstance
- getActionsInstance
- loadPost
- validate
- vaildateData
- getProductFlatSelect - product flat select + catalog_product + conditions SQL
product save/delete/massaction attribute save import products
only indexable attributes:
- filterable (layer), filterable in search (layer), visible in advanced search
- only select (int,select)/multiselect (varchar,multiselect)/decimal types, but not price
catalog_product_index_eav --- product-attribute-value_id. when filtering by manufacturer, filter products where attribute = manufactuerer and value = requested catalog_product_index_eav_decimal
eav_attribute_option (dropdown ordered ids by attribute) eav_attribute_option_value (dropdown text values) catalog_product_eav_indexer_(idx|tmp)
layer attribute.applyFilterToCollection -- join catalog_product_index_eav same for decimal
tax class (product/customer) tax_caltulation_rule - nothing interesting tax_calculation_rate - tax_calculation -- rate, rule, customer tax, product tax
so, I know customer class (by customer group) and product class, I only to choose rule and rate. rules are mostly dumb, so rate decides! rate - location and rate!
tax can be based on: billing/shipping/origin/default country,region,postcode
- Billing
- Shipping
- Origin (Shipping settings - Origin - Country,Region,Postcode)
- Default (Tax - Default Tax Destination Calculation - Country,Region,Postcode) -- cannot be explicitly chose, fallback when address not available and customer default emtpy
getRateInfo
- select matching filtering by customer class, product class and location
- {value, process}
- rate value = complex percent of all matched (unless 'calculate_subtotal' flag) --> product.tax_percent
- calculation process = array of logged calculation steps --> product.applied_rates
cataloginvetonry_stock - dummy 1 cataloginventory_stock_item - workhorse, contents of inventory tab on product edit cataloginventory_stock_status - index table, product-website-qty-status. resource indexer.reindexEntity() cataloginventory_stock_status_indexer_idx - index table, same. resource indexer.reindexAll(). same as above. why?
influence catalog listing, product view page? decrement?
product block.displayProductStockStatus() - event catalog_block_product_status_display
- from global system config
if not manage stock, always is in stock
is_in_stock - what admin entered stock_status = is_in_stock = is salable
stockItem.assignProduct
- assign product.stock_item, product.is_in_stock
- stock_status.assignProduct
- resource stock_status.getProductStatus
- product.is_salable = read cataloginventory_stock_status.stock_status. why not just stock_item.is_in_stock?
stock_status.addStockStatusToProducts ! product.stock_item may be mocked = Object.is_in_stock = is_salable = stock_status
- check is_in_stock (and parent if exists), add validation error to quote and item
- check all options qty (e.g. configurable, bundle), stock_item.checkQtyIncrements, validate qty increments, add error to quote and item
- check min sale qty by customer group
- check max qty
- check backorders
- subtractQuoteInventory (once)
- check qty or throw error
- substract qty (unless disabled in config)
- revert quote inventory
- reindexQuoteInventory
- affected stock
- affected price
(once)
- subtractQuoteInventory (once)
- check qty or throw error
- substract qty (unless disabled in config)
- reindexQuoteInventory
- affected stock
- affected price
- add qty to stock
- set is_in_stock (if config enabled)
- if config auto_return_refund, add qty back to items
- updateSetOutOfStock
- updateSetInStock
- updateLowStockDate
/checkout/cart/add cart model.add to cart (request) quote.prepare add to cart advanced protuct type.prepare add to cart advanced - add custom options
- /add
- params: qty, product, related_product, super_product_config
- cart.add product
- check min sale qty
- quote.addProduct = quote.addProductAdvanced
- event
checkout_cart_product_add_after
- cart.add product by ids (related)
- quote.addProduct
- event
checkout_cart_add_product_complete
- /addgroup -- reorder from sidebar "Last Ordered Items" -- from last order, up to 5 random (hardcoded)
- /delete
- /configure
- /updateItemOptions
- /updatePost
- /estimatePost
- /estimateUpdatePost
- /couponPost
- /ajaxDelete
- /ajaxUpdate
- init - remove paymens, addresses, (and rates if empty)
- get items -> quote items collection
- get quote product ids -> quote items -> []product_id (including children)
- get product ids - same?
- add order item - add product from order item (reorder)
- add product
- check min sale qty
- quote.addProduct = quote.addProductAdvanced
- add product by ids -- add related in addition to main
- quote.addProduct
- suggest items qty - when calling 'update_qty' -- quote_item.suggestQty. fixes min/max/increments
- update items + events before/after
- update item -> quote.updateItem
- remove item -> quote.removeItem
- save,saveQuote + events before/after -- save addresses, collect shipping rates and totals
- truncate -> quote.removeAllItems()
- get summary qty -- depending on config checkout/cart_link/use_qty
- "Display item quantities" -> get items qty
- "Display number of items in cart" -> get items count
- get items count -> quote.getItemsCount
- get items qty -> quote.getItemsQty
- checkout method -- guest/register
- get items count, get items qty
- update item
- remove item
- add product = add product advanced
- product type instance! .prepareForCartAdvanced -- can return error as string. return products with qty, cart_qty (optional) and custom options (buy request, selected options, parent link)
- _prepareProduct -- return all products to be added, e.g. many for bundle, grouped, parent and child for configurable. Hook here for extra logic
- _prepareOptions -- can return error as string, [option_id] = "option specific value", e.g. date timestamp, text comment etc, ids for dropdown
- check and save parent when super_product given (e.g. configurable)
- wrap product custom options
- store options in product.customOptions
- process file queue -- added by _prepareOptions > option_type_file.prepareForCart > addFileQueue
- _prepareProduct -- return all products to be added, e.g. many for bundle, grouped, parent and child for configurable. Hook here for extra logic
- for each candidate:
- quote._addCatalogProduct -- find or create quote_item for product, move product.custom_options > quote_item.custom_options.
sales_quote_add_item
event - quote_item.addQty (product.cart_qty)
- event
sales_quote_product_add_after
- quote._addCatalogProduct -- find or create quote_item for product, move product.custom_options > quote_item.custom_options.
- product type instance! .prepareForCartAdvanced -- can return error as string. return products with qty, cart_qty (optional) and custom options (buy request, selected options, parent link)
-collectTotals
- []address.collect totals
- getTotals -- only assigned when total[].fetch via address.addTotal
- billing address.getTotals if virtual (retrievers order)
- shipping address.getTotals
- add/merge all addresses.getTotals
- sort again
! quote.trigger_recollect - schedule collectTotals and save on next load
- events
sales_quote_collect_totals_before
,sales_quote_collect_totals_after
- clear {base,}{subtotal,subtotal_with_discount,grand_total}
- process addresses[]
- clear {base,}{subtotal,grand_total}
- address.collectTotals
- quote.{base,}{subtotal,subtotal_with_discount,grand_total} += address.value
- validate grand_total not ∞
- calculate items_count, items_qty
- validate coupon_code -- must exist in address as well
-
collectTotals
-
set{base,}TotalAmount(code) -- 1) sets values on address 2) summed by grand_total
-
get{base,}TotalAmount(code) -- for reading by other totals
-
addTotalAmount
-
getAll{base,}TotalAmounts -- used by grand total to assign and display grand_total
-
getTotals -- beware! non-caching
-
addTotal(data) -- array wrapped into sales/quote_address_total and assigned address
- events
sales_quote_address_collect_totals_before
,sales_quote_address_collect_totals_after
- total collector[].collect(address)
collect, fetch cached in config cache
collectors: one,two... one,two... block/here <sort_order>0</sort_order> <nominal_totals> </nominal_totals> <order_invoice> </order_invoice>
retrievers (optional, without sort goes last): <totals_sort> 10
nominal (tricky) | + subtotal subtotal | + discount freeshipping | + shipping giftwrapping | + tax <taxes*> (if config tax/cart_display/grandtotal) tax_subtotal | weee msrp | + reward weee | + giftcardaccount shipping | + customerbalance tax shipping | + grand_total
discount | + nominal tax | msrp tax_giftwrapping | freeshipping grand total | tax_subtotal reward | tax_shipping giftcardaccount | + giftwrapping customerbalance | + tax_giftwrappingcollect --- strings all nominal totals.collect each nominal_total.collect fetch: if address has nominal items, just add 1 total with them
recurring_initial_fee recurring_trial_payment nominal_shipping nominal_discount nominal_tax_subtotal nominal_tax
properties to extend:
- _canSetAddressAmount -- can disable _setAmount (=address.setTotalAmount). disabled in nominal-shipping
- _canAddAmountToAddress -- can disable _addAmount. disabled in all nominal-*.
- _itemRowTotalKey interface:
- processConfigArray(config) -- can change sort order
- collect:
...
- address.setTotalAmount(code, amount) -- 1) assigns value - address.{code_amount} = amount 2) address._totalAmounts.{code} = amount
- address.addTotalAmount(code, amount)
- _setAmount = address.setTotalAmount if allowed
- _addAmount = address.addTotalAmount if allowed ...
- fetch:
- custom logic...
- address.addTotal(code, as, title, value, area) -- area = -1 -- include everywhere? -- as lets user total renderer of other guys
by checkout/cart_sidebar, checkout/cart_totals
- renderTotals
- []renderTotal(total)
- get total renderer:
- block {code}_total_renderer if present
- 'renderer' from total config
- checkout/total_default
- assign totals -- all totals
- assign (total, colspan, renderingArea) -- specific total
salesrule - workhorse, grid + basic params + actions + conditions salesrule_label - translations for stores salesrule_customer_group -- assign groups, has-many salesrule_website -- assign websites, has-many salesrule_coupon -- type (0-auto, 1-specific-auto generated and manual), expiration (not applicable when coupon type=no coupon) salesrule.coupon-type = specific, use_auto_generation, salesrule_coupon.type = 1 salesrule.coupon-type = specific, no generation , salesrule_coupon.type = 0 salesrule.coupon-type = auto, -------------------, salesrule_coupon.type = 0 coupon_aggregated coupon_aggregated_order -- by orders.created at coupon_aggregated_updated -- by orders.updated at
! sales totals discount -- not used salesRule totals discount - salesrule/quote_discount
salesrule_coupon_usage -- coupon usage by customer
salesrule_customer -- rule usage per customer
salesrule_product_attribute -- attributes used in actions/conditions
-- selected by quote_item collection after load -> assign products -> add to select (used in promo)
(sales_quote_config_get_product_attributes
in 'sales/quote_config'->getProductAttributes
! {quote_item,address,quote}.applied rule ids
- by_percent -- per item
- by_fixed -- per item
- cart_fixed -- quote.subtotal
- buy_x_get_y
-- start processing from parent items -- address: discount description, shipping discount amount, discount amount -- quote_item: discount amount, discount percent
- set discount_amount 0
- calculator.init -- load all sales rules matching website-customergroup-quotecoupon
- calculator.initTotals -- for future items calculation by rules of action cart_fixed
- process rules with action = cart_fixed
- if can process rule -- conditions and coupon ok
- check each quote_item[] -- rule.actions.validate
- store total matched price and count in _rulesItemTotals
- if can process rule -- conditions and coupon ok
- process rules with action = cart_fixed
- !extension chance - quote item.no_discount
- for parent and each child item[]
- event
sales_quote_address_discount_item
- ! calculator.process
- aggregate item discount -- negative address.discount amount -= item.discount amount
- event
- process weee discount (if enabled in weee config)
- ! calculator.process shipping amount
- negative address.discount amount -= address.shipping discount amount
- if coupon applicable, load coupon, check coupon.usage limit, coupon.usage per customer usage per coupon/customer (coupon_usage)
- check rule.uses per customer (rule_customer)
- rule.conditions.validate(address)
for each rule (until stop processing met):
- if can process rule -- coupon and global conditions are ok
- if rule.actions.validate(quote item)
- work ... depending on simple action ... item.discount percent, {base,}discount_amount
- event
salesrule_validator_process
! {quote_item,address,quote}.applied rule ids
add total {discount amount, discount description}
- calculator.init
- calculator.processFreeShipping (quote_item)
- for each rule:
- if can process rule -- coupon, conditions ok
- if rule.actions.validate(quote_item)
- if simple free shipping
- per item -- quote_item.free shipping = "Maximum Qty Discount is Applied To" ?: true
- whole address -- address.free shipping = true
- stop rule processing if set
- for each rule:
after place order:
- rule.times_used++
- rule_customer.times_used++
- coupon_usage.times_used++
sales_quote_config_get_product_attributes:
- add used attributes from table
convert quote to order:
- set order.coupon_rule_name
after delete attribute in admin:
- if attribute was used in promo, remove it from used rules
after unchecking 'used in promo':
- remove from rules
aggregate rules for previous day:
- createdat -- {day,store,order status,coupon_code, .... other boring stats .... }
- updatedat
global/payment/cc/types/{type}/code,name,order global/payment/groups
Recurring profiles (magento) - recurring payment. e.g. magazine subscription, phpstorm subscription :)
- only Paypal express
- show as nominal items
- order not created ---- how to manage?????????????????????
- purchased separately from other items
- not added to totals
- nominal grand total
- shipping only fixed, rate and free (methods with carrier._isFixed = true)
- method instance must implement Mage_Payment_Model_Recurring_Profile_MethodInterface
billing agreements
-
remember my payment, e.g. save credit cart on AliExpress
-
shown as separate method
-
manage on custom account page
-
charged directly via API without interaction
<title></title/>
onepage.saveShipping: addressForm = customer/form customer/form:
- form_code
- entity_type
- is_ajax_request
- prepareRequest(POST) - clone request, clear and reassign only POST
- getAttributes:
collection '{module}/form_attribute_collection'
- all attributes from customer_form_attribute
- by entity_type, form_code, but resource is customer/form_attribute
quote.checkout_method = Mage_Checkout_Model_Type_Onepage::METHOD_REGISTER quote.password_hash
-- show billing address + select shipping: same/different onepage.saveCheckoutMethod:
- quote.setCheckoutMethod -- register/guest
- selected existing customer address:
- billing[address_id]
- billing[...] other fields sent anyway! copied from default customer billing address
- selected new billing address:
- billing[address_id] still selected. wtf? not used?
- billing[...]
... create account - quote: guest:
- customer_email
- customer_is_guest
- customer_group_id register:
- quote.getCustomer - new customer
- quote.setCustomerId(true) - will be picked up in service_quote.submitOrder, if(customer) transaction.addObject(customer)
- customer.addAddress(billing.exportCustomerAddress)
- customer.addAddress(shipping.exportCustomerAddress)
- shipping.customerAddress = ... sames as ? ...
- password, password hash
- quote.setCustomer after submit quote -send email customer:
- {billing,shippingn}: new address or save in address book? customer.addAddress(quote address.exportCustomerAddress)
- .... is default {billing,shipping}
- quote.setCustomer
get payment html -> handle checkout_onepage_paymentmethod
- event
checkout_controller_onepage_save_shipping_method
-- save specific params from shipping step, e.g. giftmessage, giftwrapping - return block
checkout/onepage_payment_methods
instanceofpayment/form_container
(checkout/onepage/payment/methods.phtml)-
in _prepareLayout create form blocks for each method and set a child payment.method.{code} helper payment.getMethodFormBlock(method) create block _formBlockType block payment/form.setMethod
-
in template foreach block payment/form_container.getMethods
- helper payment.getStoreMethods -- return all, filter isAvailable
- config.active
- event
payment_method_is_active
- method.isApplicableToQuote -- check country, currency, min-max, zero total
- helper payment.getStoreMethods -- return all, filter isAvailable
-
print method title
- form_block.method_title if present
- else config.title
-
print method form - render form_block
- can change template from layout: ....... ... fill in form fields unser name payment[], submit to checkout/onepage/savePayment
-
- address.set payment method -- not saved in DB?
- quote.payment.importData -- quote.payment == just container class, holds specific method_instance
- event
sales_quote_payment_import_data_before
-- modify POST fields from method -- import specific values - quote.collectTotals
- !method.assignData -- write needed data from POST to quote.payment fields
- !method.validate -- by default check canUseForCountry - billing country. @throws
- quote payment._beforeSave: !method.prepareSave
- event
- quote.save -- saves payment info as well ! redirect opportunity ! quote.payment.getCheckoutRedirectUrl -> method.getCheckoutRedirectUrl
payment data in POST again quote_payment.importData again onepage.saveOrder:
- prepare quote depending on mode
- guest: quote.customer_email (from billing.email), customer_is_guest=1, customer_group_id=0, customer_id=0
- register: ... quote.password_hash (not erased?!), customer_id
- customer ...
- !service_quote.submitAll
- involve new customer if registration (send confirmation/success email, auto login)
- {+} checkout_session:
- lastQuoteId
- lastSuccessQuoteId
- {-} checkout_session.clearHelperData:
- last billing agreement id
- redirect_url
- last order id
- last real order id
- last recurring profile ids
- additional messages
- event
checkout_type_onepage_save_order_after
- queue new order email (unless ...)
- {+} checkout_session:
- last order id
- ! redirect_url -- method.getOrderPlaceRedirectUrl
- last real order id (increment)
- last billing agreement id*
- last recurring profile ids*
- event
checkout_submit_all_after
! redirect to checkout_session.redirect_url (optional)
submit nominal items:
- validate addresses, each recurring item[].submit and delete submit order:
- quote._validate -- addresses, shipping and payment method
- {shipping, billing} address._validate
- basic check - firstname, lastname, street, city, telephone postcode (unless optional), country_id, region_id (if required)
- event
customer_address_validation_after
- should_ignore_validation implicit data flag
- shipping_method and rates must be selected
- quote_payment.method must be selected
- {shipping, billing} address._validate
- quote.reserve order id
- order = addressToOrder -- shipping (unless virtual)
- toOrder
- new order
- use reserved increment, assign customer
- copy fieldset quote -> order
- event
sales_convert_quote_to_order
- copy fieldset address -> order
- event
sales_convert_quote_address_to_order
- toOrder
- new order.{billing,shipping} address -> convert
fieldset + event
sales_convert_quote_address_to_order_address
- new order.payment -> convert quote_payment
fieldset + event
sales_convert_quote_payment_to_order_payment
- assign any order data from _orderData --- by adminhtml when creating order - link to previous order
- order items -> convert
fieldset + event
sales_convert_quote_item_to_order_item
- event
checkout_type_onepage_save_order
-- not necessarily onepage though? - event
sales_model_service_quote_submit_before
- transaction.save
- customer
- quote
- order
- !order.place
- order.save -- join store_name, new increment_id if not reserved, total_item_count, protect_code
- inactivate quote
- event
sales_model_service_quote_submit_success
orsales_model_service_quote_submit_failure
- event
sales_model_service_quote_submit_after
- event
sales_order_place_before
- order_payment.place
- event
sales_order_payment_place_start
- payment.{amount_ordered, shipping_amount}
- method.set store
- !method.validate -- by default check country
- default order {STATE_NEW, STATUS from config or default assigned in admin} --- bad, not used in sales reports. should set somewhere
- !method.getConfigPaymentAction: -- by default config.payment_action, can process
- no action - leave {STATE_NEW, STATUS default}
- !method.isInitializeNeeded: -- some custom logic wanted
- !method.initialize() -- work and update order {state,status}
- initialize not needed: -- typical flow, admin selected action order/authorize/capture
- order(full amount): -- rare case
- !method.order(full amount) -- canOrder + custom logic -- write payment.transactions, additional info, transaction_id, parent_transaction_id
- implicit SkipOrderProcessing -> stop here
- implicit IsTransacitonPending -> {STATE_PAYMENT_REVIEW, status default}
- implicit IsTransacitonPending+IsFraudDetected -> {STATE_PAYMENT_REVIEW, STATUS_FRAUD}
- implicit PreparedMessage
- add order transaction (TxnType = order) implicit SkipTransactionCreation, TransactionId, IsTransactionClosed, ParentTransactionId, ShouldCloseParentTransaction
- order state or default {STATE_PROCESSING, status auto}
- authorize (full amount):
- payment.amount_authorized = full_amount
- !method.authorize(full amount) -- canAuthorize + custom logic. normally create session and reserve/authorize money
- implicit IsTransacitonPending -> {STATE_PAYMENT_REVIEW, status default}
- implicit IsFraudDetected -> {STATE_PAYMENT_REVIEW, STATUS_FRAUD}
- add order transaction (TxnType = authorization) implicit SkipTransactionCreation, TransactionId, IsTransactionClosed, ParentTransactionId, ShouldCloseParentTransaction
- order state or default {STATE_PROCESSING, status auto}
- capture:
- payment.amount_authorized = full_amount
- payment._invoice
- new invoice: convert order -> invoice, order items -> invoice items, invoice.collectTotals
- invoice.register:
- invoice item[].register -- order item.*_invoiced - qty, taxes, discounts, row
- can capture and online: invoice.capture()
- payment.capture()
- implicit payment ParentTransactionId,TransactionId
- generate transaction
- event
sales_order_payment_capture
- !method.fetchTransactionInfo (if existing transaction id)
- implicit invoice IsPaid, IsTransactionPending -- if not yet paid and not pending
- !method.capture -- custom logic, check canCapture -- use $payment->getParentTransactionId()
- add transaction (TxnType = capture)
- implicit IsTransactionPending -> {STATE_PAYMENT_REVIEW, status auto}, invoice is not paid
- implicit IsTransactionPending+IsFraudDetected -> {STATE_PAYMENT_REVIEW, STATUS_FRAUD}, invoice is not paid
- no implicit pending -> {STATE_PROCESSING, status auto}, invoice is paid
- !method.processInvoice --- rare, almost deprecated
- implicit invoice IsPaid -> invoice.pay()
- invoice {STATE_PAID} unless implicit payment ForcedState
- payment.pay -- update _paid, _captured
- payment.amount_paid,shipping_captured
- event
sales_order_payment_pay
- order.total_paid
- event
sales_order_invoice_pay
- payment.capture()
- can capture and offline: invoice.pay()
- can't capture and !method.isGateway or offline: invoice.pay()
- order.*_invoiced -- total, subtotal, taxes, shipping, discounts
- default invoice state {STATE_OPEN}
- event
sales_order_invoice_register
- order(full amount): -- rare case
- event
- event
sales_order_place_after
- order: does not affect values
- authorize: fraud detection - checks same currency and capture final
- authorize: payment.amount_authorized = full_amount
- authorize can be offline
- method -- assigned in block
payment/form_container
._prepareLayout when creating
- sort_order - when get store methods
- info_instance -- quote_payment OR order_payment
- ! store (from quote or order)
- _code
- _canManageRecurringProfiles - only PayPal Express, PayPal BillMeLater (+UK)
- _formBlockType -- extends block payment/form
- _infoBlockType -- extends block payment/info
- _prepareSpecificInformation -- should be varien object
- event
payment_info_block_prepare_specific_information
-- if not set explicitly
- _canUseCheckout
- _canUseForMultishipping
- _canUseInternal
- _canVoid - to cancel online, must exist non-closed auth transaction
- _canCapture
- _canCapturePartial - qty editable
- _canRefund
- _canRefundInvoicePartial - qty editable
- _canReviewPayment - when status payment_review, accept/deny buttons will show up
!assignData -- assign specific fields from POST. write to quote.payment fields !validate -- when saving form data and before placing order !prepareSave -- when quoet payment saved. rare, used only by cc !getCheckoutRedirectUrl -- before order placed. can be in data.
- if returned, will skip order review and redirect
- if empty, normal order review step with button "Place order" ! getOrderPlaceRedirectUrl - after order created ! getOrderOptions -- extract/unserialize product.custom_options for external usage by type
- !don't forget to set 'product_calculations' = parent/child, affects invoicing getConfigData canUseForCountry -- see config.allowspecific canUseForCurrency -- custom canUseCheckout -- _property canUseForMultishipping -- _property canUseInternal -- _property canManageRecurringProfiles -- _property + instanceof recurring isGateway !initialize !order !authorize !capture
- use $authTransactionId = $payment->getParentTransactionId();
- is_capture_complete = (int)$payment->getShouldCloseParentTransaction() processInvoice --- rare, almost deprecated fetchTransactionInfo isAvailable:
- by default from config.active
- event
payment_method_is_active
- checks recurring (_canManageRecurringProfiles + implement interface)
- admin - invoice, refund
payment.additional information:
- instructions <- payment_method.getInstructions()
- can cancel
- not on hold, not payment review state
- not canceled, not complete, not closed
- !not ACTION_FLAG_CANCEL (can set after order load)
- payment.cancel:
- online <== method.canVoid and auth transaction found and not closed
- implicit payment.message
- if online:
- method.void
- insert transactions txntype void
- order {STATE_PROSESSING, status default}
- event
sales_order_payment_cancel
- order.registerCancellation:
- cancel order_items[] -- *_canceled
- order.*_canceled
- {STATE_CANCELED, status default}
- event
order_cancel_after
creditmemo.cancel:
- creditmemo_item.cancel -- update order.{qty,tax,hidden_tax}_refunded
- order payment.cancelCreditmemo:
- order payment.{amount,shipping}_refunded
- event
sales_order_payment_cancel_creditmemo
invoice.cancel -> payment.cancelInvoice (payment fields), fields order.*_invoiced, event sales_order_invoice_cancel
, state
invoice.void = payment.void (online), invoice.cancel
invoice:
- total_qty
- base_total_refunded
- is_used_for_refund
order.cancel -> payment.cancel -- decide online/offline order.registerCancellation -- fields *_canceled, state order:
- normal totals ....
- shipping_{canceled,invoiced,refunded,tax_refunded}
- subtotal_{canceled,invoiced,refunded}
- tax_{canceled,invoiced,refunded}
- total_{paid,qty_ordered,canceled,invoiced,online_refunded,offline_refunded}
order_item.cancel -- checks status_id: PENDING - nothing happened SHIPPED INVOICED REFUNDED BACKORDERED CANCELED qty_ordered = qty_canceled PARTIAL MIXED order_item:
- qty_{ordered,backordererd,canceled,invoiced,shipped,refunded}
- {tax,hidden_tax}_canceled
- {tax,hidden_tax,discount}_invoiced
- {tax,hidden_tax,discount}_refunded
payment.cancel -> _void (online/offline) -- decide online or offline, event sales_order_payment_cancel
payment.void -> _void (online) -- event sales_order_payment_void
payment.cancelInvoice -- fields payment._paid, payment._captured, event sales_order_payment_cancel_invoice
order payment:
- shipping_{captured,refunded}
- amount_{ordered,authorized,paid,paid_online,canceled,refunded,refunded_online}
method.cancel - void when no invoices, e.g. authorization method.void - actual logic
canVoid - auth transaction found and not closed canCapture - if auth transaction is found but closed, order transaction must be there
void and capture transactions - parent should be auth
order, auth, capture, void, refund
transaction->closeAuthorization: {void,capture}Transaction -> closeAuthorization (parent) authTransaction->close, closeAuthorization transaction->closeCapture: captureTransaction -> close, closeCapture refundTransaction -> closeCapture (parent)
!method.checkoutRedirectUrl and checkoutOrderPlaceUrl not supported! virtual items assigned to billing address
- enabled in config shipping > option > allow multi
- doesn't have items with decimal. why?
- doesn't have nominal items
- min amount for each address/total by config sales > min order amount > amount and validate separately
- not all virtual
- below max qty per config shipping > option > max
- quote not virtual
quote.is_multishipping quote_address_item[] -> quote_item
- type_multishipping.init:
- import customer default {shipping,billing} address and email: copy fieldset customer address -> quote address
- add all items to default shipping address -> quote_address_items[]
- split quote_address_item[] by 1 qty -- getQuoteShippingAddressesItems
- cast qty to int
- render rows product-qty-saddress
- checkout.setShippingItemsInformation -- saves changes
- checks max qty
- event
checkout_type_multishipping_set_shipping_items
- redirects to
- for each quote.getAllShippingAddresses:
- shipping rates form
- display address items
- event
checkout_controller_multishipping_shipping_post
- save shipping method for each address
- payment methods form for billing address
- save selectedpayment - checkout.setPaymentMethod
- quote payment.importData --- method.assignData, validate
- collectTotals and rates
- display selected payment method + !method info block
- for each address, display selected shipping method and address items
- check min amount
- check required agreements
- checkout.createOrders:
- _validate:
- check method.isAvailable
- all shipping addresses[].validate --
- basic check - firstname, lastname, street, city, telephone postcode (unless optional), country_id, region_id (if required)
- event
customer_address_validation_after
- should_ignore_validation implicit data flag
- shipping methods and rates must be ok
- billing address.validate
- if has virtual, add address for them based on billing
- for each address:
- _prepareOrder:
- order = convert address to order (quote to order)
- event
sales_convert_quote_to_order
- event
sales_convert_quote_address_to_order
- event
- order billing and shipping address - convert -- event
sales_convert_quote_address_to_order_address
- convert payment -- event
sales_convert_quote_payment_to_order_payment
- for all address items, convert -- event
sales_convert_quote_item_to_order_item
- order = convert address to order (quote to order)
- event
checkout_type_multishipping_create_orders_single
- _prepareOrder:
- for each created order:
- order.place -- events before/after + payment.place -- call method order/authorize/capture
- order.save
- queue new email -- mailer.send via setQueue: core/email_queue (entity type order, entity id, event new order)
- core session.order_ids
- checkout session.last_quote_id
- quote set not active
- event
checkout_submit_all_after
orcheckout_multishipping_refund_all
on error
- _validate:
- session.clear:
- event
checkout_quote_destroy
- quote_id = null
- last_successful_quote_id = null
- event
event checkout_multishipping_controller_success_action
what I remember:
- default/carriers/{code}:
- model
- title
- active
- debug method instanceof Mage_Shipping_Model_Carrier_Abstract: implements Mage_Shipping_Model_Carrier_Interface!
- isTrackingAvailable
- creating invoice, can also create shipment. dropdown with carriers
- creating shipment
- order view in admin -> link to view/remove
- !getAllowedMethods:
-- consider standard config 'allowed_methods'
- used in source model
adminhtml/system_config_source_shipping_allmethods
- in turn used in cart price rule as condition "Shipping Method is ..."
- used in source model
- collectRates(request) request->addRate(new rate) shipping adderss.shipping method shipping rates
shipping address.getGroupedAllShippingRates
-- 1 to check specific available countries
-- comma-separated available destination countries
-- error if country validation failed. or default
-- show error message or just skip carrier
shipping/rate_result - has appended shipping/rate_result_abstract[]:
- shipping/rate_result_method
- carrier
- carrier_title
- method
- method title
- price
- cost
- shipping/rate_result_error
global:
result = shipping/rate_result -- shipping/shipping.getResult - holds overall result
carrier:
result = shipping/rate_result
for each sub-method[]:
- method = shipping/rate_result_method
- result.append(method)
can return result with methods[]
can return just single method
implicit:
setStore -- store_id present
setActiveFlag('active') -- stupid
checkAvailableShipCountries
getConfigData
getFinalPriceWithHandlingFee - adds handling_fee, handling_type(fixeD/%), handling_action(per order/package)
!proccessAdditionalValidation - when collecting rates, return false to skip
!collectRates
!isShippingLabelsAvailable
!isTrackingAvailable -- when creating shipment:
- add to carriers in Shipping Information-Add Tracking Number box
- option to create labels in popup?
!isGirthAllowed... -- for packages popup, hardcoded for USPS, only when displayGirthValue
!getDeliveryConfirmationTypes -- for packages popup
!getContainerTypes
!getContentTypes -- documents, other...
!requestToShipment -- do work when creating shipment
quote.collect totals
{weight, free method weight, shipping_amount} = 0
- sum items:
- address.item_qty = sum(item[].qty)
- address.weight = sum(item.qty * item.weight)
- address.free method weight = 0 for free address, sum of only non-free item weights (item can have max qty for free shipping)
- collect shipping rates
- if shipping method selected, copy from according rate:
- address.shipping_amount = rate.price
- address.shipping_description = "rate.carrier_title - rate.method_title"
not sorted before collected
flag collect_shipping_rates -- only once, saved in DB
remove all shipping rates - mark deleted, will delete on save
!address.country_id is required
request shipping rates:
-
new request = shipping/rate_request -- just varien object
- all_items -- quote_items[]
- dest_country_id
- dest_region_id
- dest_region_code -- 2 letter state code or full region name
- dest_street
- dest_city
- dest_postcode
- package_value -- address subtotal, includes virtual price
- package_value_with_discount -- subtotal_with_discount, includes virtual price
- package_weight -- address.weight
- package_qty -- address.item_qty - all items
- package_physical_value -- subtotal - virtual_amount, excludes virtual price
- free_method_weight -- 0 for address.free_shipping, sum of items weights excluding item.free_shipping max qty
usage examples?
- store_id
- website_id
- free_shipping -- whole address
- base_currency
- package_currency
- limit_carrier --collect only specific carriers. examples?
- base_subtotal_incl_tax -- base_subtotal_incl_tax + base_extra_tax_amount
-
!shipping/shipping.collectRates
- !request.orig -> set request from config Shipping settings > Origin:
- country_id
- region_id
- city
- postcode
- read carriers from default/carriers/
- for each carrier[], shipping/shipping.collectCarrierRates:
- carrier = instantiate by and
- carrier.checkAvailableShipCountries -- by config, not to extend
- carrier.proccessAdditionalValidation if no country error -- return shipping/rate_result_error on error
- if carrier config 'shipment_requesttype' ???? examples, result???
- split packages by config 'max_package_weight'
- carrier.collectRates for each divided package with reduced weight
- aggregate prices somehow
- if not split:
- carrier.collectRates
- sort result.rates by price
- global result.append carrier result rates
-
add result rates to address
-
if shipping_method found in rates, copy address.shipping_amount
if none found, reset {shipping amount,shipping method,shipping description}
- only items with qty_decimals 'Qty Uses Decimals'
- only items with is_decimal_divided 'Can be Divided into Multiple Boxes for Shipping'
- splits into packages by max_package_weight
- not working with bundle
- prices = collect each package separately and sum each results
- carrier shipment_requesttype should be 'Use origin weight (few requests)'
sales/service_order.prepareShipment
shipment = convert order > shipment
shipment items[] = convert order items[]
item[].qty_to_ship = qty_ordered - qty_shipped - qty_refunded - qty_canceled
-- submit form
shipment.register
- shipment item[].register -- order item.qty_shipped += qty
- shipment.total_qty
add shipment comment to collection
!create shipping labels if supported:
- carrier.isShippingLabelsAvailable
- shipment.setPackages from request
- shipping/shipping.requestToShipment
- ensure non-empty:
- admin first and last name
- store name and phone
- shipping origin street, city, postcode, country
- request object:
- order_shipment -- just shipment
--- from admin user
- shipper_contact_person_name
- shipper_contact_person_first_name
- shipper_contact_person_last_name
- shipper_contact_company_name
- shipper_contact_phone_number
- shipper_email
- shipper_address_street
- shipper_address_street1
- shipper_address_street2
- shipper_address_city
- shipper_address_state_or_province_code
- shipper_address_postal_code
- shipper_address_country_code
--- from address
- recipient_contact_person_name
- recipient_contact_person_first_name
- recipient_contact_person_last_name
- recipient_contact_company_name
- recipient_contact_phone_number
- recipient_email
- recipient_address_street
- recipient_address_street1
- recipient_address_street2
- recipient_address_city
- recipient_address_state_or_province_code
- recipient_address_region_code
- recipient_address_postal_code
- recipient_address_country_code
--- other
- shipping_method
- package_weight
- packages -- from request
- container, weight, customs_value, dimensions, units, confirmation ... and items
- base_currency_code
- store_id
- !carrier.requestToShipment -- return varien object: errors, []info.tracking_number, []info.label_content
- !shipment.shipping_label -- parse and generate single PDF from each []info.label_content. can be encoded images or encoded PDF
- !save each info.tracking_number to order_shipment_track
sales/order/trackinginfo.phtml:
- carrier_title
- Tracking
- Url
- Status
- Deliverydate
- Deliverytime
- Deliverylocation
- Signedby
- TrackSummary
! if all items free shipping, updates whole request free shipping
- gather free qty (item.free_shipping ? item.qty)
!not respecting max free shipping qty?
- price = per order - just add config.price, or per item - (package_qty - free_qty)*config.price
- supports handling fee fixed/% per order/package
- if not include_virtual_price, updates request.package_value -= virtual items
- respects max freeshipping qty
- deducts request.package_value for free and virtual (if not include virtual)
- resource shipping/carrier_tablerate.getRate
select from tablerate where
$condition_name(package_weight,package_qty,package_value)
<= order value(package_weight,package_qty,package_value)
- supports handling fee
UPS - united parsel service (world), USPS - united states postal service, FedEx - Federal Express (international), DHL (world)
- enabled for RMA
- UPS type
- container
- destination type
- request_type - split/not
- pickup type
- max package weight
- handling fee
- sub methods
- !free method
--- this is shared for all carriers! _rawRequest, _freeMethod, _setFreeMethodRequest, _getQuotes, _updateFreeMethodQuote
- if all items are free_shipping > request.free_method_weight = 0, only selected submethod will get price 0
- Minimum Order Amount for Free Shipping -- if order price is big, free method is searched and set price 0
implements Mage_Shipping_Model_Carrier_Interface:
- isTrackingAvailable
- getAllowedMethods
! free shipping method - if order subtotal >= threshold, submethod.price = 0
! free shipping items support - get rate for "free shipping method" with reduced weight
- check max weight -- each item separately
- dest post code is required (unless - isZipCodeOptional by country)
- split to packages if Packages Request Type - Use origin weight (few requests)
- when total weight > max, splits by num_boxes, e.g. total 68 and max 20 -> 4 boxes
- weight = average between all boxes, e.g. 68 lbs / 20 lbs max = 4 boxes * 17 lbs average
- when parsing response price:
- sets price 0 if subtotal > free shipping min amount
- sets price = num of boxes * cost + fee
- update free method:
- fetch only rate with only paid weight: only 2 boxes * average 17.5 lbs
- cost is the same, but num of boxes is lower, so XPR 900
ship separately?
taxes when refunding? ------ totals?
credit memo totals?
partial invoice/ship/refund?
cancel order/order item/shipment/invoice/credit memo?
taxes when canceling? ----- totals?
customer attributes?
customer emails?
<adminhtml_sales_order_create_index>
adminhtml/sales_order_create, adminhtml/sales_order_create_form
adminhtml/sales_order_create_customer
adminhtml/sales_order_create_store
adminhtml/sales_order_create_data
_sidebar -- cart, wishlist, reorder, viewed, compared, pcompared?, pviewed?
_form_account -- customer form, model customer/customer. attributes from eav/attribute by entity_type customer
_shipping_address, _billing_address -- model customer/address, by entity_type address
_billing_method + form -- instanceof payment/form_container
_newsletter + form -- checkbox
_search + grid of products -- add new items, filter only adminhtml/sales/order/create/available_product_types
_items + grid -- added products
_coupons
_comment
totals
adminhtml_sales_order_create_load_block{header,sidebar,...,totals} -- every block from above
</adminhtml_sales_order_create_index>
ajax calls:
-
..create/loadBlock/block/{name}
-
..create/configureProductToAdd
-
..create/configureQuoteItems
-
helper catalog/product.skipSalableCheck -- admin can create any product (ignore inventory)
adminhtml/sales_order_create/start
- clear session quote (customer_id, store_id, quote_id etc.)
- redirect ↓
adminhtml/sales_order_create/index
- init session:
- event
create_order_session_quote_initialized
-- only weee copies store_id for self
- order create form container and form
- order data json is empty - no customer and store selected
- 3 inline steps:
- customer grid, row click callback > loadBlock (header) +customer_id
- init session - save customer_id to quote session, event
create_order_session_quote_initialized
- processActionData, event
adminhtml_sales_order_create_process_data_before
, no more post data, nothing to process
- load and return layouts
adminhtml_sales_order_create_load_block_{block}
- update page header title, user name, messages
- store selection, on selected > loadBlock (header,data) +store_id
- init session - save store_id to quote session, event
create_order_session_quote_initialized
- processActionData, event
adminhtml_sales_order_create_process_data_before
, no more post data, nothing to process
- load and return layouts
adminhtml_sales_order_create_load_block_{block}
- user name header, data and messages
---- quote saved when customer and store selected, default customer group, inactive
- data:
... shipping_method - sales/order/create/shipping/method/form.phtml
- rate.error_message, rate.method_title, rate.method_description, rate.price
- helper tax.getShippingPrice (including/excluding by config)
- "get shipping rates" > loadBlock (shipping_method, totals)
!!! can enter products custom price
quote.customer_note -- admin order comments
processData:
- order create model = model adminhtml/sales_order_create implements Mage_Checkout_Model_Cart_Interface -- same as cart model
i getquote
i setQuote
i addProduct
i saveQuote
- !importPostData - account (customer fields), comment, {billing,shipping} address, payment_method, coupon.
quote.setBillingAddress -- updates old one with new data
- !applySidebarData - add {order,cart,wishlist}_item, add product, remove item, empty cart
- updateQuoteItems - update qty or order items
- applyCoupon
- setAccountData -- customer/form, extract data from request, form attributes collection
- recollectCart
- initFromOrder -- for reorder
- initFromOrderItem -- same
- moveQuoteItem - to order, cart, wishlist, remove
- ... shipping method, rates, payment method
? admin order create
- customer, store, data inline steps
? admin order edit -- same as create + tweaks
- session.use old shipping method
- initFromOrder:
- session.order id, currency id, customer id, store id
- event
init_from_order_session_quote_initialized
- add items:
- only order items from <available_product_types> -- all should be added
- only items not shipped and not invoiced
- add items from buy request
- event
sales_convert_order_item_to_quote_item
- copy fieldset {shipping,billing}Address order -> quote
- copy fieldset order -> quote
- event
sales_convert_order_to_quote
- collect rates
- _processActionData: -- create addresses, save shipping/payment mehtods, apply coupon, tollect totals and save quote
- event
adminhtml_sales_order_create_process_data_before
- model adinhtml/sales_order_create.importPostData:
- set account data
- quote.customer_note
- create {billing,shipping} address from data
- set {shipping,payment} method from data
- save coupon, mark quote to recollect
- skip lots of {reset_shipping, collect_shipping_rates, sidebar - applySidebarData, add/move/remove/update product/items}
- save payment data
- event
adminhtml_sales_order_create_process_data
- collect totals and save quote
- skip {add products, update items, apply coupon}
- set payment data
- check payment internal, country, currency, min-max, zero total
- payment.importData -> method.assignData, validate
- order create model.importPostData -- account, addresses, shipping method and comment
- order create model.create order
- prepare customer:
- for existing, update/create addresses in book
- for new, generate password, set and validate customer/form data -- attribute data model.validte(value), save addresses
- save to quote customer_* form.user attributes
- _validate:
- check items without errors
- check method selected, available, validate
- prepare quote items -- options
- !if session.order --- editing order
- remember old id, increment, edit_increment, generate new increment: "{oldincrement}-{editincrement+1}"
(e.g. "oldincrement-1", "oldincrement-2")
- !order.cancel
- !new order = service.submitOrder
- link old order with new one
- event
checkout_submit_all_after
- clear session quote -- customer_id, store_id, quote_id
- model adminhtml/sales_order_create.updateQuoteItems:
- item.setCustomPrice, setOriginalCustomPrice
- quote.collectTotals
- subtotal quote total collector.collect
- each items[]._initItem
- item.calcRowTotal
- getCalculationPriceOriginal --- will return custom price if defined
- item.row_total = qty * calculated price original
custom price - saved in DB in quote_item
quote.is_super_mode
quote_item.getCalculationPrice - custom_price if exists, otherwise convert price
adminhtml_sales_order_creditmemo_start -> _new
_initCreditmemo:
- by order_id
- if present, _initInvoice -- just load by invoice_id and order
- sales/service_order.prepareCreditMemo or .prepareInvoiceCreditMemo
- convert order_items -> creditmemo_items
- shipping_amount, adjustment +-
- creditmemo.collectTotals
-- subtotal: subtotal = sum(row total), subtotal_incl_tax = sum(row_total_incl_tax), grand total
shipping, tax, discount, grand_total, cost_total, weee
- set back_to_stock from saved data and config auto return enabled
- event
adminhtml_sales_order_creditmemo_register_before
- register in 'current_creditmemo'
creditmemo any field = order_item.invoiced - refunded
tax proportionally refund qty/all qty
creditmemo.state OPENED,REFUNDED,CANCELED
- data - items-qty, online/offline, comment, shipping, adjustment +-
- add comment if defined (text, visible on front, notify)
- mark refundedRequested, onlineRequested
- creditmemo.register
- creditmemo item[].register
- creditmemo.refund
- state REFUNDED (if payment reassigns, state can be OPEN)
- update order.*_refunded
- invoice.is_used_for_refund, .total_refunded
- payment.refund(creditmemo)
- payment.parentTransactionId -- automatically sets capture transaction - invoice transaction id
- payment.shouldCloseParentTransaction = true -- automatically marks to close
- method.setStore -- always available, store from order
- method.processBeforeRefund -- not to extend, sets payment.refundTransactionId
- method.refund
- method.processCreditmemo -- can extend, by default creditmemo.transaction_id = payment.last_trans_id
- event
sales_order_creditmemo_refund
- order.total_online_refunded, total_offline_refunded, total_invoiced_cost
- _saveCreditmemo -- creditmemo+order in transaction: core/resource_transaction->addObject()...->save()
order:
- cancel
order_item
invoice:
- cancel
shipment
credit memo
product link types:
- related }
- upsell }
- crosssell }
- grouped
- NEW -- just created but worthless, not counted in reports
- PENDING_PAYMENT -- useful one? used by Nets before order is paid
- PAYMENT_REVIEW -- fraud etc.
- PROCESSING -- invoiced or shipped
- COMPLETE -- invoiced + shipped
- CLOSED -- fully refunded
- CANCELED
- HOLDED -- manually
Mage_Sales_Model_Service_Order:
- prepareInvoice
- prepareShipment
- prepareCreditMemo -- $order, qtys. qty to refund = invoiced - refunded
- prepareInvoiceCreditMemo
order_item.qty_{ordered,invoiced,shipped,refunded,canceled,backordered}
- getQtyToRefund = qty_invoiced - qty_refunded
- getQtyToShip
- totals = sales/order_invoice_config.getTotalModels
- each total model[].collect()
subtotal -- invoice.subtotal,subtotal,subtotal_incl_tax,grand_total + base
discount -- invoice.discount_amount,grand_total + base
shipping -- invoice.shipping_amount, shipping_incl_tax, grand_total + base
tax -- invoice.tax_amount, hidden_tax_amount, shipping_tax_amount, shipping_hidden_tax_amount + base
grand_total -- dummy
cost_total -- invoice.base_cost
wee -- invoice.subtotal, tax_amount, subtotal_incl_tax, grand_total
- by default only bundle implements this
- product.shipment_type
- product.weight_type -- 0 dynamic, 1 fixed
- configurable.getOrderOptions -- together
- buncle.getOrderOptions -- as saved
- typical usage: $item->getHasChildren() && $item->isShipSeparately()
affects:
- quote.getItemsSummaryQty -- if ship together = bundle.qty, if separately = sum(bundle.qty * child.qty)
- shipping method flatrate -- qty calculation for free boxes
- shipping method tablerate -- affects package_value and free qty
- order_item.isDummy -- don't ship irrelevant combinations
- quote address total shipping -- if separately, add calculate weights (fixed/dynamic)
- shipping packaging grid -- skip weight for ittelevant lines
- shipping USA methods -- get all items, count separately
track -- per shipment
order_shipment.getTracksCollection
order_shipment.addTrack
track -- carrier_code, track_number, title, description, weight, qty
convert customer address custom attributes to order address -- fieldsets?
address templates? in <addres_templates> -- text/oneline/html/pdf/js_template
email:
- new account -- login/password
- account confirmation -- login/password, link to confirm
- account confirmed -- just text
- password reset link
- your new password is ...
- authenticate -- check needs confirmation, check password, event
customer_customer_authenticated
- set/hash/generate/validate/encrypt/decript password
- sendNewAccountEmail
- is confirmation required, get random confirmation key
- getName -- checks prefix,middlename,suffix is visible (customer_eav_attribute)
- get/add addresses
- get {primary,default} {billing,shipping} address. customer.default_shipping/billing = {id} -- load address by id
- getAttributes
- is in store, get shared {store,website} ids -- only current website stores or all stores
- validate
- changeResetPasswordLinkToken, isResetPasswordLinkTokenExpired
customer/group:
- getTaxClassId
eav_attribute:
- is_required
- is_user_defined
catalog_eav_attribute:
- is_visible
- is_system
- multiline_count
- input_filter (e.g. date, datetime)
- validte_rules
- data_model
eav/config singleton:
- getAttribute(entity type, code)
- getEntityAttributeCodes
- eav_attribute: attribute_code, is_required, is_user_defined = 1, backend_type (Input Type), frontend_input (), default_value
- customer_eav_attribute: is_visible (Show on Frontend), input_filter, is_system = 0, validte_rules -- min/max length, input validation
? table customer_form_attribute
- _initAttribute:
- customer/attribute -- eav_attribute by entity_type_id = 1
- store data in session
- register 'entity_attribute'
- content block -- enterprise_customer/adminhtml_customer_attribute_edit -- form container, form follows
- delete button only if not user defined
- *_edit_form -- dummy
- left block -- 2 tabs, main content tab
- parent block eav/adminhtml_attribute_edit_main_abstract (same for edit product attribute etc.) -- form
- base properties fieldset:
- attribute_code
- frontend_input
- default_value_{text,yesno,textarea}
- is_unique
- frontend_class
- locked attributes from <eav_attributes><{ENITY-TYPE}><{ATTRIBUTE}><locked_fields> -- disabled, readonly
- enterprise_customer/adinhtml_customer_attribute_edit_tab_main:
- remove attributes is_unique, frontend_class
- add own fields ...
- used_in_forms = options hardcoded in helper enterprise_customer/customer.getAttributeFormOptions
- checkout_register/customer_account_create/customer_account_edit/adminhtml_checkout
- event
enterprise_customer_attribute_edit_tab_general_prepare_form
helper enterprise_eav -- input_type > backend_type:
text - varchar
textarea - text
multiline - text
date - datetime
select - int
multiselect - varchar
boolean - int
file - varchar
image - varchar
user_defined = 1, is_system = 0
used_in_forms always adds 'adminhtml_customer'
events enterprise_customer_attribute_before_save
, enterprise_customer_attribute_save
checkout form:
only in enterprise template, after fax before password inject customer defined address attributes
enterprise_customer/form.setFormCode('')
customer/form
read from table 'customer_form_attribute' by form_code = 'customer_register_address' and entity_type
only user defined and visible attributes (skip image and file)
get renderers from block 'customer_form_template' for each input type when form prepare layout
customer, customer address -> init checkout -> quote.assign customer -> quote.assignCustomerWithAddressChange
- new quote address.importCustomerAddress -- customer default billing address
- copy fieldset 'customer_address' > 'to_quote_address'
- same for shipping
copy fieldset 'sales_convert_quote' > 'to_order':
- copy user defined customer attributes with prefix -- order.'customer_*'
copy fieldset 'sales_convert_quote_address' > 'to_order_address':
- copy user defined address attributes
copy fieldset 'sales_convert_billing_address' > 'to_order':
copy fieldset 'sales_convert_shipping_address' > 'to_order':
! system > config > customer > account creation > generate human-friendly ID
- when disabled, customer_id will be always 1,2,3, ... as database handles, increment_id = null
- when enabled, increment_id is populated like in orders etc.
widget.xml:
My Widget
My Widget's description
text
1
My Parameter
My Parameter Description
1
<sort_order>0</sort_order>
something
First option
1
Second
2
<source_model>mymodule/source</source_model>
<helper_block>
mymodule/widget_helper_block
</helper_block>
value1
value2
value3
mymodule/field_renderer
Field renderer...
...instead of type
<unique_id>
</unique_id>
Template
Widget Template
default.phtml
<as_something>
Grid mode
grid.phtml
</as_something>
Other mode
other.phtml
widget/widget.getWidgetsArray
widget config xml is cached with CONFIG tag
Mage::getConfig()->loadModulesConfiguration('widget.xml', $config);
<adminhtml_widget_loadoption>
- get block widget/adminhtml_widget_options -- is form
- read and assign to block 'widget_type' and 'widget_options' -- from selected widget
- *_options.addFields:
- widget/widget.getConfigAsJson(type)
- searches widget.xml for match by type, returns first --- stupid, why not by code
- work with widget config.parameters
- for each parameter, _addField:
- ...
- categories anchor -- default,catalog_category_layered
- categories nonanchor -- default,catalog_category_default
- all products -- default,catalog_product_view
- each product type[] -- default,catalog_product_view,PRODUCT_TYPE_{type}
- all pages -- default
- specific page
ajax call <adminhtml_widget_instance_template>
- init widget instance: set type, package_theme
- block widget/adminhtml_widget_instance_edit_chooser_template
- set selected -- what?
- set widget templates -- widget/widget_instance.getWidgetSupportedTemplatesByBlock -- by selected block
ajax call <adminhtml_widget_instalce_blocks>
- block widget/adminhtml_widget_instance_edit_chooser_block
- blocks with !!!
selected layouts --- 'page groups'
resource widget_instance._afterSave:
- delete old widget_instance_pages
- for each 'page_group'[]:
- save layout updates -- return ids
- for each update in page_group.layout_handle_updates -- e.g. CATEGORY_4:
- generate widget_isntance.generate layout update xml -- checks template exists
- layout update inserts widget as block and contains setData for each parameter
- save pages and link to core layout updates ids
widget_instance -- type, theme, title, stores, parameter values
widget_instance_page -- block, entities, template
widget_isntance_page_layout -- link with generated rows in core_layout_update
all layouts:
model widget/widget_instance._layoutHandles, _specificEntitiesLayoutHandles
select page --- getLayoutsChooser:
-- all layout handles with !!!
-- EXCEPT default, catalog_category_, catalog_product_, PRODUCT_* -- all specific ones
block widget/adminhtml_widget_instance_edit_tab_main_layout.getDisplayOnOptions, getDisplayOnContainers
block widget/adminhtml_widget_instance_edit_chooser_layout.getLayoutHandles
-- all layouts look like specific CATEGORY* or PRODUCT_*
api v1 - xmlrpc/soap v1: register api.xml: convert/maincard
api.xml:
soap
1
some/class
some/model
<some_name>
0
Human message, will be translated
</some_name>
0
some/acl/path
some/model
somemethod
0
some/acl/path
array
<some_name>
0
Something weng wrong
</some_name>
<title>MyModule</title>
<sort_order>0</sort_order>
<something_nested translate="title" module="mymodule">
<title>Nested Options</title>
</something_nested>
<resources_function_prefix>
</resources_function_prefix>
</v2>
- initialize - create given adapter by requested code, create given handler and assign to adapter
- adapter.run
- zend_xmlrpc.setClass(handler).handle()
- own cache tag 'config_api' -- Web Services Configuration
- api.xml
- getAdapters: ...
- getActiveAdapters:
- only 1 and check -- extension_loaded
adapter: xmlrpc/soap/soap_v2/soap_wsi, default -> soap -- deals with request/response, exposes handler methods
setHandler
Zend_XmlRpc_Server.setClass(handler).handle()
construct:
- _getWsdlConfig - object:
- name = 'Magento'
- url = current url
- handler = unassigned when constructing, so null
wsdl requested:
- read Mage_Api/etc/wsdl.xml as template
- substitute wsdl content using core/email_template_filter, use wsdl.{name,url,handler} as vars
non-wsdl - normal call:
- instantiate server:
- ini_set wsdl_cache_enabled yes/no by config
- build wsdl url -- current or if server vars PHP_AUTH_USER, PHP_AUTH_PW defined, build link like http://name:user@host/...
- new SoapServer with self ?wsdl, setClass(handler)
- SoapServer.handle()
- {start,end}Session, login, call, multiCall, resources, {resource,global}Faults
handler->_fault('code', 'someresource'): -- can use custom message
- first search resources/someresource/faults, then global faults
handler.login(name, key)
- api/session.login
- api/user.login
- authenticate - load by name and check key
- clean older sessions -- by timeout from System > Config > API > Config > Session Timeout (1 hr default)
- log login -- increment api_user.lognum
- record session -- insert/update to api_session
- event
api_user_authenticated
- check user is active, assigned to some role
handler.call(sessId, ...) -- session is always mandatory
- api/session.isLoggedIn
-- loads user from api_session by user_id
-- check timeout not passed
-- loads ACL
-- records login, lognum
api/config.loadAclResources -- recursively build ACL paths in api/acl/resources/..., e.g. 'catalog/category/attributes'
resource api/acl.loadRoles -- [].addRole(role_user or role_group)
resource api/acl.loadRules -- tree
- instancofe api/resource
- for each object row in response, can use _getAttributes:
- filters _isAllowedAttribute: _ignoredAttributeCodes['global'], _ignoredAttributeCodes[$type]
- _attributesMap['global'], _attributesMap[$type] -- aliases, e.g. 'order_id' instead of 'entity_id'
controller Api/controllers/V2/Soap:indexAction -- /api/v2_soap/index
adapter, handler 'soap_v2'
all resources with suffix *_v2, e.g. sales/order_api -> sales/order_api_v2
- _getWsdlConfig: -- load from all modules, XML tree, elements of
Mage_Api_Model_Wsdl_Config_Element
- was varien object (name, url, handler), now model api/wsdl_config
- setHandler (handler code)
- init:
- caching enabled/disabled with main CONFIG type
- no cache tags, to reset -- flush Magento cache
- load own own wsdl2.xml + load all modules wsdl.xml
- every loaded file is processed by XML through
processFileData
:
- core/email_template_filter.filter -- vars wsdl.{name, url -- current, handler -- code? 'soap_v2'}
- run?wsdl
- result tree is printed as XML
- run normal:
- no changes, regular {new SoapServer}.setClass(handler).handle()
__call magic method:
- parse joined function to v1 resource name.method using api/v2/resources_function_prefix
!need to register by ourselves
e.g. customerCustomerInfo -> customer.info (given
<resources_function_prefix><customer>customerCustomer</></>
)
- proxies usual ->call()
- register consumers
- init/authorize/access token steps
- parse headers on REST calls
- cache enabled if config cache enabled
- no events
- to add api type, rewrite api2/server and extend _apiTypes
api2_acl_user -- assign admin user to admin role
api2_acl_role -- #1 == guest, #2 == customer, others - admin roles. create, updated, role name
api2_acl_rule -- role-resource-operation
api2_acl_attribute -- user type-resource-operation-attributes[] joined
- users, role, access to attributes
to edit ACL in admin, resource groups (endpoints) + privileges (actions)
desctibed by api2.xml > resources/privileges/{user type}/{operation}
guest - only one role, only available:
- product.read
- category.read
- product image.read
customer - only one role , available above +:
- orders.read
- order items.read
- order addresses.read
- order comments.read
- customer.read/update
- customer address.*
admin - many roles, admin user can be assigned only to one:
- all methods
Access to attributes / per user type / read or write - 2 admin roles CANNOT have different attribute access
config.xml:
something
module/model
api2.xml:
<resource_groups>
</resource_groups>
<title></title>
<working_model></working_model>
1
<action_type></action_type>
1,2,...
Human Name
<exclude_attributes>
1
</exclude_attributes>
<include_attributes>
1
</include_attributes>
<entity_only_attributes>
1
</entity_only_attributes>
<force_attributes>
1
</force_attributes>
resource_groups?
resource/{type}/group?
resource/{type}/privileges?
Accept: something/specific
Accept: something/*
Accept: /
- application/json -- default, matches / -- json_encode
- text/plain -- query style, http_build_query
- text/xml == application/xml == application/xhtml+xml
Authenticate -- for oauth_adapter.isApplicable
Version -- request.getVersion
/api/rest --> /api.php?type=rest
match ?type supported in api2/server._apiTypes = rest
api2/server.run:
- renderer = api2/renderer::factory by request.getAcceptTypes -- sorted by requested weights
- search global/api2/response/renderers to match adapter.type = Accept: type
- authenticate -- assigns server.authUser - instanceof api2/auth_user{guest/customer/admin}. getLabel, getRole.
- _route -- collect routes, run each route.match -- set request.model
- _allow -- check acl
- _dispatch
api2/auth.authenticate:
- for each registered auth adapter .isApplicableToRequest, .getUserParams . return (type=admin/user/guest, id)
-- api2/auth_adapter_oauth -> applicable if header 'Authorization', getUserParams oauth/server.checkAccessRequest
- returns user model by type
- first match hardcoded route api2/route_apiType -- matches 'api/:api_type'
- returns api_type value = rest
- request.setPathInfo -- remove api/:api_type, leaving e.g. /customers
- request.setParam api_type = rest
- config.get routes by api_type=rest -- 27 routes
- load from api2/resources/routes
- wraps api2/route_{type=rest} for each one.
route => 'customer/:customerId'
defaults => {
model => 'customer/api2_customer' -- from api2/resources/
type => '$resourceKey' -- request.getResourceType
action_type => 'entity' -- or 'collection'
}
- api2/router.setRoutes.route
- for each route api2/route_{type=rest}.match
- returns matched {names? + wildcards + defaults}
- when matched, request.setParams -- from match return
- match must return params (request.setParams), set request.resourceType and request.model
- load resource model (:resource_:api_:user_v:version)
:resource = model, e.g. customer/api2_customer
:api = api type, e.g. rest
:user = user type, i.e. guest/customer/adin
:version - requested or first available
-- {resource model}rest{guest/customer/admin}v1, e.g. {customer/api2_customer}{rest}_{customer}_v{1}
- model.set request,response,api user
- model.dispatch -- api2/resource.dispatch
- switch action_type(from route config)+operation(depend on http verb) -- {entity/collection},{retrieve/create/update/delete}
- collection.create - <action_type>collection</action_type>,POST -- _create/_multiCreate depending on data
- filter data via api2/acl_filter
- _create -- do actual stuff here and return new item location
- collection.retrieve
- _retrieveCollection
...
- _render:
- response.setMimeType = renderer.getMimeType
- response.setBody = renderer.render(data from resource method)
! customer-specific resource models just add filter to collection entity_id = apiUser.userId
filter collection - ?filter[0][attribute]=name
api2/route_rest.match -- Zend_Controller_Router_Route
- in -- filter allowed attributes to write
- out -- filter allowed attributes to read, leave only requested -- ?attrs=first,last or ?attrs[]=first&attrs[]=last
- collectionIn
- collectionOut
api2/response -- zend http response + messages[] - {message,code}
api2/request - zend http request +:
- order field/order direction/page number/page size
- action_type, ... other
resource model:
- _create
- _multiCreate
- _retrieve
- _retrieveCollection
- _update
- _multiUpdate
- _delete
- _multiDelete
- getAvailableAttributes(user type, operation)
- target rules?
- reward points?
- website restrictions?
- full page cache?
- payment bridge?
admin conditions
catalog related, upsell
checkout crossell
depends on Mage_Rule heavily - conditions, actions, customer groups, website ids
enterprise_targetrule
- name, date from/to, type(related/crossell/upsell), conditions, actions
-? actions select, actions select bind
targetrule_customersegment -- many to many
targetrule_product -- products matching rule condition
enterprise_targetrule_index --- for given (type,product,store,customer_group) find customer segments
?enterprise_targetrule_index_{related,upsell,crosssell}
?enterprise_targetrule_index_{related,upsell,crosssell}_product -- many to many
targetrule indexer is not visible
resource indexer delegates to sub-indexers 'index_{related,upsell,crosssell}'
cron runs every hour: -- Check store datetime and every day per store clean index cache
- for each website, only at local midnight, indexer.logEvent - targetrule clean targets --- what does it do? prepare to
dataobject = {type_id = null, store = []}
- indexer.registerEvent - targetrule clean targets -- process all matched above? -- what does it do?
- cleanIndex({type_id = null, store = :id}) -> delegate to all types 'index_{type}'.cleanIndex
- deletes records from 'index_related'
after product save:
- dispatch global reindex: targetrule_product.reindex_targetrules
after saving product, reindex:
index._reindex({id, store_id, rule, from_date, to_date})
- for each type, remove index by product
- remove matched product-rule associations from targetrule_product (see default mage_rule.unbind...)
- for each rule, if product matches condition, insert into targetrule_product (default mage_rule.bindRuleToEntity)
after saving rule, populate targetrule_product -- all matching condition products
related:
- filter: some category
- display related: from same category and same attribute set and same gender
upsell:
- filter: some category
- display upsell: from same category and price > 100% of matched
<catalog_product_view> catalog/product_list_related
- itemsCollection = product.getRelatedProductCollection
- link instance.get product collection -- all products linked to current
- exclude items in cart
- don't use category id when building URL
- model catalog/url._getCategoryIdForUrl <- catalog/url.getProductUrl <- catalog/url.getUrl
- ! no limit
- add checkbox to add to cart if can - not composite, no required options, saleable
replaces block catalog.product.related
renders each item in separate block catalog.product.related.item -- contents the same + Caching
targetrule_index -- true/false if generated for given product/customer segment
targetrule_index_{type} -- true/false if generated for given product/customer segment/type
targetrule_index_{type}_product -- actual data! generated and saved on demand
on first request type.getProductIds for missing customer segments, generate, save and mark generated by flag=1
- resource index._matchProductIdsBySegmentId
for each rule[]:
- ensure date from, to matches
- resource index._getProductIdsByRule
- rule.getActions().getConditionForCollection is cached in targetrule table
-! do actual search of match by PRODUCT-RULE-SEGMENT
- type index.saveResultForCustomerSegments
- check targetrule_index_{type}. if exists, meaning already created, delete {type}_products and save new ones
- if not exists, not generated, insert row and save new ones into {type}_products
- resource index.saveFlag
on next requests, return directly from renerated type index
- type index.loadProductIdsBySegmentId -- select directly from targetrule_index_{type}
resource targetrule/index.getProductIds
getItemsCollection:
- depending on behavior, add rule-based and custom items:
- get link products -- same logic wrapped far far away
- get target rule products
- exclude current product
-! index resource.getProductIds by (type,product,exclude)
- get current customer segments 0,30,32
- find matched customer segments in index table by (type,product,store,customer group)
- for each current customer segment:
- if main index has segments for this customer segment[], return type index.loadProductIdsBySegmentId -- load extended version?
- select from targetrule_index_{related,upsell} by (product,store,customer_group,customer segment)
- if main index has NO segments for this customer segment[]:
- type index._matchProductIdsBySegmentId
- type index.saveResultForCustomerSegments
- save flag
- how to handle customer balance?
- customer balance history?
- quote columns for discount?
- where input injected on cart/checkout?
- can redeem part?
- admin use points?
expiry date:
- static -- each balance increase has own expiration
- dynamic -- expiration date prolonged on each balance increase
actions:
- purchase -- on event
checkout_submit_all_after
?
- registration --
- newsletter
- review
- tag
- convert invitation to customer
- convert invitation to order
! rate currency->points must be defined
! currency->points: -- works only for whole increments, not proportionally!
for each $1 you earn 1pt, order for $10 = 10pt
for each $10 you earn 5pt, matches only when total >$10. $10 = 5pt, $15=5pt, $20=10pt
! when credit memo, can set manually Refund creditmemo amount
$order->setForcedCanCreditmemo(true);
$creditmemo->setAllowZeroGrandTotal
enterprise_reward - customer balance per website
reward_history -- new balance and change details - action, entity, delta, used, voided, expired -- in $$ and points
reward_rate -- per website/customer group/direction
reward_salesrule -- cart price rule - reward points
quote.use_reward_points, reward_points_balance, {base_,}reward_currency_amount
order.reward_points_balance, {base_,}reward_currency_amount, reward_salesrule_points, reward_currency_amount_{refunded,invoiced}
- when action happens - add bonus
- totals collector for quote, order, credit memo
- cronjob every hour:
- notify before expiring points
- expire points
-. pdf totals
currency to points - when purchase $$, you get points
- only whole points!
- when big currency points assigned for each reached step, e.g. $100 - 100pt -- you won't get you 100pt before you reach $100
points to currency
- your points are worth $$ discount
typical:
buy $1 get 1pt -- normal point values
exchange 1pt for $0.01 -- small currency values
reward.updateRewardPoints: (customer_id, store, action, action entity)
- reward.canUpdateRewardPoints
- action instance.set{action,reward,history,entity}
- action instance.canAddRewardPoints -- check here!
- reward.save
before save: -- update points balance
- load by customer -- update existing reward if can
- _preparePointsDelta: delta = action instance.getPoints
- _preparePointsBalance:
- balance += delta, but no more than max points config.
- if capped, save cropped points as well!
after save: -- update history, including currency balance
- prepareCurrencyAmount:
- currency{delta,amount} = _convertPointsToCurrency
- get rate by direction.calculateToCurrency
- e.g. direction = 1 (to currency), 4 points = 1 USD. 10 points = 10/4 = 2.5 USD
- history.prepareFromReward
- history.*, comment
- history.additional data - rate details, cropped points
- send balance notification
has models reward, reward history, entity (is passed)
13 action types by default
responsibilities:
- canAddRewardPoints -- extend
- checks history if not duplicate by entity and action
- checks applied limit exceeded
- getPoints -- extend; how much to add! used when ading and to estimate in tooltip
- getRewardLimit -- extend; max applied qty in history
- getHistoryMessage -- extend
- isRewardLimitExceeded
- estimateRewardsQtyLimit
block tooltip
- initRewardType(action instance) -- in layout
- before html - _prepareTemplateData:
- 'reward_points' = reward.estimateRewardPoints
- if has exiting record, add also current balance in points and currency
- when saving payment method, event
sales_quote_payment_import_data_before
- if balance more than min, quote.setUseRewardPoints = true -- to be used in collector
- total_quote_reward.collect:
after order placed: balance deducted
add action and model -- static reward::setActionModelClass - (action id, model)
// use only money customer spend - shipping & tax
$monetaryAmount = $quote->getBaseGrandTotal()
- $address->getBaseShippingAmount()
- $address->getBaseTaxAmount();
$monetaryAmount = $monetaryAmount < 0 ? 0 : $monetaryAmount;
global/sales/order/states
<visible_on_front></visible_on_front>
1 way - REPLACE ROUTE:
$request->setModuleName('restriction')
->setControllerName('index')
->setActionName('stub')
->setDispatched(false);
2 way - REDIRECT:
$response->setRedirect($url);
$controller->setFlag('', Mage_Core_Controller_Varien_Action::FLAG_NO_DISPATCH, true);
restrict store - require logged in?
whitelist some CMS pages?
whitelist some routes?
check customer.isLoggedIn -- work on events predispatch before?
customer/account/create - disable if configured -- predispatch?
replace any route with full-page landing. no login or signup
how?
-
event websiterestriction_frontend
-- can 1) disable restriction or 2) mark user logged in
-
website closed - ALLOW_NONE - replace all routes with 'restriction/index/stub'
- cache per website
- load CMS page by config
- apply design package/theme (from-to date support) if enabled in CMS page
- render CMS page
-
ALLOW_REGISTER, ALLOW_LOGIN:
- whitelist routes in config/frontend/enterprise/websiterestriction/full_action_names/generic/*
- if registration is enabled, whitelist routes in config/frontend/enterprise/websiterestriction/full_action_names/register/*
- see event
customer_registration_is_allowed
:
- website restriction can disable if enabled and not ALLOW_REGISTER
- mode 302 redirect to landing - whitelist only given cms page view and redirect
- non-whitelisted URL - redirect to login
allow none - nothing works, always stub
allow login or register - redirect to landing or login
add layout handle <restriction_privatesales_mode> only for ALLOW_REGISTER, ALLOW_LOGIN
- can use to display widget instance on 'page' mode
placeholder
processor
subprocessor
mymodule/processor
mymodule/processor
cache.xml:
mymodule/dynamic_block
SOME_CODE
<cache_lifetime>86400</cache_lifetime>
- app/etc/enterprise.xml: <request_processors> -- model processor
- mage::run() -> app.run() -> cache.processRequest -> enterprise_pagecache/processor.extractContent
- render cold page:
- after rendering dynamic blocks
a. save rendered dynamic blocks to cache -- own cache lifetime and tags. To be loaded from cache in applyWihoutApp!
b. wrap it in start/end tags content
- before final response in
controller_front_send_response_before
, extract dynamic blocks to save main page in cache without dynamic data
- render warm page:
- load base cache
- render and substitute all placeholders (render definitions) without app
- if not all rendered without app:
- replace request.{moduleName,controllerName,actionName} = pagecache/request/process
- save request.routingInfo - aliases, requested_{route,controller,action}
- pagecache/request/processAction -- container[].applyInApp -- process rest containers that need app
processor.extractContent:
- load cached by request id
- _processContent: -- process containers
- _processContainers -- container[].applyWithoutApp, if false, then return for further processing in app
- replace form key (cached as FORM_KEY_MARKER)
- replace session id (cached as SID_MARKER)
- if all containers processes without app, we are done! return content --> to be returned to app.run before init modules
- if unprocessed containers left, replace request routing info, return false
-- no direct response, normal magento flow ...
-- run our controller to replace containers in app
if left containers, pagecache/request/processAction:
container[].applyInApp(): -- cached page content as parameter
- _renderBlock -- place custom logic here!
- _applyToContent(cached page content, block content) -- just replace definition in cached with rendered value. start/end tags kept!
- if processor.subprocessor: -- won't save rendered to cache if missing!
- subprocessor.replaceContentToPlaceholderReplacer -- replace nested rendered blocks with definitions by kept start/end tags
- save rendered block to cache -- personal tags and lifetime
isAllowed - deny if:
- COOKIE['NO_CACHE']
- GET['no_cache']
- GET['SID']
canProcessRequest - above + following:
- check max depth -- number of GET params
- multicurrency ...
_createRequestIds (concat):
- path
- cookies:
- store
- currency
- customer group - 'CUSTOMER_INFO'
- customer logged in - 'CUSTOMER_AUTH'
- customer segment - 'CUSTOMER_SEGMENT_IDS'
- user allowed to save cookie
- design package
- getPageIdWithoutApp
- replaceContentToPlaceholderReplacer
instanceof processor_default
- allowCache
in constructor accepts string block definition key=value (container, block, cache_id, cache_lifetime + cache key info!)
splits definition and saves in _attributes
- getAttribute
- get{Start,End}Tag
- getPattern -- to match and replace containers
- _getCacheId -- must extend! default false = completely removed from cached page
- saveCache
- _saveCache
- applyWithoutApp -- extend. default try to load from cache
- applyInApp -- extend. default use _renderBlock
- _renderBlock -- extend! custom logic when in app. getLayout->getBlock->toHtml
should dispatch event
render_block
- _applyToContent(content, rendered) -- just replace definition in cached with rendered value
Notes:
placeholder is created in pagecache/config.getBlockPlaceholder.
container has access to placeholder it was created from, e.g.:
<!--{CONVERT_CERT_DYNAMIC
container="Convert_Cert_Model_Cache_Container_Dynamic"
block="Convert_Cert_Block_Dynamic"
cache_id="c1edfcf3858cef371e7eca712834ec17a50c6443" -- block.getCacheKey()
template="convert/cert/dynamic.phtml"
something="yes" xyz="33" -- from original block.getCacheKeyInfo
}-->
controller_action_predispatch_before -- disable standard block cache
core_block_abstract_to_html_after:
- collect all unique blocks cache tags:
- normal blocks - processor.requestTags[]
- blocks inside dynamic placeholder blocks - context block.addCacheTag
- renderBlockPlaceholder:
- block definition in place of dynamic content -- contains values of block->getCacheKeyInfo!
-- when later rendering in app in _renderBlock, can use $block->getAttribute
after model saved/deleted, validator.cleanEntityCache by model.getCacheIdTags == model.getId() + model._cacheTag
clean caches after category change (is active, include in menu)
- You install a payment bridge web-application on a separate server. Magento will communicate with it.
- unified interface for all operations
- separate server must be PCI compliant
- how does it handle different methods?
- how to implement more methods?
- ? 3rd-party payment applications accept and store credit cart info. -> you get token worth payment info.
Cardinal Centinel 3D Secure:
- Authorize.Net
- PayPal Payments Pro
- PayPal Payments Pro Payflow Edition (UK only)
- PayPal Payflow Pro
authorize.net CIM - Customer Information Manager - tokenize payment info
- token === hash of credit card, can bill many times
- token can include billing and shipping info
New merchant has successfully been added.
Data Transfer Key generated: 564546f3171538385c7bd61c299e6618
Merchant Key generated: 2b4b3d16d8b96c966014b6ea6ceb8bcb72c138ff61c9b477b3cd77f21d5d88b8
payment bridge instance - standalone
cc -> payment bridge method -> concrete method
model pbridge/payment_method_pbridge
< payment/method_abstract
model pbridge/model_payment_method_authorizenet
< pbridge/model_payment_method_abstract
-- all Pbridge methods here. (assignData, validate, ...)->proxy to Payment bridge instance
< payment/method_cc
-- save cc details
< payment/method_abstract
-- order/authorize/capture/cancel/void/....
block payment iframe used for:
- payment form -- all payment methods forms extend here
- payment review
- payment profile?
- admin: edit payment
_formBlockType - enterprise_pbridge/checkout_payment_abstract
-- define iframe source URL
< pbridge/iframe_abstract
-- add child block 'pbridge_iframe'
in template for form block, include iframe:
- getSourceUrl: .../bridge.php with encrypted params:
- !action = GatewayForm
- locale, order increment id (reserved)
- payment action = authorize
- amount, currency
- redirect url = enterprise_pbridge/pbridge/result
- customer id, name, billing/shipping addresses
- fill in credit card numbers: POST bridge.php
-? saves card numbers somewhere, generates token
- redirects to enterprise_pbridge/pbridge/result with encrypted data. token?
- magento is inside iframe (domain now same as parent):
- create hidden inputs with values under selected pbridge method:
-! token = efaefd79223bdf1970c276637289f5cc
- original_payment_method = authorizenet
- cc_last4 = 1111
- cc_type VI
- x_params
- calls parent js class payment.save - submits /onepage/savePayment with public info
assignData -> getPbridgeMethodInstance.assignData:
- save to info_instance: cc_last4, cc_type
- save pbridge_data (token, ori...) to info_instance.additionalData
- save token to pbridge session
validate -> getPbridgeMethodInstance.validate:
- ensure token is saved in additional data
assignData
validate
validate
authorize -> getPbridgeMethodInstance.authorize:
- build request:
- country
- order id
- !magento_payment_action = 'authorize' (can also be 'authorize_capture')
- !payment_action = place
- amount, currency
- client_identifier = hash(quote id)
- customer email, billing address, shipping address
- cart items -- reuse paypal/cart
- notify url -- /enterprise_pbridge/PbridgeIpn
- api.doAuthorize
- !api._prepareRequestParams: 'token', 'action' = 'Payments'
- _importResultToPayment: set payment.transaction_id, prepared message for transaction with original transaction id
- add response data to info_instance (order_payment)
bridge.php
Varien_Bridge.init.run
!requested action = from request 'action'
action class = Varien_Bridge_Action_Action{requested action}
Varien_Bridge_Action.runAction(action class):
- check allowed IP
- action class.execute
!gatewayCode = from request 'request_gateway_code'
gatewayConfig = merchant.getGateways(code):
- read from db config
- merge config:
- merchant - from db?
- !file config (cfg/payments.php 'payment_gateways'): title, class, active, ...
gateway = create from config 'class', e.g. Varien_Payment_Gateway_AuthorizeNet
add form fields to config -- from gateway.getFormFields
payment action = from request 'payment_action': place/capture/refund/void/cancel/accept/deny/fetch
-> proxy to Varien_Payment
-> some logic, proxy to current gateway
$_code
getFormFields
authorize
capture
void
refund
acceptPayment/denyPayment
fetchTransactionInfo
is3DSEnabled
...
$_COOKIE['currency'] ?
Varien_Http_Adapter_Curl
single-store mode:
- affects EAV attributes saving for catalog, store_id is always 0
cleanModelCache:
- clean cache ty tags getCacheTags: -- all blocks that depend on this model will be refreshed
- model._cacheTag, e.g.
catalog_category
-- refresh all blocks that depend on any category
- getCacheIdTags -- for each _cacheTag, adds
{tag}_{id}
-- all blocks that depend on specific category
- e.g. [
catalog_category
, catalog_category_1
]
getCacheKey -> getCacheKeyInfo -- variations block depends on, e.g. if customer logged in and current category --> cache.save(data, id)
getCacheTags -- internal data block depends on, e.g. when category is updated, content should be refreshed
- to add tags, there's no internal property, call addCacheTag
- to add dependency on specific model, e.g. category, call addModelTags -- e.g. will add tag
catalog_category_1
block data is saved to cache with tags it depends on
at the same time, same tags are saved to cache with themselves as dependency. wtf?
block:
- data(block::CACHE_TAGS_DATA_KEY)
- getCacheKeyInfo() -- extend. different behavior for different pages/customers/situations? put variations here
- getCacheKey() - builds key using getCacheKeyInfo
- getCacheTags() -- extend or use addCacheTag. put here all tags you depend on. changing one of them will refresh this block
- addCacheTag()
- addModelTags($model) -- if you depend on specific model, use this to clean cache when it updates
attribute backend?
attribute frontend?
dropdown types?
eav resource?
eav collection?
setup class?
?all attributes = Mage::getSingleton('eav/config')->getEntityAttributeCodes($this->getEntityType(), $object);
resource = singleton
product->getAttributes()
extends Mage_Core_Model_Resource_Abstract
-- which is stupid, only transactions and _prepareDataForTable
- getEntityType: child class must call setType, will trigger eav/config.getEntityType() -- load from
eav_entity_type
- load:
- load row from entity table without any attributes, assign to object
- loadAllAttributes:
- eav/config.getEntityAttributeCodes(entity type, object) ----- from customer_eav_attributes??? not eav_attribute.entity_type_id???
- getDefaultAttributes
- for all - getAttribute/addAttribute -- pupulates _attributeBy{Id,Code,Table}
- _loadModelAttributes: -- loads and assigns values
- groups all attributes by select from each table: select all from _varchar by customer id, from _int by customer id etc.
- union selects from all tables :) _varchar UNION _int UNION _datetime
- for each loaded attribute id and value:
- object.setData(code, value)
- attribute.backend.setEntityValueId
-- backend knows that for customer 23 varchar attribute row id is 73847. used later in collectSaveData
- _setOrigData
- _afterLoad: every attribute[].getBackend.afterLoad
- save: -- just compare orig/new data and do insertDuplicate/delete grouped by table
- delete if isDeleted
- if marked partialSave, loadAllAttributes -- unused
- _beforeSave
- all attribute[].backend.beforeSave
- _collectSaveData -- return ['newObject', 'entityRow', 'insert', 'update', 'delete']
- detects static fields, compares new data and old data
- entityRow -- _prepareStaticValue. all static fields regardless if changed or not. decimal type is locale processed
- decide on delete[]/update[] if attribute existed in original data
- insert[] non-empty new value
- _processSaveData:
-! object field value can be Zend_Db_Expr
- if entity_id defined, loads to ensure exists (of would need to insert with id)
- insert/update entity row depending on entity id
- for all collected insert/update/delete attributes, do _insertAttribute / _updateAttribute / _deleteAttributes
-- just remembers to future execution
- _processAttributeValues -- executes insertDuplicate/delete
- _afterSave
- all attribute[].backend.beforeSave
- loadAllAttributes:
- asks attribute codes from eav/config.getEntityAttributeCodes
- inits all attributes via getAttribute -- populating _attributesBy{Id,Code}
- special handling for default attributes, if don't exist in EAV, their attribute model is created in memory with fixed params
- getDefaultAttributeSourceModel
- getAttribute -- creates attribute model
- addAttribute -- register in _attributesBy{Id,Code,Table}
- _getDefaultAttributes -- extend
- getAttributesByCode
- setNewIncrementId
-- entityType.attribute_model from DB
- isStatic
- setAttributeModel -- why? attribute instance already created by
entity_type.attribute_model
- getBackend
- as assigned in
eav_attribute.backend_model
- attribute._getDefaultBackendModel -- extend, special cases for
created_at
, updated_at
, store_id
, increment_id
- getFrontend
- as assigned in
eav_attribute.frontend_model
-- do this
- attribute._getDefaultFrontendModel -- extend, not used
- usesSource -- only frontend_input == 'select', 'multiselect' OR
source_model
non-empty
- getSource
- as assigned in
eav_attribute.source_model
- attribute._getDefaultSourceModel
-> entity.getDefaultSourceModel -- extend, all catalog entities use
eav/entity_attribute_source_table
- select all options from DB
- setAttributeModel
lazy loaded on first attribute.getBackend
setAttribute -- always has attribute it works with
afterLoad
beforeSave
setEntityValueId
isStatic == attribute.isStatic
- getValue(object) -- extend
- if frontend_input is 'select' or 'boolean':
- attempts to load attribute.source.getOptionText
- or gets value from falback
entity_attribute_source_boolean
-- yes/no
- if frontend_input is 'multiselect' (value is comma-separated), gets source.getOptionText and joins 'value1, value2'
- getLabel
- returns
attribute.frontend_label
or falls back to attribute_code
- getOption(optionId)
- delegates to attribute.source.getOptionText
getAllOptions -- returns []['label', 'value']
getOptionText -- text by id
getOptionId -- by both id or label
getIndexOptionText -- for search indexing
!default source model eav/entity_attribute_source_config:
- _configNodePath -- extend and options will be taken from config node
getEntityIdField
getAttributeModel -- e.g. customer/attribute
- addAttributeToFilter('name', ['in' => ['One', 'Two']])
- addAttributeToSelect('*')
- load(true) - print result SQL
Mage_Eav_Model_Resource_Entity_Attribute_Collection
- extends plain resource of course
has children that join additional table (customer_eav_attribute
, catalog_eav_attribute
) or form in _initSelect
-getAttribute: -- returns attribute model
-getEntityType(code):
- creates new eav/entity_type.loadByCode(code)
-getEntityAttributeCodes
- respects store and attribute set - read from object argument
- used by entity when loading object
- _initAttributes: ?? study more?
- reads from
eav_attribute
+ eav_entity_attribute
(or customer_eav_attribute
) table by entity type, joins additiaonl table?
- creates models for all attributes, returnes codes
getEntityAttributeCollection
getAdditionalAttributeTable -- catalog_eav_attribute, customer_eav_attribute
_load(attribute)
getAttributeModel -- by default eav/entity_attribute; can also be e.g. customer/attribute
eav_entity -- deprecated
eav_entity_attribute -- has data. why?
eav_entity_{type} -- deprecated
customer_entity
customer_entity_{type}
customer_eav_attribute -- additional table
catalog_product_entity
catalog_product_entity_{type}
catalog_eav_attribute -- additional table
! \Mage_Eav_Model_Resource_Attribute_Collection::_getEavWebsiteTable
customer_eav_attribute_website
Mage_Eav_Model_Resource_Attribute_Collection::_initSelect
joins additional table and attribute scope
\Mage_Catalog_Model_Resource_Abstract::_canUpdateAttribute
if ($result &&
($attribute->isScopeStore() || $attribute->isScopeWebsite()) &&
!$this->_isAttributeValueEmpty($attribute, $value) &&
$value == $origData[$attribute->getAttributeCode()] &&
isset($origData['store_id']) && $origData['store_id'] != $this->getDefaultStoreId()
\Mage_Catalog_Model_Resource_Abstract::_insertAttribute
attribute = eav/config.getAttribute(customer, first_name)
attribute.getSource.getAllOptions
or see eav/entity_attribute_source_table
that is default source model for product attributes
so any attribute of entity_type catalog_product
attribute.getFrontend.getAllOptions will load from DB:
$collection = Mage::getResourceModel('eav/entity_attribute_option_collection')
->setPositionOrder('asc')
->setAttributeFilter($this->getAttribute()->getId())
->setStoreFilter($this->getAttribute()->getStoreId())
->load();
$this->_options[$storeId] = $collection->toOptionArray();
_entityTypeId = Mage::getModel('eav/entity')->setType(Mage_Catalog_Model_Product::ENTITY)->getTypeId();
catalog attribute:
- model
Mage_Catalog_Model_Resource_Eav_Attribute
- resource model
Mage_Catalog_Model_Resource_Attribute
- support
catalog_eav_attribute.apply_to
catalog attributes can apply to specific product types. if not, entity skips attribute
- support store_id and single-store mode
source:
- eav/entity_attribute_source_abstract
- _config -- extend, load from config node
- _store -- load stores from DB
- _table -- load all options from DB
backend:
- eav/entity_attribute_backend_default = abstract
- _datetime -- normalize to format
yyyy-MM-dd HH:mm:ss
- _array -- join(', ', values)
- _serialized
frontend:
- eav/entity-attribute_frontend_default = abstract
- _datetime --
ISO_8601
, date('c'), 2004-02-12T15:19:21+00:00
-
getEntityTypeId
-
{get,add,update,remove}EntityType
-
{get,add,update,remove}AttributeSet
-
{get,add.update,remove}AttributeGroup
-
get{AttributeSet,DefaultAttributeSet}Id
-
get{AttributeGroup,DefaultAttributeGroup}Id
-
_prepareValues -- extend, defaults for addAttribute
-
getAttribute
-
addAttribute -- if exists, will updateAttribute
-
updateAttribute
-
removeAttribute
-
addAttributeOption
-
getAttributeTable
-
addAttributeTo{Set,Group}
-
getDefaultEntities -- extend
-
installEntities -- supply of used getDefaultEntities
- []: addEntityType, addAttribute, setDefaultSetToEntityType
-
createEntityTables -- has bug here?
singleton
klarnacheckout/observer
apiCopyCollectionPaymentReservationNumber
getAllItems()
-- see property _nominalOnly. null - all, true - nominal, false - non-nominal
-- see quote_item.isNominal == item.product is recurring
getAllNonNominalItems() -- getAllItems + property _nominalOnly = false
getAllNominalItems() -- getAllItems + property _nominalOnly
getAllVisibleItems()
importCustomerAddress -- using fieldset
exportCustomerAddress -- to customer address
importOrderAddress -- when editing order?
toArray - rates, items, totals
-- model rule/condition_abstract
implements Mage_Rule_Model_Condition_Interface
- asHtml: type + element + operator + value + removeLink + chooserContainer
rule/condition_abstract
> rule/condition_product_abstract
salesrule/rule_condition_product
-- adds attributes {Qty,Price,Row total} in cart
enterpise_targetrule/rule_condition_product_attributes
....
- items
- info
- create -- supports individual qty - part shipment
- {add,remove,info}Track --
sales/order_shipment_track
- getCarriers -- only with isTrackingAvailable
- sendInfo -- shipment.sendMail
- addComment
- items
- info
- create -- supports itemsQty - part invoice
- capture -- whole invoice
- void -- whole invoice
- cancel -- whole invoice
- addComment
- items
- info
- create -- supports qtys, store credit amount
- cancel -- whole
- addComment --
sales/order_creditmemo_comment
Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($collection);
$product->isVisibleInCatalog()
product collection.addAttributeToSelect(model catalog/config.getProductAttributes)