Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Fri, Feb 21, 11:59 PM

in-portal

This file is larger than 256 KB, so syntax highlighting was skipped.
Index: branches/5.3.x/LICENSE
===================================================================
--- branches/5.3.x/LICENSE (revision 16194)
+++ branches/5.3.x/LICENSE (revision 16195)
Property changes on: branches/5.3.x/LICENSE
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/LICENSE:r16124-16194
Index: branches/5.3.x/robots.txt
===================================================================
--- branches/5.3.x/robots.txt (revision 16194)
+++ branches/5.3.x/robots.txt (revision 16195)
Property changes on: branches/5.3.x/robots.txt
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/robots.txt:r16124-16194
Index: branches/5.3.x/core/kernel/db/cat_event_handler.php
===================================================================
--- branches/5.3.x/core/kernel/db/cat_event_handler.php (revision 16194)
+++ branches/5.3.x/core/kernel/db/cat_event_handler.php (revision 16195)
@@ -1,3101 +1,3105 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class kCatDBEventHandler extends kDBEventHandler {
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array(
'OnSaveSettings' => Array ('self' => 'add|edit|advanced:import'),
'OnResetSettings' => Array ('self' => 'add|edit|advanced:import'),
'OnBeforeDeleteOriginal' => Array ('self' => 'edit|advanced:approve'),
'OnAfterDeleteOriginal' => Array ('self' => 'edit|advanced:approve'),
'OnCopy' => Array ('self' => true),
'OnDownloadFile' => Array ('self' => 'view'),
'OnCancelAction' => Array ('self' => true),
'OnItemBuild' => Array ('self' => true),
'OnMakeVote' => Array ('self' => true),
'OnReviewHelpful' => Array ('self' => true),
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
/**
* Load item if id is available
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function LoadItem(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$id = $this->getPassedID($event);
if ( $object->Load($id) ) {
$actions = $this->Application->recallObject('kActions');
/* @var $actions Params */
$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
$use_pending_editing = $event->getUnitConfig()->getUsePendingEditing();
if ( $use_pending_editing && $event->Special != 'original' ) {
$this->Application->SetVar($event->Prefix . '.original_id', $object->GetDBField('OrgId'));
}
}
else {
$object->setID($id);
}
}
/**
* Checks user permission to execute given $event
*
* @param kEvent $event
* @return bool
* @access public
*/
public function CheckPermission(kEvent $event)
{
if ( !$this->Application->isAdmin ) {
if ( $event->Name == 'OnSetSortingDirect' ) {
// allow sorting on front event without view permission
return true;
}
}
if ( $event->Name == 'OnExport' ) {
// save category_id before doing export
$this->Application->LinkVar('m_cat_id');
}
if ( in_array($event->Name, $this->_getMassPermissionEvents()) ) {
$items = $this->_getPermissionCheckInfo($event);
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
if ( ($event->Name == 'OnSave') && array_key_exists(0, $items) ) {
// adding new item (ID = 0)
$perm_value = $perm_helper->AddCheckPermission($items[0]['CategoryId'], $event->Prefix) > 0;
}
else {
// leave only items, that can be edited
$ids = Array ();
$check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission';
foreach ($items as $item_id => $item_data) {
if ( $perm_helper->$check_method($item_data['CreatedById'], $item_data['CategoryId'], $event->Prefix) > 0 ) {
$ids[] = $item_id;
}
}
if ( !$ids ) {
// no items left for editing -> no permission
return $perm_helper->finalizePermissionCheck($event, false);
}
$perm_value = true;
$event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method
}
return $perm_helper->finalizePermissionCheck($event, $perm_value);
}
$export_events = Array ('OnSaveSettings', 'OnResetSettings', 'OnExportBegin');
if ( in_array($event->Name, $export_events) ) {
// when import settings before selecting target import category
return $this->Application->CheckPermission('in-portal:main_import.view');
}
if ( $event->Name == 'OnProcessSelected' ) {
if ( $this->Application->RecallVar('dst_field') == 'ImportCategory' ) {
// when selecting target import category
return $this->Application->CheckPermission('in-portal:main_import.view');
}
}
return parent::CheckPermission($event);
}
/**
* Returns events, that require item-based (not just event-name based) permission check
*
* @return Array
*/
function _getMassPermissionEvents()
{
return Array (
'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove',
'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown',
'OnCut',
);
}
/**
* Returns category item IDs, that require permission checking
*
* @param kEvent $event
* @return string
*/
function _getPermissionCheckIDs($event)
{
if ($event->Name == 'OnSave') {
$selected_ids = implode(',', $this->getSelectedIDs($event, true));
if (!$selected_ids) {
$selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave)
}
}
else {
// OnEdit, OnMassDelete events, when items are checked in grid
$selected_ids = implode(',', $this->StoreSelectedIDs($event));
}
return $selected_ids;
}
/**
* Returns information used in permission checking
*
* @param kEvent $event
* @return Array
*/
function _getPermissionCheckInfo($event)
{
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
// when saving data from temp table to live table check by data from temp table
$item_ids = $this->_getPermissionCheckIDs($event);
$items = $perm_helper->GetCategoryItemData($event->Prefix, $item_ids, $event->Name == 'OnSave');
if (!$items) {
// when item not present in temp table, then permission is not checked, because there are no data in db to check
$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
list ($id, $fields_hash) = each($items_info);
if (array_key_exists('CategoryId', $fields_hash)) {
$item_category = $fields_hash['CategoryId'];
}
else {
$item_category = $this->Application->GetVar('m_cat_id');
}
$items[$id] = Array (
'CreatedById' => $this->Application->RecallVar('use_id'),
'CategoryId' => $item_category,
);
}
return $items;
}
/**
* Add selected items to clipboard with mode = COPY (CLONE)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCopy($event)
{
$this->Application->RemoveVar('clipboard');
$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
/* @var $clipboard_helper kClipboardHelper */
$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
$this->clearSelectedIDs($event);
}
/**
* Add selected items to clipboard with mode = CUT
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCut($event)
{
$this->Application->RemoveVar('clipboard');
$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
/* @var $clipboard_helper kClipboardHelper */
$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
$this->clearSelectedIDs($event);
}
/**
* Checks permission for OnPaste event
*
* @param kEvent $event
* @return bool
*/
function _checkPastePermission($event)
{
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$category_id = $this->Application->GetVar('m_cat_id');
if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) {
// no items left for editing -> no permission
return $perm_helper->finalizePermissionCheck($event, false);
}
return true;
}
/**
* Performs category item paste
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPaste($event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) {
$event->status = kEvent::erFAIL;
return;
}
$clipboard_data = $event->getEventParam('clipboard_data');
if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) {
return;
}
if ( $clipboard_data['copy'] ) {
$temp = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp kTempTablesHandler */
$this->Application->SetVar('ResetCatBeforeClone', 1); // used in "kCatDBEventHandler::OnBeforeClone"
$temp->CloneItems($event->Prefix, $event->Special, $clipboard_data['copy']);
}
if ( $clipboard_data['cut'] ) {
$object = $this->Application->recallObject($event->getPrefixSpecial() . '.item', $event->Prefix, Array ('skip_autoload' => true));
/* @var $object kCatDBItem */
foreach ($clipboard_data['cut'] as $id) {
$object->Load($id);
$object->MoveToCat();
}
}
}
/**
* Deletes all selected items.
* Automatically recurse into sub-items using temp handler, and deletes sub-items
* by calling its Delete method if sub-item has AutoDelete set to true in its config file
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnMassDelete(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$ids = $this->StoreSelectedIDs($event);
$to_delete = Array ();
$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
if ( $recycle_bin ) {
$rb = $this->Application->recallObject('c.recycle', NULL, array ('skip_autoload' => true));
/* @var $rb CategoriesItem */
$rb->Load($recycle_bin);
$object = $this->Application->recallObject($event->Prefix . '.recycleitem', NULL, Array ('skip_autoload' => true));
/* @var $object kCatDBItem */
foreach ($ids as $id) {
$object->Load($id);
if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $object->GetDBField('ParentPath')) ) {
$to_delete[] = $id;
continue;
}
$object->MoveToCat($recycle_bin);
}
$ids = $to_delete;
}
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$event->setEventParam('ids', $ids);
$this->customProcessing($event, 'before');
$ids = $event->getEventParam('ids');
if ( $ids ) {
$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
}
$this->clearSelectedIDs($event);
}
/**
* Return type clauses for list bulding on front
*
* @param kEvent $event
* @return Array
*/
function getTypeClauses($event)
{
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
$except_types = $event->getEventParam('except');
$except_types = $except_types ? explode(',', $except_types) : Array ();
$type_clauses = Array();
$user_id = $this->Application->RecallVar('user_id');
$owner_field = $this->getOwnerField($event->Prefix);
$type_clauses['my_items']['include'] = '%1$s.'.$owner_field.' = '.$user_id;
$type_clauses['my_items']['except'] = '%1$s.'.$owner_field.' <> '.$user_id;
$type_clauses['my_items']['having_filter'] = false;
$type_clauses['pick']['include'] = '%1$s.EditorsPick = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
$type_clauses['pick']['except'] = '%1$s.EditorsPick! = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
$type_clauses['pick']['having_filter'] = false;
$type_clauses['hot']['include'] = '`IsHot` = 1 AND PrimaryCat = 1';
$type_clauses['hot']['except'] = '`IsHot`! = 1 AND PrimaryCat = 1';
$type_clauses['hot']['having_filter'] = true;
$type_clauses['pop']['include'] = '`IsPop` = 1 AND PrimaryCat = 1';
$type_clauses['pop']['except'] = '`IsPop`! = 1 AND PrimaryCat = 1';
$type_clauses['pop']['having_filter'] = true;
$type_clauses['new']['include'] = '`IsNew` = 1 AND PrimaryCat = 1';
$type_clauses['new']['except'] = '`IsNew`! = 1 AND PrimaryCat = 1';
$type_clauses['new']['having_filter'] = true;
$type_clauses['displayed']['include'] = '';
$displayed = $this->Application->GetVar($event->Prefix.'_displayed_ids');
if ($displayed) {
$id_field = $event->getUnitConfig()->getIDField();
$type_clauses['displayed']['except'] = '%1$s.'.$id_field.' NOT IN ('.$displayed.')';
}
else {
$type_clauses['displayed']['except'] = '';
}
$type_clauses['displayed']['having_filter'] = false;
if (in_array('search', $types) || in_array('search', $except_types)) {
$event_mapping = Array (
'simple' => 'OnSimpleSearch',
'subsearch' => 'OnSubSearch',
'advanced' => 'OnAdvancedSearch'
);
$keywords = $event->getEventParam('keyword_string');
$type = $this->Application->GetVar('search_type', 'simple');
if ( $keywords ) {
// processing keyword_string param of ListProducts tag
$this->Application->SetVar('keywords', $keywords);
$type = 'simple';
}
$search_event = $event_mapping[$type];
$this->$search_event($event);
$object = $event->getObject();
/* @var $object kDBList */
$search_sql = ' FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search search_result
JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
$sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL());
$object->SetSelectSQL($sql);
$object->addCalculatedField('Relevance', 'search_result.Relevance');
$type_clauses['search']['include'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Categories.Status = '.STATUS_ACTIVE.')';
$type_clauses['search']['except'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Categories.Status = '.STATUS_ACTIVE.')';
$type_clauses['search']['having_filter'] = false;
}
if (in_array('related', $types) || in_array('related', $except_types)) {
$related_to = $event->getEventParam('related_to');
if (!$related_to) {
$related_prefix = $event->Prefix;
}
else {
$sql = 'SELECT Prefix
FROM '.TABLE_PREFIX.'ItemTypes
WHERE ItemName = '.$this->Conn->qstr($related_to);
$related_prefix = $this->Conn->GetOne($sql);
}
$rel_table = $this->Application->getUnitConfig('rel')->getTableName();
$item_type = (int)$event->getUnitConfig()->getItemType();
if ($item_type == 0) {
trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING);
}
// process case, then this list is called inside another list
$prefix_special = $event->getEventParam('PrefixSpecial');
if (!$prefix_special) {
$prefix_special = $this->Application->Parser->GetParam('PrefixSpecial');
}
$id = false;
if ($prefix_special !== false) {
$processed_prefix = $this->Application->processPrefix($prefix_special);
if ($processed_prefix['prefix'] == $related_prefix) {
// printing related categories within list of items (not on details page)
$list = $this->Application->recallObject($prefix_special);
/* @var $list kDBList */
$id = $list->GetID();
}
}
if ($id === false) {
// printing related categories for single item (possibly on details page)
if ($related_prefix == 'c') {
$id = $this->Application->GetVar('m_cat_id');
}
else {
$id = $this->Application->GetVar($related_prefix . '_id');
}
}
$p_item = $this->Application->recallObject($related_prefix.'.current', NULL, Array('skip_autoload' => true));
/* @var $p_item kCatDBItem */
$p_item->Load( (int)$id );
$p_resource_id = $p_item->GetDBField('ResourceId');
$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
WHERE
(Enabled = 1)
AND (
(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
OR
(Type = 1
AND (
(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
OR
(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
)
)
)';
$related_ids_array = $this->Conn->Query($sql);
$related_ids = Array();
foreach ($related_ids_array as $record) {
$related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ];
}
if (count($related_ids) > 0) {
$type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
}
else {
$type_clauses['related']['include'] = '0';
$type_clauses['related']['except'] = '1';
}
$type_clauses['related']['having_filter'] = false;
}
if (in_array('favorites', $types) || in_array('favorites', $except_types)) {
$sql = 'SELECT ResourceId
FROM ' . $this->Application->getUnitConfig('fav')->getTableName() . '
WHERE PortalUserId = '.$this->Application->RecallVar('user_id');
$favorite_ids = $this->Conn->GetCol($sql);
if ($favorite_ids) {
$type_clauses['favorites']['include'] = '%1$s.ResourceId IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
$type_clauses['favorites']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
}
else {
$type_clauses['favorites']['include'] = 0;
$type_clauses['favorites']['except'] = 1;
}
$type_clauses['favorites']['having_filter'] = false;
}
return $type_clauses;
}
/**
* Returns SQL clause, that will help to select only data from specified category & it's children
*
* @param int $category_id
* @return string
*/
function getCategoryLimitClause($category_id)
{
if (!$category_id) {
return false;
}
$tree_indexes = $this->Application->getTreeIndex($category_id);
if (!$tree_indexes) {
// id of non-existing category was given
return 'FALSE';
}
return TABLE_PREFIX.'Categories.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight'];
}
/**
* Apply any custom changes to list's sql query
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetCustomQuery(kEvent $event)
{
parent::SetCustomQuery($event);
$object = $event->getObject();
/* @var $object kCatDBList */
// add category filter if needed
if ($event->Special != 'showall' && $event->Special != 'user') {
if ( (string)$event->getEventParam('parent_cat_id') !== '' ) {
$parent_cat_id = $event->getEventParam('parent_cat_id');
}
else {
$parent_cat_id = $this->Application->GetVar('c_id');
if (!$parent_cat_id) {
$parent_cat_id = $this->Application->GetVar('m_cat_id');
}
if (!$parent_cat_id) {
$parent_cat_id = 0;
}
}
if ("$parent_cat_id" == '0') {
// replace "0" category with "Content" category id (this way template
$parent_cat_id = $this->Application->getBaseCategory();
}
if ((string)$parent_cat_id != 'any') {
if ($event->getEventParam('recursive')) {
$filter_clause = $this->getCategoryLimitClause($parent_cat_id);
if ($filter_clause !== false) {
$object->addFilter('category_filter', $filter_clause);
}
$object->addFilter('primary_filter', 'PrimaryCat = 1');
}
else {
$object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.CategoryId = '.$parent_cat_id );
}
}
else {
$object->addFilter('primary_filter', 'PrimaryCat = 1');
}
}
else {
$object->addFilter('primary_filter', 'PrimaryCat = 1');
// if using recycle bin don't show items from there
$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
if ($recycle_bin) {
$object->addFilter('recyclebin_filter', TABLE_PREFIX.'CategoryItems.CategoryId <> '.$recycle_bin);
}
}
if ($event->Special == 'user') {
$editable_user = $this->Application->GetVar('u_id');
$object->addFilter('owner_filter', '%1$s.'.$this->getOwnerField($event->Prefix).' = '.$editable_user);
}
$this->applyViewPermissionFilter($object);
$types = $event->getEventParam('types');
$this->applyItemStatusFilter($object, $types);
$except_types = $event->getEventParam('except');
$type_clauses = $this->getTypeClauses($event);
$search_helper = $this->Application->recallObject('SearchHelper');
/* @var $search_helper kSearchHelper */
$search_helper->SetComplexFilter($event, $type_clauses, $types, $except_types);
}
/**
* Adds filter, that uses *.VIEW permissions to determine if an item should be shown to a user.
*
* @param kCatDBList $object Object.
*
* @return void
* @access protected
*/
protected function applyViewPermissionFilter(kCatDBList $object)
{
if ( !$this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) {
return;
}
if ( $this->Application->RecallVar('user_id') == USER_ROOT ) {
// for "root" CATEGORY.VIEW permission is checked for items lists too
$view_perm = 1;
}
else {
// for any real user item list view permission is checked instead of CATEGORY.VIEW
$count_helper = $this->Application->recallObject('CountHelper');
/* @var $count_helper kCountHelper */
list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($object->Prefix, 'perm');
$object->addFilter('perm_filter2', $view_filter);
}
$object->addFilter('perm_filter', 'perm.PermId = ' . $view_perm);
}
/**
* Adds filter that filters out items with non-required statuses
*
* @param kDBList $object
* @param string $types
*/
function applyItemStatusFilter(&$object, $types)
{
// Link1 (before modifications) [Status = 1, OrgId = NULL], Link2 (after modifications) [Status = -2, OrgId = Link1_ID]
$pending_editing = $object->getUnitConfig()->getUsePendingEditing();
if (!$this->Application->isAdminUser) {
$types = explode(',', $types);
if (in_array('my_items', $types)) {
$allow_statuses = Array (STATUS_ACTIVE, STATUS_PENDING, STATUS_PENDING_EDITING);
$object->addFilter('status_filter', '%1$s.Status IN ('.implode(',', $allow_statuses).')');
if ($pending_editing) {
$user_id = $this->Application->RecallVar('user_id');
$this->applyPendingEditingFilter($object, $user_id);
}
}
else {
$object->addFilter('status_filter', '(%1$s.Status = ' . STATUS_ACTIVE . ') AND (' . TABLE_PREFIX . 'Categories.Status = ' . STATUS_ACTIVE . ')');
if ($pending_editing) {
// if category item uses pending editing abilities, then in no cases show pending copies on front
$object->addFilter('original_filter', '%1$s.OrgId = 0 OR %1$s.OrgId IS NULL');
}
}
}
else {
if ($pending_editing) {
$this->applyPendingEditingFilter($object);
}
}
}
/**
* Adds filter, that removes live items if they have pending editing copies
*
* @param kDBList $object
* @param int $user_id
*/
function applyPendingEditingFilter(&$object, $user_id = NULL)
{
$sql = 'SELECT OrgId
FROM '.$object->TableName.'
WHERE Status = '.STATUS_PENDING_EDITING.' AND OrgId IS NOT NULL';
if (isset($user_id)) {
$owner_field = $this->getOwnerField($object->Prefix);
$sql .= ' AND '.$owner_field.' = '.$user_id;
}
$pending_ids = $this->Conn->GetCol($sql);
if ($pending_ids) {
$object->addFilter('no_original_filter', '%1$s.'.$object->IDField.' NOT IN ('.implode(',', $pending_ids).')');
}
}
/**
* Adds calculates fields for item statuses
*
* @param kDBItem|kDBList $object
* @param kEvent $event
* @return void
* @access protected
*/
protected function prepareObject(&$object, kEvent $event)
{
$this->prepareItemStatuses($event);
$object->addCalculatedField('CachedNavbar', 'l' . $this->Application->GetVar('m_lang') . '_CachedNavbar');
if ( $event->Special == 'export' || $event->Special == 'import' ) {
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_helper kCatDBItemExportHelper */
$export_helper->prepareExportColumns($event);
}
}
/**
* Creates calculated fields for all item statuses based on config settings
*
* @param kEvent $event
*/
function prepareItemStatuses($event)
{
$object = $event->getObject( Array('skip_autoload' => true) );
$property_map = $event->getUnitConfig()->getItemPropertyMappings();
if (!$property_map) {
return ;
}
// new items
$object->addCalculatedField('IsNew', ' IF(%1$s.NewItem = 2,
IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - ' . $this->Application->ConfigValue($property_map['NewDays']) . '*3600*24), 1, 0),
%1$s.NewItem
)');
// hot items (cache updated every hour)
if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
$serial_name = $this->Application->incrementCacheSerial($event->Prefix, NULL, false);
$hot_limit = $this->Application->getCache($property_map['HotLimit'] . '[%' . $serial_name . '%]');
}
else {
$hot_limit = $this->Application->getDBCache($property_map['HotLimit']);
}
if ($hot_limit === false) {
$hot_limit = $this->CalculateHotLimit($event);
}
$object->addCalculatedField('IsHot', ' IF(%1$s.HotItem = 2,
IF(%1$s.'.$property_map['ClickField'].' >= '.$hot_limit.', 1, 0),
%1$s.HotItem
)');
// popular items
$object->addCalculatedField('IsPop', ' IF(%1$s.PopItem = 2,
IF(%1$s.CachedVotesQty >= ' . $this->Application->ConfigValue($property_map['MinPopVotes']) . ' AND %1$s.CachedRating >= ' . $this->Application->ConfigValue($property_map['MinPopRating']) . ', 1, 0),
%1$s.PopItem)');
}
/**
* Calculates hot limit for current item's table
*
* @param kEvent $event
* @return float
* @access protected
*/
protected function CalculateHotLimit($event)
{
$config = $event->getUnitConfig();
$property_map = $config->getItemPropertyMappings();
if ( !$property_map ) {
return 0.00;
}
$click_field = $property_map['ClickField'];
$last_hot = $this->Application->ConfigValue($property_map['MaxHotNumber']) - 1;
$sql = 'SELECT ' . $click_field . '
FROM ' . $config->getTableName() . '
ORDER BY ' . $click_field . ' DESC
LIMIT ' . $last_hot . ', 1';
$res = $this->Conn->GetCol($sql);
$hot_limit = (double)array_shift($res);
if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
$serial_name = $this->Application->incrementCacheSerial($event->Prefix, NULL, false);
$this->Application->setCache($property_map['HotLimit'] . '[%' . $serial_name . '%]', $hot_limit);
}
else {
$this->Application->setDBCache($property_map['HotLimit'], $hot_limit, 3600);
}
return $hot_limit;
}
/**
* Moves item to preferred category, updates item hits
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemUpdate(kEvent $event)
{
parent::OnBeforeItemUpdate($event);
$object = $event->getObject();
/* @var $object kCatDBItem */
// update hits field
$config = $event->getUnitConfig();
$property_map = $config->getUserProfileMapping();
if ( $property_map ) {
$click_field = $property_map['ClickField'];
if ( $this->Application->isAdminUser && ($this->Application->GetVar($click_field . '_original') !== false) && floor($this->Application->GetVar($click_field . '_original')) != $object->GetDBField($click_field) ) {
$sql = 'SELECT MAX(' . $click_field . ')
FROM ' . $config->getTableName() . '
WHERE FLOOR(' . $click_field . ') = ' . $object->GetDBField($click_field);
$hits = ($res = $this->Conn->GetOne($sql)) ? $res + 0.000001 : $object->GetDBField($click_field);
$object->SetDBField($click_field, $hits);
}
}
// change category
$target_category = $object->GetDBField('CategoryId');
if ( $object->GetOriginalField('CategoryId') != $target_category ) {
$object->MoveToCat($target_category);
}
}
/**
* Occurs after loading item, 'id' parameter
* allows to get id of item that was loaded
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemLoad(kEvent $event)
{
parent::OnAfterItemLoad($event);
$special = substr($event->Special, -6);
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( $special == 'import' || $special == 'export' ) {
$image_data = $object->getPrimaryImageData();
if ( $image_data ) {
$thumbnail_image = $image_data[$image_data['LocalThumb'] ? 'ThumbPath' : 'ThumbUrl'];
if ( $image_data['SameImages'] ) {
$full_image = '';
}
else {
$full_image = $image_data[$image_data['LocalImage'] ? 'LocalPath' : 'Url'];
}
$object->SetDBField('ThumbnailImage', $thumbnail_image);
$object->SetDBField('FullImage', $full_image);
$object->SetDBField('ImageAlt', $image_data['AltName']);
}
}
// substituting pending status value for pending editing
if ( $object->HasField('OrgId') && $object->GetDBField('OrgId') > 0 && $object->GetDBField('Status') == -2 ) {
$new_options = Array ();
$options = $object->GetFieldOption('Status', 'options', false, Array ());
foreach ($options as $key => $val) {
if ( $key == 2 ) {
$key = -2;
}
$new_options[$key] = $val;
}
$object->SetFieldOption('Status', 'options', $new_options);
}
if ( !$this->Application->isAdmin ) {
// linking existing images for item with virtual fields
$image_helper = $this->Application->recallObject('ImageHelper');
/* @var $image_helper ImageHelper */
$image_helper->LoadItemImages($object);
// linking existing files for item with virtual fields
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$file_helper->LoadItemFiles($object);
}
if ( $object->isVirtualField('MoreCategories') ) {
// set item's additional categories to virtual field (used in editing)
$item_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
$object->SetDBField('MoreCategories', $item_categories ? '|' . implode('|', $item_categories) . '|' : '');
}
}
/**
* Occurs after updating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemUpdate(kEvent $event)
{
parent::OnAfterItemUpdate($event);
$this->CalculateHotLimit($event);
if ( substr($event->Special, -6) == 'import' ) {
$this->setCustomExportColumns($event);
}
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( !$this->Application->isAdmin ) {
$image_helper = $this->Application->recallObject('ImageHelper');
/* @var $image_helper ImageHelper */
// process image upload in virtual fields
$image_helper->SaveItemImages($object);
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
// process file upload in virtual fields
$file_helper->SaveItemFiles($object);
if ( $event->Special != '-item' ) {
// don't touch categories during cloning
$this->processAdditionalCategories($object, 'update');
}
}
$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
if ( $this->Application->isAdminUser && $recycle_bin ) {
$sql = 'SELECT CategoryId
FROM ' . $this->Application->getUnitConfig('ci')->getTableName() . '
WHERE ItemResourceId = ' . $object->GetDBField('ResourceId') . ' AND PrimaryCat = 1';
$primary_category = $this->Conn->GetOne($sql);
if ( $primary_category == $recycle_bin ) {
$event->CallSubEvent('OnAfterItemDelete');
}
}
if ( $object->GetChangedFields() ) {
$now = time();
$object->SetDBField('Modified_date', $now);
$object->SetDBField('Modified_time', $now);
$object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
}
}
/**
* Sets values for import process
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemCreate(kEvent $event)
{
parent::OnAfterItemCreate($event);
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( substr($event->Special, -6) == 'import' ) {
$this->setCustomExportColumns($event);
}
$object->assignPrimaryCategory();
if ( !$this->Application->isAdmin ) {
$image_helper = $this->Application->recallObject('ImageHelper');
/* @var $image_helper ImageHelper */
// process image upload in virtual fields
$image_helper->SaveItemImages($object);
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
// process file upload in virtual fields
$file_helper->SaveItemFiles($object);
if ( $event->Special != '-item' ) {
// don't touch categories during cloning
$this->processAdditionalCategories($object, 'create');
}
}
}
/**
* Make record to search log
*
* @param string $keywords
* @param int $search_type 0 - simple search, 1 - advanced search
*/
function saveToSearchLog($keywords, $search_type = 0)
{
// don't save keywords for each module separately, just one time
// static variable can't help here, because each module uses it's own class instance !
if (!$this->Application->GetVar('search_logged')) {
$sql = 'UPDATE '.TABLE_PREFIX.'SearchLogs
SET Indices = Indices + 1
WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search
$this->Conn->Query($sql);
if ($this->Conn->getAffectedRows() == 0) {
$fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type);
$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLogs');
}
$this->Application->SetVar('search_logged', 1);
}
}
/**
* Makes simple search for category items
* based on keywords string
*
* @param kEvent $event
*/
function OnSimpleSearch($event)
{
$event->redirect = false;
$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
$keywords = $this->Application->unescapeRequestVariable(trim($this->Application->GetVar('keywords')));
$query_object = $this->Application->recallObject('kHTTPQuery');
/* @var $query_object kHTTPQuery */
$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
if(!isset($query_object->Get['keywords']) &&
!isset($query_object->Post['keywords']) &&
$this->Conn->Query($sql))
{
return; // used when navigating by pages or changing sorting in search results
}
if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length'))
{
$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
$this->Application->SetVar('keywords_too_short', 1);
return; // if no or too short keyword entered, doing nothing
}
$this->Application->StoreVar('keywords', $keywords);
$this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search
$event->setPseudoClass('_List');
$object = $event->getObject();
/* @var $object kDBList */
$config = $event->getUnitConfig();
$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
$lang = $this->Application->GetVar('m_lang');
$items_table = $config->getTableName();
$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
$sql = 'SELECT *
FROM ' . $this->Application->getUnitConfig('confs')->getTableName() . '
WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1';
$search_config = $this->Conn->Query($sql, 'FieldName');
$field_list = array_keys($search_config);
$join_clauses = Array();
// field processing
$weight_sum = 0;
$alias_counter = 0;
$custom_fields = $config->getCustomFields();
if ($custom_fields) {
$custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName();
$join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
}
// what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table))
$search_config_map = Array();
foreach ($field_list as $key => $field) {
$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
// processing multilingual fields
if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
$field_list[$key] = 'l'.$lang.'_'.$field;
if (!isset($search_config[$field]['ForeignField'])) {
$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
}
// processing fields from other tables
$foreign_field = $search_config[$field]['ForeignField'];
if ( $foreign_field ) {
$exploded = explode(':', $foreign_field, 2);
if ($exploded[0] == 'CALC') {
// ignoring having type clauses in simple search
unset($field_list[$key]);
continue;
}
else {
$multi_lingual = false;
if ($exploded[0] == 'MULTI') {
$multi_lingual = true;
$foreign_field = $exploded[1];
}
$exploded = explode('.', $foreign_field); // format: table.field_name
$foreign_table = TABLE_PREFIX.$exploded[0];
$alias_counter++;
$alias = 't'.$alias_counter;
if ($multi_lingual) {
$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
$search_config_map[ $field_list[$key] ] = $field;
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
else {
$field_list[$key] = $alias.'.'.$exploded[1];
$search_config_map[ $field_list[$key] ] = $field;
}
$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
$join_clauses[] = ' LEFT JOIN '.$foreign_table.' '.$alias.'
ON '.$join_clause;
}
}
else {
// processing fields from local table
if ($search_config[$field]['CustomFieldId']) {
$local_table = 'custom_data';
// search by custom field value on current language
$custom_field_id = array_search($field_list[$key], $custom_fields);
$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
// search by custom field value on primary language
$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
$field_list[$key] = $local_table.'.'.$field_list[$key];
$search_config_map[ $field_list[$key] ] = $field;
}
}
// keyword string processing
$search_helper = $this->Application->recallObject('SearchHelper');
/* @var $search_helper kSearchHelper */
$where_clause = Array ();
foreach ($field_list as $field) {
if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
// local real field
$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
if ($filter_data) {
$where_clause[] = $filter_data['value'];
}
}
elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
$custom_field_name = 'cust_' . $search_config_map[$field];
$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
if ($filter_data) {
$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
}
}
else {
$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
}
}
$where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below!
$search_scope = $this->Application->GetVar('search_scope');
if ($search_scope == 'category') {
$category_id = $this->Application->GetVar('m_cat_id');
$category_filter = $this->getCategoryLimitClause($category_id);
if ($category_filter !== false) {
$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON '.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$items_table.'.ResourceId';
$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'Categories ON '.TABLE_PREFIX.'Categories.CategoryId = '.TABLE_PREFIX.'CategoryItems.CategoryId';
$where_clause = '('.$this->getCategoryLimitClause($category_id).') AND '.$where_clause;
}
}
$where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')';
if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
$sub_search_ids = $event->MasterEvent->getEventParam('ResultIds');
if ( $sub_search_ids !== false ) {
if ( $sub_search_ids ) {
$where_clause .= 'AND (' . $items_table . '.ResourceId IN (' . implode(',', $sub_search_ids) . '))';
}
else {
$where_clause .= 'AND FALSE';
}
}
}
// making relevance clause
$positive_words = $search_helper->getPositiveKeywords($keywords);
$this->Application->StoreVar('highlight_keywords', serialize($positive_words));
$relevance_parts = Array();
reset($search_config);
foreach ($positive_words as $keyword_index => $positive_word) {
$positive_word = $search_helper->transformWildcards($positive_word);
$positive_words[$keyword_index] = $this->Conn->escape($positive_word);
}
foreach ($field_list as $field) {
if (!array_key_exists($field, $search_config_map)) {
$map_key = $search_config_map[$items_table . '.' . $field];
}
else {
$map_key = $search_config_map[$field];
}
$config_elem = $search_config[ $map_key ];
$weight = $config_elem['Priority'];
// search by whole words only ([[:<:]] - word boundary)
/*$relevance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
foreach ($positive_words as $keyword) {
$relevance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
}*/
+ if ( count($positive_words) > 1 ) {
+ $condition = $field . ' LIKE "%' . implode(' ', $positive_words) . '%"';
+ $relevance_parts[] = 'IF(' . $condition . ', ' . $weight_sum . ', 0)';
+ }
+
// search by partial word matches too
- $relevance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
- foreach ($positive_words as $keyword) {
- $relevance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
+ foreach ( $positive_words as $keyword ) {
+ $relevance_parts[] = 'IF(' . $field . ' LIKE "%' . $keyword . '%", ' . $weight . ', 0)';
}
}
$relevance_parts = array_unique($relevance_parts);
$conf_postfix = $config->getSearchConfigPostfix();
$rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100;
$rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100;
$rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100;
$relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords;
if ($rel_pop && $object->isField('Hits')) {
$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
}
if ($rel_rating && $object->isField('CachedRating')) {
$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
}
// building final search query
if (!$this->Application->GetVar('do_not_drop_search_table')) {
$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
$this->Application->SetVar('do_not_drop_search_table', true);
}
$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
if ($search_table_exists) {
$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
}
else {
$select_intro = 'CREATE TABLE '.$search_table.' AS ';
}
$edpick_clause = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0';
$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
'.$items_table.'.'.$config->getIDField().' AS ItemId,
'.$items_table.'.ResourceId,
'.$config->getItemType().' AS ItemType,
'.$edpick_clause.' AS EdPick
FROM '.$object->TableName.'
'.implode(' ', $join_clauses).'
WHERE '.$where_clause.'
GROUP BY '.$items_table.'.'.$config->getIDField().' ORDER BY Relevance DESC';
$this->Conn->Query($sql);
if ( !$search_table_exists ) {
$sql = 'ALTER TABLE ' . $search_table . '
ADD INDEX (ResourceId),
ADD INDEX (Relevance)';
$this->Conn->Query($sql);
}
}
/**
* Enter description here...
*
* @param kEvent $event
*/
function OnSubSearch($event)
{
// keep search results from other items after doing a sub-search on current item type
$this->Application->SetVar('do_not_drop_search_table', true);
$ids = Array ();
$search_table = TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search';
$sql = 'SHOW TABLES LIKE "' . $search_table . '"';
if ( $this->Conn->Query($sql) ) {
$item_type = $event->getUnitConfig()->getItemType();
// 1. get ids to be used as search bounds
$sql = 'SELECT DISTINCT ResourceId
FROM ' . $search_table . '
WHERE ItemType = ' . $item_type;
$ids = $this->Conn->GetCol($sql);
// 2. delete previously found ids
$sql = 'DELETE FROM ' . $search_table . '
WHERE ItemType = ' . $item_type;
$this->Conn->Query($sql);
}
$event->setEventParam('ResultIds', $ids);
$event->CallSubEvent('OnSimpleSearch');
}
/**
* Enter description here...
*
* @param kEvent $event
* @todo Change all hardcoded Products table & In-Commerce module usage to dynamic usage from item config !!!
*/
function OnAdvancedSearch($event)
{
$query_object = $this->Application->recallObject('kHTTPQuery');
/* @var $query_object kHTTPQuery */
if ( !isset($query_object->Post['andor']) ) {
// used when navigating by pages or changing sorting in search results
return;
}
$this->Application->RemoveVar('keywords');
$this->Application->RemoveVar('Search_Keywords');
$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
$sql = 'SELECT *
FROM '.$this->Application->getUnitConfig('confs')->getTableName().'
WHERE (ModuleName = '.$this->Conn->qstr($module_name).') AND (AdvancedSearch = 1)';
$search_config = $this->Conn->Query($sql);
$lang = $this->Application->GetVar('m_lang');
$object = $event->getObject();
/* @var $object kDBList */
$object->SetPage(1);
$config = $event->getUnitConfig();
$items_table = $config->getTableName();
$search_keywords = $this->Application->GetVar('value'); // will not be changed
$keywords = $this->Application->GetVar('value'); // will be changed down there
$verbs = $this->Application->GetVar('verb');
$glues = $this->Application->GetVar('andor');
$and_conditions = Array();
$or_conditions = Array();
$and_having_conditions = Array();
$or_having_conditions = Array();
$join_clauses = Array();
$highlight_keywords = Array();
$relevance_parts = Array();
$alias_counter = 0;
$custom_fields = $config->getCustomFields();
if ($custom_fields) {
$custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName();
$join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
}
$search_log = '';
$weight_sum = 0;
// processing fields and preparing conditions
foreach ($search_config as $record) {
$field = $record['FieldName'];
$join_clause = '';
$condition_mode = 'WHERE';
// field processing
$local_table = TABLE_PREFIX.$record['TableName'];
$weight_sum += $record['Priority']; // counting weight sum; used when making relevance clause
// processing multilingual fields
if ( $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
$field_name = 'l'.$lang.'_'.$field;
}
else {
$field_name = $field;
}
// processing fields from other tables
$foreign_field = $record['ForeignField'];
if ( $foreign_field ) {
$exploded = explode(':', $foreign_field, 2);
if($exploded[0] == 'CALC')
{
$user_groups = $this->Application->RecallVar('UserGroups');
$field_name = str_replace('{PREFIX}', TABLE_PREFIX, $exploded[1]);
$join_clause = str_replace('{PREFIX}', TABLE_PREFIX, $record['JoinClause']);
$join_clause = str_replace('{USER_GROUPS}', $user_groups, $join_clause);
$join_clause = ' LEFT JOIN '.$join_clause;
$condition_mode = 'HAVING';
}
else {
$exploded = explode('.', $foreign_field);
$foreign_table = TABLE_PREFIX.$exploded[0];
if($record['CustomFieldId']) {
$exploded[1] = 'l'.$lang.'_'.$exploded[1];
}
$alias_counter++;
$alias = 't'.$alias_counter;
$field_name = $alias.'.'.$exploded[1];
$join_clause = str_replace('{ForeignTable}', $alias, $record['JoinClause']);
$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
if($record['CustomFieldId'])
{
$join_clause .= ' AND '.$alias.'.CustomFieldId='.$record['CustomFieldId'];
}
$join_clause = ' LEFT JOIN '.$foreign_table.' '.$alias.'
ON '.$join_clause;
}
}
else
{
// processing fields from local table
if ($record['CustomFieldId']) {
$local_table = 'custom_data';
$field_name = 'l'.$lang.'_cust_'.array_search($field_name, $custom_fields);
}
$field_name = $local_table.'.'.$field_name;
}
$condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
if ($record['CustomFieldId'] && strlen($condition)) {
// search in primary value of custom field + value in current language
$field_name = $local_table.'.'.'l'.$this->Application->GetDefaultLanguageId().'_cust_'.array_search($field, $custom_fields);
$primary_condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
$condition = '('.$condition.' OR '.$primary_condition.')';
}
if ($condition) {
if ($join_clause) {
$join_clauses[] = $join_clause;
}
$relevance_parts[] = 'IF('.$condition.', '.$record['Priority'].', 0)';
if ($glues[$field] == 1) { // and
if ($condition_mode == 'WHERE') {
$and_conditions[] = $condition;
}
else {
$and_having_conditions[] = $condition;
}
}
else { // or
if ($condition_mode == 'WHERE') {
$or_conditions[] = $condition;
}
else {
$or_having_conditions[] = $condition;
}
}
// create search log record
$search_log_data = Array('search_config' => $record, 'verb' => getArrayValue($verbs, $field), 'value' => ($record['FieldType'] == 'range') ? $search_keywords[$field.'_from'].'|'.$search_keywords[$field.'_to'] : $search_keywords[$field]);
$search_log[] = $this->Application->Phrase('la_Field').' "'.$this->getHuman('Field', $search_log_data).'" '.$this->getHuman('Verb', $search_log_data).' '.$this->Application->Phrase('la_Value').' '.$this->getHuman('Value', $search_log_data).' '.$this->Application->Phrase($glues[$field] == 1 ? 'lu_And' : 'lu_Or');
}
}
if ($search_log) {
$search_log = implode('<br />', $search_log);
$search_log = preg_replace('/(.*) '.preg_quote($this->Application->Phrase('lu_and'), '/').'|'.preg_quote($this->Application->Phrase('lu_or'), '/').'$/is', '\\1', $search_log);
$this->saveToSearchLog($search_log, 1); // advanced search
}
$this->Application->StoreVar('highlight_keywords', serialize($highlight_keywords));
// making relevance clause
if($relevance_parts)
{
$conf_postfix = $config->getSearchConfigPostfix();
$rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100;
$rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100;
$rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100;
$relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords;
$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
}
else
{
$relevance_clause = '0';
}
// building having clause
if($or_having_conditions)
{
$and_having_conditions[] = '('.implode(' OR ', $or_having_conditions).')';
}
$having_clause = implode(' AND ', $and_having_conditions);
$having_clause = $having_clause ? ' HAVING '.$having_clause : '';
// building where clause
if($or_conditions)
{
$and_conditions[] = '('.implode(' OR ', $or_conditions).')';
}
// $and_conditions[] = $items_table.'.Status = 1';
$where_clause = implode(' AND ', $and_conditions);
if(!$where_clause)
{
if($having_clause)
{
$where_clause = '1';
}
else
{
$where_clause = '0';
$this->Application->SetVar('adv_search_error', 1);
}
}
$where_clause .= ' AND '.$items_table.'.Status = 1';
// building final search query
$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
$id_field = $config->getIDField();
$pick_field = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0';
$sql = ' CREATE TABLE '.$search_table.'
SELECT '.$relevance_clause.' AS Relevance,
'.$items_table.'.'.$id_field.' AS ItemId,
'.$items_table.'.ResourceId AS ResourceId,
11 AS ItemType,
'.$pick_field.' AS EdPick
FROM '.$items_table.'
'.implode(' ', $join_clauses).'
WHERE '.$where_clause.'
GROUP BY '.$items_table.'.'.$id_field.
$having_clause;
$res = $this->Conn->Query($sql);
}
function getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, &$highlight_keywords)
{
$field = $record['FieldName'];
$condition_patterns = Array (
'any' => '%s LIKE %s',
'contains' => '%s LIKE %s',
'notcontains' => '(NOT (%1$s LIKE %2$s) OR %1$s IS NULL)',
'is' => '%s = %s',
'isnot' => '(%1$s != %2$s OR %1$s IS NULL)'
);
$condition = '';
switch ($record['FieldType']) {
case 'select':
$keywords[$field] = $this->Application->unescapeRequestVariable($keywords[$field]);
if ($keywords[$field]) {
$condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] ));
}
break;
case 'multiselect':
$keywords[$field] = $this->Application->unescapeRequestVariable($keywords[$field]);
if ($keywords[$field]) {
$condition = Array ();
$values = explode('|', substr($keywords[$field], 1, -1));
foreach ($values as $selected_value) {
$condition[] = sprintf($condition_patterns['contains'], $field_name, $this->Conn->qstr('%|'.$selected_value.'|%'));
}
$condition = '('.implode(' OR ', $condition).')';
}
break;
case 'text':
$keywords[$field] = $this->Application->unescapeRequestVariable($keywords[$field]);
if (mb_strlen($keywords[$field]) >= $this->Application->ConfigValue('Search_MinKeyword_Length')) {
$highlight_keywords[] = $keywords[$field];
if (in_array($verbs[$field], Array('any', 'contains', 'notcontains'))) {
$keywords[$field] = '%'.strtr($keywords[$field], Array('%' => '\\%', '_' => '\\_')).'%';
}
$condition = sprintf($condition_patterns[$verbs[$field]], $field_name, $this->Conn->qstr( $keywords[$field] ));
}
break;
case 'boolean':
if ($keywords[$field] != -1) {
$config = $this->getUnitConfig();
$items_table = $config->getTableName();
$property_mappings = $config->getItemPropertyMappings();
switch ($field) {
case 'HotItem':
$hot_limit_var = getArrayValue($property_mappings, 'HotLimit');
if ($hot_limit_var) {
$hot_limit = (int)$this->Application->getDBCache($hot_limit_var);
$condition = 'IF('.$items_table.'.HotItem = 2,
IF('.$items_table.'.Hits >= '.
$hot_limit.
', 1, 0), '.$items_table.'.HotItem) = '.$keywords[$field];
}
break;
case 'PopItem':
$votes2pop_var = getArrayValue($property_mappings, 'VotesToPop');
$rating2pop_var = getArrayValue($property_mappings, 'RatingToPop');
if ($votes2pop_var && $rating2pop_var) {
$condition = 'IF('.$items_table.'.PopItem = 2, IF('.$items_table.'.CachedVotesQty >= '.
$this->Application->ConfigValue($votes2pop_var).
' AND '.$items_table.'.CachedRating >= '.
$this->Application->ConfigValue($rating2pop_var).
', 1, 0), '.$items_table.'.PopItem) = '.$keywords[$field];
}
break;
case 'NewItem':
$new_days_var = getArrayValue($property_mappings, 'NewDays');
if ($new_days_var) {
$condition = 'IF('.$items_table.'.NewItem = 2,
IF('.$items_table.'.CreatedOn >= (UNIX_TIMESTAMP() - '.
$this->Application->ConfigValue($new_days_var).
'*3600*24), 1, 0), '.$items_table.'.NewItem) = '.$keywords[$field];
}
break;
case 'EditorsPick':
$condition = $items_table.'.EditorsPick = '.$keywords[$field];
break;
}
}
break;
case 'range':
$range_conditions = Array();
if ($keywords[$field.'_from'] && !preg_match("/[^0-9]/i", $keywords[$field.'_from'])) {
$range_conditions[] = $field_name.' >= '.$keywords[$field.'_from'];
}
if ($keywords[$field.'_to'] && !preg_match("/[^0-9]/i", $keywords[$field.'_to'])) {
$range_conditions[] = $field_name.' <= '.$keywords[$field.'_to'];
}
if ($range_conditions) {
$condition = implode(' AND ', $range_conditions);
}
break;
case 'date':
if ($keywords[$field]) {
if (in_array($keywords[$field], Array('today', 'yesterday'))) {
$current_time = getdate();
$day_begin = mktime(0, 0, 0, $current_time['mon'], $current_time['mday'], $current_time['year']);
$time_mapping = Array('today' => $day_begin, 'yesterday' => ($day_begin - 86400));
$min_time = $time_mapping[$keywords[$field]];
}
else {
$time_mapping = Array (
'last_week' => 604800, 'last_month' => 2628000, 'last_3_months' => 7884000,
'last_6_months' => 15768000, 'last_year' => 31536000,
);
$min_time = time() - $time_mapping[$keywords[$field]];
}
$condition = $field_name.' > '.$min_time;
}
break;
}
return $condition;
}
/**
* Returns human readable representation of searched data to be placed in search log
* @param string $type
* @param Array $search_data
* @return string
* @access protected
*/
protected function getHuman($type, $search_data)
{
// all 3 variables are retrieved from $search_data array
/* @var $search_config Array */
/* @var $verb string */
/* @var $value string */
$type = ucfirst(strtolower($type));
extract($search_data);
switch ($type) {
case 'Field':
return $this->Application->Phrase($search_config['DisplayName']);
break;
case 'Verb':
return $verb ? $this->Application->Phrase('lu_advsearch_'.$verb) : '';
break;
case 'Value':
switch ($search_config['FieldType']) {
case 'date':
$values = Array(0 => 'lu_comm_Any', 'today' => 'lu_comm_Today',
'yesterday' => 'lu_comm_Yesterday', 'last_week' => 'lu_comm_LastWeek',
'last_month' => 'lu_comm_LastMonth', 'last_3_months' => 'lu_comm_Last3Months',
'last_6_months' => 'lu_comm_Last6Months', 'last_year' => 'lu_comm_LastYear');
$ret = $this->Application->Phrase($values[$value]);
break;
case 'range':
$value = explode('|', $value);
return $this->Application->Phrase('lu_comm_From').' "'.$value[0].'" '.$this->Application->Phrase('lu_comm_To').' "'.$value[1].'"';
break;
case 'boolean':
$values = Array(1 => 'lu_comm_Yes', 0 => 'lu_comm_No', -1 => 'lu_comm_Both');
$ret = $this->Application->Phrase($values[$value]);
break;
default:
$ret = $value;
break;
}
return '"'.$ret.'"';
break;
}
return '';
}
/**
* Set's correct page for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetPagination(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBList */
// get PerPage (forced -> session -> config -> 10)
$object->SetPerPage($this->getPerPage($event));
// main lists on Front-End have special get parameter for page
$page = $object->isMainList() ? $this->Application->GetVar('page') : false;
if ( !$page ) {
// page is given in "env" variable for given prefix
$page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page');
}
if ( !$page && $event->Special ) {
// when not part of env, then variables like "prefix.special_Page" are
// replaced (by PHP) with "prefix_special_Page", so check for that too
$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
}
if ( !$object->isMainList() ) {
// main lists doesn't use session for page storing
$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
if ( !$page ) {
if ( $this->Application->RewriteURLs() ) {
// when page not found by prefix+special, then try to search it without special at all
$page = $this->Application->GetVar($event->Prefix . '_Page');
if ( !$page ) {
// page not found in request -> get from session
$page = $this->Application->RecallVar($event->Prefix . '_Page');
}
if ( $page ) {
// page found in request -> store in session
$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
}
}
else {
// page not found in request -> get from session
$page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page');
}
}
else {
// page found in request -> store in session
$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
}
if ( !$event->getEventParam('skip_counting') ) {
// when stored page is larger, then maximal list page number
// (such case is also processed in kDBList::Query method)
$pages = $object->GetTotalPages();
if ( $page > $pages ) {
$page = 1;
$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true);
}
}
}
$object->SetPage($page);
}
/* === RELATED TO IMPORT/EXPORT: BEGIN === */
/**
* Shows export dialog
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnExport(kEvent $event)
{
$selected_ids = $this->StoreSelectedIDs($event);
if ( implode(',', $selected_ids) == '' ) {
// K4 fix when no ids found bad selected ids array is formed
$selected_ids = false;
}
$selected_cats_ids = $this->Application->GetVar('export_categories');
$this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : '');
$this->Application->StoreVar($event->Prefix . '_export_cats_ids', $selected_cats_ids);
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_helper kCatDBItemExportHelper */
$redirect_params = Array (
$this->Prefix . '.export_event' => 'OnNew',
'pass' => 'all,' . $this->Prefix . '.export'
);
$event->setRedirectParams($redirect_params);
}
/**
* Performs each export step & displays progress percent
*
* @param kEvent $event
*/
function OnExportProgress($event)
{
$export_object = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_object kCatDBItemExportHelper */
$action_method = 'perform'.ucfirst($event->Special);
$field_values = $export_object->$action_method($event);
// finish code is done from JS now
if ($field_values['start_from'] == $field_values['total_records']) {
if ($event->Special == 'import') {
$this->Application->StoreVar('PermCache_UpdateRequired', 1);
$event->SetRedirectParam('m_cat_id', $this->Application->RecallVar('ImportCategory'));
$event->SetRedirectParam('anchor', 'tab-' . $event->Prefix);
$event->redirect = 'catalog/catalog';
}
elseif ($event->Special == 'export') {
$event->redirect = $export_object->getModuleName($event) . '/' . $event->Special . '_finish';
$event->SetRedirectParam('pass', 'all');
}
return ;
}
$export_options = $export_object->loadOptions($event);
echo $export_options['start_from'] * 100 / $export_options['total_records'];
$event->status = kEvent::erSTOP;
}
/**
* Returns specific to each item type columns only
*
* @param kEvent $event
* @return Array
* @access protected
*/
public function getCustomExportColumns(kEvent $event)
{
return Array (
'__VIRTUAL__ThumbnailImage' => 'ThumbnailImage',
'__VIRTUAL__FullImage' => 'FullImage',
'__VIRTUAL__ImageAlt' => 'ImageAlt'
);
}
/**
* Sets non standart virtual fields (e.g. to other tables)
*
* @param kEvent $event
*/
function setCustomExportColumns($event)
{
$this->restorePrimaryImage($event);
}
/**
* Create/Update primary image record in info found in imported data
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function restorePrimaryImage($event)
{
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( !$object->GetDBField('ThumbnailImage') && !$object->GetDBField('FullImage') ) {
return ;
}
$image_data = $object->getPrimaryImageData();
$image = $this->Application->recallObject('img', NULL, Array ('skip_autoload' => true));
/* @var $image kDBItem */
if ( $image_data ) {
$image->Load($image_data['ImageId']);
}
else {
$image->Clear();
$image->SetDBField('Name', 'main');
$image->SetDBField('DefaultImg', 1);
$image->SetDBField('ResourceId', $object->GetDBField('ResourceId'));
}
if ( $object->GetDBField('ImageAlt') ) {
$image->SetDBField('AltName', $object->GetDBField('ImageAlt'));
}
if ( $object->GetDBField('ThumbnailImage') ) {
$thumbnail_field = $this->isURL($object->GetDBField('ThumbnailImage')) ? 'ThumbUrl' : 'ThumbPath';
$image->SetDBField($thumbnail_field, $object->GetDBField('ThumbnailImage'));
$image->SetDBField('LocalThumb', $thumbnail_field == 'ThumbPath' ? 1 : 0);
}
if ( !$object->GetDBField('FullImage') ) {
$image->SetDBField('SameImages', 1);
}
else {
$image->SetDBField('SameImages', 0);
$full_field = $this->isURL($object->GetDBField('FullImage')) ? 'Url' : 'LocalPath';
$image->SetDBField($full_field, $object->GetDBField('FullImage'));
$image->SetDBField('LocalImage', $full_field == 'LocalPath' ? 1 : 0);
}
if ( $image->isLoaded() ) {
$image->Update();
}
else {
$image->Create();
}
}
/**
* Detects if image url is specified in a given path (instead of path on disk)
*
* @param string $path
* @return bool
* @access protected
*/
protected function isURL($path)
{
return preg_match('#(http|https)://(.*)#', $path);
}
/**
* Prepares item for import/export operations
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnNew(kEvent $event)
{
parent::OnNew($event);
if ( $event->Special == 'import' || $event->Special == 'export' ) {
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_helper kCatDBItemExportHelper */
$export_helper->setRequiredFields($event);
}
}
/**
* Process items selected in item_selector
*
* @param kEvent $event
*/
function OnProcessSelected($event)
{
$dst_field = $this->Application->RecallVar('dst_field');
$selected_ids = $this->Application->GetVar('selected_ids');
if ( $dst_field == 'ItemCategory' ) {
// Item Edit -> Categories Tab -> New Categories
$object = $event->getObject();
/* @var $object kCatDBItem */
$category_ids = explode(',', $selected_ids['c']);
foreach ($category_ids as $category_id) {
$object->assignToCategory($category_id);
}
}
if ($dst_field == 'ImportCategory') {
// Tools -> Import -> Item Import -> Select Import Category
$this->Application->StoreVar('ImportCategory', $selected_ids['c']);
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', 0);
$event->SetRedirectParam($event->getPrefixSpecial() . '_event', 'OnExportBegin');
}
$event->SetRedirectParam('opener', 'u');
}
/**
* Saves Import/Export settings to session
*
* @param kEvent $event
*/
function OnSaveSettings($event)
{
$event->redirect = false;
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( $items_info ) {
list($id, $field_values) = each($items_info);
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$object->setID($id);
$object->SetFieldsFromHash($field_values);
$event->setEventParam('form_data', $field_values);
$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
$field_values['ImportSource'] = 2;
$field_values['ImportLocalFilename'] = $object->GetDBField('ImportFilename');
$items_info[$id] = $field_values;
$this->Application->StoreVar($event->getPrefixSpecial() . '_ItemsInfo', serialize($items_info));
}
}
/**
* Saves Import/Export settings to session
*
* @param kEvent $event
*/
function OnResetSettings($event)
{
$this->Application->StoreVar('ImportCategory', $this->Application->getBaseCategory());
}
/**
* Cancels item editing
* @param kEvent $event
* @return void
* @todo Used?
*/
function OnCancelAction($event)
{
$event->redirect = $this->Application->GetVar('cancel_template');
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
}
/* === RELATED TO IMPORT/EXPORT: END === */
/**
* Stores item's owner login into separate field together with id
*
* @param kEvent $event
* @param string $id_field
* @param string $cached_field
*/
function cacheItemOwner($event, $id_field, $cached_field)
{
$object = $event->getObject();
/* @var $object kDBItem */
$object->SetDBField($cached_field, $object->GetField($id_field));
$options = $object->GetFieldOptions($id_field);
if ( isset($options['options'][$user_id]) ) {
$object->SetDBField($cached_field, $options['options'][$user_id]);
}
else {
$user_config = $this->Application->getUnitConfig('u');
$id_field = $user_config->getIDField();
$table_name = $user_config->getTableName();
$sql = 'SELECT Username
FROM ' . $table_name . '
WHERE ' . $id_field . ' = ' . $user_id;
$object->SetDBField($cached_field, $this->Conn->GetOne($sql));
}
}
/**
* Saves edited item into temp table
* If there is no id, new item is created in temp table
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSave(kEvent $event)
{
parent::OnPreSave($event);
$use_pending_editing = $event->getUnitConfig()->getUsePendingEditing();
if ( $event->status == kEvent::erSUCCESS && $use_pending_editing ) {
// decision: clone or not clone
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( $object->GetID() == 0 || $object->GetDBField('OrgId') > 0 ) {
// new items or cloned items shouldn't be cloned again
return ;
}
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$owner_field = $this->getOwnerField($event->Prefix);
if ( $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix) == 2 ) {
// 1. clone original item
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array ($object->GetID()), NULL, NULL, NULL, true);
$ci_table = $this->Application->GetTempName(TABLE_PREFIX . 'CategoryItems');
// 2. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
$sql = 'SELECT ResourceId
FROM ' . $object->TableName . '
WHERE ' . $object->IDField . ' = ' . $cloned_ids[0];
$clone_resource_id = $this->Conn->GetOne($sql);
$sql = 'DELETE FROM ' . $ci_table . '
WHERE ItemResourceId = ' . $clone_resource_id . ' AND PrimaryCat = 1';
$this->Conn->Query($sql);
// 3. copy main item categoryitems to cloned item
$sql = ' INSERT INTO ' . $ci_table . ' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
SELECT CategoryId, ' . $clone_resource_id . ' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
FROM ' . $ci_table . '
WHERE ItemResourceId = ' . $object->GetDBField('ResourceId');
$this->Conn->Query($sql);
// 4. put cloned id to OrgId field of item being cloned
$sql = 'UPDATE ' . $object->TableName . '
SET OrgId = ' . $object->GetID() . '
WHERE ' . $object->IDField . ' = ' . $cloned_ids[0];
$this->Conn->Query($sql);
// 5. substitute id of item being cloned with clone id
$this->Application->SetVar($event->getPrefixSpecial() . '_id', $cloned_ids[0]);
$selected_ids = $this->getSelectedIDs($event, true);
$selected_ids[ array_search($object->GetID(), $selected_ids) ] = $cloned_ids[0];
$this->StoreSelectedIDs($event, $selected_ids);
// 6. delete original item from temp table
$temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($object->GetID()));
}
}
}
/**
* Sets item's owner field
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreCreate(kEvent $event)
{
parent::OnPreCreate($event);
if ( $event->status != kEvent::erSUCCESS ) {
return ;
}
$object = $event->getObject();
/* @var $object kDBItem */
$owner_field = $this->getOwnerField($event->Prefix);
$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
}
/**
* Occurs before original item of item in pending editing got deleted (for hooking only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeDeleteOriginal(kEvent $event)
{
}
/**
* Occurs after original item of item in pending editing got deleted (for hooking only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterDeleteOriginal(kEvent $event)
{
}
/**
* Occurs before an item has been cloned
* Id of newly created item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeClone(kEvent $event)
{
parent::OnBeforeClone($event);
$object = $event->getObject();
/* @var $object kDBItem */
$object->SetDBField('ResourceId', 0); // this will reset it
if ( $this->Application->GetVar('ResetCatBeforeClone') ) {
$object->SetDBField('CategoryId', NULL);
}
}
/**
* Set status for new category item based on user permission in category
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemCreate(kEvent $event)
{
parent::OnBeforeItemCreate($event);
$object = $event->getObject();
/* @var $object kCatDBItem */
$is_admin = $this->Application->isAdminUser;
$owner_field = $this->getOwnerField($event->Prefix);
if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField($owner_field)) ) {
// Front-end OR owner not specified -> set to currently logged-in user
$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
}
if ( !$this->Application->isAdmin ) {
$this->setItemStatusByPermission($event);
}
}
/**
* Sets category item status based on user permissions (only on Front-end)
*
* @param kEvent $event
*/
function setItemStatusByPermission($event)
{
$use_pending_editing = $event->getUnitConfig()->getUsePendingEditing();
if (!$use_pending_editing) {
return ;
}
$object = $event->getObject();
/* @var $object kCatDBItem */
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$primary_category = $object->GetDBField('CategoryId') > 0 ? $object->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id');
$item_status = $perm_helper->AddCheckPermission($primary_category, $event->Prefix);
if ($item_status == STATUS_DISABLED) {
$event->status = kEvent::erFAIL;
}
else {
$object->SetDBField('Status', $item_status);
}
}
/**
* Creates category item & redirects to confirmation template (front-end only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCreate(kEvent $event)
{
parent::OnCreate($event);
$this->SetFrontRedirectTemplate($event, 'suggest');
}
/**
* Returns item's categories (allows to exclude primary category)
*
* @param int $resource_id
* @param bool $with_primary
* @return Array
*/
function getItemCategories($resource_id, $with_primary = false)
{
$sql = 'SELECT CategoryId
FROM '.TABLE_PREFIX.'CategoryItems
WHERE (ItemResourceId = '.$resource_id.')';
if (!$with_primary) {
$sql .= ' AND (PrimaryCat = 0)';
}
return $this->Conn->GetCol($sql);
}
/**
* Adds new and removes old additional categories from category item
*
* @param kCatDBItem $object
* @param int $mode
*/
function processAdditionalCategories(&$object, $mode)
{
if ( !$object->isVirtualField('MoreCategories') ) {
// given category item doesn't require such type of processing
return ;
}
$process_categories = $object->GetDBField('MoreCategories');
if ($process_categories === '') {
// field was not in submit & have default value (when no categories submitted, then value is null)
return ;
}
if ($mode == 'create') {
// prevents first additional category to become primary
$object->assignPrimaryCategory();
}
$process_categories = $process_categories ? explode('|', substr($process_categories, 1, -1)) : Array ();
$existing_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
$add_categories = array_diff($process_categories, $existing_categories);
foreach ($add_categories as $category_id) {
$object->assignToCategory($category_id);
}
$remove_categories = array_diff($existing_categories, $process_categories);
foreach ($remove_categories as $category_id) {
$object->removeFromCategory($category_id);
}
}
/**
* Creates category item & redirects to confirmation template (front-end only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdate(kEvent $event)
{
$use_pending = $event->getUnitConfig()->getUsePendingEditing();
if ($this->Application->isAdminUser || !$use_pending) {
parent::OnUpdate($event);
$this->SetFrontRedirectTemplate($event, 'modify');
return ;
}
$object = $event->getObject(Array('skip_autoload' => true));
/* @var $object kCatDBItem */
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ($items_info) {
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
/* @var $temp_handler kTempTablesHandler */
$owner_field = $this->getOwnerField($event->Prefix);
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
foreach ($items_info as $id => $field_values) {
$object->Load($id);
$edit_perm = $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix);
if ($use_pending && !$object->GetDBField('OrgId') && ($edit_perm == STATUS_PENDING)) {
// pending editing enabled + not pending copy -> get/create pending copy & save changes to it
$original_id = $object->GetID();
$original_resource_id = $object->GetDBField('ResourceId');
$file_helper->PreserveItemFiles($field_values);
$object->Load($original_id, 'OrgId');
if (!$object->isLoaded()) {
// 1. user has no pending copy of live item -> clone live item
$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($original_id), NULL, NULL, NULL, true);
$object->Load($cloned_ids[0]);
$object->SetFieldsFromHash($field_values);
$event->setEventParam('form_data', $field_values);
// 1a. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
$ci_table = $this->Application->getUnitConfig('ci')->getTableName();
$sql = 'DELETE FROM '.$ci_table.'
WHERE ItemResourceId = '.$object->GetDBField('ResourceId').' AND PrimaryCat = 1';
$this->Conn->Query($sql);
// 1b. copy main item categoryitems to cloned item
$sql = 'INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
SELECT CategoryId, '.$object->GetDBField('ResourceId').' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
FROM '.$ci_table.'
WHERE ItemResourceId = '.$original_resource_id;
$this->Conn->Query($sql);
// 1c. put cloned id to OrgId field of item being cloned
$object->SetDBField('Status', STATUS_PENDING_EDITING);
$object->SetDBField('OrgId', $original_id);
}
else {
// 2. user has pending copy of live item -> just update field values
$object->SetFieldsFromHash($field_values);
$event->setEventParam('form_data', $field_values);
}
// update id in request (used for redirect in mod-rewrite mode)
$this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetID());
}
else {
// 3. already editing pending copy -> just update field values
$object->SetFieldsFromHash($field_values);
$event->setEventParam('form_data', $field_values);
}
if ($object->Update()) {
$event->status = kEvent::erSUCCESS;
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
break;
}
}
}
$this->SetFrontRedirectTemplate($event, 'modify');
}
/**
* Sets next template to one required for front-end after adding/modifying item
*
* @param kEvent $event
* @param string $template_key - {suggest,modify}
*/
function SetFrontRedirectTemplate($event, $template_key)
{
if ( $this->Application->isAdmin || $event->status != kEvent::erSUCCESS ) {
return;
}
// prepare redirect template
$object = $event->getObject();
/* @var $object kDBItem */
$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
$next_template = $is_active ? 'confirm_template' : 'pending_confirm_template';
$event->redirect = $this->Application->GetVar($template_key . '_' . $next_template);
$event->SetRedirectParam('opener', 's');
// send email events
$perm_prefix = $event->getUnitConfig()->getPermItemPrefix();
$owner_field = $this->getOwnerField($event->Prefix);
$owner_id = $object->GetDBField($owner_field);
$send_params = $object->getEmailParams();
switch ( $event->Name ) {
case 'OnCreate':
$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $owner_id, $send_params);
$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix, null, $send_params); // there are no ADD.PENDING event for admin :(
break;
case 'OnUpdate':
$event_suffix = $is_active ? 'MODIFY' : 'MODIFY.PENDING';
$user_id = is_numeric($object->GetDBField('ModifiedById')) ? $object->GetDBField('ModifiedById') : $owner_id;
$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $user_id, $send_params);
$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix, null, $send_params); // there are no ADD.PENDING event for admin :(
break;
}
}
/**
* Apply same processing to each item being selected in grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function iterateItems(kEvent $event)
{
if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) {
parent::iterateItems($event);
}
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return ;
}
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kCatDBItem */
$ids = $this->StoreSelectedIDs($event);
if ( $ids ) {
foreach ($ids as $id) {
$ret = true;
$object->Load($id);
switch ( $event->Name ) {
case 'OnMassApprove':
$ret = $object->ApproveChanges();
break;
case 'OnMassDecline':
$ret = $object->DeclineChanges();
break;
}
if ( !$ret ) {
$event->status = kEvent::erFAIL;
$event->redirect = false;
break;
}
}
}
$this->clearSelectedIDs($event);
}
/**
* Deletes items & preserves clean env
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDelete(kEvent $event)
{
parent::OnDelete($event);
if ( $event->status == kEvent::erSUCCESS && !$this->Application->isAdmin ) {
$event->SetRedirectParam('pass', 'm');
$event->SetRedirectParam('m_cat_id', 0);
}
}
/**
* Checks, that currently loaded item is allowed for viewing (non permission-based)
*
* @param kEvent $event
* @return bool
* @access protected
*/
protected function checkItemStatus(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
if ( !$object->isLoaded() ) {
if ( $event->Special != 'previous' && $event->Special != 'next' ) {
$this->_errorNotFound($event);
}
return true;
}
$status = $object->GetDBField('Status');
$user_id = $this->Application->RecallVar('user_id');
$owner_field = $this->getOwnerField($event->Prefix);
if ( ($status == STATUS_PENDING_EDITING || $status == STATUS_PENDING) && ($object->GetDBField($owner_field) == $user_id) ) {
return true;
}
return $status == STATUS_ACTIVE;
}
/**
* Set's correct sorting for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetSorting(kEvent $event)
{
if ( !$this->Application->isAdmin ) {
$event->setEventParam('same_special', true);
}
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
if ( in_array('search', $types) ) {
$event->setPseudoClass('_List');
$object = $event->getObject();
/* @var $object kDBList */
// 1. no user sorting - sort by relevance
$default_sortings = parent::_getDefaultSorting($event);
$default_sorting = key($default_sortings['Sorting']) . ',' . current($default_sortings['Sorting']);
if ( $object->isMainList() ) {
$sort_by = $this->Application->GetVar('sort_by', '');
if ( !$sort_by ) {
$this->Application->SetVar('sort_by', 'Relevance,desc|' . $default_sorting);
}
}
else {
$sorting_settings = $this->getListSetting($event, 'Sortings');
$sort_by = trim(getArrayValue($sorting_settings, 'Sort1') . ',' . getArrayValue($sorting_settings, 'Sort1_Dir'), ',');
if ( !$sort_by ) {
$event->setEventParam('sort_by', 'Relevance,desc|' . $default_sorting);
}
}
$this->_removeForcedSortings($event);
}
parent::SetSorting($event);
}
/**
* Removes forced sortings
*
* @param kEvent $event
*/
protected function _removeForcedSortings(kEvent $event)
{
$config = $event->getUnitConfig();
foreach ($config->getListSortingSpecials() as $special) {
$list_sortings = $config->getListSortingsBySpecial($special);
unset($list_sortings['ForcedSorting']);
$config->setListSortingsBySpecial('', $list_sortings);
}
}
/**
* Default sorting in search results only comes from relevance field
*
* @param kEvent $event
* @return Array
* @access protected
*/
protected function _getDefaultSorting(kEvent $event)
{
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
return in_array('search', $types) ? Array () : parent::_getDefaultSorting($event);
}
/**
* Returns current per-page setting for list
*
* @param kEvent $event
* @return int
* @access protected
*/
protected function getPerPage(kEvent $event)
{
if ( !$this->Application->isAdmin ) {
$event->setEventParam('same_special', true);
}
return parent::getPerPage($event);
}
/**
* Returns owner field for given prefix
*
* @param $prefix
* @return string
* @access protected
*/
protected function getOwnerField($prefix)
{
return $this->Application->getUnitConfig($prefix)->getOwnerField('CreatedById');
}
/**
* Creates virtual image fields for item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterConfigRead(kEvent $event)
{
parent::OnAfterConfigRead($event);
if (defined('IS_INSTALL') && IS_INSTALL) {
$this->addViewPermissionJoin($event);
return ;
}
if ( !$this->Application->isAdmin ) {
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$file_helper->createItemFiles($event->Prefix, true); // create image fields
$file_helper->createItemFiles($event->Prefix, false); // create file fields
}
$this->changeSortings($event)->addViewPermissionJoin($event);
// add grids for advanced view (with primary category column)
$config = $event->getUnitConfig();
foreach (Array ('Default', 'Radio') as $process_grid) {
$grid_data = $config->getGridByName($process_grid);
$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_primary_category_td', 'filter_block' => 'grid_like_filter');
$config->addGrids($grid_data, $process_grid . 'ShowAll');
}
// add options for CategoryId field (quick way to select item's primary category)
$category_helper = $this->Application->recallObject('CategoryHelper');
/* @var $category_helper CategoryHelper */
$virtual_fields = $config->getVirtualFields();
$virtual_fields['CategoryId']['default'] = (int)$this->Application->GetVar('m_cat_id');
$virtual_fields['CategoryId']['options'] = $category_helper->getStructureTreeAsOptions();
$config->setVirtualFields($virtual_fields);
}
/**
* Changes default sorting according to system settings.
*
* @param kEvent $event Event.
*
* @return self
* @access protected
*/
protected function changeSortings(kEvent $event)
{
$remove_sortings = Array ();
$config = $event->getUnitConfig();
if ( !$this->Application->isAdmin ) {
// remove Pick sorting on Front-end, when not required
$config_mapping = $config->getConfigMapping(Array ());
if ( !isset($config_mapping['ForceEditorPick']) || !$this->Application->ConfigValue($config_mapping['ForceEditorPick']) ) {
$remove_sortings[] = 'EditorsPick';
}
}
else {
// remove all forced sortings in Admin Console
$remove_sortings = array_merge($remove_sortings, Array ('Priority', 'EditorsPick'));
}
if ( !$remove_sortings ) {
return $this;
}
foreach ($config->getListSortingSpecials() as $special) {
$list_sortings = $config->getListSortingsBySpecial($special);
foreach ($remove_sortings as $sorting_field) {
unset($list_sortings['ForcedSorting'][$sorting_field]);
}
$config->setListSortingsBySpecial('', $list_sortings);
}
return $this;
}
/**
* Adds permission table table JOIN clause only, when advanced catalog view permissions enabled.
*
* @param kEvent $event Event.
*
* @return self
* @access protected
*/
protected function addViewPermissionJoin(kEvent $event)
{
if ( $this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) {
$join_clause = 'LEFT JOIN ' . TABLE_PREFIX . 'CategoryPermissionsCache perm ON perm.CategoryId = ' . TABLE_PREFIX . '%3$sCategoryItems.CategoryId';
}
else {
$join_clause = '';
}
$config = $event->getUnitConfig();
foreach ( $config->getListSQLSpecials() as $special ) {
$list_sql = str_replace('{PERM_JOIN}', $join_clause, $config->getListSQLsBySpecial($special));
$config->setListSQLsBySpecial($special, $list_sql);
}
return $this;
}
/**
* Returns file contents associated with item
*
* @param kEvent $event
*/
function OnDownloadFile($event)
{
$object = $event->getObject();
/* @var $object kCatDBItem */
$event->status = kEvent::erSTOP;
$field = $this->Application->GetVar('field');
if (!preg_match('/^File([\d]+)/', $field)) {
return ;
}
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$filename = $object->GetField($field, 'full_path');
$file_helper->DownloadFile($filename);
}
/**
* Saves user's vote
*
* @param kEvent $event
*/
function OnMakeVote($event)
{
$event->status = kEvent::erSTOP;
if ($this->Application->GetVar('ajax') != 'yes') {
// this is supposed to call from AJAX only
return ;
}
$rating_helper = $this->Application->recallObject('RatingHelper');
/* @var $rating_helper RatingHelper */
$object = $event->getObject( Array ('skip_autoload' => true) );
/* @var $object kCatDBItem */
$object->Load( $this->Application->GetVar('id') );
echo $rating_helper->makeVote($object);
}
/**
* Marks review as useful
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnReviewHelpful($event)
{
if ( $this->Application->GetVar('ajax') == 'yes' ) {
$event->status = kEvent::erSTOP;
}
$review_id = (int)$this->Application->GetVar('review_id');
if ( !$review_id ) {
return;
}
$spam_helper = $this->Application->recallObject('SpamHelper');
/* @var $spam_helper SpamHelper */
$spam_helper->InitHelper($review_id, 'ReviewHelpful', strtotime('+1 month') - strtotime('now'));
$field = (int)$this->Application->GetVar('helpful') ? 'HelpfulCount' : 'NotHelpfulCount';
$review_config = $this->Application->getUnitConfig('rev');
$sql = 'SELECT ' . $field . '
FROM ' . $review_config->getTableName() . '
WHERE ' . $review_config->getIDField() . ' = ' . $review_id;
$count = $this->Conn->GetOne($sql);
if ( $spam_helper->InSpamControl() ) {
if ( $this->Application->GetVar('ajax') == 'yes' ) {
echo $count;
}
return;
}
$sql = 'UPDATE ' . $review_config->getTableName() . '
SET ' . $field . ' = ' . $field . ' + 1
WHERE ' . $review_config->getIDField() . ' = ' . $review_id;
$this->Conn->Query($sql);
if ( $this->Conn->getAffectedRows() ) {
// db was changed -> review with such ID exists
$spam_helper->AddToSpamControl();
}
if ( $this->Application->GetVar('ajax') == 'yes' ) {
echo $count + 1;
}
}
/**
* [HOOK] Allows to add cloned subitem to given prefix
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCloneSubItem(kEvent $event)
{
parent::OnCloneSubItem($event);
if ( $event->MasterEvent->Prefix == 'fav' ) {
$master_config = $event->MasterEvent->getUnitConfig();
$clones = $master_config->getClones();
$sub_item_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix;
$clones[$sub_item_prefix]['ParentTableKey'] = 'ResourceId';
$clones[$sub_item_prefix]['ForeignKey'] = 'ResourceId';
$master_config->setClones($clones);
}
}
/**
* Set's new unique resource id to user
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemValidate(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$resource_id = $object->GetDBField('ResourceId');
if ( !$resource_id ) {
$object->SetDBField('ResourceId', $this->Application->NextResourceId());
}
}
}
Index: branches/5.3.x/core/kernel/utility/formatters/formatter.php
===================================================================
--- branches/5.3.x/core/kernel/utility/formatters/formatter.php (revision 16194)
+++ branches/5.3.x/core/kernel/utility/formatters/formatter.php (revision 16195)
@@ -1,302 +1,303 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class kFormatter extends kBase {
/**
* Reference to category helper
*
* @var CategoryHelper
*/
var $_categoryHelper = null;
/**
* Creates formatter instance
*
* @access public
*/
public function __construct()
{
parent::__construct();
$this->_categoryHelper = $this->Application->recallObject('CategoryHelper');
}
/**
* Replace FCK links like "@@ID@@" to real page urls, when "using_fck" option is set.
*
* @param string $value
* @param Array $options
* @param string $format
* @return string
*/
function _replaceFCKLinks(&$value, $options, $format = null)
{
if ((isset($format) && strpos($format, 'fck_ready') !== false) || (!array_key_exists('using_fck', $options) || !$options['using_fck'])) {
// in textarea, where fck will be used OR not using fck
return $value;
}
return $this->_categoryHelper->replacePageIds($value);
}
/**
* Convert's value to match type from config
*
* @param mixed $value
* @param Array $options
* @return mixed
* @access protected
*/
function TypeCast($value, $options)
{
$ret = true;
if ( isset($options['type']) ) {
$field_type = $options['type'];
if ($field_type == 'numeric') {
trigger_error('Invalid field type <strong>' . $field_type . '</strong> (in TypeCast method), please use <strong>float</strong> instead', E_USER_NOTICE);
$field_type = 'float';
}
elseif ( $field_type == 'string' ) {
if ( isset($options['allow_html']) && $options['allow_html'] ) {
$value = $this->Application->unescapeRequestVariable($value);
}
return $value;
}
$value = $this->formatNumber($value);
$type_ok = preg_match('#int|integer|double|float|real|numeric|string#', $field_type);
if ( $value != '' && $type_ok ) {
$ret = is_numeric($value);
if ($ret) {
$f = 'is_' . $field_type;
settype($value, $field_type);
$ret = $f($value);
}
}
}
return $ret ? $value : false;
}
/**
* Formats number, according to regional settings
*
* @param string $number
* @return float
*/
function formatNumber($number)
{
static $comma = null, $thousands = null;
if ( !isset($comma) || !isset($thousands) ) {
$lang = $this->Application->recallObject('lang.current');
/* @var $lang LanguagesItem */
$comma = $lang->GetDBField('DecimalPoint');
$thousands = $lang->GetDBField('ThousandSep');
}
$number = str_replace($thousands, '', $number);
$number = str_replace($comma, '.', $number);
return $number;
}
/**
* Applies type casting on each array element
* @param Array $src
* @param kDBItem|kDBList|kDBBase $object
* @return Array
* @access public
*/
public function TypeCastArray($src, &$object)
{
$dst = array ();
foreach ($src as $id => $row) {
$tmp_row = array ();
foreach ($row as $fld => $value) {
$field_options = $object->GetFieldOptions($fld);
$tmp_row[$fld] = $this->TypeCast($value, $field_options);
}
$dst[$id] = $tmp_row;
}
return $dst;
}
/**
* Formats value of a given field
*
* @param string $value
* @param string $field_name
* @param kDBItem|kDBList|kDBBase $object
* @param string $format
* @return string
*/
function Format($value, $field_name, &$object, $format = null)
{
if ( is_null($value) ) {
return '';
}
$options = $object->GetFieldOptions($field_name);
if (!isset($format) && array_key_exists('format', $options)) {
$format = $options['format'];
}
if ($value === false) {
// used ?
return $value; // for leaving badly formatted date on the form
}
$original_format = $format;
if (isset($format)) {
if (strpos($format, 'fck_ready') !== false) {
$format = trim(str_replace('fck_ready', '', $format), ';');
}
}
if (isset($format) && $format) {
$value = sprintf($format, $value);
if ( isset($options['cut_zeros']) && $options['cut_zeros'] ) {
// converts 5.00 to 5, but doesn't change 5.340 or 5.34
$value = preg_replace('/\.[0]+$/', '', $value);
}
}
if (preg_match('#int|integer|double|float|real|numeric#', $options['type'])) {
$lang = $this->Application->recallObject('lang.current');
/* @var $lang LanguagesItem */
return $lang->formatNumber($value);
}
elseif ($options['type'] == 'string') {
$value = $this->_replaceFCKLinks($value, $options, $original_format);
}
return $value;
}
/**
* Performs basic type validation on form field value
*
* @param mixed $value
* @param string $field_name
* @param kDBItem|kDBList|kDBBase $object
* @return mixed
* @access public
*/
public function Parse($value, $field_name, &$object)
{
+ $options = $object->GetFieldOptions($field_name);
+
if ($value == '') {
- return NULL;
+ return $options['type'] == 'string' ? $value : null;
}
- $options = $object->GetFieldOptions($field_name);
$tc_value = $this->TypeCast($value, $options);
if ($tc_value === false) {
return $value; // for leaving badly formatted date on the form
}
if(isset($options['type'])) {
if (preg_match('#double|float|real|numeric#', $options['type'])) {
$tc_value = str_replace(',', '.', $tc_value);
}
}
if (isset($options['regexp'])) {
if (!preg_match($options['regexp'], $value)) {
$object->SetError($field_name, 'invalid_format');
}
}
return $tc_value;
}
function HumanFormat($format)
{
return $format;
}
/**
* The method is supposed to alter config options or cofigure object in some way based on its usage of formatters
* The methods is called for every field with formatter defined when configuring item.
* Could be used for adding additional VirtualFields to an object required by some special Formatter
*
* @param string $field_name
* @param array $field_options
* @param kDBBase $object
*/
function PrepareOptions($field_name, &$field_options, &$object)
{
}
/**
* Used for split fields like timestamp -> date, time
* Called from DBItem to update sub fields values after loading item
*
* @param string $field
* @param string $value
* @param Array $options
* @param kDBItem|kDBList|kDBBase $object
* @return void
* @access public
*/
public function UpdateSubFields($field, $value, &$options, &$object)
{
}
/**
* Used for split fields like timestamp -> date, time
* Called from DBItem Validate (before validation) to get back master field value from its sub_fields
*
* @param string $field
* @param mixed $value
* @param Array $options
* @param kDBItem|kDBList|kDBBase $object
*/
function UpdateMasterFields($field, $value, &$options, &$object)
{
}
/**
* Return sample value, that can be entered in this field
*
* @param string $field
* @param Array $options
* @param kDBItem|kDBList|kDBBase $object
* @return string
* @access public
*/
public function GetSample($field, &$options, &$object)
{
return isset($options['sample_value']) ? $options['sample_value'] : '';
}
}
Index: branches/5.3.x/core/kernel/utility/logger.php
===================================================================
--- branches/5.3.x/core/kernel/utility/logger.php (revision 16194)
+++ branches/5.3.x/core/kernel/utility/logger.php (revision 16195)
@@ -1,1367 +1,1348 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Class for logging system activity
*/
class kLogger extends kBase {
/**
* Prefix of all database related errors
*/
const DB_ERROR_PREFIX = 'SQL Error:';
-
/**
* Logger state: logging of errors and user-defined messages
*/
const STATE_ENABLED = 1;
/**
* Logger state: logging of user-defined messages only
*/
const STATE_USER_ONLY = 2;
/**
* Logger state: don't log anything
*/
const STATE_DISABLED = 0;
-
/**
* Log store: automatically determine where log should be written
*/
const LS_AUTOMATIC = 1;
/**
* Log store: always write log to database
*/
const LS_DATABASE = 2;
/**
* Log store: always write log to disk
*/
const LS_DISK = 3;
-
/**
* Log level: system is unusable
*/
const LL_EMERGENCY = 0;
/**
* Log level: action must be taken immediately
*/
const LL_ALERT = 1;
/**
* Log level: the system is in a critical condition
*/
const LL_CRITICAL = 2;
/**
* Log level: there is an error condition
*/
const LL_ERROR = 3;
/**
* Log level: there is a warning condition
*/
const LL_WARNING = 4;
/**
* Log level: a normal but significant condition
*/
const LL_NOTICE = 5;
/**
* Log level: a purely informational message
*/
const LL_INFO = 6;
/**
* Log level: messages generated to debug the application
*/
const LL_DEBUG = 7;
-
/**
* Log type: PHP related activity
*/
const LT_PHP = 1;
/**
* Log type: database related activity
*/
const LT_DATABASE = 2;
/**
* Log type: custom activity
*/
const LT_OTHER = 3;
-
/**
* Log interface: Front
*/
const LI_FRONT = 1;
/**
* Log interface: Admin
*/
const LI_ADMIN = 2;
/**
* Log interface: Cron (Front)
*/
const LI_CRON_FRONT = 3;
/**
* Log interface: Cron (Admin)
*/
const LI_CRON_ADMIN = 4;
/**
* Log interface: API
*/
const LI_API = 5;
-
/**
* Log notification status: disabled
*/
const LNS_DISABLED = 0;
/**
* Log notification status: pending
*/
const LNS_PENDING = 1;
/**
* Log notification status: sent
*/
const LNS_SENT = 2;
/**
* List of error/exception handlers
*
* @var Array
* @access protected
*/
protected $_handlers = Array ();
/**
* Long messages are saved here, because "trigger_error" doesn't support error messages over 1KB in size
*
* @var Array
* @access protected
*/
protected static $_longMessages = Array ();
/**
* Log record being worked on
*
* @var Array
* @access protected
*/
protected $_logRecord = Array ();
/**
* Maximal level of a message, that can be logged
*
* @var int
* @access protected
*/
protected $_maxLogLevel = self::LL_NOTICE;
/**
* State of the logger
*
* @var int
* @access protected
*/
protected $_state = self::STATE_DISABLED;
/**
* Caches state of debug mode
*
* @var bool
* @access protected
*/
protected $_debugMode = false;
/**
* Ignores backtrace record where following files are mentioned
*
* @var Array
* @access protected
*/
protected $_ignoreInTrace = Array ('logger.php', 'db_connection.php', 'db_load_balancer.php');
/**
* Create event log
*
* @param Array $methods_to_call List of invokable kLogger class method with their parameters (if any)
* @access public
*/
public function __construct($methods_to_call = Array ())
{
parent::__construct();
$system_config = kUtil::getSystemConfig();
$this->_debugMode = $this->Application->isDebugMode();
$this->setState($system_config->get('EnableSystemLog', self::STATE_DISABLED));
$this->_maxLogLevel = $system_config->get('SystemLogMaxLevel', self::LL_NOTICE);
foreach ($methods_to_call as $method_to_call) {
call_user_func_array(Array ($this, $method_to_call[0]), $method_to_call[1]);
}
if ( !kUtil::constOn('DBG_ZEND_PRESENT') && !$this->Application->isDebugMode() ) {
// don't report error on screen if debug mode is turned off
error_reporting(0);
ini_set('display_errors', 0);
}
register_shutdown_function(Array ($this, 'catchLastError'));
}
/**
* Sets state of the logged (enabled/user-only/disabled)
*
* @param $new_state
* @return void
* @access public
*/
public function setState($new_state = null)
{
if ( isset($new_state) ) {
$this->_state = (int)$new_state;
}
if ( $this->_state === self::STATE_ENABLED ) {
$this->_enableErrorHandling();
}
elseif ( $this->_state === self::STATE_DISABLED ) {
$this->_disableErrorHandling();
}
}
/**
* Enable error/exception handling capabilities
*
* @return void
* @access protected
*/
protected function _enableErrorHandling()
{
$this->_disableErrorHandling();
$this->_handlers[self::LL_ERROR] = new kErrorHandlerStack($this);
$this->_handlers[self::LL_CRITICAL] = new kExceptionHandlerStack($this);
}
/**
* Disables error/exception handling capabilities
*
* @return void
* @access protected
*/
protected function _disableErrorHandling()
{
foreach ($this->_handlers as $index => $handler) {
$this->_handlers[$index]->__destruct();
unset($this->_handlers[$index]);
}
}
/**
* Initializes new log record. Use "kLogger::write" to save to db/disk
*
* @param string $message
* @param int $code
* @return kLogger
* @access public
*/
public function prepare($message = '', $code = null)
{
$this->_logRecord = Array (
'LogUniqueId' => kUtil::generateId(),
'LogMessage' => $message,
'LogLevel' => self::LL_INFO,
'LogCode' => $code,
'LogType' => self::LT_OTHER,
'LogHostname' => $_SERVER['HTTP_HOST'],
'LogRequestSource' => php_sapi_name() == 'cli' ? 2 : 1,
'LogRequestURI' => php_sapi_name() == 'cli' ? implode(' ', $GLOBALS['argv']) : $_SERVER['REQUEST_URI'],
'LogUserId' => USER_GUEST,
'IpAddress' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '',
'LogSessionKey' => 0,
'LogProcessId' => getmypid(),
'LogUserData' => '',
'LogNotificationStatus' => self::LNS_DISABLED,
);
if ( $this->Application->isAdmin ) {
$this->_logRecord['LogInterface'] = defined('CRON') && CRON ? self::LI_CRON_ADMIN : self::LI_ADMIN;
}
else {
$this->_logRecord['LogInterface'] = defined('CRON') && CRON ? self::LI_CRON_FRONT : self::LI_FRONT;
}
if ( $this->Application->InitDone ) {
$this->_logRecord['LogUserId'] = $this->Application->RecallVar('user_id');
$this->_logRecord['LogSessionKey'] = $this->Application->GetSID();
}
return $this;
}
/**
* Sets one or more fields of log record
*
* @param string|Array $field_name
* @param string|null $field_value
* @return kLogger
* @access public
* @throws UnexpectedValueException
*/
public function setLogField($field_name, $field_value = null)
{
if ( isset($field_value) ) {
$this->_logRecord[$field_name] = $field_value;
}
elseif ( is_array($field_name) ) {
$this->_logRecord = array_merge($this->_logRecord, $field_name);
}
else {
throw new UnexpectedValueException('Invalid arguments');
}
return $this;
}
/**
* Sets user data
*
* @param string $data
* @param bool $as_array
* @return kLogger
* @access public
*/
public function setUserData($data, $as_array = false)
{
if ( $as_array ) {
$data = serialize((array)$data);
}
return $this->setLogField('LogUserData', $data);
}
/**
* Add user data
*
* @param string $data
* @param bool $as_array
* @return kLogger
* @access public
*/
public function addUserData($data, $as_array = false)
{
$new_data = $this->_logRecord['LogUserData'];
if ( $as_array ) {
$new_data = $new_data ? unserialize($new_data) : Array ();
$new_data[] = $data;
$new_data = serialize($new_data);
}
else {
$new_data .= ($new_data ? PHP_EOL : '') . $data;
}
return $this->setLogField('LogUserData', $new_data);
}
/**
* Adds event to log record
*
* @param kEvent $event
* @return kLogger
* @access public
*/
public function addEvent(kEvent $event)
{
$this->_logRecord['LogEventName'] = (string)$event;
return $this;
}
/**
* Adds log source file & file to log record
*
* @param string|Array $file_or_trace file path
* @param int $line file line
* @return kLogger
* @access public
*/
public function addSource($file_or_trace = '', $line = 0)
{
if ( is_array($file_or_trace) ) {
$trace_info = $file_or_trace[0];
$this->_logRecord['LogSourceFilename'] = $trace_info['file'];
$this->_logRecord['LogSourceFileLine'] = $trace_info['line'];
}
else {
$this->_logRecord['LogSourceFilename'] = $file_or_trace;
$this->_logRecord['LogSourceFileLine'] = $line;
}
return $this;
}
/**
* Adds session contents to log record
*
* @param bool $include_optional Include optional session variables
* @return kLogger
* @access public
*/
public function addSessionData($include_optional = false)
{
if ( $this->Application->InitDone ) {
$this->_logRecord['LogSessionData'] = serialize($this->Application->Session->getSessionData($include_optional));
}
return $this;
}
/**
* Adds user request information to log record
*
* @return kLogger
* @access public
*/
public function addRequestData()
{
$request_data = array(
'Headers' => $this->Application->HttpQuery->getHeaders(),
);
$request_variables = Array('_GET' => $_GET, '_POST' => $_POST, '_COOKIE' => $_COOKIE);
foreach ( $request_variables as $title => $data ) {
if ( !$data ) {
continue;
}
$request_data[$title] = $data;
}
$this->_logRecord['LogRequestData'] = serialize($request_data);
return $this;
}
/**
* Adds trace to log record
*
* @param Array $trace
* @param int $skip_levels
* @param Array $skip_files
* @return kLogger
* @access public
*/
public function addTrace($trace = null, $skip_levels = 1, $skip_files = null)
{
$trace = $this->createTrace($trace, $skip_levels, $skip_files);
foreach ($trace as $trace_index => $trace_info) {
if ( isset($trace_info['args']) ) {
$trace[$trace_index]['args'] = $this->_implodeObjects($trace_info['args']);
}
}
$this->_logRecord['LogBacktrace'] = serialize($this->_removeObjectsFromTrace($trace));
return $this;
}
/**
* Remove objects from trace, since before PHP 5.2.5 there wasn't possible to remove them initially
*
* @param Array $trace
* @return Array
* @access protected
*/
protected function _removeObjectsFromTrace($trace)
{
if ( version_compare(PHP_VERSION, '5.3', '>=') ) {
return $trace;
}
$trace_indexes = array_keys($trace);
foreach ($trace_indexes as $trace_index) {
unset($trace[$trace_index]['object']);
}
return $trace;
}
/**
* Implodes object to prevent memory leaks
*
* @param Array $array
* @return Array
* @access protected
*/
protected function _implodeObjects($array)
{
$ret = Array ();
foreach ($array as $key => $value) {
if ( is_array($value) ) {
$ret[$key] = $this->_implodeObjects($value);
}
elseif ( is_object($value) ) {
if ( $value instanceof kEvent ) {
$ret[$key] = 'Event: ' . (string)$value;
}
elseif ( $value instanceof kBase ) {
$ret[$key] = (string)$value;
}
else {
$ret[$key] = 'Class: ' . get_class($value);
}
}
+ elseif ( strlen($value) > 200 ) {
+ $ret[$key] = substr($value, 0, 50) . ' ...';
+ }
else {
$ret[$key] = $value;
}
}
return $ret;
}
/**
* Removes first N levels from trace
*
* @param Array $trace
* @param int $levels
* @param Array $files
* @return Array
* @access public
*/
public function createTrace($trace = null, $levels = null, $files = null)
{
if ( !isset($trace) ) {
$trace = debug_backtrace(false);
}
if ( !$trace ) {
// no trace information
return $trace;
}
if ( isset($levels) && is_numeric($levels) ) {
for ($i = 0; $i < $levels; $i++) {
array_shift($trace);
}
}
if ( isset($files) && is_array($files) ) {
while (true) {
$trace_info = $trace[0];
$file = isset($trace_info['file']) ? basename($trace_info['file']) : '';
if ( !in_array($file, $files) ) {
break;
}
array_shift($trace);
}
}
return $trace;
}
/**
* Adds PHP error to log record
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return kLogger
* @access public
*/
public function addError($errno, $errstr, $errfile = null, $errline = null)
{
$errstr = self::expandMessage($errstr, !$this->_debugMode);
$this->_logRecord['LogLevel'] = $this->_getLogLevelByErrorNo($errno);
if ( $this->isLogType(self::LT_DATABASE, $errstr) ) {
list ($errno, $errstr, $sql) = self::parseDatabaseError($errstr);
$this->_logRecord['LogType'] = self::LT_DATABASE;
$this->_logRecord['LogUserData'] = $sql;
$trace = $this->createTrace(null, 4, $this->_ignoreInTrace);
$this->addSource($trace);
$this->addTrace($trace, 0);
}
else {
$this->_logRecord['LogType'] = self::LT_PHP;
$this->addSource((string)$errfile, $errline);
$this->addTrace(null, 4);
}
$this->_logRecord['LogCode'] = $errno;
$this->_logRecord['LogMessage'] = $errstr;
return $this;
}
/**
* Adds PHP exception to log record
*
* @param Exception $exception
* @return kLogger
* @access public
*/
public function addException($exception)
{
$errstr = self::expandMessage($exception->getMessage(), !$this->_debugMode);
$this->_logRecord['LogLevel'] = self::LL_CRITICAL;
if ( $this->isLogType(self::LT_DATABASE, $errstr) ) {
list ($errno, $errstr, $sql) = self::parseDatabaseError($errstr);
$this->_logRecord['LogType'] = self::LT_DATABASE;
$this->_logRecord['LogUserData'] = $sql;
$trace = $this->createTrace($exception->getTrace(), null, $this->_ignoreInTrace);
$this->addSource($trace);
$this->addTrace($trace, 0);
}
else {
$this->_logRecord['LogType'] = self::LT_PHP;
$errno = $exception->getCode();
$this->addSource((string)$exception->getFile(), $exception->getLine());
$this->addTrace($exception->getTrace(), 0);
}
$this->_logRecord['LogCode'] = $errno;
$this->_logRecord['LogMessage'] = $errstr;
return $this;
}
/**
* Allows to map PHP error numbers to syslog log level
*
* @param int $errno
* @return int
* @access protected
*/
protected function _getLogLevelByErrorNo($errno)
{
$error_number_mapping = Array (
self::LL_ERROR => Array (E_RECOVERABLE_ERROR, E_USER_ERROR, E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE),
self::LL_WARNING => Array (E_WARNING, E_USER_WARNING, E_CORE_WARNING, E_COMPILE_WARNING),
self::LL_NOTICE => Array (E_NOTICE, E_USER_NOTICE, E_STRICT),
);
if ( version_compare(PHP_VERSION, '5.3.0', '>=') ) {
$error_number_mapping[self::LL_NOTICE][] = E_DEPRECATED;
$error_number_mapping[self::LL_NOTICE][] = E_USER_DEPRECATED;
}
foreach ($error_number_mapping as $log_level => $error_numbers) {
if ( in_array($errno, $error_numbers) ) {
return $log_level;
}
}
return self::LL_ERROR;
}
/**
* Changes log level of a log record
*
* @param int $log_level
* @return kLogger
* @access public
*/
public function setLogLevel($log_level)
{
$this->_logRecord['LogLevel'] = $log_level;
return $this;
}
/**
* Writes prepared log to database or disk, when database isn't available
*
* @param int $storage_medium
* @return bool|int
* @access public
* @throws InvalidArgumentException
*/
public function write($storage_medium = self::LS_AUTOMATIC)
{
if ( !$this->_logRecord || $this->_logRecord['LogLevel'] > $this->_maxLogLevel || $this->_state == self::STATE_DISABLED ) {
// nothing to save OR less detailed logging requested OR disabled
return false;
}
$this->_logRecord['LogMemoryUsed'] = memory_get_usage();
$this->_logRecord['LogTimestamp'] = time();
$this->_logRecord['LogDate'] = date('Y-m-d H:i:s');
if ( $storage_medium == self::LS_AUTOMATIC ) {
$storage_medium = $this->Conn->connectionOpened() ? self::LS_DATABASE : self::LS_DISK;
}
if ( $storage_medium == self::LS_DATABASE ) {
$result = $this->Conn->doInsert($this->_logRecord, TABLE_PREFIX . 'SystemLog');
}
elseif ( $storage_medium == self::LS_DISK ) {
$result = $this->_saveToFile(RESTRICTED . '/system.log');
}
else {
throw new InvalidArgumentException('Unknown storage medium "' . $storage_medium . '"');
}
$unique_id = $this->_logRecord['LogUniqueId'];
if ( $this->_logRecord['LogNotificationStatus'] == self::LNS_SENT ) {
$this->_sendNotification($unique_id);
}
$this->_logRecord = Array ();
return $result ? $unique_id : false;
}
/**
* Catches last error happened before script ended
*
* @return void
* @access public
*/
public function catchLastError()
{
$this->write();
$last_error = error_get_last();
if ( !is_null($last_error) && isset($this->_handlers[self::LL_ERROR]) ) {
$handler = $this->_handlers[self::LL_ERROR];
/* @var $handler kErrorHandlerStack */
$handler->handle($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
}
}
/**
* Deletes log with given id from database or disk, when database isn't available
*
* @param int $unique_id
* @param int $storage_medium
* @return void
* @access public
* @throws InvalidArgumentException
*/
public function delete($unique_id, $storage_medium = self::LS_AUTOMATIC)
{
if ( $storage_medium == self::LS_AUTOMATIC ) {
$storage_medium = $this->Conn->connectionOpened ? self::LS_DATABASE : self::LS_DISK;
}
if ( $storage_medium == self::LS_DATABASE ) {
$sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemLog
WHERE LogUniqueId = ' . $unique_id;
$this->Conn->Query($sql);
}
elseif ( $storage_medium == self::LS_DISK ) {
// TODO: no way to delete a line from a file
}
else {
throw new InvalidArgumentException('Unknown storage medium "' . $storage_medium . '"');
}
}
/**
* Send notification (delayed or instant) about log record to e-mail from configuration
*
* @param bool $instant
* @return kLogger
* @access public
*/
public function notify($instant = false)
{
$this->_logRecord['LogNotificationStatus'] = $instant ? self::LNS_SENT : self::LNS_PENDING;
return $this;
}
/**
* Sends notification e-mail about message with given $unique_id
*
* @param int $unique_id
* @return void
* @access protected
*/
protected function _sendNotification($unique_id)
{
$notification_email = $this->Application->ConfigValue('SystemLogNotificationEmail');
if ( !$notification_email ) {
trigger_error('System Log notification E-mail not specified', E_USER_NOTICE);
return;
}
$send_params = Array (
'to_name' => $notification_email,
'to_email' => $notification_email,
);
// initialize list outside of e-mail event with right settings
$this->Application->recallObject('system-log.email', 'system-log_List', Array ('unique_id' => $unique_id));
$this->Application->emailAdmin('SYSTEM.LOG.NOTIFY', null, $send_params);
$this->Application->removeObject('system-log.email');
}
/**
* Adds error/exception handler
*
* @param string|Array $handler
* @param bool $is_exception
* @return void
* @access public
*/
public function addErrorHandler($handler, $is_exception = false)
{
$this->_handlers[$is_exception ? self::LL_CRITICAL : self::LL_ERROR]->add($handler);
}
/**
* SQL Error Handler
*
* When not debug mode, then fatal database query won't break anything.
*
* @param int $code
* @param string $msg
* @param string $sql
* @return bool
* @access public
* @throws RuntimeException
*/
public function handleSQLError($code, $msg, $sql)
{
$error_msg = self::shortenMessage(self::DB_ERROR_PREFIX . ' #' . $code . ' - ' . $msg . '. SQL: ' . trim($sql));
if ( isset($this->Application->Debugger) ) {
if ( kUtil::constOn('DBG_SQL_FAILURE') && !defined('IS_INSTALL') ) {
throw new RuntimeException($error_msg);
}
else {
$this->Application->Debugger->appendTrace();
}
}
// next line also trigger attached error handlers
trigger_error($error_msg, E_USER_WARNING);
return true;
}
/**
* Packs information about error into a single line
*
* @param string $errno
* @param bool $strip_tags
* @return string
* @access public
*/
public function toString($errno = null, $strip_tags = false)
{
if ( !isset($errno) ) {
$errno = $this->_logRecord['LogCode'];
}
$errstr = $this->_logRecord['LogMessage'];
$errfile = $this->_logRecord['LogSourceFilename'];
$errline = $this->_logRecord['LogSourceFileLine'];
$result = '<strong>' . $errno . ': </strong>' . "{$errstr} in {$errfile} on line {$errline}";
return $strip_tags ? strip_tags($result) : $result;
}
/**
* Saves log to file (e.g. when not possible to save into database)
*
* @param $filename
* @return bool
* @access protected
*/
protected function _saveToFile($filename)
{
$time = date('Y-m-d H:i:s');
$log_file = new SplFileObject($filename, 'a');
return $log_file->fwrite('[' . $time . '] #' . $this->toString(null, true) . PHP_EOL) > 0;
}
/**
* Checks if log type of current log record matches given one
*
* @param int $log_type
* @param string $log_message
* @return bool
* @access public
*/
public function isLogType($log_type, $log_message = null)
{
if ( $this->_logRecord['LogType'] == $log_type ) {
return true;
}
if ( $log_type == self::LT_DATABASE ) {
if ( !isset($log_message) ) {
$log_message = $this->_logRecord['LogMessage'];
}
return strpos($log_message, self::DB_ERROR_PREFIX) !== false;
}
return false;
}
/**
* Shortens message
*
* @param string $message
* @return string
* @access public
*/
public static function shortenMessage($message)
{
$max_len = ini_get('log_errors_max_len');
if ( strlen($message) > $max_len ) {
$long_key = kUtil::generateId();
self::$_longMessages[$long_key] = $message;
return mb_substr($message, 0, $max_len - strlen($long_key) - 2) . ' #' . $long_key;
}
return $message;
}
/**
* Expands shortened message
*
* @param string $message
* @param bool $clear_cache Allow debugger to expand message after it's been expanded by kLogger
* @return string
* @access public
*/
public static function expandMessage($message, $clear_cache = true)
{
if ( preg_match('/(.*)#([\d]+)$/', $message, $regs) ) {
$long_key = $regs[2];
if ( isset(self::$_longMessages[$long_key]) ) {
$message = self::$_longMessages[$long_key];
if ( $clear_cache ) {
unset(self::$_longMessages[$long_key]);
}
}
}
return $message;
}
/**
* Parses database error message into error number, error message and sql that caused that error
*
* @static
* @param string $message
* @return Array
* @access public
*/
public static function parseDatabaseError($message)
{
$regexp = '/' . preg_quote(self::DB_ERROR_PREFIX) . ' #(.*?) - (.*?)\. SQL: (.*?)$/s';
if ( preg_match($regexp, $message, $regs) ) {
// errno, errstr, sql
return Array ($regs[1], $regs[2], $regs[3]);
}
return Array (0, $message, '');
}
}
/**
* Base class for error or exception handling
*/
abstract class kHandlerStack extends kBase {
/**
* List of added handlers
*
* @var Array
* @access protected
*/
protected $_handlers = Array ();
/**
* Reference to event log, which created this object
*
* @var kLogger
* @access protected
*/
protected $_logger;
/**
* Remembers if handler is activated
*
* @var bool
* @access protected
*/
protected $_enabled = false;
public function __construct(kLogger $logger)
{
parent::__construct();
$this->_logger = $logger;
if ( !kUtil::constOn('DBG_ZEND_PRESENT') ) {
$this->attach();
$this->_enabled = true;
}
}
/**
* Detaches from error handling routines on class destruction
*
* @return void
* @access public
*/
public function __destruct()
{
if ( !$this->_enabled ) {
return;
}
$this->detach();
$this->_enabled = false;
}
/**
* Attach to error handling routines
*
* @abstract
* @return void
* @access protected
*/
abstract protected function attach();
/**
* Detach from error handling routines
*
* @abstract
* @return void
* @access protected
*/
abstract protected function detach();
/**
* Adds new handler to the stack
*
* @param callable $handler
* @return void
* @access public
*/
public function add($handler)
{
$this->_handlers[] = $handler;
}
protected function _handleFatalError($errno)
{
$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
$skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING;
if ( !$this->_handlers || ($debug_mode && $skip_reporting) ) {
// when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request)
if ( $this->_isFatalError($errno) ) {
$this->_displayFatalError($errno);
}
if ( !$this->_handlers ) {
return true;
}
}
return null;
}
/**
* Determines if given error is a fatal
*
* @abstract
* @param Exception|int $errno
* @return bool
*/
abstract protected function _isFatalError($errno);
/**
* Displays div with given error message
*
* @param string $errno
* @return void
* @access protected
*/
protected function _displayFatalError($errno)
{
$errno = $this->_getFatalErrorTitle($errno);
$margin = $this->Application->isAdmin ? '8px' : 'auto';
echo '<div style="background-color: #FEFFBF; margin: ' . $margin . '; padding: 10px; border: 2px solid red; text-align: center">' . $this->_logger->toString($errno) . '</div>';
exit;
}
/**
* Returns title to show for a fatal
*
* @abstract
* @param Exception|int $errno
* @return string
*/
abstract protected function _getFatalErrorTitle($errno);
}
/**
* Class, that handles errors
*/
class kErrorHandlerStack extends kHandlerStack {
/**
* Attach to error handling routines
*
* @return void
* @access protected
*/
protected function attach()
{
// set as error handler
$error_handler = set_error_handler(Array ($this, 'handle'));
if ( $error_handler ) {
// wrap around previous error handler, if any was set
$this->_handlers[] = $error_handler;
}
}
/**
* Detach from error handling routines
*
* @return void
* @access protected
*/
protected function detach()
{
restore_error_handler();
}
/**
* Determines if given error is a fatal
*
* @param int $errno
* @return bool
* @access protected
*/
protected function _isFatalError($errno)
{
$fatal_errors = Array (E_USER_ERROR, E_RECOVERABLE_ERROR, E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE);
return in_array($errno, $fatal_errors);
}
/**
* Returns title to show for a fatal
*
* @param int $errno
* @return string
* @access protected
*/
protected function _getFatalErrorTitle($errno)
{
return 'Fatal Error';
}
/**
* Default error handler
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @param Array $errcontext
* @return bool
* @access public
*/
public function handle($errno, $errstr, $errfile = null, $errline = null, $errcontext = Array ())
{
$log = $this->_logger->prepare()->addError($errno, $errstr, $errfile, $errline);
if ( $this->_handleFatalError($errno) ) {
$log->write();
return true;
}
$log->write();
$res = false;
- /* @var $handler Closure */
foreach ($this->_handlers as $handler) {
- if ( is_array($handler) ) {
- $object =& $handler[0];
- $method = $handler[1];
- $res = $object->$method($errno, $errstr, $errfile, $errline, $errcontext);
- }
- else {
- $res = $handler($errno, $errstr, $errfile, $errline, $errcontext);
- }
+ $res = call_user_func($handler, $errno, $errstr, $errfile, $errline, $errcontext);
}
return $res;
}
}
/**
* Class, that handles exceptions
*/
class kExceptionHandlerStack extends kHandlerStack {
/**
* Attach to error handling routines
*
* @return void
* @access protected
*/
protected function attach()
{
// set as exception handler
$exception_handler = set_exception_handler(Array ($this, 'handle'));
if ( $exception_handler ) {
// wrap around previous exception handler, if any was set
$this->_handlers[] = $exception_handler;
}
}
/**
* Detach from error handling routines
*
* @return void
* @access protected
*/
protected function detach()
{
restore_exception_handler();
}
/**
* Determines if given error is a fatal
*
* @param Exception $errno
* @return bool
*/
protected function _isFatalError($errno)
{
return true;
}
/**
* Returns title to show for a fatal
*
* @param Exception $errno
* @return string
*/
protected function _getFatalErrorTitle($errno)
{
return get_class($errno);
}
/**
* Handles exception
*
* @param Exception $exception
* @return bool
* @access public
*/
public function handle($exception)
{
$log = $this->_logger->prepare()->addException($exception);
if ( $exception instanceof kRedirectException ) {
/* @var $exception kRedirectException */
$exception->run();
}
if ( $this->_handleFatalError($exception) ) {
$log->write();
return true;
}
$log->write();
$res = false;
- /* @var $handler Closure */
foreach ($this->_handlers as $handler) {
- if ( is_array($handler) ) {
- $object =& $handler[0];
- $method = $handler[1];
- $res = $object->$method($exception);
- }
- else {
- $res = $handler($exception);
- }
+ $res = call_user_func($handler, $exception);
}
return $res;
}
-}
\ No newline at end of file
+}
Index: branches/5.3.x/core/kernel/utility/debugger.php
===================================================================
--- branches/5.3.x/core/kernel/utility/debugger.php (revision 16194)
+++ branches/5.3.x/core/kernel/utility/debugger.php (revision 16195)
@@ -1,2054 +1,2089 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
if( !class_exists('Debugger') ) {
/**
* Contains misc functions, used by debugger (mostly copied from kUtil class)
*/
class DebuggerUtil {
/**
* Trust information, provided by proxy
*
* @var bool
*/
public static $trustProxy = false;
/**
* Checks if constant is defined and has positive value
*
* @param string $const_name
* @return bool
*/
public static function constOn($const_name)
{
return defined($const_name) && constant($const_name);
}
/**
* Define constant if it was not already defined before
*
* @param string $const_name
* @param string $const_value
* @access public
*/
public static function safeDefine($const_name, $const_value)
{
if ( !defined($const_name) ) {
define($const_name, $const_value);
}
}
/**
* Formats file/memory size in nice way
*
* @param int $bytes
* @return string
* @access public
*/
public static function formatSize($bytes)
{
if ($bytes >= 1099511627776) {
$return = round($bytes / 1024 / 1024 / 1024 / 1024, 2);
$suffix = "TB";
} elseif ($bytes >= 1073741824) {
$return = round($bytes / 1024 / 1024 / 1024, 2);
$suffix = "GB";
} elseif ($bytes >= 1048576) {
$return = round($bytes / 1024 / 1024, 2);
$suffix = "MB";
} elseif ($bytes >= 1024) {
$return = round($bytes / 1024, 2);
$suffix = "KB";
} else {
$return = $bytes;
$suffix = "Byte";
}
$return .= ' '.$suffix;
return $return;
}
/**
* Checks, that user IP address is within allowed range
*
* @param string $ip_list semi-column (by default) separated ip address list
* @param string $separator ip address separator (default ";")
*
* @return bool
*/
public static function ipMatch($ip_list, $separator = ';')
{
if ( php_sapi_name() == 'cli' ) {
return false;
}
$ip_match = false;
$ip_addresses = $ip_list ? explode($separator, $ip_list) : Array ();
$client_ip = self::getClientIp();
foreach ($ip_addresses as $ip_address) {
if ( self::netMatch($ip_address, $client_ip) ) {
$ip_match = true;
break;
}
}
return $ip_match;
}
/**
* Returns the client IP address.
*
* @return string The client IP address
* @access public
*/
public static function getClientIp()
{
if ( self::$trustProxy ) {
if ( array_key_exists('HTTP_CLIENT_IP', $_SERVER) ) {
return $_SERVER['HTTP_CLIENT_IP'];
}
if ( array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) ) {
$client_ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
foreach ($client_ip as $ip_address) {
$clean_ip_address = trim($ip_address);
if ( false !== filter_var($clean_ip_address, FILTER_VALIDATE_IP) ) {
return $clean_ip_address;
}
}
return '';
}
}
return $_SERVER['REMOTE_ADDR'];
}
/**
* Checks, that given ip belongs to given subnet
*
* @param string $network
* @param string $ip
* @return bool
* @access public
*/
public static function netMatch($network, $ip) {
$network = trim($network);
$ip = trim($ip);
if ( preg_replace('/[\d\.\/-]/', '', $network) != '' ) {
$network = gethostbyname($network);
}
if ($network == $ip) {
// comparing two ip addresses directly
return true;
}
$d = strpos($network, '-');
if ($d !== false) {
// ip address range specified
$from = ip2long(trim(substr($network, 0, $d)));
$to = ip2long(trim(substr($network, $d + 1)));
$ip = ip2long($ip);
return ($ip >= $from && $ip <= $to);
}
elseif (strpos($network, '/') !== false) {
// single subnet specified
$ip_arr = explode('/', $network);
if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)) {
$ip_arr[0] .= '.0'; // Alternate form 194.1.4/24
}
$network_long = ip2long($ip_arr[0]);
$x = ip2long($ip_arr[1]);
$mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1]));
$ip_long = ip2long($ip);
return ($ip_long & $mask) == ($network_long & $mask);
}
return false;
}
}
/**
* Main debugger class, that can be used with any In-Portal (or not) project
*/
class Debugger {
const ROW_TYPE_ERROR = 'error';
const ROW_TYPE_WARNING = 'warning';
const ROW_TYPE_NOTICE = 'notice';
const ROW_TYPE_SQL = 'sql';
const ROW_TYPE_OTHER = 'other';
/**
* Holds reference to global KernelApplication instance
*
* @var kApplication
* @access private
*/
private $Application = null;
/**
- * Set to true if fatal error occurred
+ * Stores last fatal error hash or 0, when no fatal error happened
*
- * @var bool
- * @access private
+ * @var integer
*/
- private $IsFatalError = false;
-
- /**
- * Tells if last error (if any) caught by shutdown function was processed
- *
- * @var bool
- * @access private
- */
- private $_lastErrorProcessed = false;
+ private $_fatalErrorHash = 0;
private $_filterTypes = Array ('error', 'sql', 'other');
/**
* Counts warnings on the page
*
* @var int
* @access public
*/
public $WarningCount = 0;
/**
* Allows to track compile errors, like "stack-overflow"
*
* @var bool
* @access private
*/
private $_compileError = false;
/**
* Debugger data for building report
*
* @var Array
* @access private
*/
private $Data = Array ();
/**
* Holds information about each profiler record (start/end/description)
*
* @var Array
* @access private
*/
private $ProfilerData = Array ();
/**
* Holds information about total execution time per profiler key (e.g. total sql time)
*
* @var Array
* @access private
*/
private $ProfilerTotals = Array ();
/**
* Counts how much each of total types were called (e.g. total error count)
*
* @var Array
* @access private
*/
private $ProfilerTotalCount = Array ();
/**
* Holds information about all profile points registered
*
* @var Array
* @access private
*/
private $ProfilePoints = Array ();
/**
* Prevent recursion when processing debug_backtrace() function results
*
* @var Array
* @access private
*/
private $RecursionStack = Array ();
/**
* Cross browser debugger report scrollbar width detection
*
* @var int
* @access private
*/
private $scrollbarWidth = 0;
/**
* Remembers how much memory & time was spent on including files
*
* @var Array
* @access public
* @see kUtil::includeOnce
*/
public $IncludesData = Array ();
/**
* Remembers maximal include deep level
*
* @var int
* @access public
* @see kUtil::includeOnce
*/
public $IncludeLevel = 0;
/**
* Prevents report generation more then once
*
* @var bool
* @access private
*/
- private $reportDone = false;
+ private $_inReportPrinting = false;
/**
* Transparent spacer image used in case of none spacer image defined via SPACER_URL constant.
* Used while drawing progress bars (memory usage, time usage, etc.)
*
* @var string
* @access private
*/
private $dummyImage = '';
/**
* Temporary files created by debugger will be stored here
*
* @var string
* @access private
*/
private $tempFolder = '';
/**
* Debug rows will be separated using this string before writing to debug file
*
* @var string
* @access private
*/
private $rowSeparator = '@@';
/**
* Base URL for debugger includes
*
* @var string
* @access private
*/
private $baseURL = '';
/**
* Sub-folder, where In-Portal is installed
*
* @var string
* @access private
*/
private $basePath = '';
/**
* Holds last recorded timestamp (for appendTimestamp)
*
* @var int
* @access private
*/
private $LastMoment;
/**
* Determines, that current request is AJAX request
*
* @var bool
* @access private
*/
private $_isAjax = false;
/**
* Creates instance of debugger
*/
public function __construct()
{
global $start, $dbg_options;
// check if user haven't defined DEBUG_MODE contant directly
if ( defined('DEBUG_MODE') && DEBUG_MODE ) {
die('error: constant DEBUG_MODE defined directly, please use <strong>$dbg_options</strong> array instead');
}
if ( class_exists('kUtil') ) {
DebuggerUtil::$trustProxy = kUtil::getSystemConfig()->get('TrustProxy');
}
// check IP before enabling debug mode
$ip_match = DebuggerUtil::ipMatch(isset($dbg_options['DBG_IP']) ? $dbg_options['DBG_IP'] : '');
if ( !$ip_match || (isset($_COOKIE['debug_off']) && $_COOKIE['debug_off']) ) {
define('DEBUG_MODE', 0);
return;
}
// debug is allowed for user, continue initialization
$this->InitDebugger();
$this->profileStart('kernel4_startup', 'Startup and Initialization of kernel4', $start);
$this->profileStart('script_runtime', 'Script runtime', $start);
$this->LastMoment = $start;
error_reporting(E_ALL & ~E_STRICT);
// show errors on screen in case if not in Zend Studio debugging
ini_set('display_errors', DebuggerUtil::constOn('DBG_ZEND_PRESENT') ? 0 : 1);
// vertical scrollbar width differs in Firefox and other browsers
$this->scrollbarWidth = $this->isGecko() ? 22 : 25;
$this->appendRequest();
}
/**
* Set's default values to constants debugger uses
*
*/
function InitDebugger()
{
global $dbg_options;
unset($dbg_options['DBG_IP']);
// Detect fact, that this session being debugged by Zend Studio
foreach ($_COOKIE as $cookie_name => $cookie_value) {
if (substr($cookie_name, 0, 6) == 'debug_') {
DebuggerUtil::safeDefine('DBG_ZEND_PRESENT', 1);
break;
}
}
DebuggerUtil::safeDefine('DBG_ZEND_PRESENT', 0); // set this constant value to 0 (zero) to debug debugger using Zend Studio
// set default values for debugger constants
$dbg_constMap = Array (
'DBG_USE_HIGHLIGHT' => 1, // highlight output same as php code using "highlight_string" function
'DBG_WINDOW_WIDTH' => 700, // set width of debugger window (in pixels) for better viewing large amount of debug data
'DBG_USE_SHUTDOWN_FUNC' => DBG_ZEND_PRESENT ? 0 : 1, // use shutdown function to include debugger code into output
'DBG_HANDLE_ERRORS' => DBG_ZEND_PRESENT ? 0 : 1, // handle all allowed by php (see php manual) errors instead of default handler
'DBG_DOMVIEWER' => '/temp/domviewer.html', // path to DOMViewer on website
'DOC_ROOT' => str_replace('\\', '/', realpath($_SERVER['DOCUMENT_ROOT']) ), // windows hack
'DBG_LOCAL_BASE_PATH' => 'w:', // replace DOC_ROOT in filenames (in errors) using this path
'DBG_EDITOR_URL' => 'file://%F:%L',
'DBG_SHORTCUT' => 'F12', // Defines debugger activation shortcut (any symbols or Ctrl/Alt/Shift are allowed, e.g. Ctrl+Alt+F12)
);
// debugger is initialized before kHTTPQuery, so do jQuery headers check here too
if (array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
$this->_isAjax = true;
}
elseif (array_key_exists('ajax', $_GET) && $_GET['ajax'] == 'yes') {
$this->_isAjax = true;
}
// user defined options override debugger defaults
$dbg_constMap = array_merge($dbg_constMap, $dbg_options);
if ($this->_isAjax && array_key_exists('DBG_SKIP_AJAX', $dbg_constMap) && $dbg_constMap['DBG_SKIP_AJAX']) {
$dbg_constMap['DBG_SKIP_REPORTING'] = 1;
}
// allows to validate unit configs via request variable
if ( !array_key_exists('DBG_VALIDATE_CONFIGS', $dbg_constMap) ) {
$dbg_constMap['DBG_VALIDATE_CONFIGS'] = array_key_exists('validate_configs', $_GET) ? (int)$_GET['validate_configs'] : 0;
}
// when validation configs, don't show sqls for better validation error displaying
if ($dbg_constMap['DBG_VALIDATE_CONFIGS']) {
$dbg_constMap['DBG_SQL_PROFILE'] = 0;
}
// when showing explain make shure, that debugger window is large enough
if (array_key_exists('DBG_SQL_EXPLAIN', $dbg_constMap) && $dbg_constMap['DBG_SQL_EXPLAIN']) {
$dbg_constMap['DBG_WINDOW_WIDTH'] = 1000;
}
foreach ($dbg_constMap as $dbg_constName => $dbg_constValue) {
DebuggerUtil::safeDefine($dbg_constName, $dbg_constValue);
}
}
/**
* Performs debugger initialization
*
* @return void
*/
private function InitReport()
{
if ( !class_exists('kApplication') ) {
return;
}
$application =& kApplication::Instance();
// string used to separate debugger records while in file (used in debugger dump filename too)
$this->rowSeparator = '@' . (/*is_object($application->Factory) &&*/ $application->InitDone ? $application->GetSID() : 0) . '@';
// $this->rowSeparator = '@' . rand(0, 100000) . '@';
// include debugger files from this url
$reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/';
$kernel_path = preg_replace($reg_exp, '', KERNEL_PATH, 1);
$this->baseURL = PROTOCOL . SERVER_NAME . (defined('PORT') ? ':' . PORT : '') . rtrim(BASE_PATH, '/') . $kernel_path . '/utility/debugger';
// store debugger cookies at this path
$this->basePath = rtrim(BASE_PATH, '/');
// save debug output in this folder
$this->tempFolder = defined('RESTRICTED') ? RESTRICTED : WRITEABLE . '/cache';
}
/**
* Appends all passed variable values (without variable names) to debug output
*
* @return void
* @access public
*/
public function dumpVars()
{
$dump_mode = 'var_dump';
$dumpVars = func_get_args();
if ( $dumpVars[count($dumpVars) - 1] === 'STRICT' ) {
$dump_mode = 'strict_var_dump';
array_pop($dumpVars);
}
foreach ($dumpVars as $varValue) {
$this->Data[] = Array ('value' => $varValue, 'debug_type' => $dump_mode);
}
}
/**
* Transforms collected data at given index into human-readable HTML to place in debugger report
*
* @param int $dataIndex
* @return string
* @access private
*/
private function prepareHTML($dataIndex)
{
static $errors_displayed = 0;
$Data =& $this->Data[$dataIndex];
if ( $Data['debug_type'] == 'html' ) {
return $Data['html'];
}
switch ($Data['debug_type']) {
case 'error':
$errors_displayed++;
$fileLink = $this->getFileLink($Data['file'], $Data['line']);
$ret = '<b class="debug_error">' . $this->getErrorNameByCode($Data['no']) . ' (#' . $errors_displayed . ')</b>: ' . $Data['str'];
$ret .= ' in <b>' . $fileLink . '</b> on line <b>' . $Data['line'] . '</b>';
return $ret;
break;
case 'exception':
$fileLink = $this->getFileLink($Data['file'], $Data['line']);
$ret = '<b class="debug_error">' . $Data['exception_class'] . '</b>: ' . $Data['str'];
$ret .= ' in <b>' . $fileLink . '</b> on line <b>' . $Data['line'] . '</b>';
return $ret;
break;
case 'var_dump':
return $this->highlightString($this->print_r($Data['value'], true));
break;
case 'strict_var_dump':
return $this->highlightString(var_export($Data['value'], true));
break;
case 'trace':
ini_set('memory_limit', '500M');
$trace =& $Data['trace'];
$i = 0;
$traceCount = count($trace);
$ret = '';
while ( $i < $traceCount ) {
$traceRec =& $trace[$i];
$argsID = 'trace_args_' . $dataIndex . '_' . $i;
$has_args = isset($traceRec['args']);
if ( isset($traceRec['file']) ) {
$func_name = isset($traceRec['class']) ? $traceRec['class'] . $traceRec['type'] . $traceRec['function'] : $traceRec['function'];
$args_link = $has_args ? '<a href="javascript:$Debugger.ToggleTraceArgs(\'' . $argsID . '\');" title="Show/Hide Function Arguments"><b>Function</b></a>' : '<strong>Function</strong>';
$ret .= $args_link . ': ' . $this->getFileLink($traceRec['file'], $traceRec['line'], $func_name);
$ret .= ' in <b>' . basename($traceRec['file']) . '</b> on line <b>' . $traceRec['line'] . '</b><br>';
}
else {
$ret .= 'no file information available';
}
if ( $has_args ) {
// if parameter value is longer then 200 symbols, then leave only first 50
$args = $this->highlightString($this->print_r($traceRec['args'], true));
$ret .= '<div id="' . $argsID . '" style="display: none;">' . $args . '</div>';
}
$i++;
}
return $ret;
break;
case 'profiler':
$profileKey = $Data['profile_key'];
$Data =& $this->ProfilerData[$profileKey];
$runtime = ($Data['ends'] - $Data['begins']); // in seconds
$totals_key = getArrayValue($Data, 'totalsKey');
if ( $totals_key ) {
$total_before = $Data['totalsBefore'];
$total = $this->ProfilerTotals[$totals_key];
$div_width = Array ();
$total_width = ($this->getWindowWidth() - 10);
$div_width['before'] = round(($total_before / $total) * $total_width);
$div_width['current'] = round(($runtime / $total) * $total_width);
$div_width['left'] = round((($total - $total_before - $runtime) / $total) * $total_width);
$subtitle = array_key_exists('subtitle', $Data) ? ' (' . $Data['subtitle'] . ')' : '';
$ret = '<b>Name' . $subtitle . '</b>: ' . $Data['description'] . '<br />';
$additional = isset($Data['additional']) ? $Data['additional'] : Array ();
if ( isset($Data['file']) ) {
array_unshift($additional, Array ('name' => 'File', 'value' => $this->getFileLink($Data['file'], $Data['line'], basename($Data['file']) . ':' . $Data['line'])));
}
array_unshift($additional, Array ('name' => 'Runtime', 'value' => $runtime . 's'));
$ret .= '<div>'; //FF 3.5 needs this!
foreach ($additional as $mixed_param) {
$ret .= '[<strong>' . $mixed_param['name'] . '</strong>: ' . $mixed_param['value'] . '] ';
}
/*if ( isset($Data['file']) ) {
$ret .= '[<b>Runtime</b>: ' . $runtime . 's] [<b>File</b>: ' . $this->getFileLink($Data['file'], $Data['line'], basename($Data['file']) . ':' . $Data['line']) . ']<br />';
}
else {
$ret .= '<b>Runtime</b>: ' . $runtime . 's<br />';
}*/
$ret .= '</div>';
$ret .= '<div class="dbg_profiler" style="width: ' . $div_width['before'] . 'px; border-right: 0px; background-color: #298DDF;"><img src="' . $this->dummyImage . '" width="1" height="1"/></div>';
$ret .= '<div class="dbg_profiler" style="width: ' . $div_width['current'] . 'px; border-left: 0px; border-right: 0px; background-color: #EF4A4A;"><img src="' . $this->dummyImage . '" width="1" height="1"/></div>';
$ret .= '<div class="dbg_profiler" style="width: ' . $div_width['left'] . 'px; border-left: 0px; background-color: #DFDFDF;"><img src="' . $this->dummyImage . '" width="1" height="1"/></div>';
return $ret;
}
else {
return '<b>Name</b>: ' . $Data['description'] . '<br><b>Runtime</b>: ' . $runtime . 's';
}
break;
default:
return 'incorrect debug data';
break;
}
}
/**
* Returns row type for debugger row.
*
* @param integer $data_index Index of the row.
*
* @return string
*/
protected function getRowType($data_index)
{
$data = $this->Data[$data_index];
switch ($data['debug_type']) {
case 'html':
if ( strpos($data['html'], 'SQL Total time') !== false ) {
return self::ROW_TYPE_SQL;
}
break;
case 'error':
$error_map = array(
'Fatal Error' => self::ROW_TYPE_ERROR,
'Warning' => self::ROW_TYPE_WARNING,
'Notice' => self::ROW_TYPE_NOTICE,
);
return $error_map[$this->getErrorNameByCode($data['no'])];
break;
case 'exception':
return self::ROW_TYPE_ERROR;
break;
case 'profiler':
if ( preg_match('/^sql_/', $data['profile_key']) ) {
return self::ROW_TYPE_SQL;
}
break;
}
return self::ROW_TYPE_OTHER;
}
/**
* Returns debugger report window width excluding scrollbar
*
* @return int
* @access private
*/
private function getWindowWidth()
{
return DBG_WINDOW_WIDTH - $this->scrollbarWidth - 8;
}
/**
* Tells debugger to skip objects that are heavy in plan of memory usage while printing debug_backtrace results
*
* @param Object $object
* @return bool
* @access private
*/
private function IsBigObject(&$object)
{
$skip_classes = Array(
defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication',
'kFactory',
'kUnitConfigReader',
'NParser',
);
foreach ($skip_classes as $class_name) {
if ( strtolower(get_class($object)) == strtolower($class_name) ) {
return true;
}
}
return false;
}
/**
* Advanced version of print_r (for debugger only). Don't print objects recursively
*
* @param Array $array
* @param bool $return_output return output or print it out
* @param int $tab_count offset in tabs
* @return string
* @access private
*/
private function print_r(&$array, $return_output = false, $tab_count = -1)
{
static $first_line = true;
// not an array at all
if ( !is_array($array) ) {
switch ( gettype($array) ) {
case 'NULL':
return 'NULL' . "\n";
break;
case 'object':
return $this->processObject($array, $tab_count);
break;
default:
// number or string
if ( strlen($array) > 200 ) {
$array = substr($array, 0, 50) . ' ...';
}
return $array . "\n";
break;
}
}
$output = '';
$tab_count++;
$output .= "Array\n" . str_repeat(' ', $tab_count) . "(\n";
$tab_count++;
$tabsign = $tab_count ? str_repeat(' ', $tab_count) : '';
$array_keys = array_keys($array);
foreach ($array_keys as $key) {
switch ( gettype($array[$key]) ) {
case 'array':
$output .= $tabsign . '[' . $key . '] = ' . $this->print_r($array[$key], true, $tab_count);
break;
case 'boolean':
$output .= $tabsign . '[' . $key . '] = ' . ($array[$key] ? 'true' : 'false') . "\n";
break;
case 'integer':
case 'double':
case 'string':
if ( strlen($array[$key]) > 200 ) {
$array[$key] = substr($array[$key], 0, 50) . ' ...';
}
$output .= $tabsign . '[' . $key . '] = ' . $array[$key] . "\n";
break;
case 'NULL':
$output .= $tabsign . '[' . $key . "] = NULL\n";
break;
case 'object':
$output .= $tabsign . '[' . $key . "] = ";
$output .= "Object (" . get_class($array[$key]) . ") = \n" . str_repeat(' ', $tab_count + 1) . "(\n";
$output .= $this->processObject($array[$key], $tab_count + 2);
$output .= str_repeat(' ', $tab_count + 1) . ")\n";
break;
default:
$output .= $tabsign . '[' . $key . '] unknown = ' . gettype($array[$key]) . "\n";
break;
}
}
$tab_count--;
$output .= str_repeat(' ', $tab_count) . ")\n";
if ( $first_line ) {
$first_line = false;
$output .= "\n";
}
$tab_count--;
if ( $return_output ) {
return $output;
}
else {
echo $output;
}
return true;
}
/**
* Returns string representation of given object (more like print_r, but with recursion prevention check)
*
* @param Object $object
* @param int $tab_count
* @return string
* @access private
*/
private function processObject(&$object, $tab_count)
{
$object_class = get_class($object);
if ( !in_array($object_class, $this->RecursionStack) ) {
if ( $this->IsBigObject($object) ) {
return 'SKIPPED (class: ' . $object_class . ")\n";
}
$attribute_names = get_class_vars($object_class);
if ( !$attribute_names ) {
return "NO_ATTRIBUTES\n";
}
else {
$output = '';
array_push($this->RecursionStack, $object_class);
$tabsign = $tab_count ? str_repeat(' ', $tab_count) : '';
foreach ($attribute_names as $attribute_name => $attribute_value) {
if ( is_object($object->$attribute_name) ) {
// it is object
$output .= $tabsign . '[' . $attribute_name . '] = ' . $this->processObject($object->$attribute_name, $tab_count + 1);
}
else {
$output .= $tabsign . '[' . $attribute_name . '] = ' . $this->print_r($object->$attribute_name, true, $tab_count);
}
}
array_pop($this->RecursionStack);
return $output;
}
}
else {
// object [in recursion stack]
return '*** RECURSION *** (class: ' . $object_class . ")\n";
}
}
/**
* Format SQL Query using predefined formatting
* and highlighting techniques
*
* @param string $sql
* @return string
* @access public
*/
public function formatSQL($sql)
{
$sql = trim(preg_replace('/(\n|\t| )+/is', ' ', $sql));
// whitespace in the beginning of the regex is to avoid splitting inside words, for example "FROM int_ConfigurationValues" into "FROM intConfiguration\n\tValues"
$formatted_sql = preg_replace('/\s(CREATE TABLE|DROP TABLE|SELECT|UPDATE|SET|REPLACE|INSERT|DELETE|VALUES|FROM|LEFT JOIN|INNER JOIN|LIMIT|WHERE|HAVING|GROUP BY|ORDER BY)\s/is', "\n\t$1 ", ' ' . $sql);
$formatted_sql = $this->highlightString($formatted_sql);
if ( defined('DBG_SQL_EXPLAIN') && DBG_SQL_EXPLAIN ) {
if ( substr($sql, 0, 6) == 'SELECT' ) {
$formatted_sql .= '<br/>' . '<strong>Explain</strong>:<br /><br />';
$explain_result = $this->Application->Conn->Query('EXPLAIN ' . $sql, null, true);
$explain_table = '';
foreach ($explain_result as $explain_row) {
if ( !$explain_table ) {
// first row -> draw header
$explain_table .= '<tr class="explain_header"><td>' . implode('</td><td>', array_keys($explain_row)) . '</td></tr>';
}
$explain_table .= '<tr><td>' . implode('</td><td>', $explain_row) . '</td></tr>';
}
$formatted_sql .= '<table class="dbg_explain_table">' . $explain_table . '</table>';
}
}
return $formatted_sql;
}
/**
* Highlights given string using "highlight_string" method
*
* @param string $string
* @return string
* @access public
*/
public function highlightString($string)
{
if ( !(defined('DBG_USE_HIGHLIGHT') && DBG_USE_HIGHLIGHT) || $this->_compileError ) {
return nl2br($string);
}
$string = str_replace(Array ('\\', '/'), Array ('_no_match_string_', '_n_m_s_'), $string);
$this->_compileError = true; // next line is possible cause of compile error
$string = highlight_string('<?php ' . $string . ' ?>', true);
$this->_compileError = false;
$string = str_replace(Array ('_no_match_string_', '_n_m_s_'), Array ('\\', '/'), $string);
if ( strlen($string) >= 65536 ) {
// preg_replace will fail, when string is longer, then 65KB
return str_replace(Array ('&lt;?php&nbsp;', '?&gt;'), '', $string);
}
return preg_replace('/&lt;\?(.*)php&nbsp;(.*)\?&gt;/Us', '\\2', $string);
}
/**
* Determine by php type of browser used to show debugger
*
* @return bool
* @access private
*/
private function isGecko()
{
// we need isset because we may run scripts from shell with no user_agent at all
return isset($_SERVER['HTTP_USER_AGENT']) && strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'firefox') !== false;
}
/**
* Returns link for editing php file (from error) in external editor
*
* @param string $file filename with path from root folder
* @param int $line_number line number in file where error is found
* @param string $title text to show on file edit link
* @return string
* @access public
*/
public function getFileLink($file, $line_number = 1, $title = '')
{
if ( !$title ) {
$title = str_replace('/', '\\', $this->getLocalFile($file));
}
$url = str_replace('%F', $this->getLocalFile($file), DBG_EDITOR_URL);
$url = str_replace('%L', $line_number, $url);
return '<a href="' . $url . '">' . $title . '</a>';
}
/**
* Converts filepath on server to filepath in mapped DocumentRoot on developer pc
*
* @param string $remoteFile
* @return string
* @access private
*/
private function getLocalFile($remoteFile)
{
return preg_replace('/^' . preg_quote(DOC_ROOT, '/') . '/', DBG_LOCAL_BASE_PATH, $remoteFile, 1);
}
/**
* Appends call trace till this method call
*
* @param int $levels_to_shift
* @return void
* @access public
*/
public function appendTrace($levels_to_shift = 1)
{
$levels_shifted = 0;
$trace = debug_backtrace();
while ( $levels_shifted < $levels_to_shift ) {
array_shift($trace);
$levels_shifted++;
}
$this->Data[] = Array ('trace' => $trace, 'debug_type' => 'trace');
}
/**
* Appends call trace till this method call
*
* @param Exception $exception
* @return void
* @access private
*/
private function appendExceptionTrace(&$exception)
{
$trace = $exception->getTrace();
$this->Data[] = Array('trace' => $trace, 'debug_type' => 'trace');
}
/**
* Adds memory usage statistics
*
* @param string $msg
* @param int $used
* @return void
* @access public
*/
public function appendMemoryUsage($msg, $used = null)
{
if ( !isset($used) ) {
$used = round(memory_get_usage() / 1024);
}
$this->appendHTML('<b>Memory usage</b> ' . $msg . ' ' . $used . 'Kb');
}
/**
* Appends HTML code without transformations
*
* @param string $html
* @return void
* @access public
*/
public function appendHTML($html)
{
$this->Data[] = Array ('html' => $html, 'debug_type' => 'html');
}
/**
* Returns instance of FirePHP class
*
* @return FirePHP
* @link http://www.firephp.org/HQ/Use.htm
*/
function firePHP()
{
require_once('FirePHPCore/FirePHP.class.php');
return FirePHP::getInstance(true);
}
/**
* Change debugger info that was already generated before.
* Returns true if html was set.
*
* @param int $index
* @param string $html
* @param string $type = {'append','prepend','replace'}
* @return bool
* @access public
*/
public function setHTMLByIndex($index, $html, $type = 'append')
{
if ( !isset($this->Data[$index]) || $this->Data[$index]['debug_type'] != 'html' ) {
return false;
}
switch ( $type ) {
case 'append':
$this->Data[$index]['html'] .= '<br>' . $html;
break;
case 'prepend':
$this->Data[$index]['html'] = $this->Data[$index]['html'] . '<br>' . $html;
break;
case 'replace':
$this->Data[$index]['html'] = $html;
break;
}
return true;
}
/**
* Move $debugLineCount lines of input from debug output
* end to beginning.
*
* @param int $debugLineCount
* @return void
* @access private
*/
private function moveToBegin($debugLineCount)
{
$lines = array_splice($this->Data, count($this->Data) - $debugLineCount, $debugLineCount);
$this->Data = array_merge($lines, $this->Data);
}
/**
* Moves all debugger report lines after $debugLineCount into $new_row position
*
* @param int $new_row
* @param int $debugLineCount
* @return void
* @access private
*/
private function moveAfterRow($new_row, $debugLineCount)
{
$lines = array_splice($this->Data, count($this->Data) - $debugLineCount, $debugLineCount);
$rows_before = array_splice($this->Data, 0, $new_row, $lines);
$this->Data = array_merge($rows_before, $this->Data);
}
/**
* Appends HTTP REQUEST information to debugger report
*
* @return void
* @access private
*/
private function appendRequest()
{
if ( isset($_SERVER['SCRIPT_FILENAME']) ) {
$script = $_SERVER['SCRIPT_FILENAME'];
}
else {
$script = $_SERVER['DOCUMENT_ROOT'] . $_SERVER['PHP_SELF'];
}
$this->appendHTML('ScriptName: <b>' . $this->getFileLink($script, 1, basename($script)) . '</b> (<b>' . dirname($script) . '</b>)');
if ( $this->_isAjax ) {
$this->appendHTML('RequestURI: ' . $_SERVER['REQUEST_URI'] . ' (QS Length:' . strlen($_SERVER['QUERY_STRING']) . ')');
}
$tools_html = ' <table style="width: ' . $this->getWindowWidth() . 'px;">
<tr>
<td>' . $this->_getDomViewerHTML() . '</td>
<td>' . $this->_getToolsHTML() . '</td>
</tr>
</table>';
$this->appendHTML($tools_html);
ob_start();
?>
<table border="0" cellspacing="0" cellpadding="0" class="dbg_flat_table" style="width: <?php echo $this->getWindowWidth(); ?>px;">
<thead style="font-weight: bold;">
<td width="20">Src</td><td>Name</td><td>Value</td>
</thead>
<?php
$super_globals = Array ('GE' => $_GET, 'PO' => $_POST, 'CO' => $_COOKIE);
foreach ($super_globals as $prefix => $data) {
foreach ($data as $key => $value) {
if ( !is_array($value) && trim($value) == '' ) {
$value = '<b class="debug_error">no value</b>';
}
else {
$value = htmlspecialchars($this->print_r($value, true), ENT_QUOTES, 'UTF-8');
}
echo '<tr><td>' . $prefix . '</td><td>' . $key . '</td><td>' . $value . '</td></tr>';
}
}
?>
</table>
<?php
$this->appendHTML(ob_get_contents());
ob_end_clean();
}
/**
* Appends php session content to debugger output
*
* @return void
* @access private
*/
private function appendSession()
{
if ( isset($_SESSION) && $_SESSION ) {
$this->appendHTML('PHP Session: [<b>' . ini_get('session.name') . '</b>]');
$this->dumpVars($_SESSION);
$this->moveToBegin(2);
}
}
/**
* Starts profiling of a given $key
*
* @param string $key
* @param string $description
* @param int $timeStamp
* @return void
* @access public
*/
public function profileStart($key, $description = null, $timeStamp = null)
{
if ( !isset($timeStamp) ) {
$timeStamp = microtime(true);
}
$this->ProfilerData[$key] = Array ('begins' => $timeStamp, 'ends' => 5000, 'debuggerRowID' => count($this->Data));
if ( isset($description) ) {
$this->ProfilerData[$key]['description'] = $description;
}
if ( substr($key, 0, 4) == 'sql_' ) {
// append place from what was called
$trace_results = debug_backtrace();
$trace_count = count($trace_results);
$i = 0;
while ( $i < $trace_count ) {
if ( !isset($trace_results[$i]['file']) ) {
$i++;
continue;
}
$trace_file = basename($trace_results[$i]['file']);
if ( $trace_file != 'db_connection.php' && $trace_file != 'db_load_balancer.php' && $trace_file != 'adodb.inc.php' ) {
break;
}
$i++;
}
$this->ProfilerData[$key]['file'] = $trace_results[$i]['file'];
$this->ProfilerData[$key]['line'] = $trace_results[$i]['line'];
if ( isset($trace_results[$i + 1]['object']) && isset($trace_results[$i + 1]['object']->Prefix) ) {
$object =& $trace_results[$i + 1]['object'];
/* @var $object kBase */
$prefix_special = rtrim($object->Prefix . '.' . $object->Special, '.');
$this->ProfilerData[$key]['prefix_special'] = $prefix_special;
}
unset($trace_results);
}
$this->Data[] = Array ('profile_key' => $key, 'debug_type' => 'profiler');
}
/**
* Ends profiling for a given $key
*
* @param string $key
* @param string $description
* @param int $timeStamp
* @return void
* @access public
*/
public function profileFinish($key, $description = null, $timeStamp = null)
{
if ( !isset($timeStamp) ) {
$timeStamp = microtime(true);
}
$this->ProfilerData[$key]['ends'] = $timeStamp;
if ( isset($description) ) {
$this->ProfilerData[$key]['description'] = $description;
}
if ( substr($key, 0, 4) == 'sql_' ) {
$func_arguments = func_get_args();
$rows_affected = $func_arguments[3];
$additional = Array ();
if ( $rows_affected > 0 ) {
$additional[] = Array ('name' => 'Affected Rows', 'value' => $rows_affected);
if ( isset($func_arguments[4]) ) {
if ( strlen($func_arguments[4]) > 200 ) {
$func_arguments[4] = substr($func_arguments[4], 0, 50) . ' ...';
}
$additional[] = Array ('name' => 'Result', 'value' => $func_arguments[4]);
}
}
$additional[] = Array ('name' => 'Query Number', 'value' => $func_arguments[5]);
if ( $func_arguments[6] ) {
$this->profilerAddTotal('cachable_queries', $key);
$this->ProfilerData[$key]['subtitle'] = 'cachable';
}
if ( (string)$func_arguments[7] !== '' ) {
$additional[] = Array ('name' => 'Server #', 'value' => $func_arguments[7]);
}
if ( array_key_exists('prefix_special', $this->ProfilerData[$key]) ) {
$additional[] = Array ('name' => 'PrefixSpecial', 'value' => $this->ProfilerData[$key]['prefix_special']);
}
$this->ProfilerData[$key]['additional'] =& $additional;
}
}
/**
* Collects total execution time from profiler record
*
* @param string $total_key
* @param string $key
* @param int $value
* @return void
* @access public
*/
public function profilerAddTotal($total_key, $key = null, $value = null)
{
if ( !isset($this->ProfilerTotals[$total_key]) ) {
$this->ProfilerTotals[$total_key] = 0;
$this->ProfilerTotalCount[$total_key] = 0;
}
if ( !isset($value) ) {
$value = $this->ProfilerData[$key]['ends'] - $this->ProfilerData[$key]['begins'];
}
if ( isset($key) ) {
$this->ProfilerData[$key]['totalsKey'] = $total_key;
$this->ProfilerData[$key]['totalsBefore'] = $this->ProfilerTotals[$total_key];
}
$this->ProfilerTotals[$total_key] += $value;
$this->ProfilerTotalCount[$total_key]++;
}
/**
* Traces relative code execution speed between this method calls
*
* @param string $message
* @return void
* @access public
*/
public function appendTimestamp($message)
{
global $start;
$time = microtime(true);
$from_last = $time - $this->LastMoment;
$from_start = $time - $start;
$this->appendHTML(sprintf("<strong>%s</strong> %.5f from last %.5f from start", $message, $from_last, $from_start));
$this->LastMoment = $time;
}
/**
* Returns unique ID for each method call
*
* @return int
* @access public
*/
public function generateID()
{
list($usec, $sec) = explode(' ', microtime());
$id_part_1 = substr($usec, 4, 4);
$id_part_2 = mt_rand(1, 9);
$id_part_3 = substr($sec, 6, 4);
$digit_one = substr($id_part_1, 0, 1);
if ( $digit_one == 0 ) {
$digit_one = mt_rand(1, 9);
$id_part_1 = preg_replace('/^0/', '', $id_part_1);
$id_part_1 = $digit_one . $id_part_1;
}
return $id_part_1 . $id_part_2 . $id_part_3;
}
/**
* Returns error name based on it's code
*
* @param int $error_code
* @return string
* @access private
*/
private function getErrorNameByCode($error_code)
{
$error_map = Array (
'Fatal Error' => Array (E_RECOVERABLE_ERROR, E_USER_ERROR, E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE),
'Warning' => Array (E_WARNING, E_USER_WARNING, E_CORE_WARNING, E_COMPILE_WARNING),
'Notice' => Array (E_NOTICE, E_USER_NOTICE, E_STRICT),
);
if ( defined('E_DEPRECATED') ) {
// since PHP 5.3
$error_map['Notice'][] = E_DEPRECATED;
$error_map['Notice'][] = E_USER_DEPRECATED;
}
foreach ($error_map as $error_name => $error_codes) {
if ( in_array($error_code, $error_codes) ) {
return $error_name;
}
}
return '';
}
/**
* Returns profile total key (check against missing key too)
*
* @param string $key
* @return int
* @access private
*/
private function getProfilerTotal($key)
{
if ( isset($this->ProfilerTotalCount[$key]) ) {
return (int)$this->ProfilerTotalCount[$key];
}
return 0;
}
/**
* Counts how much calls were made to a place, where this method is called (basic version of profiler)
*
* @param string $title
* @param int $level
* @return void
* @access public
*/
public function ProfilePoint($title, $level = 1)
{
$trace_results = debug_backtrace();
$level = min($level, count($trace_results) - 1);
do {
$point = $trace_results[$level];
$location = $point['file'] . ':' . $point['line'];
$level++;
$has_more = isset($trace_results[$level]);
} while ( $has_more && $point['function'] == $trace_results[$level]['function'] );
if ( !isset($this->ProfilePoints[$title]) ) {
$this->ProfilePoints[$title] = Array ();
}
if ( !isset($this->ProfilePoints[$title][$location]) ) {
$this->ProfilePoints[$title][$location] = 0;
}
$this->ProfilePoints[$title][$location]++;
}
/**
* Generates report
*
- * @param bool $returnResult
- * @param bool $clean_output_buffer
+ * @param boolean $return_result Returns or output report contents.
+ * @param boolean $clean_output_buffer Clean output buffers before displaying anything.
+ * @param boolean $is_shutdown_func Called from shutdown function.
*
* @return string
- * @access public
*/
- public function printReport($returnResult = false, $clean_output_buffer = true)
+ public function printReport($return_result = false, $clean_output_buffer = true, $is_shutdown_func = false)
{
- if ( $this->reportDone ) {
+ if ( $this->_inReportPrinting ) {
// don't print same report twice (in case if shutdown function used + compression + fatal error)
return '';
}
+ $this->_inReportPrinting = true;
$last_error = error_get_last();
- if ( !is_null($last_error) && !$this->_lastErrorProcessed ) {
- $this->_lastErrorProcessed = true;
- $this->saveError($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
+ if ( !is_null($last_error) && $is_shutdown_func ) {
+ $this->saveError(
+ $last_error['type'],
+ $last_error['message'],
+ $last_error['file'],
+ $last_error['line'],
+ null,
+ $is_shutdown_func
+ );
}
$this->profileFinish('script_runtime');
- $this->breakOutofBuffering(!$returnResult);
+ $this->_breakOutOfBuffering(!$return_result);
$debugger_start = memory_get_usage();
if ( defined('SPACER_URL') ) {
$this->dummyImage = SPACER_URL;
}
$this->InitReport(); // set parameters required by AJAX
// defined here, because user can define this constant while script is running, not event before debugger is started
DebuggerUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 0);
DebuggerUtil::safeDefine('DBG_TOOLBAR_BUTTONS', 1);
$this->appendSession(); // show php session if any
// ensure, that 1st line of debug output always is this one:
$top_line = '<table cellspacing="0" cellpadding="0" style="width: ' . $this->getWindowWidth() . 'px; margin: 0px;"><tr><td align="left" width="50%">[<a href="javascript:window.location.reload();">Reload Frame</a>] [<a href="javascript:$Debugger.Toggle(27);">Hide Debugger</a>] [<a href="javascript:$Debugger.Clear();">Clear Debugger</a>]</td><td align="right" width="50%">[Current Time: <b>' . date('H:i:s') . '</b>] [File Size: <b>#DBG_FILESIZE#</b>]</td></tr><tr><td align="left" colspan="2" style="padding-top: 5px;">' . $this->getFilterDropdown() . '</td></tr></table>';
$this->appendHTML($top_line);
$this->moveToBegin(1);
if ( count($this->ProfilePoints) > 0 ) {
foreach ($this->ProfilePoints as $point => $locations) {
arsort($this->ProfilePoints[$point]);
}
$this->appendHTML($this->highlightString($this->print_r($this->ProfilePoints, true)));
}
if ( DebuggerUtil::constOn('DBG_SQL_PROFILE') && isset($this->ProfilerTotals['sql']) ) {
// sql query profiling was enabled -> show totals
if ( array_key_exists('cachable_queries', $this->ProfilerTotalCount) ) {
$append = ' <strong>Cachable queries</strong>: ' . $this->ProfilerTotalCount['cachable_queries'];
}
else {
$append = '';
}
$this->appendHTML('<b>SQL Total time:</b> ' . $this->ProfilerTotals['sql'] . ' <b>Number of queries</b>: ' . $this->ProfilerTotalCount['sql'] . $append);
}
if ( DebuggerUtil::constOn('DBG_PROFILE_INCLUDES') && isset($this->ProfilerTotals['includes']) ) {
// included file profiling was enabled -> show totals
$this->appendHTML('<b>Included Files Total time:</b> ' . $this->ProfilerTotals['includes'] . ' Number of includes: ' . $this->ProfilerTotalCount['includes']);
}
if ( DebuggerUtil::constOn('DBG_PROFILE_MEMORY') ) {
// detailed memory usage reporting by objects was enabled -> show totals
$this->appendHTML('<b>Memory used by Objects:</b> ' . round($this->ProfilerTotals['objects'] / 1024, 2) . 'Kb');
}
if ( DebuggerUtil::constOn('DBG_INCLUDED_FILES') ) {
$files = get_included_files();
$this->appendHTML('<strong>Included files:</strong>');
foreach ($files as $file) {
$this->appendHTML($this->getFileLink($this->getLocalFile($file)) . ' (' . round(filesize($file) / 1024, 2) . 'Kb)');
}
}
if ( DebuggerUtil::constOn('DBG_PROFILE_INCLUDES') ) {
$totals = $totals_configs = Array ('mem' => 0, 'time' => 0);
$this->appendHTML('<b>Included files statistics:</b>' . (DebuggerUtil::constOn('DBG_SORT_INCLUDES_MEM') ? ' (sorted by memory usage)' : ''));
if ( is_array($this->IncludesData['mem']) ) {
if ( DebuggerUtil::constOn('DBG_SORT_INCLUDES_MEM') ) {
array_multisort($this->IncludesData['mem'], SORT_DESC, $this->IncludesData['file'], $this->IncludesData['time'], $this->IncludesData['level']);
}
foreach ($this->IncludesData['file'] as $key => $file_name) {
$this->appendHTML(str_repeat('&nbsp;->&nbsp;', ($this->IncludesData['level'][$key] >= 0 ? $this->IncludesData['level'][$key] : 0)) . $file_name . ' Mem: ' . sprintf("%.4f Kb", $this->IncludesData['mem'][$key] / 1024) . ' Time: ' . sprintf("%.4f", $this->IncludesData['time'][$key]));
if ( $this->IncludesData['level'][$key] == 0 ) {
$totals['mem'] += $this->IncludesData['mem'][$key];
$totals['time'] += $this->IncludesData['time'][$key];
}
elseif ( $this->IncludesData['level'][$key] == -1 ) {
$totals_configs['mem'] += $this->IncludesData['mem'][$key];
$totals_configs['time'] += $this->IncludesData['time'][$key];
}
}
$this->appendHTML('<b>Sub-Total classes:</b> ' . ' Mem: ' . sprintf("%.4f Kb", $totals['mem'] / 1024) . ' Time: ' . sprintf("%.4f", $totals['time']));
$this->appendHTML('<b>Sub-Total configs:</b> ' . ' Mem: ' . sprintf("%.4f Kb", $totals_configs['mem'] / 1024) . ' Time: ' . sprintf("%.4f", $totals_configs['time']));
$this->appendHTML('<span class="error"><b>Grand Total:</b></span> ' . ' Mem: ' . sprintf("%.4f Kb", ($totals['mem'] + $totals_configs['mem']) / 1024) . ' Time: ' . sprintf("%.4f", $totals['time'] + $totals_configs['time']));
}
}
$skip_reporting = DebuggerUtil::constOn('DBG_SKIP_REPORTING') || DebuggerUtil::constOn('DBG_ZEND_PRESENT');
if ( ($this->_isAjax && !DebuggerUtil::constOn('DBG_SKIP_AJAX')) || !$skip_reporting ) {
$debug_file = $this->tempFolder . '/debug_' . $this->rowSeparator . '.txt';
if ( file_exists($debug_file) ) {
unlink($debug_file);
}
$i = 0;
$fp = fopen($debug_file, 'a');
$lineCount = count($this->Data);
while ( $i < $lineCount ) {
$html = $this->prepareHTML($i);
$row_type = $this->getRowType($i);
fwrite($fp, json_encode(Array ('html' => $html, 'row_type' => $row_type)) . $this->rowSeparator);
$i++;
}
fclose($fp);
}
if ( $skip_reporting ) {
// let debugger write report and then don't output anything
- $this->reportDone = true;
return '';
}
$application =& kApplication::Instance();
$dbg_path = str_replace(FULL_PATH, '', $this->tempFolder);
$debugger_params = Array (
'FilterTypes' => $this->_filterTypes,
'RowSeparator' => $this->rowSeparator,
'ErrorsCount' => (int)$this->getProfilerTotal('error_handling'),
- 'IsFatalError' => $this->IsFatalError,
+ 'IsFatalError' => $this->_fatalErrorHappened(),
'SQLCount' => (int)$this->getProfilerTotal('sql'),
'SQLTime' => isset($this->ProfilerTotals['sql']) ? sprintf('%.5f', $this->ProfilerTotals['sql']) : 0,
'ScriptTime' => sprintf('%.5f', $this->ProfilerData['script_runtime']['ends'] - $this->ProfilerData['script_runtime']['begins']),
'ScriptMemory' => DebuggerUtil::formatSize($this->getMemoryUsed($debugger_start)),
'Shortcut' => DBG_SHORTCUT,
);
ob_start();
// the <script .. /script> and hidden div helps browser to break out of script tag or attribute esacped
// with " or ' in case fatal error (or user-error) occurs inside it in compiled template,
// otherwise it has no effect
?>
<div style="display: none" x='nothing'><script></script></div><html><body></body></html>
<link rel="stylesheet" rev="stylesheet" href="<?php echo $this->baseURL; ?>/debugger.css?v2" type="text/css" media="screen" />
<script type="text/javascript" src="<?php echo $this->baseURL; ?>/debugger.js?v4"></script>
<script type="text/javascript">
var $Debugger = new Debugger(<?php echo json_encode($debugger_params); ?>);
$Debugger.createEnvironment(<?php echo DBG_WINDOW_WIDTH; ?>, <?php echo $this->getWindowWidth(); ?>);
$Debugger.DOMViewerURL = '<?php echo constant('DBG_DOMVIEWER'); ?>';
$Debugger.DebugURL = '<?php echo $this->baseURL.'/debugger_responce.php?sid='.$this->rowSeparator.'&path='.urlencode($dbg_path); ?>';
$Debugger.EventURL = '<?php echo /*is_object($application->Factory) &&*/ $application->InitDone ? $application->HREF('dummy', '', Array ('pass' => 'm', '__NO_REWRITE__' => 1)) : ''; ?>';
$Debugger.BasePath = '<?php echo $this->basePath; ?>';
<?php
$is_install = defined('IS_INSTALL') && IS_INSTALL;
- if ( $this->IsFatalError || (!$is_install && DBG_RAISE_ON_WARNINGS && $this->WarningCount) ) {
+ if ( $this->_fatalErrorHappened()
+ || (!$is_install && DBG_RAISE_ON_WARNINGS && $this->WarningCount)
+ ) {
echo '$Debugger.Toggle();';
}
if ( DBG_TOOLBAR_BUTTONS ) {
echo '$Debugger.AddToolbar("$Debugger");';
}
?>
window.focus();
</script>
<?php
- if ( $returnResult ) {
+ if ( $return_result ) {
$ret = ob_get_contents();
+
if ( $clean_output_buffer ) {
ob_end_clean();
}
+
$ret .= $this->getShortReport($this->getMemoryUsed($debugger_start));
- $this->reportDone = true;
return $ret;
}
else {
if ( !DebuggerUtil::constOn('DBG_HIDE_FULL_REPORT') ) {
- $this->breakOutofBuffering();
+ $this->_breakOutOfBuffering();
}
elseif ( $clean_output_buffer ) {
ob_clean();
}
- echo $this->getShortReport($this->getMemoryUsed($debugger_start));
- $this->reportDone = true;
+ echo $this->getShortReport($this->getMemoryUsed($debugger_start));
}
return '';
}
function getFilterDropdown()
{
$filter_options = '';
foreach ( $this->_filterTypes as $filter_type ) {
$filter_options .= '<option value="' . $filter_type . '">' . $filter_type . '</option>';
}
return 'Show: <select id="dbg_filter" onchange="$Debugger.Filter()"><option value="">All</option>' . $filter_options .'</select>';
}
function getMemoryUsed($debugger_start)
{
if ( !isset($this->ProfilerTotals['error_handling']) ) {
$memory_used = $debugger_start;
$this->ProfilerTotalCount['error_handling'] = 0;
}
else {
$memory_used = $debugger_start - $this->ProfilerTotals['error_handling'];
}
return $memory_used;
}
/**
* Format's memory usage report by debugger
*
* @param int $memory_used
* @return string
* @access private
*/
private function getShortReport($memory_used)
{
if ( DebuggerUtil::constOn('DBG_TOOLBAR_BUTTONS') ) {
// evenrything is in toolbar - don't duplicate
return '';
}
else {
// toolbar not visible, then show sql & error count too
$info = Array (
'Script Runtime' => 'PROFILE:script_runtime',
'SQL\'s Runtime' => 'PROFILE_T:sql',
'-' => 'SEP:-',
'Notice / Warning' => 'PROFILE_TC:error_handling',
'SQLs Count' => 'PROFILE_TC:sql',
);
}
$ret = ''; // '<tr><td>Application:</td><td><b>' . DebuggerUtil::formatSize($memory_used) . '</b> (' . $memory_used . ')</td></tr>';
foreach ($info as $title => $value_key) {
list ($record_type, $record_data) = explode(':', $value_key, 2);
switch ( $record_type ) {
case 'PROFILE': // profiler totals value
$Data =& $this->ProfilerData[$record_data];
$profile_time = ($Data['ends'] - $Data['begins']); // in seconds
$ret .= '<tr><td>' . $title . ':</td><td><b>' . sprintf('%.5f', $profile_time) . ' s</b></td></tr>';
break;
case 'PROFILE_TC': // profile totals record count
$record_cell = '<td>';
if ( $record_data == 'error_handling' && $this->ProfilerTotalCount[$record_data] > 0 ) {
$record_cell = '<td class="debug_error">';
}
$ret .= '<tr>' . $record_cell . $title . ':</td>' . $record_cell . '<b>' . $this->ProfilerTotalCount[$record_data] . '</b></td></tr>';
break;
case 'PROFILE_T': // profile total
$record_cell = '<td>';
$total = array_key_exists($record_data, $this->ProfilerTotals) ? $this->ProfilerTotals[$record_data] : 0;
$ret .= '<tr>' . $record_cell . $title . ':</td>' . $record_cell . '<b>' . sprintf('%.5f', $total) . ' s</b></td></tr>';
break;
case 'SEP':
$ret .= '<tr><td colspan="2" style="height: 1px; background-color: #000000; padding: 0px;"><img src="' . $this->dummyImage . '" height="1" alt=""/></td></tr>';
break;
}
}
return '<br /><table class="dbg_stats_table"><tr><td style="border-color: #FFFFFF;"><table class="dbg_stats_table" align="left">' . $ret . '</table></td></tr></table>';
}
/**
- * User-defined error handler
+ * Detects if there was a fatal error at some point
*
- * @throws Exception
- * @param int $errno
- * @param string $errstr
- * @param string $errfile
- * @param int $errline
- * @param array $errcontext
- * @return bool
- * @access public
+ * @return boolean
*/
- public function saveError($errno, $errstr, $errfile = null, $errline = null, $errcontext = Array ())
+ private function _fatalErrorHappened()
{
+ return $this->_fatalErrorHash !== 0;
+ }
+
+ /**
+ * Creates error hash
+ *
+ * @param string $errfile File, where error happened.
+ * @param integer $errline Line in file, where error happened.
+ *
+ * @return integer
+ */
+ private function _getErrorHash($errfile, $errline)
+ {
+ return crc32($errfile . ':' . $errline);
+ }
+
+ /**
+ * User-defined error handler
+ *
+ * @param integer $errno Error code.
+ * @param string $errstr Error message.
+ * @param string $errfile Error file.
+ * @param integer $errline Error line.
+ * @param array $errcontext Error context.
+ * @param boolean $is_shutdown_func Called from shutdown function.
+ *
+ * @return boolean
+ * @throws Exception When unknown error code given.
+ */
+ public function saveError(
+ $errno,
+ $errstr,
+ $errfile = null,
+ $errline = null,
+ array $errcontext = null,
+ $is_shutdown_func = false
+ ) {
$this->ProfilerData['error_handling']['begins'] = memory_get_usage();
$errorType = $this->getErrorNameByCode($errno);
if (!$errorType) {
throw new Exception('Unknown error type [' . $errno . ']');
}
elseif ( substr($errorType, 0, 5) == 'Fatal' ) {
- $this->IsFatalError = true;
+ $this->_fatalErrorHash = $this->_getErrorHash($errfile, $errline);
$this->appendTrace(4);
}
$this->expandError($errstr, $errfile, $errline);
$this->Data[] = Array (
'no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline,
'context' => $errcontext, 'debug_type' => 'error'
);
$this->ProfilerData['error_handling']['ends'] = memory_get_usage();
$this->profilerAddTotal('error_handling', 'error_handling');
if ($errorType == 'Warning') {
$this->WarningCount++;
}
- if ( $this->IsFatalError ) {
- // append debugger report to data in buffer & clean buffer afterwards
- die( $this->breakOutofBuffering(false) . $this->printReport(true) );
+ if ( $this->_fatalErrorHappened()
+ && $this->_getErrorHash($errfile, $errline) === $this->_fatalErrorHash
+ ) {
+ // Append debugger report to data in buffer & clean buffer afterwards.
+ echo $this->_breakOutOfBuffering(false) . $this->printReport(true);
+
+ if ( !$is_shutdown_func ) {
+ exit;
+ }
}
return true;
}
/**
* Adds exception details into debugger but don't cause fatal error
*
* @param Exception $exception
* @return void
* @access public
*/
public function appendException($exception)
{
$this->ProfilerData['error_handling']['begins'] = memory_get_usage();
$this->appendExceptionTrace($exception);
$errno = $exception->getCode();
$errstr = $exception->getMessage();
$errfile = $exception->getFile();
$errline = $exception->getLine();
$this->expandError($errstr, $errfile, $errline);
$this->Data[] = Array (
'no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline,
'exception_class' => get_class($exception), 'debug_type' => 'exception'
);
$this->ProfilerData['error_handling']['ends'] = memory_get_usage();
$this->profilerAddTotal('error_handling', 'error_handling');
}
/**
* User-defined exception handler
*
* @param Exception $exception
* @return void
* @access public
*/
public function saveException($exception)
{
$this->appendException($exception);
- $this->IsFatalError = true;
+ $this->_fatalErrorHash = $this->_getErrorHash($exception->getFile(), $exception->getLine());
- // append debugger report to data in buffer & clean buffer afterwards
- die( $this->breakOutofBuffering(false) . $this->printReport(true) );
+ // Append debugger report to data in buffer & clean buffer afterwards.
+ echo $this->_breakOutOfBuffering(false) . $this->printReport(true);
}
/**
* Transforms short error messages into long ones
*
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return void
* @access private
*/
private function expandError(&$errstr, &$errfile, &$errline)
{
$errstr = kLogger::expandMessage($errstr);
list ($errno, $errstr, $sql) = kLogger::parseDatabaseError($errstr);
if ( $errno != 0 ) {
$errstr = '<span class="debug_error">' . $errstr . ' (' . $errno . ')</span><br/><strong>SQL</strong>: ' . $this->formatSQL($sql);
}
if ( strpos($errfile, 'eval()\'d code') !== false ) {
$errstr = '[<b>EVAL</b>, line <b>' . $errline . '</b>]: ' . $errstr;
$tmpStr = $errfile;
$pos = strpos($tmpStr, '(');
$errfile = substr($tmpStr, 0, $pos);
$pos++;
$errline = substr($tmpStr, $pos, strpos($tmpStr, ')', $pos) - $pos);
}
}
/**
* Break buffering in case if fatal error is happened in the middle
*
* @param bool $flush
* @return string
* @access private
*/
- private function breakOutofBuffering($flush = true)
+ private function _breakOutOfBuffering($flush = true)
{
$buffer_content = Array ();
while ( ob_get_level() ) {
$buffer_content[] = ob_get_clean();
}
$ret = implode('', array_reverse($buffer_content));
if ( $flush ) {
echo $ret;
flush();
}
return $ret;
}
/**
* Saves given message to "vb_debug.txt" file in DocumentRoot
*
* @param string $msg
* @return void
* @access public
*/
public function saveToFile($msg)
{
$fp = fopen($_SERVER['DOCUMENT_ROOT'] . '/vb_debug.txt', 'a');
fwrite($fp, $msg . "\n");
fclose($fp);
}
/**
* Prints given constant values in a table
*
* @param mixed $constants
* @return void
* @access public
*/
public function printConstants($constants)
{
if ( !is_array($constants) ) {
$constants = explode(',', $constants);
}
$constant_tpl = '<tr><td>%s</td><td><b>%s</b></td></tr>';
$ret = '<table class="dbg_flat_table" style="width: ' . $this->getWindowWidth() . 'px;">';
foreach ($constants as $constant_name) {
$ret .= sprintf($constant_tpl, $constant_name, constant($constant_name));
}
$ret .= '</table>';
$this->appendHTML($ret);
}
/**
* Attaches debugger to Application
*
* @return void
* @access public
*/
public function AttachToApplication()
{
if ( !DebuggerUtil::constOn('DBG_HANDLE_ERRORS') ) {
return;
}
if ( class_exists('kApplication') ) {
$this->Application =& kApplication::Instance();
$this->Application->Debugger = $this;
}
// kLogger will auto-detect these automatically
// error/exception handlers registered before debugger will be removed!
set_error_handler( Array ($this, 'saveError') );
set_exception_handler( Array ($this, 'saveException') );
}
/**
* Returns HTML for tools section
*
* @return string
* @access private
*/
private function _getToolsHTML()
{
$html = '<table>
<tr>
<td>System Tools:</td>
<td>
<select id="reset_cache" style="border: 1px solid #000000;">
<option value=""></option>
<option value="events[adm][OnResetModRwCache]">Reset mod_rewrite Cache</option>
<option value="events[adm][OnResetCMSMenuCache]">Reset SMS Menu Cache</option>
<option value="events[adm][OnResetSections]">Reset Sections Cache</option>
<option value="events[adm][OnResetConfigsCache]">Reset Configs Cache</option>
<option value="events[adm][OnRebuildThemes]">Re-build Themes Files</option>
<option value="events[lang][OnReflectMultiLingualFields]">Re-build Multilanguage Fields</option>
<option value="events[adm][OnDeleteCompiledTemplates]">Delete Compiled Templates</option>
</select>
</td>
<td>
<input type="button" class="button" onclick="$Debugger.resetCache(\'reset_cache\');" value="Go"/>
</td>
</tr>
</table>';
return $html;
}
/**
* Returns HTML for dom viewer section
*
* @return string
* @access private
*/
private function _getDomViewerHTML()
{
$html = '<table>
<tr>
<td>
<a href="http://www.brainjar.com/dhtml/domviewer/" target="_blank">DomViewer</a>:
</td>
<td>
<input id="dbg_domviewer" type="text" value="window" style="border: 1px solid #000000;"/>
</td>
<td>
<button class="button" onclick="return $Debugger.OpenDOMViewer();">Show</button>
</td>
</tr>
</table>';
return $html;
}
}
if ( !function_exists('memory_get_usage') ) {
// PHP 4.x and compiled without --enable-memory-limit option
function memory_get_usage()
{
return -1;
}
}
if ( !DebuggerUtil::constOn('DBG_ZEND_PRESENT') ) {
$debugger = new Debugger();
}
if ( DebuggerUtil::constOn('DBG_USE_SHUTDOWN_FUNC') ) {
- register_shutdown_function(Array (&$debugger, 'printReport'));
+ register_shutdown_function(array(&$debugger, 'printReport'), false, true, true);
}
- }
+ }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/nparser/ntags.php
===================================================================
--- branches/5.3.x/core/kernel/nparser/ntags.php (revision 16194)
+++ branches/5.3.x/core/kernel/nparser/ntags.php (revision 16195)
@@ -1,712 +1,721 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class _BlockTag extends kBase {
/**
* Enter description here...
*
* @var NParser
*/
var $Parser = null;
var $Tag = null;
/**
* Contains parameter names, that should be given to tag in any case
*
* @var Array
*/
var $_requiredParams = Array ();
/**
* Creates new tag
*
* @param Array $tag
* @access public
*/
public function __construct($tag)
{
parent::__construct();
$this->Tag = $tag;
}
function Open($tag)
{
if (!$this->_checkRequiredParams($tag)) {
return false;
}
return '';
}
/**
* Checks, that all required attributes for tag are passed
*
* @param Array $tag
* @return bool
*/
function _checkRequiredParams($tag)
{
$missing_params = array_diff($this->_requiredParams, array_keys($tag['NP']));
if (!$missing_params) {
return true;
}
$error_msg = 'Tag ' . $this->Parser->TagInfo($tag, true) . ' called <strong>without required ' . implode(', ', $missing_params) . '</strong> attribute';
if (count($missing_params) > 1) {
$error_msg .= '(-s)';
}
throw new ParserException($error_msg, 0, null, $tag);
}
/**
* All tags inside block tag are passed through to this method
* Any returned result is appened to current level's buffer
* This can be used to implement special handling of such tags as <inp2:m_else/>
*
* @param unknown_type $tag
* @return unknown
*/
function PassThrough(&$tag)
{
return '';
}
function Close($tag)
{
return $this->Parser->Buffers[$this->Parser->Level];
}
function AppendCode(&$o, $code, $php_tags=true)
{
if ($php_tags) {
$o .= '<?'."php\n" . ( is_array($code) ? "\t".implode("\n\t", $code)."\n" : $code) .'?>';
}
else {
$o .= ( is_array($code) ? "\t".implode("\n\t", $code)."\n" : $code);
}
}
}
class _Tag_Comment extends _BlockTag {
function Open($tag)
{
$this->Parser->SkipComments = false;
return '';
}
function Close($tag)
{
$this->Parser->SkipComments = true;
return $this->Parser->Buffers[$this->Parser->Level];
}
}
class _Tag_DefineElement extends _BlockTag {
/*var $ElemName;
function Open($tag)
{
$o = '';
$pointer = abs(crc32($tag['file'])).'_'.$tag['line'];
$f_name = $tag['NP']['name'].'_'.$pointer;
$this->ElemName = $tag['NP']['name'];
$code[] = "\$_parser->Elements['{$tag['NP']['name']}'] = '$f_name';";
// function_exists is required here, because a template may be included more than once
// leading to Cannot redeclare error
$code[] = "if (!function_exists('{$f_name}')) {";
$code[] = "function $f_name(&\$_parser, \$params) {";
$code[] = "global \$application;";
$code[] = "if (!\$_output = \$_parser->CacheStart('{$pointer}')) {";
$defaults = $this->Parser->CompileParamsArray($tag['NP']);
$code[] = "\$params = array_merge($defaults, \$params);";
$code[] = "if (!isset(\$params['PrefixSpecial']) && isset(\$params['prefix'])) {\$params['PrefixSpecial'] = \$params['prefix'];};";
$code[] = "extract(\$params);";
$code[] = "\$_parser->SetParams(\$params);";
$code[] = 'ob_start();';
$this->AppendCode($o, $code, false);
return $o . '?'.'>';
}
function Close($tag)
{
$o = $this->Parser->Buffers[$this->Parser->Level];
$code[] = "\$_parser->CacheEnd();\n";
$code[] = '$_output = ob_get_contents();';
$code[] = 'ob_end_clean();';
$code[] = '}';
$code[] = "return \$_output === true ? '' : \$_output;";
$code[] = '}}';
$code[] = "\$_parser->CachableElements['".$this->ElemName."'] = ".($this->Parser->Cachable[$this->Parser->Level] ? 'true':'false').';';
$o .= '<?'.'php';
$this->AppendCode($o, $code, false);
return $o;
// $this->Parser->Definitions .= $o."\n";
// return '';
} */
public function __construct($tag)
{
parent::__construct($tag);
$this->_requiredParams = Array ('name');
}
function Open($tag)
{
$o = parent::Open($tag);
if ($o === false) {
// some required params not passed
return $o;
}
$f_name = $tag['NP']['name'].'_'.abs(crc32($tag['file'])).'_'.$tag['line'];
$this->Tag['function_name'] = $f_name; // for later use in closing tag
$code[] = "\$_parser->Elements['{$tag['NP']['name']}'] = '$f_name';";
// function_exists is required here, because a template may be included more than once
// leading to Cannot redeclare error
$code[] = "if (!function_exists('{$f_name}')) {";
$code[] = "function $f_name(&\$_parser, \$params) {";
$code[] = "global \$application;";
$tag['NP'] = $this->_extractParams($tag['NP']);
$defaults = $this->Parser->CompileParamsArray($tag['NP']);
$code[] = "\$params = array_merge($defaults, \$params);";
$code[] = "if (!isset(\$params['PrefixSpecial']) && isset(\$params['prefix'])) {\$params['PrefixSpecial'] = \$params['prefix'];};";
$code[] = "extract(\$params);";
$code[] = "\$_parser->SetParams(\$params);";
$code[] = 'ob_start();';
$this->AppendCode($o, $code);
return $o;
}
/**
* Converts $param_name to $params['param_name']
*
* @param Array $params
* @return Array
*/
function _extractParams($params)
{
foreach ($params as $param_name => $param_value) {
$params[$param_name] = preg_replace('/[\{]{0,1}([\$])(.*?[^\$\s\{\}]*)[\}]{0,1}/', '{$params[\'\\2\']}', $param_value);
}
return $params;
}
function Close($tag)
{
$o = $this->Parser->Buffers[$this->Parser->Level];
$code[] = '$_output = ob_get_contents();';
$code[] = 'ob_end_clean();';
$code[] = 'return $_output;';
$code[] = '}}';
$end_pos = $tag['pos'] + strlen($tag['opening']) + strlen($tag['tag']) + strlen($tag['closing']) + TAG_NAMESPACE_LENGTH;
$code[] = "\$_parser->ElementLocations['{$this->Tag['function_name']}'] = Array('template' => '{$this->Tag['template']}', 'start_pos' => {$this->Tag['pos']}, 'end_pos' => {$end_pos});";
$this->AppendCode($o, $code);
return $o;
}
}
class _Tag_Capture extends _Tag_DefineElement {
public function __construct($tag)
{
parent::__construct($tag);
$this->_requiredParams = Array ('to_var');
}
function Open($tag)
{
if (!$this->_checkRequiredParams($tag)) {
return false;
}
$tag['NP']['name'] = '__capture_'.$tag['NP']['to_var'];
$o = '';
// $this->AppendCode($o, "\$_parser->Captures['{$tag['NP']['to_var']}'] = 1;", false);
$this->AppendCode($o, "\$_parser->Captures['{$tag['NP']['to_var']}'] = 1;");
$o .= parent::Open($tag);
return $o;
}
}
class _Tag_RenderElement extends _Tag_DefineElement {
var $Single = true;
var $OriginalTag;
var $_lambdaName = '';
public function __construct($tag)
{
parent::__construct($tag);
if (!$tag['is_closing']) {
$this->_requiredParams = Array ('design');
}
}
function Open($tag)
{
if (!$this->_checkRequiredParams($tag)) {
return false;
}
$o = '';
if ($tag['is_closing']) {
if (isset($tag['NP']['design'])) {
$this->RenderDesignCode($o, $tag['NP']);
return $o;
}
$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
/* $pointer = abs(crc32($tag['file'])).'_'.$tag['line'];
// $code[] = "}";
// $code[] = "if (!\$_parser->CachableElements['".$tag['NP']['name']."']) {";
// $code[] = "\$_parser->CacheEndInside();";
// $code[] = "}";
$o .= '<?'.'php ';
$this->AppendCode($o, $this->Parser->BreakCache('', $pointer.'a', "\$_parser->CachableElements['".$tag['NP']['name']."']"), false);
$this->AppendCode($o, "echo (\$_parser->ParseBlock($to_pass));\n", false);
$this->AppendCode($o, $this->Parser->BreakCache('', $pointer.'b') . " ?".">\n", false);
// $this->AppendCode($o, "if (!\$_parser->CacheStartOrContinue(\$_parser->CachableElements['".$tag['NP']['name']."'], '{$pointer}')) {".' ?'.'>', false);
*/
- $this->AppendCode($o, "echo (\$_parser->ParseBlock($to_pass));");
+ $code = array("echo (\$_parser->ParseBlock($to_pass));");
+
+ if ( array_key_exists('result_to_var', $tag['NP']) && $tag['NP']['result_to_var'] ) {
+ $param_name = $tag['NP']['result_to_var'];
+ $code[] = "\$params['{$param_name}'] = \$_parser->GetParam('{$param_name}');";
+ $code[] = "\${$param_name} = \$params['{$param_name}'];";
+ }
+
+ $this->AppendCode($o, $code);
+
return $o;
}
$this->Single = false;
$this->OriginalTag = $tag;
$tag['NP']['name'] = $tag['NP']['design'] . '_' . abs(crc32($tag['file'])) . '_' . $tag['line']; //'__lambda';
return parent::Open($tag);
}
function RenderDesignCode(&$o, $params)
{
$to_pass = $this->Parser->CompileParamsArray($params);
$code[] = "echo (\$_parser->ParseBlock(array_merge($to_pass, array('name'=>\"{$params['design']}\",'content'=>\$_parser->ParseBlock($to_pass), 'keep_data_exists'=>1))));";
$this->AppendCode($o, $code);
}
function Close($tag)
{
if ($this->Single) {
return $this->Parser->Buffers[$this->Parser->Level];
}
$o = parent::Close($tag);
$this->OriginalTag['NP']['name'] = $this->OriginalTag['NP']['design'] . '_' . abs(crc32($this->OriginalTag['file'])) . '_' . $this->OriginalTag['line']; //'__lambda';
$this->RenderDesignCode($o, $this->OriginalTag['NP']);
return $o;
}
}
class _Tag_RenderElements extends _BlockTag {
public function __construct($tag)
{
parent::__construct($tag);
$this->_requiredParams = Array ('elements');
}
function Open($tag)
{
$o = parent::Open($tag);
if ($o === false) {
// some required params not passed
return $o;
}
$element_names = array_map('trim', explode(',', $tag['NP']['elements']));
unset($tag['NP']['elements']);
$class = '_Tag_RenderElement';
$instance = new $class($tag);
/* @var $instance _Tag_RenderElement */
$instance->Parser =& $this->Parser;
$skip_elements = array_key_exists('skip', $tag['NP']) ? array_map('trim', explode(',', $tag['NP']['skip'])) : Array ();
foreach ($element_names as $element_name) {
if (in_array($element_name, $skip_elements) || !$element_name) {
// empty element name OR element should be excluded
continue;
}
$tag['NP']['name'] = $element_name;
$o .= $instance->Open($tag);
}
return $o;
}
}
class _Tag_Param extends _BlockTag {
public function __construct($tag)
{
parent::__construct($tag);
$this->_requiredParams = Array ('name');
}
function Open($tag)
{
$o = parent::Open($tag);
if ($o === false) {
// some required params not passed
return $o;
}
$capture_params = $tag['NP'];
$capture_params['name'] = '__capture_' . $tag['NP']['name'];
$capture_to_pass = $this->Parser->CompileParamsArray($capture_params);
$code[] = "if (isset(\$_parser->Captures['{$tag['NP']['name']}'])) {";
$code[] = "\${$tag['NP']['name']} = \$_parser->ParseBlock($capture_to_pass);";
$code[] = "}";
$code[] = "else {";
$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
$code[] = '$_p_ =& $_parser->GetProcessor(\'m\');';
$code[] = '$_tag_params = ' . $to_pass . ';';
$code[] = "\${$tag['NP']['name']} = \$_p_->PostProcess(\${$tag['NP']['name']}, \$_p_->PreparePostProcess(\$_tag_params));";
$code[] = "}";
if (array_key_exists('result_to_var', $tag['NP']) && $tag['NP']['result_to_var']) {
$code[] = "\$params['{$tag['NP']['result_to_var']}'] = \$_parser->GetParam('{$tag['NP']['result_to_var']}');";
if (array_key_exists('plus', $tag['NP'])) {
$code[] = "\$params['{$tag['NP']['result_to_var']}'] += {$tag['NP']['plus']};";
}
$code[] = "\${$tag['NP']['result_to_var']} = \$params['{$tag['NP']['result_to_var']}'];";
}
elseif (array_key_exists('plus', $tag['NP'])) {
$code[] = "\${$tag['NP']['name']} += {$tag['NP']['plus']};";
}
$code[] = "\$params['{$tag['NP']['name']}'] = \${$tag['NP']['name']};";
$code[] = "echo (\${$tag['NP']['name']});";
$this->AppendCode($o, $code);
return $o;
}
}
class _Tag_Include extends _BlockTag {
function Open($tag)
{
$o = '';
$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
$this->AppendCode($o, "echo (\$_parser->IncludeTemplate($to_pass))");
return $o;
}
}
class _Tag_If extends _BlockTag {
/**
* Permanently inverses if tag (used in ifnot tag)
*
* @var bool
*/
var $_Inversed = false;
/**
* Count of "elseif" tags inside
*
* @var int
*/
var $_elseIfCount = 0;
public function __construct($tag)
{
parent::__construct($tag);
$this->_requiredParams = Array ('check');
}
function Open($tag)
{
$o = parent::Open($tag);
if ($o === false) {
// some required params not passed
return $o;
}
$this->AppendCheckCode($o, $tag);
return $o;
}
/**
* Adds check code to $o
*
* @param string $o
* @param Array $tag
* @return void
* @access protected
*/
protected function AppendCheckCode(&$o, $tag)
{
$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
$code = Array ();
$code[] = "\$_splited = \$_parser->SplitTag(array('tag'=>\"{$tag['NP']['check']}\", 'file'=>'{$tag['file']}', 'line'=>{$tag['line']}));";
$code[] = 'if ($_splited[\'prefix\'] == \'__auto__\') {$_splited[\'prefix\'] = $PrefixSpecial;}';
$code[] = '$_p_ =& $_parser->GetProcessor($_splited[\'prefix\']);';
if ( isset($tag['NP']['inverse']) || $this->_Inversed ) {
$code[] = "if (!\$_p_->ProcessParsedTag(\$_splited['name'], $to_pass, \$_splited['prefix'], '{$tag['file']}', {$tag['line']})) {";
}
else {
$code[] = "if (\$_p_->ProcessParsedTag(\$_splited['name'], $to_pass, \$_splited['prefix'], '{$tag['file']}', {$tag['line']})) {";
}
$this->AppendCode($o, $code);
}
function PassThrough(&$tag)
{
$o = '';
if ($tag['name'] == 'else') {
$this->AppendCode($o, '} else {');
$tag['processed'] = true;
}
if ($tag['name'] == 'elseif') {
if (!$this->_checkRequiredParams($tag)) {
return '';
}
$this->_elseIfCount++; // add same count of closing brackets in closing tag
$this->AppendCode($o, "} else {");
$this->AppendCheckCode($o, $tag);
$tag['processed'] = true;
}
return $o;
}
function Close($tag)
{
$o = $this->Parser->Buffers[$this->Parser->Level];
$code = str_repeat("}\n", $this->_elseIfCount + 1);
$this->AppendCode($o, $code);
return $o;
}
}
class _Tag_IfNot extends _Tag_If {
public function __construct($tag)
{
parent::__construct($tag);
$this->_Inversed = true;
}
}
class _Tag_DefaultParam extends _BlockTag {
function Open($tag)
{
$o = '';
foreach ($tag['NP'] as $key => $val) {
$code[] = 'if (!isset($' . $key . ')) $params["' . $key . '"] = "' . $val . '";';
$code[] = '$' . $key . ' = isset($' . $key . ') ? $' . $key . ' : "' . $val . '";';
$code[] = '$_parser->SetParam("' . $key . '", $' . $key . ');';
}
$this->AppendCode($o, $code);
return $o;
}
}
class _Tag_SetParam extends _BlockTag {
function Open($tag)
{
$o = '';
foreach ($tag['NP'] as $key => $val) {
$code[] = '$params[\'' . $key . '\'] = "' . $val . '";';
$code[] = '$' . $key . ' = "' . $val . '";';
$code[] = '$_parser->SetParam(\'' . $key . '\', $' . $key . ');';
}
$this->AppendCode($o, $code);
return $o;
}
}
class _Tag_Cache extends _BlockTag {
function Open($tag)
{
$o = '';
$pointer = abs(crc32($tag['file'])).'_'.$tag['line'];
$key = array_key_exists('key', $tag['NP']) ? $tag['NP']['key'] : '';
$this->AppendCode($o, "if (!\$_parser->CacheStart('{$pointer}', \"{$key}\")) {\n");
return $o;
}
function Close($tag)
{
$o = $this->Parser->Buffers[$this->Parser->Level];
$cache_timeout = array_key_exists('cache_timeout', $this->Tag['NP']) ? $this->Tag['NP']['cache_timeout'] : 0;
$this->AppendCode($o, "\$_parser->CacheEnd(" . (int)$cache_timeout . ");\n}\n");
return $o;
}
}
class _Tag_IfDataExists extends _BlockTag {
function Open($tag)
{
$o = '';
$code = array();
$code[] = "ob_start();\n";
$code[] = '$_tmp_data_exists = $_parser->DataExists;';
$code[] = '$_parser->DataExists = false;';
$this->AppendCode($o, $code);
return $o;
}
function Close($tag)
{
$o = $this->Parser->Buffers[$this->Parser->Level];
$code = array();
$code[] = '$res = ob_get_clean();';
$code[] = 'if ($_parser->DataExists) {echo $res;}';
if (array_key_exists('block_no_data', $this->Tag['NP'])) {
$tag_params = $this->Tag['NP'];
$tag_params['name'] = $tag_params['block_no_data'];
unset($tag_params['block_no_data']);
$to_pass = $this->Parser->CompileParamsArray($tag_params);
$code[] = "else { echo (\$_parser->ParseBlock($to_pass)); }";
}
$code[] = '$_parser->DataExists = $_tmp_data_exists;';
$this->AppendCode($o, $code);
return $o;
}
}
class _Tag_Compress extends _BlockTag {
public function __construct($tag)
{
parent::__construct($tag);
if ($tag['is_closing']) {
if (!array_key_exists('files', $tag['NP'])) {
$this->_requiredParams = Array ('from');
}
else {
$this->_requiredParams = Array ('files');
}
}
else {
$this->_requiredParams = Array ('type'); // js/css
}
}
/**
* When used as non-block tag, then compress given files and return url to result
*
* @param Array $tag
* @return string
*/
function Open($tag)
{
$o = parent::Open($tag);
if ($o === false) {
// some required params not passed
return $o;
}
if ($tag['is_closing']) {
$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
$this->AppendCode($o, "echo \$_parser->CompressScript($to_pass, false);");
}
else {
$this->AppendCode($o, "ob_start();");
}
return $o;
}
/**
* When used as block tag, then compress contents between tags
*
* @param Array $tag
* @return string
*/
function Close($tag)
{
if ($this->Tag['is_closing']) {
return parent::Close($tag);
}
$o = parent::Close($tag);
$code = Array ();
$code[] = '$res = ob_get_clean();';
$code[] = 'echo $_parser->CompressScript($res, true, \'' . $this->Tag['NP']['type'] . '\');';
$this->AppendCode($o, $code);
return $o;
}
}
Index: branches/5.3.x/core/units/categories/categories_event_handler.php
===================================================================
--- branches/5.3.x/core/units/categories/categories_event_handler.php (revision 16194)
+++ branches/5.3.x/core/units/categories/categories_event_handler.php (revision 16195)
@@ -1,2900 +1,2904 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class CategoriesEventHandler extends kDBEventHandler {
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array (
'OnRebuildCache' => Array ('self' => 'add|edit'),
'OnCopy' => Array ('self' => true),
'OnCut' => Array ('self' => 'edit'),
'OnPasteClipboard' => Array ('self' => true),
'OnPaste' => Array ('self' => 'add|edit', 'subitem' => 'edit'),
'OnRecalculatePriorities' => Array ('self' => 'add|edit'), // category ordering
'OnItemBuild' => Array ('self' => true), // always allow to view individual categories (regardless of CATEGORY.VIEW right)
'OnUpdatePreviewBlock' => Array ('self' => true), // for FCKEditor integration
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
/**
* Categories are sorted using special sorting event
*
*/
function mapEvents()
{
parent::mapEvents();
$events_map = Array (
'OnMassMoveUp' => 'OnChangePriority',
'OnMassMoveDown' => 'OnChangePriority',
);
$this->eventMethods = array_merge($this->eventMethods, $events_map);
}
/**
* Checks user permission to execute given $event
*
* @param kEvent $event
* @return bool
* @access public
*/
public function CheckPermission(kEvent $event)
{
if ( $event->Name == 'OnResetCMSMenuCache' ) {
// events from "Tools -> System Tools" section are controlled via that section "edit" permission
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$perm_value = $this->Application->CheckPermission('in-portal:service.edit');
return $perm_helper->finalizePermissionCheck($event, $perm_value);
}
if ( !$this->Application->isAdmin ) {
if ( $event->Name == 'OnSetSortingDirect' ) {
// allow sorting on front event without view permission
return true;
}
if ( $event->Name == 'OnItemBuild' ) {
$category_id = $this->getPassedID($event);
if ( $category_id == 0 ) {
return true;
}
}
}
if ( in_array($event->Name, $this->_getMassPermissionEvents()) ) {
$items = $this->_getPermissionCheckInfo($event);
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
if ( ($event->Name == 'OnSave') && array_key_exists(0, $items) ) {
// adding new item (ID = 0)
$perm_value = $perm_helper->AddCheckPermission($items[0]['ParentId'], $event->Prefix) > 0;
}
else {
// leave only items, that can be edited
$ids = Array ();
$check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission';
foreach ($items as $item_id => $item_data) {
if ( $perm_helper->$check_method($item_data['CreatedById'], $item_data['ParentId'], $event->Prefix) > 0 ) {
$ids[] = $item_id;
}
}
if ( !$ids ) {
// no items left for editing -> no permission
return $perm_helper->finalizePermissionCheck($event, false);
}
$perm_value = true;
$event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method
}
return $perm_helper->finalizePermissionCheck($event, $perm_value);
}
if ( $event->Name == 'OnRecalculatePriorities' ) {
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$category_id = $this->Application->GetVar('m_cat_id');
return $perm_helper->AddCheckPermission($category_id, $event->Prefix) || $perm_helper->ModifyCheckPermission(0, $category_id, $event->Prefix);
}
if ( $event->Name == 'OnPasteClipboard' ) {
// forces permission check to work by current category for "Paste In Category" operation
$category_id = $this->Application->GetVar('m_cat_id');
$this->Application->SetVar('c_id', $category_id);
}
return parent::CheckPermission($event);
}
/**
* Returns events, that require item-based (not just event-name based) permission check
*
* @return Array
*/
function _getMassPermissionEvents()
{
return Array (
'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove',
'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown',
'OnCut',
);
}
/**
* Returns category item IDs, that require permission checking
*
* @param kEvent $event
* @return string
*/
function _getPermissionCheckIDs($event)
{
if ($event->Name == 'OnSave') {
$selected_ids = implode(',', $this->getSelectedIDs($event, true));
if (!$selected_ids) {
$selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave)
}
}
else {
// OnEdit, OnMassDelete events, when items are checked in grid
$selected_ids = implode(',', $this->StoreSelectedIDs($event));
}
return $selected_ids;
}
/**
* Returns information used in permission checking
*
* @param kEvent $event
* @return Array
*/
function _getPermissionCheckInfo($event)
{
// when saving data from temp table to live table check by data from temp table
$config = $event->getUnitConfig();
$id_field = $config->getIDField();
$table_name = $config->getTableName();
if ($event->Name == 'OnSave') {
$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $event->Prefix);
}
$sql = 'SELECT ' . $id_field . ', CreatedById, ParentId
FROM ' . $table_name . '
WHERE ' . $id_field . ' IN (' . $this->_getPermissionCheckIDs($event) . ')';
$items = $this->Conn->Query($sql, $id_field);
if (!$items) {
// when creating new category, then no IDs are stored in session
$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
list ($id, $fields_hash) = each($items_info);
if (array_key_exists('ParentId', $fields_hash)) {
$item_category = $fields_hash['ParentId'];
}
else {
$item_category = $this->Application->RecallVar('m_cat_id'); // saved in c:OnPreCreate event permission checking
}
$items[$id] = Array (
'CreatedById' => $this->Application->RecallVar('user_id'),
'ParentId' => $item_category,
);
}
return $items;
}
/**
* Set's mark, that root category is edited
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnEdit(kEvent $event)
{
$category_id = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
$home_category = $this->Application->getBaseCategory();
$this->Application->StoreVar('IsRootCategory_' . $this->Application->GetVar('m_wid'), ($category_id === '0') || ($category_id == $home_category));
parent::OnEdit($event);
if ( $event->status == kEvent::erSUCCESS ) {
// keep "Section Properties" link (in browse modes) clean
$this->Application->DeleteVar('admin');
}
}
/**
* Adds selected link to listing
*
* @param kEvent $event
*/
function OnProcessSelected($event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$selected_ids = $this->Application->GetVar('selected_ids');
$this->RemoveRequiredFields($object);
$object->SetDBField($this->Application->RecallVar('dst_field'), $selected_ids['c']);
$object->Update();
$event->SetRedirectParam('opener', 'u');
}
/**
* Apply system filter to categories list
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetCustomQuery(kEvent $event)
{
parent::SetCustomQuery($event);
$object = $event->getObject();
/* @var $object kDBList */
// don't show "Content" category in advanced view
$object->addFilter('system_categories', '%1$s.Status <> 4');
// show system templates from current theme only + all virtual templates
$object->addFilter('theme_filter', '%1$s.ThemeId = ' . $this->_getCurrentThemeId() . ' OR %1$s.ThemeId = 0');
if ($event->Special == 'showall') {
// if using recycle bin don't show categories from there
$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
if ($recycle_bin) {
$sql = 'SELECT TreeLeft, TreeRight
FROM '.TABLE_PREFIX.'Categories
WHERE CategoryId = '.$recycle_bin;
$tree_indexes = $this->Conn->GetRow($sql);
$object->addFilter('recyclebin_filter', '%1$s.TreeLeft < '.$tree_indexes['TreeLeft'].' OR %1$s.TreeLeft > '.$tree_indexes['TreeRight']);
}
}
if ( (string)$event->getEventParam('parent_cat_id') !== '' ) {
$parent_cat_id = $event->getEventParam('parent_cat_id');
if ("$parent_cat_id" == 'Root') {
$module_name = $event->getEventParam('module') ? $event->getEventParam('module') : 'In-Commerce';
$parent_cat_id = $this->Application->findModule('Name', $module_name, 'RootCat');
}
}
else {
$parent_cat_id = $this->Application->GetVar('c_id');
if (!$parent_cat_id) {
$parent_cat_id = $this->Application->GetVar('m_cat_id');
}
if (!$parent_cat_id) {
$parent_cat_id = 0;
}
}
if ("$parent_cat_id" == '0') {
// replace "0" category with "Content" category id (this way template
$parent_cat_id = $this->Application->getBaseCategory();
}
if ("$parent_cat_id" != 'any') {
if ($event->getEventParam('recursive')) {
if ($parent_cat_id > 0) {
// not "Home" category
$tree_indexes = $this->Application->getTreeIndex($parent_cat_id);
$object->addFilter('parent_filter', '%1$s.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']);
}
}
else {
$object->addFilter('parent_filter', '%1$s.ParentId = '.$parent_cat_id);
}
}
$this->applyViewPermissionFilter($object);
if (!$this->Application->isAdminUser) {
// apply status filter only on front
$object->addFilter('status_filter', $object->TableName.'.Status = 1');
}
// process "types" and "except" parameters
$type_clauses = Array();
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
$except_types = $event->getEventParam('except');
$except_types = $except_types ? explode(',', $except_types) : Array ();
$config = $event->getUnitConfig();
if (in_array('related', $types) || in_array('related', $except_types)) {
$related_to = $event->getEventParam('related_to');
if (!$related_to) {
$related_prefix = $event->Prefix;
}
else {
$sql = 'SELECT Prefix
FROM '.TABLE_PREFIX.'ItemTypes
WHERE ItemName = '.$this->Conn->qstr($related_to);
$related_prefix = $this->Conn->GetOne($sql);
}
$rel_table = $this->Application->getUnitConfig('rel')->getTableName();
$item_type = (int)$config->getItemType();
if ($item_type == 0) {
trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING);
}
// process case, then this list is called inside another list
$prefix_special = $event->getEventParam('PrefixSpecial');
if (!$prefix_special) {
$prefix_special = $this->Application->Parser->GetParam('PrefixSpecial');
}
$id = false;
if ($prefix_special !== false) {
$processed_prefix = $this->Application->processPrefix($prefix_special);
if ($processed_prefix['prefix'] == $related_prefix) {
// printing related categories within list of items (not on details page)
$list = $this->Application->recallObject($prefix_special);
/* @var $list kDBList */
$id = $list->GetID();
}
}
if ($id === false) {
// printing related categories for single item (possibly on details page)
if ($related_prefix == 'c') {
$id = $this->Application->GetVar('m_cat_id');
}
else {
$id = $this->Application->GetVar($related_prefix . '_id');
}
}
$p_item = $this->Application->recallObject($related_prefix . '.current', null, Array('skip_autoload' => true));
/* @var $p_item kCatDBItem */
$p_item->Load( (int)$id );
$p_resource_id = $p_item->GetDBField('ResourceId');
$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
WHERE
(Enabled = 1)
AND (
(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
OR
(Type = 1
AND (
(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
OR
(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
)
)
)';
$related_ids_array = $this->Conn->Query($sql);
$related_ids = Array();
foreach ($related_ids_array as $key => $record) {
$related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ];
}
if (count($related_ids) > 0) {
$type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).')';
$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).')';
}
else {
$type_clauses['related']['include'] = '0';
$type_clauses['related']['except'] = '1';
}
$type_clauses['related']['having_filter'] = false;
}
if (in_array('category_related', $type_clauses)) {
$object->removeFilter('parent_filter');
$resource_id = $this->Conn->GetOne('
SELECT ResourceId FROM '.$config->getTableName().'
WHERE CategoryId = '.$parent_cat_id
);
$sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'CatalogRelationships
WHERE SourceId = '.$resource_id.' AND SourceType = 1';
$related_cats = $this->Conn->GetCol($sql);
$related_cats = is_array($related_cats) ? $related_cats : Array();
$sql = 'SELECT DISTINCT(SourceId) FROM '.TABLE_PREFIX.'CatalogRelationships
WHERE TargetId = '.$resource_id.' AND TargetType = 1 AND Type = 1';
$related_cats2 = $this->Conn->GetCol($sql);
$related_cats2 = is_array($related_cats2) ? $related_cats2 : Array();
$related_cats = array_unique( array_merge( $related_cats2, $related_cats ) );
if ($related_cats) {
$type_clauses['category_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')';
$type_clauses['category_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')';
}
else
{
$type_clauses['category_related']['include'] = '0';
$type_clauses['category_related']['except'] = '1';
}
$type_clauses['category_related']['having_filter'] = false;
}
if (in_array('product_related', $types)) {
$object->removeFilter('parent_filter');
$product_id = $event->getEventParam('product_id') ? $event->getEventParam('product_id') : $this->Application->GetVar('p_id');
$sql = 'SELECT ResourceId
FROM ' . $this->Application->getUnitConfig('p')->getTableName() . '
WHERE ProductId = ' . $product_id;
$resource_id = $this->Conn->GetOne($sql);
$sql = 'SELECT DISTINCT(TargetId)
FROM ' . TABLE_PREFIX . 'CatalogRelationships
WHERE SourceId = '.$resource_id.' AND TargetType = 1';
$related_cats = $this->Conn->GetCol($sql);
$related_cats = is_array($related_cats) ? $related_cats : Array();
$sql = 'SELECT DISTINCT(SourceId)
FROM ' . TABLE_PREFIX . 'CatalogRelationships
WHERE TargetId = '.$resource_id.' AND SourceType = 1 AND Type = 1';
$related_cats2 = $this->Conn->GetCol($sql);
$related_cats2 = is_array($related_cats2) ? $related_cats2 : Array();
$related_cats = array_unique( array_merge( $related_cats2, $related_cats ) );
if ($related_cats) {
$type_clauses['product_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')';
$type_clauses['product_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')';
}
else {
$type_clauses['product_related']['include'] = '0';
$type_clauses['product_related']['except'] = '1';
}
$type_clauses['product_related']['having_filter'] = false;
}
$type_clauses['menu']['include'] = '%1$s.IsMenu = 1';
$type_clauses['menu']['except'] = '%1$s.IsMenu = 0';
$type_clauses['menu']['having_filter'] = false;
if (in_array('search', $types) || in_array('search', $except_types)) {
$event_mapping = Array (
'simple' => 'OnSimpleSearch',
'subsearch' => 'OnSubSearch',
'advanced' => 'OnAdvancedSearch'
);
$keywords = $event->getEventParam('keyword_string');
$type = $this->Application->GetVar('search_type', 'simple');
if ( $keywords ) {
// processing keyword_string param of ListProducts tag
$this->Application->SetVar('keywords', $keywords);
$type = 'simple';
}
$search_event = $event_mapping[$type];
$this->$search_event($event);
$object = $event->getObject();
/* @var $object kDBList */
$search_sql = ' FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search
search_result JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
$sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL());
$object->SetSelectSQL($sql);
$object->addCalculatedField('Relevance', 'search_result.Relevance');
$type_clauses['search']['include'] = '1';
$type_clauses['search']['except'] = '0';
$type_clauses['search']['having_filter'] = false;
}
$search_helper = $this->Application->recallObject('SearchHelper');
/* @var $search_helper kSearchHelper */
$search_helper->SetComplexFilter($event, $type_clauses, implode(',', $types), implode(',', $except_types));
}
/**
* Adds filter, that uses *.VIEW permissions to determine if an item should be shown to a user.
*
* @param kDBList $object Object.
*
* @return void
* @access protected
*/
protected function applyViewPermissionFilter(kDBList $object)
{
if ( !$this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) {
return;
}
if ( $this->Application->RecallVar('user_id') == USER_ROOT ) {
// for "root" CATEGORY.VIEW permission is checked for items lists too
$view_perm = 1;
}
else {
$count_helper = $this->Application->recallObject('CountHelper');
/* @var $count_helper kCountHelper */
list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($object->Prefix, 'perm');
$object->addFilter('perm_filter2', $view_filter);
}
$object->addFilter('perm_filter', 'perm.PermId = ' . $view_perm); // check for CATEGORY.VIEW permission
}
/**
* Returns current theme id
*
* @return int
*/
function _getCurrentThemeId()
{
$themes_helper = $this->Application->recallObject('ThemesHelper');
/* @var $themes_helper kThemesHelper */
return (int)$themes_helper->getCurrentThemeId();
}
/**
* Returns ID of current item to be edited
* by checking ID passed in get/post as prefix_id
* or by looking at first from selected ids, stored.
* Returned id is also stored in Session in case
* it was explicitly passed as get/post
*
* @param kEvent $event
* @return int
* @access public
*/
public function getPassedID(kEvent $event)
{
if ( ($event->Special == 'page') || $this->_isVirtual($event) || ($event->Prefix == 'st') ) {
return $this->_getPassedStructureID($event);
}
if ( $this->Application->isAdmin ) {
return parent::getPassedID($event);
}
return $this->Application->GetVar('m_cat_id');
}
/**
* Enter description here...
*
* @param kEvent $event
* @return int
*/
function _getPassedStructureID($event)
{
static $page_by_template = Array ();
if ( $event->Special == 'current' ) {
return $this->Application->GetVar('m_cat_id');
}
$event->setEventParam('raise_warnings', 0);
$page_id = parent::getPassedID($event);
if ( $page_id === false ) {
$template = $event->getEventParam('page');
if ( !$template ) {
$template = $this->Application->GetVar('t');
}
// bug: when template contains "-" symbols (or others, that stripDisallowed will replace) it's not found
if ( !array_key_exists($template, $page_by_template) ) {
$config = $event->getUnitConfig();
$template_crc = kUtil::crc32(mb_strtolower($template));
$sql = 'SELECT ' . $config->getIDField() . '
FROM ' . $config->getTableName() . '
WHERE
(
(NamedParentPathHash = ' . $template_crc . ') OR
(`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplateHash = ' . $template_crc . ')
) AND (ThemeId = ' . $this->_getCurrentThemeId() . ' OR ThemeId = 0)';
$page_id = $this->Conn->GetOne($sql);
}
else {
$page_id = $page_by_template[$template];
}
if ( $page_id ) {
$page_by_template[$template] = $page_id;
}
}
if ( !$page_id && !$this->Application->isAdmin ) {
$page_id = $this->Application->GetVar('m_cat_id');
}
return $page_id;
}
function ParentGetPassedID($event)
{
return parent::getPassedID($event);
}
/**
* Adds calculates fields for item statuses
*
* @param kCatDBItem $object
* @param kEvent $event
* @return void
* @access protected
*/
protected function prepareObject(&$object, kEvent $event)
{
if ( $this->_isVirtual($event) ) {
return;
}
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$object->addCalculatedField(
'IsNew',
' IF(%1$s.NewItem = 2,
IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '.
$this->Application->ConfigValue('Category_DaysNew').
'*3600*24), 1, 0),
%1$s.NewItem
)');
}
/**
* Checks, that this is virtual page
*
* @param kEvent $event
* @return int
* @access protected
*/
protected function _isVirtual(kEvent $event)
{
return strpos($event->Special, '-virtual') !== false;
}
/**
* Gets right special for configuring virtual page
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function _getCategorySpecial(kEvent $event)
{
return $this->_isVirtual($event) ? '-virtual' : $event->Special;
}
/**
* Set correct parent path for newly created categories
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterCopyToLive(kEvent $event)
{
parent::OnAfterCopyToLive($event);
$object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true, 'live_table' => true));
/* @var $object CategoriesItem */
$parent_path = false;
$object->Load($event->getEventParam('id'));
if ( $event->getEventParam('temp_id') == 0 ) {
if ( $object->isLoaded() ) {
// update path only for real categories (not including "Home" root category)
$fields_hash = $object->buildParentBasedFields();
$this->Conn->doUpdate($fields_hash, $object->TableName, 'CategoryId = ' . $object->GetID());
$parent_path = $fields_hash['ParentPath'];
}
}
else {
$parent_path = $object->GetDBField('ParentPath');
}
if ( $parent_path ) {
$cache_updater = $this->Application->makeClass('kPermCacheUpdater', Array (null, $parent_path));
/* @var $cache_updater kPermCacheUpdater */
$cache_updater->OneStepRun();
}
}
/**
* Set cache modification mark if needed
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeDeleteFromLive(kEvent $event)
{
parent::OnBeforeDeleteFromLive($event);
$id = $event->getEventParam('id');
// loading anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event
$temp_object = $event->getObject(Array ('skip_autoload' => true));
/* @var $temp_object CategoriesItem */
$temp_object->Load($id);
if ( $id == 0 ) {
if ( $temp_object->isLoaded() ) {
// new category -> update cache (not loaded when "Home" category)
$this->Application->StoreVar('PermCache_UpdateRequired', 1);
}
return ;
}
// existing category was edited, check if in-cache fields are modified
$live_object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('live_table' => true, 'skip_autoload' => true));
/* @var $live_object CategoriesItem */
$live_object->Load($id);
$cached_fields = Array ('l' . $this->Application->GetDefaultLanguageId() . '_Name', 'Filename', 'Template', 'ParentId', 'Priority');
foreach ($cached_fields as $cached_field) {
if ( $live_object->GetDBField($cached_field) != $temp_object->GetDBField($cached_field) ) {
// use session instead of REQUEST because of permission editing in category can contain
// multiple submits, that changes data before OnSave event occurs
$this->Application->StoreVar('PermCache_UpdateRequired', 1);
break;
}
}
// remember category filename change between temp and live records
if ( $temp_object->GetDBField('Filename') != $live_object->GetDBField('Filename') ) {
$filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ());
$filename_changes[ $live_object->GetID() ] = Array (
'from' => $live_object->GetDBField('Filename'),
'to' => $temp_object->GetDBField('Filename')
);
$this->Application->SetVar($event->Prefix . '_filename_changes', $filename_changes);
}
}
/**
* Calls kDBEventHandler::OnSave original event
* Used in proj-cms:StructureEventHandler->OnSave
*
* @param kEvent $event
*/
function parentOnSave($event)
{
parent::OnSave($event);
}
/**
* Reset root-category flag when new category is created
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreCreate(kEvent $event)
{
// 1. for permission editing of Home category
$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
parent::OnPreCreate($event);
$object = $event->getObject();
/* @var $object kDBItem */
// 2. preset template
$category_id = $this->Application->GetVar('m_cat_id');
$root_category = $this->Application->getBaseCategory();
if ( $category_id == $root_category ) {
$object->SetDBField('Template', $this->_getDefaultDesign());
}
// 3. set default owner
$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
}
/**
* Checks cache update mark and redirect to cache if needed
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSave(kEvent $event)
{
// get data from live table before it is overwritten by parent OnSave method call
$ids = $this->getSelectedIDs($event, true);
$is_editing = implode('', $ids);
$old_statuses = $is_editing ? $this->_getCategoryStatus($ids) : Array ();
$object = $event->getObject();
/* @var $object CategoriesItem */
parent::OnSave($event);
if ( $event->status != kEvent::erSUCCESS ) {
return;
}
if ( $this->Application->RecallVar('PermCache_UpdateRequired') ) {
$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
}
$this->Application->StoreVar('RefreshStructureTree', 1);
$this->_resetMenuCache();
if ( $is_editing ) {
// send email event to category owner, when it's status is changed (from admin)
$object->SwitchToLive();
$new_statuses = $this->_getCategoryStatus($ids);
$process_statuses = Array (STATUS_ACTIVE, STATUS_DISABLED);
foreach ($new_statuses as $category_id => $new_status) {
if ( $new_status != $old_statuses[$category_id] && in_array($new_status, $process_statuses) ) {
$object->Load($category_id);
$email_event = $new_status == STATUS_ACTIVE ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
$this->Application->emailUser($email_event, $object->GetDBField('CreatedById'), $object->getEmailParams());
}
}
}
// change opener stack in case if edited category filename was changed
$filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ());
if ( $filename_changes ) {
$opener_stack = $this->Application->makeClass('kOpenerStack');
/* @var $opener_stack kOpenerStack */
list ($template, $params, $index_file) = $opener_stack->pop();
foreach ($filename_changes as $change_info) {
$template = str_ireplace($change_info['from'], $change_info['to'], $template);
}
$opener_stack->push($template, $params, $index_file);
$opener_stack->save();
}
}
/**
* Returns statuses of given categories
*
* @param Array $category_ids
* @return Array
*/
function _getCategoryStatus($category_ids)
{
$config = $this->getUnitConfig();
$id_field = $config->getIDField();
$table_name = $config->getTableName();
$sql = 'SELECT Status, ' . $id_field . '
FROM ' . $table_name . '
WHERE ' . $id_field . ' IN (' . implode(',', $category_ids) . ')';
return $this->Conn->GetCol($sql, $id_field);
}
/**
* Creates a new item in temp table and
* stores item id in App vars and Session on success
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveCreated(kEvent $event)
{
$object = $event->getObject( Array ('skip_autoload' => true) );
/* @var $object CategoriesItem */
if ( $object->IsRoot() ) {
// don't create root category while saving permissions
return;
}
parent::OnPreSaveCreated($event);
}
/**
* Deletes sym link to other category
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemDelete(kEvent $event)
{
parent::OnAfterItemDelete($event);
$object = $event->getObject();
/* @var $object kDBItem */
$sql = 'UPDATE ' . $object->TableName . '
SET SymLinkCategoryId = NULL
WHERE SymLinkCategoryId = ' . $object->GetID();
$this->Conn->Query($sql);
// delete direct subscriptions to category, that was deleted
$sql = 'SELECT SubscriptionId
FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions
WHERE CategoryId = ' . $object->GetID();
$ids = $this->Conn->GetCol($sql);
if ( $ids ) {
$temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event->MasterEvent));
/* @var $temp_handler kTempTablesHandler */
$temp_handler->DeleteItems('system-event-subscription', '', $ids);
}
}
/**
* Exclude root categories from deleting
*
* @param kEvent $event
* @param string $type
* @return void
* @access protected
*/
protected function customProcessing(kEvent $event, $type)
{
if ( $event->Name == 'OnMassDelete' && $type == 'before' ) {
$ids = $event->getEventParam('ids');
if ( !$ids || $this->Application->ConfigValue('AllowDeleteRootCats') ) {
return;
}
$root_categories = Array ();
// get module root categories and exclude them
foreach ($this->Application->ModuleInfo as $module_info) {
$root_categories[] = $module_info['RootCat'];
}
$root_categories = array_unique($root_categories);
if ( $root_categories && array_intersect($ids, $root_categories) ) {
$event->setEventParam('ids', array_diff($ids, $root_categories));
$this->Application->StoreVar('root_delete_error', 1);
}
}
}
/**
* Checks, that given template exists (physically) in given theme
*
* @param string $template
* @param int $theme_id
* @return bool
*/
function _templateFound($template, $theme_id = null)
{
static $init_made = false;
if (!$init_made) {
$this->Application->InitParser(true);
$init_made = true;
}
if (!isset($theme_id)) {
$theme_id = $this->_getCurrentThemeId();
}
$theme_name = $this->_getThemeName($theme_id);
return $this->Application->TemplatesCache->TemplateExists('theme:' . $theme_name . '/' . $template);
}
/**
* Removes ".tpl" in template path
*
* @param string $template
* @return string
*/
function _stripTemplateExtension($template)
{
// return preg_replace('/\.[^.\\\\\\/]*$/', '', $template);
return preg_replace('/^[\\/]{0,1}(.*)\.tpl$/', "$1", $template);
}
/**
* Deletes all selected items.
* Automatically recourse into sub-items using temp handler, and deletes sub-items
* by calling its Delete method if sub-item has AutoDelete set to true in its config file
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnMassDelete(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$to_delete = Array ();
$ids = $this->StoreSelectedIDs($event);
$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
if ( $recycle_bin ) {
$rb = $this->Application->recallObject('c.recycle', null, Array ('skip_autoload' => true));
/* @var $rb CategoriesItem */
$rb->Load($recycle_bin);
$cat = $event->getObject(Array ('skip_autoload' => true));
/* @var $cat CategoriesItem */
foreach ($ids as $id) {
$cat->Load($id);
if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $cat->GetDBField('ParentPath')) ) {
// already in "Recycle Bin" -> delete for real
$to_delete[] = $id;
continue;
}
// just move into "Recycle Bin" category
$cat->SetDBField('ParentId', $recycle_bin);
$cat->Update();
}
$ids = $to_delete;
}
$event->setEventParam('ids', $ids);
$this->customProcessing($event, 'before');
$ids = $event->getEventParam('ids');
if ( $ids ) {
$recursive_helper = $this->Application->recallObject('RecursiveHelper');
/* @var $recursive_helper kRecursiveHelper */
foreach ($ids as $id) {
$recursive_helper->DeleteCategory($id, $event->Prefix);
}
}
$this->clearSelectedIDs($event);
$this->_ensurePermCacheRebuild($event);
}
/**
* Add selected items to clipboard with mode = COPY (CLONE)
*
* @param kEvent $event
*/
function OnCopy($event)
{
$this->Application->RemoveVar('clipboard');
$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
/* @var $clipboard_helper kClipboardHelper */
$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
$this->clearSelectedIDs($event);
}
/**
* Add selected items to clipboard with mode = CUT
*
* @param kEvent $event
*/
function OnCut($event)
{
$this->Application->RemoveVar('clipboard');
$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
/* @var $clipboard_helper kClipboardHelper */
$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
$this->clearSelectedIDs($event);
}
/**
* Controls all item paste operations. Can occur only with filled clipboard.
*
* @param kEvent $event
*/
function OnPasteClipboard($event)
{
$clipboard = unserialize( $this->Application->RecallVar('clipboard') );
foreach ($clipboard as $prefix => $clipboard_data) {
$paste_event = new kEvent($prefix.':OnPaste', Array('clipboard_data' => $clipboard_data));
$this->Application->HandleEvent($paste_event);
$event->copyFrom($paste_event);
}
}
/**
* Checks permission for OnPaste event
*
* @param kEvent $event
* @return bool
*/
function _checkPastePermission($event)
{
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$category_id = $this->Application->GetVar('m_cat_id');
if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) {
// no items left for editing -> no permission
return $perm_helper->finalizePermissionCheck($event, false);
}
return true;
}
/**
* Paste categories with sub-items from clipboard
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPaste($event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) {
$event->status = kEvent::erFAIL;
return;
}
$clipboard_data = $event->getEventParam('clipboard_data');
if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) {
return;
}
// 1. get ParentId of moved category(-es) before it gets updated!!!)
$source_category_id = 0;
$config = $event->getUnitConfig();
$id_field = $config->getIDField();
$table_name = $config->getTableName();
if ( $clipboard_data['cut'] ) {
$sql = 'SELECT ParentId
FROM ' . $table_name . '
WHERE ' . $id_field . ' = ' . $clipboard_data['cut'][0];
$source_category_id = $this->Conn->GetOne($sql);
}
$recursive_helper = $this->Application->recallObject('RecursiveHelper');
/* @var $recursive_helper kRecursiveHelper */
if ( $clipboard_data['cut'] ) {
$recursive_helper->MoveCategories($clipboard_data['cut'], $this->Application->GetVar('m_cat_id'));
}
if ( $clipboard_data['copy'] ) {
// don't allow to copy/paste system OR theme-linked virtual pages
$sql = 'SELECT ' . $id_field . '
FROM ' . $table_name . '
WHERE ' . $id_field . ' IN (' . implode(',', $clipboard_data['copy']) . ') AND (`Type` = ' . PAGE_TYPE_VIRTUAL . ') AND (ThemeId = 0)';
$allowed_ids = $this->Conn->GetCol($sql);
if ( !$allowed_ids ) {
return;
}
foreach ($allowed_ids as $id) {
$recursive_helper->PasteCategory($id, $event->Prefix);
}
}
$priority_helper = $this->Application->recallObject('PriorityHelper');
/* @var $priority_helper kPriorityHelper */
if ( $clipboard_data['cut'] ) {
$ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $source_category_id);
if ( $ids ) {
$priority_helper->massUpdateChanged($event->Prefix, $ids);
}
}
// recalculate priorities of newly pasted categories in destination category
$parent_id = $this->Application->GetVar('m_cat_id');
$ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id);
if ( $ids ) {
$priority_helper->massUpdateChanged($event->Prefix, $ids);
}
if ( $clipboard_data['cut'] || $clipboard_data['copy'] ) {
$this->_ensurePermCacheRebuild($event);
}
}
/**
* Ensures, that category permission cache is rebuild when category is added/edited/deleted
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function _ensurePermCacheRebuild(kEvent $event)
{
$this->Application->StoreVar('PermCache_UpdateRequired', 1);
$this->Application->StoreVar('RefreshStructureTree', 1);
}
/**
* Occurs when pasting category
*
* @param kEvent $event
*/
/*function OnCatPaste($event)
{
$inp_clipboard = $this->Application->RecallVar('ClipBoard');
$inp_clipboard = explode('-', $inp_clipboard, 2);
if($inp_clipboard[0] == 'COPY')
{
$config = $event->getUnitConfig();
$cat_ids = $event->getEventParam('cat_ids');
$saved_cat_id = $this->Application->GetVar('m_cat_id');
$ids_sql = 'SELECT ' . $config->getIDField() . '
FROM ' . $config->getTableName() . '
WHERE ResourceId IN (%s)';
$resource_ids_sql = 'SELECT ItemResourceId FROM '.TABLE_PREFIX.'CategoryItems WHERE CategoryId = %s AND PrimaryCat = 1';
$object = $this->Application->recallObject($event->Prefix.'.item', $event->Prefix, Array('skip_autoload' => true));
foreach($cat_ids as $source_cat => $dest_cat)
{
$item_resource_ids = $this->Conn->GetCol( sprintf($resource_ids_sql, $source_cat) );
if(!$item_resource_ids) continue;
$this->Application->SetVar('m_cat_id', $dest_cat);
$item_ids = $this->Conn->GetCol( sprintf($ids_sql, implode(',', $item_resource_ids) ) );
$temp = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
if($item_ids) $temp->CloneItems($event->Prefix, $event->Special, $item_ids);
}
$this->Application->SetVar('m_cat_id', $saved_cat_id);
}
}*/
/**
* Clears clipboard content
*
* @param kEvent $event
*/
function OnClearClipboard($event)
{
$this->Application->RemoveVar('clipboard');
}
/**
* Sets correct status for new categories created on front-end
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemCreate(kEvent $event)
{
parent::OnBeforeItemCreate($event);
$object = $event->getObject();
/* @var $object CategoriesItem */
if ( $object->GetDBField('ParentId') <= 0 ) {
// no parent category - use current (happens during import)
$object->SetDBField('ParentId', $this->Application->GetVar('m_cat_id'));
}
$this->_beforeItemChange($event);
if ( $this->Application->isAdmin || $event->Prefix == 'st' ) {
// don't check category permissions when auto-creating structure pages
return ;
}
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$new_status = false;
$category_id = $this->Application->GetVar('m_cat_id');
if ( $perm_helper->CheckPermission('CATEGORY.ADD', 0, $category_id) ) {
$new_status = STATUS_ACTIVE;
}
else {
if ( $perm_helper->CheckPermission('CATEGORY.ADD.PENDING', 0, $category_id) ) {
$new_status = STATUS_PENDING;
}
}
if ( $new_status ) {
$object->SetDBField('Status', $new_status);
// don't forget to set Priority for suggested from Front-End categories
$min_priority = $this->_getNextPriority($object->GetDBField('ParentId'), $object->TableName);
$object->SetDBField('Priority', $min_priority);
}
else {
$event->status = kEvent::erPERM_FAIL;
return ;
}
}
/**
* Returns next available priority for given category from given table
*
* @param int $category_id
* @param string $table_name
* @return int
*/
function _getNextPriority($category_id, $table_name)
{
$sql = 'SELECT MIN(Priority)
FROM ' . $table_name . '
WHERE ParentId = ' . $category_id;
return (int)$this->Conn->GetOne($sql) - 1;
}
/**
* Sets correct status for new categories created on front-end
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemUpdate(kEvent $event)
{
parent::OnBeforeItemUpdate($event);
$this->_beforeItemChange($event);
$object = $event->getObject();
/* @var $object kDBItem */
if ( $object->GetChangedFields() ) {
$object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
}
}
/**
* Creates needed sql query to load item,
* if no query is defined in config for
* special requested, then use list query
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function ItemPrepareQuery(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$sqls = $object->getFormOption('ItemSQLs', Array ());
$category_special = $this->_getCategorySpecial($event);
$special = isset($sqls[$category_special]) ? $category_special : '';
// preferred special not found in ItemSQLs -> use analog from ListSQLs
return isset($sqls[$special]) ? $sqls[$special] : $this->ListPrepareQuery($event);
}
/**
* Creates needed sql query to load list,
* if no query is defined in config for
* special requested, then use default
* query
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function ListPrepareQuery(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$special = $this->_getCategorySpecial($event);
$sqls = $object->getFormOption('ListSQLs', Array ());
return $sqls[array_key_exists($special, $sqls) ? $special : ''];
}
/**
* Performs redirect to correct suggest confirmation template
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCreate(kEvent $event)
{
parent::OnCreate($event);
if ( $this->Application->isAdmin || $event->status != kEvent::erSUCCESS ) {
// don't sent email or rebuild cache directly after category is created by admin
return;
}
$object = $event->getObject();
/* @var $object kDBItem */
$cache_updater = $this->Application->makeClass('kPermCacheUpdater', Array (null, $object->GetDBField('ParentPath')));
/* @var $cache_updater kPermCacheUpdater */
$cache_updater->OneStepRun();
$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
$next_template = $is_active ? 'suggest_confirm_template' : 'suggest_pending_confirm_template';
$event->redirect = $this->Application->GetVar($next_template);
$event->SetRedirectParam('opener', 's');
// send email events
$send_params = $object->getEmailParams();
$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
$perm_prefix = $event->getUnitConfig()->getPermItemPrefix();
$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $object->GetDBField('CreatedById'), $send_params);
$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix, null, $send_params);
}
/**
* Returns current per-page setting for list
*
* @param kEvent $event
* @return int
* @access protected
*/
protected function getPerPage(kEvent $event)
{
if ( !$this->Application->isAdmin ) {
$same_special = $event->getEventParam('same_special');
$event->setEventParam('same_special', true);
$per_page = parent::getPerPage($event);
$event->setEventParam('same_special', $same_special);
}
return parent::getPerPage($event);
}
/**
* Set's correct page for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetPagination(kEvent $event)
{
parent::SetPagination($event);
if ( !$this->Application->isAdmin ) {
$page_var = $event->getEventParam('page_var');
if ( $page_var !== false ) {
$page = $this->Application->GetVar($page_var);
if ( is_numeric($page) ) {
$object = $event->getObject();
/* @var $object kDBList */
$object->SetPage($page);
}
}
}
}
/**
* Apply same processing to each item being selected in grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function iterateItems(kEvent $event)
{
if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) {
parent::iterateItems($event);
}
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object CategoriesItem */
$ids = $this->StoreSelectedIDs($event);
if ( $ids ) {
$propagate_category_status = $this->Application->GetVar('propagate_category_status');
$status_field = $event->getUnitConfig()->getStatusField(true);
foreach ($ids as $id) {
$object->Load($id);
$object->SetDBField($status_field, $event->Name == 'OnMassApprove' ? 1 : 0);
if ( $object->Update() ) {
if ( $propagate_category_status ) {
$sql = 'UPDATE ' . $object->TableName . '
SET ' . $status_field . ' = ' . $object->GetDBField($status_field) . '
WHERE TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight');
$this->Conn->Query($sql);
}
$event->status = kEvent::erSUCCESS;
$email_event = $event->Name == 'OnMassApprove' ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
$this->Application->emailUser($email_event, $object->GetDBField('CreatedById'), $object->getEmailParams());
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
break;
}
}
}
$this->clearSelectedIDs($event);
$this->Application->StoreVar('RefreshStructureTree', 1);
}
/**
* Checks, that currently loaded item is allowed for viewing (non permission-based)
*
* @param kEvent $event
* @return bool
* @access protected
*/
protected function checkItemStatus(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
if ( !$object->isLoaded() ) {
return true;
}
if ( $object->GetDBField('Status') != STATUS_ACTIVE && $object->GetDBField('Status') != 4 ) {
if ( !$object->GetDBField('DirectLinkEnabled') || !$object->GetDBField('DirectLinkAuthKey') ) {
return false;
}
return $this->Application->GetVar('authkey') == $object->GetDBField('DirectLinkAuthKey');
}
return true;
}
/**
* Set's correct sorting for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetSorting(kEvent $event)
{
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
if ( in_array('search', $types) ) {
$event->setPseudoClass('_List');
$object = $event->getObject();
/* @var $object kDBList */
// 1. no user sorting - sort by relevance
$default_sortings = parent::_getDefaultSorting($event);
$default_sorting = key($default_sortings['Sorting']) . ',' . current($default_sortings['Sorting']);
if ( $object->isMainList() ) {
$sort_by = $this->Application->GetVar('sort_by', '');
if ( !$sort_by ) {
$this->Application->SetVar('sort_by', 'Relevance,desc|' . $default_sorting);
}
elseif ( strpos($sort_by, 'Relevance,') !== false ) {
$this->Application->SetVar('sort_by', $sort_by . '|' . $default_sorting);
}
}
else {
$sorting_settings = $this->getListSetting($event, 'Sortings');
$sort_by = trim(getArrayValue($sorting_settings, 'Sort1') . ',' . getArrayValue($sorting_settings, 'Sort1_Dir'), ',');
if ( !$sort_by ) {
$event->setEventParam('sort_by', 'Relevance,desc|' . $default_sorting);
}
elseif ( strpos($sort_by, 'Relevance,') !== false ) {
$event->setEventParam('sort_by', $sort_by . '|' . $default_sorting);
}
}
$this->_removeForcedSortings($event);
}
parent::SetSorting($event);
}
/**
* Removes forced sortings
*
* @param kEvent $event
*/
protected function _removeForcedSortings(kEvent $event)
{
$config = $event->getUnitConfig();
foreach ($config->getListSortingSpecials() as $special) {
$list_sortings = $config->getListSortingsBySpecial($special);
unset($list_sortings['ForcedSorting']);
$config->setListSortingsBySpecial('', $list_sortings);
}
}
/**
* Default sorting in search results only comes from relevance field
*
* @param kEvent $event
* @return Array
* @access protected
*/
protected function _getDefaultSorting(kEvent $event)
{
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
return in_array('search', $types) ? Array () : parent::_getDefaultSorting($event);
}
// ============= for cms page processing =======================
/**
* Returns default design template
*
* @return string
*/
function _getDefaultDesign()
{
$default_design = trim($this->Application->ConfigValue('cms_DefaultDesign'), '/');
if (!$default_design) {
// theme-based alias for default design
return '#default_design#';
}
if (strpos($default_design, '#') === false) {
// real template, not alias, so prefix with "/"
return '/' . $default_design;
}
// alias
return $default_design;
}
/**
* Returns default design based on given virtual template (used from kApplication::Run)
*
* @param string $t
* @return string
* @access public
*/
public function GetDesignTemplate($t = null)
{
if ( !isset($t) ) {
$t = $this->Application->GetVar('t');
}
$page = $this->Application->recallObject($this->Prefix . '.-virtual', null, Array ('page' => $t));
/* @var $page CategoriesItem */
if ( $page->isLoaded() ) {
$real_t = $page->GetDBField('CachedTemplate');
$this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId'));
if ( $page->GetDBField('FormId') ) {
$this->Application->SetVar('form_id', $page->GetDBField('FormId'));
}
}
else {
$not_found = $this->Application->ConfigValue('ErrorTemplate');
$real_t = $not_found ? $not_found : 'error_notfound';
$themes_helper = $this->Application->recallObject('ThemesHelper');
/* @var $themes_helper kThemesHelper */
$theme_id = $this->Application->GetVar('m_theme');
$category_id = $themes_helper->getPageByTemplate($real_t, $theme_id);
$this->Application->SetVar('m_cat_id', $category_id);
header('HTTP/1.0 404 Not Found');
}
// replace alias in form #alias_name# to actual template used in this theme
if ( $this->Application->isAdmin ) {
$themes_helper = $this->Application->recallObject('ThemesHelper');
/* @var $themes_helper kThemesHelper */
// only, used when in "Design Mode"
$this->Application->SetVar('theme.current_id', $themes_helper->getCurrentThemeId());
}
$theme = $this->Application->recallObject('theme.current');
/* @var $theme kDBItem */
$template = $theme->GetField('TemplateAliases', $real_t);
if ( $template ) {
return $template;
}
return $real_t;
}
/**
* Sets category id based on found template (used from kApplication::Run)
*
* @deprecated
*/
/*function SetCatByTemplate()
{
$t = $this->Application->GetVar('t');
$page = $this->Application->recallObject($this->Prefix . '.-virtual');
if ( $page->isLoaded() ) {
$this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId'));
}
}*/
/**
* Prepares template paths
*
* @param kEvent $event
*/
function _beforeItemChange($event)
{
$object = $event->getObject();
/* @var $object CategoriesItem */
$object->checkFilename();
$object->generateFilename();
$now = time();
if ( !$this->Application->isDebugMode() && strpos($event->Special, 'rebuild') === false ) {
$object->SetDBField('Type', $object->GetOriginalField('Type'));
$object->SetDBField('Protected', $object->GetOriginalField('Protected'));
if ( $object->GetDBField('Protected') ) {
// some fields are read-only for protected pages, when debug mode is off
$object->SetDBField('AutomaticFilename', $object->GetOriginalField('AutomaticFilename'));
$object->SetDBField('Filename', $object->GetOriginalField('Filename'));
$object->SetDBField('Status', $object->GetOriginalField('Status'));
}
}
$is_admin = $this->Application->isAdminUser;
if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField('CreatedById')) ) {
$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
}
if ($object->GetChangedFields()) {
$object->SetDBField('Modified_date', $now);
$object->SetDBField('Modified_time', $now);
}
$object->setRequired('PageCacheKey', $object->GetDBField('OverridePageCacheKey'));
$object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') ));
if ($object->GetDBField('Type') == PAGE_TYPE_TEMPLATE) {
if (!$this->_templateFound($object->GetDBField('Template'), $object->GetDBField('ThemeId'))) {
$object->SetError('Template', 'template_file_missing', 'la_error_TemplateFileMissing');
}
}
$this->_saveTitleField($object, 'Title');
$this->_saveTitleField($object, 'MenuTitle');
$root_category = $this->Application->getBaseCategory();
if ( file_exists(FULL_PATH . '/themes') && ($object->GetDBField('ParentId') == $root_category) && ($object->GetDBField('Template') == CATEGORY_TEMPLATE_INHERIT) ) {
// there are themes + creating top level category
$object->SetError('Template', 'no_inherit');
}
if ( !$this->Application->isAdminUser && $object->isVirtualField('cust_RssSource') ) {
// only administrator can set/change "cust_RssSource" field
if ($object->GetDBField('cust_RssSource') != $object->GetOriginalField('cust_RssSource')) {
$object->SetError('cust_RssSource', 'not_allowed', 'la_error_OperationNotAllowed');
}
}
if ( !$object->GetDBField('DirectLinkAuthKey') ) {
$key_parts = Array (
$object->GetID(),
$object->GetDBField('ParentId'),
$object->GetField('Name'),
'b38'
);
$object->SetDBField('DirectLinkAuthKey', substr( md5( implode(':', $key_parts) ), 0, 20 ));
}
}
/**
* Sets page name to requested field in case when:
* 1. page was auto created (through theme file rebuild)
* 2. requested field is empty
*
* @param kDBItem $object
* @param string $field
* @author Alex
*/
function _saveTitleField(&$object, $field)
{
$value = $object->GetField($field, 'no_default'); // current value of target field
$ml_formatter = $this->Application->recallObject('kMultiLanguage');
/* @var $ml_formatter kMultiLanguage */
$src_field = $ml_formatter->LangFieldName('Name');
$dst_field = $ml_formatter->LangFieldName($field);
$dst_field_not_changed = $object->GetOriginalField($dst_field) == $value;
if ($value == '' || preg_match('/^_Auto: (.*)/', $value) || (($object->GetOriginalField($src_field) == $value) && $dst_field_not_changed)) {
// target field is empty OR target field value starts with "_Auto: " OR (source field value
// before change was equals to current target field value AND target field value wasn't changed)
$object->SetField($dst_field, $object->GetField($src_field));
}
}
/**
* Don't allow to delete system pages, when not in debug mode
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemDelete(kEvent $event)
{
parent::OnBeforeItemDelete($event);
$object = $event->getObject();
/* @var $object kDBItem */
if ( $object->GetDBField('Protected') && !$this->Application->isDebugMode(false) ) {
$event->status = kEvent::erFAIL;
}
}
/**
* Creates category based on given TPL file
*
* @param CategoriesItem $object
* @param string $template
* @param int $theme_id
* @param int $system_mode
* @param array $template_info
* @return bool
*/
function _prepareAutoPage(&$object, $template, $theme_id = null, $system_mode = SMS_MODE_AUTO, $template_info = Array ())
{
$template = $this->_stripTemplateExtension($template);
if ($system_mode == SMS_MODE_AUTO) {
$page_type = $this->_templateFound($template, $theme_id) ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL;
}
else {
$page_type = $system_mode == SMS_MODE_FORCE ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL;
}
if (($page_type == PAGE_TYPE_TEMPLATE) && ($template_info === false)) {
// do not auto-create system pages, when browsing through site
return false;
}
if (!isset($theme_id)) {
$theme_id = $this->_getCurrentThemeId();
}
$root_category = $this->Application->getBaseCategory();
$page_category = $this->Application->GetVar('m_cat_id');
if (!$page_category) {
$page_category = $root_category;
$this->Application->SetVar('m_cat_id', $page_category);
}
if (($page_type == PAGE_TYPE_VIRTUAL) && (strpos($template, '/') !== false)) {
// virtual page, but have "/" in template path -> create it's path
$category_path = explode('/', $template);
$template = array_pop($category_path);
$page_category = $this->_getParentCategoryFromPath($category_path, $root_category, $theme_id);
}
$page_name = ($page_type == PAGE_TYPE_TEMPLATE) ? '_Auto: ' . $template : $template;
$page_description = '';
if ($page_type == PAGE_TYPE_TEMPLATE) {
$design_template = strtolower($template); // leading "/" not added !
if ($template_info) {
if (array_key_exists('name', $template_info) && $template_info['name']) {
$page_name = $template_info['name'];
}
if (array_key_exists('desc', $template_info) && $template_info['desc']) {
$page_description = $template_info['desc'];
}
if (array_key_exists('section', $template_info) && $template_info['section']) {
// this will override any global "m_cat_id"
$page_category = $this->_getParentCategoryFromPath(explode('||', $template_info['section']), $root_category, $theme_id);
}
}
}
else {
$design_template = $this->_getDefaultDesign(); // leading "/" added !
}
$object->Clear();
$object->SetDBField('ParentId', $page_category);
$object->SetDBField('Type', $page_type);
$object->SetDBField('Protected', 1); // $page_type == PAGE_TYPE_TEMPLATE
$object->SetDBField('IsMenu', 0);
$object->SetDBField('ThemeId', $theme_id);
// put all templates to then end of list (in their category)
$min_priority = $this->_getNextPriority($page_category, $object->TableName);
$object->SetDBField('Priority', $min_priority);
$object->SetDBField('Template', $design_template);
$object->SetDBField('CachedTemplate', $design_template);
$primary_language = $this->Application->GetDefaultLanguageId();
$current_language = $this->Application->GetVar('m_lang');
$object->SetDBField('l' . $primary_language . '_Name', $page_name);
$object->SetDBField('l' . $current_language . '_Name', $page_name);
$object->SetDBField('l' . $primary_language . '_Description', $page_description);
$object->SetDBField('l' . $current_language . '_Description', $page_description);
return $object->Create();
}
function _getParentCategoryFromPath($category_path, $base_category, $theme_id = null)
{
static $category_ids = Array ();
if (!$category_path) {
return $base_category;
}
if (array_key_exists(implode('||', $category_path), $category_ids)) {
return $category_ids[ implode('||', $category_path) ];
}
$backup_category_id = $this->Application->GetVar('m_cat_id');
$object = $this->Application->recallObject($this->Prefix . '.rebuild-path', null, Array ('skip_autoload' => true));
/* @var $object CategoriesItem */
$parent_id = $base_category;
$filenames_helper = $this->Application->recallObject('FilenamesHelper');
/* @var $filenames_helper kFilenamesHelper */
$safe_category_path = array_map(Array (&$filenames_helper, 'replaceSequences'), $category_path);
foreach ($category_path as $category_order => $category_name) {
$this->Application->SetVar('m_cat_id', $parent_id);
// get virtual category first, when possible
$sql = 'SELECT ' . $object->IDField . '
FROM ' . $object->TableName . '
WHERE
(
Filename = ' . $this->Conn->qstr($safe_category_path[$category_order]) . ' OR
Filename = ' . $this->Conn->qstr( $filenames_helper->replaceSequences('_Auto: ' . $category_name) ) . '
) AND
(ParentId = ' . $parent_id . ') AND
(ThemeId = 0 OR ThemeId = ' . $theme_id . ')
ORDER BY ThemeId ASC';
$parent_id = $this->Conn->GetOne($sql);
if ($parent_id === false) {
// page not found
$template = implode('/', array_slice($safe_category_path, 0, $category_order + 1));
// don't process system templates in sub-categories
$system = $this->_templateFound($template, $theme_id) && (strpos($template, '/') === false);
if (!$this->_prepareAutoPage($object, $category_name, $theme_id, $system ? SMS_MODE_FORCE : false)) {
// page was not created
break;
}
$parent_id = $object->GetID();
}
}
$this->Application->SetVar('m_cat_id', $backup_category_id);
$category_ids[ implode('||', $category_path) ] = $parent_id;
return $parent_id;
}
/**
* Returns theme name by it's id. Used in structure page creation.
*
* @param int $theme_id
* @return string
*/
function _getThemeName($theme_id)
{
static $themes = null;
if (!isset($themes)) {
$theme_config = $this->Application->getUnitConfig('theme');
$id_field = $theme_config->getIDField();
$table_name = $theme_config->getTableName();
$sql = 'SELECT Name, ' . $id_field . '
FROM ' . $table_name . '
WHERE Enabled = 1';
$themes = $this->Conn->GetCol($sql, $id_field);
}
return array_key_exists($theme_id, $themes) ? $themes[$theme_id] : false;
}
/**
* Resets SMS-menu cache
*
* @param kEvent $event
*/
function OnResetCMSMenuCache($event)
{
if ($this->Application->GetVar('ajax') == 'yes') {
$event->status = kEvent::erSTOP;
}
$this->_resetMenuCache();
$event->SetRedirectParam('action_completed', 1);
}
/**
* Performs reset of category-related caches (menu, structure dropdown, template mapping)
*
* @return void
* @access protected
*/
protected function _resetMenuCache()
{
// reset cms menu cache (all variables are automatically rebuild, when missing)
if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
$this->Application->rebuildCache('master:cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime);
$this->Application->rebuildCache('master:StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime);
$this->Application->rebuildCache('master:template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime);
}
else {
$this->Application->rebuildDBCache('cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime);
$this->Application->rebuildDBCache('StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime);
$this->Application->rebuildDBCache('template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime);
}
}
/**
* Updates structure config
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterConfigRead(kEvent $event)
{
parent::OnAfterConfigRead($event);
if (defined('IS_INSTALL') && IS_INSTALL) {
// skip any processing, because Categories table doesn't exists until install is finished
$this->addViewPermissionJoin($event);
return ;
}
$site_config_helper = $this->Application->recallObject('SiteConfigHelper');
/* @var $site_config_helper SiteConfigHelper */
$settings = $site_config_helper->getSettings();
$root_category = $this->Application->getBaseCategory();
$config = $event->getUnitConfig();
// set root category
$config->addSectionAdjustments(Array (
'in-portal:browse' => Array (
'url' => Array ('m_cat_id' => $root_category),
'late_load' => Array ('m_cat_id' => $root_category),
'onclick' => 'checkCatalog(' . $root_category . ')',
),
'in-portal:browse_site' => Array (
'url' => Array ('editing_mode' => $settings['default_editing_mode']),
)
));
// prepare structure dropdown
$category_helper = $this->Application->recallObject('CategoryHelper');
/* @var $category_helper CategoryHelper */
$fields = $config->getFields();
$fields['ParentId']['default'] = (int)$this->Application->GetVar('m_cat_id');
$fields['ParentId']['options'] = $category_helper->getStructureTreeAsOptions();
// limit design list by theme
$theme_id = $this->_getCurrentThemeId();
$design_sql = $fields['Template']['options_sql'];
$design_sql = str_replace('(tf.FilePath = "/designs")', '(' . implode(' OR ', $this->getDesignFolders()) . ')' . ' AND (t.ThemeId = ' . $theme_id . ')', $design_sql);
$fields['Template']['options_sql'] = $design_sql;
// adds "Inherit From Parent" option to "Template" field
$fields['Template']['options'] = Array (CATEGORY_TEMPLATE_INHERIT => $this->Application->Phrase('la_opt_InheritFromParent'));
$config->setFields($fields);
if ($this->Application->isAdmin) {
// don't sort by Front-End sorting fields
$config_mapping = $config->getConfigMapping();
$remove_keys = Array ('DefaultSorting1Field', 'DefaultSorting2Field', 'DefaultSorting1Dir', 'DefaultSorting2Dir');
foreach ($remove_keys as $remove_key) {
unset($config_mapping[$remove_key]);
}
$config->setConfigMapping($config_mapping);
}
else {
// sort by parent path on Front-End only
$config->setListSortingsBySpecial('', Array (
'ForcedSorting' => Array ('CurrentSort' => 'asc'),
));
}
$this->addViewPermissionJoin($event);
// add grids for advanced view (with primary category column)
foreach (Array ('Default', 'Radio') as $process_grid) {
$grid_data = $config->getGridByName($process_grid);
$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_parent_category_td', 'filter_block' => 'grid_like_filter');
$config->addGrids($grid_data, $process_grid . 'ShowAll');
}
}
/**
* Adds permission table table JOIN clause only, when advanced catalog view permissions enabled.
*
* @param kEvent $event Event.
*
* @return self
* @access protected
*/
protected function addViewPermissionJoin(kEvent $event)
{
if ( $this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) {
$join_clause = 'LEFT JOIN ' . TABLE_PREFIX . 'CategoryPermissionsCache perm ON perm.CategoryId = %1$s.CategoryId';
}
else {
$join_clause = '';
}
$config = $event->getUnitConfig();
foreach ( $config->getListSQLSpecials() as $special ) {
$list_sql = str_replace('{PERM_JOIN}', $join_clause, $config->getListSQLsBySpecial($special));
$config->setListSQLsBySpecial($special, $list_sql);
}
return $this;
}
/**
* Returns folders, that can contain design templates
*
* @return array
* @access protected
*/
protected function getDesignFolders()
{
$ret = Array ('tf.FilePath = "/designs"', 'tf.FilePath = "/platform/designs"');
foreach ($this->Application->ModuleInfo as $module_info) {
$ret[] = 'tf.FilePath = "/' . $module_info['TemplatePath'] . 'designs"';
}
return array_unique($ret);
}
/**
* Removes this item and it's children (recursive) from structure dropdown
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemLoad(kEvent $event)
{
parent::OnAfterItemLoad($event);
if ( !$this->Application->isAdmin ) {
// calculate priorities dropdown only for admin
return;
}
$object = $event->getObject();
/* @var $object kDBItem */
// remove this category & it's children from dropdown
$sql = 'SELECT ' . $object->IDField . '
FROM ' . $event->getUnitConfig()->getTableName() . '
WHERE ParentPath LIKE "' . $object->GetDBField('ParentPath') . '%"';
$remove_categories = $this->Conn->GetCol($sql);
$options = $object->GetFieldOption('ParentId', 'options');
foreach ($remove_categories as $remove_category) {
unset($options[$remove_category]);
}
$object->SetFieldOption('ParentId', 'options', $options);
}
/**
* Occurs after creating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemCreate(kEvent $event)
{
parent::OnAfterItemCreate($event);
$object = $event->getObject();
/* @var $object CategoriesItem */
// need to update path after category is created, so category is included in that path
$fields_hash = $object->buildParentBasedFields();
$this->Conn->doUpdate($fields_hash, $object->TableName, $object->IDField . ' = ' . $object->GetID());
$object->SetDBFieldsFromHash($fields_hash);
}
/**
* Enter description here...
*
* @param kEvent $event
*/
function OnAfterRebuildThemes($event)
{
$sql = 'SELECT t.ThemeId, CONCAT( tf.FilePath, \'/\', tf.FileName ) AS Path, tf.FileMetaInfo
FROM ' . TABLE_PREFIX . 'ThemeFiles AS tf
LEFT JOIN ' . TABLE_PREFIX . 'Themes AS t ON t.ThemeId = tf.ThemeId
WHERE t.Enabled = 1 AND tf.FileType = 1
AND (
SELECT COUNT(CategoryId)
FROM ' . TABLE_PREFIX . 'Categories c
WHERE CONCAT(\'/\', c.Template, \'.tpl\') = CONCAT( tf.FilePath, \'/\', tf.FileName ) AND (c.ThemeId = t.ThemeId)
) = 0 ';
$files = $this->Conn->Query($sql, 'Path');
if ( !$files ) {
// all possible pages are already created
return;
}
kUtil::setResourceLimit();
$dummy = $this->Application->recallObject($event->Prefix . '.rebuild', NULL, Array ('skip_autoload' => true));
/* @var $dummy CategoriesItem */
$error_count = 0;
foreach ($files as $a_file => $file_info) {
$status = $this->_prepareAutoPage($dummy, $a_file, $file_info['ThemeId'], SMS_MODE_FORCE, unserialize($file_info['FileMetaInfo'])); // create system page
if ( !$status ) {
$error_count++;
}
}
if ( $this->Application->ConfigValue('CategoryPermissionRebuildMode') == CategoryPermissionRebuild::SILENT ) {
$updater = $this->Application->makeClass('kPermCacheUpdater');
/* @var $updater kPermCacheUpdater */
$updater->OneStepRun();
}
$this->_resetMenuCache();
if ( $error_count ) {
// allow user to review error after structure page creation
$event->MasterEvent->redirect = false;
}
}
/**
* Processes OnMassMoveUp, OnMassMoveDown events
*
* @param kEvent $event
*/
function OnChangePriority($event)
{
$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
$event->CallSubEvent('priority:' . $event->Name);
$this->Application->StoreVar('RefreshStructureTree', 1);
$this->_resetMenuCache();
}
/**
* Completely recalculates priorities in current category
*
* @param kEvent $event
*/
function OnRecalculatePriorities($event)
{
if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
$event->status = kEvent::erFAIL;
return;
}
$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
$event->CallSubEvent('priority:' . $event->Name);
$this->_resetMenuCache();
}
/**
* Update Preview Block for FCKEditor
*
* @param kEvent $event
*/
function OnUpdatePreviewBlock($event)
{
$event->status = kEvent::erSTOP;
$string = $this->Application->unescapeRequestVariable($this->Application->GetVar('preview_content'));
$category_helper = $this->Application->recallObject('CategoryHelper');
/* @var $category_helper CategoryHelper */
$string = $category_helper->replacePageIds($string);
$this->Application->StoreVar('_editor_preview_content_', $string);
}
/**
* Makes simple search for categories
* based on keywords string
*
* @param kEvent $event
*/
function OnSimpleSearch($event)
{
$event->redirect = false;
$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
$keywords = $this->Application->unescapeRequestVariable(trim($this->Application->GetVar('keywords')));
$query_object = $this->Application->recallObject('kHTTPQuery');
/* @var $query_object kHTTPQuery */
$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
if ( !isset($query_object->Get['keywords']) && !isset($query_object->Post['keywords']) && $this->Conn->Query($sql) ) {
// used when navigating by pages or changing sorting in search results
return;
}
if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length'))
{
$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
$this->Application->SetVar('keywords_too_short', 1);
return; // if no or too short keyword entered, doing nothing
}
$this->Application->StoreVar('keywords', $keywords);
$this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search
$keywords = strtr($keywords, Array('%' => '\\%', '_' => '\\_'));
$event->setPseudoClass('_List');
$object = $event->getObject();
/* @var $object kDBList */
$config = $event->getUnitConfig();
$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
$lang = $this->Application->GetVar('m_lang');
$items_table = $config->getTableName();
$module_name = 'In-Portal';
$sql = 'SELECT *
FROM ' . $this->Application->getUnitConfig('confs')->getTableName() . '
WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1';
$search_config = $this->Conn->Query($sql, 'FieldName');
$field_list = array_keys($search_config);
$join_clauses = Array();
// field processing
$weight_sum = 0;
$alias_counter = 0;
$custom_fields = $config->getCustomFields();
if ($custom_fields) {
$custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName();
$join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
}
// what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table))
$search_config_map = Array();
foreach ($field_list as $key => $field) {
$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
// processing multilingual fields
if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
$field_list[$key] = 'l'.$lang.'_'.$field;
if (!isset($search_config[$field]['ForeignField'])) {
$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
}
// processing fields from other tables
$foreign_field = $search_config[$field]['ForeignField'];
if ( $foreign_field ) {
$exploded = explode(':', $foreign_field, 2);
if ($exploded[0] == 'CALC') {
// ignoring having type clauses in simple search
unset($field_list[$key]);
continue;
}
else {
$multi_lingual = false;
if ($exploded[0] == 'MULTI') {
$multi_lingual = true;
$foreign_field = $exploded[1];
}
$exploded = explode('.', $foreign_field); // format: table.field_name
$foreign_table = TABLE_PREFIX.$exploded[0];
$alias_counter++;
$alias = 't'.$alias_counter;
if ($multi_lingual) {
$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
$search_config_map[ $field_list[$key] ] = $field;
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
else {
$field_list[$key] = $alias.'.'.$exploded[1];
$search_config_map[ $field_list[$key] ] = $field;
}
$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
$join_clauses[] = ' LEFT JOIN '.$foreign_table.' '.$alias.'
ON '.$join_clause;
}
}
else {
// processing fields from local table
if ($search_config[$field]['CustomFieldId']) {
$local_table = 'custom_data';
// search by custom field value on current language
$custom_field_id = array_search($field_list[$key], $custom_fields);
$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
// search by custom field value on primary language
$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
$field_list[$key] = $local_table.'.'.$field_list[$key];
$search_config_map[ $field_list[$key] ] = $field;
}
}
// keyword string processing
$search_helper = $this->Application->recallObject('SearchHelper');
/* @var $search_helper kSearchHelper */
$where_clause = Array ();
foreach ($field_list as $field) {
if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
// local real field
$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
if ($filter_data) {
$where_clause[] = $filter_data['value'];
}
}
elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
$custom_field_name = 'cust_' . $search_config_map[$field];
$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
if ($filter_data) {
$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
}
}
else {
$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
}
}
$where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below!
$where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')';
if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
$sub_search_ids = $event->MasterEvent->getEventParam('ResultIds');
if ( $sub_search_ids !== false ) {
if ( $sub_search_ids ) {
$where_clause .= 'AND (' . $items_table . '.ResourceId IN (' . implode(',', $sub_search_ids) . '))';
}
else {
$where_clause .= 'AND FALSE';
}
}
}
// exclude template based sections from search results (ie. registration)
if ( $this->Application->ConfigValue('ExcludeTemplateSectionsFromSearch') ) {
$where_clause .= ' AND ' . $items_table . '.ThemeId = 0';
}
// making relevance clause
$positive_words = $search_helper->getPositiveKeywords($keywords);
$this->Application->StoreVar('highlight_keywords', serialize($positive_words));
$revelance_parts = Array();
reset($search_config);
foreach ($positive_words as $keyword_index => $positive_word) {
$positive_word = $search_helper->transformWildcards($positive_word);
$positive_words[$keyword_index] = $this->Conn->escape($positive_word);
}
foreach ($field_list as $field) {
if (!array_key_exists($field, $search_config_map)) {
$map_key = $search_config_map[$items_table . '.' . $field];
}
else {
$map_key = $search_config_map[$field];
}
$config_elem = $search_config[ $map_key ];
$weight = $config_elem['Priority'];
// search by whole words only ([[:<:]] - word boundary)
/*$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
foreach ($positive_words as $keyword) {
$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
}*/
+ if ( count($positive_words) > 1 ) {
+ $condition = $field . ' LIKE "%' . implode(' ', $positive_words) . '%"';
+ $revelance_parts[] = 'IF(' . $condition . ', ' . $weight_sum . ', 0)';
+ }
+
// search by partial word matches too
- $revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
- foreach ($positive_words as $keyword) {
- $revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
+ foreach ( $positive_words as $keyword ) {
+ $revelance_parts[] = 'IF(' . $field . ' LIKE "%' . $keyword . '%", ' . $weight . ', 0)';
}
}
$revelance_parts = array_unique($revelance_parts);
$conf_postfix = $config->getSearchConfigPostfix();
$rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100;
$rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100;
$rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100;
$relevance_clause = '('.implode(' + ', $revelance_parts).') / '.$weight_sum.' * '.$rel_keywords;
if ($rel_pop && $object->isField('Hits')) {
$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
}
if ($rel_rating && $object->isField('CachedRating')) {
$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
}
// building final search query
if (!$this->Application->GetVar('do_not_drop_search_table')) {
$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
$this->Application->SetVar('do_not_drop_search_table', true);
}
$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
if ($search_table_exists) {
$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
}
else {
$select_intro = 'CREATE TABLE '.$search_table.' AS ';
}
$edpick_clause = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0';
$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
'.$items_table.'.'.$config->getIDField().' AS ItemId,
'.$items_table.'.ResourceId,
'.$config->getItemType().' AS ItemType,
'.$edpick_clause.' AS EdPick
FROM '.$object->TableName.'
'.implode(' ', $join_clauses).'
WHERE '.$where_clause.'
GROUP BY '.$items_table.'.'.$config->getIDField().' ORDER BY Relevance DESC';
$this->Conn->Query($sql);
if ( !$search_table_exists ) {
$sql = 'ALTER TABLE ' . $search_table . '
ADD INDEX (ResourceId),
ADD INDEX (Relevance)';
$this->Conn->Query($sql);
}
}
/**
* Enter description here...
*
* @param kEvent $event
*/
function OnSubSearch($event)
{
// keep search results from other items after doing a sub-search on current item type
$this->Application->SetVar('do_not_drop_search_table', true);
$ids = Array ();
$search_table = TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search';
$sql = 'SHOW TABLES LIKE "' . $search_table . '"';
if ( $this->Conn->Query($sql) ) {
$item_type = $event->getUnitConfig()->getItemType();
// 1. get ids to be used as search bounds
$sql = 'SELECT DISTINCT ResourceId
FROM ' . $search_table . '
WHERE ItemType = ' . $item_type;
$ids = $this->Conn->GetCol($sql);
// 2. delete previously found ids
$sql = 'DELETE FROM ' . $search_table . '
WHERE ItemType = ' . $item_type;
$this->Conn->Query($sql);
}
$event->setEventParam('ResultIds', $ids);
$event->CallSubEvent('OnSimpleSearch');
}
/**
* Make record to search log
*
* @param string $keywords
* @param int $search_type 0 - simple search, 1 - advanced search
*/
function saveToSearchLog($keywords, $search_type = 0)
{
// don't save keywords for each module separately, just one time
// static variable can't help here, because each module uses it's own class instance !
if (!$this->Application->GetVar('search_logged')) {
$sql = 'UPDATE '.TABLE_PREFIX.'SearchLogs
SET Indices = Indices + 1
WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search
$this->Conn->Query($sql);
if ($this->Conn->getAffectedRows() == 0) {
$fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type);
$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLogs');
}
$this->Application->SetVar('search_logged', 1);
}
}
/**
* Load item if id is available
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function LoadItem(kEvent $event)
{
if ( !$this->_isVirtual($event) ) {
parent::LoadItem($event);
return;
}
$object = $event->getObject();
/* @var $object kDBItem */
$id = $this->getPassedID($event);
if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) {
// object is already loaded by same id
return;
}
if ( $object->Load($id, null, true) ) {
$actions = $this->Application->recallObject('kActions');
/* @var $actions Params */
$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
}
else {
$object->setID($id);
}
}
/**
* Returns constrain for priority calculations
*
* @param kEvent $event
* @return void
* @see PriorityEventHandler
* @access protected
*/
protected function OnGetConstrainInfo(kEvent $event)
{
$constrain = ''; // for OnSave
$event_name = $event->getEventParam('original_event');
$actual_event_name = $event->getEventParam('actual_event');
if ( $actual_event_name == 'OnSavePriorityChanges' || $event_name == 'OnAfterItemLoad' || $event_name == 'OnAfterItemDelete' ) {
$object = $event->getObject();
/* @var $object kDBItem */
$constrain = 'ParentId = ' . $object->GetDBField('ParentId');
}
elseif ( $actual_event_name == 'OnPreparePriorities' ) {
$constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id');
}
elseif ( $event_name == 'OnSave' ) {
$constrain = '';
}
else {
$constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id');
}
$event->setEventParam('constrain_info', Array ($constrain, ''));
}
/**
* Set's new unique resource id to user
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemValidate(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$resource_id = $object->GetDBField('ResourceId');
if ( !$resource_id ) {
$object->SetDBField('ResourceId', $this->Application->NextResourceId());
}
}
/**
* Occurs before an item has been cloned
* Id of newly created item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeClone(kEvent $event)
{
parent::OnBeforeClone($event);
$object = $event->getObject();
/* @var $object kDBItem */
$object->SetDBField('ResourceId', 0); // this will reset it
}
}
Index: branches/5.3.x/core/units/users/users_config.php
===================================================================
--- branches/5.3.x/core/units/users/users_config.php (revision 16194)
+++ branches/5.3.x/core/units/users/users_config.php (revision 16195)
@@ -1,663 +1,663 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
$config = Array (
'Prefix' => 'u',
'ItemClass' => Array ('class' => 'UsersItem', 'file' => 'users_item.php', 'build_event' => 'OnItemBuild'),
'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'),
'EventHandlerClass' => Array ('class' => 'UsersEventHandler', 'file' => 'users_event_handler.php', 'build_event' => 'OnBuild'),
'TagProcessorClass' => Array ('class' => 'UsersTagProcessor', 'file' => 'users_tag_processor.php', 'build_event' => 'OnBuild'),
'RegisterClasses' => Array (
Array ('pseudo' => 'UsersSyncronizeManager', 'class' => 'UsersSyncronizeManager', 'file' => 'users_syncronize.php', 'build_event' => ''),
),
'AutoLoad' => true,
'ConfigPriority' => 0,
'Hooks' => Array (
Array (
'Mode' => hBEFORE,
'Conditional' => false,
'HookToPrefix' => '',
'HookToSpecial' => '*',
'HookToEvent' => Array ('OnAfterConfigRead'),
'DoPrefix' => 'cdata',
'DoSpecial' => '*',
'DoEvent' => 'OnDefineCustomFields',
),
Array (
'Mode' => hAFTER,
'Conditional' => false,
'HookToPrefix' => 'adm',
'HookToSpecial' => '*',
'HookToEvent' => Array ('OnStartup'),
'DoPrefix' => '',
'DoSpecial' => '*',
'DoEvent' => 'OnAutoLoginUser',
),
Array (
'Mode' => hBEFORE,
'Conditional' => false,
'HookToPrefix' => 'img',
'HookToSpecial' => '*',
'HookToEvent' => Array ('OnAfterConfigRead'),
'DoPrefix' => '',
'DoSpecial' => '*',
'DoEvent' => 'OnCloneSubItem',
),
// Captcha processing
Array (
- 'Mode' => hAFTER,
+ 'Mode' => hBEFORE,
'Conditional' => false,
'HookToPrefix' => '',
'HookToSpecial' => '*',
'HookToEvent' => Array ('OnAfterConfigRead'),
'DoPrefix' => 'captcha',
'DoSpecial' => '*',
'DoEvent' => 'OnPrepareCaptcha',
),
/*Array (
'Mode' => hAFTER,
'Conditional' => false,
'HookToPrefix' => '',
'HookToSpecial' => '*',
'HookToEvent' => Array ('OnBeforeItemCreate'),
'DoPrefix' => 'captcha',
'DoSpecial' => '*',
'DoEvent' => 'OnValidateCode',
),*/
),
'QueryString' => Array (
1 => 'id',
2 => 'Page',
3 => 'PerPage',
4 => 'event',
5 => 'mode',
),
'ScheduledTasks' => Array (
'membership_expiration' => Array ('EventName' => 'OnCheckExpiredMembership', 'RunSchedule' => '*/30 * * * *'),
'delete_expired_sessions' => Array ('EventName' => 'OnDeleteExpiredSessions', 'RunSchedule' => '0 */12 * * *'),
),
'IDField' => 'PortalUserId',
'StatusField' => Array ('Status'),
'TitleField' => 'Username',
'ItemType' => 6, // used for custom fields only (on user's case)
'StatisticsInfo' => Array (
'pending' => Array (
'icon' => 'icon16_user_pending.gif',
'label' => 'la_Text_Users',
'js_url' => '#url#',
'url' => Array ('t' => 'users/users_list', 'pass' => 'm,u', 'u_event' => 'OnSetFilterPattern', 'u_filters' => 'show_active=0,show_pending=1,show_disabled=0'),
'status' => STATUS_PENDING,
),
),
'TitlePresets' => Array (
'default' => Array (
'new_status_labels' => Array ('u' => '!la_title_Adding_User!'),
'edit_status_labels' => Array ('u' => '!la_title_Editing_User!'),
),
'users_list' => Array (
'prefixes' => Array ('u_List'), 'format' => "!la_title_Users!",
'toolbar_buttons' => Array ('new_item', 'edit', 'delete', 'setprimary', 'approve', 'decline', 'frontend_mail', 'e-mail', 'export', 'view', 'dbl-click'),
),
'users_edit' => Array (
'prefixes' => Array ('u'), 'format' => "#u_status# #u_titlefield# - !la_title_General!",
'toolbar_buttons' => Array ('select', 'cancel', 'reset_edit', 'prev', 'next'),
),
'user_edit_images' => Array (
'prefixes' => Array ('u', 'u-img_List'), 'format' => "#u_status# '#u_titlefield#' - !la_title_Images!",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next', 'new_item', 'edit', 'delete', 'approve', 'decline', 'setprimary', 'move_up', 'move_down', 'view', 'dbl-click'),
),
'user_edit_groups' => Array (
'prefixes' => Array ('u', 'u-ug_List'), 'format' => "#u_status# '#u_titlefield#' - !la_title_Groups!",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next', 'select_user', 'edit', 'delete', 'setprimary', 'view', 'dbl-click'),
),
'user_edit_items' => Array (
'prefixes' => Array ('u'), 'format' => "#u_status# '#u_titlefield#' - !la_title_Items!",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next', 'edit', 'delete', 'view', 'dbl-click'),
),
'user_edit_custom' => Array (
'prefixes' => Array ('u'), 'format' => "#u_status# '#u_titlefield#' - !la_title_Custom!",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'),
),
'admin_list' => Array (
'prefixes' => Array ('u.admins_List'), 'format' => "!la_title_Administrators!",
'toolbar_buttons' => Array ('new_item', 'edit', 'delete', 'approve', 'decline', 'clone', 'refresh', 'view', 'dbl-click'),
),
'admins_edit' => Array (
'new_status_labels' => Array ('u' => '!la_title_AddingAdministrator!'),
'edit_status_labels' => Array ('u' => '!la_title_EditingAdministrator!'),
'prefixes' => Array ('u'), 'format' => "#u_status# #u_titlefield#",
'toolbar_buttons' => Array ('select', 'cancel', 'reset_edit', 'prev', 'next'),
),
'regular_users_list' => Array (
'prefixes' => Array ('u.regular_List'), 'format' => "!la_title_Users!",
'toolbar_buttons' => Array (),
),
'root_edit' => Array (
'prefixes' => Array ('u'), 'format' => "!la_title_Editing_User! 'root'",
'toolbar_buttons' => Array ('select', 'cancel'),
),
'user_edit_group' => Array (
'prefixes' => Array ('u', 'u-ug'),
'edit_status_labels' => Array ('u-ug' => '!la_title_EditingMembership!'),
'format' => "#u_status# '#u_titlefield#' - #u-ug_status# '#u-ug_titlefield#'",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'),
),
'user_image_edit' => Array (
'prefixes' => Array ('u', 'u-img'),
'new_status_labels' => Array ('u-img' => '!la_title_Adding_Image!'),
'edit_status_labels' => Array ('u-img' => '!la_title_Editing_Image!'),
'new_titlefield' => Array ('u-img' => '!la_title_New_Image!'),
'format' => "#u_status# '#u_titlefield#' - #u-img_status# '#u-img_titlefield#'",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'),
),
'user_select' => Array (
'prefixes' => Array ('u_List'), 'format' => "!la_title_Users! - !la_title_SelectUser!",
'toolbar_buttons' => Array ('select', 'cancel', 'dbl-click'),
),
'group_user_select' => Array (
'prefixes' => Array ('u.group_List'), 'format' => "!la_title_Users! - !la_title_SelectUser!",
'toolbar_buttons' => Array ('select', 'cancel', 'view', 'dbl-click'),
),
'tree_users' => Array ('format' => '!la_section_overview!'),
),
'EditTabPresets' => Array (
'Default' => Array (
'general' => Array ('title' => 'la_tab_General', 't' => 'users/users_edit', 'priority' => 1),
'groups' => Array ('title' => 'la_tab_Groups', 't' => 'users/users_edit_groups', 'priority' => 2),
'images' => Array ('title' => 'la_tab_Images', 't' => 'users/user_edit_images', 'priority' => 3),
'items' => Array ('title' => 'la_tab_Items', 't' => 'users/user_edit_items', 'priority' => 4),
'custom' => Array ('title' => 'la_tab_Custom', 't' => 'users/users_edit_custom', 'priority' => 5),
),
'Admins' => Array (
'general' => Array ('title' => 'la_tab_General', 't' => 'users/admins_edit', 'priority' => 1),
'groups' => Array ('title' => 'la_tab_Groups', 't' => 'users/admins_edit_groups', 'priority' => 2),
),
),
'PermSection' => Array ('main' => 'in-portal:user_list', 'custom' => 'in-portal:user_custom'),
'Sections' => Array (
'in-portal:user_list' => Array (
'parent' => 'in-portal:users',
'icon' => 'users',
'label' => 'la_title_Users', // 'la_tab_User_List',
'url' => Array ('t' => 'users/users_list', 'pass' => 'm'),
'permissions' => Array ('view', 'add', 'edit', 'delete', 'advanced:ban', 'advanced:send_email', /*'advanced:add_favorite', 'advanced:remove_favorite',*/),
'priority' => 1,
'type' => stTREE,
),
'in-portal:admins' => Array (
'parent' => 'in-portal:users',
'icon' => 'administrators',
'label' => 'la_title_Administrators',
'url' => Array ('t' => 'users/admins_list', 'pass' => 'm'),
'permissions' => Array ('view', 'add', 'edit', 'delete'),
'perm_prefix' => 'u',
'priority' => 2,
'type' => stTREE,
),
// user settings
'in-portal:user_setting_folder' => Array (
'parent' => 'in-portal:system',
'icon' => 'conf_users',
'label' => 'la_title_Users',
'use_parent_header' => 1,
'url' => Array ('t' => 'index', 'pass_section' => true, 'pass' => 'm'),
'permissions' => Array ('view'),
'priority' => 2,
'container' => true,
'type' => stTREE,
),
'in-portal:configure_users' => Array (
'parent' => 'in-portal:user_setting_folder',
'icon' => 'conf_users_general',
'label' => 'la_tab_ConfigSettings',
'url' => Array ('t' => 'config/config_universal', 'module' => 'In-Portal:Users', 'pass_section' => true, 'pass' => 'm'),
'permissions' => Array ('view', 'add', 'edit'),
'priority' => 1,
'type' => stTREE,
),
'in-portal:user_custom' => Array (
'parent' => 'in-portal:user_setting_folder',
'icon' => 'conf_customfields',
'label' => 'la_tab_ConfigCustom',
'url' => Array ('t' => 'custom_fields/custom_fields_list', 'cf_type' => 6, 'pass_section' => true, 'pass' => 'm,cf'),
'permissions' => Array ('view', 'add', 'edit', 'delete'),
'priority' => 2,
'type' => stTREE,
),
),
'TableName' => TABLE_PREFIX.'Users',
'CustomDataTableName' => TABLE_PREFIX . 'UserCustomData',
'ListSQLs' => Array (
'' => ' SELECT %1$s.* %2$s FROM %1$s
LEFT JOIN '.TABLE_PREFIX.'UserGroups g ON %1$s.PrimaryGroupId = g.GroupId
LEFT JOIN '.TABLE_PREFIX.'%3$sUserCustomData cust ON %1$s.ResourceId = cust.ResourceId
LEFT JOIN '.TABLE_PREFIX.'%3$sCatalogImages img ON img.ResourceId = %1$s.ResourceId AND img.DefaultImg = 1',
'online' => ' SELECT %1$s.* %2$s FROM %1$s
LEFT JOIN '.TABLE_PREFIX.'UserSessions s ON s.PortalUserId = %1$s.PortalUserId
LEFT JOIN '.TABLE_PREFIX.'UserGroups g ON %1$s.PrimaryGroupId = g.GroupId
LEFT JOIN '.TABLE_PREFIX.'%3$sUserCustomData cust ON %1$s.ResourceId = cust.ResourceId
LEFT JOIN '.TABLE_PREFIX.'%3$sCatalogImages img ON img.ResourceId = %1$s.ResourceId AND img.DefaultImg = 1',
),
'ListSortings' => Array (
'' => Array (
'Sorting' => Array ('Username' => 'asc'),
)
),
'SubItems' => Array ('addr', 'u-cdata', 'u-ug', 'u-img', 'fav', 'user-profile'),
/**
* Required for depricated public profile templates to work
*/
'UserProfileMapping' => Array (
'pp_firstname' => 'FirstName',
'pp_lastname' => 'LastName',
'pp_dob' => 'dob',
'pp_email' => 'Email',
'pp_phone' => 'Phone',
'pp_street' => 'Street',
'pp_city' => 'City',
'pp_state' => 'State',
'pp_zip' => 'Zip',
'pp_country' => 'Country',
),
'CalculatedFields' => Array (
'' => Array (
'PrimaryGroup' => 'g.Name',
'FullName' => 'CONCAT(%1$s.FirstName, " ", %1$s.LastName)',
'AltName' => 'img.AltName',
'SameImages' => 'img.SameImages',
'LocalThumb' => 'img.LocalThumb',
'ThumbPath' => 'img.ThumbPath',
'ThumbUrl' => 'img.ThumbUrl',
'LocalImage' => 'img.LocalImage',
'LocalPath' => 'img.LocalPath',
'FullUrl' => 'img.Url',
),
),
'Forms' => Array (
'default' => Array (
'Fields' => Array (
'PortalUserId' => Array ('default' => 0),
'Username' => Array (
'max_len' => 255,
'formatter' => 'kFormatter', 'regexp' => '/^[A-Z\d_\-\.]+$/i',
'error_msgs' => Array (
'unique' => '!lu_user_already_exist!', 'invalid_format' => '!la_error_InvalidLogin!', 'banned' => '!la_error_UserBanned!'
),
'unique' => Array (), 'default' => '',
),
'Password' => Array (
'formatter' => 'kPasswordFormatter', 'hashing_method_field' => 'PasswordHashingMethod', 'verify_field' => 'VerifyPassword',
'default' => ''
),
'PasswordHashingMethod' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (
PasswordHashingMethod::MD5 => 'md5',
PasswordHashingMethod::MD5_AND_PHPPASS => 'md5+phppass',
PasswordHashingMethod::PHPPASS => 'phppass'
),
'default' => PasswordHashingMethod::PHPPASS
),
'FirstName' => Array ('default' => ''),
'LastName' => Array ('default' => ''),
'Company' => Array ('default' => ''),
'Email' => Array (
'formatter' => 'kFormatter', 'regexp' => '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i',
'sample_value' => 'email@domain.com', 'error_msgs' => Array (
'invalid_format' => '!la_invalid_email!', 'unique' => '!lu_email_already_exist!'
),
'unique' => Array (), 'required' => 1, 'default' => ''
),
'CreatedOn' => Array ('formatter' => 'kDateFormatter', 'default' => '#NOW#'),
'Phone' => Array ('default' => ''),
'Fax' => Array ('default' => ''),
'Street' => Array ('default' => ''),
'Street2' => Array ('error_field' => 'Street', 'default' => ''),
'City' => Array ('default' => ''),
'State' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (),
'default' => '',
),
'Zip' => Array ('default' => ''),
'Country' => Array (
'formatter' => 'kOptionsFormatter',
'options_sql' => ' SELECT IF(l%2$s_Name = "", l%3$s_Name, l%2$s_Name) AS Name, IsoCode
FROM ' . TABLE_PREFIX . 'CountryStates
WHERE Type = ' . DESTINATION_TYPE_COUNTRY . '
ORDER BY Name',
'option_key_field' => 'IsoCode', 'option_title_field' => 'Name',
'default' => '',
),
'ResourceId' => Array ('default' => 0),
'Status' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Enabled', 0 => 'la_Disabled', 2 => 'la_Pending'), 'use_phrases' => 1,
'default' => 1
),
'Modified' => Array ('formatter' => 'kDateFormatter', 'default' => NULL),
'dob' => Array ('formatter' => 'kDateFormatter', 'default' => NULL),
'TimeZone' => Array ('default' => ''),
'IPAddress' => Array ('default' => ''),
'IsBanned' => Array ('default' => 0),
'PwResetConfirm' => Array ('default' => ''),
'PwRequestTime' => Array ('formatter' => 'kDateFormatter', 'default' => NULL),
'FrontLanguage' => Array (
'formatter' => 'kOptionsFormatter', 'options_sql' => 'SELECT %s FROM ' . TABLE_PREFIX . 'Languages ORDER BY PackName',
'option_key_field' => 'LanguageId', 'option_title_field' => 'LocalName', 'default' => NULL
),
'AdminLanguage' => Array (
'formatter' => 'kOptionsFormatter', 'options_sql' => 'SELECT %s FROM ' . TABLE_PREFIX . 'Languages ORDER BY PackName',
'option_key_field' => 'LanguageId', 'option_title_field' => 'LocalName', 'default' => NULL
),
'DisplayToPublic' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (
'FirstName' => 'lu_fld_FirstName', 'LastName' => 'lu_fld_LastName', 'dob' => 'lu_fld_BirthDate',
'Email' => 'lu_fld_Email', 'Phone' => 'lu_fld_Phone', 'Street' => 'lu_fld_AddressLine1',
'Street2' => 'lu_fld_AddressLine2', 'City' => 'lu_fld_City', 'State' => 'lu_fld_State',
'Zip' => 'lu_fld_Zip', 'Country' => 'lu_fld_Country',
), 'use_phrases' => 1, 'multiple' => 1,
'default' => NULL
),
'UserType' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (0 => 'la_opt_UserTypeUser', 1 => 'la_opt_UserTypeAdmin'), 'use_phrases' => 1,
'default' => 0
),
'PrimaryGroupId' => Array (
'formatter' => 'kOptionsFormatter', 'options_sql' => 'SELECT %1$s FROM ' . TABLE_PREFIX . 'UserGroups WHERE Enabled = 1 AND FrontRegistration = 1',
'option_key_field' => 'GroupId', 'option_title_field' => 'Name', 'default' => NULL
),
'OldStyleLogin' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1,
'default' => 0
),
'IPRestrictions' => Array ('default' => NULL),
'EmailVerified' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1,
'default' => 0
),
'PrevEmails' => Array ('default' => NULL),
),
'VirtualFields' => Array (
'PrimaryGroup' => Array ('default' => ''),
'RootPassword' => Array (
'formatter' => 'kPasswordFormatter', 'hashing_method' => PasswordHashingMethod::PHPPASS, 'salt' => 'b38',
'verify_field' => 'VerifyRootPassword', 'default' => 'b38:d41d8cd98f00b204e9800998ecf8427e'
),
'EmailPassword' => Array ('default' => ''),
'FullName' => Array ('default' => ''),
'AltName' => Array ('default' => ''),
'SameImages' => Array ('default' => ''),
'LocalThumb' => Array ('default' => ''),
'ThumbPath' => Array ('default' => ''),
'ThumbUrl' => Array ('default' => ''),
'LocalImage' => Array ('default' => ''),
'LocalPath' => Array ('default' => ''),
'FullUrl' => Array ('default' => ''),
'SubscribeToMailing' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'lu_Yes', 0 => 'lu_No'), 'use_phrases' => 1,
'default' => 0,
),
),
),
'registration' => Array (
// Front-End user registration form
/*'Fields' => Array (
'FirstName' => Array ('required' => 1),
),*/
),
'recommend' => Array (
'VirtualFields' => Array (
'RecommendEmail' => Array (
'type' => 'string',
'formatter' => 'kFormatter', 'regexp' => '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i',
'error_msgs' => Array ('required' => '!lu_InvalidEmail!', 'invalid_format' => '!lu_InvalidEmail!', 'send_error' => '!lu_email_send_error!'),
'sample_value' => 'email@domain.com',
'required' => 1, 'default' => ''
),
),
),
'subscription' => Array (
'VirtualFields' => Array (
'SubscriberEmail' => Array (
'type' => 'string',
'formatter' => 'kFormatter', 'regexp' => '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i',
'error_msgs' => Array ('required' => '!lu_InvalidEmail!', 'invalid_format' => '!lu_InvalidEmail!'),
'sample_value' => 'email@domain.com',
'required' => 1, 'default' => ''
),
),
),
'forgot_password' => Array (
'VirtualFields' => Array (
'ForgotLogin' => Array (
'type' => 'string',
'error_msgs' => Array (
'required' => '!lu_ferror_forgotpw_nodata!',
'unknown_username' => '!lu_ferror_unknown_username!',
'unknown_email' => '!lu_ferror_unknown_email!',
'reset_denied' => '!lu_ferror_reset_denied!',
),
'required' => 1, 'default' => ''
),
),
),
'login' => Array (
'Fields' => Array (
// make sure all fields, required by default are not required on this form
'Email' => Array ('required' => 0),
),
'VirtualFields' => Array (
'UserLogin' => Array (
'type' => 'string',
'error_msgs' => Array (
'no_permission' => '!la_no_permissions!', 'invalid_password' => '!la_invalid_password!'
),
'default' => ''
),
'UserPassword' => Array ('type' => 'string', 'default' => ''),
'UserRememberLogin' => Array ('type' => 'int', 'default' => 0),
),
),
),
'Fields' => Array (
'PortalUserId' => Array ('type' => 'int', 'not_null' => 1),
'Username' => Array ('type' => 'string', 'not_null' => 1),
'Password' => Array ('type' => 'string', 'skip_empty' => 1),
'PasswordHashingMethod' => Array ('type' => 'int'),
'FirstName' => Array ('type' => 'string', 'not_null' => 1),
'LastName' => Array ('type' => 'string', 'not_null' => 1),
'Company' => Array ('type' => 'string', 'not_null' => 1),
'Email' => Array ('type' => 'string', 'not_null' => 1),
'CreatedOn' => Array ('type' => 'int'),
'Phone' => Array ('type' => 'string', 'not_null' => 1),
'Fax' => Array ('type' => 'string', 'not_null' => 1),
'Street' => Array ('type' => 'string', 'not_null' => 1),
'Street2' => Array ('type' => 'string', 'not_null' => 1),
'City' => Array ('type' => 'string', 'not_null' => 1),
'State' => Array ('type' => 'string', 'not_null' => 1),
'Zip' => Array ('type' => 'string', 'not_null' => 1),
'Country' => Array ('type' => 'string', 'not_null' => 1),
'ResourceId' => Array ('type' => 'int', 'not_null' => 1),
'Status' => Array ('type' => 'int', 'not_null' => 1),
'Modified' => Array ('type' => 'int'),
'dob' => Array ('type' => 'int'),
'TimeZone' => Array ('type' => 'string', 'not_null' => 1),
'IPAddress' => Array ('type' => 'string', 'not_null' => 1),
'IsBanned' => Array ('type' => 'int', 'not_null' => 1),
'PwResetConfirm' => Array ('type' => 'string', 'not_null' => 1),
'PwRequestTime' => Array ('type' => 'int'),
'FrontLanguage' => Array ('type' => 'int'),
'AdminLanguage' => Array ('type' => 'int'),
'DisplayToPublic' => Array ('type' => 'string'),
'UserType' => Array ('type' => 'int', 'not_null' => 1),
'PrimaryGroupId' => Array ('type' => 'int'),
'OldStyleLogin' => Array ('type' => 'int', 'not_null' => 1),
'IPRestrictions' => Array ('type' => 'string'),
'EmailVerified' => Array ('type' => 'int', 'not_null' => 1),
'PrevEmails' => Array ('type' => 'string'),
),
'VirtualFields' => Array (
'PrimaryGroup' => Array ('type' => 'string'),
'RootPassword' => Array ('type' => 'string', 'skip_empty' => 1),
'EmailPassword' => Array ('type' => 'string'),
'FullName' => Array ('type' => 'string'),
'AltName' => Array ('type' => 'string'),
'SameImages' => Array ('type' => 'string'),
'LocalThumb' => Array ('type' => 'string'),
'ThumbPath' => Array ('type' => 'string'),
'ThumbUrl' => Array ('type' => 'string'),
'LocalImage' => Array ('type' => 'string'),
'LocalPath' => Array ('type' => 'string'),
'FullUrl' => Array ('type' => 'string'),
'SubscribeToMailing' => Array ('type' => 'int'),
),
'Grids' => Array (
// not in use
'Default' => Array (
'Icons' => Array (
0 => 'icon16_user_disabled.png',
1 => 'icon16_user.png',
2 => 'icon16_user_pending.png'
),
'Fields' => Array (
'Username' => Array ('data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_like_filter'),
'LastName' => Array ('filter_block' => 'grid_like_filter'),
'FirstName' => Array ('filter_block' => 'grid_like_filter'),
'Email' => Array ('filter_block' => 'grid_like_filter'),
'PrimaryGroup' => Array ('title' => 'la_col_PrimaryGroup', 'filter_block' => 'grid_like_filter'),
'CreatedOn' => Array ('filter_block' => 'grid_date_range_filter'),
'Modified' => Array ('filter_block' => 'grid_date_range_filter'),
'Status' => Array ('filter_block' => 'grid_options_filter', 'width' => 100, ),
'IPAddress' => Array ('filter_block' => 'grid_like_filter', 'width' => 100, 'hidden' => 1),
),
),
// used
'UserSelector' => Array (
'Icons' => Array (
0 => 'icon16_user_disabled.png',
1 => 'icon16_user.png',
2 => 'icon16_user_pending.png'
),
'Selector' => 'radio',
'Fields' => Array (
'Username' => Array ('data_block' => 'grid_login_td', 'filter_block' => 'grid_like_filter', 'width' => 150, ),
'FirstName' => Array ('filter_block' => 'grid_like_filter', 'width' => 150, ),
'LastName' => Array ('filter_block' => 'grid_like_filter', 'width' => 150, ),
'Email' => Array ('filter_block' => 'grid_like_filter', 'width' => 200, ),
'PrimaryGroup' => Array ('title' => 'la_col_PrimaryGroup', 'filter_block' => 'grid_like_filter', 'width' => 100, ),
'CreatedOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 150, ),
'Modified' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 150, ),
'Status' => Array ('filter_block' => 'grid_options_filter', 'width' => 100, ),
'IPAddress' => Array ('filter_block' => 'grid_like_filter', 'width' => 100, 'hidden' => 1),
),
),
// used
'Admins' => Array (
'Icons' => Array (
0 => 'icon16_admin_disabled.png',
1 => 'icon16_admin.png',
2 => 'icon16_admin_disabled.png',
),
'Fields' => Array (
'PortalUserId' => Array ('title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 70),
'Username' => Array ('filter_block' => 'grid_like_filter', 'width' => 150, ),
'FirstName' => Array ('filter_block' => 'grid_like_filter', 'width' => 150, ),
'LastName' => Array ('filter_block' => 'grid_like_filter', 'width' => 150, ),
'Email' => Array ('filter_block' => 'grid_like_filter', 'width' => 200, ),
'Status' => Array ('filter_block' => 'grid_options_filter', 'width' => 100, ),
),
),
// used
'RegularUsers' => Array (
'Icons' => Array (
0 => 'icon16_user_disabled.png',
1 => 'icon16_user.png',
2 => 'icon16_user_pending.png'
),
'Fields' => Array (
'PortalUserId' => Array ('title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 70),
'Username' => Array ('filter_block' => 'grid_like_filter', 'width' => 150, ),
'FirstName' => Array ('filter_block' => 'grid_like_filter', 'width' => 150, ),
'LastName' => Array ('filter_block' => 'grid_like_filter', 'width' => 150, ),
'Email' => Array ('filter_block' => 'grid_like_filter', 'width' => 200, ),
'PrimaryGroup' => Array ('title' => 'la_col_PrimaryGroup', 'filter_block' => 'grid_like_filter', 'width' => 140),
'Status' => Array ('filter_block' => 'grid_options_filter', 'width' => 100, ),
'CreatedOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 100),
'Modified' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 100),
'IPAddress' => Array ('filter_block' => 'grid_like_filter', 'width' => 100, 'hidden' => 1),
'EmailVerified' => Array ('filter_block' => 'grid_options_filter', 'width' => 100, 'hidden' => 1),
),
),
),
);
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/file_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/file_helper.php (revision 16194)
+++ branches/5.3.x/core/units/helpers/file_helper.php (revision 16195)
@@ -1,460 +1,481 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class FileHelper extends kHelper {
/**
* Puts existing item images (from sub-item) to virtual fields (in main item)
*
* @param kCatDBItem $object
* @return void
* @access public
*/
public function LoadItemFiles(&$object)
{
$max_file_count = $this->Application->ConfigValue($object->Prefix.'_MaxImageCount'); // file count equals to image count (temporary measure)
$sql = 'SELECT *
FROM '.TABLE_PREFIX.'CatalogFiles
WHERE ResourceId = '.$object->GetDBField('ResourceId').'
ORDER BY FileId ASC
LIMIT 0, '.(int)$max_file_count;
$item_files = $this->Conn->Query($sql);
$file_counter = 1;
foreach ($item_files as $item_file) {
$file_path = $item_file['FilePath'];
$object->SetDBField('File'.$file_counter, $file_path);
$object->SetOriginalField('File'.$file_counter, $file_path);
$object->SetFieldOption('File'.$file_counter, 'original_field', $item_file['FileName']);
$file_counter++;
}
}
/**
* Saves newly uploaded images to external image table
*
* @param kCatDBItem $object
* @return void
* @access public
*/
public function SaveItemFiles(&$object)
{
$table_name = $this->Application->getUnitConfig('#file')->getTableName();
$max_file_count = $object->getUnitConfig()->getFileCount();
// $max_file_count = $this->Application->ConfigValue($object->Prefix . '_MaxImageCount');
$this->CheckFolder(FULL_PATH . ITEM_FILES_PATH);
$i = 0;
while ($i < $max_file_count) {
$field = 'File'.($i + 1);
$field_options = $object->GetFieldOptions($field);
$file_path = $object->GetDBField($field);
if ($file_path) {
if (isset($field_options['original_field'])) {
$key_clause = 'FileName = '.$this->Conn->qstr($field_options['original_field']).' AND ResourceId = '.$object->GetDBField('ResourceId');
if ($object->GetDBField('Delete'.$field)) {
// if item was cloned, then new filename is in db (not in $image_src)
$sql = 'SELECT FilePath
FROM '.$table_name.'
WHERE '.$key_clause;
$file_path = $this->Conn->GetOne($sql);
if (@unlink(FULL_PATH.ITEM_FILES_PATH.$file_path)) {
$sql = 'DELETE FROM '.$table_name.'
WHERE '.$key_clause;
$this->Conn->Query($sql);
}
}
else {
// image record found -> update
$fields_hash = Array (
'FilePath' => $file_path,
);
$this->Conn->doUpdate($fields_hash, $table_name, $key_clause);
}
}
else {
// record not found -> create
$fields_hash = Array (
'ResourceId' => $object->GetDBField('ResourceId'),
'FileName' => $field,
'Status' => STATUS_ACTIVE,
'FilePath' => $file_path,
);
$this->Conn->doInsert($fields_hash, $table_name);
$field_options['original_field'] = $field;
$object->SetFieldOptions($field, $field_options);
}
}
$i++;
}
}
/**
* Preserves cloned item images/files to be rewritten with original item images/files
*
* @param Array $field_values
* @return void
* @access public
*/
public function PreserveItemFiles(&$field_values)
{
foreach ($field_values as $field_name => $field_value) {
if ( !is_array($field_value) ) {
continue;
}
if ( isset($field_value['upload']) && ($field_value['error'] == UPLOAD_ERR_NO_FILE) ) {
// this is upload field, but nothing was uploaded this time
unset($field_values[$field_name]);
}
}
}
/**
* Determines what image/file fields should be created (from post or just dummy fields for 1st upload)
*
* @param string $prefix
* @param bool $is_image
* @return void
* @access public
*/
public function createItemFiles($prefix, $is_image = false)
{
$items_info = $this->Application->GetVar($prefix);
if ($items_info) {
list (, $fields_values) = each($items_info);
$this->createUploadFields($prefix, $fields_values, $is_image);
}
else {
$this->createUploadFields($prefix, Array(), $is_image);
}
}
/**
* Dynamically creates virtual fields for item for each image/file field in submit
*
* @param string $prefix
* @param Array $fields_values
* @param bool $is_image
* @return void
* @access public
*/
public function createUploadFields($prefix, $fields_values, $is_image = false)
{
$field_options = Array ('type' => 'string', 'max_len' => 240, 'default' => '',);
if ( $is_image ) {
$field_options['formatter'] = 'kPictureFormatter';
$field_options['include_path'] = 1;
$field_options['allowed_types'] = Array ('image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png', 'image/gif', 'image/bmp');
$field_prefix = 'Image';
}
else {
$field_options['formatter'] = 'kUploadFormatter';
$field_options['upload_dir'] = ITEM_FILES_PATH;
$field_options['allowed_types'] = Array ('application/pdf', 'application/msexcel', 'application/msword', 'application/mspowerpoint');
$field_prefix = 'File';
}
$image_count = 0;
$config = $this->Application->getUnitConfig($prefix);
foreach ($fields_values as $field_name => $field_value) {
if ( preg_match('/^(' . $field_prefix . '[\d]+|Primary' . $field_prefix . ')$/', $field_name) ) {
$config->addFields($field_options, $field_name);
$config->addVirtualFields($field_options, $field_name);
$this->_createCustomFields($prefix, $field_name, $config, $is_image);
$image_count++;
}
}
if ( !$image_count ) {
// no images found in POST -> create default image fields
$image_count = $this->Application->ConfigValue($prefix . '_MaxImageCount');
if ( $is_image ) {
$created_count = 1;
$image_names = Array ('Primary' . $field_prefix => '');
while ( $created_count < $image_count ) {
$image_names[$field_prefix . $created_count] = '';
$created_count++;
}
}
else {
$created_count = 0;
$image_names = Array ();
while ( $created_count < $image_count ) {
$image_names[$field_prefix . ($created_count + 1)] = '';
$created_count++;
}
}
if ( $created_count ) {
$this->createUploadFields($prefix, $image_names, $is_image);
}
return;
}
$config->setSetting($field_prefix . 'Count', $image_count);
}
/**
* Adds ability to create more virtual fields associated with main image/file
*
* @param string $prefix
* @param string $field_name
* @param kUnitConfig $config
* @param bool $is_image
* @return void
* @access protected
*/
protected function _createCustomFields($prefix, $field_name, kUnitConfig $config, $is_image = false)
{
$config->addVirtualFields(Array ('type' => 'int', 'default' => 0), 'Delete' . $field_name);
if ( $is_image ) {
$config->addVirtualFields(Array ('type' => 'string', 'default' => ''), $field_name . 'Alt');
}
}
/**
* Downloads file to user
*
* @param string $filename
* @return void
* @access public
*/
public function DownloadFile($filename)
{
$this->Application->setContentType(kUtil::mimeContentType($filename), false);
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
header('Content-Length: ' . filesize($filename));
readfile($filename);
flush();
}
/**
* Creates folder with given $path
*
* @param string $path
* @return bool
* @access public
*/
public function CheckFolder($path)
{
$result = true;
if (!file_exists($path) || !is_dir($path)) {
$parent_path = preg_replace('#(/|\\\)[^/\\\]+(/|\\\)?$#', '', rtrim($path , '/\\'));
$result = $this->CheckFolder($parent_path);
if ($result) {
$result = mkdir($path);
if ($result) {
chmod($path, 0777);
// don't commit any files from created folder
if (file_exists(FULL_PATH . '/CVS')) {
$cvsignore = fopen($path . '/.cvsignore', 'w');
fwrite($cvsignore, '*.*');
fclose($cvsignore);
chmod($path . '/.cvsignore', 0777);
}
}
else {
trigger_error('Cannot create directory "<strong>' . $path . '</strong>"', E_USER_WARNING);
return false;
}
}
}
return $result;
}
/**
* Copies all files and directories from $source to $destination directory. Create destination directory, when missing.
*
* @param string $source
* @param string $destination
* @return bool
* @access public
*/
public function copyFolderRecursive($source, $destination)
{
if ( substr($source, -1) == DIRECTORY_SEPARATOR ) {
$source = substr($source, 0, -1);
$destination .= DIRECTORY_SEPARATOR . basename($source);
}
$iterator = new DirectoryIterator($source);
/* @var $file_info DirectoryIterator */
$result = $this->CheckFolder($destination);
foreach ($iterator as $file_info) {
if ( $file_info->isDot() ) {
continue;
}
$file = $file_info->getFilename();
if ( $file_info->isDir() ) {
$result = $this->copyFolderRecursive($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
}
else {
$result = copy($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
}
if (!$result) {
trigger_error('Cannot create file/directory "<strong>' . $destination . DIRECTORY_SEPARATOR . $file . '</strong>"', E_USER_WARNING);
break;
}
}
return $result;
}
/**
* Copies all files from $source to $destination directory. Create destination directory, when missing.
*
* @param string $source
* @param string $destination
* @return bool
* @access public
*/
public function copyFolder($source, $destination)
{
if ( substr($source, -1) == DIRECTORY_SEPARATOR ) {
$source = substr($source, 0, -1);
$destination .= DIRECTORY_SEPARATOR . basename($source);
}
$iterator = new DirectoryIterator($source);
/* @var $file_info DirectoryIterator */
$result = $this->CheckFolder($destination);
foreach ($iterator as $file_info) {
if ( $file_info->isDot() || !$file_info->isFile() ) {
continue;
}
$file = $file_info->getFilename();
$result = copy($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
if ( !$result ) {
trigger_error('Cannot create file "<strong>' . $destination . DIRECTORY_SEPARATOR . $file . '</strong>"', E_USER_WARNING);
break;
}
}
return $result;
}
/**
* Transforms given path to file into it's url, where each each component is encoded (excluding domain and protocol)
*
* @param string $path Path to file.
* @param string|null $domain Alternative domain to use in url.
*
* @return string
* @access public
*/
public function pathToUrl($path, $domain = null)
{
$path = str_replace(DIRECTORY_SEPARATOR, '/', preg_replace('/^' . preg_quote(FULL_PATH, '/') . '(.*)/', '\\1', $path, 1));
// TODO: why?
$path = implode('/', array_map('rawurlencode', explode('/', $path)));
return rtrim($this->Application->BaseURL($domain), '/') . $path;
}
/**
* Transforms given url to path to it
*
* @param string $url Url.
* @param string|null $domain Alternative domain to use in url.
*
* @return string
*/
public function urlToPath($url, $domain = null)
{
$base_url = rtrim($this->Application->BaseURL($domain), '/');
// escape replacement patterns, like "\<number>"
$full_path = preg_replace('/(\\\[\d]+)/', '\\\\\1', FULL_PATH);
$path = preg_replace('/^' . preg_quote($base_url, '/') . '(.*)/', $full_path . '\\1', $url, 1);
return str_replace('/', DIRECTORY_SEPARATOR, kUtil::unescape($path, kUtil::ESCAPE_URL));
}
/**
+ * Makes given paths DocumentRoot agnostic.
+ *
+ * @param array $paths List of file paths.
+ *
+ * @return array
+ */
+ public function makeRelative(array $paths)
+ {
+ foreach ( $paths as $index => $path ) {
+ $replaced_count = 0;
+ $relative_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $path, 1, $replaced_count);
+
+ if ( $replaced_count === 1 ) {
+ $paths[$index] = $relative_path;
+ }
+ }
+
+ return $paths;
+ }
+
+ /**
* Ensures, that new file will not overwrite any of previously created files with same name
*
* @param string $path
* @param string $name
* @param Array $forbidden_names
* @return string
*/
public function ensureUniqueFilename($path, $name, $forbidden_names = Array ())
{
$parts = pathinfo($name);
$ext = '.' . $parts['extension'];
$filename = $parts['filename'];
$path = rtrim($path, '/');
$original_checked = false;
$new_name = $filename . $ext;
if ( $parts['dirname'] != '.' ) {
$path .= '/' . ltrim($parts['dirname'], '/');
}
// make sure target folder always exists, especially for cases,
// when storage engine folder is supplied as a part of $name
$this->CheckFolder($path);
while (file_exists($path . '/' . $new_name) || in_array($path . '/' . $new_name, $forbidden_names)) {
if ( preg_match('/(.*)_([0-9]*)(' . preg_quote($ext, '/') . ')/', $new_name, $regs) ) {
$new_name = $regs[1] . '_' . ((int)$regs[2] + 1) . $regs[3];
}
elseif ( $original_checked ) {
$new_name = $filename . '_1' . $ext;
}
$original_checked = true;
}
if ( $parts['dirname'] != '.' ) {
$new_name = $parts['dirname'] . '/' . $new_name;
}
return $new_name;
}
}
Index: branches/5.3.x/core/units/helpers/image_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/image_helper.php (revision 16194)
+++ branches/5.3.x/core/units/helpers/image_helper.php (revision 16195)
@@ -1,753 +1,759 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class ImageHelper extends kHelper {
/**
* File helper reference
*
* @var FileHelper
*/
var $fileHelper = null;
public function __construct()
{
parent::__construct();
ini_set('gd.jpeg_ignore_warning', 1);
$this->fileHelper = $this->Application->recallObject('FileHelper');
}
/**
* Parses format string into array
*
* @param string $format sample format: "resize:300x500;wm:inc/wm.png|c|-20"
* @return Array sample result: Array('max_width' => 300, 'max_height' => 500, 'wm_filename' => 'inc/wm.png', 'h_margin' => 'c', 'v_margin' => -20)
*/
function parseFormat($format)
{
$res = Array ();
$format_parts = explode(';', $format);
foreach ($format_parts as $format_part) {
if (preg_match('/^resize:(\d*)x(\d*)$/', $format_part, $regs)) {
$res['max_width'] = $regs[1];
$res['max_height'] = $regs[2];
}
elseif (preg_match('/^wm:([^\|]*)\|([^\|]*)\|([^\|]*)$/', $format_part, $regs)) {
$res['wm_filename'] = FULL_PATH.THEMES_PATH.'/'.$regs[1];
$res['h_margin'] = strtolower($regs[2]);
$res['v_margin'] = strtolower($regs[3]);
}
elseif (preg_match('/^crop:([^\|]*)\|([^\|]*)$/', $format_part, $regs)) {
$res['crop_x'] = strtolower($regs[1]);
$res['crop_y'] = strtolower($regs[2]);
}
elseif ($format_part == 'img_size' || $format_part == 'img_sizes') {
$res['image_size'] = true;
}
elseif (preg_match('/^fill:(.*)$/', $format_part, $regs)) {
$res['fill'] = $regs[1];
} elseif (preg_match('/^default:(.*)$/', $format_part, $regs)) {
$res['default'] = FULL_PATH.THEMES_PATH.'/'.$regs[1];
}
elseif ( preg_match('/^filter:(.*)$/', $format_part, $regs) ) {
$format_part_params = explode('|', $regs[1]);
$res['filter_type'] = array_shift($format_part_params);
$res['filter_params'] = $format_part_params;
}
}
return $res;
}
/**
* Resized given image to required dimensions & saves resized image to "resized" subfolder in source image folder
*
* @param string $src_image full path to image (on server)
* @param mixed $max_width maximal allowed resized image width or false if no limit
* @param mixed $max_height maximal allowed resized image height or false if no limit
*
* @return string direct url to resized image
* @throws RuntimeException When image doesn't exist.
*/
function ResizeImage($src_image, $max_width, $max_height = false)
{
$image_size = false;
if (is_numeric($max_width)) {
$params['max_width'] = $max_width;
$params['max_height'] = $max_height;
}
else {
$params = $this->parseFormat($max_width);
if (array_key_exists('image_size', $params)) {
// image_size param shouldn't affect resized file name (crc part)
$image_size = $params['image_size'];
unset($params['image_size']);
}
}
if ((!$src_image || !file_exists($src_image)) && array_key_exists('default', $params) && !(defined('DBG_IMAGE_RECOVERY') && DBG_IMAGE_RECOVERY)) {
$src_image = $params['default'];
}
if ( !strlen($src_image) || !file_exists($src_image) ) {
throw new RuntimeException(sprintf('Image "%s" doesn\'t exist', $src_image));
}
if ($params['max_width'] > 0 || $params['max_height'] > 0) {
list ($params['target_width'], $params['target_height'], $needs_resize) = $this->GetImageDimensions($src_image, $params['max_width'], $params['max_height'], $params);
if (!is_numeric($params['max_width'])) {
$params['max_width'] = $params['target_width'];
}
if (!is_numeric($params['max_height'])) {
$params['max_height'] = $params['target_height'];
}
$src_path = dirname($src_image);
$transform_keys = Array ('crop_x', 'crop_y', 'fill', 'wm_filename', 'filter_type');
- if ($needs_resize || array_intersect(array_keys($params), $transform_keys)) {
- // resize required OR watermarking required -> change resulting image name !
+ // Resize required OR watermarking required -> change resulting image name !
+ if ( $needs_resize || array_intersect(array_keys($params), $transform_keys) ) {
+ // Escape replacement patterns, like "\<number>".
+ $src_path_escaped = preg_replace('/(\\\[\d]+)/', '\\\\\1', $src_path);
ksort($params);
- $src_path_escaped = preg_replace('/(\\\[\d]+)/', '\\\\\1', $src_path); // escape replacement patterns, like "\<number>"
- $dst_image = preg_replace('/^'.preg_quote($src_path, '/').'(.*)\.(.*)$/', $src_path_escaped . DIRECTORY_SEPARATOR . 'resized\\1_' . crc32(serialize($params)) . '.\\2', $src_image);
+ $params_hash = kUtil::crc32(serialize($this->fileHelper->makeRelative($params)));
+ $dst_image = preg_replace(
+ '/^' . preg_quote($src_path, '/') . '(.*)\.(.*)$/',
+ $src_path_escaped . DIRECTORY_SEPARATOR . 'resized\\1_' . $params_hash . '.\\2',
+ $src_image
+ );
$this->fileHelper->CheckFolder( dirname($dst_image) );
if (!file_exists($dst_image) || filemtime($src_image) > filemtime($dst_image)) {
// resized image not available OR should be recreated due source image change
$params['dst_image'] = $dst_image;
$image_resized = $this->ScaleImage($src_image, $params);
if (!$image_resized) {
// resize failed, because of server error
$dst_image = $src_image;
}
}
// resize/watermarking ok
$src_image = $dst_image;
}
}
if ($image_size) {
// return only image size (resized or not)
$image_info = $this->getImageInfo($src_image);
return $image_info ? $image_info[3] : '';
}
return $this->fileHelper->pathToUrl($src_image);
}
/**
* Proportionally resizes given image to destination dimensions
*
* @param string $src_image full path to source image (already existing)
* @param Array $params
* @return bool
*/
function ScaleImage($src_image, $params)
{
$image_info = $this->getImageInfo($src_image);
if (!$image_info) {
return false;
}
/*list ($params['max_width'], $params['max_height'], $resized) = $this->GetImageDimensions($src_image, $params['max_width'], $params['max_height'], $params);
if (!$resized) {
// image dimensions are smaller or equals to required dimensions
return false;
}*/
if (!$this->Application->ConfigValue('ForceImageMagickResize') && function_exists('imagecreatefromjpeg')) {
// try to resize using GD
$resize_map = Array (
'image/jpeg' => 'imagecreatefromjpeg:imagejpeg:jpg',
'image/gif' => 'imagecreatefromgif:imagegif:gif',
'image/png' => 'imagecreatefrompng:imagepng:png',
'image/bmp' => 'imagecreatefrombmp:imagejpeg:bmp',
'image/x-ms-bmp' => 'imagecreatefrombmp:imagejpeg:bmp',
);
$mime_type = $image_info['mime'];
if (!isset($resize_map[$mime_type])) {
return false;
}
list ($read_function, $write_function, $file_extension) = explode(':', $resize_map[$mime_type]);
// when source image has large dimensions (over 1MB filesize), then 16M is not enough
kUtil::setResourceLimit();
$src_image_rs = @$read_function($src_image);
if ($src_image_rs) {
$dst_image_rs = imagecreatetruecolor($params['target_width'], $params['target_height']); // resize target size
$preserve_transparency = ($file_extension == 'gif') || ($file_extension == 'png');
if ($preserve_transparency) {
// preserve transparency of PNG and GIF images
$dst_image_rs = $this->_preserveTransparency($src_image_rs, $dst_image_rs, $image_info[2]);
}
// 1. resize
imagecopyresampled($dst_image_rs, $src_image_rs, 0, 0, 0, 0, $params['target_width'], $params['target_height'], $image_info[0], $image_info[1]);
$watermark_size = 'target';
if (array_key_exists('crop_x', $params) || array_key_exists('crop_y', $params)) {
// 2.1. crop image to given size
$dst_image_rs =& $this->_cropImage($dst_image_rs, $params, $preserve_transparency ? $image_info[2] : false);
$watermark_size = 'max';
} elseif (array_key_exists('fill', $params)) {
// 2.2. fill image margins from resize with given color
$dst_image_rs =& $this->_applyFill($dst_image_rs, $params, $preserve_transparency ? $image_info[2] : false);
$watermark_size = 'max';
}
// 3. apply watermark
$dst_image_rs =& $this->_applyWatermark($dst_image_rs, $params[$watermark_size . '_width'], $params[$watermark_size . '_height'], $params);
if ($write_function == 'imagegif') {
return @$write_function($dst_image_rs, $params['dst_image']);
}
// 4. apply filter
$this->applyFilter($dst_image_rs, $params);
return @$write_function($dst_image_rs, $params['dst_image'], $write_function == 'imagepng' ? 0 : 100);
}
}
else {
// try to resize using ImageMagick
// TODO: implement crop and watermarking using imagemagick
exec('/usr/bin/convert '.$src_image.' -resize '.$params['target_width'].'x'.$params['target_height'].' '.$params['dst_image'], $shell_output, $exec_status);
return $exec_status == 0;
}
return false;
}
/**
* Preserve transparency for GIF and PNG images
*
* @param resource $src_image_rs
* @param resource $dst_image_rs
* @param int $image_type
* @return resource
*/
function _preserveTransparency($src_image_rs, $dst_image_rs, $image_type)
{
$transparent_index = imagecolortransparent($src_image_rs);
// if we have a specific transparent color
if ( $transparent_index >= 0 && $transparent_index < imagecolorstotal($src_image_rs) ) {
// get the original image's transparent color's RGB values
$transparent_color = imagecolorsforindex($src_image_rs, $transparent_index);
// allocate the same color in the new image resource
$transparent_index = imagecolorallocate($dst_image_rs, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
// completely fill the background of the new image with allocated color
imagefill($dst_image_rs, 0, 0, $transparent_index);
// set the background color for new image to transparent
imagecolortransparent($dst_image_rs, $transparent_index);
return $dst_image_rs;
}
// always make a transparent background color for PNGs that don't have one allocated already
if ( $image_type == IMAGETYPE_PNG ) {
// turn off transparency blending (temporarily)
imagealphablending($dst_image_rs, false);
// create a new transparent color for image
$transparent_color = imagecolorallocatealpha($dst_image_rs, 0, 0, 0, 127);
// completely fill the background of the new image with allocated color
imagefill($dst_image_rs, 0, 0, $transparent_color);
// restore transparency blending
imagesavealpha($dst_image_rs, true);
}
return $dst_image_rs;
}
/**
* Fills margins (if any) of resized are with given color
*
* @param resource $src_image_rs resized image resource
* @param Array $params crop parameters
* @param int|bool $image_type
* @return resource
*/
function &_applyFill(&$src_image_rs, $params, $image_type = false)
{
$x_position = round(($params['max_width'] - $params['target_width']) / 2); // center
$y_position = round(($params['max_height'] - $params['target_height']) / 2); // center
// crop resized image
$fill_image_rs = imagecreatetruecolor($params['max_width'], $params['max_height']);
if ($image_type !== false) {
$fill_image_rs = $this->_preserveTransparency($src_image_rs, $fill_image_rs, $image_type);
}
$fill = $params['fill'];
if (substr($fill, 0, 1) == '#') {
// hexdecimal color
$color = imagecolorallocate($fill_image_rs, hexdec( substr($fill, 1, 2) ), hexdec( substr($fill, 3, 2) ), hexdec( substr($fill, 5, 2) ));
}
else {
// for now we don't support color names, but we will in future
return $src_image_rs;
}
imagefill($fill_image_rs, 0, 0, $color);
imagecopy($fill_image_rs, $src_image_rs, $x_position, $y_position, 0, 0, $params['target_width'], $params['target_height']);
return $fill_image_rs;
}
/**
* Crop given image resource using given params and return resulting image resource
*
* @param resource $src_image_rs resized image resource
* @param Array $params crop parameters
* @param int|bool $image_type
* @return resource
*/
function &_cropImage(&$src_image_rs, $params, $image_type = false)
{
if ($params['crop_x'] == 'c') {
$x_position = round(($params['max_width'] - $params['target_width']) / 2); // center
}
elseif ($params['crop_x'] >= 0) {
$x_position = $params['crop_x']; // margin from left
}
else {
$x_position = $params['target_width'] - ($params['max_width'] - $params['crop_x']); // margin from right
}
if ($params['crop_y'] == 'c') {
$y_position = round(($params['max_height'] - $params['target_height']) / 2); // center
}
elseif ($params['crop_y'] >= 0) {
$y_position = $params['crop_y']; // margin from top
}
else {
$y_position = $params['target_height'] - ($params['max_height'] - $params['crop_y']); // margin from bottom
}
// crop resized image
$crop_image_rs = imagecreatetruecolor($params['max_width'], $params['max_height']);
if ($image_type !== false) {
$crop_image_rs = $this->_preserveTransparency($src_image_rs, $crop_image_rs, $image_type);
}
if (array_key_exists('fill', $params)) {
// fill image margins from resize with given color
$crop_image_rs =& $this->_applyFill($crop_image_rs, $params, $image_type);
}
imagecopy($crop_image_rs, $src_image_rs, $x_position, $y_position, 0, 0, $params['target_width'], $params['target_height']);
return $crop_image_rs;
}
/**
* Apply watermark (transparent PNG image) to given resized image resource
*
* @param resource $src_image_rs
* @param int $max_width
* @param int $max_height
* @param Array $params
* @return resource
*/
function &_applyWatermark(&$src_image_rs, $max_width, $max_height, $params)
{
$watermark_file = array_key_exists('wm_filename', $params) ? $params['wm_filename'] : false;
if (!$watermark_file || !file_exists($watermark_file)) {
// no watermark required, or provided watermark image is missing
return $src_image_rs;
}
$watermark_img_rs = imagecreatefrompng($watermark_file);
list ($watermark_width, $watermark_height) = $this->getImageInfo($watermark_file);
imagealphablending($src_image_rs, true);
if ($params['h_margin'] == 'c') {
$x_position = round($max_width / 2 - $watermark_width / 2); // center
}
elseif ($params['h_margin'] >= 0) {
$x_position = $params['h_margin']; // margin from left
}
else {
$x_position = $max_width - ($watermark_width - $params['h_margin']); // margin from right
}
if ($params['v_margin'] == 'c') {
$y_position = round($max_height / 2 - $watermark_height / 2); // center
}
elseif ($params['v_margin'] >= 0) {
$y_position = $params['v_margin']; // margin from top
}
else {
$y_position = $max_height - ($watermark_height - $params['v_margin']); // margin from bottom
}
imagecopy($src_image_rs, $watermark_img_rs, $x_position, $y_position, 0, 0, $watermark_width, $watermark_height);
return $src_image_rs;
}
/**
* Applies filter to an image.
*
* @param resource $src_image_rs Source image.
* @param array $params Parameters.
*
* @return boolean
* @access protected
* @throws InvalidArgumentException When unknown filter type given.
* @link http://php.net/manual/en/function.imagefilter.php
*/
protected function applyFilter(&$src_image_rs, array $params)
{
if ( !array_key_exists('filter_type', $params) ) {
return true;
}
$filter_type = strtoupper($params['filter_type']);
$filter_params = (array)$params['filter_params'];
if ( !defined('IMG_FILTER_' . $filter_type) ) {
throw new InvalidArgumentException(sprintf('Unknown filter type "%s"', $filter_type));
}
array_unshift($filter_params, constant('IMG_FILTER_' . $filter_type));
array_unshift($filter_params, $src_image_rs);
return call_user_func_array('imagefilter', $filter_params);
}
/**
* Returns destination image size without actual resizing (useful for <img .../> HTML tag)
*
* @param string $src_image full path to source image (already existing)
* @param int $dst_width destination image width (in pixels)
* @param int $dst_height destination image height (in pixels)
* @param Array $params
* @return Array resized image dimensions (0 - width, 1 - height)
*/
function GetImageDimensions($src_image, $dst_width, $dst_height, $params)
{
$image_info = $this->getImageInfo($src_image);
if (!$image_info) {
return false;
}
$orig_width = $image_info[0];
$orig_height = $image_info[1];
$too_large = is_numeric($dst_width) ? ($orig_width > $dst_width) : false;
$too_large = $too_large || (is_numeric($dst_height) ? ($orig_height > $dst_height) : false);
if ($too_large) {
$width_ratio = $dst_width ? $dst_width / $orig_width : 1;
$height_ratio = $dst_height ? $dst_height / $orig_height : 1;
if (array_key_exists('crop_x', $params) || array_key_exists('crop_y', $params)) {
// resize by smallest inverted radio
$resize_by = $this->_getCropImageMinRatio($image_info, $dst_width, $dst_height);
if ($resize_by === false) {
return Array ($orig_width, $orig_height, false);
}
$ratio = $resize_by == 'width' ? $width_ratio : $height_ratio;
}
else {
$ratio = min($width_ratio, $height_ratio);
}
$width = ceil($orig_width * $ratio);
$height = ceil($orig_height * $ratio);
}
else {
$width = $orig_width;
$height = $orig_height;
}
return Array ($width, $height, $too_large);
}
/**
* Returns ratio type with smaller relation of original size to target size
*
* @param Array $image_info image information from "ImageHelper::getImageInfo"
* @param int $dst_width destination image width (in pixels)
* @param int $dst_height destination image height (in pixels)
* @return Array
*/
function _getCropImageMinRatio($image_info, $dst_width, $dst_height)
{
$width_ratio = $dst_width ? $image_info[0] / $dst_width : 1;
$height_ratio = $dst_height ? $image_info[1] / $dst_height : 1;
$minimal_ratio = min($width_ratio, $height_ratio);
if ($minimal_ratio < 1) {
// ratio is less then 1, image will be enlarged -> don't allow that
return false;
}
return $width_ratio < $height_ratio ? 'width' : 'height';
}
/**
* Returns image dimensions + checks if given file is existing image
*
* @param string $src_image full path to source image (already existing)
* @return mixed
*/
function getImageInfo($src_image)
{
if (!file_exists($src_image)) {
return false;
}
$image_info = @getimagesize($src_image);
if (!$image_info) {
trigger_error('Image <b>'.$src_image.'</b> <span class="debug_error">missing or invalid</span>', E_USER_WARNING);
return false;
}
return $image_info;
}
/**
* Returns maximal image size (width & height) among fields specified
*
* @param kDBItem $object
* @param string $fields
* @param string $format any format, that returns full url (e.g. files_resized:WxH, resize:WxH, full_url, full_urls)
* @return string
*/
function MaxImageSize(&$object, $fields, $format = null)
{
static $cached_sizes = Array ();
$cache_key = $object->getPrefixSpecial().'_'.$object->GetID();
if (!isset($cached_sizes[$cache_key])) {
$images = Array ();
$fields = explode(',', $fields);
foreach ($fields as $field) {
$image_data = $object->GetField($field, $format);
if (!$image_data) {
continue;
}
$images = array_merge($images, explode('|', $image_data));
}
$max_width = 0;
$max_height = 0;
$base_url = rtrim($this->Application->BaseURL(), '/');
foreach ($images as $image_url) {
$image_path = preg_replace('/^'.preg_quote($base_url, '/').'(.*)/', FULL_PATH.'\\1', $image_url);
$image_info = $this->getImageInfo($image_path);
$max_width = max($max_width, $image_info[0]);
$max_height = max($max_height, $image_info[1]);
}
$cached_sizes[$cache_key] = Array ($max_width, $max_height);
}
return $cached_sizes[$cache_key];
}
/**
* Puts existing item images (from sub-item) to virtual fields (in main item)
*
* @param kCatDBItem|kDBItem $object
*/
function LoadItemImages(&$object)
{
if (!$this->_canUseImages($object)) {
return ;
}
$max_image_count = $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
$sql = 'SELECT *
FROM '.TABLE_PREFIX.'CatalogImages
WHERE ResourceId = '.$object->GetDBField('ResourceId').'
ORDER BY Priority DESC
LIMIT 0, ' . (int)$max_image_count;
$item_images = $this->Conn->Query($sql);
$image_counter = 1;
foreach ($item_images as $item_image) {
$image_path = $item_image['ThumbPath'];
if ($item_image['DefaultImg'] == 1 || $item_image['Name'] == 'main') {
// process primary image separately
if ( $object->isField('PrimaryImage') ) {
$object->SetDBField('PrimaryImage', $image_path);
$object->SetOriginalField('PrimaryImage', $image_path);
$object->SetFieldOption('PrimaryImage', 'original_field', $item_image['Name']);
$this->_loadCustomFields($object, $item_image, 0);
}
continue;
}
if (abs($item_image['Priority'])) {
// use Priority as image counter, when specified
$image_counter = abs($item_image['Priority']);
}
if ( $object->isField('Image'.$image_counter) ) {
$object->SetDBField('Image'.$image_counter, $image_path);
$object->SetOriginalField('Image'.$image_counter, $image_path);
$object->SetFieldOption('Image'.$image_counter, 'original_field', $item_image['Name']);
$this->_loadCustomFields($object, $item_image, $image_counter);
}
$image_counter++;
}
}
/**
* Saves newly uploaded images to external image table
*
* @param kCatDBItem|kDBItem $object
*/
function SaveItemImages(&$object)
{
if (!$this->_canUseImages($object)) {
return ;
}
$table_name = $this->Application->getUnitConfig('img')->getTableName();
$max_image_count = $object->getUnitConfig()->getImageCount(); // $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
$i = 0;
while ($i < $max_image_count) {
$field = $i ? 'Image'.$i : 'PrimaryImage';
$field_options = $object->GetFieldOptions($field);
$image_src = $object->GetDBField($field);
if ($image_src) {
if (isset($field_options['original_field'])) {
$key_clause = 'Name = '.$this->Conn->qstr($field_options['original_field']).' AND ResourceId = '.$object->GetDBField('ResourceId');
if ($object->GetDBField('Delete'.$field)) {
// if item was cloned, then new filename is in db (not in $image_src)
$sql = 'SELECT ThumbPath
FROM '.$table_name.'
WHERE '.$key_clause;
$image_src = $this->Conn->GetOne($sql);
if (@unlink(FULL_PATH.$image_src)) {
$sql = 'DELETE FROM '.$table_name.'
WHERE '.$key_clause;
$this->Conn->Query($sql);
}
}
else {
// image record found -> update
$fields_hash = Array (
'ThumbPath' => $image_src,
);
$this->_saveCustomFields($object, $fields_hash, $i);
$this->Conn->doUpdate($fields_hash, $table_name, $key_clause);
}
}
else {
// image record not found -> create
$fields_hash = Array (
'ResourceId' => $object->GetDBField('ResourceId'),
'Name' => $field,
'AltName' => $field,
'Enabled' => STATUS_ACTIVE,
'DefaultImg' => $i ? 0 : 1, // first image is primary, others not primary
'ThumbPath' => $image_src,
'Priority' => ($i == 0)? 0 : $i * (-1),
);
$this->_saveCustomFields($object, $fields_hash, $i);
$this->Conn->doInsert($fields_hash, $table_name);
$field_options['original_field'] = $field;
$object->SetFieldOptions($field, $field_options);
}
}
$i++;
}
}
/**
* Adds ability to load custom fields along with main image field
*
* @param kCatDBItem|kDBItem $object
* @param Array $fields_hash
* @param int $counter 0 - primary image, other number - additional image number
*/
function _loadCustomFields(&$object, $fields_hash, $counter)
{
$field_name = $counter ? 'Image' . $counter . 'Alt' : 'PrimaryImageAlt';
$object->SetDBField($field_name, (string)$fields_hash['AltName']);
}
/**
* Adds ability to save custom field along with main image save
*
* @param kCatDBItem|kDBItem $object
* @param Array $fields_hash
* @param int $counter 0 - primary image, other number - additional image number
*/
function _saveCustomFields(&$object, &$fields_hash, $counter)
{
$field_name = $counter ? 'Image' . $counter . 'Alt' : 'PrimaryImageAlt';
$fields_hash['AltName'] = (string)$object->GetDBField($field_name);
}
/**
* Checks, that item can use image upload capabilities
*
* @param kCatDBItem|kDBItem $object
* @return bool
*/
function _canUseImages(&$object)
{
$prefix = $object->Prefix == 'p' ? 'img' : $object->Prefix . '-img';
return $this->Application->prefixRegistred($prefix);
}
}
Index: branches/5.3.x/core/units/helpers/country_states_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/country_states_helper.php (revision 16194)
+++ branches/5.3.x/core/units/helpers/country_states_helper.php (revision 16195)
@@ -1,246 +1,245 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class kCountryStatesHelper extends kHelper
{
/**
* ID of current language.
*
* @var integer
*/
protected $currentLanguage;
/**
* ID of primary language.
*
* @var integer
*/
protected $primaryLanguage;
/**
* Populates language ids that are used.
*/
public function __construct()
{
parent::__construct();
// Don't use GetVar('m_lang') since it's always equals to default language on editing form in admin.
$this->currentLanguage = $this->Application->Phrases->LanguageId;
$this->primaryLanguage = $this->Application->GetDefaultLanguageId();
}
/**
* Returns countries, that have states
*
* @return Array
*/
function getCountriesWithStates()
{
static $cache = null;
if ( !isset($cache) ) {
$table_name = $this->Application->getUnitConfig('country-state')->getTableName();
$sql = 'SELECT DISTINCT cname.IsoCode, cid.StateCountryId
FROM ' . $table_name . ' cid
JOIN ' . $table_name . ' cname ON cname.CountryStateId = cid.StateCountryId
WHERE cid.StateCountryId IS NOT NULL';
$cache = $this->Conn->GetCol($sql, 'StateCountryId');
}
return $cache;
}
/**
* Checks, that country with given 3symbol ISO code has states
*
* @param string $country_code
* @return bool
*/
function CountryHasStates($country_code)
{
return $country_code ? in_array($country_code, $this->getCountriesWithStates()) : false;
}
/**
* Prepares states dropdown based on country selected
*
* @param kEvent $event
* @param string $state_field
* @param string $country_field
*/
function PopulateStates($event, $state_field, $country_field)
{
$object = $event->getObject();
/* @var $object kDBItem */
$country_iso = $object->GetDBField($country_field);
if ( !$country_iso ) {
return;
}
$field_options = $object->GetFieldOptions($state_field);
$field_options['options'] = $this->getStates($country_iso);
- $field_options['options'][''] = '';
$object->SetFieldOptions($state_field, $field_options, $object->isVirtualField($state_field));
}
/**
* Returns list of given country states
*
* @param string $country_iso
* @return Array
*/
public function getStates($country_iso)
{
$country_id = $this->getCountryStateId($country_iso, DESTINATION_TYPE_COUNTRY);
if ( !$country_id ) {
return Array ();
}
$cache_key = 'country_states[%CountryStateSerial%]';
$cache_key .= ':PL=' . $this->primaryLanguage . ':CL=' . $this->currentLanguage . ':ISO=' . $country_iso;
$states = $this->Application->getCache($cache_key);
if ( $states === false ) {
$sql = 'SELECT IF(l' . $this->currentLanguage . '_Name = "", l' . $this->primaryLanguage . '_Name, l' . $this->currentLanguage . '_Name) AS Name, IsoCode
FROM ' . $this->Application->getUnitConfig('country-state')->getTableName() . '
WHERE (Type = ' . DESTINATION_TYPE_STATE . ') AND (StateCountryId = ' . $country_id . ')
ORDER BY Name ASC';
$states = $this->Conn->GetCol($sql, 'IsoCode');
$this->Application->setCache($cache_key, $states);
}
return $states;
}
/**
* Returns valid state ISO code for state name and country code passed
*
* @param string $state_name
* @param string $country_iso
* @return string
*/
function getStateIso($state_name, $country_iso)
{
if ( !$this->CountryHasStates($country_iso) ) {
return $state_name;
}
$table_name = $this->Application->getUnitConfig('country-state')->getTableName();
$country_id = $this->getCountryStateId($country_iso, DESTINATION_TYPE_COUNTRY);
$sql = 'SELECT IsoCode
FROM ' . $table_name . '
WHERE (Type = ' . DESTINATION_TYPE_STATE . ') AND (StateCountryId = %1$s) AND
(
(IsoCode = %2$s) OR (UPPER(l%3$s_Name) = %2$s) OR (UPPER(l%4$s_Name) = %2$s)
)';
$state_name = trim(mb_strtoupper($state_name));
$sql = sprintf($sql, $country_id, $this->Conn->qstr($state_name), $this->currentLanguage, $this->primaryLanguage);
return $this->Conn->GetOne($sql);
}
/**
* Checks, that entered state matches entered country
*
* @param kEvent $event
* @param string $state_field
* @param string $country_field
* @param bool $auto_required
* @return void
*/
function CheckStateField($event, $state_field, $country_field, $auto_required = true)
{
$object = $event->getObject();
/* @var $object kDBItem */
$country_iso = $object->GetDBField($country_field);
if ( $auto_required ) {
$object->setRequired($state_field, $this->CountryHasStates($country_iso));
}
$state = $object->GetDBField($state_field);
if ( $country_iso && $state ) {
$state_iso = $this->getStateIso($state, $country_iso);
if ( $state_iso !== false ) {
// replace state name with it's ISO code
$object->SetDBField($state_field, $state_iso);
}
else {
// state not found by name -> report error
$object->SetError($state_field, 'invalid_state', 'la_invalid_state');
}
}
}
/**
* Returns country/state id based on given iso code and it's type
*
* @param string $iso_code
* @param int $type
* @return int
*/
function getCountryStateId($iso_code, $type)
{
$config = $this->Application->getUnitConfig('country-state');
$cache_key = 'country_state_id[%CountryStateSerial%]:ISO=' . $iso_code . ';Type=' . $type;
$id = $this->Application->getCache($cache_key);
if ( $id === false ) {
$sql = 'SELECT ' . $config->getIDField() . '
FROM ' . $config->getTableName() . '
WHERE (Type = ' . $type . ') AND (IsoCode = ' . $this->Conn->qstr($iso_code) . ')';
$id = (int)$this->Conn->GetOne($sql);
$this->Application->setCache($cache_key, $id);
}
return $id;
}
/**
* Returns 3 symbols ISO code from 2 symbols ISO code or otherwise, when $from_short parameter is used
*
* @param string $iso_code
* @param bool $from_short
* @return string
*/
function getCountryIso($iso_code, $from_short = false)
{
if ($from_short) {
$sql = 'SELECT IsoCode
FROM ' . TABLE_PREFIX . 'CountryStates
WHERE ShortIsoCode = ' . $this->Conn->qstr($iso_code) . ' AND `Type` = ' . DESTINATION_TYPE_COUNTRY;
}
else {
$sql = 'SELECT ShortIsoCode
FROM ' . TABLE_PREFIX . 'CountryStates
WHERE IsoCode = ' . $this->Conn->qstr($iso_code) . ' AND `Type` = ' . DESTINATION_TYPE_COUNTRY;
}
return $this->Conn->GetOne($sql);
}
}
Index: branches/5.3.x/core/units/helpers/minifiers/minify_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/minifiers/minify_helper.php (revision 16194)
+++ branches/5.3.x/core/units/helpers/minifiers/minify_helper.php (revision 16195)
@@ -1,315 +1,315 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2010 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class MinifyHelper extends kHelper {
/**
* Debug mode mark
*
* @var bool
*/
var $debugMode = false;
/**
* Folder, that contains produced CSS/JS files
*
* @var string
* @access protected
*/
protected $resourceFolder = '';
public function __construct()
{
parent::__construct();
$this->debugMode = $this->Application->isDebugMode(false);
$this->resourceFolder = WRITEABLE . '/cache';
}
/**
* When used as non-block tag, then compress given files and return url to result
*
* @param Array $params
* @return string
* @access public
*/
public function CompressScriptTag($params)
{
// put to queue
if ( array_key_exists('to', $params) ) {
$files = $this->Application->GetVar($params['to'], '');
$this->Application->SetVar($params['to'], $files . '|' . $params['files']);
return '';
}
if ( array_key_exists('from', $params) ) {
// get from queue
$files = $this->Application->GetVar($params['from']);
}
else {
// get from tag
$files = $params['files'];
}
$files = $this->_getTemplatePaths( array_map('trim', explode('|', $files)) );
if ( !$files ) {
trigger_error('No files specified for compression.', E_USER_NOTICE);
return '';
}
$extension = pathinfo($files[0], PATHINFO_EXTENSION);
$save_as = isset($params['save_as']) ? $params['save_as'] : false;
$dst_file = $this->resourceFolder . DIRECTORY_SEPARATOR . ($this->debugMode ? 'd' : 'c') . '_';
+ /** @var FileHelper $file_helper */
+ $file_helper = $this->Application->recallObject('FileHelper');
+
if ( $save_as ) {
$dst_file .= $save_as . ( strpos($save_as, '.') === false ? '.' . $extension : '' );
}
else {
- $dst_file .= $this->_getHash($files) . '.' . $extension;
+ $dst_file .= $this->_getHash($file_helper->makeRelative($files)) . '.' . $extension;
}
$was_compressed = file_exists($dst_file);
if ( !$was_compressed || ($this->debugMode && filemtime($dst_file) < $this->_getMaxFileDate($files)) ) {
$string = '';
$path_length = strlen(FULL_PATH) + 1;
foreach ($files as $file) {
if ( !file_exists($file) ) {
continue;
}
// add filename before for easier debugging
if ( $this->debugMode ) {
$string .= '/* === File: ' . substr($file, $path_length) . ' === */' . "\n";
$string .= '/* ' . str_repeat('=', strlen(substr($file, $path_length)) + 14) . ' */' . "\n\n";
}
// add file content
$string .= file_get_contents($file) . "\n\n";
}
// replace templates base
if ( isset($params['templates_base']) ) {
$templates_base = $params['templates_base'];
}
else {
$templates_base = $this->Application->ProcessParsedTag('m', 'TemplatesBase', Array ());
}
$templates_base = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', BASE_PATH . '/', $templates_base);
$string = str_replace('@templates_base@', rtrim($templates_base, '/'), $string);
if ( !$this->debugMode ) {
// don't compress merged js/css file in debug mode to allow js/css debugging
$this->compressString($string, $extension);
}
// save compressed file
file_put_contents($dst_file, $string);
}
- $file_helper = $this->Application->recallObject('FileHelper');
- /* @var $file_helper FileHelper */
-
return $file_helper->pathToUrl($dst_file) . '?ts=' . date('Y-m-d_H:i:s', filemtime($dst_file));
}
/**
* Returns maximal modification date across given files
*
* @param Array $files
* @return int
* @access protected
*/
protected function _getMaxFileDate($files)
{
$ret = 0;
foreach ($files as $file) {
if ( file_exists($file) ) {
$ret = max($ret, filemtime($file));
}
}
return $ret;
}
/**
* Returns hash string based on given files
*
* @param Array $files
* @return int
* @access protected
*/
protected function _getHash($files)
{
$hash = $files;
if ($this->Application->isAdmin) {
array_unshift($hash, 'A:1');
}
else {
array_unshift($hash, 'A:0;T:' . $this->Application->GetVar('m_theme'));
}
- return crc32( implode('|', $hash) );
+ return kUtil::crc32(implode('|', $hash));
}
/**
* Deletes compression info file
*
* @todo also delete all listed there files
* @access public
*/
public function delete()
{
$iterator = new DirectoryIterator($this->resourceFolder);
/* @var $file_info DirectoryIterator */
foreach ($iterator as $file_info) {
if ( !$file_info->isDir() && preg_match('/^(c|d)_.*.(css|js)$/', $file_info->getFilename()) ) {
unlink( $file_info->getPathname() );
}
}
}
/**
* Compress $string based on $extension
*
* @param string $string
* @param string $extension
* @return void
* @access protected
*/
public function compressString(&$string, $extension)
{
$compression_engine = kUtil::getSystemConfig()->get('CompressionEngine');
if ( !$compression_engine ) {
// compression method not specified - use none
return;
}
switch ( $compression_engine ) {
case 'yui':
$this->compressViaJava($string, $extension);
break;
case 'php':
$this->compressViaPHP($string, $extension);
break;
}
}
/**
* Compresses string using YUI compressor (uses Java)
*
* @param string $string
* @param string $extension
* @return void
* @access protected
*/
protected function compressViaJava(&$string, $extension)
{
$tmp_file = tempnam('/tmp', 'to_compress_');
file_put_contents($tmp_file, $string);
$command = 'java -jar ' . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'yuicompressor-2.4.2.jar --type ' . $extension . ' --charset utf-8 ' . $tmp_file;
$string = shell_exec($command);
unlink($tmp_file);
}
/**
* Compresses string using PHP compressor
*
* @param string $string
* @param string $extension
* @return void
* @access protected
*/
protected function compressViaPHP(&$string, $extension)
{
$minifier = $this->Application->makeClass($extension == 'js' ? 'JsMinifyHelper' : 'CssMinifyHelper');
/* @var $minifier JsMinifyHelper */
$string = $minifier->minify($string);
}
/**
* Get full paths on disk for each of given templates
*
* @param Array $templates
* @return Array
* @access protected
*/
protected function _getTemplatePaths($templates)
{
$ret = Array ();
$reg_exp = '/^' . preg_quote($this->Application->BaseURL(), '/') . '(.*)/';
foreach ($templates as $template) {
if ( !$template ) {
continue;
}
if ( preg_match($reg_exp, $template, $regs) ) {
// full url (from current domain) to a file
$ret[] = FULL_PATH . '/' . $regs[1];
}
elseif ( strpos($template, '{module_path}') !== false ) {
$ret = array_merge($ret, $this->_moduleInclude($template));
}
else {
$ret[] = $this->Application->TemplatesCache->GetRealFilename($template);
}
}
return $ret;
}
/**
*
* @param string $template
* @return Array
* @access protected
*/
protected function _moduleInclude($template)
{
$ret = $included = Array ();
foreach ($this->Application->ModuleInfo as $module_name => $module_data) {
if ( $module_name == 'In-Portal' ) {
continue;
}
$module_prefix = $this->Application->isAdmin ? mb_strtolower($module_name) . '/' : $module_data['TemplatePath'];
if ( in_array($module_prefix, $included) ) {
continue;
}
$ret[] = $this->Application->TemplatesCache->GetRealFilename(str_replace('{module_path}', $module_prefix, $template));
$included[] = $module_prefix;
}
return $ret;
}
- }
\ No newline at end of file
+ }
Index: branches/5.3.x/core/units/forms/form_submissions/form_submissions_config.php
===================================================================
--- branches/5.3.x/core/units/forms/form_submissions/form_submissions_config.php (revision 16194)
+++ branches/5.3.x/core/units/forms/form_submissions/form_submissions_config.php (revision 16195)
@@ -1,166 +1,166 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
$config = Array (
'Prefix' => 'formsubs',
'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'),
'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'),
'EventHandlerClass' => Array ('class' => 'FormSubmissionsEventHandler', 'file' => 'form_submissions_eh.php', 'build_event' => 'OnBuild'),
'TagProcessorClass' => Array ('class' => 'FormSubmissionTagProcessor', 'file' => 'form_submission_tp.php', 'build_event' => 'OnBuild'),
'AutoLoad' => true,
'QueryString' => Array (
1 => 'id',
2 => 'Page',
3 => 'PerPage',
4 => 'event',
5 => 'mode',
),
'Hooks' => Array (
Array (
- 'Mode' => hAFTER,
+ 'Mode' => hBEFORE,
'Conditional' => false,
'HookToPrefix' => '',
'HookToSpecial' => '*',
'HookToEvent' => Array ('OnAfterConfigRead'),
'DoPrefix' => '',
'DoSpecial' => '',
'DoEvent' => 'OnBuildFormFields',
),
// Captcha processing
Array (
- 'Mode' => hAFTER,
+ 'Mode' => hBEFORE,
'Conditional' => false,
'HookToPrefix' => '',
'HookToSpecial' => '*',
'HookToEvent' => Array ('OnAfterConfigRead'),
'DoPrefix' => 'captcha',
'DoSpecial' => '*',
'DoEvent' => 'OnPrepareCaptcha',
),
),
'TitlePresets' => Array (
'default' => Array (
'new_status_labels' => Array ('form' => '!la_title_Adding_Form!'),
'edit_status_labels' => Array ('form' => '!la_title_Editing_Form!'),
),
'formsubs_list' => Array (
'prefixes' => Array ('form', 'formsubs_List'),
'format' => "!la_title_FormSubmissions! '#form_titlefield#'",
'toolbar_buttons' => Array ('edit', 'delete', 'view', 'dbl-click'),
),
'formsubs_view' => Array (
'prefixes' => Array ('formsubs'),
'format' => "!la_title_ViewingFormSubmission!",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'),
),
'submission_edit_logs' => Array (
'prefixes' => Array ('formsubs', 'submission-log_List'),
'format' => "!la_title_ViewingFormSubmission! - !la_title_Messages! (#submission-log_recordcount#)"
),
'submission_log_edit' => Array (
'new_status_labels' => Array ('submission-log' => '!la_title_NewReply!'),
'edit_status_labels' => Array ('submission-log' => '!la_title_ViewingReply!'),
'prefixes' => Array ('submission-log'), 'format' => "!la_title_ViewingFormSubmission! - #submission-log_status#"
),
),
'EditTabPresets' => Array (
'Default' => Array (
Array ('title' => 'la_tab_General', 't' => 'submissions/submission_view', 'priority' => 1),
Array ('title' => 'la_tab_Messages', 't' => 'submissions/submission_edit_logs', 'priority' => 2),
),
),
'PermSection' => Array ('main' => 'in-portal:submissions'),
'IDField' => 'FormSubmissionId',
// 'TitleField' => 'Name',
'StatusField' => Array ('LogStatus'),
'TableName' => TABLE_PREFIX.'FormSubmissions',
'ListSQLs' => Array (
'' => ' SELECT %1$s.* %2$s
FROM %1$s',
),
/*'ForeignKey' => 'FormId',
'ParentTableKey' => 'FormId',
'ParentPrefix' => 'form',
'AutoDelete' => true,
'AutoClone' => true,*/
'SubItems' => Array ('submission-log', 'draft'),
'ListSortings' => Array (
'' => Array (
'Sorting' => Array ('SubmissionTime' => 'desc'),
)
),
'Fields' => Array (
'FormSubmissionId' => Array ('type' => 'int', 'not_null' => 1,'default' => 0),
'FormId' => Array ('type' => 'int', 'not_null' => '1', 'default' => 0),
'SubmissionTime' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => '#NOW#'),
'IPAddress' => Array ('type' => 'string', 'max_len' => 15, 'not_null' => 1, 'default' => ''),
'ReferrerURL' => Array ('type' => 'string', 'default' => NULL),
'LogStatus' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_Replied', 2 => 'la_opt_NotReplied', 3 => 'la_opt_NewEmail', 4 => 'la_opt_Bounce'), 'use_phrases' => 1,
'not_null' => 1, 'required' => 1, 'default' => 2
),
'LastUpdatedOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL),
'Notes' => Array ('type' => 'string', 'default' => NULL),
'MessageId' => Array ('type' => 'string', 'max_len' => 255, 'default' => NULL),
),
'VirtualFields' => Array (
'MergeToSubmission' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (),
'default' => NULL
),
'IsMergeToSubmission' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1,
'default' => 0
),
),
'Grids' => Array (
'Default' => Array (
'Icons' => Array ('default' => 'icon16_item.png', 1 => 'icon16_replied.gif', 2 => 'icon16_not_replied.gif', 3 => 'icon16_new_email.gif', 4 => 'icon16_bounce.gif'),
'Fields' => Array (
'FormSubmissionId' => Array ('title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 60 ),
'SubmissionTime' => Array ('title' => 'la_prompt_SumbissionTime', 'filter_block' => 'grid_date_range_filter', 'width' => 145 ),
'IPAddress' => Array ('filter_block' => 'grid_like_filter', 'width' => 100 ),
'ReferrerURL' => Array ('filter_block' => 'grid_like_filter', 'first_chars' => 100, 'width' => 200 ),
'LogStatus' => Array ('title' => 'column:la_fld_Status', 'filter_block' => 'grid_options_filter', 'width' => 100 ),
'LastUpdatedOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145 ),
),
),
),
);
\ No newline at end of file
Index: branches/5.3.x/core/units/forms/forms/forms_config.php
===================================================================
--- branches/5.3.x/core/units/forms/forms/forms_config.php (revision 16194)
+++ branches/5.3.x/core/units/forms/forms/forms_config.php (revision 16195)
@@ -1,227 +1,227 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
$config = Array (
'Prefix' => 'form',
'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'),
'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'),
'EventHandlerClass' => Array ('class' => 'FormsEventHandler', 'file' => 'forms_eh.php', 'build_event' => 'OnBuild'),
'TagProcessorClass' => Array ('class' => 'FormsTagProcessor', 'file' => 'forms_tp.php', 'build_event' => 'OnBuild'),
'AutoLoad' => true,
'QueryString' => Array (
1 => 'id',
2 => 'Page',
3 => 'PerPage',
4 => 'event',
5 => 'mode',
),
'ScheduledTasks' => Array (
'check_submission_repies' => Array ('EventName' => 'OnProcessReplies', 'RunSchedule' => '0 * * * *'),
'check_bounced_submission_repies' => Array ('EventName' => 'OnProcessBouncedReplies', 'RunSchedule' => '0 */5 * * *'),
),
'Hooks' => Array (
Array (
- 'Mode' => hAFTER,
+ 'Mode' => hBEFORE,
'Conditional' => false,
'HookToPrefix' => 'form', //self
'HookToSpecial' => '*',
'HookToEvent' => Array ('OnAfterConfigRead'),
'DoPrefix' => '',
'DoSpecial' => '',
'DoEvent' => 'OnCreateSubmissionNodes',
),
),
'TableName' => TABLE_PREFIX.'Forms',
'IDField' => 'FormId',
'TitleField' => 'Title',
'PermSection' => Array ('main' => 'in-portal:forms'),
'Sections' => Array (
'in-portal:forms' => Array (
'parent' => 'in-portal:site',
'icon' => 'form',
'use_parent_header' => 1,
'label' => 'la_tab_CMSForms', //'la_tab_FormsConfig',
'url' => Array ('t' => 'forms/forms_list', 'pass' => 'm'), // set "container" parameter (in section definition, not url) to true when form editing should be disabled, but submissions still visible
'permissions' => Array ('view', 'add', 'edit', 'delete'),
'priority' => 6,
// 'show_mode' => smSUPER_ADMIN,
'type' => stTREE,
),
),
'TitlePresets' => Array (
'default' => Array (
'new_status_labels' => Array ('form' => '!la_title_Adding_Form!'),
'edit_status_labels' => Array ('form' => '!la_title_Editing_Form!'),
'new_titlefield' => Array ('form' => ''),
),
'forms_list' => Array (
'prefixes' => Array ('form_List'),
'format' => "!la_title_Forms!",
'toolbar_buttons' => Array ('new_item', 'edit', 'delete', 'view', 'dbl-click'),
),
'forms_edit' => Array (
'prefixes' => Array ('form'),
'format' => "#form_status# '#form_titlefield#' - !la_title_General!",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'),
),
'forms_edit_fields' => Array (
'prefixes' => Array ('form'),
'format' => "#form_status# '#form_titlefield#' - !la_title_Fields!",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next', 'new_item', 'edit', 'delete', 'move_up', 'move_down', 'view', 'dbl-click'),
),
'form_edit_emails' => Array (
'prefixes' => Array ('form'),
'format' => "#form_status# '#form_titlefield#' - !la_title_EmailCommunication!",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'),
),
'form_field_edit' => Array (
'prefixes' => Array ('form', 'formflds'),
'new_status_labels' => Array ('formflds'=>"!la_title_Adding_FormField!"),
'edit_status_labels' => Array ('formflds' => '!la_title_Editing_FormField!'),
'new_titlefield' => Array ('formflds' => ''),
'format' => "#form_status# '#form_titlefield#' - #formflds_status# '#formflds_titlefield#'",
'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'),
),
'tree_submissions' => Array (
'format' => "!la_title_FormSubmissions!",
),
),
'EditTabPresets' => Array (
'Default' => Array (
'general' => Array ('title' => 'la_tab_General', 't' => 'forms/forms_edit', 'priority' => 1),
'fields' => Array ('title' => 'la_tab_Fields', 't' => 'forms/forms_edit_fields', 'priority' => 2),
'emails' => Array ('title' => 'la_tab_EmailCommunication', 't' => 'forms/form_edit_emails', 'priority' => 3),
),
),
'ListSQLs' => Array (
'' => ' SELECT %1$s.* %2$s
FROM %1$s',
),
'SubItems' => Array ('formflds'),
'ListSortings' => Array (
'' => Array (
'Sorting' => Array ('Title' => 'asc'),
)
),
'Fields' => Array (
'FormId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0, 'filter_type' => 'equals'),
'Title' => Array ('type' => 'string', 'not_null' => 1, 'default' => '', 'required' => 1),
'Description' => Array ('type' => 'string', 'formatter' => 'kFormatter', 'using_fck' => 1, 'default' => null),
'RequireLogin' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1,
'not_null' => 1, 'default' => 0
),
'UseSecurityImage' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1,
'not_null' => 1, 'default' => 0
),
'SubmitNotifyEmail' => Array (
'type' => 'string', 'max_len' => 255,
'formatter' => 'kFormatter', 'regexp' => '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i',
'sample_value' => 'email@domain.com', 'error_msgs' => Array ('invalid_format' => '!la_invalid_email!'),
'not_null' => 1, 'default' => ''
),
'EnableEmailCommunication' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1,
'not_null' => 1, 'default' => 0
),
'ProcessUnmatchedEmails' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1,
'not_null' => 1, 'default' => 0
),
'ReplyFromName' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''),
'ReplyFromEmail' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''),
'ReplyCc' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''),
'ReplyBcc' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''),
'ReplyMessageSignature' => Array ('type' => 'string', 'default' => NULL),
'ReplyServer' => Array (
'type' => 'string', 'max_len' => 255,
'error_msgs' => Array (
'connection_failed' => '!la_error_ConnectionFailed!',
'message_listing_failed' => '!la_error_MessagesListReceivingFailed!',
),
'not_null' => 1, 'default' => ''
),
'ReplyPort' => Array ('type' => 'int', 'not_null' => 1, 'default' => 110),
'ReplyUsername' => Array (
'type' => 'string', 'max_len' => 255,
'error_msgs' => Array (
'login_failed' => '!la_error_LoginFailed!',
),
'not_null' => 1, 'default' => ''
),
'ReplyPassword' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''),
'BounceEmail' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''),
'BounceServer' => Array (
'type' => 'string', 'max_len' => 255,
'error_msgs' => Array (
'connection_failed' => '!la_error_ConnectionFailed!',
'message_listing_failed' => '!la_error_MessageListingFailed!',
),
'not_null' => 1, 'default' => ''
),
'BouncePort' => Array ('type' => 'int', 'not_null' => 1, 'default' => 110),
'BounceUsername' => Array (
'type' => 'string', 'max_len' => 255,
'error_msgs' => Array (
'login_failed' => '!la_error_LoginFailed!',
),
'not_null' => 1, 'default' => ''
),
'BouncePassword' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''),
),
'Grids' => Array (
'Default' => Array (
'Icons' => Array (
'default' => 'icon16_item.png',
0 => 'icon16_disabled.png',
1 => 'icon16_item.png',
2 => 'icon16_pending.png',
),
'Fields' => Array (
'FormId' => Array ( 'title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 60, ),
'Title' => Array ('filter_block' => 'grid_like_filter', 'width' => 220, ),
'Description' => Array ('filter_block' => 'grid_like_filter', 'width' => 300, ),
'RequireLogin' => Array ('filter_block' => 'grid_options_filter', 'width' => 80,),
'UseSecurityImage' => Array ('filter_block' => 'grid_options_filter', 'width' => 110,),
'EnableEmailCommunication' => Array ('title' => 'la_col_EnableEmailCommunication', 'filter_block' => 'grid_options_filter', 'width' => 120,),
'SubmitNotifyEmail' => Array ('filter_block' => 'grid_like_filter'),
),
),
),
);
\ No newline at end of file
Index: branches/5.3.x/core/admin_templates/js/simple_grid.js
===================================================================
--- branches/5.3.x/core/admin_templates/js/simple_grid.js (revision 16194)
+++ branches/5.3.x/core/admin_templates/js/simple_grid.js (revision 16195)
@@ -1,210 +1,213 @@
function SimpleGrid($id_prefix, $field_mask, $show_order) {
this.ItemIDs = new Array ();
this.NextNumber = 0;
this.IDPrefix = $id_prefix;
this.FieldMask = $field_mask;
this.Container = this.getControl('container');
this.ShowOrder = $show_order;
}
SimpleGrid.prototype.registerRecord = function ($id) {
var $prev_id = this.lastID();
if ($prev_id !== false) {
this.getControl('button_' + $prev_id).value = '-';
}
$id = parseInt($id);
this.ItemIDs.push($id);
if ($id < 0 && Math.abs($id) > Math.abs(this.NextNumber)) {
this.NextNumber = $id;
}
if (this.ShowOrder) {
this.getControl('number_'+$id).innerHTML = this.ItemIDs.length;
}
this.processRecordDependancies($id);
}
SimpleGrid.prototype.renumberRecords = function () {
if (!this.ShowOrder) {
return ;
}
var $me = this;
var $number = 1;
$('> div', this.getControl('container')).each(
function () {
var $div_id = $(this).attr('id');
if ( !$div_id.match( new RegExp('^' + $me.IDPrefix + '_(.*)') ) ) {
return ;
}
$('#' + jq($me.IDPrefix + '_number_' + RegExp.$1)).html($number);
$number++;
}
);
}
SimpleGrid.prototype.nextNumber = function () {
return --this.NextNumber;
}
SimpleGrid.prototype.lastID = function () {
return this.ItemIDs.length ? this.ItemIDs.slice(this.ItemIDs.length - 1)[0] : false;
}
SimpleGrid.prototype.toggleRecord = function ($item_id) {
if (this.lastID() == $item_id) {
this.addRecord();
}
else {
this.removeRecord($item_id);
}
}
SimpleGrid.prototype.getControl = function ($name) {
return document.getElementById(this.IDPrefix + '_' + $name);
}
SimpleGrid.prototype.getField = function ($id, $field_name, $prepend, $append) {
$prepend = isset($prepend) ? $prepend + '_' : '';
$append = isset($append) ? '_' + $append : '';
return document.getElementById($prepend + this.FieldMask.replace('#FIELD#', $field_name).replace('#ID#', $id) + $append);
}
SimpleGrid.prototype.markError = function ($item_id, $field_name, $error_found, $error_message) {
if (!isset($error_found)) {
$error_found = true;
}
var $field = $( this.getField($item_id, $field_name) );
if ($error_found) {
$field.css('background-color', '#EF2C2C').attr('title', $error_message === undefined ? '' : $error_message)
// TODO: duplicate focus events possible on one element, fix this later
.focus(
function($e) {
$(this).css('background-color', '').attr('title', '');
}
);
}
else {
$field.css('background-color', '').attr('title', '');
}
}
SimpleGrid.prototype.validateRecord = function ($item_id) {
return true;
}
SimpleGrid.prototype.validate = function () {
var $i = 0;
var $is_valid = true;
while ($i < this.ItemIDs.length) {
if (!this.validateRecord(this.ItemIDs[$i])) {
$is_valid = false;
}
$i++;
}
return $is_valid;
}
SimpleGrid.prototype.addRecord = function ($after_item_id) {
var new_item_number = this.nextNumber();
var $item_mask = this.getControl('mask').value;
var $result_html = $item_mask.replace(/#NUMBER#/g, new_item_number);
var $div = document.createElement('DIV');
$div.id = this.IDPrefix + '_' + new_item_number;
if ($after_item_id === undefined) {
this.getControl('container').appendChild($div);
}
else {
$('#' + jq(this.IDPrefix + '_' + $after_item_id)).after($div);
}
$div.innerHTML = $result_html;
var $js_mask = this.getControl('js_mask').value;
var result_js = $js_mask.replace(/#NUMBER#/g, new_item_number);
eval(result_js);
this.renumberRecords();
this.updateTotals();
this.processOddEven();
return new_item_number;
}
SimpleGrid.prototype.removeRecord = function ($number) {
var $index = array_search($number, this.ItemIDs);
if ($index == -1) {
// don't allow to delete missing rows
return false;
}
this.getControl('container').removeChild( this.getControl($number) );
this.ItemIDs.splice($index, 1);
this.renumberRecords();
this.updateTotals();
this.processOddEven();
return true;
}
SimpleGrid.prototype.processOddEven = function () {
- var $me = this;
- var $class_name = 'odd';
+ var $me = this,
+ $prev_class_name = 'even',
+ $next_class_name = 'odd';
$('> div', this.getControl('container')).each(
function () {
var $div_id = $(this).attr('id');
if ( !$div_id.match( new RegExp('^' + $me.IDPrefix + '_(.*)') ) ) {
return ;
}
- $me.getControl(RegExp.$1).className = $class_name;
- $class_name = $class_name == 'odd' ? 'even' : 'odd';
+ $prev_class_name = $next_class_name == 'odd' ? 'even' : 'odd';
+
+ $($me.getControl(RegExp.$1)).removeClass($prev_class_name).addClass($next_class_name);
+ $next_class_name = $prev_class_name;
}
);
}
SimpleGrid.prototype.removeRecords = function () {
while (this.ItemIDs.length > 0) {
var $item_id = this.ItemIDs[this.ItemIDs.length - 1];
this.removeRecord($item_id);
}
}
SimpleGrid.prototype.updateTotals = function () {
// prototype
}
SimpleGrid.prototype.processRecordDependancies = function ($id) {
// prototype
}
SimpleGrid.prototype.each = function($callback) {
var $result = null;
for (var $i = 0; $i < this.ItemIDs.length; $i++) {
$result = $callback.call(this, this.ItemIDs[$i]);
if ($result === false) {
break;
}
}
}
Index: branches/5.3.x/index.php
===================================================================
--- branches/5.3.x/index.php (revision 16194)
+++ branches/5.3.x/index.php (revision 16195)
Property changes on: branches/5.3.x/index.php
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/index.php:r16124-16194
Index: branches/5.3.x/CREDITS
===================================================================
--- branches/5.3.x/CREDITS (revision 16194)
+++ branches/5.3.x/CREDITS (revision 16195)
Property changes on: branches/5.3.x/CREDITS
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/CREDITS:r16124-16194
Index: branches/5.3.x/README
===================================================================
--- branches/5.3.x/README (revision 16194)
+++ branches/5.3.x/README (revision 16195)
Property changes on: branches/5.3.x/README
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/README:r16124-16194
Index: branches/5.3.x/composer.json
===================================================================
--- branches/5.3.x/composer.json (revision 16194)
+++ branches/5.3.x/composer.json (revision 16195)
@@ -1,11 +1,17 @@
{
"name": "In-Portal",
"require": {
"symfony/console": "~2.6",
"stecman/symfony-console-completion": "~0.5"
},
"require-dev": {
+ "aik099/phpunit-mink": "~2.0",
+ "qa-tools/qa-tools": "~1.0",
"aik099/coding-standard": "dev-in-portal",
- "nikic/php-parser": "~1.2"
+ "nikic/php-parser": "~1.2",
+ "mockery/mockery": "~0.9",
+ "mindplay/annotations": "~1.2@dev",
+
+ "behat/mink": "~1.6"
}
}
Index: branches/5.3.x/tools/build/build_all.xml
===================================================================
--- branches/5.3.x/tools/build/build_all.xml (nonexistent)
+++ branches/5.3.x/tools/build/build_all.xml (revision 16195)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="In-Portal (full)" default="build">
+ <property file="build.properties" />
+ <resolvepath propertyName="base.dir" file="${base.dir}"/>
+
+ <property name="scan.dir" value="${base.dir}"/>
+ <property name="pdepend.exclude" value="blog,build,core/editor,core/ckeditor,core/units/general/libchart,modules/mpdf,system,vendor"/>
+ <property name="phpcpd.exclude" value="--exclude blog --exclude build --exclude core/editor --exclude core/ckeditor --exclude core/units/general/libchart --exclude modules/mpdf --exclude system --exclude vendor"/>
+
+ <import file="targets/common.xml"/>
+
+ <target name="phpmd-ci" description="Perform project mess detection using PHPMD (creates a log file for the CI server)" depends="init">
+ <parallel threadCount="4">
+ <phingcall target="phpmd-ci-inc">
+ <property name="param.report.file" value="pmd_modules.xml"/>
+ <property name="param.include" value="${scan.dir}/modules"/>
+ <property name="param.exclude" value="admin_templates,mpdf"/>
+ </phingcall>
+
+ <phingcall target="phpmd-ci-inc">
+ <property name="param.report.file" value="pmd_core.xml"/>
+ <property name="param.include" value="${scan.dir}/core"/>
+ <property name="param.exclude" value="admin_templates,ckeditor,editor,units/general/libchart"/>
+ </phingcall>
+ </parallel>
+ </target>
+
+ <fileset dir="${base.dir}" id="php.files">
+ <include name="**/*.php"/>
+ <exclude name="blog/**"/>
+ <exclude name="build/**"/>
+ <exclude name="core/editor/**"/>
+ <exclude name="core/ckeditor/**"/>
+ <exclude name="core/units/general/libchart/**"/>
+ <exclude name="modules/mpdf/**"/>
+ <exclude name="system/**"/>
+ <exclude name="vendor/**"/>
+ </fileset>
+</project>
Property changes on: branches/5.3.x/tools/build/build_all.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/build_custom.xml
===================================================================
--- branches/5.3.x/tools/build/build_custom.xml (nonexistent)
+++ branches/5.3.x/tools/build/build_custom.xml (revision 16195)
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="In-Portal (custom module only)" default="build">
+ <property file="build.properties" />
+ <resolvepath propertyName="base.dir" file="${base.dir}"/>
+
+ <property name="scan.dir" value="${base.dir}/modules/custom"/>
+
+ <!--TODO: update tasks, that uses "pdepend.exclude" and "phpcpd.exclude" to handle empty exclusion list-->
+ <property name="pdepend.exclude" value="no-match"/>
+ <property name="phpcpd.exclude" value="--exclude no-match"/>
+
+ <import file="targets/common.xml"/>
+
+ <target name="phpmd-ci" description="Perform project mess detection using PHPMD (creates a log file for the CI server)" depends="init">
+ <parallel threadCount="4">
+ <phingcall target="phpmd-ci-inc">
+ <property name="param.report.file" value="pmd_custom.xml"/>
+ <property name="param.include" value="${scan.dir}"/>
+ <property name="param.exclude" value="admin_templates"/>
+ </phingcall>
+ </parallel>
+ </target>
+
+ <fileset dir="${base.dir}" id="php.files">
+ <include name="modules/custom/**/*.php"/>
+ </fileset>
+</project>
Property changes on: branches/5.3.x/tools/build/build_custom.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/inc/custom_phpunit.xml
===================================================================
--- branches/5.3.x/tools/build/inc/custom_phpunit.xml (nonexistent)
+++ branches/5.3.x/tools/build/inc/custom_phpunit.xml (revision 16195)
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit colors="true"
+ bootstrap="../../../core/kernel/tests/bootstrap.php"
+ strict="true"
+ verbose="true">
+
+ <php>
+ <ini name="memory_limit" value="-1"/>
+ </php>
+
+ <testsuites>
+ <testsuite name="all">
+ <directory>../../../core/tests</directory>
+ <directory>../../../modules/custom/tests</directory>
+ <exclude>../../../core/tests/unit</exclude>
+ </testsuite>
+ </testsuites>
+
+ <listeners>
+ <listener class="\Mockery\Adapter\Phpunit\TestListener"/>
+ </listeners>
+
+ <logging>
+ <!--<log type="coverage-html" target="../../../build/coverage" title="BankAccount"
+ charset="UTF-8" yui="true" highlight="true"
+ lowUpperBound="35" highLowerBound="70"/>
+ <log type="coverage-clover" target="../../../build/logs/clover.xml"/>-->
+ <log type="junit" target="../../../build/logs/junit.xml" logIncompleteSkipped="false"/>
+ </logging>
+
+ <filter>
+ <blacklist>
+ <directory suffix=".php">..</directory>
+ </blacklist>
+ <whitelist addUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">../../../modules/custom</directory>
+ <exclude>
+ <directory>../../../blog</directory>
+ <directory>../../../modules/mpdf</directory>
+ <directory>../../../core/editor</directory>
+ <directory>../../../core/ckeditor</directory>
+ <directory>../../../system</directory>
+ <directory>../../../vendor</directory>
+
+ <directory>../../../core/tests</directory>
+ <directory>../../../modules/*/tests</directory>
+
+ <file>../../../core/kernel/tests/bootstrap.php</file>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
Property changes on: branches/5.3.x/tools/build/inc/custom_phpunit.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/inc/phpunit.xml
===================================================================
--- branches/5.3.x/tools/build/inc/phpunit.xml (nonexistent)
+++ branches/5.3.x/tools/build/inc/phpunit.xml (revision 16195)
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit colors="true"
+ bootstrap="../../../core/kernel/tests/bootstrap.php"
+ strict="true"
+ verbose="true">
+
+ <php>
+ <ini name="memory_limit" value="-1"/>
+ </php>
+
+ <testsuites>
+ <testsuite name="all">
+ <directory>../../../core/tests</directory>
+ <directory>../../../modules/*/tests</directory>
+ <exclude>../../../core/tests/unit</exclude>
+ </testsuite>
+ <testsuite name="custom">
+ <directory>../../../modules/custom/tests</directory>
+ </testsuite>
+ </testsuites>
+
+ <listeners>
+ <listener class="\Mockery\Adapter\Phpunit\TestListener"/>
+ </listeners>
+
+ <logging>
+ <!--<log type="coverage-html" target="../../../build/coverage" title="BankAccount"
+ charset="UTF-8" yui="true" highlight="true"
+ lowUpperBound="35" highLowerBound="70"/>
+ <log type="coverage-clover" target="../../../build/logs/clover.xml"/>-->
+ <log type="junit" target="../../../build/logs/junit.xml" logIncompleteSkipped="false"/>
+ </logging>
+
+ <filter>
+ <blacklist>
+ <directory suffix=".php">..</directory>
+ </blacklist>
+ <whitelist addUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">../../..</directory>
+ <exclude>
+ <directory>../../../blog</directory>
+ <directory>../../../modules/mpdf</directory>
+ <directory>../../../core/editor</directory>
+ <directory>../../../core/ckeditor</directory>
+ <directory>../../../system</directory>
+ <directory>../../../vendor</directory>
+
+ <directory>../../../core/tests</directory>
+ <directory>../../../modules/*/tests</directory>
+
+ <file>../../../core/kernel/tests/bootstrap.php</file>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
Property changes on: branches/5.3.x/tools/build/inc/phpunit.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/inc/phpmd.xml
===================================================================
--- branches/5.3.x/tools/build/inc/phpmd.xml (nonexistent)
+++ branches/5.3.x/tools/build/inc/phpmd.xml (revision 16195)
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<ruleset name="InPortal"
+ xmlns="http://pmd.sf.net/ruleset/1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
+ xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
+ <description>
+ In-Portal standard. Version: 1.0.1 from 21-Mar-2014
+ </description>
+
+ <rule ref="rulesets/codesize.xml">
+ <!-- event handlers are usually large; figure out how to exclude only for them -->
+ <exclude name="TooManyMethods"/>
+ <exclude name="ExcessiveClassComplexity"/>
+ <exclude name="ExcessiveClassLength"/>
+
+ <!-- exclude because defined below -->
+ <exclude name="ExcessiveMethodLength"/>
+ </rule>
+
+ <rule ref="rulesets/codesize.xml/ExcessiveMethodLength">
+ <properties>
+ <property name="minimum" value="120" />
+ </properties>
+ </rule>
+
+ <rule ref="rulesets/design.xml">
+ <!-- event handlers usually do much; figure out how to exclude only for them -->
+ <exclude name="CouplingBetweenObjects"/>
+ </rule>
+ <rule ref="rulesets/naming.xml">
+ <!-- let PHP_CodeSniffer check for them -->
+ <exclude name="LongVariable"/>
+ <exclude name="ConstantNamingConventions"/>
+
+ <!-- exclude because defined below -->
+ <exclude name="ShortVariable"/>
+ </rule>
+
+ <rule ref="rulesets/naming.xml/ShortVariable">
+ <properties>
+ <property name="minimum" value="2" />
+ </properties>
+ </rule>
+
+ <rule ref="rulesets/unusedcode.xml">
+ <!-- let PhpStorm deal with this -->
+ <exclude name="UnusedFormalParameter"/>
+ </rule>
+</ruleset>
Property changes on: branches/5.3.x/tools/build/inc/phpmd.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/targets/db.xml
===================================================================
--- branches/5.3.x/tools/build/targets/db.xml (nonexistent)
+++ branches/5.3.x/tools/build/targets/db.xml (revision 16195)
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="inc_db" default="build">
+ <target name="db-fill-ci" description="Inserts testing data into database" depends="create-aliases,db-clean-ci,config-create">
+ <echo msg="Filling database '${db.name}' with demo data"/>
+
+ <foreach param="filename" absparam="db.file" target="db-import-file">
+ <filelist dir="${base.dir}/system" files="build-schema.sql,build-data.sql,build-patch.sql"/>
+ </foreach>
+ </target>
+
+ <target name="db-clean-ci" description="Cleans the database">
+ <echo msg="Deleting database '${db.name}'"/>
+ <phingcall target="db-inline-query">
+ <property name="skip-db" value="true"/>
+ <property name="db.query" value="DROP DATABASE ${db.name};"/>
+ </phingcall>
+
+ <echo msg="Creating database '${db.name}'"/>
+ <phingcall target="db-inline-query">
+ <property name="skip-db" value="true"/>
+ <property name="db.query" value="CREATE DATABASE ${db.name};"/>
+ </phingcall>
+ </target>
+
+ <target name="db-dump" description="Dumps database (to be used in CI)" depends="create-aliases">
+ <echo msg="Truncating '${db.tableprefix}SystemCache' table in '${db.name}' database"/>
+ <phingcall target="db-inline-query">
+ <property name="db.query" value="TRUNCATE TABLE ${db.tableprefix}SystemCache;"/>
+ </phingcall>
+
+ <echo msg="Truncating '${db.tableprefix}SystemLog' table in '${db.name}' database"/>
+ <phingcall target="db-inline-query">
+ <property name="db.query" value="TRUNCATE TABLE ${db.tableprefix}SystemLog;"/>
+ </phingcall>
+
+ <echo msg="Dumping database '${db.name}' schema"/>
+ <exec executable="mysqldump" checkreturn="true">
+ <arg value="--host=${db.host}"/>
+ <arg value="--user=${db.user}"/>
+ <arg value="--password=${db.password}"/>
+ <arg value="--compact"/>
+ <arg value="--no-data"/>
+ <arg value="${db.name}"/>
+ <arg value="&gt;"/>
+ <arg path="${base.dir}/system/build-schema.sql"/>
+ </exec>
+
+ <echo msg="Dumping database '${db.name}' data"/>
+ <exec executable="mysqldump" checkreturn="true">
+ <arg value="--host=${db.host}"/>
+ <arg value="--user=${db.user}"/>
+ <arg value="--password=${db.password}"/>
+ <arg value="--compact"/>
+ <arg value="--no-create-info"/>
+ <arg value="${db.name}"/>
+ <arg value="&gt;"/>
+ <arg path="${base.dir}/system/build-data.sql"/>
+ </exec>
+
+ <echo msg="Creating empty '${db.name}' patch"/>
+ <touch file="${base.dir}/system/build-patch.sql" />
+ </target>
+
+ <!-- ## Helper targets
+
+ These targets are used throughout the project and should normally not be
+ executed directly.-->
+ <target name="db-import-file" description="Import file into database" hidden="true">
+ <echo msg="Importing '${db.file}' into '${db.name}' database"/>
+
+ <!--<pdosqlexec url="${pdo.dsn}" userid="${db.user}" password="${db.password}" encoding="utf-8">
+ <transaction src="${db.file}"/>
+ </pdosqlexec>-->
+
+ <exec executable="mysql" checkreturn="true">
+ <arg value="--host=${db.host}"/>
+ <arg value="--user=${db.user}"/>
+ <arg value="--password=${db.password}"/>
+ <arg value="${db.name}"/>
+ <arg value="&lt;"/>
+ <arg path="${db.file}"/>
+ </exec>
+ </target>
+
+ <target name="db-inline-query" description="Runs sql query in database" hidden="true">
+ <property name="skip-db" value="false"/>
+
+ <if>
+ <isfalse value="${skip-db}"/>
+ <then>
+ <exec executable="mysql" checkreturn="true">
+ <arg value="--host=${db.host}"/>
+ <arg value="--user=${db.user}"/>
+ <arg value="--password=${db.password}"/>
+ <arg value="${db.name}"/>
+ <arg value="-BNe"/>
+ <arg value="${db.query}"/>
+ </exec>
+ </then>
+ <else>
+ <exec executable="mysql" checkreturn="true">
+ <arg value="--host=${db.host}"/>
+ <arg value="--user=${db.user}"/>
+ <arg value="--password=${db.password}"/>
+ <arg value="-BNe"/>
+ <arg value="${db.query}"/>
+ </exec>
+ </else>
+ </if>
+ </target>
+</project>
Property changes on: branches/5.3.x/tools/build/targets/db.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/targets/common.xml
===================================================================
--- branches/5.3.x/tools/build/targets/common.xml (nonexistent)
+++ branches/5.3.x/tools/build/targets/common.xml (revision 16195)
@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+### Requirements
+
+Using the template requires a range of tools packages present. They can be
+installed as follows:
+
+ Install packages:
+ composer global require sebastian/phpcpd:~2.0 phploc/phploc:~2.1 phpmd/phpmd:~2.2 phing/phing:~2.1 squizlabs/php_codesniffer:~2.3
+
+ Add following to the your ".bashrc" file:
+ export PATH="$PATH:$HOME/.composer/vendor/bin"
+-->
+<project name="inc_common" default="build">
+ <import file="targets/db.xml"/>
+ <import file="targets/deploy.xml"/>
+ <import file="targets/folders.xml"/>
+
+ <php function="ini_set">
+ <param value="memory_limit"/>
+ <param value="-1"/>
+ </php>
+
+ <!-- AutoLoader is needed to make PhpLoc (installed globally via Composer) work. -->
+ <if>
+ <available file="${env.HOME}/.composer/vendor/autoload.php" type="file"/>
+ <then>
+ <autoloader autoloaderpath="${env.HOME}/.composer/vendor/autoload.php"/>
+ </then>
+ </if>
+
+ <target name="build" description="Builds a project" depends="init,verify,analyze"/>
+
+ <target name="build-parallel" description="Builds a project (long targets executed in parallel)" depends="init,lint,tools-parallel,phpunit"/>
+
+ <target name="tools-parallel" description="Run tools in parallel">
+ <parallel threadCount="2">
+ <sequential>
+ <phingcall target="pdepend"/>
+ <phingcall target="phpmd-ci"/>
+ </sequential>
+
+ <phingcall target="phpcpd"/>
+ <phingcall target="phpcs-ci"/>
+ <phingcall target="phploc"/>
+ <phingcall target="phpdoc"/>
+ </parallel>
+ </target>
+
+ <!-- ### Verify code consistency
+
+ Make sure that our code is clean and functional. -->
+ <target name="verify" description="Verify code consistency" depends="init,lint"/>
+ <!--<target name="verify" description="Verify code consistency" depends="init,lint,phpunit"/>-->
+
+ <!-- ### Analyze code
+
+ Perform static analysis of the code to generate statistics, identify potential
+ problems and opportunities for refactorings and make sure that the code
+ complies with coding standards. -->
+ <target name="analyze" description="Analyze code" depends="init,phploc,phpmd-ci,phpcpd,phpcs-ci"/>
+ <!--<target name="analyze" description="Analyze code" depends="init,phploc,pdepend,phpmd-ci,phpcpd,phpcs-ci"/>-->
+
+ <!-- ### Generate documentation
+
+ Generate HTML documentation and code browser for the project. -->
+ <target name="docs" description="Generate documentation" depends="init,phpdoc" />
+
+ <!-- ### PHP linting
+
+ Check files for syntax errors. -->
+ <target name="lint" description="Perform syntax check of sourcecode files" depends="init">
+ <phplint haltonfailure="true" deprecatedAsError="false" cachefile="${build.cache.dir}/phplint.txt">
+ <fileset refid="php.files"/>
+ </phplint>
+ </target>
+
+ <!-- ### Generate code statistics
+
+ Measures the size of the project using
+ [phploc](http://sebastianbergmann.github.com/phploc/) and generates
+ statistics. -->
+ <target name="phploc" description="Measure project size using PHPLOC" depends="init">
+ <phploc reportType="csv" reportName="phploc" reportDirectory="${build.logs.dir}">
+ <fileset refid="php.files"/>
+ </phploc>
+ </target>
+
+ <target name="pdepend" description="Calculate software metrics using PHP_Depend" depends="init">
+ <exec executable="pdepend" passthru="true">
+ <arg value="--jdepend-xml=${build.logs.dir}/jdepend.xml"/>
+ <arg value="--jdepend-chart=${build.pdepend.dir}/dependencies.svg"/>
+ <arg value="--overview-pyramid=${build.pdepend.dir}/overview-pyramid.svg"/>
+ <arg value="--ignore=${pdepend.exclude}"/>
+ <arg path="${scan.dir}"/>
+ </exec>
+ </target>
+
+ <!-- ### Detect code mess
+
+ Uses [PHPMD](http://phpmd.org/) to detect code mess and look for potential
+ problems. -->
+
+ <target name="phpmd-ci" description="Perform project mess detection using PHPMD (creates a log file for the CI server)" depends="init">
+ <fail message="Please create own 'phpmd-ci' task using 'parallel' and 'phpmd-ci-inc' tasks." />
+ </target>
+
+ <target name="phpmd-ci-inc">
+ <exec executable="phpmd" passthru="true">
+ <arg path="${param.include}"/>
+ <arg value="xml"/>
+ <arg value="${build.config.dir}/inc/phpmd.xml"/>
+ <arg value="--reportfile"/>
+ <arg value="${build.logs.dir}/${param.report.file}"/>
+ <arg value="--suffixes"/>
+ <arg value="php"/>
+ <arg value="--exclude"/>
+ <arg value="${param.exclude}"/>
+ </exec>
+ </target>
+
+ <target name="phpcs" description="Find coding standard violations using PHP_CodeSniffer (print human readable output)" depends="init">
+ <exec executable="phpcs" passthru="true">
+ <arg value="--standard=${vendor.dir}/aik099/coding-standard/CodingStandard"/>
+ <arg value="-wps"/>
+ <arg value="--encoding=UTF-8"/>
+ <arg value="--extensions=php"/>
+ <arg value="-d"/>
+ <arg value="memory_limit=-1"/>
+ <arg path="${scan.dir}"/>
+ </exec>
+ </target>
+
+ <target name="phpcbf" description="Fixes coding standard violations using PHP_CodeSniffer" depends="init">
+ <exec executable="phpcbf" passthru="true">
+ <arg value="--standard=${vendor.dir}/aik099/coding-standard/CodingStandard"/>
+ <arg value="-wps"/>
+ <arg value="--no-patch"/>
+ <arg value="--encoding=UTF-8"/>
+ <arg value="--extensions=php"/>
+ <arg value="-d"/>
+ <arg value="memory_limit=-1"/>
+ <arg path="${scan.dir}"/>
+ </exec>
+ </target>
+
+ <target name="phpcs-ci" description="Find coding standard violations using PHP_CodeSniffer (creates a log file for the CI server)" depends="init">
+ <exec executable="phpcs" passthru="true">
+ <arg value="--report=checkstyle"/>
+ <arg value="--report-file=${build.logs.dir}/checkstyle.xml"/>
+ <arg value="--standard=${vendor.dir}/aik099/coding-standard/CodingStandard"/>
+ <arg value="-wp"/>
+ <arg value="--encoding=UTF-8"/>
+ <arg value="--extensions=php"/>
+ <arg value="-d"/>
+ <arg value="memory_limit=-1"/>
+ <arg path="${scan.dir}"/>
+ </exec>
+ </target>
+
+ <!-- ### Detect potential copy/pasting
+
+ Uses [phpcpd](https://github.com/sebastianbergmann/phpcpd) to detect duplicate
+ code. This indicates potential refactorings.-->
+ <target name="phpcpd" description="Find duplicate code using PHPCPD" depends="init">
+ <exec command="which phpcpd" outputProperty="phpcpd_binary"/>
+ <exec executable="php" passthru="true">
+ <arg value="-d memory_limit=-1"/>
+ <arg value="${phpcpd_binary}"/>
+ <arg value="--log-pmd=${build.logs.dir}/pmd-cpd.xml"/>
+ <arg value="--progress"/>
+ <arg line="--names-exclude=*_config.php ${phpcpd.exclude}"/>
+ <arg path="${scan.dir}"/>
+ </exec>
+ </target>
+
+ <!-- ### Generate documentation -->
+ <!-- #### Generate API Documentation
+
+ Uses [phpDocumentor](http://www.phpdoc.org/) to generate documentation. -->
+ <target name="phpdoc" description="Generate API documentation using phpdoc" depends="init">
+ <phpdoc2 title="API Documentation" destdir="${build.api.dir}" template="responsive" quiet="true">
+ <fileset refid="php.files"/>
+ </phpdoc2>
+ </target>
+
+ <target name="phpunit" description="Run tests with PHPUnit" depends="init">
+ <exec executable="phpunit" logoutput="true" checkreturn="true">
+ <arg value="--configuration"/>
+ <arg path="${build.config.dir}/phpunit.xml"/>
+ <arg value="--testsuite"/>
+ <arg value="custom"/>
+ </exec>
+ </target>
+
+ <target name="init" depends="create-aliases,folders-create,deploy-ci" description="Prepare for build">
+ </target>
+
+ <target name="create-aliases" description="Creating directory aliases" hidden="true">
+ <property name="pdo.dsn.without.db" value="mysql:host=${db.host}"/>
+ <property name="pdo.dsn" value="${pdo.dsn.without.db};dbname=${db.name}"/>
+
+ <property name="build.config.dir" value="."/>
+ <property name="build.dir" value="${base.dir}/build"/>
+ <!--<property name="build.api.dir" value="${build.dir}/api"/>-->
+ <!--<property name="build.coverage.dir" value="${build.dir}/coverage"/>-->
+ <property name="build.logs.dir" value="${build.dir}/logs"/>
+ <property name="build.cache.dir" value="${build.dir}/cache"/>
+ <!--<property name="build.pdepend.dir" value="${build.dir}/pdepend"/>-->
+ <property name="vendor.dir" value="${base.dir}/vendor"/>
+ </target>
+</project>
Property changes on: branches/5.3.x/tools/build/targets/common.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/targets/deploy.xml
===================================================================
--- branches/5.3.x/tools/build/targets/deploy.xml (nonexistent)
+++ branches/5.3.x/tools/build/targets/deploy.xml (revision 16195)
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="inc_deploy" default="build">
+ <target name="deploy-ci" description="Deployment tasks, that are required after SVN checkout" depends="create-aliases">
+ <property name="tools.dir" value="${base.dir}/tools"/>
+
+ <echo msg="Fixing shell scripts permissions under '/tools' folder"/>
+ <chmod mode="0755" verbose="true" failonerror="true">
+ <fileset dir="${tools.dir}">
+ <include name="*.sh"/>
+ </fileset>
+ </chmod>
+
+ <echo msg="Fixing '/system' folder permissions"/>
+ <exec dir="${tools.dir}" command="./fix_perms.sh" logoutput="true"/>
+
+ <echo msg="Removing compiled templates under '/system/cache' folder"/>
+ <exec dir="${tools.dir}" command="./clear_cache.sh" logoutput="true"/>
+
+ <composer command="install" composer="/usr/local/bin/composer">
+ <arg value="--working-dir"/>
+ <arg path="${base.dir}"/>
+ <arg value="--ansi"/>
+ </composer>
+
+ <if>
+ <available file="${base.dir}/system/build-schema.sql" type="file"/>
+ <then>
+ <phingcall target="db-fill-ci"/>
+ <phingcall target="inportal-deploy"/>
+ </then>
+ </if>
+ </target>
+
+ <target name="inportal-deploy" description="Runs In-Portal deployment script">
+ <echo msg="Running In-Portal Deployment Script (part of 'do_update') ..."/>
+
+ <exec executable="php" logoutput="true" checkreturn="true">
+ <arg path="${base.dir}/tools/run_event.php"/>
+ <arg value="adm:OnDeploy"/>
+ <arg value="${cli.key}"/>
+ <arg value="${client.ip.address}"/>
+ </exec>
+ </target>
+</project>
Property changes on: branches/5.3.x/tools/build/targets/deploy.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/targets/folders.xml
===================================================================
--- branches/5.3.x/tools/build/targets/folders.xml (nonexistent)
+++ branches/5.3.x/tools/build/targets/folders.xml (revision 16195)
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="inc_folders" default="build">
+ <target name="folders-create" description="Creates folders, to store build artifacts" depends="create-aliases,folders-delete">
+ <!--<mkdir dir="${build.api.dir}"/>-->
+ <!--<mkdir dir="${build.coverage.dir}"/>-->
+ <mkdir dir="${build.logs.dir}"/>
+ <!--<mkdir dir="${build.pdepend.dir}"/>-->
+ <mkdir dir="${build.cache.dir}"/>
+ <mkdir dir="${build.dir}/test_data"/>
+ <mkdir dir="${build.dir}/system/remote_code_coverage"/>
+
+ <copy todir="${base.dir}/system" overwrite="true">
+ <fileset dir="${build.dir}/test_data">
+ <include name="**/*"/>
+ </fileset>
+ </copy>
+
+ <if>
+ <isfalse value="${local.build}"/>
+ <then>
+ <echo message="Setting 'RewriteBase /' to solve problems with 'VirtualDocumentRoot' option"/>
+ <echo file="${base.dir}/.htaccess" append="true">
+ RewriteBase /
+ </echo>
+ </then>
+ </if>
+ </target>
+
+ <target name="folders-delete" description="Deletes folders, where build artifacts are stored" depends="create-aliases">
+ <!--<delete dir="${build.api.dir}"/>-->
+ <!--<delete dir="${build.coverage.dir}"/>-->
+ <delete dir="${build.logs.dir}"/>
+ <!--<delete dir="${build.pdepend.dir}"/>-->
+ </target>
+
+ <target name="config-create" description="Creates '/system/config.php' file" depends="create-aliases,config-delete">
+ <touch file="${base.dir}/system/config.php" />
+
+ <echo file="${base.dir}/system/config.php">&lt;?php
+ $_CONFIG['Misc']['WriteablePath'] = '/system';
+ $_CONFIG['Misc']['RestrictedPath'] = '/system/.restricted';
+ $_CONFIG['Misc']['WebsitePath'] = '${website.path}';
+ $_CONFIG['Misc']['Domain'] = '${website.domain}';
+ $_CONFIG['Misc']['AdminDirectory'] = '/admin';
+ $_CONFIG['Misc']['AdminPresetsDirectory'] = '/admin';
+ $_CONFIG['Misc']['ApplicationClass'] = 'kApplication';
+ $_CONFIG['Misc']['ApplicationPath'] = '/core/kernel/application.php';
+ $_CONFIG['Misc']['CacheHandler'] = 'Fake';
+ $_CONFIG['Misc']['MemcacheServers'] = 'localhost:11211';
+ $_CONFIG['Misc']['CompressionEngine'] = 'php';
+ $_CONFIG['Misc']['WebsiteCharset'] = 'utf-8';
+ $_CONFIG['Misc']['EnableSystemLog'] = '1';
+ $_CONFIG['Misc']['SystemLogMaxLevel'] = '5';
+ $_CONFIG['Misc']['TrustProxy'] = '0';
+
+ $_CONFIG['Database']['DBType'] = 'mysql';
+ $_CONFIG['Database']['DBHost'] = '${db.host}';
+ $_CONFIG['Database']['DBName'] = '${db.name}';
+ $_CONFIG['Database']['DBUser'] = '${db.user}';
+ $_CONFIG['Database']['DBUserPassword'] = '${db.password}';
+ $_CONFIG['Database']['DBCollation'] = 'utf8_general_ci';
+ $_CONFIG['Database']['TablePrefix'] = '${db.tableprefix}';
+ $_CONFIG['Database']['DBCharset'] = 'utf8';
+ </echo>
+ </target>
+
+ <target name="config-delete" description="Deletes '/system/config.php' file" depends="create-aliases">
+ <delete file="${base.dir}/system/config.php"/>
+ </target>
+</project>
Property changes on: branches/5.3.x/tools/build/targets/folders.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/tools/build/build.properties.dist
===================================================================
--- branches/5.3.x/tools/build/build.properties.dist (nonexistent)
+++ branches/5.3.x/tools/build/build.properties.dist (revision 16195)
@@ -0,0 +1,13 @@
+local.build=1
+base.dir=../..
+client.ip.address=
+cli.key=b674006f3edb1d9cd4d838c150b0567d
+
+website.domain=
+website.path=
+
+db.host=localhost
+db.user=
+db.password=
+db.name=
+db.tableprefix=
Index: branches/5.3.x/tools/build
===================================================================
--- branches/5.3.x/tools/build (nonexistent)
+++ branches/5.3.x/tools/build (revision 16195)
Property changes on: branches/5.3.x/tools/build
___________________________________________________________________
Added: svn:ignore
## -0,0 +1 ##
+build.properties
Index: branches/5.3.x/LICENSES
===================================================================
--- branches/5.3.x/LICENSES (revision 16194)
+++ branches/5.3.x/LICENSES (revision 16195)
Property changes on: branches/5.3.x/LICENSES
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/LICENSES:r16124-16194
Index: branches/5.3.x/INSTALL
===================================================================
--- branches/5.3.x/INSTALL (revision 16194)
+++ branches/5.3.x/INSTALL (revision 16195)
Property changes on: branches/5.3.x/INSTALL
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/INSTALL:r16124-16194
Index: branches/5.3.x/COPYRIGHT
===================================================================
--- branches/5.3.x/COPYRIGHT (revision 16194)
+++ branches/5.3.x/COPYRIGHT (revision 16195)
Property changes on: branches/5.3.x/COPYRIGHT
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/COPYRIGHT:r16124-16194
Index: branches/5.3.x/.arcconfig
===================================================================
--- branches/5.3.x/.arcconfig (revision 16194)
+++ branches/5.3.x/.arcconfig (revision 16195)
@@ -1,6 +1,5 @@
{
- "project.name" : "in-portal",
- "repository.callsign": "INP",
- "phabricator.uri" : "http://qa.in-portal.org/",
- "lint.phpcs.standard" : "vendor/aik099/coding-standard/CodingStandard"
+ "project.name": "in-portal",
+ "repository.callsign": "INP",
+ "phabricator.uri": "http://qa.in-portal.org/"
}
Index: branches/5.3.x/.arclint
===================================================================
--- branches/5.3.x/.arclint (revision 16194)
+++ branches/5.3.x/.arclint (revision 16195)
@@ -1,8 +1,13 @@
{
- "linters": {
- "checkstyle": {
- "type": "phpcs",
- "include": "(\\.php$)"
- }
- }
+ "linters": {
+ "php-lint": {
+ "type": "php",
+ "include": "(\\.php$)"
+ },
+ "checkstyle": {
+ "type": "phpcs",
+ "include": "(\\.php$)",
+ "phpcs.standard": "vendor/aik099/coding-standard/CodingStandard"
+ }
+ }
}
Index: branches/5.3.x/.htaccess
===================================================================
--- branches/5.3.x/.htaccess (revision 16194)
+++ branches/5.3.x/.htaccess (revision 16195)
Property changes on: branches/5.3.x/.htaccess
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x/.htaccess:r16124-16194
Index: branches/5.3.x/composer.lock
===================================================================
--- branches/5.3.x/composer.lock (revision 16194)
+++ branches/5.3.x/composer.lock (revision 16195)
@@ -1,209 +1,1790 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "0084b5c04f49b47e81eff44c0343b57f",
+ "hash": "36a7149b43200b7f6376b78988f5138f",
"packages": [
{
"name": "stecman/symfony-console-completion",
"version": "0.5.1",
"source": {
"type": "git",
"url": "https://github.com/stecman/symfony-console-completion.git",
"reference": "1a9fc7ab4820cd1aabbdc584c6b25d221e7b6cb5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stecman/symfony-console-completion/zipball/1a9fc7ab4820cd1aabbdc584c6b25d221e7b6cb5",
"reference": "1a9fc7ab4820cd1aabbdc584c6b25d221e7b6cb5",
"shasum": ""
},
"require": {
"php": ">=5.3.2",
"symfony/console": "~2.2"
},
"require-dev": {
"phpunit/phpunit": "~4.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.5.x-dev"
}
},
"autoload": {
"psr-4": {
"Stecman\\Component\\Symfony\\Console\\BashCompletion\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Stephen Holdaway",
"email": "stephen@stecman.co.nz"
}
],
"description": "Automatic BASH completion for Symfony Console Component based applications.",
"time": "2015-05-07 12:21:50"
},
{
"name": "symfony/console",
"version": "v2.6.7",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "ebc5679854aa24ed7d65062e9e3ab0b18a917272"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/ebc5679854aa24ed7d65062e9e3ab0b18a917272",
"reference": "ebc5679854aa24ed7d65062e9e3ab0b18a917272",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1",
"symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.1"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-05-02 15:18:45"
}
],
"packages-dev": [
{
"name": "aik099/coding-standard",
"version": "dev-in-portal",
"source": {
"type": "git",
"url": "https://github.com/aik099/CodingStandard.git",
- "reference": "a47f2b767c99a33f437802b3315179887018e114"
+ "reference": "987a6781521c9293b3e36ff3877cdff63010b291"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aik099/CodingStandard/zipball/a47f2b767c99a33f437802b3315179887018e114",
- "reference": "a47f2b767c99a33f437802b3315179887018e114",
+ "url": "https://api.github.com/repos/aik099/CodingStandard/zipball/987a6781521c9293b3e36ff3877cdff63010b291",
+ "reference": "987a6781521c9293b3e36ff3877cdff63010b291",
"shasum": ""
},
"require-dev": {
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Alexander Obuhovich",
"email": "aik.bold@gmail.com"
}
],
"description": "The PHP_CodeSniffer coding standard I'm using on all of my projects.",
"keywords": [
"PHP_CodeSniffer",
"codesniffer"
],
- "time": "2015-05-06 08:54:38"
+ "time": "2015-05-17 06:49:37"
+ },
+ {
+ "name": "aik099/phpunit-mink",
+ "version": "v2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/minkphp/phpunit-mink.git",
+ "reference": "62889c5b065d69b695c87df6218b22147d78b147"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/minkphp/phpunit-mink/zipball/62889c5b065d69b695c87df6218b22147d78b147",
+ "reference": "62889c5b065d69b695c87df6218b22147d78b147",
+ "shasum": ""
+ },
+ "require": {
+ "behat/mink": "~1.6@dev",
+ "behat/mink-selenium2-driver": "~1.2",
+ "php": ">=5.3.2",
+ "phpunit/phpunit": ">=3.7.8",
+ "pimple/pimple": "~2.0|~3.0",
+ "symfony/event-dispatcher": "~2.4"
+ },
+ "require-dev": {
+ "aik099/coding-standard": "dev-master",
+ "mockery/mockery": "~0.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "aik099\\": "./library/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Alexander Obuhovich",
+ "email": "aik.bold@gmail.com"
+ }
+ ],
+ "description": "Library for using Mink in PHPUnit tests. Supports session sharing between tests in a test case.",
+ "homepage": "http://github.com/minkphp/phpunit-mink",
+ "keywords": [
+ "BrowserStack",
+ "Mink",
+ "Sauce",
+ "SauceLabs",
+ "phpunit",
+ "selenium",
+ "tests"
+ ],
+ "time": "2015-05-06 13:33:55"
+ },
+ {
+ "name": "behat/mink",
+ "version": "v1.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/minkphp/Mink.git",
+ "reference": "8b68523a339ec991bcd638b39dc8f04f808da88a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/minkphp/Mink/zipball/8b68523a339ec991bcd638b39dc8f04f808da88a",
+ "reference": "8b68523a339ec991bcd638b39dc8f04f808da88a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.1",
+ "symfony/css-selector": "~2.0"
+ },
+ "suggest": {
+ "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
+ "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation",
+ "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)",
+ "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Behat\\Mink\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ }
+ ],
+ "description": "Browser controller/emulator abstraction for PHP",
+ "homepage": "http://mink.behat.org/",
+ "keywords": [
+ "browser",
+ "testing",
+ "web"
+ ],
+ "time": "2015-02-04 17:02:06"
+ },
+ {
+ "name": "behat/mink-selenium2-driver",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/minkphp/MinkSelenium2Driver.git",
+ "reference": "8018fee80bf6573f909ece3e0dfc07d0eb352210"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/8018fee80bf6573f909ece3e0dfc07d0eb352210",
+ "reference": "8018fee80bf6573f909ece3e0dfc07d0eb352210",
+ "shasum": ""
+ },
+ "require": {
+ "behat/mink": "~1.6@dev",
+ "instaclick/php-webdriver": "~1.1",
+ "php": ">=5.3.1"
+ },
+ "type": "mink-driver",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Behat\\Mink\\Driver": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Pete Otaqui",
+ "email": "pete@otaqui.com",
+ "homepage": "https://github.com/pete-otaqui"
+ }
+ ],
+ "description": "Selenium2 (WebDriver) driver for Mink framework",
+ "homepage": "http://mink.behat.org/",
+ "keywords": [
+ "ajax",
+ "browser",
+ "javascript",
+ "selenium",
+ "testing",
+ "webdriver"
+ ],
+ "time": "2014-09-29 13:12:12"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119",
+ "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3,<8.0-DEV"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "2.0.*@ALPHA"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Instantiator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://github.com/doctrine/instantiator",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2014-10-13 12:58:55"
+ },
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c",
+ "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "1.3.3",
+ "satooshi/php-coveralls": "dev-master"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ],
+ "files": [
+ "hamcrest/Hamcrest.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "time": "2015-05-11 14:41:42"
+ },
+ {
+ "name": "instaclick/php-webdriver",
+ "version": "1.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/instaclick/php-webdriver.git",
+ "reference": "6aa16bbc02a5897200ab70316e0d2a01664afc51"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/6aa16bbc02a5897200ab70316e0d2a01664afc51",
+ "reference": "6aa16bbc02a5897200ab70316e0d2a01664afc51",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "satooshi/php-coveralls": "dev-master"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "WebDriver": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Justin Bishop",
+ "email": "jubishop@gmail.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Anthon Pang",
+ "email": "apang@softwaredevelopment.ca",
+ "role": "Fork Maintainer"
+ }
+ ],
+ "description": "PHP WebDriver for Selenium 2",
+ "homepage": "http://instaclick.com/",
+ "keywords": [
+ "browser",
+ "selenium",
+ "webdriver",
+ "webtest"
+ ],
+ "time": "2015-04-05 19:52:55"
+ },
+ {
+ "name": "mindplay/annotations",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-annotations/php-annotations.git",
+ "reference": "3e6d5ea14fcc032e18dd0f81b014aa7ca47a1881"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-annotations/php-annotations/zipball/3e6d5ea14fcc032e18dd0f81b014aa7ca47a1881",
+ "reference": "3e6d5ea14fcc032e18dd0f81b014aa7ca47a1881",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/php-code-coverage": "~1.2.1",
+ "phpunit/php-file-iterator": ">=1.3.0@stable"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "mindplay\\annotations\\": "src\\annotations"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0+"
+ ],
+ "authors": [
+ {
+ "name": "Rasmus Schultz",
+ "email": "rasmus@mindplay.dk"
+ }
+ ],
+ "description": "Industrial-strength annotations for PHP",
+ "homepage": "http://blog.mindplay.dk/",
+ "keywords": [
+ "annotations",
+ "framework"
+ ],
+ "time": "2015-04-15 12:54:35"
+ },
+ {
+ "name": "mockery/mockery",
+ "version": "0.9.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/padraic/mockery.git",
+ "reference": "70bba85e4aabc9449626651f48b9018ede04f86b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/padraic/mockery/zipball/70bba85e4aabc9449626651f48b9018ede04f86b",
+ "reference": "70bba85e4aabc9449626651f48b9018ede04f86b",
+ "shasum": ""
+ },
+ "require": {
+ "hamcrest/hamcrest-php": "~1.1",
+ "lib-pcre": ">=7.0",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.9.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Mockery": "library/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Pádraic Brady",
+ "email": "padraic.brady@gmail.com",
+ "homepage": "http://blog.astrumfutura.com"
+ },
+ {
+ "name": "Dave Marshall",
+ "email": "dave.marshall@atstsolutions.co.uk",
+ "homepage": "http://davedevelopment.co.uk"
+ }
+ ],
+ "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
+ "homepage": "http://github.com/padraic/mockery",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "library",
+ "mock",
+ "mock objects",
+ "mockery",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "time": "2015-04-02 19:54:00"
},
{
"name": "nikic/php-parser",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "dff239267fd1befa1cd40430c9ed12591aa720ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dff239267fd1befa1cd40430c9ed12591aa720ca",
"reference": "dff239267fd1befa1cd40430c9ed12591aa720ca",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=5.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"files": [
"lib/bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov"
}
],
"description": "A PHP parser written in PHP",
"keywords": [
"parser",
"php"
],
"time": "2015-05-02 15:40:40"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "dflydev/markdown": "~1.0",
+ "erusev/parsedown": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "phpDocumentor": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "mike.vanriel@naenius.com"
+ }
+ ],
+ "time": "2015-02-03 12:10:50"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+ "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "phpdocumentor/reflection-docblock": "~2.0",
+ "sebastian/comparator": "~1.1"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Prophecy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2015-04-27 22:15:08"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "2.0.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "934fd03eb6840508231a7f73eb8940cf32c3b66c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/934fd03eb6840508231a7f73eb8940cf32c3b66c",
+ "reference": "934fd03eb6840508231a7f73eb8940cf32c3b66c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "phpunit/php-file-iterator": "~1.3",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-token-stream": "~1.3",
+ "sebastian/environment": "~1.0",
+ "sebastian/version": "~1.0"
+ },
+ "require-dev": {
+ "ext-xdebug": ">=2.1.4",
+ "phpunit/phpunit": "~4"
+ },
+ "suggest": {
+ "ext-dom": "*",
+ "ext-xdebug": ">=2.2.1",
+ "ext-xmlwriter": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2015-04-11 04:35:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb",
+ "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2015-04-02 05:19:05"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
+ "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "Text/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2014-01-30 17:20:04"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
+ "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "PHP/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ ""
+ ],
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2013-08-02 07:42:54"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "eab81d02569310739373308137284e0158424330"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/eab81d02569310739373308137284e0158424330",
+ "reference": "eab81d02569310739373308137284e0158424330",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "time": "2015-04-08 04:46:07"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "4.6.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "3afe303d873a4d64c62ef84de491b97b006fbdac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3afe303d873a4d64c62ef84de491b97b006fbdac",
+ "reference": "3afe303d873a4d64c62ef84de491b97b006fbdac",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "php": ">=5.3.3",
+ "phpspec/prophecy": "~1.3,>=1.3.1",
+ "phpunit/php-code-coverage": "~2.0,>=2.0.11",
+ "phpunit/php-file-iterator": "~1.4",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-timer": "~1.0",
+ "phpunit/phpunit-mock-objects": "~2.3",
+ "sebastian/comparator": "~1.1",
+ "sebastian/diff": "~1.2",
+ "sebastian/environment": "~1.2",
+ "sebastian/exporter": "~1.2",
+ "sebastian/global-state": "~1.0",
+ "sebastian/version": "~1.0",
+ "symfony/yaml": "~2.1|~3.0"
+ },
+ "suggest": {
+ "phpunit/php-invoker": "~1.1"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.6.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2015-04-29 15:18:52"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "2.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "74ffb87f527f24616f72460e54b595f508dccb5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/74ffb87f527f24616f72460e54b595f508dccb5c",
+ "reference": "74ffb87f527f24616f72460e54b595f508dccb5c",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "~1.0,>=1.0.2",
+ "php": ">=5.3.3",
+ "phpunit/php-text-template": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ],
+ "time": "2015-04-02 05:36:41"
+ },
+ {
+ "name": "pimple/pimple",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/silexphp/Pimple.git",
+ "reference": "876bf0899d01feacd2a2e83f04641e51350099ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/silexphp/Pimple/zipball/876bf0899d01feacd2a2e83f04641e51350099ef",
+ "reference": "876bf0899d01feacd2a2e83f04641e51350099ef",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Pimple": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
+ "homepage": "http://pimple.sensiolabs.org",
+ "keywords": [
+ "container",
+ "dependency injection"
+ ],
+ "time": "2014-07-24 09:48:15"
+ },
+ {
+ "name": "qa-tools/qa-tools",
+ "version": "v1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/qa-tools/qa-tools.git",
+ "reference": "5456f64af5b94c5655f21a592b8817690c2096c7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/qa-tools/qa-tools/zipball/5456f64af5b94c5655f21a592b8817690c2096c7",
+ "reference": "5456f64af5b94c5655f21a592b8817690c2096c7",
+ "shasum": ""
+ },
+ "require": {
+ "behat/mink": "~1.6",
+ "mindplay/annotations": "~1.2@dev",
+ "php": ">=5.3.2"
+ },
+ "replace": {
+ "aik099/qa-tools": "self.version"
+ },
+ "require-dev": {
+ "aik099/coding-standard": "dev-master",
+ "behat/mink-selenium2-driver": "~1.2@dev",
+ "brianium/paratest": "~0.7",
+ "mockery/mockery": "~0.9"
+ },
+ "suggest": {
+ "aik099/phpunit-mink": "Allows to use PageObject inside PHPUnit tests and remotely collect code coverage information"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "QATools\\QATools\\": "./library/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Alexander Obuhovich",
+ "email": "aik.bold@gmail.com"
+ },
+ {
+ "name": "Dmitry Kushnikov",
+ "email": "dkushnikov@gmail.com"
+ },
+ {
+ "name": "Michael Geppert",
+ "email": "evangelion1204@aol.com"
+ }
+ ],
+ "description": "Library that provides easy-to-use way of interaction with web-page elements in functional tests using PageObject pattern.",
+ "keywords": [
+ "BEM",
+ "HtmlElements",
+ "Mink",
+ "PageObject",
+ "acceptance",
+ "functional",
+ "phpunit",
+ "tests"
+ ],
+ "time": "2014-09-26 15:15:51"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "1dd8869519a225f7f2b9eb663e225298fade819e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dd8869519a225f7f2b9eb663e225298fade819e",
+ "reference": "1dd8869519a225f7f2b9eb663e225298fade819e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/diff": "~1.2",
+ "sebastian/exporter": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "time": "2015-01-29 16:28:08"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3",
+ "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "http://www.github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff"
+ ],
+ "time": "2015-02-22 15:13:53"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5a8c7d31914337b69923db26c4221b81ff5a196e",
+ "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "time": "2015-01-01 10:01:08"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "84839970d05254c73cde183a721c7af13aede943"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/84839970d05254c73cde183a721c7af13aede943",
+ "reference": "84839970d05254c73cde183a721c7af13aede943",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2015-01-27 07:23:06"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
+ "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2014-10-06 09:23:50"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "3989662bbb30a29d20d9faa04a846af79b276252"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/3989662bbb30a29d20d9faa04a846af79b276252",
+ "reference": "3989662bbb30a29d20d9faa04a846af79b276252",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2015-01-24 09:48:32"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ab931d46cd0d3204a91e1b9a40c4bc13032b58e4",
+ "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "time": "2015-02-24 06:35:25"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v2.6.7",
+ "target-dir": "Symfony/Component/CssSelector",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/CssSelector.git",
+ "reference": "189cf0f7f56d7c4be3b778df15a7f16a29f3680d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/CssSelector/zipball/189cf0f7f56d7c4be3b778df15a7f16a29f3680d",
+ "reference": "189cf0f7f56d7c4be3b778df15a7f16a29f3680d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\CssSelector\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony CssSelector Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-05-02 15:18:45"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v2.6.7",
+ "target-dir": "Symfony/Component/EventDispatcher",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/EventDispatcher.git",
+ "reference": "672593bc4b0043a0acf91903bb75a1c82d8f2e02"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/672593bc4b0043a0acf91903bb75a1c82d8f2e02",
+ "reference": "672593bc4b0043a0acf91903bb75a1c82d8f2e02",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.0,>=2.0.5",
+ "symfony/dependency-injection": "~2.6",
+ "symfony/expression-language": "~2.6",
+ "symfony/phpunit-bridge": "~2.7",
+ "symfony/stopwatch": "~2.3"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony EventDispatcher Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-05-02 15:18:45"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v2.6.7",
+ "target-dir": "Symfony/Component/Yaml",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/Yaml.git",
+ "reference": "f157ab074e453ecd4c0fa775f721f6e67a99d9e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/Yaml/zipball/f157ab074e453ecd4c0fa775f721f6e67a99d9e2",
+ "reference": "f157ab074e453ecd4c0fa775f721f6e67a99d9e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\Yaml\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-05-02 15:18:45"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
- "aik099/coding-standard": 20
+ "aik099/coding-standard": 20,
+ "mindplay/annotations": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
Index: branches/5.3.x
===================================================================
--- branches/5.3.x (revision 16194)
+++ branches/5.3.x (revision 16195)
Property changes on: branches/5.3.x
___________________________________________________________________
Modified: svn:ignore
## -2,6 +2,8 ##
.buildpath
.idea
vendor
+build
atlassian-ide-plugin.xml
.project
.settings
+tmp
Modified: svn:mergeinfo
## -0,0 +0,1 ##
Merged /in-portal/branches/5.2.x:r16124-16194

Event Timeline