diff --git a/include/Ajax/CommonAjax.php b/include/Ajax/CommonAjax.php new file mode 100644 index 0000000..ea57325 --- /dev/null +++ b/include/Ajax/CommonAjax.php @@ -0,0 +1,23 @@ + diff --git a/include/Ajax/TagCloud.php b/include/Ajax/TagCloud.php new file mode 100644 index 0000000..306a32d --- /dev/null +++ b/include/Ajax/TagCloud.php @@ -0,0 +1,67 @@ +id; +if($ajaxaction == "SAVETAG") +{ + + require_once('include/freetag/freetag.class.php'); + $tagfields=function_exists(iconv) ? @iconv("UTF-8",$default_charset,$_REQUEST['tagfields']) : $_REQUEST['tagfields']; + $tagfields =str_replace(array("'",'"'),'',$tagfields); + if($tagfields != "") + { + $freetag = new freetag(); + if (isset($_REQUEST["tagfields"]) && trim($_REQUEST["tagfields"]) != "") + { + $freetag->tag_object($userid,$crmid,$tagfields,$module); + $tagcloud = $freetag->get_tag_cloud_html($module,$userid,$crmid); + echo $tagcloud; + } + } + else + { + echo ":#:FAILURE"; + } +} +elseif($ajaxaction == 'GETTAGCLOUD') +{ + require_once('include/freetag/freetag.class.php'); + $freetag = new freetag(); + if(trim($module) != "") + { + $tagcloud = $freetag->get_tag_cloud_html($module,$userid,$crmid); + echo $tagcloud; + }else + { + $tagcloud = $freetag->get_tag_cloud_html("",$userid); + echo $tagcloud; + } +}elseif($ajaxaction == 'DELETETAG') +{ + if(is_numeric($_REQUEST['tagid'])) + { + $tagid = $_REQUEST['tagid']; + global $adb; + $query="delete from vtiger_freetagged_objects where tag_id=? and object_id=?"; + $result=$adb->pquery($query, array($tagid, $crmid)); + echo 'SUCCESS'; + }else + { + die("An invalid tagid to delete."); + } + +} +?> diff --git a/include/ChartUtils.php b/include/ChartUtils.php new file mode 100644 index 0000000..203fba6 --- /dev/null +++ b/include/ChartUtils.php @@ -0,0 +1,523 @@ += 14) + $name = substr($name, 0, 44); + if ($pos >= 2) { + $val = explode(" ", $name); + $n = count($val) - 1; + $x = ""; + for ($j = 0; $j < count($val); $j++) { + if ($j != $n) { + $x .=" " . $val[$j]; + } else { + $x .= "@#" . $val[$j]; + } + } + $name = $x; + } + $name = str_replace("@#", " ", $name); + $temp[] = html_entity_decode($name, ENT_QUOTES, $default_charset); + } + $xaxisData = $temp; + + // Set the basic parameters of the graph + $canvas = & Image_Canvas::factory('png', array('width' => $width, 'height' => $height, 'usemap' => true)); + $imagemap = $canvas->getImageMap(); + $graph = & Image_Graph::factory('graph', $canvas); + + $font = & $graph->addNew('font', calculate_font_name($lang_crm)); + $font->setSize(8); + $font_color = "#000000"; + $font->setColor($font_color); + $graph->setFont($font); + + $titlestr = & Image_Graph::factory('title', array($title, 8)); + $plotarea = & Image_Graph::factory('plotarea', array( + 'axis', + 'axis', + $charttype + )); + $graph->add(Image_Graph::vertical($titlestr, $plotarea, 5)); + + // Now create a bar plot + $max = 0; + // To create unique lables we need to keep track of lable name and its count + $uniquex = array(); + + $xlabels = array(); + $dataset = & Image_Graph::factory('dataset'); + if ($charttype == 'horizontal') { + $fill = & Image_Graph::factory('gradient', array(IMAGE_GRAPH_GRAD_VERTICAL_MIRRORED, $color, 'white')); + } else { + $fill = & Image_Graph::factory('gradient', array(IMAGE_GRAPH_GRAD_HORIZONTAL_MIRRORED, $color, 'white')); + } + + for ($i = 0; $i < count($yaxisData); $i++) { + $x = 1 + $i; + if ($yaxisData[$i] >= $max) + $max = $yaxisData[$i]; + $dataset->addPoint( + $x, + $yaxisData[$i], + array( + 'url' => $target[$i], + 'alt' => $alts[$i] . '=' . $yaxisData[$i] + ) + ); + $xlabels[$x] = $xaxisData[$i]; + + // To have unique names even in case of duplicates let us add the id + $xaxisData_appearance = $uniquex[$xaxisData[$i]]; + if ($xaxisData_appearance == null) { + $uniquex[$xaxisData[$i]] = 1; + } else { + $xlabels[$x] = $xaxisData[$i] . ' [' . $xaxisData_appearance . ']'; + $uniquex[$xaxisData[$i]] = $xaxisData_appearance + 1; + } + } + $bplot = & $plotarea->addNew('bar', $dataset); + $bplot->setFillStyle($fill); + + //You can change the width of the bars if you like + if (!empty($xaxisData)) + $bplot->setBarWidth($barwidth / count($xaxisData), "%"); + //$bplot->setPadding(array('top'=>10)); + $bplot->setBackground(Image_Graph::factory('gradient', array(IMAGE_GRAPH_GRAD_HORIZONTAL, 'white', 'white'))); + $xaxis = & $plotarea->getAxis(IMAGE_GRAPH_AXIS_X); + $yaxis = & $plotarea->getAxis(IMAGE_GRAPH_AXIS_Y); + $yaxis->setFontSize(8); + $xaxis->setFontSize(8); + + if ($charttype == 'horizontal') { // Invert X-axis and put Y-axis at bottom + $xaxis->setInverted(false); + $yaxis->setAxisIntersection('max'); + } + + // set grid + $gridY = & $plotarea->addNew('line_grid', IMAGE_GRAPH_AXIS_Y); + $gridY->setLineColor('#FFFFFF@0.5'); + $gridY2 = & $plotarea->addNew('bar_grid', null, IMAGE_GRAPH_AXIS_Y); + $gridY2->setFillColor('#FFFFFF@0.2'); + + + // Add some grace to y-axis so the bars doesn't go all the way to the end of the plot area + $yaxis->forceMaximum(round(($max * 1.1) + 0.5)); + $ticks = get_tickspacing(round(($max * 1.1) + 0.5)); + + // First make the labels look right + if ($charttype == 'horizontal') + $yaxis->setFontAngle('vertical'); + $yaxis->setLabelInterval($ticks[0]); + $yaxis->setTickOptions(-5, 0); + $yaxis->setLabelInterval($ticks[1], 2); + $yaxis->setTickOptions(-2, 0, 2); + + // Create the xaxis labels + $array_data = & Image_Graph::factory('Image_Graph_DataPreprocessor_Array', + array($xlabels) + ); + + // The fix the tick marks + $xaxis->setDataPreprocessor($array_data); + $xaxis->forceMinimum(0.5); + $xaxis->forceMaximum(0.5 + count($yaxisData)); + if ($charttype == 'vertical') + $xaxis->setFontAngle('vertical'); + $xaxis->setLabelInterval(1); + $xaxis->setTickOptions(0, 0); + $xaxis->setLabelInterval(2, 2); + + // set markers + if ($width > 400 && $height > 400) { + $marker = & $graph->addNew('value_marker', IMAGE_GRAPH_VALUE_Y); + $marker->setFillColor('000000@0.0'); + $marker->setBorderColor('000000@0.0'); + $marker->setFontSize(8); + // shift markers 20 pix right + if ($charttype == 'horizontal') { + $marker_pointing = & $graph->addNew('Image_Graph_Marker_Pointing', array(10, 0, & $marker)); + } else { + $marker_pointing = & $graph->addNew('Image_Graph_Marker_Pointing', array(0, -10, & $marker)); + } + $marker_pointing->setLineColor('000000@0.0'); + $bplot->setMarker($marker_pointing); + } + + //Getting the graph in the form of html page + $img = $graph->done( + array( + 'tohtml' => true, + 'border' => 0, + 'filename' => $cache_file_name, + 'filepath' => '', + 'urlpath' => '' + )); + + return $img; + } + + // Function to generate Pie Chart + public static function getPieChart($xaxisData, $yaxisData, $title='', $width='', $height='', $charttype='vertical', $cachedFileName=false, $target=false, $color='') { + + global $log, $lang_crm, $default_charset; + + require_once('include/utils/utils.php'); + require_once('include/utils/GraphUtils.php'); + include_once ('Image/Graph.php'); + include_once ('Image/Canvas.php'); + + if ($cachedFileName === false) { + $cache_file_name = 'cache/images/pie_chart_' . time() . '.png'; + } else { + $cache_file_name = $cachedFileName; + } + + if (empty($width)) + $width = '500'; + if (empty($height)) + $height = '400'; + if ($target === false) + $target = array(); + + $alts = array(); + $temp = array(); + for ($i = 0; $i < count($xaxisData); $i++) { + $name = html_entity_decode($xaxisData[$i], ENT_QUOTES, $default_charset); + $pos = substr_count($name, " "); + $alts[] = $name; + //If the datax value of a string is greater, adding '\n' to it so that it'll come in 2nd line + if (strlen($name) >= 14) + $name = substr($name, 0, 34); + if ($pos >= 2) { + $val = explode(" ", $name); + $n = count($val) - 1; + $x = ""; + for ($j = 0; $j < count($val); $j++) { + if ($j != $n) { + $x .=" " . $val[$j]; + } else { + $x .= "@#" . $val[$j]; + } + } + $name = $x; + } + $name = str_replace("@#", "\n", $name); + $temp[] = $name; + } + $xaxisData = $temp; + $width = $width + ($width / 5); + + $canvas = & Image_Canvas::factory('png', array('width' => $width, 'height' => $height, 'usemap' => true)); + $imagemap = $canvas->getImageMap(); + $graph = & Image_Graph::factory('graph', $canvas); + $font = & $graph->addNew('font', calculate_font_name($lang_crm)); + $font->setSize(8); + $font->setColor($color); + $graph->setFont($font); + // create the plotarea layout + $title = & Image_Graph::factory('title', array($title, 10)); + $plotarea = & Image_Graph::factory('plotarea', array( + 'category', + 'axis' + )); + $graph->add(Image_Graph::vertical($title, $plotarea, 5)); + // To create unique lables we need to keep track of lable name and its count + $uniquex = array(); + // Generate colours + $colors = color_generator(count($yaxisData), '#33DDFF', '#3322FF'); + $dataset = & Image_Graph::factory('dataset'); + $fills = & Image_Graph::factory('Image_Graph_Fill_Array'); + $sum = 0; + $pcvalues = array(); + for ($i = 0; $i < count($yaxisData); $i++) { + $sum += $yaxisData[$i]; + } + for ($i = 0; $i < count($yaxisData); $i++) { + // To have unique names even in case of duplicates let us add the id + $datalabel = $xaxisData[$i]; + $xaxisData_appearance = $uniquex[$xaxisData[$i]]; + if ($xaxisData_appearance == null) { + $uniquex[$xaxisData[$i]] = 1; + } else { + $datalabel = $xaxisData[$i] . ' [' . $xaxisData_appearance . ']'; + $uniquex[$xaxisData[$i]] = $xaxisData_appearance + 1; + } + $dataset->addPoint( + $datalabel, + $yaxisData[$i], + array( + 'url' => $target[$i], + 'alt' => $alts[$i] . '=' . sprintf('%0.1f%%', 100 * $yaxisData[$i] / $sum) + ) + ); + $pcvalues[$yaxisData[$i]] = sprintf('%0.1f%%', 100 * $yaxisData[$i] / $sum); + $fills->addColor($colors[$i]); + } + if ($sum == 0) + return null; + // create the pie chart and associate the filling colours + $gbplot = & $plotarea->addNew('pie', $dataset); + $plotarea->setPadding(array('top' => 0, 'bottom' => 0, 'left' => 0, 'right' => ($width / 20))); + $plotarea->hideAxis(); + $gbplot->setFillStyle($fills); + // format the data values + $marker_array = & Image_Graph::factory('Image_Graph_DataPreprocessor_Array', array($pcvalues)); + // set markers + $marker = & $graph->addNew('value_marker', IMAGE_GRAPH_VALUE_Y); + $marker->setDataPreprocessor($marker_array); + $marker->setFillColor('#FFFFFF'); + $marker->setBorderColor($color); + $marker->setFontColor($color); + $marker->setFontSize(8); + $pointingMarker = & $graph->addNew('Image_Graph_Marker_Pointing_Angular', array(20, &$marker)); + $gbplot->setMarker($pointingMarker); + $legend_box = & $plotarea->addNew('legend'); + $legend_box->setPadding(array('top' => 20, 'bottom' => 0, 'left' => 0, 'right' => 0)); + $legend_box->setFillColor('#F5F5F5'); + $legend_box->showShadow(); + + $img = $graph->done(array( + 'tohtml' => true, + 'border' => 0, + 'filename' => $cache_file_name, + 'filepath' => '', + 'urlpath' => '' + )); + return $img; + } + + //Generates Chart Data in form of an array from the Query Result of reports + public static function generateChartDataFromReports($queryResult, $groupbyField, $fieldDetails='', $reportid='') { + require_once 'modules/Reports/CustomReportUtils.php'; + require_once('include/Webservices/Utils.php'); + require_once('include/Webservices/Query.php'); + global $adb, $current_user, $theme, $default_charset; + $inventorymodules = array('Quotes', 'SalesOrder', 'PurchaseOrder', 'Invoice', 'Products', 'PriceBooks', 'Vendors', 'Services'); + $rows = $adb->num_rows($queryResult); + $condition = "is"; + $current_theme = $theme; + $groupByFields = array(); + $yaxisArray = array(); + $ChartDataArray = array(); + $target_val = array(); + + $report = new ReportRun($reportid); + $restrictedModules = array(); + if($report->secondarymodule!='') { + $reportModules = explode(":",$report->secondarymodule); + } else { + $reportModules = array(); + } + array_push($reportModules,$report->primarymodule); + + $restrictedModules = false; + foreach($reportModules as $mod) { + if(isPermitted($mod,'index') != "yes" || vtlib_isModuleActive($mod) == false) { + if(!is_array($restrictedModules)) $restrictedModules = array(); + $restrictedModules[] = $mod; + } + } + + if(is_array($restrictedModules) && count($restrictedModules) > 0) { + $ChartDataArray['error'] = "

".getTranslatedString('LBL_NO_ACCESS', 'Reports').' - '.implode(',', $restrictedModules)."

"; + return $ChartDataArray; + } + + if ($fieldDetails != '') { + list($tablename, $colname, $module_field, $fieldname, $single) = explode(":", $fieldDetails); + list($module, $field) = split("_", $module_field); + $dateField = false; + if ($single == 'D') { + $dateField = true; + $query = "SELECT * FROM vtiger_reportgroupbycolumn WHERE reportid=? ORDER BY sortid"; + $result = $adb->pquery($query, array($reportid)); + $criteria = $adb->query_result($result, 0, 'dategroupbycriteria'); + } + } + preg_match('/&/', $groupbyField, $matches); + if (!empty($matches)) { + $groupfield = str_replace('&', '&', $groupbyField); + $groupbyField = $report->replaceSpecialChar($groupfield); + } + $handler = vtws_getModuleHandlerFromName($module, $current_user); + $meta = $handler->getMeta(); + $meta->retrieveMeta(); + $referenceFields = $meta->getReferenceFieldDetails(); + + if($rows > 0) { + $resultRow = $adb->query_result_rowdata($queryResult, 0); + if(!array_key_exists($groupbyField, $resultRow)) { + $ChartDataArray['error'] = "

".getTranslatedString('LBL_NO_PERMISSION_FIELD', 'Dashboard')."

"; + return $ChartDataArray; + } + } + for ($i = 0; $i < $rows; $i++) { + $groupFieldValue = $adb->query_result($queryResult, $i, strtolower($groupbyField)); + $decodedGroupFieldValue = html_entity_decode($groupFieldValue, ENT_QUOTES, $default_charset); + if (!empty($groupFieldValue)) { + if (in_array($module_field, $report->append_currency_symbol_to_value)) { + $valueComp = explode('::', $groupFieldValue); + $groupFieldValue = $valueComp[1]; + } + if ($dateField) { + if (!empty($groupFieldValue)) + $groupByFields[] = CustomReportUtils::getXAxisDateFieldValue($groupFieldValue, $criteria); + else + $groupByFields[] = "Null"; + } + else if (in_array($fieldname, array_keys($referenceFields))) { + if (count($referenceFields[$fieldname]) > 1) { + $refenceModule = CustomReportUtils::getEntityTypeFromName($decodedGroupFieldValue, $referenceFields[$fieldname]); + } + else { + $refenceModule = $referenceFields[$fieldname][0]; + } + $groupByFields[] = $groupFieldValue; + + if ($fieldname == 'currency_id' && in_array($module, $inventorymodules)) { + $tablename = 'vtiger_currency_info'; + } elseif ($refenceModule == 'DocumentFolders' && $fieldname == 'folderid') { + $tablename = 'vtiger_attachmentsfolder'; + $colname = 'foldername'; + } else { + require_once "modules/$refenceModule/$refenceModule.php"; + $focus = new $refenceModule(); + $tablename = $focus->table_name; + $colname = $focus->list_link_field; + $condition = "c"; + } + } else { + $groupByFields[] = $groupFieldValue; + } + $yaxisArray[] = $adb->query_result($queryResult, $i, 'groupby_count'); + if ($fieldDetails != '') { + if ($dateField) { + $advanceSearchCondition = CustomReportUtils::getAdvanceSearchCondition($fieldDetails, $criteria, $groupFieldValue); + if ($module == 'Calendar') { + $link_val = "index.php?module=" . $module . "&query=true&action=ListView&" . $advanceSearchCondition; + }else + $link_val = "index.php?module=" . $module . "&query=true&action=index&" . $advanceSearchCondition; + } + else { + $cvid = getCvIdOfAll($module); + $esc_search_str = urlencode($decodedGroupFieldValue); + if ($single == 'DT') { + $esc_search_str = urlencode($groupFieldValue); + if (strtolower($fieldname) == 'modifiedtime' || strtolower($fieldname) == 'createdtime') { + $tablename = 'vtiger_crmentity'; + $colname = $fieldname; + } + } + if ($fieldname == 'assigned_user_id') { + $tablename = 'vtiger_crmentity'; + $colname = 'smownerid'; + } + + if ($module == 'Calendar') { + $link_val = "index.php?module=" . $module . "&action=ListView&search_text=" . $esc_search_str . "&search_field=" . $fieldname . "&searchtype=BasicSearch&query=true&operator=e&viewname=" . $cvid; + } else { + $link_val = "index.php?module=" . $module . "&action=index&search_text=" . $esc_search_str . "&search_field=" . $fieldname . "&searchtype=BasicSearch&query=true&operator=e&viewname=" . $cvid; + } + } + + $target_val[] = $link_val; + } + } + } + if(count($groupByFields) == 0) { + $ChartDataArray['error'] = "
".getTranslatedString('LBL_NO_DATA', 'Reports')." diff --git a/include/ComboStrings.php b/include/ComboStrings.php new file mode 100644 index 0000000..91166cf --- /dev/null +++ b/include/ComboStrings.php @@ -0,0 +1,364 @@ + Array(''=>'' + , 'Analyst'=>'Analyst' + , 'Competitor'=>'Competitor' + , 'Customer'=>'Customer' + , 'Integrator'=>'Integrator' + , 'Investor'=>'Investor' + , 'Partner'=>'Partner' + , 'Press'=>'Press' + , 'Prospect'=>'Prospect' + , 'Reseller'=>'Reseller' + , 'Other'=>'Other' + ), +'industry_dom' => Array(''=>'' + , 'Apparel'=>'Apparel' + , 'Banking'=>'Banking' + , 'Biotechnology'=>'Biotechnology' + , 'Chemicals'=>'Chemicals' + , 'Communications'=>'Communications' + , 'Construction'=>'Construction' + , 'Consulting'=>'Consulting' + , 'Education'=>'Education' + , 'Electronics'=>'Electronics' + , 'Energy'=>'Energy' + , 'Engineering'=>'Engineering' + , 'Entertainment'=>'Entertainment' + , 'Environmental'=>'Environmental' + , 'Finance'=>'Finance' + , 'Food & Beverage'=>'Food & Beverage' + , 'Government'=>'Government' + , 'Healthcare'=>'Healthcare' + , 'Hospitality'=>'Hospitality' + , 'Insurance'=>'Insurance' + , 'Machinery'=>'Machinery' + , 'Manufacturing'=>'Manufacturing' + , 'Media'=>'Media' + , 'Not For Profit'=>'Not For Profit' + , 'Recreation'=>'Recreation' + , 'Retail'=>'Retail' + , 'Shipping'=>'Shipping' + , 'Technology'=>'Technology' + , 'Telecommunications'=>'Telecommunications' + , 'Transportation'=>'Transportation' + , 'Utilities'=>'Utilities' + , 'Other'=>'Other' + ), +'leadsource_dom' => Array(''=>'' + , 'Cold Call'=>'Cold Call' + , 'Existing Customer'=>'Existing Customer' + , 'Self Generated'=>'Self Generated' + , 'Employee'=>'Employee' + , 'Partner'=>'Partner' + , 'Public Relations'=>'Public Relations' + , 'Direct Mail'=>'Direct Mail' + , 'Conference'=>'Conference' + , 'Trade Show'=>'Trade Show' + , 'Web Site'=>'Web Site' + , 'Word of mouth'=>'Word of mouth' + , 'Other'=>'Other' + ), +'leadstatus_dom' => Array(''=>'' + , 'Attempted to Contact'=>'Attempted to Contact' + , 'Cold'=>'Cold' + , 'Contact in Future'=>'Contact in Future' + , 'Contacted'=>'Contacted' + , 'Hot'=>'Hot' + , 'Junk Lead'=>'Junk Lead' + , 'Lost Lead'=>'Lost Lead' + , 'Not Contacted'=>'Not Contacted' + , 'Pre Qualified'=>'Pre Qualified' + , 'Qualified'=>'Qualified' + , 'Warm'=>'Warm' + ), +'rating_dom' => Array(''=>'' + , 'Acquired'=>'Acquired' + , 'Active'=>'Active' + , 'Market Failed'=>'Market Failed' + , 'Project Cancelled'=>'Project Cancelled' + , 'Shutdown'=>'Shutdown' + ), +'opportunity_type_dom' => Array(''=>'' + , 'Existing Business'=>'Existing Business' + , 'New Business'=>'New Business' + ), +'sales_stage_dom' => Array('Prospecting'=>'Prospecting' + , 'Qualification'=>'Qualification' + , 'Needs Analysis'=>'Needs Analysis' + , 'Value Proposition'=>'Value Proposition' + , 'Id. Decision Makers'=>'Id. Decision Makers' + , 'Perception Analysis'=>'Perception Analysis' + , 'Proposal/Price Quote'=>'Proposal/Price Quote' + , 'Negotiation/Review'=>'Negotiation/Review' + , 'Closed Won'=>'Closed Won' + , 'Closed Lost'=>'Closed Lost' + ), +'salutationtype_dom' => Array(''=>'' + , 'Mr.'=>'Mr.' + , 'Ms.'=>'Ms.' + , 'Mrs.'=>'Mrs.' + , 'Dr.'=>'Dr.' + , 'Prof.'=>'Prof.' + ), +'eventstatus_dom' => Array('Planned'=>'Planned' + , 'Held'=>'Held' + , 'Not Held'=>'Not Held' + ), +'taskstatus_dom' => Array('Not Started'=>'Not Started' + , 'In Progress'=>'In Progress' + , 'Completed'=>'Completed' + , 'Pending Input'=>'Pending Input' + , 'Deferred'=>'Deferred' + , 'Planned'=>'Planned' + ), +'taskpriority_dom' => Array('High'=>'High' + ,'Medium'=>'Medium' + ,'Low'=>'Low' + ), +'duration_minutes_dom' => Array('00'=>'00' + , '15'=>'15' + , '30'=>'30' + , '45'=>'45' + ), +'productcategory_dom' => Array(''=>'' + , 'Hardware'=>'Hardware' + , 'Software'=>'Software' + , 'CRM Applications'=>'CRM Applications' + ), +'manufacturer_dom' => Array(''=>'' + , 'AltvetPet Inc.'=>'AltvetPet Inc.' + , 'LexPon Inc.'=>'LexPon Inc.' + , 'MetBeat Corp'=>'MetBeat Corp' + ), +'ticketcategories_dom' => Array('Big Problem'=>'Big Problem' + ,'Small Problem'=>'Small Problem' + ,'Other Problem'=>'Other Problem' + ), +'ticketpriorities_dom' => Array('Low'=>'Low' + ,'Normal'=>'Normal' + ,'High'=>'High' + ,'Urgent'=>'Urgent' + ), +'ticketseverities_dom' => Array('Minor'=>'Minor' + ,'Major'=>'Major' + ,'Feature'=>'Feature' + ,'Critical'=>'Critical' + ), + +'ticketstatus_dom' => Array('Open'=>'Open' + ,'In Progress'=>'In Progress' + ,'Wait For Response'=>'Wait For Response' + ,'Closed'=>'Closed' + ), + +'activitytype_dom' => Array('Call'=>'Call' + , 'Meeting'=>'Meeting' + ), + +'faqcategories_dom' => Array('General'=>'General' + ), + +'faqstatus_dom' => Array('Draft'=>'Draft' + ,'Reviewed'=>'Reviewed' + ,'Published'=>'Published' + ,'Obsolete'=>'Obsolete' + ), + + +'currency_dom' => Array('Rupees'=>'Rupees', + 'Dollar'=>'Dollar', + 'Euro'=>'Euro' + ), + +'visibility_dom' => Array('Private'=>'Private', + 'Public'=>'Public' + ), + +'usageunit_dom' => Array('Box'=>'Box', + 'Carton'=>'Carton', + 'Dozen'=>'Dozen', + 'Each'=>'Each', + 'Hours'=>'Hours', + 'Impressions'=>'Impressions', + 'Lb'=>'Lb', + 'M'=>'M', + 'Pack'=>'Pack', + 'Pages'=>'Pages', + 'Pieces'=>'Pieces', + 'Quantity'=>'Quantity', + 'Reams'=>'Reams', + 'Sheet'=>'Sheet', + 'Spiral Binder'=>'Spiral Binder', + 'Sq Ft'=>'Sq Ft' + ), + +'glacct_dom' => Array('300-Sales-Software'=>'300-Sales-Software', + '301-Sales-Hardware'=>'301-Sales-Hardware', + '302-Rental-Income'=>'302-Rental-Income', + '303-Interest-Income'=>'303-Interest-Income', + '304-Sales-Software-Support'=>'304-Sales-Software-Support', + '305-Sales Other'=>'305-Sales Other', + '306-Internet Sales'=>'306-Internet Sales', + '307-Service-Hardware Labor'=>'307-Service-Hardware Labor', + '308-Sales-Books'=>'308-Sales-Books' + ), + +'quotestage_dom' => Array('Created'=>'Created', + 'Delivered'=>'Delivered', + 'Reviewed'=>'Reviewed', + 'Accepted'=>'Accepted', + 'Rejected'=>'Rejected' + ), + +'carrier_dom' => Array('FedEx'=>'FedEx', + 'UPS'=>'UPS', + 'USPS'=>'USPS', + 'DHL'=>'DHL', + 'BlueDart'=>'BlueDart' + ), + +'taxclass_dom' => Array('SalesTax'=>'SalesTax', + 'Vat'=>'Vat' + ), + +'recurringtype_dom' => Array(''=>'', + 'Daily'=>'Daily', + 'Weekly'=>'Weekly', + 'Monthly'=>'Monthly', + 'Yearly'=>'Yearly' + ), + +'invoicestatus_dom' => Array('AutoCreated'=>'AutoCreated', + 'Created'=>'Created', + 'Approved'=>'Approved', + 'Sent'=>'Sent', + 'Credit Invoice'=>'Credit Invoice', + 'Paid'=>'Paid' + ), + +'postatus_dom' => Array('Created'=>'Created', + 'Approved'=>'Approved', + 'Delivered'=>'Delivered', + 'Cancelled'=>'Cancelled', + 'Received Shipment'=>'Received Shipment' + ), + +'sostatus_dom' => Array('Created'=>'Created', + 'Approved'=>'Approved', + 'Delivered'=>'Delivered', + 'Cancelled'=>'Cancelled' + ), + +'campaignstatus_dom' => Array(''=>'', + 'Planning'=>'Planning', + 'Active'=>'Active', + 'Inactive'=>'Inactive', + 'Completed'=>'Completed', + 'Cancelled'=>'Cancelled', ), + + +'campaigntype_dom' => Array(''=>'', + 'Conference'=>'Conference', + 'Webinar'=>'Webinar', + 'Trade Show'=>'Trade Show', 'Public Relations'=>'Public Relations', 'Partners'=>'Partners', + 'Referral Program'=>'Referral Program', + 'Advertisement'=>'Advertisement', + 'Banner Ads'=>'Banner Ads', + 'Direct Mail'=>'Direct Mail', + 'Email'=>'Email', + 'Telemarketing'=>'Telemarketing', + 'Others'=>'Others' + ), + +'expectedresponse_dom' => Array(''=>'', + 'Excellent'=>'Excellent', + 'Good'=>'Good', + 'Average'=>'Average', + 'Poor'=>'Poor' + ), +'status_dom' => Array('Active'=>'Active', + 'Inactive'=>'Inactive' + ), +'activity_view_dom' => Array('Today'=>'Today', + 'This Week'=>'This Week', + 'This Month'=>'This Month', + 'This Year'=>'This Year' + ), +'lead_view_dom' => Array('Today'=>'Today', + 'Last 2 Days'=>'Last 2 Days', + 'Last Week'=>'Last Week' + ), +'date_format_dom' => Array('dd-mm-yyyy'=>'dd-mm-yyyy', + 'mm-dd-yyyy'=>'mm-dd-yyyy', + 'yyyy-mm-dd'=>'yyyy-mm-dd' + ), +'reminder_interval_dom' => Array('None'=>'None', + '1 Minute'=>'1 Minute', + '5 Minutes'=>'5 Minutes', + '15 Minutes'=>'15 Minutes', + '30 Minutes'=>'30 Minutes', + '45 Minutes'=>'45 Minutes', + '1 Hour'=>'1 Hour', + '1 Day'=>'1 Day' + ), + +'recurring_frequency_dom' => Array('--None--'=>'--None--', + 'Daily' => 'Daily', + 'Weekly' => 'Weekly', + 'Monthly' => 'Monthly', + 'Quarterly' => 'Quarterly', + 'Yearly' => 'Yearly' + ), +'payment_duration_dom' => Array('Net 30 days'=>'Net 30 days', + 'Net 45 days'=>'Net 45 days', + 'Net 60 days'=>'Net 60 days' + ), +'campaignrelstatus_dom' => Array('--None--'=>'--None--', + 'Contacted - Successful' => 'Contacted - Successful', + 'Contacted - Unsuccessful' => 'Contacted - Unsuccessful', + 'Contacted - Never Contact Again' => 'Contacted - Never Contact Again' + ), +'currency_grouping_pattern_dom' => Array('123,456,789' => '123,456,789', + '123456789' => '123456789', + '123456,789' => '123456,789', + '12,34,56,789' => '12,34,56,789' + ), +'currency_decimal_separator_dom' => Array("." => ".", + "," => ",", + "'" => "'", + " " => " ", + "$" => "$" + ), +'currency_grouping_separator_dom' => Array("." => ".", + "," => ",", + "'" => "'", + " " => " ", + "$" => "$" + ), +'currency_symbol_placement_dom' => Array("$1.0" => "$1.0", + "1.0$" => "1.0$" + ), + +); + +require_once('modules/Users/UserTimeZonesArray.php'); +$usertimezonesClass = new UserTimeZones(); +$arrayOfSupportedTimeZones = $usertimezonesClass->userTimeZones(); +$combo_strings['time_zone_dom'] = array_combine($arrayOfSupportedTimeZones,$arrayOfSupportedTimeZones); + +?> + + diff --git a/include/ComboUtil.php b/include/ComboUtil.php new file mode 100644 index 0000000..41ec6ef --- /dev/null +++ b/include/ComboUtil.php @@ -0,0 +1,68 @@ +debug("Entering getComboArray(".$combofieldNames.") method ..."); + global $adb,$current_user; + $roleid=$current_user->roleid; + $comboFieldArray = Array(); + foreach ($combofieldNames as $tableName => $arrayName) + { + $fldArrName= $arrayName; + $arrayName = Array(); + + $sql = "select $tableName from vtiger_$tableName"; + $params = array(); + if(!is_admin($current_user)) + { + $subrole = getRoleSubordinates($roleid); + if(count($subrole)> 0) + { + $roleids = $subrole; + array_push($roleids, $roleid); + } + else + { + $roleids = $roleid; + } + $sql = "select distinct $tableName from vtiger_$tableName inner join vtiger_role2picklist on vtiger_role2picklist.picklistvalueid = vtiger_$tableName.picklist_valueid where roleid in(". generateQuestionMarks($roleids) .") order by sortid"; + $params = array($roleids); + } + $result = $adb->pquery($sql, $params); + while($row = $adb->fetch_array($result)) + { + $val = $row[$tableName]; + $arrayName[$val] = getTranslatedString($val); + } + $comboFieldArray[$fldArrName] = $arrayName; + } + $log->debug("Exiting getComboArray method ..."); + return $comboFieldArray; +} +function getUniquePicklistID() +{ + global $adb; + /*$sql="select id from vtiger_picklistvalues_seq"; + $picklistvalue_id = $adb->query_result($adb->pquery($sql, array()),0,'id'); + + $qry = "update vtiger_picklistvalues_seq set id =?"; + $adb->pquery($qry, array(++$picklistvalue_id));*/ + return $adb->getUniqueID('vtiger_picklistvalues'); +} + +?> diff --git a/include/CustomFieldUtil.php b/include/CustomFieldUtil.php new file mode 100644 index 0000000..e220bd4 --- /dev/null +++ b/include/CustomFieldUtil.php @@ -0,0 +1,285 @@ +debug("Entering getCustomFieldTypeName(".$uitype.") method ..."); + global $log; + $log->info("uitype is ".$uitype); + $fldname = ''; + + /* + * salutation type is an exception where the uitype 55 is considered to be as text. + */ + + if($uitype == 1 || $uitype == 55 || $uitype == 255) + { + $fldname = $mod_strings['Text']; + } + elseif($uitype == 7) + { + $fldname = $mod_strings['Number']; + } + elseif($uitype == 9) + { + $fldname = $mod_strings['Percent']; + } + elseif($uitype == 5 || $uitype == 23) + { + $fldname = $mod_strings['Date']; + } + elseif($uitype == 13) + { + $fldname = $mod_strings['Email']; + } + elseif($uitype == 11) + { + $fldname = $mod_strings['Phone']; + } + elseif($uitype == 15 ) + { + $fldname = $mod_strings['PickList']; + } + elseif($uitype == 17) + { + $fldname = $mod_strings['LBL_URL']; + } + elseif($uitype == 56) + { + $fldname = $mod_strings['LBL_CHECK_BOX']; + } + elseif($uitype == 71) + { + $fldname = $mod_strings['Currency']; + } + elseif($uitype == 21 || $uitype == 19) + { + $fldname = $mod_strings['LBL_TEXT_AREA']; + } + elseif($uitype == 33) + { + $fldname = $mod_strings['LBL_MULTISELECT_COMBO']; + } + elseif($uitype == 85) + { + $fldname = $mod_strings['Skype']; + } +$log->debug("Exiting getCustomFieldTypeName method ..."); + return $fldname; +} + +/** + * Function to get custom vtiger_fields + * @param $module :: vtiger_table name -- Type string + * returns customfields in key-value pair array format + */ +function getCustomFieldArray($module) +{ + global $log; + $log->debug("Entering getCustomFieldArray(".$module.") method ..."); + global $adb; + $custquery = "select tablename,fieldname from vtiger_field where tablename=? and vtiger_field.presence in (0,2) order by tablename"; + $custresult = $adb->pquery($custquery, array('vtiger_'.strtolower($module).'cf')); + $custFldArray = Array(); + $noofrows = $adb->num_rows($custresult); + for($i=0; $i<$noofrows; $i++) + { + $colName=$adb->query_result($custresult,$i,"fieldname"); + $custFldArray[$colName] = $i; + } + $log->debug("Exiting getCustomFieldArray method ..."); + return $custFldArray; +} + +/** + * Function to get columnname and vtiger_fieldlabel from vtiger_field vtiger_table + * @param $module :: module name -- Type string + * @param $trans_array :: translated column vtiger_fields -- Type array + * returns trans_array in key-value pair array format + */ +function getCustomFieldTrans($module, $trans_array) +{ + global $log; + $log->debug("Entering getCustomFieldTrans(".$module.",". $trans_array.") method ..."); + global $adb; + $tab_id = getTabid($module); + $custquery = "select columnname,fieldlabel from vtiger_field where generatedtype=2 and vtiger_field.presence in (0,2) and tabid=?"; + $custresult = $adb->pquery($custquery, array($tab_id)); + $custFldArray = Array(); + $noofrows = $adb->num_rows($custresult); + for($i=0; $i<$noofrows; $i++) + { + $colName=$adb->query_result($custresult,$i,"columnname"); + $fldLbl = $adb->query_result($custresult,$i,"fieldlabel"); + $trans_array[$colName] = $fldLbl; + } + $log->debug("Exiting getCustomFieldTrans method ..."); +} + + +/** + * Function to get customfield record from vtiger_field vtiger_table + * @param $tab :: Tab ID -- Type integer + * @param $datatype :: vtiger_field name -- Type string + * @param $id :: vtiger_field Id -- Type integer + * returns the data result in string format + */ +function getCustomFieldData($tab,$id,$datatype) +{ + global $log; + $log->debug("Entering getCustomFieldData(".$tab.",".$id.",".$datatype.") method ..."); + global $adb; + $query = "select * from vtiger_field where tabid=? and fieldid=? and vtiger_field.presence in (0,2)"; + $result = $adb->pquery($query, array($tab, $id)); + $return_data=$adb->fetch_array($result); + $log->debug("Exiting getCustomFieldData method ..."); + return $return_data[$datatype]; +} + + +/** + * Function to get customfield type,length value,decimal value and picklist value + * @param $label :: vtiger_field typename -- Type string + * @param $typeofdata :: datatype -- Type string + * returns the vtiger_field type,length,decimal + * and picklist value in ';' separated array format + */ +function getFldTypeandLengthValue($label,$typeofdata) +{ + global $log; + global $mod_strings,$app_strings; + $log->debug("Entering getFldTypeandLengthValue(".$label.",".$typeofdata.") method ..."); + if($label == $mod_strings['Text']) + { + $types = explode("~",$typeofdata); + $data_array=array('0',$types[3]); + $fieldtype = implode(";",$data_array); + } + elseif($label == $mod_strings['Number']) + { + $types = explode("~",$typeofdata); + $data_decimal = explode(",",$types[2]); + $data_array=array('1',$data_decimal[0],$data_decimal[1]); + $fieldtype = implode(";",$data_array); + } + elseif($label == $mod_strings['Percent']) + { + $types = explode("~",$typeofdata); + $data_array=array('2','5',$types[3]); + $fieldtype = implode(";",$data_array); + } + elseif($label == $mod_strings['Currency']) + { + $types = explode("~",$typeofdata); + $data_decimal = explode(",",$types[2]); + $data_array=array('3',$data_decimal[0],$data_decimal[1]); + $fieldtype = implode(";",$data_array); + } + elseif($label == $mod_strings['Date']) + { + $fieldtype = '4'; + } + elseif($label == $mod_strings['Email']) + { + $fieldtype = '5'; + } + elseif($label == $mod_strings['Phone']) + { + $fieldtype = '6'; + } + elseif($label == $mod_strings['PickList']) + { + $fieldtype = '7'; + } + elseif($label == $mod_strings['LBL_URL']) + { + $fieldtype = '8'; + } + elseif($label == $mod_strings['LBL_CHECK_BOX']) + { + $fieldtype = '9'; + } + elseif($label == $mod_strings['LBL_TEXT_AREA']) + { + $fieldtype = '10'; + } + elseif($label == $mod_strings['LBL_MULTISELECT_COMBO']) + { + $fieldtype = '11'; + } + elseif($label == $mod_strings['Skype']) + { + $fieldtype = '12'; + } + $log->debug("Exiting getFldTypeandLengthValue method ..."); + return $fieldtype; +} + +function getCalendarCustomFields($tabid,$mode='edit',$col_fields='') { + global $adb, $log, $current_user; + $log->debug("Entering getCalendarCustomFields($tabid, $mode, $col_fields)"); + + require('user_privileges/user_privileges_'.$current_user->id.'.php'); + + $block = getBlockId($tabid,"LBL_CUSTOM_INFORMATION"); + $custparams = array($block, $tabid); + + if($is_admin == true || $profileGlobalPermission[1] == 0 || $profileGlobalPermission[2] == 0) { + $custquery = "select * from vtiger_field where block=? AND vtiger_field.tabid=? ORDER BY fieldid"; + } else { + $profileList = getCurrentUserProfileList(); + $custquery = "SELECT vtiger_field.* FROM vtiger_field" . + " INNER JOIN vtiger_profile2field ON vtiger_profile2field.fieldid=vtiger_field.fieldid" . + " INNER JOIN vtiger_def_org_field ON vtiger_def_org_field.fieldid=vtiger_field.fieldid" . + " WHERE vtiger_field.block=? AND vtiger_field.tabid=? AND vtiger_profile2field.visible=0" . + " AND vtiger_def_org_field.visible=0 AND vtiger_profile2field.profileid IN (". generateQuestionMarks($profileList) .")"; + + if ($mode == 'edit') { + $custquery .= " AND vtiger_profile2field.readonly = 0"; + } + $custquery .= " ORDER BY vtiger_field.fieldid"; + array_push($custparams, $profileList); + } + $custresult = $adb->pquery($custquery, $custparams); + + $custFldArray = Array(); + $noofrows = $adb->num_rows($custresult); + for($i=0; $i<$noofrows; $i++) + { + $fieldname=$adb->query_result($custresult,$i,"fieldname"); + $fieldlabel=$adb->query_result($custresult,$i,"fieldlabel"); + $columnName=$adb->query_result($custresult,$i,"columnname"); + $uitype=$adb->query_result($custresult,$i,"uitype"); + $maxlength = $adb->query_result($custresult,$i,"maximumlength"); + $generatedtype = $adb->query_result($custresult,$i,"generatedtype"); + $typeofdata = $adb->query_result($custresult,$i,"typeofdata"); + + if ($mode == 'edit') + $custfld = getOutputHtml($uitype, $fieldname, $fieldlabel, $maxlength, $col_fields,$generatedtype,'Calendar',$mode, $typeofdata); + if ($mode == 'detail_view') + $custfld = getDetailViewOutputHtml($uitype, $fieldname, $fieldlabel, $col_fields,$generatedtype,$tabid); + $custFldArray[] = $custfld; + } + $log->debug("Exiting getCalendarCustomFields()"); + return $custFldArray; +} +?> diff --git a/include/DatabaseUtil.php b/include/DatabaseUtil.php new file mode 100644 index 0000000..4fe7d29 --- /dev/null +++ b/include/DatabaseUtil.php @@ -0,0 +1,87 @@ +Execute("show variables like '%_database' "); + $db_character_set = null; + $db_collation_type = null; + while(!$dbvarRS->EOF) { + $arr = $dbvarRS->FetchRow(); + $arr = array_change_key_case($arr); + switch($arr['variable_name']) { + case 'character_set_database' : $db_character_set = $arr['value']; break; + case 'collation_database' : $db_collation_type = $arr['value']; break; + } + // If we have all the required information break the loop. + if($db_character_set != null && $db_collation_type != null) break; + } + return (stristr($db_character_set, 'utf8') && stristr($db_collation_type, 'utf8')); +} + +function get_db_charset($conn) { + global $db_type; + if($db_type == 'pgsql') + return 'UTF8'; + $dbvarRS = &$conn->query("show variables like '%_database' "); + $db_character_set = null; + while(!$dbvarRS->EOF) { + $arr = $dbvarRS->FetchRow(); + $arr = array_change_key_case($arr); + if($arr['variable_name'] == 'character_set_database') { + $db_character_set = $arr['value']; + break; + } + } + return $db_character_set; +} + +//Make a count query +function mkCountQuery($query) +{ + // Remove all the \n, \r and white spaces to keep the space between the words consistent. + // This is required for proper pattern matching for words like ' FROM ', 'ORDER BY', 'GROUP BY' as they depend on the spaces between the words. + $query = preg_replace("/[\n\r\s]+/"," ",$query); + + //Strip of the current SELECT fields and replace them by "select count(*) as count" + // Space across FROM has to be retained here so that we do not have a clash with string "from" found in select clause + $query = "SELECT count(*) AS count ".substr($query, stripos($query,' FROM '),strlen($query)); + + //Strip of any "GROUP BY" clause + if(stripos($query,'GROUP BY') > 0) + $query = substr($query, 0, stripos($query,'GROUP BY')); + + //Strip of any "ORDER BY" clause + if(stripos($query,'ORDER BY') > 0) + $query = substr($query, 0, stripos($query,'ORDER BY')); + + //That's it + return( $query); +} + +//Added for PHP version less than 5 +if (!function_exists("stripos")) +{ + function stripos($query,$needle) + { + return strpos(strtolower($query),strtolower($needle)); + } +} + +?> diff --git a/include/DetailViewBlockStatus.php b/include/DetailViewBlockStatus.php new file mode 100644 index 0000000..45193fc --- /dev/null +++ b/include/DetailViewBlockStatus.php @@ -0,0 +1,9 @@ +assign("BLOCKINITIALSTATUS",$aAllBlockStatus); +?> \ No newline at end of file diff --git a/include/FormValidationUtil.php b/include/FormValidationUtil.php new file mode 100644 index 0000000..8556646 --- /dev/null +++ b/include/FormValidationUtil.php @@ -0,0 +1,114 @@ +pquery( + "SELECT fieldlabel,fieldname,typeofdata FROM vtiger_field + WHERE displaytype IN (1,3) AND presence in (0,2) AND tabid=?", Array($tabid)); + $fieldinfos = Array(); + while($fieldrow = $adb->fetch_array($fieldres)) { + $fieldlabel = getTranslatedString($fieldrow['fieldlabel'], $fieldModuleName); + $fieldname = $fieldrow['fieldname']; + $typeofdata= $fieldrow['typeofdata']; + $fieldinfos[$fieldname] = Array($fieldlabel => $typeofdata); + } + return $fieldinfos; + } else { + // TODO: Call the old API defined below in the file? + return getDBValidationData_510($tablearray, $tabid); + } +} + +/** Function to get the details for fieldlabels for a given table array + * @param $tablearray -- tablearray:: Type string array (table names in array) + * @param $tabid -- tabid:: Type integer + * @returns $fieldName_array -- fieldName_array:: Type string array (field name details) + * + */ + + +function getDBValidationData_510($tablearray,$tabid='') +{ + global $log; + $log->debug("Entering getDBValidationData(".$tablearray.",".$tabid.") method ..."); + $sql = ''; + $params = array(); + $tab_con = ""; + $numValues = count($tablearray); + global $adb,$mod_strings; + + if($tabid!='') $tab_con = ' and tabid='. $adb->sql_escape_string($tabid); + + for($i=0;$i<$numValues;$i++) + { + + if(in_array("emails",$tablearray)) + { + if($numValues > 1 && $i != $numValues-1) + { + $sql .= "select fieldlabel,fieldname,typeofdata from vtiger_field where tablename=? and tabid=10 and vtiger_field.presence in (0,2) and displaytype <> 2 union "; + array_push($params, $tablearray[$i]); + } + else + { + $sql .= "select fieldlabel,fieldname,typeofdata from vtiger_field where tablename=? and tabid=10 and vtiger_field.presence in (0,2) and displaytype <> 2 "; + array_push($params, $tablearray[$i]); + } + } + else + { + if($numValues > 1 && $i != $numValues-1) + { + $sql .= "select fieldlabel,fieldname,typeofdata from vtiger_field where tablename=? $tab_con and displaytype in (1,3) and vtiger_field.presence in (0,2) union "; + array_push($params, $tablearray[$i]); + } + else + { + $sql .= "select fieldlabel,fieldname,typeofdata from vtiger_field where tablename=? $tab_con and displaytype in (1,3) and vtiger_field.presence in (0,2)"; + array_push($params, $tablearray[$i]); + } + } + } + $result = $adb->pquery($sql, $params); + $noofrows = $adb->num_rows($result); + $fieldModuleName = empty($tabid)? false : getTabModuleName($tabid); + $fieldName_array = Array(); + for($i=0;$i<$noofrows;$i++) + { + // Translate label with reference to module language string + $fieldlabel = getTranslatedString($adb->query_result($result,$i,'fieldlabel'), $fieldModuleName); + $fieldname = $adb->query_result($result,$i,'fieldname'); + $typeofdata = $adb->query_result($result,$i,'typeofdata'); + //echo '
'.$fieldlabel.'....'.$fieldname.'....'.$typeofdata; + $fldLabel_array = Array(); + $fldLabel_array[$fieldlabel] = $typeofdata; + $fieldName_array[$fieldname] = $fldLabel_array; + + } + + + $log->debug("Exiting getDBValidationData method ..."); + return $fieldName_array; + + + +} +?> diff --git a/include/HTTP_Session/Session.php b/include/HTTP_Session/Session.php new file mode 100644 index 0000000..c811c0b --- /dev/null +++ b/include/HTTP_Session/Session.php @@ -0,0 +1,804 @@ + + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Session.php,v 1.15 2007/07/14 12:11:54 troehr Exp $ + * @link http://pear.php.net/package/HTTP_Session + * @since File available since Release 0.4.0 + */ + +// @const HTTP_SESSION_STARTED - The session was started with the current request +define("HTTP_SESSION_STARTED", 1); +// @const HTTP_SESSION_STARTED - No new session was started with the current request +define("HTTP_SESSION_CONTINUED", 2); + +/** + * Class for managing HTTP sessions + * + * Provides access to session-state values as well as session-level + * settings and lifetime management methods. + * Based on the standart PHP session handling mechanism + * it provides for you more advanced features such as + * database container, idle and expire timeouts, etc. + * + * Example 1: + * + * + * // Setting some options and detecting of a new session + * HTTP_Session::setCookieless(false); + * HTTP_Session::start('MySessionID'); + * HTTP_Session::set('variable', 'Tet string'); + * if (HTTP_Session::isNew()) { + * echo('new session was created with the current request'); + * $visitors++; // Increase visitors count + * } + * + * //HTTP_Session::regenerateId(); + * + * + * Example 2: + * + * + * // Using database container + * HTTP_Session::setContainer('DB'); + * HTTP_Session::start(); + * + * + * Example 3: + * + * + * // Setting timeouts + * HTTP_Session::start(); + * HTTP_Session::setExpire(time() + 60 * 60); // expires in one hour + * HTTP_Session::setIdle(time()+ 10 * 60); // idles in ten minutes + * if (HTTP_Session::isExpired()) { + * // expired + * echo('Your session is expired!'); + * HTTP_Session::destroy(); + * } + * if (HTTP_Session::isIdle()) { + * // idle + * echo('You've been idle for too long!'); + * HTTP_Session::destroy(); + * } + * HTTP_Session::updateIdle(); + * + * + * @category HTTP + * @package HTTP_Session + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/HTTP_Session + * @since Class available since Release 0.4.0 + */ +class HTTP_Session +{ + /** + * Sets user-defined session storage functions + * + * Sets the user-defined session storage functions which are used + * for storing and retrieving data associated with a session. + * This is most useful when a storage method other than + * those supplied by PHP sessions is preferred. + * i.e. Storing the session data in a local database. + * + * @param string $container Container name + * @param array $container_options Container options + * + * @static + * @access public + * @return void + * @see session_set_save_handler() + */ + function setContainer($container, $container_options = null) + { + $container_class = 'HTTP_Session_Container_' . $container; + $container_classfile = 'HTTP/Session/Container/' . $container . '.php'; + + include_once $container_classfile; + $container = new $container_class($container_options); + + $container->set(); + } + + /** + * Initializes session data + * + * Creates a session (or resumes the current one + * based on the session id being passed + * via a GET variable or a cookie). + * You can provide your own name and/or id for a session. + * + * @param string $name string Name of a session, default is 'SessionID' + * @param string $id string Id of a session which will be used + * only when the session is new + * + * @static + * @access public + * @return void + * @see session_name() + * @see session_id() + * @see session_start() + */ + function start($name = 'SessionID', $id = null) + { + HTTP_Session::name($name); + if ($id) { + HTTP_Session::id($id); + } elseif (is_null(HTTP_Session::detectID())) { + HTTP_Session::id($id ? $id : uniqid(dechex(rand()))); + } + session_start(); + if (!isset($_SESSION['__HTTP_Session_Info'])) { + $_SESSION['__HTTP_Session_Info'] = HTTP_SESSION_STARTED; + } else { + $_SESSION['__HTTP_Session_Info'] = HTTP_SESSION_CONTINUED; + } + } + + /** + * Writes session data and ends session + * + * Session data is usually stored after your script + * terminated without the need to call HTTP_Session::stop(), + * but as session data is locked to prevent concurrent + * writes only one script may operate on a session at any time. + * When using framesets together with sessions you will + * experience the frames loading one by one due to this + * locking. You can reduce the time needed to load all the + * frames by ending the session as soon as all changes + * to session variables are done. + * + * @static + * @access public + * @return void + * @see session_write_close() + */ + function pause() + { + session_write_close(); + } + + /** + * Frees all session variables and destroys all data + * registered to a session + * + * This method resets the $_SESSION variable and + * destroys all of the data associated + * with the current session in its storage (file or DB). + * It forces new session to be started after this method + * is called. It does not unset the session cookie. + * + * @static + * @access public + * @return void + * @see session_unset() + * @see session_destroy() + */ + function destroy() + { + session_unset(); + session_destroy(); + + // set session handlers again to avoid fatal error in case + // HTTP_Session::start() will be called afterwards + if (isset($GLOBALS['HTTP_Session_Container']) && + is_a($GLOBALS['HTTP_Session_Container'], 'HTTP_Session_Container')) { + $GLOBALS['HTTP_Session_Container']->set(); + } + } + + /** + * Calls session_regenerate_id() if available + * + * @param bool $deleteOldSessionData Whether to delete data of old session + * + * @static + * @access public + * @return bool + */ + function regenerateId($deleteOldSessionData = false) + { + if (function_exists('session_regenerate_id')) { + return session_regenerate_id($deleteOldSessionData); + + // emulate session_regenerate_id() + } else { + + do { + $newId = uniqid(dechex(rand())); + } while ($newId === session_id()); + + if ($deleteOldSessionData) { + session_unset(); + } + + session_id($newId); + + return true; + } + } + + /** + * This function copies session data of specified id to specified table + * + * @param string $targetTable Table to replicate data to + * @param string $id ID of the session + * + * @static + * @access public + * @return bool + */ + function replicate($targetTable, $id = null) + { + return $GLOBALS['HTTP_Session_Container']->replicate($targetTable, $id); + } + + /** + * Free all session variables + * + * @todo TODO Save expire and idle timestamps? + * @static + * @access public + * @return void + */ + function clear() + { + $info = $_SESSION['__HTTP_Session_Info']; + session_unset(); + $_SESSION['__HTTP_Session_Info'] = $info; + } + + /** + * Tries to find any session id in $_GET, $_POST or $_COOKIE + * + * @static + * @access private + * @return string Session ID (if exists) or null + */ + function detectID() + { + if (HTTP_Session::useCookies()) { + if (isset($_COOKIE[HTTP_Session::name()])) { + return $_COOKIE[HTTP_Session::name()]; + } + } else { + if (isset($_GET[HTTP_Session::name()])) { + return $_GET[HTTP_Session::name()]; + } + if (isset($_POST[HTTP_Session::name()])) { + return $_POST[HTTP_Session::name()]; + } + } + return null; + } + + /** + * Sets new name of a session + * + * @param string $name New name of a session + * + * @static + * @access public + * @return string Previous name of a session + * @see session_name() + */ + function name($name = null) + { + return isset($name) ? session_name($name) : session_name(); + } + + /** + * Sets new ID of a session + * + * @param string $id New ID of a session + * + * @static + * @access public + * @return string Previous ID of a session + * @see session_id() + */ + function id($id = null) + { + return isset($id) ? session_id($id) : session_id(); + } + + /** + * Sets the maximum expire time + * + * @param integer $time Time in seconds + * @param bool $add Add time to current expire time or not + * + * @static + * @access public + * @return void + */ + function setExpire($time, $add = false) + { + if ($add) { + if (!isset($_SESSION['__HTTP_Session_Expire_TS'])) { + $_SESSION['__HTTP_Session_Expire_TS'] = time() + $time; + } + + // update session.gc_maxlifetime + $currentGcMaxLifetime = HTTP_Session::setGcMaxLifetime(null); + HTTP_Session::setGcMaxLifetime($currentGcMaxLifetime + $time); + + } elseif (!isset($_SESSION['__HTTP_Session_Expire_TS'])) { + $_SESSION['__HTTP_Session_Expire_TS'] = $time; + } + } + + /** + * Sets the maximum idle time + * + * Sets the time-out period allowed + * between requests before the session-state + * provider terminates the session. + * + * @param int $time Time in seconds + * @param bool $add Add time to current maximum idle time or not + * + * @static + * @access public + * @return void + */ + function setIdle($time, $add = false) + { + if ($add) { + $_SESSION['__HTTP_Session_Idle'] = $time; + } else { + // substract time again because it doesn't make any sense to provide + // the idle time as a timestamp + // keep $add functionality to provide BC + $_SESSION['__HTTP_Session_Idle'] = $time - time(); + } + } + + /** + * Returns the time up to the session is valid + * + * @static + * @access public + * @return integer Time when the session idles + */ + function sessionValidThru() + { + if (!isset($_SESSION['__HTTP_Session_Idle_TS']) || + !isset($_SESSION['__HTTP_Session_Idle'])) { + return 0; + } else { + return $_SESSION['__HTTP_Session_Idle_TS'] + + $_SESSION['__HTTP_Session_Idle']; + } + } + + /** + * Check if session is expired + * + * @static + * @access public + * @return bool + */ + function isExpired() + { + if (isset($_SESSION['__HTTP_Session_Expire_TS']) && + $_SESSION['__HTTP_Session_Expire_TS'] < time()) { + return true; + } else { + return false; + } + } + + /** + * Check if session is idle + * + * @static + * @access public + * @return bool + */ + function isIdle() + { + if (isset($_SESSION['__HTTP_Session_Idle_TS']) && + (($_SESSION['__HTTP_Session_Idle_TS'] + + $_SESSION['__HTTP_Session_Idle']) < time())) { + return true; + } else { + return false; + } + } + + /** + * Updates the idletime + * + * @static + * @access public + * @return void + */ + function updateIdle() + { + $_SESSION['__HTTP_Session_Idle_TS'] = time(); + } + + /** + * If optional parameter is specified it indicates + * whether the module will use cookies to store + * the session id on the client side + * + * It returns the previous value of this property + * + * @param bool $useCookies If specified it will replace the previous value + * of this property + * + * @static + * @access public + * + * @return bool The previous value of the property + */ + function useCookies($useCookies = null) + { + $return = ini_get('session.use_cookies') ? true : false; + if (isset($useCookies)) { + ini_set('session.use_cookies', $useCookies ? 1 : 0); + } + return $return; + } + + /** + * Gets a value indicating whether the session + * was created with the current request + * + * You MUST call this method only after you have started + * the session with the HTTP_Session::start() method. + * + * @static + * @access public + * @return bool True if the session was created + * with the current request, false otherwise + */ + function isNew() + { + // The best way to check if a session is new is to check + // for existence of a session data storage + // with the current session id, but this is impossible + // with the default PHP module wich is 'files'. + // So we need to emulate it. + return !isset($_SESSION['__HTTP_Session_Info']) || + $_SESSION['__HTTP_Session_Info'] == HTTP_SESSION_STARTED; + } + + /** + * Register variable with the current session + * + * @param string $name Name of a global variable + * + * @deprecated Use set()/setRef() instead + * + * @static + * @access public + * @return bool + * @see session_register() + */ + function register($name) + { + return session_register($name); + } + + /** + * Unregister a variable from the current session + * + * @param string $name Name of a global variable + * + * @deprecated Use get()/getRef() instead + * + * @static + * @access public + * @return bool + * @see session_unregister() + */ + function unregister($name) + { + return session_unregister($name); + } + + /** + * Checks if a session variable is registered + * + * @param string $name Variable name + * + * @deprecated Use is_set() instead + * + * @static + * @access public + * @return bool + */ + function registered($name) + { + return session_is_registered($name); + } + + /** + * Returns session variable + * + * @param string $name Name of a variable + * @param mixed $default Default value of a variable if not set + * + * @static + * @access public + * @return mixed Value of a variable + */ + function get($name, $default = null) + { + if (!isset($_SESSION[$name]) && isset($default)) { + $_SESSION[$name] = $default; + } + $return = (isset($_SESSION[$name])) ? $_SESSION[$name] : null; + return $return; + } + + /** + * Returns session variable by reference + * + * @param string $name Name of a variable + * + * @static + * @access public + * @return mixed Value of a variable + */ + function &getRef($name) + { + if (isset($_SESSION[$name])) { + $return =& $_SESSION[$name]; + } else { + $return = null; + } + + return $return; + } + + /** + * Sets session variable + * + * @param string $name Name of a variable + * @param mixed $value Value of a variable + * + * @static + * @access public + * @return mixed Old value of a variable + */ + function set($name, $value) + { + $return = (isset($_SESSION[$name])) ? $_SESSION[$name] : null; + if (null === $value) { + unset($_SESSION[$name]); + } else { + $_SESSION[$name] = $value; + } + return $return; + } + + /** + * Sets session variable by reference + * + * @param string $name Name of a variable + * @param mixed $value Value of a variable + * + * @static + * @access public + * @return mixed Old value of a variable + */ + function setRef($name, &$value) + { + $return = (isset($_SESSION[$name])) ? $_SESSION[$name] : null; + + $_SESSION[$name] =& $value; + + return $return; + } + + /** + * Checks if a session variable is set + * + * @param string $name Variable name + * + * @static + * @access public + * @return bool + */ + function is_set($name) + { + return isset($_SESSION[$name]); + } + + /** + * Returns local variable of a script + * + * Two scripts can have local variables with the same names + * + * @param string $name Name of a variable + * @param mixed $default Default value of a variable if not set + * + * @static + * @access public + * @return mixed Value of a local variable + */ + function &getLocal($name, $default = null) + { + $local = md5(HTTP_Session::localName()); + if (!isset($_SESSION[$local]) || !is_array($_SESSION[$local])) { + $_SESSION[$local] = array(); + } + if (!isset($_SESSION[$local][$name]) && isset($default)) { + $_SESSION[$local][$name] = $default; + } + return $_SESSION[$local][$name]; + } + + /** + * Sets local variable of a script. + * Two scripts can have local variables with the same names. + * + * @param string $name Name of a local variable + * @param mixed $value Value of a local variable + * + * @static + * @access public + * @return mixed Old value of a local variable + */ + function setLocal($name, $value) + { + $local = md5(HTTP_Session::localName()); + if (!isset($_SESSION[$local]) || !is_array($_SESSION[$local])) { + $_SESSION[$local] = array(); + } + $return = (isset($_SESSION[$local][$name])) ? $_SESSION[$local][$name] + : null; + + if (null === $value) { + unset($_SESSION[$local][$name]); + } else { + $_SESSION[$local][$name] = $value; + } + return $return; + } + + /** + * Sets new local name + * + * @param string $name New local name + * + * @static + * @access public + * @return string Previous local name + */ + function localName($name = null) + { + $return = (isset($GLOBALS['__HTTP_Session_Localname'])) ? $GLOBALS['__HTTP_Session_Localname'] + : null; + + if (!empty($name)) { + $GLOBALS['__HTTP_Session_Localname'] = $name; + } + return $return; + } + + /** + * Initialize + * + * @static + * @access private + * @return void + */ + function _init() + { + // Disable auto-start of a sesion + ini_set('session.auto_start', 0); + + // Set local name equal to the current script name + HTTP_Session::localName($_SERVER['PHP_SELF']); + } + + /** + * If optional parameter is specified it indicates + * whether the session id will automatically be appended to + * all links + * + * It returns the previous value of this property + * + * @param bool $useTransSID If specified it will replace the previous value + * of this property + * + * @static + * @access public + * @return bool The previous value of the property + */ + function useTransSID($useTransSID = null) + { + $return = ini_get('session.use_trans_sid') ? true : false; + if (isset($useTransSID)) { + ini_set('session.use_trans_sid', $useTransSID ? 1 : 0); + } + return $return; + } + + /** + * If optional parameter is specified it determines the number of seconds + * after which session data will be seen as 'garbage' and cleaned up + * + * It returns the previous value of this property + * + * @param bool $gcMaxLifetime If specified it will replace the previous value + * of this property + * + * @static + * @access public + * @return bool The previous value of the property + */ + function setGcMaxLifetime($gcMaxLifetime = null) + { + $return = ini_get('session.gc_maxlifetime'); + if (isset($gcMaxLifetime) && is_int($gcMaxLifetime) && $gcMaxLifetime >= 1) { + ini_set('session.gc_maxlifetime', $gcMaxLifetime); + } + return $return; + } + + /** + * If optional parameter is specified it determines the + * probability that the gc (garbage collection) routine is started + * and session data is cleaned up + * + * It returns the previous value of this property + * + * @param bool $gcProbability If specified it will replace the previous value + * of this property + * + * @static + * @access public + * @return bool The previous value of the property + */ + function setGcProbability($gcProbability = null) + { + $return = ini_get('session.gc_probability'); + if (isset($gcProbability) && + is_int($gcProbability) && + $gcProbability >= 1 && + $gcProbability <= 100) { + ini_set('session.gc_probability', $gcProbability); + } + return $return; + } +} + +HTTP_Session::_init(); +?> \ No newline at end of file diff --git a/include/HTTP_Session/Session/Container.php b/include/HTTP_Session/Session/Container.php new file mode 100644 index 0000000..ba85fa2 --- /dev/null +++ b/include/HTTP_Session/Session/Container.php @@ -0,0 +1,280 @@ + + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Container.php,v 1.8 2007/07/14 12:11:54 troehr Exp $ + * @link http://pear.php.net/package/HTTP_Session + * @since File available since Release 0.4.0 + */ + +/** + * Container class for storing session data + * + * @category HTTP + * @package HTTP_Session + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/HTTP_Session + * @since Class available since Release 0.4.0 + */ +class HTTP_Session_Container +{ + /** + * Additional options for the container object + * + * @var array + * @access private + */ + var $options = array(); + + /** + * Constrtuctor method + * + * @param array $options Additional options for the container object + * + * @access public + * @return object + */ + function HTTP_Session_Container($options = null) + { + $this->_setDefaults(); + if (is_array($options)) { + $this->_parseOptions(); + } + } + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + } + + /** + * Parse options passed to the container class + * + * @param array $options Options + * + * @access private + * @return void + */ + function _parseOptions($options) + { + foreach ($options as $option => $value) { + if (in_array($option, array_keys($this->options))) { + $this->options[$option] = $value; + } + } + } + + /** + * This function is called by the session handler to initialize things + * + * @param string $save_path Save path + * @param string $session_name Session name + * + * @access public + * @return bool + */ + function open($save_path, $session_name) + { + return true; + } + + /** + * This function is called when the page is finished + * executing and the session handler needs to close things off + * + * Has to be overwritten by each container class + * + * @access public + * @return bool + */ + function close() + { + return true; + } + + /** + * This function is called by the session handler + * to read the data associated with a given session ID. + * This function must retrieve and return the session data + * for the session identified by $id. + * + * Has to be overwritten by each container class + * + * @param string $id ID of the session + * + * @access public + * @return string + */ + function read($id) + { + return ''; + } + + /** + * This function is called when the session handler + * has session data to save, which usually happens + * at the end of your script + * + * Has to be overwritten by each container class + * + * @param string $id ID of the session + * @param mixed $data The data associated with a given session ID + * + * @access public + * @return bool + */ + function write($id, $data) + { + return true; + } + + /** + * This function is called when a session is destroyed. + * It is responsible for deleting the session and cleaning things up. + * + * Has to be overwritten by each container class + * + * @param string $id ID of the session + * + * @access public + * @return bool + */ + function destroy($id) + { + return true; + } + + /** + * This function copies session data of specified id to specified table + * + * Has to be overwritten by each container class + * + * @param string $targetTable Table to replicate data to + * @param string $id ID of the session + * + * @access public + * @return bool + */ + function replicate($targetTable, $id = null) + { + return true; + } + + /** + * This function is responsible for garbage collection. + * In the case of session handling, it is responsible + * for deleting old, stale sessions that are hanging around. + * The session handler will call this every now and then. + * + * Has to be overwritten by each container class + * + * @param integer $maxlifetime Maximum lifetime + * + * @access public + * @return bool + */ + function gc($maxlifetime) + { + return true; + } + + /** + * Set session save handler + * + * @access public + * @return void + */ + function set() + { + $GLOBALS['HTTP_Session_Container'] =& $this; + session_module_name('user'); + session_set_save_handler('HTTP_Session_Open', + 'HTTP_Session_Close', + 'HTTP_Session_Read', + 'HTTP_Session_Write', + 'HTTP_Session_Destroy', + 'HTTP_Session_GC'); + } + + /** + * Destructor for compatibility with PHP >= 5.0.5 + * + * @access private + * @return void + */ + function __destruct() + { + $GLOBALS['HTTP_Session_Container'] =& $this; + session_write_close(); + } +} + +// Delegate function calls to the object's methods +/** @ignore */ +function HTTP_Session_Open($save_path, $session_name) +{ + return (isset($GLOBALS['HTTP_Session_Container'])) ? $GLOBALS['HTTP_Session_Container']->open($save_path, $session_name) + : false; +} +/** @ignore */ +function HTTP_Session_Close() +{ + return (isset($GLOBALS['HTTP_Session_Container'])) ? $GLOBALS['HTTP_Session_Container']->close() + : false; +} +/** @ignore */ +function HTTP_Session_Read($id) +{ + return (isset($GLOBALS['HTTP_Session_Container'])) ? $GLOBALS['HTTP_Session_Container']->read($id) + : false; +} +/** @ignore */ +function HTTP_Session_Write($id, $data) +{ + return (isset($GLOBALS['HTTP_Session_Container'])) ? $GLOBALS['HTTP_Session_Container']->write($id, $data) + : false; +} +/** @ignore */ +function HTTP_Session_Destroy($id) +{ + return (isset($GLOBALS['HTTP_Session_Container'])) ? $GLOBALS['HTTP_Session_Container']->destroy($id) + : false; +} +/** @ignore */ +function HTTP_Session_GC($maxlifetime) +{ + return (isset($GLOBALS['HTTP_Session_Container'])) ? $GLOBALS['HTTP_Session_Container']->gc($maxlifetime) + : false; +} +?> \ No newline at end of file diff --git a/include/HTTP_Session/Session/Container/DB.php b/include/HTTP_Session/Session/Container/DB.php new file mode 100644 index 0000000..3b60073 --- /dev/null +++ b/include/HTTP_Session/Session/Container/DB.php @@ -0,0 +1,364 @@ + + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: DB.php,v 1.7 2007/07/14 12:11:54 troehr Exp $ + * @link http://pear.php.net/package/HTTP_Session + * @since File available since Release 0.4.0 + */ + +require_once 'HTTP/Session/Container.php'; +require_once 'DB.php'; + +/** + * Database container for session data + * + * Create the following table to store session data + * + * CREATE TABLE `sessiondata` ( + * `id` CHAR(32) NOT NULL, + * `expiry` INT UNSIGNED NOT NULL DEFAULT 0, + * `data` TEXT NOT NULL, + * PRIMARY KEY (`id`) + * ); + * + * + * @category HTTP + * @package HTTP_Session + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/HTTP_Session + * @since Class available since Release 0.4.0 + */ +class HTTP_Session_Container_DB extends HTTP_Session_Container +{ + /** + * DB connection object + * + * @var object DB + * @access private + */ + var $db = null; + + /** + * Session data cache id + * + * @var mixed + * @access private + */ + var $crc = false; + + /** + * Constrtuctor method + * + * $options is an array with the options.
+ * The options are: + *
    + *
  • 'dsn' - The DSN string
  • + *
  • 'table' - Table with session data, default is 'sessiondata'
  • + *
  • 'autooptimize' - Boolean, 'true' to optimize + * the table on garbage collection, default is 'false'.
  • + *
+ * + * @param array $options Options + * + * @access public + * @return object + */ + function HTTP_Session_Container_DB($options) + { + $this->_setDefaults(); + if (is_array($options)) { + $this->_parseOptions($options); + } else { + $this->options['dsn'] = $options; + } + } + + /** + * Connect to database by using the given DSN string + * + * @param string $dsn DSN string + * + * @access private + * @return mixed Object on error, otherwise bool + */ + function _connect($dsn) + { + if (is_string($dsn) || is_array($dsn)) { + $this->db = DB::connect($dsn); + } else if (is_object($dsn) && is_a($dsn, "db_common")) { + $this->db = $dsn; + } else if (is_object($dsn) && DB::isError($dsn)) { + return new DB_Error($dsn->code, PEAR_ERROR_DIE); + } else { + return new PEAR_Error("The given dsn was not valid in file " . __FILE__ + . " at line " . __LINE__, + 41, + PEAR_ERROR_RETURN, + null, + null + ); + + } + + if (DB::isError($this->db)) { + return new DB_Error($this->db->code, PEAR_ERROR_DIE); + } + + return true; + } + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['dsn'] = null; + $this->options['table'] = 'sessiondata'; + $this->options['autooptimize'] = false; + } + + /** + * Establish connection to a database + * + * @param string $save_path Save path + * @param string $session_name Session name + * + * @return bool + */ + function open($save_path, $session_name) + { + if (DB::isError($this->_connect($this->options['dsn']))) { + return false; + } else { + return true; + } + } + + /** + * Free resources + * + * @return void + */ + function close() + { + return true; + } + + /** + * Read session data + * + * @param string $id Session id + * + * @return void + */ + function read($id) + { + $query = sprintf("SELECT data FROM %s WHERE id = %s AND expiry >= %d", + $this->options['table'], + $this->db->quoteSmart(md5($id)), + time()); + $result = $this->db->getOne($query); + if (DB::isError($result)) { + new DB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + $this->crc = strlen($result) . crc32($result); + return $result; + } + + /** + * Write session data + * + * @param string $id Session id + * @param mixed $data Data + * + * @return bool + */ + function write($id, $data) + { + if ((false !== $this->crc) && + ($this->crc === strlen($data) . crc32($data))) { + // $_SESSION hasn't been touched, no need to update the blob column + $query = sprintf("UPDATE %s SET expiry = %d WHERE id = %s", + $this->options['table'], + time() + ini_get('session.gc_maxlifetime'), + $this->db->quoteSmart(md5($id))); + } else { + // Check if table row already exists + $query = sprintf("SELECT COUNT(id) FROM %s WHERE id = %s", + $this->options['table'], + $this->db->quoteSmart(md5($id))); + $result = $this->db->getOne($query); + if (DB::isError($result)) { + new DB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + if (0 == intval($result)) { + // Insert new row into table + $query = sprintf("INSERT INTO %s (id, expiry, data) VALUES (%s, %d, %s)", + $this->options['table'], + $this->db->quoteSmart(md5($id)), + time() + ini_get('session.gc_maxlifetime'), + $this->db->quoteSmart($data)); + } else { + // Update existing row + $query = sprintf("UPDATE %s SET expiry = %d, data = %s WHERE id = %s", + $this->options['table'], + time() + ini_get('session.gc_maxlifetime'), + $this->db->quoteSmart($data), + $this->db->quoteSmart(md5($id))); + } + } + $result = $this->db->query($query); + if (DB::isError($result)) { + new DB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Destroy session data + * + * @param string $id Session id + * + * @return void + */ + function destroy($id) + { + $query = sprintf("DELETE FROM %s WHERE id = %s", + $this->options['table'], + $this->db->quoteSmart(md5($id))); + $result = $this->db->query($query); + if (DB::isError($result)) { + new DB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Replicate session data to table specified in option 'replicateBeforeDestroy' + * + * @param string $targetTable Table to replicate to + * @param string $id Id of record to replicate + * + * @access private + * @return bool + */ + function replicate($targetTable, $id = null) + { + if (is_null($id)) { + $id = HTTP_Session::id(); + } + + // Check if table row already exists + $query = sprintf("SELECT COUNT(id) FROM %s WHERE id = %s", + $targetTable, + $this->db->quoteSmart(md5($id))); + $result = $this->db->getOne($query); + if (DB::isError($result)) { + new DB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + + // Insert new row into dest table + if (0 == intval($result)) { + $query = sprintf("INSERT INTO %s SELECT * FROM %s WHERE id = %s", + $targetTable, + $this->options['table'], + $this->db->quoteSmart(md5($id))); + + } else { + // Update existing row + $query = sprintf("UPDATE %s dst, %s src SET dst.expiry = src.expiry, dst.data = src.data WHERE dst.id = src.id AND src.id = %s", + $targetTable, + $this->options['table'], + $this->db->quoteSmart(md5($id))); + } + + $result = $this->db->query($query); + if (DB::isError($result)) { + new DB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Garbage collection + * + * @param int $maxlifetime Maximum lifetime + * + * @return bool + */ + function gc($maxlifetime) + { + $query = sprintf("DELETE FROM %s WHERE expiry < %d", + $this->options['table'], + time()); + $result = $this->db->query($query); + if (DB::isError($result)) { + new DB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + if ($this->options['autooptimize']) { + switch($this->db->phptype) { + case 'mysql': + $query = sprintf("OPTIMIZE TABLE %s", $this->options['table']); + break; + case 'pgsql': + $query = sprintf("VACUUM %s", $this->options['table']); + break; + default: + $query = null; + break; + } + if (isset($query)) { + $result = $this->db->query($query); + if (DB::isError($result)) { + new DB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + } + } + + return true; + } +} +?> \ No newline at end of file diff --git a/include/HTTP_Session/Session/Container/MDB.php b/include/HTTP_Session/Session/Container/MDB.php new file mode 100644 index 0000000..2a8f54c --- /dev/null +++ b/include/HTTP_Session/Session/Container/MDB.php @@ -0,0 +1,364 @@ + + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: MDB.php,v 1.5 2007/07/14 12:11:55 troehr Exp $ + * @link http://pear.php.net/package/HTTP_Session + * @since File available since Release 0.5.0 + */ + +require_once 'HTTP/Session/Container.php'; +require_once 'MDB.php'; + +/** + * Database container for session data + * + * Create the following table to store session data + * + * CREATE TABLE `sessiondata` ( + * `id` CHAR(32) NOT NULL, + * `expiry` INT UNSIGNED NOT NULL DEFAULT 0, + * `data` TEXT NOT NULL, + * PRIMARY KEY (`id`) + * ); + * + * + * @category HTTP + * @package HTTP_Session + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/HTTP_Session + * @since Class available since Release 0.4.0 + */ +class HTTP_Session_Container_MDB extends HTTP_Session_Container +{ + + /** + * MDB connection object + * + * @var object MDB + * @access private + */ + var $db = null; + + /** + * Session data cache id + * + * @var mixed + * @access private + */ + var $crc = false; + + /** + * Constructor method + * + * $options is an array with the options.
+ * The options are: + *
    + *
  • 'dsn' - The DSN string
  • + *
  • 'table' - Table with session data, default is 'sessiondata'
  • + *
  • 'autooptimize' - Boolean, 'true' to optimize + * the table on garbage collection, default is 'false'.
  • + *
+ * + * @param array $options Options + * + * @access public + * @return object + */ + function HTTP_Session_Container_MDB($options) + { + $this->_setDefaults(); + if (is_array($options)) { + $this->_parseOptions($options); + } else { + $this->options['dsn'] = $options; + } + } + + /** + * Connect to database by using the given DSN string + * + * @param string $dsn DSN string + * + * @access private + * @return mixed Object on error, otherwise bool + */ + function _connect($dsn) + { + if (is_string($dsn) || is_array($dsn)) { + $this->db = MDB::connect($dsn); + } else if (is_object($dsn) && is_a($dsn, 'mdb_common')) { + $this->db = $dsn; + } else if (is_object($dsn) && MDB::isError($dsn)) { + return new MDB_Error($dsn->code, PEAR_ERROR_DIE); + } else { + return new PEAR_Error("The given dsn was not valid in file " . __FILE__ + . " at line " . __LINE__, + 41, + PEAR_ERROR_RETURN, + null, + null + ); + + } + + if (MDB::isError($this->db)) { + return new MDB_Error($this->db->code, PEAR_ERROR_DIE); + } + + return true; + } + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['dsn'] = null; + $this->options['table'] = 'sessiondata'; + $this->options['autooptimize'] = false; + } + + /** + * Establish connection to a database + * + * @param string $save_path Save path + * @param string $session_name Session name + * + * @return bool + */ + function open($save_path, $session_name) + { + if (MDB::isError($this->_connect($this->options['dsn']))) { + return false; + } else { + return true; + } + } + + /** + * Free resources + * + * @return bool + */ + function close() + { + return true; + } + + /** + * Read session data + * + * @param string $id Session id + * + * @return mixed + */ + function read($id) + { + $query = sprintf("SELECT data FROM %s WHERE id = %s AND expiry >= %d", + $this->options['table'], + $this->db->getTextValue(md5($id)), + time()); + $result = $this->db->getOne($query); + if (MDB::isError($result)) { + new MDB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + $this->crc = strlen($result) . crc32($result); + return $result; + } + + /** + * Write session data + * + * @param string $id Session id + * @param mixed $data Data + * + * @return bool + */ + function write($id, $data) + { + if ((false !== $this->crc) && + ($this->crc === strlen($data) . crc32($data))) { + // $_SESSION hasn't been touched, no need to update the blob column + $query = sprintf("UPDATE %s SET expiry = %d WHERE id = %s", + $this->options['table'], + time() + ini_get('session.gc_maxlifetime'), + $this->db->getTextValue(md5($id))); + } else { + // Check if table row already exists + $query = sprintf("SELECT COUNT(id) FROM %s WHERE id = %s", + $this->options['table'], + $this->db->getTextValue(md5($id))); + $result = $this->db->getOne($query); + if (MDB::isError($result)) { + new MDB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + if (0 == intval($result)) { + // Insert new row into table + $query = sprintf("INSERT INTO %s (id, expiry, data) VALUES (%s, %d, %s)", + $this->options['table'], + $this->db->getTextValue(md5($id)), + time() + ini_get('session.gc_maxlifetime'), + $this->db->getTextValue($data)); + } else { + // Update existing row + $query = sprintf("UPDATE %s SET expiry = %d, data = %s WHERE id = %s", + $this->options['table'], + time() + ini_get('session.gc_maxlifetime'), + $this->db->getTextValue($data), + $this->db->getTextValue(md5($id))); + } + } + $result = $this->db->query($query); + if (MDB::isError($result)) { + new MDB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Destroy session data + * + * @param string $id Session id + * + * @return bool + */ + function destroy($id) + { + $query = sprintf("DELETE FROM %s WHERE id = %s", + $this->options['table'], + $this->db->getTextValue(md5($id))); + $result = $this->db->query($query); + if (MDB::isError($result)) { + new MDB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Replicate session data to table specified in option 'replicateBeforeDestroy' + * + * @param string $targetTable Table to replicate to + * @param string $id Id of record to replicate + * + * @access private + * @return bool + */ + function replicate($targetTable, $id = null) + { + if (is_null($id)) { + $id = HTTP_Session::id(); + } + + // Check if table row already exists + $query = sprintf("SELECT COUNT(id) FROM %s WHERE id = %s", + $targetTable, + $this->db->getTextValue(md5($id))); + $result = $this->db->getOne($query); + if (MDB::isError($result)) { + new MDB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + + // Insert new row into dest table + if (0 == intval($result)) { + $query = sprintf("INSERT INTO %s SELECT * FROM %s WHERE id = %s", + $targetTable, + $this->options['table'], + $this->db->getTextValue(md5($id))); + } else { + // Update existing row + $query = sprintf("UPDATE %s dst, %s src SET dst.expiry = src.expiry, dst.data = src.data WHERE dst.id = src.id AND src.id = %s", + $targetTable, + $this->options['table'], + $this->db->getTextValue(md5($id))); + } + + $result = $this->db->query($query); + if (MDB::isError($result)) { + new MDB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Garbage collection + * + * @param int $maxlifetime Maximum lifetime + * + * @return bool + */ + function gc($maxlifetime) + { + $query = sprintf("DELETE FROM %s WHERE expiry < %d", + $this->options['table'], + time()); + $result = $this->db->query($query); + if (MDB::isError($result)) { + new MDB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + if ($this->options['autooptimize']) { + switch($this->db->phptype) { + case 'mysql': + $query = sprintf("OPTIMIZE TABLE %s", $this->options['table']); + break; + case 'pgsql': + $query = sprintf("VACUUM %s", $this->options['table']); + break; + default: + $query = null; + break; + } + if (isset($query)) { + $result = $this->db->query($query); + if (MDB::isError($result)) { + new MDB_Error($result->code, PEAR_ERROR_DIE); + return false; + } + } + } + + return true; + } +} +?> \ No newline at end of file diff --git a/include/HTTP_Session/Session/Container/MDB2.php b/include/HTTP_Session/Session/Container/MDB2.php new file mode 100644 index 0000000..e36616b --- /dev/null +++ b/include/HTTP_Session/Session/Container/MDB2.php @@ -0,0 +1,364 @@ + + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: MDB2.php,v 1.5 2007/07/14 12:11:55 troehr Exp $ + * @link http://pear.php.net/package/HTTP_Session + * @since File available since Release 0.5.0 + */ + +require_once 'HTTP/Session/Container.php'; +require_once 'MDB2.php'; + +/** + * Database container for session data + * + * Create the following table to store session data + * + * CREATE TABLE `sessiondata` ( + * `id` CHAR(32) NOT NULL, + * `expiry` INT UNSIGNED NOT NULL DEFAULT 0, + * `data` TEXT NOT NULL, + * PRIMARY KEY (`id`) + * ); + * + * + * @category HTTP + * @package HTTP_Session + * @author David Costa + * @author Michael Metz + * @author Stefan Neufeind + * @author Torsten Roehr + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/HTTP_Session + * @since Class available since Release 0.5.0 + */ +class HTTP_Session_Container_MDB2 extends HTTP_Session_Container +{ + /** + * MDB2 connection object + * + * @var object MDB2 + * @access private + */ + var $db = null; + + /** + * Session data cache id + * + * @var mixed + * @access private + */ + var $crc = false; + + /** + * Constructor method + * + * $options is an array with the options.
+ * The options are: + *
    + *
  • 'dsn' - The DSN string
  • + *
  • 'table' - Table with session data, default is 'sessiondata'
  • + *
  • 'autooptimize' - Boolean, 'true' to optimize + * the table on garbage collection, default is 'false'.
  • + *
+ * + * @param array $options Options + * + * @access public + * @return void + */ + function HTTP_Session_Container_MDB2($options) + { + $this->_setDefaults(); + if (is_array($options)) { + $this->_parseOptions($options); + } else { + $this->options['dsn'] = $options; + } + } + + /** + * Connect to database by using the given DSN string + * + * @param string $dsn DSN string + * + * @access private + * @return mixed Object on error, otherwise bool + */ + function _connect($dsn) + { + if (is_string($dsn) || is_array($dsn)) { + $this->db = MDB2::connect($dsn); + } else if (is_object($dsn) && is_a($dsn, 'MDB2_Driver_Common')) { + $this->db = $dsn; + } else if (is_object($dsn) && MDB2::isError($dsn)) { + return $dsn; + } else { + return new PEAR_Error("The given dsn was not valid in file " . __FILE__ + . " at line " . __LINE__, + 41, + PEAR_ERROR_RETURN, + null, + null + ); + + } + + if (MDB2::isError($this->db)) { + return new MDB2_Error($this->db->code, PEAR_ERROR_DIE); + } + + return true; + } + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['dsn'] = null; + $this->options['table'] = 'sessiondata'; + $this->options['autooptimize'] = false; + } + + /** + * Establish connection to a database + * + * @param string $save_path Save path + * @param string $session_name Session name + * + * @return bool + */ + function open($save_path, $session_name) + { + if (MDB2::isError($this->_connect($this->options['dsn']))) { + return false; + } else { + return true; + } + } + + /** + * Free resources + * + * @return bool + */ + function close() + { + return true; + } + + /** + * Read session data + * + * @param string $id Session id + * + * @return mixed + */ + function read($id) + { + $query = sprintf("SELECT data FROM %s WHERE id = %s AND expiry >= %d", + $this->options['table'], + $this->db->quote(md5($id), 'text'), + time()); + $result = $this->db->queryOne($query); + if (MDB2::isError($result)) { + $this->db->raiseError($result->code, PEAR_ERROR_DIE); + return false; + } + $this->crc = strlen($result) . crc32($result); + return $result; + } + + /** + * Write session data + * + * @param string $id Session id + * @param mixed $data Data + * + * @return bool + */ + function write($id, $data) + { + if ((false !== $this->crc) && + ($this->crc === strlen($data) . crc32($data))) { + // $_SESSION hasn't been touched, no need to update the blob column + $query = sprintf("UPDATE %s SET expiry = %d WHERE id = %s", + $this->options['table'], + time() + ini_get('session.gc_maxlifetime'), + $this->db->quote(md5($id), 'text')); + } else { + // Check if table row already exists + $query = sprintf("SELECT COUNT(id) FROM %s WHERE id = %s", + $this->options['table'], + $this->db->quote(md5($id), 'text')); + $result = $this->db->queryOne($query); + if (MDB2::isError($result)) { + $this->db->raiseError($result->code, PEAR_ERROR_DIE); + return false; + } + if (0 == intval($result)) { + // Insert new row into table + $query = sprintf("INSERT INTO %s (id, expiry, data) VALUES (%s, %d, %s)", + $this->options['table'], + $this->db->quote(md5($id), 'text'), + time() + ini_get('session.gc_maxlifetime'), + $this->db->quote($data, 'text')); + } else { + // Update existing row + $query = sprintf("UPDATE %s SET expiry = %d, data = %s WHERE id = %s", + $this->options['table'], + time() + ini_get('session.gc_maxlifetime'), + $this->db->quote($data, 'text'), + $this->db->quote(md5($id), 'text')); + } + } + $result = $this->db->query($query); + if (MDB2::isError($result)) { + $this->db->raiseError($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Destroy session data + * + * @param string $id Session id + * + * @return bool + */ + function destroy($id) + { + $query = sprintf("DELETE FROM %s WHERE id = %s", + $this->options['table'], + $this->db->quote(md5($id), 'text')); + $result = $this->db->query($query); + if (MDB2::isError($result)) { + $this->db->raiseError($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Replicate session data to table specified in option 'replicateBeforeDestroy' + * + * @param string $targetTable Table to replicate to + * @param string $id Id of record to replicate + * + * @access private + * @return bool + */ + function replicate($targetTable, $id = null) + { + if (is_null($id)) { + $id = HTTP_Session::id(); + } + + // Check if table row already exists + $query = sprintf("SELECT COUNT(id) FROM %s WHERE id = %s", + $targetTable, + $this->db->quote(md5($id), 'text')); + $result = $this->db->queryOne($query); + if (MDB2::isError($result)) { + $this->db->raiseError($result->code, PEAR_ERROR_DIE); + return false; + } + + // Insert new row into dest table + if (0 == intval($result)) { + $query = sprintf("INSERT INTO %s SELECT * FROM %s WHERE id = %s", + $targetTable, + $this->options['table'], + $this->db->quote(md5($id), 'text')); + + } else { + // Update existing row + $query = sprintf("UPDATE %s dst, %s src SET dst.expiry = src.expiry, dst.data = src.data WHERE dst.id = src.id AND src.id = %s", + $targetTable, + $this->options['table'], + $this->db->quote(md5($id), 'text')); + } + + $result = $this->db->query($query); + if (MDB2::isError($result)) { + $this->db->raiseError($result->code, PEAR_ERROR_DIE); + return false; + } + + return true; + } + + /** + * Garbage collection + * + * @param int $maxlifetime Maximum lifetime + * + * @return bool + */ + function gc($maxlifetime) + { + $query = sprintf("DELETE FROM %s WHERE expiry < %d", + $this->options['table'], + time()); + $result = $this->db->query($query); + if (MDB2::isError($result)) { + $this->db->raiseError($result->code, PEAR_ERROR_DIE); + return false; + } + if ($this->options['autooptimize']) { + switch($this->db->phptype) { + case 'mysql': + $query = sprintf("OPTIMIZE TABLE %s", $this->options['table']); + break; + case 'pgsql': + $query = sprintf("VACUUM %s", $this->options['table']); + break; + default: + $query = null; + break; + } + if (isset($query)) { + $result = $this->db->query($query); + if (MDB2::isError($result)) { + $this->db->raiseError($result->code, PEAR_ERROR_DIE); + return false; + } + } + } + + return true; + } +} +?> \ No newline at end of file diff --git a/include/HTTP_Session/Session/Container/Memcache.php b/include/HTTP_Session/Session/Container/Memcache.php new file mode 100644 index 0000000..d2889ff --- /dev/null +++ b/include/HTTP_Session/Session/Container/Memcache.php @@ -0,0 +1,202 @@ + + * @author Torsten Roehr + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Memcache.php,v 1.3 2007/07/14 12:11:55 troehr Exp $ + * @link http://pear.php.net/package/HTTP_Session + * @since File available since Release 0.5.6 + */ + +require_once 'HTTP/Session/Container.php'; + +/** + * Database container for session data + * + * @category HTTP + * @package HTTP_Session + * @author Chad Wagner + * @author Torsten Roehr + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/HTTP_Session + * @since Class available since Release 0.5.6 + */ +class HTTP_Session_Container_Memcache extends HTTP_Session_Container +{ + /** + * Memcache connection object + * + * @var object Memcache + * @access private + */ + var $mc; + + /** + * Constructor method + * + * $options is an array with the options.
+ * The options are: + *
    + *
  • 'memcache' - Memcache object + *
  • 'prefix' - Key prefix, default is 'sessiondata:'
  • + *
+ * + * @param array $options Options + * + * @access public + * @return object + */ + function HTTP_Session_Container_Memcache($options) + { + $this->_setDefaults(); + + if (is_array($options)) { + $this->_parseOptions($options); + } + } + + /** + * Connect to database by using the given DSN string + * + * @param string $mc Memcache object + * + * @access private + * @return mixed Object on error, otherwise bool + */ + function _connect($mc) + { + if (is_object($mc) && is_a($mc, 'Memcache')) { + $this->mc = $mc; + + } else { + + return new PEAR_Error('The given memcache object was not valid in file ' + . __FILE__ . ' at line ' . __LINE__, + 41, + PEAR_ERROR_RETURN, + null, + null + ); + } + + return true; + } + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['prefix'] = 'sessiondata:'; + $this->options['memcache'] = null; + } + + /** + * Establish connection to a database + * + * @param string $save_path Save path + * @param string $session_name Session name + * + * @access public + * @return mixed Object on error, otherwise bool + */ + function open($save_path, $session_name) + { + return $this->_connect($this->options['memcache']); + } + + /** + * Free resources + * + * @access public + * @return bool + */ + function close() + { + return true; + } + + /** + * Read session data + * + * @param string $id Session id + * + * @access public + * @return mixed + */ + function read($id) + { + $result = $this->mc->get($this->options['prefix'] . $id); + return $result; + } + + /** + * Write session data + * + * @param string $id Session id + * @param mixed $data Session data + * + * @access public + * @return bool + */ + function write($id, $data) + { + $this->mc->set($this->options['prefix'] . $id, + $data, + MEMCACHE_COMPRESSED, + time() + ini_get('session.gc_maxlifetime')); + + return true; + } + + /** + * Destroy session data + * + * @param string $id Session id + * + * @access public + * @return bool + */ + function destroy($id) + { + $this->mc->delete($this->options['prefix'] . $id); + return true; + } + + /** + * Garbage collection + * + * @param int $maxlifetime Maximum lifetime + * + * @access public + * @return bool + */ + function gc($maxlifetime) + { + return true; + } +} +?> \ No newline at end of file diff --git a/include/InventoryHandler.php b/include/InventoryHandler.php new file mode 100644 index 0000000..ace5bc5 --- /dev/null +++ b/include/InventoryHandler.php @@ -0,0 +1,16 @@ + diff --git a/include/InventoryPDFController.php b/include/InventoryPDFController.php new file mode 100644 index 0000000..01d3427 --- /dev/null +++ b/include/InventoryPDFController.php @@ -0,0 +1,417 @@ +moduleName = $module; + } + + function loadRecord($id) { + global $current_user; + $this->focus = $focus = CRMEntity::getInstance($this->moduleName); + $focus->retrieve_entity_info($id,$this->moduleName); + $focus->apply_field_security(); + $focus->id = $id; + $this->associated_products = getAssociatedProducts($this->moduleName,$focus); + } + + function getPDFGenerator() { + return new Vtiger_PDF_Generator(); + } + + function getContentViewer() { + if($this->focusColumnValue('hdnTaxType') == "individual") { + $contentViewer = new Vtiger_PDF_InventoryContentViewer(); + } else { + $contentViewer = new Vtiger_PDF_InventoryTaxGroupContentViewer(); + } + $contentViewer->setContentModels($this->buildContentModels()); + $contentViewer->setSummaryModel($this->buildSummaryModel()); + $contentViewer->setLabelModel($this->buildContentLabelModel()); + $contentViewer->setWatermarkModel($this->buildWatermarkModel()); + return $contentViewer; + } + + function getHeaderViewer() { + $headerViewer = new Vtiger_PDF_InventoryHeaderViewer(); + $headerViewer->setModel($this->buildHeaderModel()); + return $headerViewer; + } + + function getFooterViewer() { + $footerViewer = new Vtiger_PDF_InventoryFooterViewer(); + $footerViewer->setModel($this->buildFooterModel()); + $footerViewer->setLabelModel($this->buildFooterLabelModel()); + $footerViewer->setOnLastPage(); + return $footerViewer; + } + + function getPagerViewer() { + $pagerViewer = new Vtiger_PDF_PagerViewer(); + $pagerViewer->setModel($this->buildPagermodel()); + return $pagerViewer; + } + + function Output($filename, $type) { + if(is_null($this->focus)) return; + + $pdfgenerator = $this->getPDFGenerator(); + + $pdfgenerator->setPagerViewer($this->getPagerViewer()); + $pdfgenerator->setHeaderViewer($this->getHeaderViewer()); + $pdfgenerator->setFooterViewer($this->getFooterViewer()); + $pdfgenerator->setContentViewer($this->getContentViewer()); + + $pdfgenerator->generate($filename, $type); + } + + + // Helper methods + + function buildContentModels() { + $associated_products = $this->associated_products; + $contentModels = array(); + $productLineItemIndex = 0; + $totaltaxes = 0; + foreach($associated_products as $productLineItem) { + ++$productLineItemIndex; + + $contentModel = new Vtiger_PDF_Model(); + + $discountPercentage = 0.00; + $total_tax_percent = 0.00; + $producttotal_taxes = 0.00; + $quantity = ''; $listPrice = ''; $discount = ''; $taxable_total = ''; + $tax_amount = ''; $producttotal = ''; + + + $quantity = $productLineItem["qty{$productLineItemIndex}"]; + $listPrice = $productLineItem["listPrice{$productLineItemIndex}"]; + $discount = $productLineItem["discountTotal{$productLineItemIndex}"]; + $taxable_total = $quantity * $listPrice - $discount; + $taxable_total = number_format($taxable_total, 2,'.',''); //Convert to 2 decimals + $producttotal = $taxable_total; + if($this->focus->column_fields["hdnTaxType"] == "individual") { + for($tax_count=0;$tax_countset('Name', $productName); + $contentModel->set('Code', $productLineItem["hdnProductcode{$productLineItemIndex}"]); + $contentModel->set('Quantity', $quantity); + $contentModel->set('Price', $this->formatPrice($listPrice)); + $contentModel->set('Discount', $this->formatPrice($discount)."\n ($discountPercentage%)"); + $contentModel->set('Tax', $this->formatPrice($tax)."\n ($total_tax_percent%)"); + $contentModel->set('Total', $this->formatPrice($producttotal)); + $contentModel->set('Comment', $productLineItem["comment{$productLineItemIndex}"]); + + $contentModels[] = $contentModel; + } + $this->totaltaxes = $totaltaxes; //will be used to add it to the net total + + return $contentModels; + } + + function buildContentLabelModel() { + $labelModel = new Vtiger_PDF_Model(); + $labelModel->set('Code', getTranslatedString('Product Code',$this->moduleName)); + $labelModel->set('Name', getTranslatedString('Product Name',$this->moduleName)); + $labelModel->set('Quantity', getTranslatedString('Quantity',$this->moduleName)); + $labelModel->set('Price', getTranslatedString('Price',$this->moduleName)); + $labelModel->set('Discount', getTranslatedString('Discount',$this->moduleName)); + $labelModel->set('Tax', getTranslatedString('Tax',$this->moduleName)); + $labelModel->set('Total', getTranslatedString('Total',$this->moduleName)); + $labelModel->set('Comment', getTranslatedString('Comment'),$this->moduleName); + return $labelModel; + } + + function buildSummaryModel() { + $associated_products = $this->associated_products; + $final_details = $associated_products[1]['final_details']; + + $summaryModel = new Vtiger_PDF_Model(); + + $netTotal = $discount = $handlingCharges = $handlingTaxes = 0; + $adjustment = $grandTotal = 0; + + $productLineItemIndex = 0; + $sh_tax_percent = 0; + foreach($associated_products as $productLineItem) { + ++$productLineItemIndex; + $netTotal += $productLineItem["netPrice{$productLineItemIndex}"]; + } + $netTotal = number_format(($netTotal + $this->totaltaxes), 2,'.', ''); + $summaryModel->set(getTranslatedString("Net Total", $this->moduleName), $this->formatPrice($netTotal)); + + $discount_amount = $final_details["discount_amount_final"]; + $discount_percent = $final_details["discount_percentage_final"]; + + $discount = 0.0; + if(!empty($discount_amount)) { + $discount = $discount_amount; + } else if(!empty($discount_percent)) { + $discount = (($discount_percent*$final_details["hdnSubTotal"])/100); + } + $summaryModel->set(getTranslatedString("Discount", $this->moduleName), $this->formatPrice($discount)); + + $group_total_tax_percent = '0.00'; + //To calculate the group tax amount + if($final_details['taxtype'] == 'group') { + $group_tax_details = $final_details['taxes']; + for($i=0;$iset(getTranslatedString("Tax:", $this->moduleName)."($group_total_tax_percent%)", $this->formatPrice($final_details['tax_totalamount'])); + } + //Shipping & Handling taxes + $sh_tax_details = $final_details['sh_taxes']; + for($i=0;$ibuildCurrencySymbol(); + + $summaryModel->set(getTranslatedString("Shipping & Handling Charges", $this->moduleName), $this->formatPrice($final_details['shipping_handling_charge'])); + $summaryModel->set(getTranslatedString("Shipping & Handling Tax:", $this->moduleName)."($sh_tax_percent%)", $this->formatPrice($final_details['shtax_totalamount'])); + $summaryModel->set(getTranslatedString("Adjustment", $this->moduleName), $this->formatPrice($final_details['adjustment'])); + $summaryModel->set(getTranslatedString("Grand Total : (in $currencySymbol)", $this->moduleName), $this->formatPrice($final_details['grandTotal'])); // TODO add currency string + + return $summaryModel; + } + + function buildHeaderModel() { + $headerModel = new Vtiger_PDF_Model(); + $headerModel->set('title', $this->buildHeaderModelTitle()); + $modelColumns = array($this->buildHeaderModelColumnLeft(), $this->buildHeaderModelColumnCenter(), $this->buildHeaderModelColumnRight()); + $headerModel->set('columns', $modelColumns); + + return $headerModel; + } + + function buildHeaderModelTitle() { + return $this->moduleName; + } + + function buildHeaderModelColumnLeft() { + global $adb; + + // Company information + $result = $adb->pquery("SELECT * FROM vtiger_organizationdetails", array()); + $num_rows = $adb->num_rows($result); + if($num_rows) { + $resultrow = $adb->fetch_array($result); + + $addressValues = array(); + $addressValues[] = $resultrow['address']; + if(!empty($resultrow['city'])) $addressValues[]= "\n".$resultrow['city']; + if(!empty($resultrow['state'])) $addressValues[]= ",".$resultrow['state']; + if(!empty($resultrow['code'])) $addressValues[]= $resultrow['code']; + if(!empty($resultrow['country'])) $addressValues[]= "\n".$resultrow['country']; + + + if(!empty($resultrow['phone'])) $additionalCompanyInfo[]= "\n".getTranslatedString("Phone: ", $this->moduleName). $resultrow['phone']; + if(!empty($resultrow['fax'])) $additionalCompanyInfo[]= "\n".getTranslatedString("Fax: ", $this->moduleName). $resultrow['fax']; + if(!empty($resultrow['website'])) $additionalCompanyInfo[]= "\n".getTranslatedString("Website: ", $this->moduleName). $resultrow['website']; + + $modelColumnLeft = array( + 'logo' => "test/logo/".$resultrow['logoname'], + 'summary' => decode_html($resultrow['organizationname']), + 'content' => $this->joinValues($addressValues, ' '). $this->joinValues($additionalCompanyInfo, ' ') + ); + } + return $modelColumnLeft; + } + + + function buildHeaderModelColumnCenter() { + $customerName = $this->resolveReferenceLabel($this->focusColumnValue('account_id'), 'Accounts'); + $contactName = $this->resolveReferenceLabel($this->focusColumnValue('contact_id'), 'Contacts'); + + $customerNameLabel = getTranslatedString('Customer Name', $this->moduleName); + $contactNameLabel = getTranslatedString('Contact Name', $this->moduleName); + $modelColumnCenter = array( + $customerNameLabel => $customerName, + $contactNameLabel => $contactName, + ); + return $modelColumnCenter; + } + + function buildHeaderModelColumnRight() { + $issueDateLabel = getTranslatedString('Issued Date', $this->moduleName); + $validDateLabel = getTranslatedString('Valid Date', $this->moduleName); + $billingAddressLabel = getTranslatedString('Billing Address', $this->moduleName); + $shippingAddressLabel = getTranslatedString('Shipping Address', $this->moduleName); + + $modelColumnRight = array( + 'dates' => array( + $issueDateLabel => $this->formatDate(date("Y-m-d")), + $validDateLabel => $this->formatDate($this->focusColumnValue('validtill')), + ), + $billingAddressLabel => $this->buildHeaderBillingAddress(), + $shippingAddressLabel => $this->buildHeaderShippingAddress() + ); + return $modelColumnRight; + } + + function buildFooterModel() { + $footerModel = new Vtiger_PDF_Model(); + $footerModel->set(Vtiger_PDF_InventoryFooterViewer::$DESCRIPTION_DATA_KEY, from_html($this->focusColumnValue('description'))); + $footerModel->set(Vtiger_PDF_InventoryFooterViewer::$TERMSANDCONDITION_DATA_KEY, from_html($this->focusColumnValue('terms_conditions'))); + return $footerModel; + } + + function buildFooterLabelModel() { + $labelModel = new Vtiger_PDF_Model(); + $labelModel->set(Vtiger_PDF_InventoryFooterViewer::$DESCRIPTION_LABEL_KEY, getTranslatedString('Description',$this->moduleName)); + $labelModel->set(Vtiger_PDF_InventoryFooterViewer::$TERMSANDCONDITION_LABEL_KEY, getTranslatedString('Terms & Conditions',$this->moduleName)); + return $labelModel; + } + + function buildPagerModel() { + $footerModel = new Vtiger_PDF_Model(); + $footerModel->set('format', '-%s-'); + return $footerModel; + } + + function getWatermarkContent() { + return ''; + } + + function buildWatermarkModel() { + $watermarkModel = new Vtiger_PDF_Model(); + $watermarkModel->set('content', $this->getWatermarkContent()); + return $watermarkModel; + } + + function buildHeaderBillingAddress() { + $billPoBox = $this->focusColumnValues(array('bill_pobox')); + $billStreet = $this->focusColumnValues(array('bill_street')); + $billCity = $this->focusColumnValues(array('bill_city')); + $billState = $this->focusColumnValues(array('bill_state')); + $billCountry = $this->focusColumnValues(array('bill_country')); + $billCode = $this->focusColumnValues(array('bill_code')); + $address = $this->joinValues(array($billPoBox, $billStreet), ' '); + $address .= "\n".$this->joinValues(array($billCity, $billState), ',')." ".$billCode; + $address .= "\n".$billCountry; + return $address; + } + + function buildHeaderShippingAddress() { + $shipPoBox = $this->focusColumnValues(array('ship_pobox')); + $shipStreet = $this->focusColumnValues(array('ship_street')); + $shipCity = $this->focusColumnValues(array('ship_city')); + $shipState = $this->focusColumnValues(array('ship_state')); + $shipCountry = $this->focusColumnValues(array('ship_country')); + $shipCode = $this->focusColumnValues(array('ship_code')); + $address = $this->joinValues(array($shipPoBox, $shipStreet), ' '); + $address .= "\n".$this->joinValues(array($shipCity, $shipState), ',')." ".$shipCode; + $address .= "\n".$shipCountry; + return $address; + } + + function buildCurrencySymbol() { + global $adb; + $currencyId = $this->focus->column_fields['currency_id']; + if(!empty($currencyId)) { + $result = $adb->pquery("SELECT currency_symbol FROM vtiger_currency_info WHERE id=?", array($currencyId)); + return decode_html($adb->query_result($result,0,'currency_symbol')); + } + return false; + } + function focusColumnValues($names, $delimeter="\n") { + if(!is_array($names)) { + $names = array($names); + } + $values = array(); + foreach($names as $name) { + $value = $this->focusColumnValue($name, false); + if($value !== false) { + $values[] = $value; + } + } + return $this->joinValues($values, $delimeter); + } + + function focusColumnValue($key, $defvalue='') { + $focus = $this->focus; + if(isset($focus->column_fields[$key])) { + return $focus->column_fields[$key]; + } + return $defvalue; + } + + function resolveReferenceLabel($id, $module=false) { + if(empty($id)) { + return ''; + } + if($module === false) { + $module = getSalesEntityType($id); + } + $label = getEntityName($module, array($id)); + return decode_html($label[$id]); + } + + function joinValues($values, $delimeter= "\n") { + $valueString = ''; + foreach($values as $value) { + if(empty($value)) continue; + $valueString .= $value . $delimeter; + } + return rtrim($valueString, $delimeter); + } + + function formatNumber($value) { + return number_format($value); + } + + function formatPrice($value, $decimal=2) { + $currencyField = new CurrencyField($value); + return $currencyField->getDisplayValue(null, true); + } + + function formatDate($value) { + return DateTimeField::convertToUserFormat($value); + } + +} +?> \ No newline at end of file diff --git a/include/ListView/ListView.php b/include/ListView/ListView.php new file mode 100644 index 0000000..25bc7ae --- /dev/null +++ b/include/ListView/ListView.php @@ -0,0 +1,288 @@ +debug("Entering ListView() method ..."); + + + if(!$this->initialized){ + global $list_max_entries_per_page; + $this->records_per_page = $list_max_entries_per_page + 0; + $this->initialized = true; + global $theme, $app_strings, $image_path, $currentModule; + $this->local_theme = $theme; + $this->local_app_strings = &$app_strings; + $this->local_image_path = $image_path; + $this->local_current_module = $currentModule; + + if(empty($this->local_image_path)){ + $this->local_image_path = 'themes/'.$theme.'/images'; + } + $this->log = LoggerManager::getLogger('listView_'.$this->local_current_module); + $log->debug("Exiting ListView method ..."); + } +} +/**sets the header title */ + function setHeaderTitle($value){ + global $log; + $log->debug("Entering setHeaderTitle(".$value.") method ..."); + $this->header_title = $value; + $log->debug("Exiting setHeaderTitle method ..."); +} +/**sets the header text this is text thats appended to the header vtiger_table and is usually used for the creation of buttons + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function setHeaderText($value){ + global $log; + $log->debug("Entering setHeaderText(".$value.") method ..."); + $this->header_text = $value; + $log->debug("Exiting setHeaderText method ..."); +} +/**sets the parameters dealing with the db + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function setQuery($where, $limit, $orderBy, $varName, $allowOrderByOveride= true){ + global $log; + $log->debug("Entering setQuery(".$where.",". $limit.",". $orderBy.",". $varName.",". $allowOrderByOveride.") method ..."); + $this->query_where = $where; + if($this->getSessionVariable("query", "where") != $where){ + $this->querey_where_has_changed = true; + $this->setSessionVariable("query", "where", $where); + } + + $this->query_limit = $limit; + if(!$allowOrderByOveride){ + $this->query_orderby = $orderBy; + $log->debug("Exiting setQuery method ..."); + return; + } + $sortBy = $this->getSessionVariable($varName, "ORDER_BY") ; + + if(empty($sortBy)){ + $this->setUserVariable($varName, "ORDER_BY", $orderBy); + $sortBy = $orderBy; + }else{ + $this->setUserVariable($varName, "ORDER_BY", $sortBy); + } + if($sortBy == 'amount'){ + $sortBy = 'amount*1'; + } + + $desc = false; + $desc = $this->getSessionVariable($varName, $sortBy."_desc"); + + if(empty($desc)) + $desc = false; + if(isset($_REQUEST[$this->getSessionVariableName($varName, "ORDER_BY")])) + $last = $this->getSessionVariable($varName, "ORDER_BY_LAST"); + if(!empty($last) && $last == $sortBy){ + $desc = !$desc; + }else { + $this->setSessionVariable($varName, "ORDER_BY_LAST", $sortBy); + } + $this->setSessionVariable($varName, $sortBy."_desc", $desc); + if(!empty($sortBy)){ + if(substr_count(strtolower($sortBy), ' desc') == 0 && substr_count(strtolower($sortBy), ' asc') == 0){ + if($desc){ + $this->query_orderby = $sortBy.' desc'; + }else{ + $this->query_orderby = $sortBy.' asc'; + } + } + else{ + $this->query_orderby = $sortBy; + } + }else { + $this->query_orderby = ""; + } + $log->debug("Exiting setQuery method ..."); + + + + +} + +/**sets the theme used only use if it is different from the global + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function setTheme($theme){ + global $log; + $log->debug("Entering setTheme(".$theme.") method ..."); + $this->local_theme = $theme; + if(isset($this->xTemplate))$this->xTemplate->assign("THEME", $this->local_theme ); + $log->debug("Exiting setTheme method ..."); +} + +/**sets the AppStrings used only use if it is different from the global + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function setAppStrings(&$app_strings){ + global $log; + $log->debug("Entering setAppStrings(".$app_strings.") method ..."); + unset($this->local_app_strings); + $this->local_app_strings = $app_strings; + if(isset($this->xTemplate))$this->xTemplate->assign("APP", $this->local_app_strings ); + $log->debug("Exiting setAppStrings method ..."); +} + +/**sets the ModStrings used + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function setModStrings(&$mod_strings){ + global $log; + $log->debug("Entering setModStrings(".$mod_strings.") method ..."); + unset($this->local_module_strings); + $this->local_mod_strings = $mod_strings; + if(isset($this->xTemplate))$this->xTemplate->assign("MOD", $this->local_mod_strings ); + $log->debug("Exiting setModStrings method ..."); +} + +/**sets the ImagePath used + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function setImagePath($image_path){ + global $log; + $log->debug("Entering setImagePath(".$image_path.") method ..."); + $this->local_image_path = $image_path; + if(empty($this->local_image_path)){ + $this->local_image_path = 'themes/'.$this->local_theme.'/images'; + } + if(isset($this->xTemplate))$this->xTemplate->assign("IMAGE_PATH", $this->local_image_path ); + $log->debug("Exiting setImagePath method ..."); +} + +/**sets the currentModule only use if this is different from the global + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function setCurrentModule($currentModule){ + global $log; + $log->debug("Entering setCurrentModule(".$currentModule.") method ..."); + unset($this->local_current_module); + $this->local_current_module = $currentModule; + $this->log = LoggerManager::getLogger('listView_'.$this->local_current_module); + if(isset($this->xTemplate))$this->xTemplate->assign("MODULE_NAME", $this->local_current_module ); + $log->debug("Exiting setCurrentModule method ..."); + +} + + +/**INTERNAL FUNCTION sets a session variable + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function setSessionVariable($localVarName,$varName, $value){ + global $log; + $log->debug("Entering setSessionVariable(".$localVarName.",".$varName.",". $value.") method ..."); + $_SESSION[$this->local_current_module."_".$localVarName."_".$varName] = $value; + $log->debug("Exiting setSessionVariable method ..."); +} + +function setUserVariable($localVarName,$varName, $value){ + global $log; + $log->debug("Entering setUserVariable(".$localVarName.",".$varName.",". $value.") method ..."); + global $current_user; + $current_user->setPreference($this->local_current_module."_".$localVarName."_".$varName, $value); + $log->debug("Exiting setUserVariable method ..."); +} + +/**INTERNAL FUNCTION returns a session variable first checking the querey for it then checking the session + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________. +*/ + function getSessionVariable($localVarName,$varName){ + global $log; + $log->debug("Entering getSessionVariable(".$localVarName.",".$varName.") method ..."); + if(isset($_REQUEST[$this->getSessionVariableName($localVarName, $varName)])){ + $this->setSessionVariable($localVarName,$varName,vtlib_purify($_REQUEST[$this->getSessionVariableName($localVarName, $varName)])); + } + if(isset($_SESSION[$this->getSessionVariableName($localVarName, $varName)])){ + $log->debug("Exiting getSessionVariable method ..."); + return $_SESSION[$this->getSessionVariableName($localVarName, $varName)]; + } + $log->debug("Exiting getSessionVariable method ..."); + return ""; +} + +/** + + * @return void + * @param unknown $localVarName + * @param unknown $varName + * @desc INTERNAL FUNCTION returns the session/query variable name + * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc. + * All Rights Reserved. + * Contributor(s): ______________________________________.. + */ +function getSessionVariableName($localVarName,$varName){ + global $log; + $log->debug("Entering getSessionVariableName(".$localVarName.",".$varName.") method ..."); + $log->debug("Exiting getSessionVariableName method ..."); + return $this->local_current_module."_".$localVarName."_".$varName; +} + + +} + +?> \ No newline at end of file diff --git a/include/ListView/ListViewController.php b/include/ListView/ListViewController.php new file mode 100644 index 0000000..2eea4bd --- /dev/null +++ b/include/ListView/ListViewController.php @@ -0,0 +1,717 @@ +queryGenerator = $generator; + $this->db = $db; + $this->user = $user; + $this->nameList = array(); + $this->typeList = array(); + $this->ownerNameList = array(); + $this->picklistValueMap = array(); + $this->picklistRoleMap = array(); + $this->headerSortingEnabled = true; + } + + public function isHeaderSortingEnabled() { + return $this->headerSortingEnabled; + } + + public function setHeaderSorting($enabled) { + $this->headerSortingEnabled = $enabled; + } + + public function setupAccessiblePicklistValueList($name) { + $isRoleBased = vtws_isRoleBasedPicklist($name); + $this->picklistRoleMap[$name] = $isRoleBased; + if ($this->picklistRoleMap[$name]) { + $this->picklistValueMap[$name] = getAssignedPicklistValues($name,$this->user->roleid, $this->db); + } + } + + public function fetchNameList($field, $result) { + $referenceFieldInfoList = $this->queryGenerator->getReferenceFieldInfoList(); + $fieldName = $field->getFieldName(); + $rowCount = $this->db->num_rows($result); + + $idList = array(); + for ($i = 0; $i < $rowCount; $i++) { + $id = $this->db->query_result($result, $i, $field->getColumnName()); + if (!isset($this->nameList[$fieldName][$id])) { + $idList[$id] = $id; + } + } + + $idList = array_keys($idList); + if(count($idList) == 0) { + return; + } + $moduleList = $referenceFieldInfoList[$fieldName]; + foreach ($moduleList as $module) { + $meta = $this->queryGenerator->getMeta($module); + if ($meta->isModuleEntity()) { + if($module == 'Users') { + $nameList = getOwnerNameList($idList); + } else { + //TODO handle multiple module names overriding each other. + $nameList = getEntityName($module, $idList); + } + } else { + $nameList = vtws_getActorEntityName($module, $idList); + } + $entityTypeList = array_intersect(array_keys($nameList), $idList); + foreach ($entityTypeList as $id) { + $this->typeList[$id] = $module; + } + if(empty($this->nameList[$fieldName])) { + $this->nameList[$fieldName] = array(); + } + foreach ($entityTypeList as $id) { + $this->typeList[$id] = $module; + $this->nameList[$fieldName][$id] = $nameList[$id]; + } + } + } + + /**This function generates the List view entries in a list view + * Param $focus - module object + * Param $result - resultset of a listview query + * Param $navigation_array - navigation values in an array + * Param $relatedlist - check for related list flag + * Param $returnset - list query parameters in url string + * Param $edit_action - Edit action value + * Param $del_action - delete action value + * Param $oCv - vtiger_customview object + * Returns an array type + */ + function getListViewEntries($focus, $module,$result,$navigationInfo,$skipActions=false) { + + require('user_privileges/user_privileges_'.$this->user->id.'.php'); + global $listview_max_textlength, $theme,$default_charset; + $fields = $this->queryGenerator->getFields(); + $whereFields = $this->queryGenerator->getWhereFields(); + $meta = $this->queryGenerator->getMeta($this->queryGenerator->getModule()); + + $moduleFields = $meta->getModuleFields(); + $accessibleFieldList = array_keys($moduleFields); + $listViewFields = array_intersect($fields, $accessibleFieldList); + + $referenceFieldList = $this->queryGenerator->getReferenceFieldList(); + foreach ($referenceFieldList as $fieldName) { + if (in_array($fieldName, $listViewFields)) { + $field = $moduleFields[$fieldName]; + $this->fetchNameList($field, $result); + } + } + + $db = PearDatabase::getInstance(); + $rowCount = $db->num_rows($result); + $ownerFieldList = $this->queryGenerator->getOwnerFieldList(); + foreach ($ownerFieldList as $fieldName) { + if (in_array($fieldName, $listViewFields)) { + $field = $moduleFields[$fieldName]; + $idList = array(); + for ($i = 0; $i < $rowCount; $i++) { + $id = $this->db->query_result($result, $i, $field->getColumnName()); + if (!isset($this->ownerNameList[$fieldName][$id])) { + $idList[] = $id; + } + } + if(count($idList) > 0) { + if(!is_array($this->ownerNameList[$fieldName])) { + $this->ownerNameList[$fieldName] = getOwnerNameList($idList); + } else { + //array_merge API loses key information so need to merge the arrays + // manually. + $newOwnerList = getOwnerNameList($idList); + foreach ($newOwnerList as $id => $name) { + $this->ownerNameList[$fieldName][$id] = $name; + } + } + } + } + } + + foreach ($listViewFields as $fieldName) { + $field = $moduleFields[$fieldName]; + if(!$is_admin && ($field->getFieldDataType() == 'picklist' || + $field->getFieldDataType() == 'multipicklist')) { + $this->setupAccessiblePicklistValueList($fieldName); + } + } + + $useAsterisk = get_use_asterisk($this->user->id); + + $data = array(); + for ($i = 0; $i < $rowCount; ++$i) { + //Getting the recordId + if($module != 'Users') { + $baseTable = $meta->getEntityBaseTable(); + $moduleTableIndexList = $meta->getEntityTableIndexList(); + $baseTableIndex = $moduleTableIndexList[$baseTable]; + + $recordId = $db->query_result($result,$i,$baseTableIndex); + $ownerId = $db->query_result($result,$i,"smownerid"); + }else { + $recordId = $db->query_result($result,$i,"id"); + } + $row = array(); + + foreach ($listViewFields as $fieldName) { + $field = $moduleFields[$fieldName]; + $uitype = $field->getUIType(); + $rawValue = $this->db->query_result($result, $i, $field->getColumnName()); + if($module == 'Calendar') { + $activityType = $this->db->query_result($result, $i, 'activitytype'); + } + + if($uitype != 8){ + $value = html_entity_decode($rawValue,ENT_QUOTES,$default_charset); + } else { + $value = $rawValue; + } + + if($module == 'Documents' && $fieldName == 'filename') { + $downloadtype = $db->query_result($result,$i,'filelocationtype'); + if($downloadtype == 'I') { + $ext =substr($value, strrpos($value, ".") + 1); + $ext = strtolower($ext); + if($value != ''){ + if($ext == 'bin' || $ext == 'exe' || $ext == 'rpm') { + $fileicon = ""; + } elseif($ext == 'jpg' || $ext == 'gif' || $ext == 'bmp') { + $fileicon = ""; + } elseif($ext == 'txt' || $ext == 'doc' || $ext == 'xls') { + $fileicon = ""; + } elseif($ext == 'zip' || $ext == 'gz' || $ext == 'rar') { + $fileicon = ""; + } else { + $fileicon = ""; + } + } + } elseif($downloadtype == 'E') { + if(trim($value) != '' ) { + $fileicon = "".getTranslatedString("; + } else { + $value = '--'; + $fileicon = ''; + } + } else { + $value = ' --'; + $fileicon = ''; + } + + $fileName = $db->query_result($result,$i,'filename'); + + $downloadType = $db->query_result($result,$i,'filelocationtype'); + $status = $db->query_result($result,$i,'filestatus'); + $fileIdQuery = "select attachmentsid from vtiger_seattachmentsrel where crmid=?"; + $fileIdRes = $db->pquery($fileIdQuery,array($recordId)); + $fileId = $db->query_result($fileIdRes,0,'attachmentsid'); + if($fileName != '' && $status == 1) { + if($downloadType == 'I' ) { + $value = "".textlength_check($value). + ""; + } elseif($downloadType == 'E') { + $value = "".textlength_check($value). + ""; + } else { + $value = ' --'; + } + } + $value = $fileicon.$value; + } elseif($module == 'Documents' && $fieldName == 'filesize') { + $downloadType = $db->query_result($result,$i,'filelocationtype'); + if($downloadType == 'I') { + $filesize = $value; + if($filesize < 1024) + $value=$filesize.' B'; + elseif($filesize > 1024 && $filesize < 1048576) + $value=round($filesize/1024,2).' KB'; + else if($filesize > 1048576) + $value=round($filesize/(1024*1024),2).' MB'; + } else { + $value = ' --'; + } + } elseif( $module == 'Documents' && $fieldName == 'filestatus') { + if($value == 1) + $value=getTranslatedString('yes',$module); + elseif($value == 0) + $value=getTranslatedString('no',$module); + else + $value='--'; + } elseif( $module == 'Documents' && $fieldName == 'filetype') { + $downloadType = $db->query_result($result,$i,'filelocationtype'); + if($downloadType == 'E' || $downloadType != 'I') { + $value = '--'; + } + } elseif ($field->getUIType() == '27') { + if ($value == 'I') { + $value = getTranslatedString('LBL_INTERNAL',$module); + }elseif ($value == 'E') { + $value = getTranslatedString('LBL_EXTERNAL',$module); + }else { + $value = ' --'; + } + }elseif ($field->getFieldDataType() == 'picklist') { + if ($value != '' && !$is_admin && $this->picklistRoleMap[$fieldName] && + !in_array($value, $this->picklistValueMap[$fieldName])) { + $value = "".getTranslatedString('LBL_NOT_ACCESSIBLE', + $module).""; + } else { + $value = getTranslatedString($value,$module); + $value = textlength_check($value); + } + }elseif($field->getFieldDataType() == 'date' || + $field->getFieldDataType() == 'datetime') { + if($value != '' && $value != '0000-00-00') { + $date = new DateTimeField($value); + $value = $date->getDisplayDate(); + if($field->getFieldDataType() == 'datetime') { + $value .= (' ' . $date->getDisplayTime()); + } + } elseif ($value == '0000-00-00') { + $value = ''; + } + } elseif($field->getFieldDataType() == 'currency') { + if($value != '') { + if($field->getUIType() == 72) { + if($fieldName == 'unit_price') { + $currencyId = getProductBaseCurrency($recordId,$module); + $cursym_convrate = getCurrencySymbolandCRate($currencyId); + $currencySymbol = $cursym_convrate['symbol']; + } else { + $currencyInfo = getInventoryCurrencyInfo($module, $recordId); + $currencySymbol = $currencyInfo['currency_symbol']; + } + $value = number_format($value, 2,'.',''); + $currencyValue = CurrencyField::convertToUserFormat($value, null, true); + $value = CurrencyField::appendCurrencySymbol($currencyValue, $currencySymbol); + } else { + //changes made to remove vtiger_currency symbol infront of each + //vtiger_potential amount + if ($value != 0) { + $value = CurrencyField::convertToUserFormat($value); + } + } + } + } elseif($field->getFieldDataType() == 'url') { + $matchPattern = "^[\w]+:\/\/^"; + preg_match($matchPattern, $rawValue, $matches); + if(!empty ($matches[0])){ + $value = ''.textlength_check($value).''; + }else{ + $value = ''.textlength_check($value).''; + } + } elseif ($field->getFieldDataType() == 'email') { + if($_SESSION['internal_mailer'] == 1) { + //check added for email link in user detailview + $fieldId = $field->getFieldId(); + $value = "".textlength_check($value).""; + }else { + $value = ''.textlength_check($value).''; + } + } elseif($field->getFieldDataType() == 'boolean') { + if($value == 1) { + $value = getTranslatedString('yes',$module); + } elseif($value == 0) { + $value = getTranslatedString('no',$module); + } else { + $value = '--'; + } + } elseif($field->getUIType() == 98) { + $value = ''.textlength_check(getRoleName($value)).''; + } elseif($field->getFieldDataType() == 'multipicklist') { + $value = ($value != "") ? str_replace(' |##| ',', ',$value) : ""; + if(!$is_admin && $value != '') { + $valueArray = ($rawValue != "") ? explode(' |##| ',$rawValue) : array(); + $notaccess = ''.getTranslatedString('LBL_NOT_ACCESSIBLE', + $module).""; + $tmp = ''; + $tmpArray = array(); + foreach($valueArray as $index => $val) { + if(!$listview_max_textlength || + !(strlen(preg_replace("/(<\/?)(\w+)([^>]*>)/i","",$tmp)) > + $listview_max_textlength)) { + if (!$is_admin && $this->picklistRoleMap[$fieldName] && + !in_array(trim($val), $this->picklistValueMap[$fieldName])) { + $tmpArray[] = $notaccess; + $tmp .= ', '.$notaccess; + } else { + $tmpArray[] = $val; + $tmp .= ', '.$val; + } + } else { + $tmpArray[] = '...'; + $tmp .= '...'; + } + } + $value = implode(', ', $tmpArray); + $value = textlength_check($value); + } + } elseif ($field->getFieldDataType() == 'skype') { + $value = ($value != "") ? "".textlength_check($value)."" : ""; + } elseif ($field->getFieldDataType() == 'phone') { + if($useAsterisk == 'true') { + $value = "".textlength_check($value).""; + } else { + $value = textlength_check($value); + } + } elseif($field->getFieldDataType() == 'reference') { + $referenceFieldInfoList = $this->queryGenerator->getReferenceFieldInfoList(); + $moduleList = $referenceFieldInfoList[$fieldName]; + if(count($moduleList) == 1) { + $parentModule = $moduleList[0]; + } else { + $parentModule = $this->typeList[$value]; + } + if(!empty($value) && !empty($this->nameList[$fieldName]) && !empty($parentModule)) { + $parentMeta = $this->queryGenerator->getMeta($parentModule); + $value = textlength_check($this->nameList[$fieldName][$value]); + if ($parentMeta->isModuleEntity() && $parentModule != "Users") { + $value = "$value"; + } + } else { + $value = '--'; + } + } elseif($field->getFieldDataType() == 'owner') { + $value = textlength_check($this->ownerNameList[$fieldName][$value]); + } elseif ($field->getUIType() == 25) { + //TODO clean request object reference. + $contactId=$_REQUEST['record']; + $emailId=$this->db->query_result($result,$i,"activityid"); + $result1 = $this->db->pquery("SELECT access_count FROM vtiger_email_track WHERE ". + "crmid=? AND mailid=?", array($contactId,$emailId)); + $value=$this->db->query_result($result1,0,"access_count"); + if(!$value) { + $value = 0; + } + } elseif($field->getUIType() == 8){ + if(!empty($value)){ + $temp_val = html_entity_decode($value,ENT_QUOTES,$default_charset); + $json = new Zend_Json(); + $value = vt_suppressHTMLTags(implode(',',$json->decode($temp_val))); + } + } elseif ( in_array($uitype,array(7,9,90)) ) { + $value = "".textlength_check($value)."
"; + } else { + $value = textlength_check($value); + } + + $parenttab = getParentTab(); + $nameFields = $this->queryGenerator->getModuleNameFields($module); + $nameFieldList = explode(',',$nameFields); + if(in_array($fieldName, $nameFieldList) && $module != 'Emails' ) { + $value = "$value"; + } elseif($fieldName == $focus->list_link_field && $module != 'Emails') { + $value = "$value"; + } + + // vtlib customization: For listview javascript triggers + $value = "$value "; + // END + $row[] = $value; + } + + //Added for Actions ie., edit and delete links in listview + $actionLinkInfo = ""; + if(isPermitted($module,"EditView","") == 'yes'){ + $edit_link = $this->getListViewEditLink($module,$recordId); + if(isset($navigationInfo['start']) && $navigationInfo['start'] > 1 && $module != 'Emails') { + $actionLinkInfo .= "".getTranslatedString("LNK_EDIT", + $module)." "; + } else { + $actionLinkInfo .= "".getTranslatedString("LNK_EDIT", + $module)." "; + } + } + + if(isPermitted($module,"Delete","") == 'yes'){ + $del_link = $this->getListViewDeleteLink($module,$recordId); + if($actionLinkInfo != "" && $del_link != "") + $actionLinkInfo .= " | "; + if($del_link != "") + $actionLinkInfo .= "".getTranslatedString("LNK_DELETE", + $module).""; + } + // Record Change Notification + if(method_exists($focus, 'isViewed') && + PerformancePrefs::getBoolean('LISTVIEW_RECORD_CHANGE_INDICATOR', true)) { + if(!$focus->isViewed($recordId)) { + $actionLinkInfo .= " | "; + } + } + // END + if($actionLinkInfo != "" && !$skipActions) { + $row[] = $actionLinkInfo; + } + $data[$recordId] = $row; + + } + return $data; + } + + public function getListViewEditLink($module,$recordId, $activityType='') { + if($module == 'Emails') + return 'javascript:;" onclick="OpenCompose(\''.$recordId.'\',\'edit\');'; + if($module != 'Calendar') { + $return_action = "index"; + } else { + $return_action = 'ListView'; + } + //Added to fix 4600 + $url = getBasic_Advance_SearchURL(); + $parent = getParentTab(); + //Appending view name while editing from ListView + $link = "index.php?module=$module&action=EditView&record=$recordId&return_module=$module". + "&return_action=$return_action&parenttab=$parent".$url."&return_viewname=". + $_SESSION['lvs'][$module]["viewname"]; + + if($module == 'Calendar') { + if($activityType == 'Task') { + $link .= '&activity_mode=Task'; + } else { + $link .= '&activity_mode=Events'; + } + } + return $link; + } + + public function getListViewDeleteLink($module,$recordId) { + $parenttab = getParentTab(); + $viewname = $_SESSION['lvs'][$module]['viewname']; + //Added to fix 4600 + $url = getBasic_Advance_SearchURL(); + if($module == "Calendar") + $return_action = "ListView"; + else + $return_action = "index"; + //This is added to avoid the del link in Product related list for the following modules + $link = "index.php?module=$module&action=Delete&record=$recordId". + "&return_module=$module&return_action=$return_action". + "&parenttab=$parenttab&return_viewname=".$viewname.$url; + + // vtlib customization: override default delete link for custom modules + $requestModule = vtlib_purify($_REQUEST['module']); + $requestRecord = vtlib_purify($_REQUEST['record']); + $requestAction = vtlib_purify($_REQUEST['action']); + $requestFile = vtlib_purify($_REQUEST['file']); + $isCustomModule = vtlib_isCustomModule($requestModule); + + if($isCustomModule && (!in_array($requestAction, Array('index','ListView')) && + ($requestAction == $requestModule.'Ajax' && !in_array($requestFile, Array('index','ListView'))))) { + + $link = "index.php?module=$requestModule&action=updateRelations&parentid=$requestRecord"; + $link .= "&destination_module=$module&idlist=$entity_id&mode=delete&parenttab=$parenttab"; + } + // END + return $link; + } + + public function getListViewHeader($focus, $module,$sort_qry='',$sorder='',$orderBy='', + $skipActions=false) { + global $log, $singlepane_view; + global $theme; + + $arrow=''; + $qry = getURLstring($focus); + $theme_path="themes/".$theme."/"; + $image_path=$theme_path."images/"; + $header = Array(); + + //Get the vtiger_tabid of the module + $tabid = getTabid($module); + $tabname = getParentTab(); + global $current_user; + + require('user_privileges/user_privileges_'.$current_user->id.'.php'); + $fields = $this->queryGenerator->getFields(); + $whereFields = $this->queryGenerator->getWhereFields(); + $meta = $this->queryGenerator->getMeta($this->queryGenerator->getModule()); + + $moduleFields = $meta->getModuleFields(); + $accessibleFieldList = array_keys($moduleFields); + $listViewFields = array_intersect($fields, $accessibleFieldList); + //Added on 14-12-2005 to avoid if and else check for every list + //vtiger_field for arrow image and change order + $change_sorder = array('ASC'=>'DESC','DESC'=>'ASC'); + $arrow_gif = array('ASC'=>'arrow_down.gif','DESC'=>'arrow_up.gif'); + foreach($listViewFields as $fieldName) { + $field = $moduleFields[$fieldName]; + + if(in_array($field->getColumnName(),$focus->sortby_fields)) { + if($orderBy == $field->getColumnName()) { + $temp_sorder = $change_sorder[$sorder]; + $arrow = " "; + } else { + $temp_sorder = 'ASC'; + } + $label = getTranslatedString($field->getFieldLabelKey(), $module); + //added to display vtiger_currency symbol in listview header + if($label =='Amount') { + $label .=' ('.getTranslatedString('LBL_IN', $module).' '. + $user_info['currency_symbol'].')'; + } + if($field->getUIType() == '9') { + $label .=' (%)'; + } + if($module == 'Users' && $fieldName == 'User Name') { + $name = "". + getTranslatedString('LBL_LIST_USER_NAME_ROLE',$module)."".$arrow.""; + } else { + if($this->isHeaderSortingEnabled()) { + $name = "".$label."".$arrow.""; + + } else { + $name = $label; + } + } + $arrow = ''; + } else { + $name = getTranslatedString($field->getFieldLabelKey(), $module); + } + //added to display vtiger_currency symbol in related listview header + if($name =='Amount') { + $name .=' ('.getTranslatedString('LBL_IN').' '.$user_info['currency_symbol'].')'; + } + + $header[]=$name; + } + + //Added for Action - edit and delete link header in listview + if(!$skipActions && (isPermitted($module,"EditView","") == 'yes' || + isPermitted($module,"Delete","") == 'yes')) + $header[] = getTranslatedString("LBL_ACTION", $module); + return $header; + } + + public function getBasicSearchFieldInfoList() { + $fields = $this->queryGenerator->getFields(); + $whereFields = $this->queryGenerator->getWhereFields(); + $meta = $this->queryGenerator->getMeta($this->queryGenerator->getModule()); + + $moduleFields = $meta->getModuleFields(); + $accessibleFieldList = array_keys($moduleFields); + $listViewFields = array_intersect($fields, $accessibleFieldList); + $basicSearchFieldInfoList = array(); + foreach ($listViewFields as $fieldName) { + $field = $moduleFields[$fieldName]; + $basicSearchFieldInfoList[$fieldName] = getTranslatedString($field->getFieldLabelKey(), + $this->queryGenerator->getModule()); + } + return $basicSearchFieldInfoList; + } + + public function getAdvancedSearchOptionString() { + $module = $this->queryGenerator->getModule(); + $meta = $this->queryGenerator->getMeta($module); + + $moduleFields = $meta->getModuleFields(); + $i =0; + foreach ($moduleFields as $fieldName=>$field) { + if($field->getFieldDataType() == 'reference') { + $typeOfData = 'V'; + } else if($field->getFieldDataType() == 'boolean') { + $typeOfData = 'C'; + } else { + $typeOfData = $field->getTypeOfData(); + $typeOfData = explode("~",$typeOfData); + $typeOfData = $typeOfData[0]; + } + $label = getTranslatedString($field->getFieldLabelKey(), $module); + if(empty($label)) { + $label = $field->getFieldLabelKey(); + } + if($label == "Start Date & Time") { + $fieldlabel = "Start Date"; + } + $selected = ''; + if($i++ == 0) { + $selected = "selected"; + } + + // place option in array for sorting later + //$blockName = getTranslatedString(getBlockName($field->getBlockId()), $module); + $blockName = getTranslatedString($field->getBlockName(), $module); + + $fieldLabelEscaped = str_replace(" ","_",$field->getFieldLabelKey()); + $optionvalue = $field->getTableName().":".$field->getColumnName().":".$fieldName.":".$module."_".$fieldLabelEscaped.":".$typeOfData; + + $OPTION_SET[$blockName][$label] = ""; + + } + // sort array on block label + ksort($OPTION_SET, SORT_STRING); + + foreach ($OPTION_SET as $key=>$value) { + $shtml .= ""; + // sort array on field labels + ksort($value, SORT_STRING); + $shtml .= implode('',$value); + } + + return $shtml; + } + +} +?> \ No newline at end of file diff --git a/include/ListView/ListViewPagging.php b/include/ListView/ListViewPagging.php new file mode 100644 index 0000000..267083a --- /dev/null +++ b/include/ListView/ListViewPagging.php @@ -0,0 +1,86 @@ +getViewId($currentModule); + $customview_html = $customView->getCustomViewCombo($viewid); + $viewinfo = $customView->getCustomViewByCvid($viewid); + + if($viewid != "0"&& $viewid != 0){ + $listquery = getListQuery($currentModule); + $list_query= $customView->getModifiedCvListQuery($viewid, $listquery, $currentModule); + }else{ + $list_query = getListQuery($currentModule); + } + // Enabling Module Search + $url_string = ''; + if($_REQUEST['query'] == 'true') { + if(!empty($_REQUEST['tagSearchText'])){ + $searchValue = vtlib_purify($_REQUEST['globalSearchText']); + $where = '(' . getTagWhere($searchValue, $current_user->id) . ')'; + } else if(!empty($_REQUEST['globalSearch'])){ + $searchValue = vtlib_purify($_REQUEST['globalSearchText']); + $where = '(' . getUnifiedWhere($list_query,$currentModule,$searchValue) . ')'; + $url_string .= '&query=true&globalSearch=true&globalSearchText='.$searchValue; + }else{ + list($where, $ustring) = split('#@@#', getWhereCondition($currentModule)); + $url_string .= "&query=true$ustring"; + } + } + //print_r($where); + if($where != '') { + $list_query = "$list_query AND $where"; + $_SESSION['export_where'] = $where; + }else{ + unset($_SESSION['export_where']); + } + // Sorting + if($order_by) { + if($order_by == 'smownerid'){ + if( $adb->dbType == "pgsql"){ + $list_query .= ' GROUP BY user_name'; + } + $list_query .= ' ORDER BY user_name '.$sorder; + }else { + $tablename = getTableNameForField($currentModule, $order_by); + $tablename = ($tablename != '')? ($tablename . '.') : ''; + if( $adb->dbType == "pgsql"){ + $list_query .= ' GROUP BY '. $tablename . $order_by; + } + $list_query .= ' ORDER BY ' . $tablename . $order_by . ' ' . $sorder; + } + } +}else{ + //TODO: remove after calendar module listview cleanup. + //its failing for calendar module. + $dummyQuery = getListQuery($currentModule); + $list_query = $_SESSION[$currentModule.'_listquery']; +} + +$count_result = $adb->query(mkCountQuery($list_query)); +$noofrows = $adb->query_result($count_result,0,"count"); + +$pageNumber = ceil($noofrows/$list_max_entries_per_page); +if($pageNumber == 0){ + $pageNumber = 1; +} +echo $app_strings['LBL_LIST_OF'].' '.$pageNumber; +?> \ No newline at end of file diff --git a/include/ListView/ListViewSession.php b/include/ListView/ListViewSession.php new file mode 100644 index 0000000..b31667c --- /dev/null +++ b/include/ListView/ListViewSession.php @@ -0,0 +1,218 @@ +debug("Entering ListViewSession() method ..."); + + $this->module = $currentModule; + $this->sortby = 'ASC'; + $this->start =1; + } + + function getCurrentPage($currentModule,$viewId){ + if(!empty($_SESSION['lvs'][$currentModule][$viewId]['start'])){ + return $_SESSION['lvs'][$currentModule][$viewId]['start']; + } + return 1; + } + + function getRequestStartPage(){ + $start = $_REQUEST['start']; + if(!is_numeric($start)){ + $start = 1; + } + if($start < 1){ + $start = 1; + } + $start = ceil($start); + return $start; + } + + function getListViewNavigation($currentRecordId){ + global $currentModule,$current_user,$adb,$log,$list_max_entries_per_page; + Zend_Json::$useBuiltinEncoderDecoder = true; + $reUseData = false; + $displayBufferRecordCount = 10; + $bufferRecordCount = 15; + if($currentModule == 'Documents'){ + $sql = "select folderid from vtiger_notes where notesid=?"; + $params = array($currentRecordId); + $result = $adb->pquery($sql,$params); + $folderId = $adb->query_result($result,0,'folderid'); + } + $cv = new CustomView(); + $viewId = $cv->getViewId($currentModule); + if(!empty($_SESSION[$currentModule.'_DetailView_Navigation'.$viewId])){ + $recordNavigationInfo = Zend_Json::decode($_SESSION[$currentModule.'_DetailView_Navigation'.$viewId]); + $pageNumber =0; + if(count($recordNavigationInfo) == 1){ + foreach ($recordNavigationInfo as $recordIdList) { + if(in_array($currentRecordId,$recordIdList)){ + $reUseData = true; + } + } + }else{ + $recordList = array(); + $recordPageMapping = array(); + foreach ($recordNavigationInfo as $start=>$recordIdList){ + foreach ($recordIdList as $index=>$recordId) { + $recordList[] = $recordId; + $recordPageMapping[$recordId] = $start; + if($recordId == $currentRecordId){ + $searchKey = count($recordList)-1; + } + } + } + if($searchKey > $displayBufferRecordCount -1 && $searchKey < count($recordList)-$displayBufferRecordCount){ + $reUseData= true; + } + } + } + + if($reUseData === false){ + $recordNavigationInfo = array(); + if(!empty($_REQUEST['start'])){ + $start = ListViewSession::getRequestStartPage(); + }else{ + $start = ListViewSession::getCurrentPage($currentModule,$viewId); + } + $startRecord = (($start - 1) * $list_max_entries_per_page) - $bufferRecordCount; + if($startRecord < 0){ + $startRecord = 0; + } + + $list_query = $_SESSION[$currentModule.'_listquery']; + $instance = CRMEntity::getInstance($currentModule); + $instance->getNonAdminAccessControlQuery($currentModule, $current_user); + vtlib_setup_modulevars($currentModule, $instance); + if($currentModule=='Documents' && !empty($folderId)){ + $list_query = preg_replace("/[\n\r\s]+/"," ",$list_query); + $list_query .= " AND vtiger_notes.folderid=$folderId"; + $order_by = $instance->getOrderByForFolder($folderId); + $sorder = $instance->getSortOrderForFolder($folderId); + $tablename = getTableNameForField($currentModule,$order_by); + $tablename = (($tablename != '')?($tablename."."):''); + if(!empty($order_by)){ + $list_query .= ' ORDER BY '.$tablename.$order_by.' '.$sorder; + } + } + if($start !=1){ + $recordCount = ($list_max_entries_per_page+2 * $bufferRecordCount); + }else{ + $recordCount = ($list_max_entries_per_page+ $bufferRecordCount); + } + if( $adb->dbType == "pgsql"){ + $list_query .= " OFFSET $startRecord LIMIT $recordCount"; + }else{ + $list_query .= " LIMIT $startRecord, $recordCount"; + } + + $resultAllCRMIDlist_query=$adb->pquery($list_query,array()); + $navigationRecordList = array(); + while($forAllCRMID = $adb->fetch_array($resultAllCRMIDlist_query)) { + $navigationRecordList[] = $forAllCRMID[$instance->table_index]; + } + + $pageCount = 0; + $current = $start; + if($start ==1){ + $firstPageRecordCount = $list_max_entries_per_page; + }else{ + $firstPageRecordCount = $bufferRecordCount; + $current -=1; + } + + $searchKey = array_search($currentRecordId,$navigationRecordList); + $recordNavigationInfo = array(); + if($searchKey !== false){ + foreach ($navigationRecordList as $index => $recordId) { + if(!is_array($recordNavigationInfo[$current])){ + $recordNavigationInfo[$current] = array(); + } + if($index == $firstPageRecordCount || $index == ($firstPageRecordCount+$pageCount * $list_max_entries_per_page)){ + $current++; + $pageCount++; + } + $recordNavigationInfo[$current][] = $recordId; + } + } + $_SESSION[$currentModule.'_DetailView_Navigation'.$viewId] = + Zend_Json::encode($recordNavigationInfo); + } + return $recordNavigationInfo; + } + + function getRequestCurrentPage($currentModule, $query, $viewid, $queryMode = false) { + global $list_max_entries_per_page, $adb; + $start = 1; + if(isset($_REQUEST['query']) && $_REQUEST['query'] == 'true'&& $_REQUEST['start']!="last"){ + return ListViewSession::getRequestStartPage(); + } + if(!empty($_REQUEST['start'])){ + $start = $_REQUEST['start']; + if($start == 'last'){ + $count_result = $adb->query( mkCountQuery( $query)); + $noofrows = $adb->query_result($count_result,0,"count"); + if($noofrows > 0){ + $start = ceil($noofrows/$list_max_entries_per_page); + } + } + if(!is_numeric($start)){ + $start = 1; + }elseif($start < 1){ + $start = 1; + } + $start = ceil($start); + }else if(!empty($_SESSION['lvs'][$currentModule][$viewid]['start'])){ + $start = $_SESSION['lvs'][$currentModule][$viewid]['start']; + } + if(!$queryMode) { + $_SESSION['lvs'][$currentModule][$viewid]['start'] = intval($start); + } + return $start; + } + + function setSessionQuery($currentModule,$query,$viewid){ + if(isset($_SESSION[$currentModule.'_listquery'])){ + if($_SESSION[$currentModule.'_listquery'] != $query){ + unset($_SESSION[$currentModule.'_DetailView_Navigation'.$viewid]); + } + } + $_SESSION[$currentModule.'_listquery'] = $query; + } + + function hasViewChanged($currentModule) { + if(empty($_SESSION['lvs'][$currentModule]['viewname'])) return true; + if(empty($_REQUEST['viewname'])) return false; + if($_REQUEST['viewname'] != $_SESSION['lvs'][$currentModule]['viewname']) return true; + return false; + } +} +?> \ No newline at end of file diff --git a/include/ListView/RelatedListViewContents.php b/include/ListView/RelatedListViewContents.php new file mode 100644 index 0000000..e8d3a8d --- /dev/null +++ b/include/ListView/RelatedListViewContents.php @@ -0,0 +1,93 @@ + 0) { + $recordid = vtlib_purify($_REQUEST['record']); + if($_SESSION['rlvs'][$currentModule][$relationId]['currentRecord'] != $recordid) { + $resetCookie = true; + } else { + $resetCookie = false; + } + $_SESSION['rlvs'][$currentModule][$relationId]['currentRecord'] = $recordid; + $actions = vtlib_purify($_REQUEST['actions']); + $header = vtlib_purify($_REQUEST['header']); + $modObj->id = $recordid; + $relationInfo = getRelatedListInfoById($relationId); + $relatedModule = getTabModuleName($relationInfo['relatedTabId']); + $function_name = $relationInfo['functionName']; + + $relatedListData = $modObj->$function_name($recordid, getTabid($currentModule), + $relationInfo['relatedTabId'], $actions); + require_once('Smarty_setup.php'); + global $theme, $mod_strings, $app_strings; + $theme_path="themes/".$theme."/"; + $image_path=$theme_path."images/"; + + $smarty = new vtigerCRM_Smarty; + // vtlib customization: Related module could be disabled, check it + if(is_array($relatedListData)) { + if( ($relatedModule == "Contacts" || $relatedModule == "Leads" || + $relatedModule == "Accounts") && $currentModule == 'Campaigns' && + !$resetCookie) { + //TODO for 5.3 this should be COOKIE not REQUEST, change here else where + // this logic is used for listview checkbox selection propogation. + $checkedRecordIdString = $_REQUEST[$relatedModule.'_all']; + $checkedRecordIdString = rtrim($checkedRecordIdString); + $checkedRecordIdList = explode(';', $checkedRecordIdString); + $relatedListData["checked"]=array(); + if (isset($relatedListData['entries'])) { + foreach($relatedListData['entries'] as $key=>$val) { + if(in_array($key,$checkedRecordIdList)) { + $relatedListData["checked"][$key] = 'checked'; + } else { + $relatedListData["checked"][$key] = ''; + } + } + } + $smarty->assign("SELECTED_RECORD_LIST", $checkedRecordIdString); + } else { + $smarty->assign('RESET_COOKIE', $resetCookie); + } + } + // END + require_once('include/ListView/RelatedListViewSession.php'); + RelatedListViewSession::addRelatedModuleToSession($relationId,$header); + + $smarty->assign("MOD", $mod_strings); + $smarty->assign("APP", $app_strings); + $smarty->assign("THEME", $theme); + $smarty->assign("IMAGE_PATH", $image_path); + $smarty->assign("ID",$recordid); + $smarty->assign("MODULE",$currentModule); + $smarty->assign("RELATED_MODULE",$relatedModule); + $smarty->assign("HEADER",$header); + $smarty->assign("RELATEDLISTDATA", $relatedListData); + + $smarty->display("RelatedListDataContents.tpl"); + } +}else if($ajaxaction == "DISABLEMODULE"){ + $relationId = vtlib_purify($_REQUEST['relation_id']); + if(!empty($relationId) && ((int)$relationId) > 0) { + $header = vtlib_purify($_REQUEST['header']); + require_once('include/ListView/RelatedListViewSession.php'); + RelatedListViewSession::removeRelatedModuleFromSession($relationId,$header); + } + echo "SUCCESS"; +} + +?> diff --git a/include/ListView/RelatedListViewSession.php b/include/ListView/RelatedListViewSession.php new file mode 100644 index 0000000..1c64299 --- /dev/null +++ b/include/ListView/RelatedListViewSession.php @@ -0,0 +1,116 @@ +debug("Entering RelatedListViewSession() method ..."); + + $this->module = $currentModule; + $this->start =1; + } + + public static function addRelatedModuleToSession($relationId, $header) { + global $currentModule; + $_SESSION['relatedlist'][$currentModule][$relationId] = $header; + $start = RelatedListViewSession::getRequestStartPage(); + RelatedListViewSession::saveRelatedModuleStartPage($relationId, $start); + } + + public static function removeRelatedModuleFromSession($relationId, $header) { + global $currentModule; + + unset($_SESSION['relatedlist'][$currentModule][$relationId]); + } + + public static function getRelatedModulesFromSession() { + global $currentModule; + + $allRelatedModuleList = isPresentRelatedLists($currentModule); + $moduleList = array(); + if(is_array($_SESSION['relatedlist'][$currentModule])){ + foreach ($allRelatedModuleList as $relationId=>$label) { + if(array_key_exists($relationId, $_SESSION['relatedlist'][$currentModule])){ + $moduleList[] = $_SESSION['relatedlist'][$currentModule][$relationId]; + } + } + } + return $moduleList; + } + + public static function saveRelatedModuleStartPage($relationId, $start) { + global $currentModule; + + $_SESSION['rlvs'][$currentModule][$relationId]['start'] = $start; + } + + public static function getCurrentPage($relationId) { + global $currentModule; + + if(!empty($_SESSION['rlvs'][$currentModule][$relationId]['start'])){ + return $_SESSION['rlvs'][$currentModule][$relationId]['start']; + } + return 1; + } + + public static function getRequestStartPage(){ + $start = $_REQUEST['start']; + if(!is_numeric($start)){ + $start = 1; + } + if($start < 1){ + $start = 1; + } + $start = ceil($start); + return $start; + } + + public static function getRequestCurrentPage($relationId, $query) { + global $list_max_entries_per_page, $adb; + + $start = 1; + if(!empty($_REQUEST['start'])){ + $start = $_REQUEST['start']; + if($start == 'last'){ + $count_result = $adb->query( mkCountQuery( $query)); + $noofrows = $adb->query_result($count_result,0,"count"); + if($noofrows > 0){ + $start = ceil($noofrows/$list_max_entries_per_page); + } + } + if(!is_numeric($start)){ + $start = 1; + }elseif($start < 1){ + $start = 1; + } + $start = ceil($start); + }else { + $start = RelatedListViewSession::getCurrentPage($relationId); + } + return $start; + } + +} +?> \ No newline at end of file diff --git a/include/Menu.php b/include/Menu.php new file mode 100644 index 0000000..4595ed1 --- /dev/null +++ b/include/Menu.php @@ -0,0 +1,95 @@ + $app_strings['LNK_NEW_CONTACT'], + 'Leads'=> $app_strings['LNK_NEW_LEAD'], + 'Accounts' => $app_strings['LNK_NEW_ACCOUNT'], + 'Potentials' => $app_strings['LNK_NEW_OPPORTUNITY'], + 'HelpDesk' => $app_strings['LNK_NEW_HDESK'], + 'Faq' => $app_strings['LNK_NEW_FAQ'], + 'Products' => $app_strings['LNK_NEW_PRODUCT'], + 'Documents' => $app_strings['LNK_NEW_NOTE'], + 'Emails' => $app_strings['LNK_NEW_EMAIL'], + 'Events' => $app_strings['LNK_NEW_EVENT'], + 'Tasks' => $app_strings['LNK_NEW_TASK'], + 'Vendor' => $app_strings['LNK_NEW_VENDOR'], + 'PriceBook' => $app_strings['LNK_NEW_PRICEBOOK'], + 'Quotes' => $app_strings['LNK_NEW_QUOTE'], + 'PurchaseOrder' => $app_strings['LNK_NEW_PO'], + 'SalesOrder' => $app_strings['LNK_NEW_SO'], + 'Invoice' => $app_strings['LNK_NEW_INVOICE'] + ); +$module_menu = Array(); +$i= 0; +$add_url = ""; +foreach($module_menu_array as $module1 => $label) +{ + $add_url=''; + $curr_action = 'EditView'; + $ret_action = 'DetailView'; + if($module1 == 'Events') + { + $module_display = 'Activities'; + $add_url = "&activity_mode=Events"; + $tabid = getTabid($module1); + } + elseif($module1 == 'Tasks') + { + $module_display = 'Activities'; + $add_url = "&activity_mode=Task"; + $tabid = getTabid("Activities"); + } + else + { + $module_display = $module1; + $tabid = getTabid($module1); + } + + if(in_array($module_display, $moduleList)) + { + + if(isPermitted($module_display,'EditView') == 'yes') + { + $tempArray = Array("index.php?module=".$module_display."&action=".$curr_action."&return_module=".$module_display."&return_action=".$ret_action.$add_url, $label); + $module_menu[$i] = $tempArray; + $i++; + } + } + elseif($module_display == 'Faq') + { + $tempArray = Array("index.php?module=".$module_display."&action=".$curr_action."&return_module=".$module_display."&return_action=".$ret_action.$add_url, $label); + $module_menu[$i] = $tempArray; + $i++; + } + +} + + +?> diff --git a/include/PopulateComboValues.php b/include/PopulateComboValues.php new file mode 100644 index 0000000..bef34db --- /dev/null +++ b/include/PopulateComboValues.php @@ -0,0 +1,161 @@ +debug("Entering insertComboValues(".$values.", ".$tableName.") method ..."); + global $adb; + //inserting the value in the vtiger_picklistvalues_seq for the getting uniqueID for each picklist values... + $i=0; + foreach ($values as $val => $cal) + { + $picklist_valueid = getUniquePicklistID(); + $id = $adb->getUniqueID('vtiger_'.$tableName); + if($val != '') + { + $params = array($id, $val, 1, $picklist_valueid); + $adb->pquery("insert into vtiger_$tableName values(?,?,?,?)", $params); + } + else + { + $params = array($id, '--None--', 1, $picklist_valueid); + $adb->pquery("insert into vtiger_$tableName values(?,?,?,?)", $params); + } + + //Default entries for role2picklist relation has been inserted.. + + $sql="select roleid from vtiger_role"; + $role_result = $adb->pquery($sql, array()); + $numrow = $adb->num_rows($role_result); + for($k=0; $k < $numrow; $k ++) + { + $roleid = $adb->query_result($role_result,$k,'roleid'); + $params = array($roleid, $picklist_valueid, $picklistid, $i); + $adb->pquery("insert into vtiger_role2picklist values(?,?,?,?)", $params); + } + + $i++; + } + + + $log->debug("Exiting insertComboValues method ..."); + } + + + /** + * To populate the combo vtiger_tables at startup time + */ + + function create_tables () + { + global $log; + $log->debug("Entering create_tables () method ..."); + + global $app_list_strings,$adb; + global $combo_strings; + $comboRes = $adb->query("SELECT distinct fieldname FROM vtiger_field WHERE uitype IN ('15') OR fieldname = 'salutationtype' and vtiger_field.presence in (0,2)"); + $noOfCombos = $adb->num_rows($comboRes); + for($i=0; $i<$noOfCombos; $i++) + { + $comTab = $adb->query_result($comboRes, $i, 'fieldname'); + $picklistid = $adb->getUniqueID("vtiger_picklist"); + $params = array($picklistid, $comTab); + $picklist_qry = "insert into vtiger_picklist values(?,?)"; + $adb->pquery($picklist_qry, $params); + + $this->insertComboValues($combo_strings[$comTab."_dom"],$comTab,$picklistid); + } + + //we have to decide what are all the picklist and picklist values are non editable + //presence = 0 means you cannot edit the picklist value + //presence = 1 means you can edit the picklist value + $noneditable_tables = Array("ticketstatus","taskstatus","eventstatus","faqstatus","quotestage","postatus","sostatus","invoicestatus","activitytype"); + $noneditable_values = Array( + "Closed Won"=>"sales_stage", + "Closed Lost"=>"sales_stage", + ); + foreach($noneditable_tables as $picklistname) + { + $adb->pquery("update vtiger_".$picklistname." set PRESENCE=0", array()); + } + foreach($noneditable_values as $picklistname => $value) + { + $adb->pquery("update vtiger_$value set PRESENCE=0 where $value=?", array($picklistname)); + } + + $log->debug("Exiting create_tables () method ..."); + + } + + + function create_nonpicklist_tables () + { + global $log; + $log->debug("Entering create_nonpicklist_tables () method ..."); + + global $app_list_strings,$adb; + global $combo_strings; + // uitype -> 16 - Non standard picklist, 115 - User status, 83 - Tax Class + $comboRes = $adb->query("SELECT distinct fieldname FROM vtiger_field WHERE uitype IN ('16','115','83') AND fieldname NOT IN ('hdnTaxType','email_flag') and vtiger_field.presence in (0,2)"); + $noOfCombos = $adb->num_rows($comboRes); + for($i=0; $i<$noOfCombos; $i++) + { + $comTab = $adb->query_result($comboRes, $i, 'fieldname'); + $this->insertNonPicklistValues($combo_strings[$comTab."_dom"],$comTab); + } + $log->debug("Exiting create_tables () method ..."); + } + function insertNonPicklistValues($values, $tableName) + { + global $log; + $log->debug("Entering insertNonPicklistValues(".$values.", ".$tableName.") method ..."); + global $adb; + $i=0; + foreach ($values as $val => $cal) + { + $id = $adb->getUniqueID('vtiger_'.$tableName); + if($val != '') + { + $params = array($id, $val, $i ,1); + } + else + { + $params = array($id, '--None--', $i ,1); + } + $adb->pquery("insert into vtiger_$tableName values(?,?,?,?)", $params); + $i++; + } + $log->debug("Exiting insertNonPicklistValues method ..."); + } + +} +?> diff --git a/include/QueryGenerator/QueryGenerator.php b/include/QueryGenerator/QueryGenerator.php new file mode 100644 index 0000000..f8ae71b --- /dev/null +++ b/include/QueryGenerator/QueryGenerator.php @@ -0,0 +1,1048 @@ +module = $module; + $this->customViewColumnList = null; + $this->stdFilterList = null; + $this->conditionals = array(); + $this->user = $user; + $this->advFilterList = null; + $this->fields = array(); + $this->referenceModuleMetaInfo = array(); + $this->moduleNameFields = array(); + $this->whereFields = array(); + $this->groupType = self::$AND; + $this->meta = $this->getMeta($module); + $this->moduleNameFields[$module] = $this->meta->getNameFields(); + $this->referenceFieldInfoList = $this->meta->getReferenceFieldDetails(); + $this->referenceFieldList = array_keys($this->referenceFieldInfoList);; + $this->ownerFields = $this->meta->getOwnerFields(); + $this->columns = null; + $this->fromClause = null; + $this->whereClause = null; + $this->query = null; + $this->conditionalWhere = null; + $this->groupInfo = ''; + $this->manyToManyRelatedModuleConditions = array(); + $this->conditionInstanceCount = 0; + $this->customViewFields = array(); + } + + /** + * + * @param String:ModuleName $module + * @return EntityMeta + */ + public function getMeta($module) { + $db = PearDatabase::getInstance(); + if (empty($this->referenceModuleMetaInfo[$module])) { + $handler = vtws_getModuleHandlerFromName($module, $this->user); + $meta = $handler->getMeta(); + $this->referenceModuleMetaInfo[$module] = $meta; + $this->moduleNameFields[$module] = $meta->getNameFields(); + } + return $this->referenceModuleMetaInfo[$module]; + } + + public function reset() { + $this->fromClause = null; + $this->whereClause = null; + $this->columns = null; + $this->query = null; + } + + public function setFields($fields) { + $this->fields = $fields; + } + + public function getCustomViewFields() { + return $this->customViewFields; + } + + public function getFields() { + return $this->fields; + } + + public function getWhereFields() { + return $this->whereFields; + } + + public function getOwnerFieldList() { + return $this->ownerFields; + } + + public function getModuleNameFields($module) { + return $this->moduleNameFields[$module]; + } + + public function getReferenceFieldList() { + return $this->referenceFieldList; + } + + public function getReferenceFieldInfoList() { + return $this->referenceFieldInfoList; + } + + public function getModule () { + return $this->module; + } + + public function getConditionalWhere() { + return $this->conditionalWhere; + } + + public function getDefaultCustomViewQuery() { + $customView = new CustomView($this->module); + $viewId = $customView->getViewId($this->module); + return $this->getCustomViewQueryById($viewId); + } + + public function initForDefaultCustomView() { + $customView = new CustomView($this->module); + $viewId = $customView->getViewId($this->module); + $this->initForCustomViewById($viewId); + } + + public function initForCustomViewById($viewId) { + $customView = new CustomView($this->module); + $this->customViewColumnList = $customView->getColumnsListByCvid($viewId); + foreach ($this->customViewColumnList as $customViewColumnInfo) { + $details = explode(':', $customViewColumnInfo); + if(empty($details[2]) && $details[1] == 'crmid' && $details[0] == 'vtiger_crmentity') { + $name = 'id'; + $this->customViewFields[] = $name; + } else { + $this->fields[] = $details[2]; + $this->customViewFields[] = $details[2]; + } + } + + if($this->module == 'Calendar' && !in_array('activitytype', $this->fields)) { + $this->fields[] = 'activitytype'; + } + + if($this->module == 'Documents') { + if(in_array('filename', $this->fields)) { + if(!in_array('filelocationtype', $this->fields)) { + $this->fields[] = 'filelocationtype'; + } + if(!in_array('filestatus', $this->fields)) { + $this->fields[] = 'filestatus'; + } + } + } + $this->fields[] = 'id'; + + $this->stdFilterList = $customView->getStdFilterByCvid($viewId); + $this->advFilterList = $customView->getAdvFilterByCvid($viewId); + + if(is_array($this->stdFilterList)) { + $value = array(); + if(!empty($this->stdFilterList['columnname'])) { + $this->startGroup(''); + $name = explode(':',$this->stdFilterList['columnname']); + $name = $name[2]; + $value[] = $this->fixDateTimeValue($name, $this->stdFilterList['startdate']); + $value[] = $this->fixDateTimeValue($name, $this->stdFilterList['enddate'], false); + $this->addCondition($name, $value, 'BETWEEN'); + } + } + if($this->conditionInstanceCount <= 0 && is_array($this->advFilterList) && count($this->advFilterList) > 0) { + $this->startGroup(''); + } elseif($this->conditionInstanceCount > 0 && is_array($this->advFilterList) && count($this->advFilterList) > 0) { + $this->addConditionGlue(self::$AND); + } + if(is_array($this->advFilterList) && count($this->advFilterList) > 0) { + foreach ($this->advFilterList as $groupindex=>$groupcolumns) { + $filtercolumns = $groupcolumns['columns']; + if(count($filtercolumns) > 0) { + $this->startGroup(''); + foreach ($filtercolumns as $index=>$filter) { + $name = explode(':',$filter['columnname']); + if(empty($name[2]) && $name[1] == 'crmid' && $name[0] == 'vtiger_crmentity') { + $name = $this->getSQLColumn('id'); + } else { + $name = $name[2]; + } + $this->addCondition($name, $filter['value'], $filter['comparator']); + $columncondition = $filter['column_condition']; + if(!empty($columncondition)) { + $this->addConditionGlue($columncondition); + } + } + $this->endGroup(); + $groupConditionGlue = $groupcolumns['condition']; + if(!empty($groupConditionGlue)) + $this->addConditionGlue($groupConditionGlue); + } + } + } + if($this->conditionInstanceCount > 0) { + $this->endGroup(); + } + } + + public function getCustomViewQueryById($viewId) { + $this->initForCustomViewById($viewId); + return $this->getQuery(); + } + + public function getQuery() { + if(empty($this->query)) { + $conditionedReferenceFields = array(); + $allFields = array_merge($this->whereFields,$this->fields); + foreach ($allFields as $fieldName) { + if(in_array($fieldName,$this->referenceFieldList)) { + $moduleList = $this->referenceFieldInfoList[$fieldName]; + foreach ($moduleList as $module) { + if(empty($this->moduleNameFields[$module])) { + $meta = $this->getMeta($module); + } + } + } elseif(in_array($fieldName, $this->ownerFields )) { + $meta = $this->getMeta('Users'); + $meta = $this->getMeta('Groups'); + } + } + + $query = "SELECT "; + $query .= $this->getSelectClauseColumnSQL(); + $query .= $this->getFromClause(); + $query .= $this->getWhereClause(); + $this->query = $query; + return $query; + } else { + return $this->query; + } + } + + public function getSQLColumn($name) { + if ($name == 'id') { + $baseTable = $this->meta->getEntityBaseTable(); + $moduleTableIndexList = $this->meta->getEntityTableIndexList(); + $baseTableIndex = $moduleTableIndexList[$baseTable]; + return $baseTable.'.'.$baseTableIndex; + } + + $moduleFields = $this->meta->getModuleFields(); + $field = $moduleFields[$name]; + $sql = ''; + //TODO optimization to eliminate one more lookup of name, incase the field refers to only + //one module or is of type owner. + $column = $field->getColumnName(); + return $field->getTableName().'.'.$column; + } + + public function getSelectClauseColumnSQL(){ + $columns = array(); + $moduleFields = $this->meta->getModuleFields(); + $accessibleFieldList = array_keys($moduleFields); + $accessibleFieldList[] = 'id'; + $this->fields = array_intersect($this->fields, $accessibleFieldList); + foreach ($this->fields as $field) { + $sql = $this->getSQLColumn($field); + $columns[] = $sql; + } + $this->columns = implode(', ',$columns); + return $this->columns; + } + + public function getFromClause() { + if(!empty($this->query) || !empty($this->fromClause)) { + return $this->fromClause; + } + $moduleFields = $this->meta->getModuleFields(); + $tableList = array(); + $tableJoinMapping = array(); + $tableJoinCondition = array(); + foreach ($this->fields as $fieldName) { + if ($fieldName == 'id') { + continue; + } + + $field = $moduleFields[$fieldName]; + $baseTable = $field->getTableName(); + $tableIndexList = $this->meta->getEntityTableIndexList(); + $baseTableIndex = $tableIndexList[$baseTable]; + if($field->getFieldDataType() == 'reference') { + $moduleList = $this->referenceFieldInfoList[$fieldName]; + $tableJoinMapping[$field->getTableName()] = 'INNER JOIN'; + foreach($moduleList as $module) { + if($module == 'Users') { + $tableJoinCondition[$fieldName]['vtiger_users'] = $field->getTableName(). + ".".$field->getColumnName()." = vtiger_users.id"; + $tableJoinCondition[$fieldName]['vtiger_groups'] = $field->getTableName(). + ".".$field->getColumnName()." = vtiger_groups.groupid"; + $tableJoinMapping['vtiger_users'] = 'LEFT JOIN'; + $tableJoinMapping['vtiger_groups'] = 'LEFT JOIN'; + } + } + } elseif($field->getFieldDataType() == 'owner') { + $tableList['vtiger_users'] = 'vtiger_users'; + $tableList['vtiger_groups'] = 'vtiger_groups'; + $tableJoinMapping['vtiger_users'] = 'LEFT JOIN'; + $tableJoinMapping['vtiger_groups'] = 'LEFT JOIN'; + } + $tableList[$field->getTableName()] = $field->getTableName(); + $tableJoinMapping[$field->getTableName()] = + $this->meta->getJoinClause($field->getTableName()); + } + $baseTable = $this->meta->getEntityBaseTable(); + $moduleTableIndexList = $this->meta->getEntityTableIndexList(); + $baseTableIndex = $moduleTableIndexList[$baseTable]; + foreach ($this->whereFields as $fieldName) { + if(empty($fieldName)) { + continue; + } + $field = $moduleFields[$fieldName]; + if(empty($field)) { + // not accessible field. + continue; + } + $baseTable = $field->getTableName(); + // When a field is included in Where Clause, but not is Select Clause, and the field table is not base table, + // The table will not be present in tablesList and hence needs to be added to the list. + if(empty($tableList[$baseTable])) { + $tableList[$baseTable] = $field->getTableName(); + $tableJoinMapping[$baseTable] = $this->meta->getJoinClause($field->getTableName()); + } + if($field->getFieldDataType() == 'reference') { + $moduleList = $this->referenceFieldInfoList[$fieldName]; + $tableJoinMapping[$field->getTableName()] = 'INNER JOIN'; + foreach($moduleList as $module) { + $meta = $this->getMeta($module); + $nameFields = $this->moduleNameFields[$module]; + $nameFieldList = explode(',',$nameFields); + foreach ($nameFieldList as $index=>$column) { + // for non admin user users module is inaccessible. + // so need hard code the tablename. + if($module == 'Users') { + $instance = CRMEntity::getInstance($module); + $referenceTable = $instance->table_name; + $tableIndexList = $instance->tab_name_index; + $referenceTableIndex = $tableIndexList[$referenceTable]; + } else { + $referenceField = $meta->getFieldByColumnName($column); + $referenceTable = $referenceField->getTableName(); + $tableIndexList = $meta->getEntityTableIndexList(); + $referenceTableIndex = $tableIndexList[$referenceTable]; + } + if(isset($moduleTableIndexList[$referenceTable])) { + $referenceTableName = "$referenceTable $referenceTable$fieldName"; + $referenceTable = "$referenceTable$fieldName"; + } else { + $referenceTableName = $referenceTable; + } + //should always be left join for cases where we are checking for null + //reference field values. + $tableJoinMapping[$referenceTableName] = 'LEFT JOIN'; + $tableJoinCondition[$fieldName][$referenceTableName] = $baseTable.'.'. + $field->getColumnName().' = '.$referenceTable.'.'.$referenceTableIndex; + } + } + } elseif($field->getFieldDataType() == 'owner') { + $tableList['vtiger_users'] = 'vtiger_users'; + $tableList['vtiger_groups'] = 'vtiger_groups'; + $tableJoinMapping['vtiger_users'] = 'LEFT JOIN'; + $tableJoinMapping['vtiger_groups'] = 'LEFT JOIN'; + } else { + $tableList[$field->getTableName()] = $field->getTableName(); + $tableJoinMapping[$field->getTableName()] = + $this->meta->getJoinClause($field->getTableName()); + } + } + + $defaultTableList = $this->meta->getEntityDefaultTableList(); + foreach ($defaultTableList as $table) { + if(!in_array($table, $tableList)) { + $tableList[$table] = $table; + $tableJoinMapping[$table] = 'INNER JOIN'; + } + } + $ownerFields = $this->meta->getOwnerFields(); + if (count($ownerFields) > 0) { + $ownerField = $ownerFields[0]; + } + $baseTable = $this->meta->getEntityBaseTable(); + $sql = " FROM $baseTable "; + unset($tableList[$baseTable]); + foreach ($defaultTableList as $tableName) { + $sql .= " $tableJoinMapping[$tableName] $tableName ON $baseTable.". + "$baseTableIndex = $tableName.$moduleTableIndexList[$tableName]"; + unset($tableList[$tableName]); + } + foreach ($tableList as $tableName) { + if($tableName == 'vtiger_users') { + $field = $moduleFields[$ownerField]; + $sql .= " $tableJoinMapping[$tableName] $tableName ON ".$field->getTableName().".". + $field->getColumnName()." = $tableName.id"; + } elseif($tableName == 'vtiger_groups') { + $field = $moduleFields[$ownerField]; + $sql .= " $tableJoinMapping[$tableName] $tableName ON ".$field->getTableName().".". + $field->getColumnName()." = $tableName.groupid"; + } else { + $sql .= " $tableJoinMapping[$tableName] $tableName ON $baseTable.". + "$baseTableIndex = $tableName.$moduleTableIndexList[$tableName]"; + } + } + + if( $this->meta->getTabName() == 'Documents') { + $tableJoinCondition['folderid'] = array( + 'vtiger_attachmentsfolder'=>"$baseTable.folderid = vtiger_attachmentsfolder.folderid" + ); + $tableJoinMapping['vtiger_attachmentsfolder'] = 'INNER JOIN'; + } + + foreach ($tableJoinCondition as $fieldName=>$conditionInfo) { + foreach ($conditionInfo as $tableName=>$condition) { + if(!empty($tableList[$tableName])) { + $tableNameAlias = $tableName.'2'; + $condition = str_replace($tableName, $tableNameAlias, $condition); + } else { + $tableNameAlias = ''; + } + $sql .= " $tableJoinMapping[$tableName] $tableName $tableNameAlias ON $condition"; + } + } + + foreach ($this->manyToManyRelatedModuleConditions as $conditionInfo) { + $relatedModuleMeta = RelatedModuleMeta::getInstance($this->meta->getTabName(), + $conditionInfo['relatedModule']); + $relationInfo = $relatedModuleMeta->getRelationMeta(); + $relatedModule = $this->meta->getTabName(); + $sql .= ' INNER JOIN '.$relationInfo['relationTable']." ON ". + $relationInfo['relationTable'].".$relationInfo[$relatedModule]=". + "$baseTable.$baseTableIndex"; + } + + $sql .= $this->meta->getEntityAccessControlQuery(); + $this->fromClause = $sql; + return $sql; + } + + public function getWhereClause() { + if(!empty($this->query) || !empty($this->whereClause)) { + return $this->whereClause; + } + $deletedQuery = $this->meta->getEntityDeletedQuery(); + $sql = ''; + if(!empty($deletedQuery)) { + $sql .= " WHERE $deletedQuery"; + } + if($this->conditionInstanceCount > 0) { + $sql .= ' AND '; + } elseif(empty($deletedQuery)) { + $sql .= ' WHERE '; + } + + $moduleFieldList = $this->meta->getModuleFields(); + $baseTable = $this->meta->getEntityBaseTable(); + $moduleTableIndexList = $this->meta->getEntityTableIndexList(); + $baseTableIndex = $moduleTableIndexList[$baseTable]; + $groupSql = $this->groupInfo; + $fieldSqlList = array(); + foreach ($this->conditionals as $index=>$conditionInfo) { + $fieldName = $conditionInfo['name']; + $field = $moduleFieldList[$fieldName]; + if(empty($field)) { + continue; + } + $fieldSql = '('; + $fieldGlue = ''; + $valueSqlList = $this->getConditionValue($conditionInfo['value'], + $conditionInfo['operator'], $field); + if(!is_array($valueSqlList)) { + $valueSqlList = array($valueSqlList); + } + foreach ($valueSqlList as $valueSql) { + if (in_array($fieldName, $this->referenceFieldList)) { + $moduleList = $this->referenceFieldInfoList[$fieldName]; + foreach($moduleList as $module) { + $nameFields = $this->moduleNameFields[$module]; + $nameFieldList = explode(',',$nameFields); + $meta = $this->getMeta($module); + $columnList = array(); + foreach ($nameFieldList as $column) { + if($module == 'Users') { + $instance = CRMEntity::getInstance($module); + $referenceTable = $instance->table_name; + if(count($this->ownerFields) > 0 || + $this->getModule() == 'Quotes') { + $referenceTable .= '2'; + } + } else { + $referenceField = $meta->getFieldByColumnName($column); + $referenceTable = $referenceField->getTableName(); + } + if(isset($moduleTableIndexList[$referenceTable])) { + $referenceTable = "$referenceTable$fieldName"; + } + $columnList[] = "$referenceTable.$column"; + } + if(count($columnList) > 1) { + $columnSql = getSqlForNameInDisplayFormat(array('first_name'=>$columnList[0],'last_name'=>$columnList[1]),'Users'); + } else { + $columnSql = implode('', $columnList); + } + + $fieldSql .= "$fieldGlue trim($columnSql) $valueSql"; + $fieldGlue = ' OR'; + } + } elseif (in_array($fieldName, $this->ownerFields)) { + $concatSql = getSqlForNameInDisplayFormat(array('first_name'=>"vtiger_users.first_name",'last_name'=>"vtiger_users.last_name"), 'Users'); + $fieldSql .= "$fieldGlue trim($concatSql) $valueSql or "."vtiger_groups.groupname $valueSql"; + } else { + if($fieldName == 'birthday' && !$this->isRelativeSearchOperators( + $conditionInfo['operator'])) { + $fieldSql .= "$fieldGlue DATE_FORMAT(".$field->getTableName().'.'. + $field->getColumnName().",'%m%d') ".$valueSql; + } else { + $fieldSql .= "$fieldGlue ".$field->getTableName().'.'. + $field->getColumnName().' '.$valueSql; + } + } + $fieldGlue = ' OR'; + } + $fieldSql .= ')'; + $fieldSqlList[$index] = $fieldSql; + } + foreach ($this->manyToManyRelatedModuleConditions as $index=>$conditionInfo) { + $relatedModuleMeta = RelatedModuleMeta::getInstance($this->meta->getTabName(), + $conditionInfo['relatedModule']); + $relationInfo = $relatedModuleMeta->getRelationMeta(); + $relatedModule = $this->meta->getTabName(); + $fieldSql = "(".$relationInfo['relationTable'].'.'. + $relationInfo[$conditionInfo['column']].$conditionInfo['SQLOperator']. + $conditionInfo['value'].")"; + $fieldSqlList[$index] = $fieldSql; + } + + $groupSql = $this->makeGroupSqlReplacements($fieldSqlList, $groupSql); + if($this->conditionInstanceCount > 0) { + $this->conditionalWhere = $groupSql; + $sql .= $groupSql; + } + $sql .= " AND $baseTable.$baseTableIndex > 0"; + $this->whereClause = $sql; + return $sql; + } + + /** + * + * @param mixed $value + * @param String $operator + * @param WebserviceField $field + */ + private function getConditionValue($value, $operator, $field) { + + $operator = strtolower($operator); + $db = PearDatabase::getInstance(); + + if(is_string($value)) { + $valueArray = explode(',' , $value); + } elseif(is_array($value)) { + $valueArray = $value; + } else{ + $valueArray = array($value); + } + $sql = array(); + if($operator == 'between' || $operator == 'bw') { + if($field->getFieldName() == 'birthday') { + $valueArray[0] = getValidDBInsertDateTimeValue($valueArray[0]); + $valueArray[1] = getValidDBInsertDateTimeValue($valueArray[1]); + $sql[] = "BETWEEN DATE_FORMAT(".$db->quote($valueArray[0]).", '%m%d') AND ". + "DATE_FORMAT(".$db->quote($valueArray[1]).", '%m%d')"; + } else { + if($this->isDateType($field->getFieldDataType())) { + $valueArray[0] = getValidDBInsertDateTimeValue($valueArray[0]); + $valueArray[1] = getValidDBInsertDateTimeValue($valueArray[1]); + } + $sql[] = "BETWEEN ".$db->quote($valueArray[0])." AND ". + $db->quote($valueArray[1]); + } + return $sql; + } + foreach ($valueArray as $value) { + if(!$this->isStringType($field->getFieldDataType())) { + $value = trim($value); + } + if((strtolower(trim($value)) == 'null') || + (trim($value) == '' && !$this->isStringType($field->getFieldDataType())) && + ($operator == 'e' || $operator == 'n')) { + if($operator == 'e'){ + $sql[] = "IS NULL"; + continue; + } + $sql[] = "IS NOT NULL"; + continue; + } elseif($field->getFieldDataType() == 'boolean') { + $value = strtolower($value); + if ($value == 'yes') { + $value = 1; + } elseif($value == 'no') { + $value = 0; + } + } elseif($this->isDateType($field->getFieldDataType())) { + $value = getValidDBInsertDateTimeValue($value); + } + + if($field->getFieldName() == 'birthday' && !$this->isRelativeSearchOperators( + $operator)) { + $value = "DATE_FORMAT(".$db->quote($value).", '%m%d')"; + } else { + $value = $db->sql_escape_string($value); + } + + if(trim($value) == '' && ($operator == 's' || $operator == 'ew' || $operator == 'c') + && ($this->isStringType($field->getFieldDataType()) || + $field->getFieldDataType() == 'picklist' || + $field->getFieldDataType() == 'multipicklist')) { + $sql[] = "LIKE ''"; + continue; + } + + if(trim($value) == '' && ($operator == 'k') && + $this->isStringType($field->getFieldDataType())) { + $sql[] = "NOT LIKE ''"; + continue; + } + + switch($operator) { + case 'e': $sqlOperator = "="; + break; + case 'n': $sqlOperator = "<>"; + break; + case 's': $sqlOperator = "LIKE"; + $value = "$value%"; + break; + case 'ew': $sqlOperator = "LIKE"; + $value = "%$value"; + break; + case 'c': $sqlOperator = "LIKE"; + $value = "%$value%"; + break; + case 'k': $sqlOperator = "NOT LIKE"; + $value = "%$value%"; + break; + case 'l': $sqlOperator = "<"; + break; + case 'g': $sqlOperator = ">"; + break; + case 'm': $sqlOperator = "<="; + break; + case 'h': $sqlOperator = ">="; + break; + case 'a': $sqlOperator = ">"; + break; + case 'b': $sqlOperator = "<"; + break; + } + if(!$this->isNumericType($field->getFieldDataType()) && + ($field->getFieldName() != 'birthday' || ($field->getFieldName() == 'birthday' + && $this->isRelativeSearchOperators($operator)))){ + $value = "'$value'"; + } + if($this->isNumericType($field->getFieldDataType()) && empty($value)) { + $value = '0'; + } + $sql[] = "$sqlOperator $value"; + } + return $sql; + } + + private function makeGroupSqlReplacements($fieldSqlList, $groupSql) { + $pos = 0; + $nextOffset = 0; + foreach ($fieldSqlList as $index => $fieldSql) { + $pos = strpos($groupSql, $index.'', $nextOffset); + if($pos !== false) { + $beforeStr = substr($groupSql,0,$pos); + $afterStr = substr($groupSql, $pos + strlen($index)); + $nextOffset = strlen($beforeStr.$fieldSql); + $groupSql = $beforeStr.$fieldSql.$afterStr; + } + } + return $groupSql; + } + + private function isRelativeSearchOperators($operator) { + $nonDaySearchOperators = array('l','g','m','h'); + return in_array($operator, $nonDaySearchOperators); + } + private function isNumericType($type) { + return ($type == 'integer' || $type == 'double' || $type == 'currency'); + } + + private function isStringType($type) { + return ($type == 'string' || $type == 'text' || $type == 'email' || $type == 'reference'); + } + + private function isDateType($type) { + return ($type == 'date' || $type == 'datetime'); + } + + private function fixDateTimeValue($name, $value, $first = true) { + $moduleFields = $this->meta->getModuleFields(); + $field = $moduleFields[$name]; + $type = $field->getFieldDataType(); + if($type == 'datetime') { + if(strrpos($value, ' ') === false) { + if($first) { + return $value.' 00:00:00'; + }else{ + return $value.' 23:59:59'; + } + } + } + return $value; + } + + public function addCondition($fieldname,$value,$operator,$glue= null,$newGroup = false, + $newGroupType = null) { + $conditionNumber = $this->conditionInstanceCount++; + $this->groupInfo .= "$conditionNumber "; + $this->whereFields[] = $fieldname; + $this->reset(); + $this->conditionals[$conditionNumber] = $this->getConditionalArray($fieldname, + $value, $operator); + } + + public function addRelatedModuleCondition($relatedModule,$column, $value, $SQLOperator) { + $conditionNumber = $this->conditionInstanceCount++; + $this->groupInfo .= "$conditionNumber "; + $this->manyToManyRelatedModuleConditions[$conditionNumber] = array('relatedModule'=> + $relatedModule,'column'=>$column,'value'=>$value,'SQLOperator'=>$SQLOperator); + } + + private function getConditionalArray($fieldname,$value,$operator) { + if(is_string($value)) { + $value = trim($value); + } elseif(is_array($value)) { + $value = array_map(trim, $value); + } + return array('name'=>$fieldname,'value'=>$value,'operator'=>$operator); + } + + public function startGroup($groupType) { + $this->groupInfo .= " $groupType ("; + } + + public function endGroup() { + $this->groupInfo .= ')'; + } + + public function addConditionGlue($glue) { + $this->groupInfo .= " $glue "; + } + + public function addUserSearchConditions($input) { + global $log,$default_charset; + if($input['searchtype']=='advance') { + + $json = new Zend_Json(); + $advft_criteria = $_REQUEST['advft_criteria']; + if(!empty($advft_criteria)) $advft_criteria = $json->decode($advft_criteria); + $advft_criteria_groups = $_REQUEST['advft_criteria_groups']; + if(!empty($advft_criteria_groups)) $advft_criteria_groups = $json->decode($advft_criteria_groups); + + if(empty($advft_criteria) || count($advft_criteria) <= 0) { + return ; + } + + $advfilterlist = getAdvancedSearchCriteriaList($advft_criteria, $advft_criteria_groups, $this->getModule()); + + if(empty($advfilterlist) || count($advfilterlist) <= 0) { + return ; + } + + if($this->conditionInstanceCount > 0) { + $this->startGroup(self::$AND); + } else { + $this->startGroup(''); + } + foreach ($advfilterlist as $groupindex=>$groupcolumns) { + $filtercolumns = $groupcolumns['columns']; + if(count($filtercolumns) > 0) { + $this->startGroup(''); + foreach ($filtercolumns as $index=>$filter) { + $name = explode(':',$filter['columnname']); + if(empty($name[2]) && $name[1] == 'crmid' && $name[0] == 'vtiger_crmentity') { + $name = $this->getSQLColumn('id'); + } else { + $name = $name[2]; + } + $this->addCondition($name, $filter['value'], $filter['comparator']); + $columncondition = $filter['column_condition']; + if(!empty($columncondition)) { + $this->addConditionGlue($columncondition); + } + } + $this->endGroup(); + $groupConditionGlue = $groupcolumns['condition']; + if(!empty($groupConditionGlue)) + $this->addConditionGlue($groupConditionGlue); + } + } + $this->endGroup(); + } elseif($input['type']=='dbrd') { + if($this->conditionInstanceCount > 0) { + $this->startGroup(self::$AND); + } else { + $this->startGroup(''); + } + $allConditionsList = $this->getDashBoardConditionList(); + $conditionList = $allConditionsList['conditions']; + $relatedConditionList = $allConditionsList['relatedConditions']; + $noOfConditions = count($conditionList); + $noOfRelatedConditions = count($relatedConditionList); + foreach ($conditionList as $index=>$conditionInfo) { + $this->addCondition($conditionInfo['fieldname'], $conditionInfo['value'], + $conditionInfo['operator']); + if($index < $noOfConditions - 1 || $noOfRelatedConditions > 0) { + $this->addConditionGlue(self::$AND); + } + } + foreach ($relatedConditionList as $index => $conditionInfo) { + $this->addRelatedModuleCondition($conditionInfo['relatedModule'], + $conditionInfo['conditionModule'], $conditionInfo['finalValue'], + $conditionInfo['SQLOperator']); + if($index < $noOfRelatedConditions - 1) { + $this->addConditionGlue(self::$AND); + } + } + $this->endGroup(); + } else { + if(isset($input['search_field']) && $input['search_field'] !="") { + $fieldName=vtlib_purify($input['search_field']); + } else { + return ; + } + if($this->conditionInstanceCount > 0) { + $this->startGroup(self::$AND); + } else { + $this->startGroup(''); + } + $moduleFields = $this->meta->getModuleFields(); + $field = $moduleFields[$fieldName]; + $type = $field->getFieldDataType(); + if(isset($input['search_text']) && $input['search_text']!="") { + // search other characters like "|, ?, ?" by jagi + $value = $input['search_text']; + $stringConvert = function_exists(iconv) ? @iconv("UTF-8",$default_charset,$value) + : $value; + if(!$this->isStringType($type)) { + $value=trim($stringConvert); + } + + if($type == 'picklist') { + global $mod_strings; + // Get all the keys for the for the Picklist value + $mod_keys = array_keys($mod_strings, $value); + if(sizeof($mod_keys) >= 1) { + // Iterate on the keys, to get the first key which doesn't start with LBL_ (assuming it is not used in PickList) + foreach($mod_keys as $mod_idx=>$mod_key) { + $stridx = strpos($mod_key, 'LBL_'); + // Use strict type comparision, refer strpos for more details + if ($stridx !== 0) { + $value = $mod_key; + break; + } + } + } + } + if($type == 'currency') { + // Some of the currency fields like Unit Price, Total, Sub-total etc of Inventory modules, do not need currency conversion + if($field->getUIType() == '72') { + $value = CurrencyField::convertToDBFormat($value, null, true); + } else { + $currencyField = new CurrencyField($value); + if($this->getModule() == 'Potentials' && $fieldName == 'amount') { + $currencyField->setNumberofDecimals(2); + } + $value = $currencyField->getDBInsertedValue(); + } + } + } + if(!empty($input['operator'])) { + $operator = $input['operator']; + } elseif(trim(strtolower($value)) == 'null'){ + $operator = 'e'; + } else { + if(!$this->isNumericType($type) && !$this->isDateType($type)) { + $operator = 'c'; + } else { + $operator = 'h'; + } + } + $this->addCondition($fieldName, $value, $operator); + $this->endGroup(); + } + } + + public function getDashBoardConditionList() { + if(isset($_REQUEST['leadsource'])) { + $leadSource = $_REQUEST['leadsource']; + } + if(isset($_REQUEST['date_closed'])) { + $dateClosed = $_REQUEST['date_closed']; + } + if(isset($_REQUEST['sales_stage'])) { + $salesStage = $_REQUEST['sales_stage']; + } + if(isset($_REQUEST['closingdate_start'])) { + $dateClosedStart = $_REQUEST['closingdate_start']; + } + if(isset($_REQUEST['closingdate_end'])) { + $dateClosedEnd = $_REQUEST['closingdate_end']; + } + if(isset($_REQUEST['owner'])) { + $owner = vtlib_purify($_REQUEST['owner']); + } + if(isset($_REQUEST['campaignid'])) { + $campaignId = vtlib_purify($_REQUEST['campaignid']); + } + if(isset($_REQUEST['quoteid'])) { + $quoteId = vtlib_purify($_REQUEST['quoteid']); + } + if(isset($_REQUEST['invoiceid'])) { + $invoiceId = vtlib_purify($_REQUEST['invoiceid']); + } + if(isset($_REQUEST['purchaseorderid'])) { + $purchaseOrderId = vtlib_purify($_REQUEST['purchaseorderid']); + } + + $conditionList = array(); + if(!empty($dateClosedStart) && !empty($dateClosedEnd)) { + + $conditionList[] = array('fieldname'=>'closingdate', 'value'=>$dateClosedStart, + 'operator'=>'h'); + $conditionList[] = array('fieldname'=>'closingdate', 'value'=>$dateClosedEnd, + 'operator'=>'m'); + } + if(!empty($salesStage)) { + if($salesStage == 'Other') { + $conditionList[] = array('fieldname'=>'sales_stage', 'value'=>'Closed Won', + 'operator'=>'n'); + $conditionList[] = array('fieldname'=>'sales_stage', 'value'=>'Closed Lost', + 'operator'=>'n'); + } else { + $conditionList[] = array('fieldname'=>'sales_stage', 'value'=> $salesStage, + 'operator'=>'e'); + } + } + if(!empty($leadSource)) { + $conditionList[] = array('fieldname'=>'leadsource', 'value'=>$leadSource, + 'operator'=>'e'); + } + if(!empty($dateClosed)) { + $conditionList[] = array('fieldname'=>'closingdate', 'value'=>$dateClosed, + 'operator'=>'h'); + } + if(!empty($owner)) { + $conditionList[] = array('fieldname'=>'assigned_user_id', 'value'=>$owner, + 'operator'=>'e'); + } + $relatedConditionList = array(); + if(!empty($campaignId)) { + $relatedConditionList[] = array('relatedModule'=>'Campaigns','conditionModule'=> + 'Campaigns','finalValue'=>$campaignId, 'SQLOperator'=>'='); + } + if(!empty($quoteId)) { + $relatedConditionList[] = array('relatedModule'=>'Quotes','conditionModule'=> + 'Quotes','finalValue'=>$quoteId, 'SQLOperator'=>'='); + } + if(!empty($invoiceId)) { + $relatedConditionList[] = array('relatedModule'=>'Invoice','conditionModule'=> + 'Invoice','finalValue'=>$invoiceId, 'SQLOperator'=>'='); + } + if(!empty($purchaseOrderId)) { + $relatedConditionList[] = array('relatedModule'=>'PurchaseOrder','conditionModule'=> + 'PurchaseOrder','finalValue'=>$purchaseOrderId, 'SQLOperator'=>'='); + } + return array('conditions'=>$conditionList,'relatedConditions'=>$relatedConditionList); + } + + public function initForGlobalSearchByType($type, $value, $operator='s') { + $fieldList = $this->meta->getFieldNameListByType($type); + if($this->conditionInstanceCount <= 0) { + $this->startGroup(''); + } else { + $this->startGroup(self::$AND); + } + $nameFieldList = explode(',',$this->getModuleNameFields($this->module)); + foreach ($nameFieldList as $nameList) { + $field = $this->meta->getFieldByColumnName($nameList); + $this->fields[] = $field->getFieldName(); + } + foreach ($fieldList as $index => $field) { + $fieldName = $this->meta->getFieldByColumnName($field); + $this->fields[] = $fieldName->getFieldName(); + if($index > 0) { + $this->addConditionGlue(self::$OR); + } + $this->addCondition($fieldName->getFieldName(), $value, $operator); + } + $this->endGroup(); + if(!in_array('id', $this->fields)) { + $this->fields[] = 'id'; + } + } + +} +?> \ No newline at end of file diff --git a/include/RelatedListView.php b/include/RelatedListView.php new file mode 100644 index 0000000..7f8afa6 --- /dev/null +++ b/include/RelatedListView.php @@ -0,0 +1,660 @@ +debug("Entering GetRelatedList(".$module.",".$relatedmodule.",".get_class($focus).",".$query.",".$button.",".$returnset.",".$edit_val.",".$del_val.") method ..."); + + require_once('Smarty_setup.php'); + require_once("data/Tracker.php"); + require_once('include/database/PearDatabase.php'); + + global $adb; + global $app_strings; + global $current_language; + + $current_module_strings = return_module_language($current_language, $module); + + global $list_max_entries_per_page; + global $urlPrefix; + + + global $currentModule; + global $theme; + global $theme_path; + global $theme_path; + global $mod_strings; + // focus_list is the means of passing data to a ListView. + global $focus_list; + $smarty = new vtigerCRM_Smarty; + if (!isset($where)) $where = ""; + + + $button = '
'.$button.'
'; + + // Added to have Purchase Order as form Title + $theme_path="themes/".$theme."/"; + $image_path=$theme_path."images/"; + $smarty->assign("MOD", $mod_strings); + $smarty->assign("APP", $app_strings); + $smarty->assign("THEME", $theme); + $smarty->assign("IMAGE_PATH",$image_path); + $smarty->assign("MODULE",$relatedmodule); + + // We do not have RelatedListView in Detail View mode of Calendar module. So need to skip it. + if ($module!= 'Calendar') { + $focus->initSortByField($relatedmodule); + } + //Retreive the list from Database + //Appending the security parameter Security fix by Don + if($relatedmodule != 'Faq' && $relatedmodule != 'PriceBook' + && $relatedmodule != 'Vendors' && $relatedmodule != 'Users') { + global $current_user; + $secQuery = getNonAdminAccessControlQuery($relatedmodule, $current_user); + if(strlen($secQuery) > 1) { + $query = appendFromClauseToQuery($query, $secQuery); + } + } + if($relatedmodule == 'Leads') { + $query .= " AND vtiger_leaddetails.converted = 0"; + } + + + if(isset($where) && $where != '') + { + $query .= ' and '.$where; + } + + if(!$_SESSION['rlvs'][$module][$relatedmodule]) + { + $modObj = new ListViewSession(); + $modObj->sortby = $focus->default_order_by; + $modObj->sorder = $focus->default_sort_order; + $_SESSION['rlvs'][$module][$relatedmodule] = get_object_vars($modObj); + } + + if(!empty($_REQUEST['order_by'])) { + if(method_exists($focus,getSortOrder)) + $sorder = $focus->getSortOrder(); + if(method_exists($focus,getOrderBy)) + $order_by = $focus->getOrderBy(); + + if(isset($order_by) && $order_by != '') { + $_SESSION['rlvs'][$module][$relatedmodule]['sorder'] = $sorder; + $_SESSION['rlvs'][$module][$relatedmodule]['sortby'] = $order_by; + } + + } elseif($_SESSION['rlvs'][$module][$relatedmodule]) { + $sorder = $_SESSION['rlvs'][$module][$relatedmodule]['sorder']; + $order_by = $_SESSION['rlvs'][$module][$relatedmodule]['sortby']; + } else { + $order_by = $focus->default_order_by; + $sorder = $focus->default_sort_order; + } + + //Added by Don for AssignedTo ordering issue in Related Lists + $query_order_by = $order_by; + if($order_by == 'smownerid') { + $userNameSql = getSqlForNameInDisplayFormat(array('first_name'=>'vtiger_users.first_name', + 'last_name' => 'vtiger_users.last_name'), 'Users'); + $query_order_by = "case when (vtiger_users.user_name not like '') then $userNameSql else vtiger_groups.groupname end "; + } elseif($order_by != 'crmid' && !empty($order_by)) { + $tabname = getTableNameForField($relatedmodule, $order_by); + if($tabname !== '' and $tabname != NULL) + $query_order_by = $tabname.".".$query_order_by; + } + if(!empty($query_order_by)){ + $query .= ' ORDER BY '.$query_order_by.' '.$sorder; + } + + if($relatedmodule == 'Calendar') + $mod_listquery = "activity_listquery"; + else + $mod_listquery = strtolower($relatedmodule)."_listquery"; + $_SESSION[$mod_listquery] = $query; + + $url_qry .="&order_by=".$order_by."&sorder=".$sorder; + $computeCount = $_REQUEST['withCount']; + if(PerformancePrefs::getBoolean('LISTVIEW_COMPUTE_PAGE_COUNT', false) === true || + (boolean) $computeCount == true){ + //Retreiving the no of rows + if($relatedmodule == "Calendar") { + //for calendar related list, count will increase when we have multiple contacts + //relationship for single activity + $count_query = mkCountQuery($query); + $count_result = $adb->query($count_query); + $noofrows =$adb->query_result($count_result,0,"count"); + } else { + $count_query = mkCountQuery($query); + $count_result = $adb->query($count_query); + + if($adb->num_rows($count_result) > 0) + $noofrows =$adb->query_result($count_result,0,"count"); + else + $noofrows = $adb->num_rows($count_result); + } + }else{ + $noofrows = null; + } + + //Setting Listview session object while sorting/pagination + if(isset($_REQUEST['relmodule']) && $_REQUEST['relmodule']!='' && $_REQUEST['relmodule'] == $relatedmodule) + { + $relmodule = vtlib_purify($_REQUEST['relmodule']); + if($_SESSION['rlvs'][$module][$relmodule]) + { + setSessionVar($_SESSION['rlvs'][$module][$relmodule],$noofrows,$list_max_entries_per_page,$module,$relmodule); + } + } + global $relationId; + $start = RelatedListViewSession::getRequestCurrentPage($relationId, $query); + $navigation_array = VT_getSimpleNavigationValues($start, $list_max_entries_per_page, + $noofrows); + + $limit_start_rec = ($start-1) * $list_max_entries_per_page; + + if( $adb->dbType == "pgsql") + $list_result = $adb->pquery($query. + " OFFSET $limit_start_rec LIMIT $list_max_entries_per_page", array()); + else + $list_result = $adb->pquery($query. + " LIMIT $limit_start_rec, $list_max_entries_per_page", array()); + + //Retreive the List View Table Header + $id = vtlib_purify($_REQUEST['record']); + $listview_header = getListViewHeader($focus,$relatedmodule,'',$sorder,$order_by,$id,'',$module,$skipActions);//"Accounts"); + if ($noofrows > 15) { + $smarty->assign('SCROLLSTART','
'); + $smarty->assign('SCROLLSTOP','
'); + } + $smarty->assign("LISTHEADER", $listview_header); + + if($module == 'PriceBook' && $relatedmodule == 'Products') { + $listview_entries = getListViewEntries($focus,$relatedmodule,$list_result,$navigation_array,'relatedlist',$returnset,$edit_val,$del_val,'','','','',$skipActions); + } + if($module == 'Products' && $relatedmodule == 'PriceBook') { + $listview_entries = getListViewEntries($focus,$relatedmodule,$list_result,$navigation_array,'relatedlist',$returnset,'EditListPrice','DeletePriceBookProductRel','','','','',$skipActions); + } elseif($relatedmodule == 'SalesOrder') { + $listview_entries = getListViewEntries($focus,$relatedmodule,$list_result,$navigation_array,'relatedlist',$returnset,'SalesOrderEditView','DeleteSalesOrder','','','','',$skipActions); + }else { + $listview_entries = getListViewEntries($focus,$relatedmodule,$list_result,$navigation_array,'relatedlist',$returnset,$edit_val,$del_val,'','','','',$skipActions); + } + + $navigationOutput = Array(); + $navigationOutput[] = getRecordRangeMessage($list_result, $limit_start_rec,$noofrows); + if(empty($id) && !empty($_REQUEST['record'])) $id = vtlib_purify($_REQUEST['record']); + $navigationOutput[] = getRelatedTableHeaderNavigation($navigation_array, $url_qry,$module,$relatedmodule,$id); + + $related_entries = array('header'=>$listview_header,'entries'=>$listview_entries,'navigation'=>$navigationOutput); + + $log->debug("Exiting GetRelatedList method ..."); + return $related_entries; +} + +/** Function to get related list entries in detailed array format + * @param $parentmodule -- parentmodulename:: Type string + * @param $query -- query:: Type string + * @param $id -- id:: Type string + * @returns $entries_list -- entries list:: Type string array + * + */ + +function getAttachmentsAndNotes($parentmodule,$query,$id,$sid='') +{ + global $log; + $log->debug("Entering getAttachmentsAndNotes(".$parentmodule.",".$query.",".$id.",".$sid.") method ..."); + global $theme; + + $list = ''; + + $theme_path="themes/".$theme."/"; + $image_path=$theme_path."images/"; + + global $adb,$current_user; + global $mod_strings; + global $app_strings, $listview_max_textlength; + + $result=$adb->query($query); + $noofrows = $adb->num_rows($result); + + $_SESSION['Documents_listquery'] = $query; + $header[] = $app_strings['LBL_TITLE']; + $header[] = $app_strings['LBL_DESCRIPTION']; + $header[] = $app_strings['LBL_ATTACHMENTS']; + $header[] = $app_strings['LBL_ASSIGNED_TO']; + $header[] = $app_strings['LBL_ACTION']; + + if($result) + { + while($row = $adb->fetch_array($result)) + { + if($row['activitytype'] == 'Attachments') { + $query1="select setype,createdtime from vtiger_crmentity where crmid=?"; + $params1 = array($row['attachmentsid']); + } else { + $query1="select setype,createdtime from vtiger_crmentity where crmid=?"; + $params1 = array($row['crmid']); + } + + $query1 .=" order by createdtime desc"; + $res=$adb->pquery($query1, $params1); + $num_rows = $adb->num_rows($res); + for($i=0; $i<$num_rows; $i++) + { + $setype = $adb->query_result($res,$i,'setype'); + $createdtime = $adb->query_result($res,$i,'createdtime'); + } + + if(($setype != "Products Image") && ($setype != "Contacts Image")) + { + $entries = Array(); + if(trim($row['activitytype']) == 'Documents') + { + $module = 'Documents'; + $editaction = 'EditView'; + $deleteaction = 'Delete'; + } + elseif($row['activitytype'] == 'Attachments') + { + $module = 'uploads'; + $editaction = 'upload'; + $deleteaction = 'deleteattachments'; + } + if($module == 'Documents') + { + $entries[] = ''.textlength_check($row['title']).''; + } + elseif($module == 'uploads') + { + $entries[] = $row['title']; + } + if((getFieldVisibilityPermission('Documents', $current_user->id, 'notecontent') == '0') || $row['activitytype'] == 'Documents') + { + $row['description'] = preg_replace("/(<\/?)(\w+)([^>]*>)/i","",$row['description']); + if($listview_max_textlength && (strlen($row['description']) > $listview_max_textlength)) + { + $row['description'] = substr($row['description'],0,$listview_max_textlength).'...'; + } + $entries[] = nl2br($row['description']); + } + else + $entries[] = " " .$app_strings['LBL_NOT_ACCESSIBLE'].""; + + $attachmentname = $row['filename'];//explode('_',$row['filename'],2); + + if((getFieldVisibilityPermission('Documents', $current_user->id, 'filename') == 0)) + { + global $adb; + + $prof_id = fetchUserProfileId($current_user->id); + $modulepermissionQuery = "select permissions from vtiger_profile2tab where tabid=8 and profileid= ?"; + $modulepermissionresult = $adb->pquery($modulepermissionQuery,array($prof_id)); + $moduleviewpermission = $adb->query_result($modulepermissionresult,0,'permissions'); + + $folderQuery = 'select folderid,filelocationtype,filestatus,filename from vtiger_notes where notesid = ?'; + $folderresult = $adb->pquery($folderQuery,array($row["crmid"])); + $folder_id = $adb->query_result($folderresult,0,'folderid'); + $download_type = $adb->query_result($folderresult,0,'filelocationtype'); + $filestatus = $adb->query_result($folderresult,0,'filestatus'); + $filename = $adb->query_result($folderresult,0,'filename'); + + $fileQuery = $adb->pquery("select attachmentsid from vtiger_seattachmentsrel where crmid = ?",array($row['crmid'])); + $fileid = $adb->query_result($fileQuery,0,'attachmentsid'); + if($moduleviewpermission == 0) + { + if($download_type == 'I' ) + { + if($filestatus == 1 ) + $entries[] = ''.textlength_check($attachmentname).''; + elseif(isset($attachmentname) && $attachmentname != '') + $entries[] = textlength_check($attachmentname); + else + $entries[] = ' --'; + } + elseif($download_type == 'E' ) + { + if($filestatus == 1) + $entries[] = ''.textlength_check($attachmentname).''; + elseif(isset($attachmentname) && $attachmentname != '') + $entries[] = textlength_check($attachmentname); + else + $entries[] = ' --'; + } + else{ + $entries[] = ' --'; + } + } + else + { + if(isset($attachmentname)) + $entries[] = textlength_check($attachmentname); + else + $entries[] = ' --'; + } + } + else + $entries[]=''; + + $assignedToQuery = $adb->pquery('SELECT smownerid FROM vtiger_crmentity WHERE crmid = ?',array($row['crmid'])); + $assignedTo = $adb->query_result($assignedToQuery,0,'smownerid'); + if($assignedTo != '' ){ + $entries[] = $assignedTo; + } + $del_param = 'index.php?module='.$module.'&action='.$deleteaction.'&return_module='.$parentmodule.'&return_action='.vtlib_purify($_REQUEST['action']).'&record='.$row["crmid"].'&return_id='.vtlib_purify($_REQUEST["record"]).'&parenttab='.vtlib_purify($_REQUEST["parenttab"]); + + if($module == 'Documents') + { + $edit_param = 'index.php?module='.$module.'&action='.$editaction.'&return_module='.$parentmodule.'&return_action='.vtlib_purify($_REQUEST['action']).'&record='.$row["crmid"].'&filename='.$row['filename'].'&fileid='.$row['attachmentsid'].'&return_id='.vtlib_purify($_REQUEST["record"]).'&parenttab='.vtlib_purify($_REQUEST["parenttab"]); + + $entries[] .= ''.$app_strings['LNK_EDIT'].' | '.$app_strings['LNK_DELETE'].''; + } + else + { + $entries[] = ''.$app_strings['LNK_DELETE'].''; + } + $entries_list[] = $entries; + } + } + } + + if($entries_list != '') + $return_data = array('header'=>$header,'entries'=>$entries_list); + $log->debug("Exiting getAttachmentsAndNotes method ..."); + return $return_data; + +} + +/** Function to get related list entries in detailed array format + * @param $parentmodule -- parentmodulename:: Type string + * @param $query -- query:: Type string + * @param $id -- id:: Type string + * @returns $return_data -- return data:: Type string array + * + */ + +function getHistory($parentmodule,$query,$id) +{ + global $log; + $log->debug("Entering getHistory(".$parentmodule.",".$query.",".$id.") method ..."); + $parentaction = vtlib_purify($_REQUEST['action']); + global $theme; + $theme_path="themes/".$theme."/"; + $image_path=$theme_path."images/"; + + global $adb; + global $mod_strings; + global $app_strings; + + //Appending the security parameter + global $current_user; + $rel_tab_id = getTabid("Calendar"); + + global $current_user; + require('user_privileges/user_privileges_'.$current_user->id.'.php'); + require('user_privileges/sharing_privileges_'.$current_user->id.'.php'); + $tab_id = getTabid('Calendar'); + if($is_admin == false && $profileGlobalPermission[1] == 1 && $profileGlobalPermission[2] == 1 && $defaultOrgSharingPermission[$tab_id] == 3) + { + $sec_parameter=getListViewSecurityParameter('Calendar'); + $query .= ' '.$sec_parameter; + } + $query.= ' '."ORDER BY vtiger_activity.date_start DESC,vtiger_activity.time_start DESC"; + $result=$adb->query($query); + $noofrows = $adb->num_rows($result); + + if($noofrows == 0) + { + //There is no entries for history + } + else + { + //Form the header columns + $header[] = $app_strings['LBL_TYPE']; + $header[] = $app_strings['LBL_SUBJECT']; + $header[] = $app_strings['LBL_RELATED_TO']; + $header[] = $app_strings['LBL_START_DATE']." & ".$app_strings['LBL_TIME']; + $header[] = $app_strings['LBL_END_DATE']." & ".$app_strings['LBL_TIME']; + //$header[] = $app_strings['LBL_DESCRIPTION']; + $header[] = $app_strings['LBL_STATUS']; + $header[] = $app_strings['LBL_ASSIGNED_TO']; + + $i = 1; + while($row = $adb->fetch_array($result)) + { + $entries = Array(); + if($row['activitytype'] == 'Task') + { + $activitymode = 'Task'; + $icon = 'Tasks.gif'; + $status = $row['status']; + $status = $app_strings[$status]; + } + else + { + $activitymode = 'Events'; + $icon = 'Activities.gif'; + $status = $row['eventstatus']; + $status = $app_strings[$status]; + } + + $typeofactivity = $row['activitytype']; + $typeofactivity = getTranslatedString($typeofactivity, 'Calendar'); + $entries[] = $typeofactivity; + + $activity = ''.$row['subject'].''; + $entries[] = $activity; + + $parentname = getRelatedTo('Calendar',$result,$i-1); + $entries[] = $parentname; + + $date = new DateTimeField($row['date_start']." ".$row['time_start']); + $entries[] = $date->getDisplayDateTimeValue(); + $date = new DateTimeField($row['due_date']." ".$row['time_end']); + $entries[] = $date->getDisplayDate(); + + $entries[] = $status; + + if($row['user_name'] == null && $row['groupname'] != null) + { + $entries[] = $row['groupname']; + } + else + { + $entries[] = $row['user_name']; + } + + $i++; + $entries_list[] = $entries; + } + + $return_data = array('header'=>$header,'entries'=>$entries_list); + $log->debug("Exiting getHistory method ..."); + return $return_data; + } +} + +/** Function to display the Products which are related to the PriceBook + * @param string $query - query to get the list of products which are related to the current PriceBook + * @param object $focus - PriceBook object which contains all the information of the current PriceBook + * @param string $returnset - return_module, return_action and return_id which are sequenced with & to pass to the URL which is optional + * return array $return_data which will be formed like array('header'=>$header,'entries'=>$entries_list) where as $header contains all the header columns and $entries_list will contain all the Product entries + */ +function getPriceBookRelatedProducts($query,$focus,$returnset='') +{ + global $log; + $log->debug("Entering getPriceBookRelatedProducts(".$query.",".get_class($focus).",".$returnset.") method ..."); + + global $adb; + global $app_strings; + global $mod_strings; + global $current_language,$current_user; + $current_module_strings = return_module_language($current_language, 'PriceBook'); + + global $list_max_entries_per_page; + global $urlPrefix; + + global $theme; + $pricebook_id = vtlib_purify($_REQUEST['record']); + $theme_path="themes/".$theme."/"; + $image_path=$theme_path."images/"; + + $computeCount = $_REQUEST['withCount']; + if(PerformancePrefs::getBoolean('LISTVIEW_COMPUTE_PAGE_COUNT', false) === true || + ((boolean) $computeCount) == true){ + $noofrows = $adb->query_result($adb->query(mkCountQuery($query)),0,'count'); + }else{ + $noofrows = null; + } + + $module = 'PriceBooks'; + $relatedmodule = 'Products'; + if(!$_SESSION['rlvs'][$module][$relatedmodule]) + { + $modObj = new ListViewSession(); + $modObj->sortby = $focus->default_order_by; + $modObj->sorder = $focus->default_sort_order; + $_SESSION['rlvs'][$module][$relatedmodule] = get_object_vars($modObj); + } + + + if(isset($_REQUEST['relmodule']) && $_REQUEST['relmodule']!='' && $_REQUEST['relmodule'] == $relatedmodule) { + $relmodule = vtlib_purify($_REQUEST['relmodule']); + if($_SESSION['rlvs'][$module][$relmodule]) { + setSessionVar($_SESSION['rlvs'][$module][$relmodule],$noofrows,$list_max_entries_per_page,$module,$relmodule); + } + } + global $relationId; + $start = RelatedListViewSession::getRequestCurrentPage($relationId, $query); + $navigation_array = VT_getSimpleNavigationValues($start, $list_max_entries_per_page, + $noofrows); + + $limit_start_rec = ($start-1) * $list_max_entries_per_page; + + if( $adb->dbType == "pgsql") + $list_result = $adb->pquery($query. + " OFFSET $limit_start_rec LIMIT $list_max_entries_per_page", array()); + else + $list_result = $adb->pquery($query. + " LIMIT $limit_start_rec, $list_max_entries_per_page", array()); + + $header=array(); + $header[]=$mod_strings['LBL_LIST_PRODUCT_NAME']; + if(getFieldVisibilityPermission('Products', $current_user->id, 'productcode') == '0') + $header[]=$mod_strings['LBL_PRODUCT_CODE']; + if(getFieldVisibilityPermission('Products', $current_user->id, 'unit_price') == '0') + $header[]=$mod_strings['LBL_PRODUCT_UNIT_PRICE']; + $header[]=$mod_strings['LBL_PB_LIST_PRICE']; + if(isPermitted("PriceBooks","EditView","") == 'yes' || isPermitted("PriceBooks","Delete","") == 'yes') + $header[]=$mod_strings['LBL_ACTION']; + + $currency_id = $focus->column_fields['currency_id']; + $numRows = $adb->num_rows($list_result); + for($i=0; $i<$numRows; $i++) { + $entity_id = $adb->query_result($list_result,$i,"crmid"); + $unit_price = $adb->query_result($list_result,$i,"unit_price"); + if($currency_id != null) { + $prod_prices = getPricesForProducts($currency_id, array($entity_id)); + $unit_price = $prod_prices[$entity_id]; + } + $listprice = $adb->query_result($list_result,$i,"listprice"); + $field_name=$entity_id."_listprice"; + + $entries = Array(); + $entries[] = textlength_check($adb->query_result($list_result,$i,"productname")); + if(getFieldVisibilityPermission('Products', $current_user->id, 'productcode') == '0') + $entries[] = $adb->query_result($list_result,$i,"productcode"); + if(getFieldVisibilityPermission('Products', $current_user->id, 'unit_price') == '0') + $entries[] = CurrencyField::convertToUserFormat($unit_price, null, true); + + $entries[] = CurrencyField::convertToUserFormat($listprice, null, true); + $action = ""; + if(isPermitted("PriceBooks","EditView","") == 'yes' && isPermitted('Products', 'EditView', $entity_id) == 'yes') { + $action .= ''.$app_strings['; + } else { + $action .= ''; + } + if(isPermitted("PriceBooks","Delete","") == 'yes' && isPermitted('Products', 'Delete', $entity_id) == 'yes') { + if($action != "") + $action .= ' | '; + $action .= ''.$app_strings['; + } + if($action != "") + $entries[] = $action; + $entries_list[] = $entries; + } + $navigationOutput[] = getRecordRangeMessage($list_result, $limit_start_rec,$noofrows); + $navigationOutput[] = getRelatedTableHeaderNavigation($navigation_array, '',$module, + $relatedmodule,$focus->id); + $return_data = array('header'=>$header,'entries'=>$entries_list,'navigation'=>$navigationOutput); + + $log->debug("Exiting getPriceBookRelatedProducts method ..."); + return $return_data; +} + +function CheckFieldPermission($fieldname,$module) { + global $current_user,$adb; + require('user_privileges/user_privileges_'.$current_user->id.'.php'); + if($fieldname == '' || $module == '') { + return "false"; + } + + if(getFieldVisibilityPermission($module, $current_user->id, $fieldname) == '0') { + return "true"; + } + return "false"; +} + +function CheckColumnPermission($tablename, $columnname, $module) +{ + global $adb; + + $res = $adb->pquery("select fieldname from vtiger_field where tablename=? and columnname=? and vtiger_field.presence in (0,2)", array($tablename, $columnname)); + $fieldname = $adb->query_result($res, 0, 'fieldname'); + return CheckFieldPermission($fieldname, $module); +} +?> \ No newline at end of file diff --git a/include/Webservices/AuthToken.php b/include/Webservices/AuthToken.php new file mode 100644 index 0000000..7779348 --- /dev/null +++ b/include/Webservices/AuthToken.php @@ -0,0 +1,31 @@ +retrieve_user_id($username); + $authToken = uniqid(); + + $servertime = time(); + $expireTime = time()+(60*5); + + $sql = "delete from vtiger_ws_userauthtoken where userid=?"; + $adb->pquery($sql,array($userid)); + + $sql = "insert into vtiger_ws_userauthtoken(userid,token,expireTime) values (?,?,?)"; + $adb->pquery($sql,array($userid,$authToken,$expireTime)); + + return array("token"=>$authToken,"serverTime"=>$servertime,"expireTime"=>$expireTime); + } + +?> \ No newline at end of file diff --git a/include/Webservices/ChangePassword.php b/include/Webservices/ChangePassword.php new file mode 100644 index 0000000..0d98006 --- /dev/null +++ b/include/Webservices/ChangePassword.php @@ -0,0 +1,70 @@ + + */ + +/** + * @param WebserviceId $id + * @param String $oldPassword + * @param String $newPassword + * @param String $confirmPassword + * @param Users $user + * + */ +function vtws_changePassword($id, $oldPassword, $newPassword, $confirmPassword, $user) { + vtws_preserveGlobal('current_user',$user); + $idComponents = vtws_getIdComponents($id); + if($idComponents[1] == $user->id || is_admin($user)) { + $newUser = new Users(); + $newUser->retrieveCurrentUserInfoFromFile($idComponents[1]); + if(!is_admin($user)) { + if(empty($oldPassword)) { + throw new WebServiceException(WebServiceErrorCode::$INVALIDOLDPASSWORD, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$INVALIDOLDPASSWORD)); + } + if(!$user->verifyPassword($oldPassword)) { + throw new WebServiceException(WebServiceErrorCode::$INVALIDOLDPASSWORD, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$INVALIDOLDPASSWORD)); + } + } + if(strcmp($newPassword, $confirmPassword) === 0) { + $db = PearDatabase::getInstance(); + $db->dieOnError = true; + $db->startTransaction(); + $success = $newUser->change_password($oldPassword, $newPassword, false); + $error = $db->hasFailedTransaction(); + $db->completeTransaction(); + if($error) { + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + if(!$success) { + throw new WebServiceException(WebServiceErrorCode::$CHANGEPASSWORDFAILURE, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$CHANGEPASSWORDFAILURE)); + } + } else { + throw new WebServiceException(WebServiceErrorCode::$CHANGEPASSWORDFAILURE, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$CHANGEPASSWORDFAILURE)); + } + VTWS_PreserveGlobal::flush(); + return array('message' => 'Changed password successfully'); + } +} + + +?> diff --git a/include/Webservices/ConvertLead.php b/include/Webservices/ConvertLead.php new file mode 100644 index 0000000..1726958 --- /dev/null +++ b/include/Webservices/ConvertLead.php @@ -0,0 +1,266 @@ +id); + } + if (empty($entityvalues['transferRelatedRecordsTo'])) { + $entityvalues['transferRelatedRecordsTo'] = 'Contacts'; + } + + + $leadObject = VtigerWebserviceObject::fromName($adb, 'Leads'); + $handlerPath = $leadObject->getHandlerPath(); + $handlerClass = $leadObject->getHandlerClass(); + + require_once $handlerPath; + + $leadHandler = new $handlerClass($leadObject, $user, $adb, $log); + + + $leadInfo = vtws_retrieve($entityvalues['leadId'], $user); + $sql = "select converted from vtiger_leaddetails where converted = 1 and leadid=?"; + $leadIdComponents = vtws_getIdComponents($entityvalues['leadId']); + $result = $adb->pquery($sql, array($leadIdComponents[1])); + if ($result === false) { + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_' . + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + $rowCount = $adb->num_rows($result); + if ($rowCount > 0) { + throw new WebServiceException(WebServiceErrorCode::$LEAD_ALREADY_CONVERTED, + "Lead is already converted"); + } + + $entityIds = array(); + + $availableModules = array('Accounts', 'Contacts', 'Potentials'); + + if (!(($entityvalues['entities']['Accounts']['create']) || ($entityvalues['entities']['Contacts']['create']))) { + return null; + } + + foreach ($availableModules as $entityName) { + if ($entityvalues['entities'][$entityName]['create']) { + $entityvalue = $entityvalues['entities'][$entityName]; + $entityObject = VtigerWebserviceObject::fromName($adb, $entityvalue['name']); + $handlerPath = $entityObject->getHandlerPath(); + $handlerClass = $entityObject->getHandlerClass(); + + require_once $handlerPath; + + $entityHandler = new $handlerClass($entityObject, $user, $adb, $log); + + $entityObjectValues = array(); + $entityObjectValues['assigned_user_id'] = $entityvalues['assignedTo']; + $entityObjectValues = vtws_populateConvertLeadEntities($entityvalue, $entityObjectValues, $entityHandler, $leadHandler, $leadInfo); + + //update potential related to property + if ($entityvalue['name'] == 'Potentials') { + if (!empty($entityIds['Accounts'])) { + $entityObjectValues['related_to'] = $entityIds['Accounts']; + } else { + $entityObjectValues['related_to'] = $entityIds['Contacts']; + } + } + + //update the contacts relation + if ($entityvalue['name'] == 'Contacts') { + if (!empty($entityIds['Accounts'])) { + $entityObjectValues['account_id'] = $entityIds['Accounts']; + } + } + + try { + $create = true; + if ($entityvalue['name'] == 'Accounts') { + $sql = "SELECT vtiger_account.accountid FROM vtiger_account,vtiger_crmentity WHERE vtiger_crmentity.crmid=vtiger_account.accountid AND vtiger_account.accountname=? AND vtiger_crmentity.deleted=0"; + $result = $adb->pquery($sql, array($entityvalue['accountname'])); + if ($adb->num_rows($result) > 0) { + $entityIds[$entityName] = vtws_getWebserviceEntityId('Accounts', $adb->query_result($result, 0, 'accountid')); + $create = false; + } + } + if ($create) { + $entityRecord = vtws_create($entityvalue['name'], $entityObjectValues, $user); + $entityIds[$entityName] = $entityRecord['id']; + } + } catch (Exception $e) { + return null; + } + } + } + + + try { + $accountIdComponents = vtws_getIdComponents($entityIds['Accounts']); + $accountId = $accountIdComponents[1]; + + $contactIdComponents = vtws_getIdComponents($entityIds['Contacts']); + $contactId = $contactIdComponents[1]; + + if (!empty($accountId) && !empty($contactId) && !empty($entityIds['Potentials'])) { + $potentialIdComponents = vtws_getIdComponents($entityIds['Potentials']); + $potentialId = $potentialIdComponents[1]; + $sql = "insert into vtiger_contpotentialrel values(?,?)"; + $result = $adb->pquery($sql, array($contactId, $potentialIdComponents[1])); + if ($result === false) { + throw new WebServiceException(WebServiceErrorCode::$FAILED_TO_CREATE_RELATION, + "Failed to related Contact with the Potential"); + } + } + + $transfered = vtws_convertLeadTransferHandler($leadIdComponents, $entityIds, $entityvalues); + + $relatedIdComponents = vtws_getIdComponents($entityIds[$entityvalues['transferRelatedRecordsTo']]); + vtws_getRelatedActivities($leadIdComponents[1], $accountId, $contactId, $relatedIdComponents[1]); + vtws_updateConvertLeadStatus($entityIds, $entityvalues['leadId'], $user); + } catch (Exception $e) { + foreach ($entityIds as $entity => $id) { + vtws_delete($id, $user); + } + return null; + } + + return $entityIds; +} + +/* + * populate the entity fields with the lead info. + * if mandatory field is not provided populate with '????' + * returns the entity array. + */ + +function vtws_populateConvertLeadEntities($entityvalue, $entity, $entityHandler, $leadHandler, $leadinfo) { + global $adb, $log; + $column; + $entityName = $entityvalue['name']; + $sql = "SELECT * FROM vtiger_convertleadmapping"; + $result = $adb->pquery($sql, array()); + if ($adb->num_rows($result)) { + switch ($entityName) { + case 'Accounts':$column = 'accountfid'; + break; + case 'Contacts':$column = 'contactfid'; + break; + case 'Potentials':$column = 'potentialfid'; + break; + default:$column = 'leadfid'; + break; + } + + $leadFields = $leadHandler->getMeta()->getModuleFields(); + $entityFields = $entityHandler->getMeta()->getModuleFields(); + $row = $adb->fetch_array($result); + $count = 1; + do { + $entityField = vtws_getFieldfromFieldId($row[$column], $entityFields); + if ($entityField == null) { + //user doesn't have access so continue.TODO update even if user doesn't have access + continue; + } + $leadField = vtws_getFieldfromFieldId($row['leadfid'], $leadFields); + if ($leadField == null) { + //user doesn't have access so continue.TODO update even if user doesn't have access + continue; + } + $leadFieldName = $leadField->getFieldName(); + $entityFieldName = $entityField->getFieldName(); + $entity[$entityFieldName] = $leadinfo[$leadFieldName]; + $count++; + } while ($row = $adb->fetch_array($result)); + + foreach ($entityvalue as $fieldname => $fieldvalue) { + if (!empty($fieldvalue)) { + $entity[$fieldname] = $fieldvalue; + } + } + + $entity = vtws_validateConvertLeadEntityMandatoryValues($entity, $entityHandler, $leadinfo, $entityName); + } + return $entity; +} + +function vtws_validateConvertLeadEntityMandatoryValues($entity, $entityHandler, $leadinfo, $module) { + + $mandatoryFields = $entityHandler->getMeta()->getMandatoryFields(); + foreach ($mandatoryFields as $field) { + if (empty($entity[$field])) { + $fieldInfo = vtws_getConvertLeadFieldInfo($module, $field); + if (($fieldInfo['type']['name'] == 'picklist' || $fieldInfo['type']['name'] == 'multipicklist' + || $fieldInfo['type']['name'] == 'date' || $fieldInfo['type']['name'] == 'datetime') + && ($fieldInfo['editable'] == true)) { + $entity[$field] = $fieldInfo['default']; + } else { + $entity[$field] = '????'; + } + } + } + return $entity; +} + +function vtws_getConvertLeadFieldInfo($module, $fieldname) { + global $adb, $log, $current_user; + $describe = vtws_describe($module, $current_user); + foreach ($describe['fields'] as $index => $fieldInfo) { + if ($fieldInfo['name'] == $fieldname) { + return $fieldInfo; + } + } + return false; +} + +//function to handle the transferring of related records for lead +function vtws_convertLeadTransferHandler($leadIdComponents, $entityIds, $entityvalues) { + + try { + $entityidComponents = vtws_getIdComponents($entityIds[$entityvalues['transferRelatedRecordsTo']]); + vtws_transferLeadRelatedRecords($leadIdComponents[1], $entityidComponents[1], $entityvalues['transferRelatedRecordsTo']); + } catch (Exception $e) { + return false; + } + + return true; +} + +function vtws_updateConvertLeadStatus($entityIds, $leadId, $user) { + global $adb, $log; + $leadIdComponents = vtws_getIdComponents($leadId); + if ($entityIds['Accounts'] != '' || $entityIds['Contacts'] != '') { + $sql = "UPDATE vtiger_leaddetails SET converted = 1 where leadid=?"; + $result = $adb->pquery($sql, array($leadIdComponents[1])); + if ($result === false) { + throw new WebServiceException(WebServiceErrorCode::$FAILED_TO_MARK_CONVERTED, + "Failed mark lead converted"); + } + //updating the campaign-lead relation --Minnie + $sql = "DELETE FROM vtiger_campaignleadrel WHERE leadid=?"; + $adb->pquery($sql, array($leadIdComponents[1])); + + $sql = "DELETE FROM vtiger_tracker WHERE item_id=?"; + $adb->pquery($sql, array($leadIdComponents[1])); + + //update the modifiedtime and modified by information for the record + $leadModifiedTime = $adb->formatDate(date('Y-m-d H:i:s'), true); + $crmentityUpdateSql = "UPDATE vtiger_crmentity SET modifiedtime=?, modifiedby=? WHERE crmid=?"; + $adb->pquery($crmentityUpdateSql, array($leadModifiedTime, $user->id, $leadIdComponents[1])); + } +} + +?> diff --git a/include/Webservices/Create.php b/include/Webservices/Create.php new file mode 100644 index 0000000..394ddf7 --- /dev/null +++ b/include/Webservices/Create.php @@ -0,0 +1,85 @@ +getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject, $user, $adb, $log); + $meta = $handler->getMeta(); + if ($meta->hasWriteAccess() !== true) { + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Permission to write is denied"); + } + + $referenceFields = $meta->getReferenceFieldDetails(); + foreach ($referenceFields as $fieldName => $details) { + if (isset($element[$fieldName]) && strlen($element[$fieldName]) > 0) { + $ids = vtws_getIdComponents($element[$fieldName]); + $elemTypeId = $ids[0]; + $elemId = $ids[1]; + $referenceObject = VtigerWebserviceObject::fromId($adb, $elemTypeId); + if (!in_array($referenceObject->getEntityName(), $details)) { + throw new WebServiceException(WebServiceErrorCode::$REFERENCEINVALID, + "Invalid reference specified for $fieldName"); + } + if ($referenceObject->getEntityName() == 'Users') { + if(!$meta->hasAssignPrivilege($element[$fieldName])) { + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Cannot assign record to the given user"); + } + } + if (!in_array($referenceObject->getEntityName(), $types['types']) && $referenceObject->getEntityName() != 'Users') { + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, + "Permission to access reference type is denied" . $referenceObject->getEntityName()); + } + } else if ($element[$fieldName] !== NULL) { + unset($element[$fieldName]); + } + } + + + if ($meta->hasMandatoryFields($element)) { + + $ownerFields = $meta->getOwnerFields(); + if (is_array($ownerFields) && sizeof($ownerFields) > 0) { + foreach ($ownerFields as $ownerField) { + if (isset($element[$ownerField]) && $element[$ownerField] !== null && + !$meta->hasAssignPrivilege($element[$ownerField])) { + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Cannot assign record to the given user"); + } + } + } + $entity = $handler->create($elementType, $element); + VTWS_PreserveGlobal::flush(); + return $entity; + } else { + + return null; + } +} +?> \ No newline at end of file diff --git a/include/Webservices/DataTransform.php b/include/Webservices/DataTransform.php new file mode 100644 index 0000000..3487625 --- /dev/null +++ b/include/Webservices/DataTransform.php @@ -0,0 +1,277 @@ +getFieldColumnMapping(); + $columnFieldMapping = array_flip($fieldColumnMapping); + foreach($row as $col=>$val){ + if(array_key_exists($col,$columnFieldMapping)) + $newRow[$columnFieldMapping[$col]] = $val; + } + $newRow = DataTransform::sanitizeData($newRow,$meta,true); + return $newRow; + } + + function sanitizeDataWithCountColumn($row,$meta){ + $newRow = array(); + foreach($row as $col=>$val){ + $newRow['count'] = $val; + } + return $newRow; + } + + function filterAndSanitize($row,$meta){ + + $row = DataTransform::filterAllColumns($row,$meta); + $row = DataTransform::sanitizeData($row,$meta); + return $row; + } + + function sanitizeData($newRow,$meta,$t=null){ + + $newRow = DataTransform::sanitizeReferences($newRow,$meta); + $newRow = DataTransform::sanitizeOwnerFields($newRow,$meta,$t); + $newRow = DataTransform::sanitizeFields($newRow,$meta); + return $newRow; + } + + function sanitizeForInsert($row,$meta){ + global $adb; + $associatedToUser = false; + $parentTypeId = null; + if(strtolower($meta->getEntityName()) == "emails"){ + if(isset($row['parent_id'])){ + $components = vtws_getIdComponents($row['parent_id']); + $userObj = VtigerWebserviceObject::fromName($adb,'Users'); + $parentTypeId = $components[0]; + if($components[0] == $userObj->getEntityId()){ + $associatedToUser = true; + } + } + } + // added to handle the setting reminder time + if(strtolower($meta->getEntityName()) == "events"){ + if(isset($row['reminder_time'])&& $row['reminder_time']!= null && $row['reminder_time'] != 0){ + $_REQUEST['set_reminder'] = "Yes"; + $_REQUEST['mode'] = 'edit'; + + $reminder = $row['reminder_time']; + $seconds = (int)$reminder%60; + $minutes = (int)($reminder/60)%60; + $hours = (int)($reminder/(60*60))%24; + $days = (int)($reminder/(60*60*24)); + + //at vtiger there cant be 0 minutes reminder so we are setting to 1 + if($minutes == 0){ + $minutes = 1; + } + + $_REQUEST['remmin'] = $minutes; + $_REQUEST['remhrs'] = $hours; + $_REQUEST['remdays'] = $days; + } else { + $_REQUEST['set_reminder'] = "No"; + } + } elseif(strtolower($meta->getEntityName()) == "calendar") { + if(empty($row['sendnotification']) || strtolower($row['sendnotificaiton'])=='no' + || $row['sendnotificaiton'] == '0' || $row['sendnotificaiton'] == 'false' + || strtolower($row['sendnotificaiton']) == 'n') { + unset($row['sendnotification']); + } + } + $references = $meta->getReferenceFieldDetails(); + foreach($references as $field=>$typeList){ + if(strpos($row[$field],'x')!==false){ + $row[$field] = vtws_getIdComponents($row[$field]); + $row[$field] = $row[$field][1]; + } + } + $ownerFields = $meta->getOwnerFields(); + foreach($ownerFields as $index=>$field){ + if(isset($row[$field]) && $row[$field]!=null){ + $ownerDetails = vtws_getIdComponents($row[$field]); + $row[$field] = $ownerDetails[1]; + } + } + if(strtolower($meta->getEntityName()) == "emails"){ + if(isset($row['parent_id'])){ + if($associatedToUser === true){ + $_REQUEST['module'] = 'Emails'; + $row['parent_id'] = $row['parent_id']."@-1|"; + $_REQUEST['parent_id'] = $row['parent_id']; + }else{ + $referenceHandler = vtws_getModuleHandlerFromId($parentTypeId, + $meta->getUser()); + $referenceMeta = $referenceHandler->getMeta(); + $fieldId = getEmailFieldId($referenceMeta, $row['parent_id']); + $row['parent_id'] .= "@$fieldId|"; + } + } + } + if($row["id"]){ + unset($row["id"]); + } + if(isset($row[$meta->getObectIndexColumn()])){ + unset($row[$meta->getObectIndexColumn()]); + } + + $row = DataTransform::sanitizeDateFieldsForInsert($row,$meta); + $row = DataTransform::sanitizeCurrencyFieldsForInsert($row,$meta); + + return $row; + + } + + function filterAllColumns($row,$meta){ + + $recordString = DataTransform::$recordString; + + $allFields = $meta->getFieldColumnMapping(); + $newRow = array(); + foreach($allFields as $field=>$col){ + $newRow[$field] = $row[$field]; + } + if(isset($row[$recordString])){ + $newRow[$recordString] = $row[$recordString]; + } + return $newRow; + + } + + function sanitizeFields($row,$meta){ + $default_charset = VTWS_PreserveGlobal::getGlobal('default_charset'); + $recordString = DataTransform::$recordString; + + $recordModuleString = DataTransform::$recordModuleString; + + if(isset($row[$recordModuleString])){ + unset($row[$recordModuleString]); + } + + if(isset($row['id'])){ + if(strpos($row['id'],'x')===false){ + $row['id'] = vtws_getId($meta->getEntityId(),$row['id']); + } + } + + if(isset($row[$recordString])){ + $row['id'] = vtws_getId($meta->getEntityId(),$row[$recordString]); + unset($row[$recordString]); + } + + if(!isset($row['id'])){ + if($row[$meta->getObectIndexColumn()] ){ + $row['id'] = vtws_getId($meta->getEntityId(),$row[$meta->getObectIndexColumn()]); + }else{ + //TODO Handle this. + //echo 'error id noy set' ; + } + }else if(isset($row[$meta->getObectIndexColumn()]) && strcmp($meta->getObectIndexColumn(),"id")!==0){ + unset($row[$meta->getObectIndexColumn()]); + } + + foreach ($row as $field => $value) { + $row[$field] = html_entity_decode($value, ENT_QUOTES, $default_charset); + } + return $row; + } + + function sanitizeReferences($row,$meta){ + global $adb,$log; + $references = $meta->getReferenceFieldDetails(); + foreach($references as $field=>$typeList){ + if(strtolower($meta->getEntityName()) == "emails"){ + if(isset($row['parent_id'])){ + list($row['parent_id'], $fieldId) = explode('@', + $row['parent_id']); + } + } + if($row[$field]){ + $found = false; + foreach ($typeList as $entity) { + $webserviceObject = VtigerWebserviceObject::fromName($adb,$entity); + $handlerPath = $webserviceObject->getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$meta->getUser(),$adb,$log); + $entityMeta = $handler->getMeta(); + if($entityMeta->exists($row[$field])){ + $row[$field] = vtws_getId($webserviceObject->getEntityId(),$row[$field]); + $found = true; + break; + } + } + if($found !== true){ + //This is needed as for query operation of the related record is deleted. + $row[$field] = null; + } + //0 is the default for most of the reference fields, so handle the case and return null instead as its the + //only valid value, which is not a reference Id. + }elseif(isset($row[$field]) && $row[$field]==0){ + $row[$field] = null; + } + } + return $row; + } + + function sanitizeOwnerFields($row,$meta,$t=null){ + global $adb; + $ownerFields = $meta->getOwnerFields(); + foreach($ownerFields as $index=>$field){ + if(isset($row[$field]) && $row[$field]!=null){ + $ownerType = vtws_getOwnerType($row[$field]); + $webserviceObject = VtigerWebserviceObject::fromName($adb,$ownerType); + $row[$field] = vtws_getId($webserviceObject->getEntityId(),$row[$field]); + } + } + return $row; + } + + function sanitizeDateFieldsForInsert($row,$meta){ + global $current_user; + $moduleFields = $meta->getModuleFields(); + foreach($moduleFields as $fieldName=>$fieldObj){ + if($fieldObj->getFieldDataType()=="date"){ + if(!empty($row[$fieldName])){ + $dateFieldObj = new DateTimeField($row[$fieldName]); + $row[$fieldName] = $dateFieldObj->getDisplayDate($current_user); + } + } + } + return $row; + } + + function sanitizeCurrencyFieldsForInsert($row,$meta){ + global $current_user; + $moduleFields = $meta->getModuleFields(); + foreach($moduleFields as $fieldName=>$fieldObj){ + if($fieldObj->getFieldDataType()=="currency"){ + if(!empty($row[$fieldName])){ + $row[$fieldName] = CurrencyField::convertToUserFormat($row[$fieldName],$current_user,true); + } + } + } + return $row; + } + } + +?> diff --git a/include/Webservices/Delete.php b/include/Webservices/Delete.php new file mode 100644 index 0000000..b4e2d90 --- /dev/null +++ b/include/Webservices/Delete.php @@ -0,0 +1,49 @@ +getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + $meta = $handler->getMeta(); + $entityName = $meta->getObjectEntityName($id); + + $types = vtws_listtypes(null, $user); + if(!in_array($entityName,$types['types'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied"); + } + + if($entityName !== $webserviceObject->getEntityName()){ + throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Id specified is incorrect"); + } + + if(!$meta->hasPermission(EntityMeta::$DELETE,$id)){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to read given object is denied"); + } + + $idComponents = vtws_getIdComponents($id); + if(!$meta->exists($idComponents[1])){ + throw new WebServiceException(WebServiceErrorCode::$RECORDNOTFOUND,"Record you are trying to access is not found"); + } + + if($meta->hasDeleteAccess()!==true){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to delete is denied"); + } + $entity = $handler->delete($id); + VTWS_PreserveGlobal::flush(); + return $entity; + } + +?> \ No newline at end of file diff --git a/include/Webservices/DeleteUser.php b/include/Webservices/DeleteUser.php new file mode 100644 index 0000000..c19378b --- /dev/null +++ b/include/Webservices/DeleteUser.php @@ -0,0 +1,70 @@ +getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + $meta = $handler->getMeta(); + $entityName = $meta->getObjectEntityName($id); + + $types = vtws_listtypes($user); + if(!in_array($entityName,$types['types'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, + "Permission to perform the operation is denied, EntityName = ".$entityName); + } + + if($entityName !== $webserviceObject->getEntityName()){ + throw new WebServiceException(WebServiceErrorCode::$INVALIDID, + "Id specified is incorrect"); + } + + if(!$meta->hasPermission(EntityMeta::$DELETE,$id)){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, + "Permission to read given object is denied"); + } + + $idComponents = vtws_getIdComponents($id); + if(!$meta->exists($idComponents[1])){ + throw new WebServiceException(WebServiceErrorCode::$RECORDNOTFOUND, + "Record you are trying to access is not found, idComponent = ".$idComponents); + } + + if($meta->hasWriteAccess()!==true){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, + "Permission to write is denied"); + } + + $newIdComponents = vtws_getIdComponents($newOwnerId); + if(empty($newIdComponents[1])) { + //force the default user to be the default admin user. + //added cause eazybusiness team is sending this value empty + $newIdComponents[1] = 1; + } + vtws_transferOwnership($idComponents[1], $newIdComponents[1]); + //delete from user vtiger_table; + $sql = "delete from vtiger_users where id=?"; + vtws_runQueryAsTransaction($sql, array($idComponents[1]), $result); + + VTWS_PreserveGlobal::flush(); + return array("status"=>"successful"); + } + +?> diff --git a/include/Webservices/DescribeObject.php b/include/Webservices/DescribeObject.php new file mode 100644 index 0000000..2de2d89 --- /dev/null +++ b/include/Webservices/DescribeObject.php @@ -0,0 +1,33 @@ +getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + $meta = $handler->getMeta(); + + $types = vtws_listtypes(null, $user); + if(!in_array($elementType,$types['types'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied"); + } + + $entity = $handler->describe($elementType); + VTWS_PreserveGlobal::flush(); + return $entity; + } + +?> \ No newline at end of file diff --git a/include/Webservices/EntityMeta.php b/include/Webservices/EntityMeta.php new file mode 100644 index 0000000..16d9b98 --- /dev/null +++ b/include/Webservices/EntityMeta.php @@ -0,0 +1,258 @@ +webserviceObject = $webserviceObject; + $this->objectName = $this->webserviceObject->getEntityName(); + $this->objectId = $this->webserviceObject->getEntityId(); + + $this->user = $user; + } + + public function getEmailFields(){ + if($this->emailFields === null){ + $this->emailFields = array(); + foreach ($this->moduleFields as $fieldName=>$webserviceField) { + if(strcasecmp($webserviceField->getFieldType(),'e') === 0){ + array_push($this->emailFields, $fieldName); + } + } + } + + return $this->emailFields; + } + + public function getFieldColumnMapping(){ + if($this->fieldColumnMapping === null){ + $this->fieldColumnMapping = array(); + foreach ($this->moduleFields as $fieldName=>$webserviceField) { + $this->fieldColumnMapping[$fieldName] = $webserviceField->getColumnName(); + } + $this->fieldColumnMapping['id'] = $this->idColumn; + } + return $this->fieldColumnMapping; + } + + public function getMandatoryFields(){ + if($this->mandatoryFields === null){ + $this->mandatoryFields = array(); + foreach ($this->moduleFields as $fieldName=>$webserviceField) { + if($webserviceField->isMandatory() === true){ + array_push($this->mandatoryFields,$fieldName); + } + } + } + return $this->mandatoryFields; + } + + public function getReferenceFieldDetails(){ + if($this->referenceFieldDetails === null){ + $this->referenceFieldDetails = array(); + foreach ($this->moduleFields as $fieldName=>$webserviceField) { + if(strcasecmp($webserviceField->getFieldDataType(),'reference') === 0){ + $this->referenceFieldDetails[$fieldName] = $webserviceField->getReferenceList(); + } + } + } + return $this->referenceFieldDetails; + } + + public function getOwnerFields(){ + if($this->ownerFields === null){ + $this->ownerFields = array(); + foreach ($this->moduleFields as $fieldName=>$webserviceField) { + if(strcasecmp($webserviceField->getFieldDataType(),'owner') === 0){ + array_push($this->ownerFields, $fieldName); + } + } + } + return $this->ownerFields; + } + + public function getObectIndexColumn(){ + return $this->idColumn; + } + + public function getUserAccessibleColumns(){ + if($this->userAccessibleColumns === null){ + $this->userAccessibleColumns = array(); + foreach ($this->moduleFields as $fieldName=>$webserviceField) { + array_push($this->userAccessibleColumns,$webserviceField->getColumnName()); + } + array_push($this->userAccessibleColumns,$this->idColumn); + } + return $this->userAccessibleColumns; + } + + public function getFieldByColumnName($column){ + $fields = $this->getModuleFields(); + foreach ($fields as $fieldName=>$webserviceField) { + if($column == $webserviceField->getColumnName()) { + return $webserviceField; + } + } + return null; + } + + public function getColumnTableMapping(){ + if($this->columnTableMapping === null){ + $this->columnTableMapping = array(); + foreach ($this->moduleFields as $fieldName=>$webserviceField) { + $this->columnTableMapping[$webserviceField->getColumnName()] = $webserviceField->getTableName(); + } + $this->columnTableMapping[$this->idColumn] = $this->baseTable; + } + return $this->columnTableMapping; + } + + function getUser(){ + return $this->user; + } + + function hasMandatoryFields($row){ + + $mandatoryFields = $this->getMandatoryFields(); + $hasMandatory = true; + foreach($mandatoryFields as $ind=>$field){ + // dont use empty API as '0'(zero) is a valid value. + if( !isset($row[$field]) || $row[$field] === "" || $row[$field] === null ){ + throw new WebServiceException(WebServiceErrorCode::$MANDFIELDSMISSING, + "$field does not have a value"); + } + } + return $hasMandatory; + + } + public function isUpdateMandatoryFields($element){ + if(!is_array($element)){ + throw new WebServiceException(WebServiceErrorCode::$MANDFIELDSMISSING, + "Mandatory field does not have a value"); + } + $mandatoryFields = $this->getMandatoryFields(); + $updateFields = array_keys($element); + $hasMandatory = true; + $updateMandatoryFields = array_intersect($updateFields, $mandatoryFields); + if(!empty($updateMandatoryFields)){ + foreach($updateMandatoryFields as $ind=>$field){ + // dont use empty API as '0'(zero) is a valid value. + if( !isset($element[$field]) || $element[$field] === "" || $element[$field] === null ){ + throw new WebServiceException(WebServiceErrorCode::$MANDFIELDSMISSING, + "$field does not have a value"); + } + } + } + return $hasMandatory; + } + + public function getModuleFields(){ + return $this->moduleFields; + } + + public function getFieldNameListByType($type) { + $type = strtolower($type); + $typeList = array(); + $moduleFields = $this->getModuleFields(); + foreach ($moduleFields as $fieldName=>$webserviceField) { + if(strcmp($webserviceField->getFieldDataType(),$type) === 0){ + array_push($typeList, $fieldName); + } + } + return $typeList; + } + + public function getFieldListByType($type) { + $type = strtolower($type); + $typeList = array(); + $moduleFields = $this->getModuleFields(); + foreach ($moduleFields as $fieldName=>$webserviceField) { + if(strcmp($webserviceField->getFieldDataType(),$type) === 0){ + array_push($typeList, $webserviceField); + } + } + return $typeList; + } + + public function getIdColumn(){ + return $this->idColumn; + } + + public function getEntityBaseTable() { + return $this->baseTable; + } + + public function getEntityTableIndexList() { + return $this->tableIndexList; + } + + public function getEntityDefaultTableList() { + return $this->defaultTableList; + } + + public function getEntityTableList() { + return $this->tableList; + } + + public function getEntityAccessControlQuery(){ + $accessControlQuery = ''; + return $accessControlQuery; + } + + public function getEntityDeletedQuery(){ + if($this->getEntityName() == 'Leads') { + return "vtiger_crmentity.deleted=0 and vtiger_leaddetails.converted=0"; + } + if($this->getEntityName() != "Users"){ + return "vtiger_crmentity.deleted=0"; + } + // not sure whether inactive users should be considered deleted or not. + return "vtiger_users.status='Active'"; + } + + abstract function hasPermission($operation,$webserviceId); + abstract function hasAssignPrivilege($ownerWebserviceId); + abstract function hasDeleteAccess(); + abstract function hasAccess(); + abstract function hasReadAccess(); + abstract function hasWriteAccess(); + abstract function getEntityName(); + abstract function getEntityId(); + abstract function exists($recordId); + abstract function getObjectEntityName($webserviceId); + abstract public function getNameFields(); + abstract public function getName($webserviceId); + abstract public function isModuleEntity(); +} +?> \ No newline at end of file diff --git a/include/Webservices/ExtendSession.php b/include/Webservices/ExtendSession.php new file mode 100644 index 0000000..4c44ef6 --- /dev/null +++ b/include/Webservices/ExtendSession.php @@ -0,0 +1,26 @@ +set("authenticatedUserId", $userId); + $crmObject = VtigerWebserviceObject::fromName($adb,"Users"); + $userId = vtws_getId($crmObject->getEntityId(),$userId); + $vtigerVersion = vtws_getVtigerVersion(); + $resp = array("sessionName"=>$sessionManager->getSessionId(),"userId"=>$userId,"version"=>$API_VERSION,"vtigerVersion"=>$vtigerVersion); + return $resp; + }else{ + throw new WebServiceException(WebServiceErrorCode::$AUTHFAILURE,"Authencation Failed"); + } + } +?> \ No newline at end of file diff --git a/include/Webservices/GetUpdates.php b/include/Webservices/GetUpdates.php new file mode 100644 index 0000000..0577af3 --- /dev/null +++ b/include/Webservices/GetUpdates.php @@ -0,0 +1,282 @@ +id); + + if(!isset($elementType) || $elementType=='' || $elementType==null){ + $typed=false; + } + + + + $adb->startTransaction(); + + $accessableModules = array(); + $entityModules = array(); + $modulesDetails = vtws_listtypes(null,$user); + $moduleTypes = $modulesDetails['types']; + $modulesInformation = $modulesDetails["information"]; + + foreach($modulesInformation as $moduleName=>$entityInformation){ + if($entityInformation["isEntity"]) + $entityModules[] = $moduleName; + } + if(!$typed){ + $accessableModules = $entityModules; + } + else{ + if(!in_array($elementType,$entityModules)) + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied"); + $accessableModules[] = $elementType; + } + + $accessableModules = array_diff($accessableModules,$ignoreModules); + + if(count($accessableModules)<=0) + { + $output['lastModifiedTime'] = $mtime; + $output['more'] = false; + return $output; + } + + if($typed){ + $handler = vtws_getModuleHandlerFromName($elementType, $user); + $moduleMeta = $handler->getMeta(); + $entityDefaultBaseTables = $moduleMeta->getEntityDefaultTableList(); + //since there will be only one base table for all entities + $baseCRMTable = $entityDefaultBaseTables[0]; + if($elementType=="Calendar" || $elementType=="Events" ){ + $baseCRMTable = getSyncQueryBaseTable($elementType); + } + } + else + $baseCRMTable = " vtiger_crmentity "; + + //modifiedtime - next token + $q = "SELECT modifiedtime FROM $baseCRMTable WHERE modifiedtime>? and setype IN(".generateQuestionMarks($accessableModules).") "; + $params = array($datetime); + foreach($accessableModules as $entityModule){ + if($entityModule == "Events") + $entityModule = "Calendar"; + $params[] = $entityModule; + } + if(!$applicationSync){ + $q .= ' and smownerid IN('.generateQuestionMarks($ownerIds).')'; + $params = array_merge($params,$ownerIds); + } + + $q .=" order by modifiedtime limit $numRecordsLimit"; + $result = $adb->pquery($q,$params); + + $modTime = array(); + for($i=0;$i<$adb->num_rows($result);$i++){ + $modTime[] = $adb->query_result($result,$i,'modifiedtime'); + } + if(!empty($modTime)){ + $maxModifiedTime = max($modTime); + } + if(!$maxModifiedTime){ + $maxModifiedTime = $datetime; + } + + + + foreach($accessableModules as $elementType){ + $handler = vtws_getModuleHandlerFromName($elementType, $user); + $moduleMeta = $handler->getMeta(); + $deletedQueryCondition = $moduleMeta->getEntityDeletedQuery(); + preg_match_all("/(?:\s+\w+[ \t\n\r]+)?([^=]+)\s*=([^\s]+|'[^']+')/",$deletedQueryCondition,$deletedFieldDetails); + $fieldNameDetails = $deletedFieldDetails[1]; + $deleteFieldValues = $deletedFieldDetails[2]; + $deleteColumnNames = array(); + foreach($fieldNameDetails as $tableName_fieldName){ + $fieldComp = explode(".",$tableName_fieldName); + $deleteColumnNames[$tableName_fieldName] = $fieldComp[1]; + } + $params = array($moduleMeta->getTabName(),$datetime,$maxModifiedTime); + + + $queryGenerator = new QueryGenerator($elementType, $user); + $fields = array(); + $moduleFeilds = $moduleMeta->getModuleFields(); + $moduleFeildNames = array_keys($moduleFeilds); + $moduleFeildNames[]='id'; + $queryGenerator->setFields($moduleFeildNames); + $selectClause = "SELECT ".$queryGenerator->getSelectClauseColumnSQL(); + // adding the fieldnames that are present in the delete condition to the select clause + // since not all fields present in delete condition will be present in the fieldnames of the module + foreach($deleteColumnNames as $table_fieldName=>$columnName){ + if(!in_array($columnName,$moduleFeildNames)){ + $selectClause .=", ".$table_fieldName; + } + } + if($elementType=="Emails") + $fromClause = vtws_getEmailFromClause(); + else + $fromClause = $queryGenerator->getFromClause(); + $fromClause .= " INNER JOIN (select modifiedtime, crmid,deleted,setype FROM $baseCRMTable WHERE setype=? and modifiedtime >? and modifiedtime<=?"; + if(!$applicationSync){ + $fromClause.= 'and smownerid IN('.generateQuestionMarks($ownerIds).')'; + $params = array_merge($params,$ownerIds); + } + $fromClause.= ' ) vtiger_ws_sync ON (vtiger_crmentity.crmid = vtiger_ws_sync.crmid)'; + $q = $selectClause." ".$fromClause; + $result = $adb->pquery($q, $params); + $recordDetails = array(); + $deleteRecordDetails = array(); + while($arre = $adb->fetchByAssoc($result)){ + $key = $arre[$moduleMeta->getIdColumn()]; + if(vtws_isRecordDeleted($arre,$deleteColumnNames,$deleteFieldValues)){ + if(!$moduleMeta->hasAccess()){ + continue; + } + $output["deleted"][] = vtws_getId($moduleMeta->getEntityId(), $key); + } + else{ + if(!$moduleMeta->hasAccess() ||!$moduleMeta->hasPermission(EntityMeta::$RETRIEVE,$key)){ + continue; + } + try{ + $output["updated"][] = DataTransform::sanitizeDataWithColumn($arre,$moduleMeta);; + }catch(WebServiceException $e){ + //ignore records the user doesn't have access to. + continue; + }catch(Exception $e){ + throw new WebServiceException(WebServiceErrorCode::$INTERNALERROR,"Unknown Error while processing request"); + } + } + } + } + + $q = "SELECT crmid FROM $baseCRMTable WHERE modifiedtime>? and setype IN(".generateQuestionMarks($accessableModules).")"; + $params = array($maxModifiedTime); + + foreach($accessableModules as $entityModule){ + if($entityModule == "Events") + $entityModule = "Calendar"; + $params[] = $entityModule; + } + if(!$applicationSync){ + $q.='and smownerid IN('.generateQuestionMarks($ownerIds).')'; + $params = array_merge($params,$ownerIds); + } + + $result = $adb->pquery($q,$params); + if($adb->num_rows($result)>0){ + $output['more'] = true; + } + else{ + $output['more'] = false; + } + if(!$maxModifiedTime){ + $modifiedtime = $mtime; + }else{ + $modifiedtime = vtws_getSeconds($maxModifiedTime); + } + if(is_string($modifiedtime)){ + $modifiedtime = intval($modifiedtime); + } + $output['lastModifiedTime'] = $modifiedtime; + + $error = $adb->hasFailedTransaction(); + $adb->completeTransaction(); + + if($error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + VTWS_PreserveGlobal::flush(); + return $output; + } + + function vtws_getSeconds($mtimeString){ + //TODO handle timezone and change time to gmt. + return strtotime($mtimeString); + } + + function vtws_isRecordDeleted($recordDetails,$deleteColumnDetails,$deletedValues){ + $deletedRecord = false; + $i=0; + foreach($deleteColumnDetails as $tableName_fieldName=>$columnName){ + if($recordDetails[$columnName]!=$deletedValues[$i++]){ + $deletedRecord = true; + break; + } + } + return $deletedRecord; + } + + function vtws_getEmailFromClause(){ + $q = "FROM vtiger_activity + INNER JOIN vtiger_crmentity ON vtiger_activity.activityid = vtiger_crmentity.crmid + LEFT JOIN vtiger_users ON vtiger_crmentity.smownerid = vtiger_users.id + LEFT JOIN vtiger_groups ON vtiger_crmentity.smownerid = vtiger_groups.groupid + LEFT JOIN vtiger_seattachmentsrel ON vtiger_activity.activityid = vtiger_seattachmentsrel.crmid + LEFT JOIN vtiger_attachments ON vtiger_seattachmentsrel.attachmentsid = vtiger_attachments.attachmentsid + LEFT JOIN vtiger_email_track ON vtiger_activity.activityid = vtiger_email_track.mailid + INNER JOIN vtiger_emaildetails ON vtiger_activity.activityid = vtiger_emaildetails.emailid + LEFT JOIN vtiger_users vtiger_users2 ON vtiger_emaildetails.idlists = vtiger_users2.id + LEFT JOIN vtiger_groups vtiger_groups2 ON vtiger_emaildetails.idlists = vtiger_groups2.groupid"; + return $q; + } + + function getSyncQueryBaseTable($elementType){ + if($elementType!="Calendar" && $elementType!="Events"){ + return "vtiger_crmentity"; + } + else{ + $activityCondition = getCalendarTypeCondition($elementType); + $query = "vtiger_crmentity INNER JOIN vtiger_activity ON (vtiger_crmentity.crmid = vtiger_activity.activityid and $activityCondition)"; + return $query; + } + } + + function getCalendarTypeCondition($elementType){ + if($elementType == "Events") + $activityCondition = "vtiger_activity.activitytype !='Task' and vtiger_activity.activitytype !='Emails'"; + else + $activityCondition = "vtiger_activity.activitytype ='Task'"; + return $activityCondition; + } + +?> diff --git a/include/Webservices/Login.php b/include/Webservices/Login.php new file mode 100644 index 0000000..dd20363 --- /dev/null +++ b/include/Webservices/Login.php @@ -0,0 +1,63 @@ +retrieve_user_id($username); + + $token = vtws_getActiveToken($userId); + if($token == null){ + throw new WebServiceException(WebServiceErrorCode::$INVALIDTOKEN,"Specified token is invalid or expired"); + } + + $accessKey = vtws_getUserAccessKey($userId); + if($accessKey == null){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSKEYUNDEFINED,"Access key for the user is undefined"); + } + + $accessCrypt = md5($token.$accessKey); + if(strcmp($accessCrypt,$pwd)!==0){ + throw new WebServiceException(WebServiceErrorCode::$INVALIDUSERPWD,"Invalid username or password"); + } + $user = $user->retrieveCurrentUserInfoFromFile($userId); + if($user->status != 'Inactive'){ + return $user; + } + throw new WebServiceException(WebServiceErrorCode::$AUTHREQUIRED,'Given user is inactive'); + } + + function vtws_getActiveToken($userId){ + global $adb; + + $sql = "select * from vtiger_ws_userauthtoken where userid=? and expiretime >= ?"; + $result = $adb->pquery($sql,array($userId,time())); + if($result != null && isset($result)){ + if($adb->num_rows($result)>0){ + return $adb->query_result($result,0,"token"); + } + } + return null; + } + + function vtws_getUserAccessKey($userId){ + global $adb; + + $sql = "select * from vtiger_users where id=?"; + $result = $adb->pquery($sql,array($userId)); + if($result != null && isset($result)){ + if($adb->num_rows($result)>0){ + return $adb->query_result($result,0,"accesskey"); + } + } + return null; + } + +?> diff --git a/include/Webservices/Logout.php b/include/Webservices/Logout.php new file mode 100644 index 0000000..15e9193 --- /dev/null +++ b/include/Webservices/Logout.php @@ -0,0 +1,24 @@ +startSession($sessionId); + + if(!isset($sessionId) || !$sessionManager->isValid()){ + return $sessionManager->getError(); + } + + $sessionManager->destroy(); +// $sessionManager->setExpire(1); + return array("message"=>"successfull"); + +} +?> diff --git a/include/Webservices/ModuleTypes.php b/include/Webservices/ModuleTypes.php new file mode 100644 index 0000000..c332ef8 --- /dev/null +++ b/include/Webservices/ModuleTypes.php @@ -0,0 +1,136 @@ +id][$fieldTypeString])) { + return $types[$user->id][$fieldTypeString]; + } + try{ + global $log; + /** + * @var PearDatabase + */ + $db = PearDatabase::getInstance(); + + vtws_preserveGlobal('current_user',$user); + //get All the modules the current user is permitted to Access. + $allModuleNames = getPermittedModuleNames(); + if(array_search('Calendar',$allModuleNames) !== false){ + array_push($allModuleNames,'Events'); + } + + if(!empty($fieldTypeList)) { + $sql = "SELECT distinct(vtiger_field.tabid) as tabid FROM vtiger_field LEFT JOIN vtiger_ws_fieldtype ON ". + "vtiger_field.uitype=vtiger_ws_fieldtype.uitype + INNER JOIN vtiger_profile2field ON vtiger_field.fieldid = vtiger_profile2field.fieldid + INNER JOIN vtiger_def_org_field ON vtiger_def_org_field.fieldid = vtiger_field.fieldid + INNER JOIN vtiger_role2profile ON vtiger_profile2field.profileid = vtiger_role2profile.profileid + INNER JOIN vtiger_user2role ON vtiger_user2role.roleid = vtiger_role2profile.roleid + where vtiger_profile2field.visible=0 and vtiger_def_org_field.visible = 0 + and vtiger_field.presence in (0,2) + and vtiger_user2role.userid=? and fieldtype in (". + generateQuestionMarks($fieldTypeList).')'; + $params = array(); + $params[] = $user->id; + foreach($fieldTypeList as $fieldType) + $params[] = $fieldType; + $result = $db->pquery($sql, $params); + $it = new SqlResultIterator($db, $result); + $moduleList = array(); + foreach ($it as $row) { + $moduleList[] = getTabModuleName($row->tabid); + } + $allModuleNames = array_intersect($moduleList, $allModuleNames); + + $params = $fieldTypeList; + + $sql = "select name from vtiger_ws_entity inner join vtiger_ws_entity_tables on ". + "vtiger_ws_entity.id=vtiger_ws_entity_tables.webservice_entity_id inner join ". + "vtiger_ws_entity_fieldtype on vtiger_ws_entity_fieldtype.table_name=". + "vtiger_ws_entity_tables.table_name where fieldtype=(". + generateQuestionMarks($fieldTypeList).')'; + $result = $db->pquery($sql, $params); + $it = new SqlResultIterator($db, $result); + $entityList = array(); + foreach ($it as $row) { + $entityList[] = $row->name; + } + } + //get All the CRM entity names. + if($webserviceEntities === false || !CRMEntity::isBulkSaveMode()) { + // Bulk Save Mode: For re-using information + $webserviceEntities = vtws_getWebserviceEntities(); + } + + $accessibleModules = array_values(array_intersect($webserviceEntities['module'],$allModuleNames)); + $entities = $webserviceEntities['entity']; + $accessibleEntities = array(); + if(empty($fieldTypeList)) { + foreach($entities as $entity){ + $webserviceObject = VtigerWebserviceObject::fromName($db,$entity); + $handlerPath = $webserviceObject->getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + $handler = new $handlerClass($webserviceObject,$user,$db,$log); + $meta = $handler->getMeta(); + if($meta->hasAccess()===true){ + array_push($accessibleEntities,$entity); + } + } + } + }catch(WebServiceException $exception){ + throw $exception; + }catch(Exception $exception){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + "An Database error occured while performing the operation"); + } + + $default_language = VTWS_PreserveGlobal::getGlobal('default_language'); + $current_language = vtws_preserveGlobal('current_language',$default_language); + + $appStrings = return_application_language($current_language); + $appListString = return_app_list_strings_language($current_language); + vtws_preserveGlobal('app_strings',$appStrings); + vtws_preserveGlobal('app_list_strings',$appListString); + + $informationArray = array(); + foreach ($accessibleModules as $module) { + $vtigerModule = ($module == 'Events')? 'Calendar':$module; + $informationArray[$module] = array('isEntity'=>true,'label'=>getTranslatedString($module,$vtigerModule), + 'singular'=>getTranslatedString('SINGLE_'.$module,$vtigerModule)); + } + + foreach ($accessibleEntities as $entity) { + $label = (isset($appStrings[$entity]))? $appStrings[$entity]:$entity; + $singular = (isset($appStrings['SINGLE_'.$entity]))? $appStrings['SINGLE_'.$entity]:$entity; + $informationArray[$entity] = array('isEntity'=>false,'label'=>$label, + 'singular'=>$singular); + } + + VTWS_PreserveGlobal::flush(); + $types[$user->id][$fieldTypeString] = array("types"=>array_merge($accessibleModules,$accessibleEntities), + 'information'=>$informationArray); + return $types[$user->id][$fieldTypeString]; + } + +?> \ No newline at end of file diff --git a/include/Webservices/OperationManager.php b/include/Webservices/OperationManager.php new file mode 100644 index 0000000..bf0b742 --- /dev/null +++ b/include/Webservices/OperationManager.php @@ -0,0 +1,181 @@ +useBuiltinEncoderDecoder = true; + } + + class OperationManager{ + private $format; + private $formatsData=array( + "json"=>array( + "includePath"=>"include/Zend/Json.php", + "class"=>"Zend_Json", + "encodeMethod"=>"encode", + "decodeMethod"=>"decode", + "postCreate"=>"setBuiltIn" + ) + ); + private $operationMeta = null; + private $formatObjects ; + private $inParamProcess ; + private $sessionManager; + private $pearDB; + private $operationName; + private $type; + private $handlerPath; + private $handlerMethod; + private $preLogin; + private $operationId; + private $operationParams; + + function OperationManager($adb,$operationName,$format, $sessionManager){ + + $this->format = strtolower($format); + $this->sessionManager = $sessionManager; + $this->formatObjects = array(); + + foreach($this->formatsData as $frmt=>$frmtData){ + require_once($frmtData["includePath"]); + $instance = new $frmtData["class"](); + $this->formatObjects[$frmt]["encode"] = array(&$instance,$frmtData["encodeMethod"]); + $this->formatObjects[$frmt]["decode"] = array(&$instance,$frmtData["decodeMethod"]); + if($frmtData["postCreate"]){ + call_user_func($frmtData["postCreate"],$instance); + } + } + + $this->pearDB = $adb; + $this->operationName = $operationName; + $this->inParamProcess = array(); + $this->inParamProcess["encoded"] = &$this->formatObjects[$this->format]["decode"]; + $this->fillOperationDetails($operationName); + } + + function isPreLoginOperation(){ + return $this->preLogin == 1; + } + + private function fillOperationDetails($operationName){ + $sql = "select * from vtiger_ws_operation where name=?"; + $result = $this->pearDB->pquery($sql,array($operationName)); + if($result){ + $rowCount = $this->pearDB->num_rows($result); + if($rowCount > 0){ + $row = $this->pearDB->query_result_rowdata($result,0); + $this->type = $row['type']; + $this->handlerMethod = $row['handler_method']; + $this->handlerPath = $row['handler_path']; + $this->preLogin = $row['prelogin']; + $this->operationName = $row['name']; + $this->operationId = $row['operationid']; + $this->fillOperationParameters(); + return; + } + } + throw new WebServiceException(WebServiceErrorCode::$UNKNOWNOPERATION,"Unknown operation requested"); + } + + private function fillOperationParameters(){ + $sql = "select * from vtiger_ws_operation_parameters where operationid=? order by sequence"; + $result = $this->pearDB->pquery($sql,array($this->operationId)); + $this->operationParams = array(); + if($result){ + $rowCount = $this->pearDB->num_rows($result); + if($rowCount > 0){ + for ($i=0;$i<$rowCount;++$i){ + $row = $this->pearDB->query_result_rowdata($result,$i); + array_push($this->operationParams,array($row['name']=>$row['type'])); + } + } + } + } + + public function getOperationInput(){ + $type = strtolower($this->type); + switch($type){ + case 'get': $input = &$_GET; + return $input; + case 'post': $input = &$_POST; + return $input; + default: $input = &$_REQUEST; + return $input; + } + } + + function sanitizeOperation($input){ + return $this->sanitizeInputForType($input); + } + + function sanitizeInputForType($input){ + + $sanitizedInput = array(); + foreach($this->operationParams as $ind=>$columnDetails){ + foreach ($columnDetails as $columnName => $type) { + $sanitizedInput[$columnName] = $this->handleType($type,vtws_getParameter($input,$columnName));; + } + } + return $sanitizedInput; + } + + function handleType($type,$value){ + $result; + $value = stripslashes($value); + $type = strtolower($type); + if($this->inParamProcess[$type]){ + $result = call_user_func($this->inParamProcess[$type],$value); + }else{ + $result = $value; + } + return $result; + } + + function runOperation($params,$user){ + global $API_VERSION; + try{ + $operation = strtolower($this->operationName); + if(!$this->preLogin){ + $params[] = $user; + return call_user_func_array($this->handlerMethod,$params); + }else{ + $userDetails = call_user_func_array($this->handlerMethod,$params); + if(is_array($userDetails)){ + return $userDetails; + }else{ + $this->sessionManager->set("authenticatedUserId", $userDetails->id); + global $adb; + $webserviceObject = VtigerWebserviceObject::fromName($adb,"Users"); + $userId = vtws_getId($webserviceObject->getEntityId(),$userDetails->id); + $vtigerVersion = vtws_getVtigerVersion(); + $resp = array("sessionName"=>$this->sessionManager->getSessionId(),"userId"=>$userId,"version"=>$API_VERSION,"vtigerVersion"=>$vtigerVersion); + return $resp; + } + } + }catch(WebServiceException $e){ + throw $e; + }catch(Exception $e){ + throw new WebServiceException(WebServiceErrorCode::$INTERNALERROR,"Unknown Error while processing request"); + } + + } + + function encode($param){ + return call_user_func($this->formatObjects[$this->format]["encode"],$param); + } + + function getOperationIncludes(){ + $includes = array(); + array_push($includes,$this->handlerPath); + return $includes; + } + + } + +?> \ No newline at end of file diff --git a/include/Webservices/PreserveGlobal.php b/include/Webservices/PreserveGlobal.php new file mode 100644 index 0000000..9ef780a --- /dev/null +++ b/include/Webservices/PreserveGlobal.php @@ -0,0 +1,54 @@ + 0){ + $$name = array_pop(VTWS_PreserveGlobal::$globalData[$name]); + } + $$name; + } + + static function getGlobal($name){ + global $$name; + return VTWS_PreserveGlobal::preserveGlobal($name,$$name); + } + + static function flush(){ + foreach (VTWS_PreserveGlobal::$globalData as $name => $detail) { + //$name store the name of the global. + global $$name; + if(is_array(VTWS_PreserveGlobal::$globalData[$name]) && count(VTWS_PreserveGlobal::$globalData[$name]) > 0) { + $$name = array_pop(VTWS_PreserveGlobal::$globalData[$name]); + } + } + } + +} + +?> diff --git a/include/Webservices/Query.php b/include/Webservices/Query.php new file mode 100644 index 0000000..63fdd0c --- /dev/null +++ b/include/Webservices/Query.php @@ -0,0 +1,69 @@ +getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + // Cache the instance for re-use + if(!isset($vtws_query_cache[$moduleName]['handler'])) { + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + $vtws_query_cache[$moduleName]['handler'] = $handler; + } else { + $handler = $vtws_query_cache[$moduleName]['handler']; + } + // END + + // Cache the instance for re-use + if(!isset($vtws_query_cache[$moduleName]['meta'])) { + $meta = $handler->getMeta(); + $vtws_query_cache[$moduleName]['meta'] = $meta; + } else { + $meta = $vtws_query_cache[$moduleName]['meta']; + } + // END + + $types = vtws_listtypes(null, $user); + if(!in_array($webserviceObject->getEntityName(),$types['types'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied"); + } + + if(!$meta->hasReadAccess()){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to read is denied"); + } + + $result = $handler->query($q); + VTWS_PreserveGlobal::flush(); + return $result; + } + +?> \ No newline at end of file diff --git a/include/Webservices/QueryParser.php b/include/Webservices/QueryParser.php new file mode 100644 index 0000000..1cbca97 --- /dev/null +++ b/include/Webservices/QueryParser.php @@ -0,0 +1,64 @@ +query = $q; + $this->out = array(); + $this->hasError = false; + $this->user = $user; + } + + function parse(){ + + $lex = new VTQL_Lexer($this->query); + $parser = new VTQL_Parser($this->user, $lex,$this->out); + while ($lex->yylex()) { + $parser->doParse($lex->token, $lex->value); + } + $parser->doParse(0, 0); + + if($parser->isSuccess()){ + $this->hasError = false; + $this->query = $parser->getQuery(); + $this->meta = $parser->getObjectMetaData(); + }else{ + $this->hasError = true; + $this->error = $parser->getErrorMsg(); + } + + return $this->hasError; + + } + + function getSql(){ + return $this->query; + } + + function getObjectMetaData(){ + return $this->meta; + } + + function getError(){ + return $this->error; + } + + } +?> \ No newline at end of file diff --git a/include/Webservices/RelatedModuleMeta.php b/include/Webservices/RelatedModuleMeta.php new file mode 100644 index 0000000..255cab5 --- /dev/null +++ b/include/Webservices/RelatedModuleMeta.php @@ -0,0 +1,88 @@ +module = $module; + $this->relatedModule = $relatedModule; + } + + /** + * + * @param $module + * @param $relatedModule + * @return RelatedModuleMeta + */ + public static function getInstance($module, $relatedModule) { + return new RelatedModuleMeta($module, $relatedModule); + } + + public function getRelationMeta() { + $campaignContactRel = array('Campaigns','Contacts'); + $productInvoiceRel = array('Products','Invoice'); + $productQuotesRel = array('Products','Quotes'); + $productPurchaseOrder = array('Products','PurchaseOrder'); + if(in_array($this->module, $campaignContactRel) && in_array($this->relatedModule, + $campaignContactRel)) { + return $this->getRelationMetaInfo($this->CAMPAIGNCONTACTREL); + } + if(in_array($this->module, $productInvoiceRel) && in_array($this->relatedModule, + $productInvoiceRel)) { + return $this->getRelationMetaInfo($this->PRODUCTINVOICEREL); + } + if(in_array($this->module, $productQuotesRel) && in_array($this->relatedModule, + $productQuotesRel)) { + return $this->getRelationMetaInfo($this->PRODUCTQUOTESREL); + } + if(in_array($this->module, $productPurchaseOrder) && in_array($this->relatedModule, + $productPurchaseOrder)) { + return $this->getRelationMetaInfo($this->PRODUCTPURCHASEORDERREL); + } + } + + private function getRelationMetaInfo($relationId) { + switch($relationId) { + case $this->CAMPAIGNCONTACTREL: return array( + 'relationTable' => 'vtiger_campaigncontrel', + 'Campaigns' => 'campaignid', + 'Contacts' => 'contactid' + ); + case $this->PRODUCTINVOICEREL: return array( + 'relationTable' => 'vtiger_inventoryproductrel', + 'Products' => 'productid', + 'Invoice' => 'id' + ); + case $this->PRODUCTQUOTESREL: return array( + 'relationTable' => 'vtiger_inventoryproductrel', + 'Products' => 'productid', + 'Quotes' => 'id' + ); + case $this->PRODUCTPURCHASEORDERREL: return array( + 'relationTable' => 'vtiger_inventoryproductrel', + 'Products' => 'productid', + 'PurchaseOrder' => 'id' + ); + } + } +} +?> \ No newline at end of file diff --git a/include/Webservices/Retrieve.php b/include/Webservices/Retrieve.php new file mode 100644 index 0000000..b5cb194 --- /dev/null +++ b/include/Webservices/Retrieve.php @@ -0,0 +1,49 @@ +getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + $meta = $handler->getMeta(); + $entityName = $meta->getObjectEntityName($id); + $types = vtws_listtypes(null, $user); + if(!in_array($entityName,$types['types'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied"); + } + if($meta->hasReadAccess()!==true){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to write is denied"); + } + + if($entityName !== $webserviceObject->getEntityName()){ + throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Id specified is incorrect"); + } + + if(!$meta->hasPermission(EntityMeta::$RETRIEVE,$id)){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to read given object is denied"); + } + + $idComponents = vtws_getIdComponents($id); + if(!$meta->exists($idComponents[1])){ + throw new WebServiceException(WebServiceErrorCode::$RECORDNOTFOUND,"Record you are trying to access is not found"); + } + + $entity = $handler->retrieve($id); + VTWS_PreserveGlobal::flush(); + return $entity; + } +?> diff --git a/include/Webservices/Revise.php b/include/Webservices/Revise.php new file mode 100644 index 0000000..2616c12 --- /dev/null +++ b/include/Webservices/Revise.php @@ -0,0 +1,87 @@ +getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + $meta = $handler->getMeta(); + $entityName = $meta->getObjectEntityName($element['id']); + + $types = vtws_listtypes(null, $user); + if(!in_array($entityName,$types['types'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied"); + } + + if($entityName !== $webserviceObject->getEntityName()){ + throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Id specified is incorrect"); + } + + if(!$meta->hasPermission(EntityMeta::$UPDATE,$element['id'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to read given object is denied"); + } + + if(!$meta->exists($idList[1])){ + throw new WebServiceException(WebServiceErrorCode::$RECORDNOTFOUND,"Record you are trying to access is not found"); + } + + if($meta->hasWriteAccess()!==true){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to write is denied"); + } + + $referenceFields = $meta->getReferenceFieldDetails(); + foreach($referenceFields as $fieldName=>$details){ + if(isset($element[$fieldName]) && strlen($element[$fieldName]) > 0){ + $ids = vtws_getIdComponents($element[$fieldName]); + $elemTypeId = $ids[0]; + $elemId = $ids[1]; + $referenceObject = VtigerWebserviceObject::fromId($adb,$elemTypeId); + if (!in_array($referenceObject->getEntityName(),$details)){ + throw new WebServiceException(WebServiceErrorCode::$REFERENCEINVALID, + "Invalid reference specified for $fieldName"); + } + if ($referenceObject->getEntityName() == 'Users') { + if(!$meta->hasAssignPrivilege($element[$fieldName])) { + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Cannot assign record to the given user"); + } + } + if (!in_array($referenceObject->getEntityName(), $types['types']) && $referenceObject->getEntityName() != 'Users') { + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, + "Permission to access reference type is denied ".$referenceObject->getEntityName()); + } + } + } + //check if the element has mandtory fields filled + $meta->isUpdateMandatoryFields($element); + + $ownerFields = $meta->getOwnerFields(); + if(is_array($ownerFields) && sizeof($ownerFields) >0){ + foreach($ownerFields as $ownerField){ + if(isset($element[$ownerField]) && $element[$ownerField]!==null && + !$meta->hasAssignPrivilege($element[$ownerField])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Cannot assign record to the given user"); + } + } + } + + $entity = $handler->revise($element); + VTWS_PreserveGlobal::flush(); + return $entity; + } + +?> \ No newline at end of file diff --git a/include/Webservices/SessionManager.php b/include/Webservices/SessionManager.php new file mode 100644 index 0000000..b89d252 --- /dev/null +++ b/include/Webservices/SessionManager.php @@ -0,0 +1,132 @@ +maxLife = $now + $maxWebServiceSessionLifeSpan; + $this->idleLife = $now + $maxWebServiceSessionIdleTime; + + HTTP_Session::useCookies(false); //disable cookie usage. may this could be moved out constructor? + // only first invocation of following method, which is setExpire + //have an effect and any further invocation will be have no effect. + HTTP_Session::setExpire($this->maxLife); + // this method replaces the new with old time if second params is true + //otherwise it subtracts the time from previous time + HTTP_Session::setIdle($this->idleLife, true); + } + + function isValid(){ + + $valid = true; + // expired + if (HTTP_Session::isExpired()) { + $valid = false; + HTTP_Session::destroy(); + throw new WebServiceException(WebServiceErrorCode::$SESSLIFEOVER,"Session has life span over please login again"); + } + + // idled + if (HTTP_Session::isIdle()) { + $valid = false; + HTTP_Session::destroy(); + throw new WebServiceException(WebServiceErrorCode::$SESSIONIDLE,"Session has been invalidated to due lack activity"); + } + //echo "
is new: ", HTTP_Session::isNew(); + //invalid sessionId provided. + //echo "
get: ",$this->get($this->sessionVar); + if(!$this->get($this->sessionVar) && !HTTP_Session::isNew()){ + $valid = false; + HTTP_Session::destroy(); + throw new WebServiceException(WebServiceErrorCode::$SESSIONIDINVALID,"Session Identifier provided is Invalid"); + } + + return $valid; + } + + function startSession($sid = null,$adoptSession=false){ + +// if($sid){ +// HTTP_Session::id($sid); +// } + + if(!$sid || strlen($sid) ===0){ + $sid = null; + } + + //session name is used for guessing the session id by http_session so pass null. + HTTP_Session::start(null, $sid); + + $newSID = HTTP_Session::id(); + + if(!$sid || $adoptSession==true){ + $this->set($this->sessionVar,"true"); + }else{ + if(!$this->get($this->sessionVar)){ + HTTP_Session::destroy(); + throw new WebServiceException(WebServiceErrorCode::$SESSIONIDINVALID,"Session Identifier provided is Invalid"); + $newSID = null; + } + } + + if(!$this->isValid()){ + $newSID = null; + } + $sid = $newSID; + return $sid; + + } + + function getSessionId(){ + return HTTP_Session::id(); + } + + function set($var_name, $var_value){ + //TODO test setRef and getRef combination + //echo "
setting name: ",$var_name," :value: ",$var_value; + HTTP_Session::set($var_name, $var_value); + } + + function get($name){ + //echo "
getting for: ",$name," :value: ",HTTP_Session::get($name); + return HTTP_Session::get($name); + } + + FUNCTION getError(){ + return $this->error; + } + + function destroy(){ + HTTP_Session::destroy(); + } + + } + +?> diff --git a/include/Webservices/State.php b/include/Webservices/State.php new file mode 100644 index 0000000..c5c1621 --- /dev/null +++ b/include/Webservices/State.php @@ -0,0 +1,25 @@ +success = false; + $this->result = array(); + $this->error = array(); + } + + + } +?> \ No newline at end of file diff --git a/include/Webservices/Update.php b/include/Webservices/Update.php new file mode 100644 index 0000000..986b783 --- /dev/null +++ b/include/Webservices/Update.php @@ -0,0 +1,88 @@ +getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + $meta = $handler->getMeta(); + $entityName = $meta->getObjectEntityName($element['id']); + + $types = vtws_listtypes(null, $user); + if(!in_array($entityName,$types['types'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied"); + } + + if($entityName !== $webserviceObject->getEntityName()){ + throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Id specified is incorrect"); + } + + if(!$meta->hasPermission(EntityMeta::$UPDATE,$element['id'])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to read given object is denied"); + } + + if(!$meta->exists($idList[1])){ + throw new WebServiceException(WebServiceErrorCode::$RECORDNOTFOUND,"Record you are trying to access is not found"); + } + + if($meta->hasWriteAccess()!==true){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to write is denied"); + } + + $referenceFields = $meta->getReferenceFieldDetails(); + foreach($referenceFields as $fieldName=>$details){ + if(isset($element[$fieldName]) && strlen($element[$fieldName]) > 0){ + $ids = vtws_getIdComponents($element[$fieldName]); + $elemTypeId = $ids[0]; + $elemId = $ids[1]; + $referenceObject = VtigerWebserviceObject::fromId($adb,$elemTypeId); + if (!in_array($referenceObject->getEntityName(),$details)){ + throw new WebServiceException(WebServiceErrorCode::$REFERENCEINVALID, + "Invalid reference specified for $fieldName"); + } + if ($referenceObject->getEntityName() == 'Users') { + if(!$meta->hasAssignPrivilege($element[$fieldName])) { + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Cannot assign record to the given user"); + } + } + if (!in_array($referenceObject->getEntityName(), $types['types']) && $referenceObject->getEntityName() != 'Users') { + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, + "Permission to access reference type is denied ".$referenceObject->getEntityName()); + } + }else if($element[$fieldName] !== NULL){ + unset($element[$fieldName]); + } + } + + $meta->hasMandatoryFields($element); + + $ownerFields = $meta->getOwnerFields(); + if(is_array($ownerFields) && sizeof($ownerFields) >0){ + foreach($ownerFields as $ownerField){ + if(isset($element[$ownerField]) && $element[$ownerField]!==null && + !$meta->hasAssignPrivilege($element[$ownerField])){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Cannot assign record to the given user"); + } + } + } + + $entity = $handler->update($element); + VTWS_PreserveGlobal::flush(); + return $entity; + } + +?> \ No newline at end of file diff --git a/include/Webservices/Utils.php b/include/Webservices/Utils.php new file mode 100644 index 0000000..cc0c6f4 --- /dev/null +++ b/include/Webservices/Utils.php @@ -0,0 +1,919 @@ +getAllUserGroups($id); + $groups = $userGroups->user_groups; + + foreach ($groups as $group) { + $groupUsers->getAllUsersInGroup($group); + $usersInGroup = $groupUsers->group_users; + foreach ($usersInGroup as $user) { + if($user != $id){ + $allUsers[$user] = getUserFullName($user); + } + } + } + return $allUsers; +} + +function vtws_generateRandomAccessKey($length=10){ + $source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + $accesskey = ""; + $maxIndex = strlen($source); + for($i=0;$i<$length;++$i){ + $accesskey = $accesskey.substr($source,rand(null,$maxIndex),1); + } + return $accesskey; +} + +/** + * get current vtiger version from the database. + */ +function vtws_getVtigerVersion(){ + global $adb; + $query = 'select * from vtiger_version'; + $result = $adb->pquery($query, array()); + $version = ''; + while($row = $adb->fetch_array($result)) + { + $version = $row['current_version']; + } + return $version; +} + +function vtws_getUserAccessibleGroups($moduleId, $user){ + global $adb; + require('user_privileges/user_privileges_'.$user->id.'.php'); + require('user_privileges/sharing_privileges_'.$user->id.'.php'); + $tabName = getTabname($moduleId); + if($is_admin==false && $profileGlobalPermission[2] == 1 && + ($defaultOrgSharingPermission[$moduleId] == 3 or $defaultOrgSharingPermission[$moduleId] == 0)){ + $result=get_current_user_access_groups($tabName); + }else{ + $result = get_group_options(); + } + + $groups = array(); + if($result != null && $result != '' && is_object($result)){ + $rowCount = $adb->num_rows($result); + for ($i = 0; $i < $rowCount; $i++) { + $nameArray = $adb->query_result_rowdata($result,$i); + $groupId=$nameArray["groupid"]; + $groupName=$nameArray["groupname"]; + $groups[] = array('id'=>$groupId,'name'=>$groupName); + } + } + return $groups; +} + +function vtws_getWebserviceGroupFromGroups($groups){ + global $adb; + $webserviceObject = VtigerWebserviceObject::fromName($adb,'Groups'); + foreach($groups as $index=>$group){ + $groups[$index]['id'] = vtws_getId($webserviceObject->getEntityId(),$group['id']); + } + return $groups; +} + +function vtws_getUserWebservicesGroups($tabId,$user){ + $groups = vtws_getUserAccessibleGroups($tabId,$user); + return vtws_getWebserviceGroupFromGroups($groups); +} + +function vtws_getIdComponents($elementid){ + return explode("x",$elementid); +} + +function vtws_getId($objId, $elemId){ + return $objId."x".$elemId; +} + +function getEmailFieldId($meta, $entityId){ + global $adb; + //no email field accessible in the module. since its only association pick up the field any way. + $query="SELECT fieldid,fieldlabel,columnname FROM vtiger_field WHERE tabid=? + and uitype=13 and presence in (0,2)"; + $result = $adb->pquery($query, array($meta->getTabId())); + + //pick up the first field. + $fieldId = $adb->query_result($result,0,'fieldid'); + return $fieldId; +} + +function vtws_getParameter($parameterArray, $paramName,$default=null){ + + if (!get_magic_quotes_gpc()) { + if(is_array($parameterArray[$paramName])) { + $param = array_map('addslashes', $parameterArray[$paramName]); + } else { + $param = addslashes($parameterArray[$paramName]); + } + } else { + $param = $parameterArray[$paramName]; + } + if(!$param){ + $param = $default; + } + return $param; +} + +function vtws_getEntityNameFields($moduleName){ + + global $adb; + $query = "select fieldname,tablename,entityidfield from vtiger_entityname where modulename = ?"; + $result = $adb->pquery($query, array($moduleName)); + $rowCount = $adb->num_rows($result); + $nameFields = array(); + if($rowCount > 0){ + $fieldsname = $adb->query_result($result,0,'fieldname'); + if(!(strpos($fieldsname,',') === false)){ + $nameFields = explode(',',$fieldsname); + }else{ + array_push($nameFields,$fieldsname); + } + } + return $nameFields; +} + +/** function to get the module List to which are crm entities. + * @return Array modules list as array + */ +function vtws_getModuleNameList(){ + global $adb; + + $sql = "select name from vtiger_tab where isentitytype=1 and name not in ('Rss','Webmails',". + "'Recyclebin','Events') order by tabsequence"; + $res = $adb->pquery($sql, array()); + $mod_array = Array(); + while($row = $adb->fetchByAssoc($res)){ + array_push($mod_array,$row['name']); + } + return $mod_array; +} + +function vtws_getWebserviceEntities(){ + global $adb; + + $sql = "select name,id,ismodule from vtiger_ws_entity"; + $res = $adb->pquery($sql, array()); + $moduleArray = Array(); + $entityArray = Array(); + while($row = $adb->fetchByAssoc($res)){ + if($row['ismodule'] == '1'){ + array_push($moduleArray,$row['name']); + }else{ + array_push($entityArray,$row['name']); + } + } + return array('module'=>$moduleArray,'entity'=>$entityArray); +} + +/** + * + * @param VtigerWebserviceObject $webserviceObject + * @return CRMEntity + */ +function vtws_getModuleInstance($webserviceObject){ + $moduleName = $webserviceObject->getEntityName(); + return CRMEntity::getInstance($moduleName); +} + +function vtws_isRecordOwnerUser($ownerId){ + global $adb; + $result = $adb->pquery("select first_name from vtiger_users where id = ?",array($ownerId)); + $rowCount = $adb->num_rows($result); + $ownedByUser = ($rowCount > 0); + return $ownedByUser; +} + +function vtws_isRecordOwnerGroup($ownerId){ + global $adb; + $result = $adb->pquery("select groupname from vtiger_groups where groupid = ?",array($ownerId)); + $rowCount = $adb->num_rows($result); + $ownedByGroup = ($rowCount > 0); + return $ownedByGroup; +} + +function vtws_getOwnerType($ownerId){ + if(vtws_isRecordOwnerGroup($ownerId) == true){ + return 'Groups'; + } + if(vtws_isRecordOwnerUser($ownerId) == true){ + return 'Users'; + } + throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Invalid owner of the record"); +} + +function vtws_runQueryAsTransaction($query,$params,&$result){ + global $adb; + + $adb->startTransaction(); + $result = $adb->pquery($query,$params); + $error = $adb->hasFailedTransaction(); + $adb->completeTransaction(); + return !$error; +} + +function vtws_getCalendarEntityType($id){ + global $adb; + + $sql = "select activitytype from vtiger_activity where activityid=?"; + $result = $adb->pquery($sql,array($id)); + $seType = 'Calendar'; + if($result != null && isset($result)){ + if($adb->num_rows($result)>0){ + $activityType = $adb->query_result($result,0,"activitytype"); + if($activityType !== "Task"){ + $seType = "Events"; + } + } + } + return $seType; +} + +/*** + * Get the webservice reference Id given the entity's id and it's type name + */ +function vtws_getWebserviceEntityId($entityName, $id){ + global $adb; + $webserviceObject = VtigerWebserviceObject::fromName($adb,$entityName); + return $webserviceObject->getEntityId().'x'.$id; +} + +function vtws_addDefaultModuleTypeEntity($moduleName){ + global $adb; + $isModule = 1; + $moduleHandler = array('file'=>'include/Webservices/VtigerModuleOperation.php', + 'class'=>'VtigerModuleOperation'); + return vtws_addModuleTypeWebserviceEntity($moduleName,$moduleHandler['file'],$moduleHandler['class'],$isModule); +} + +function vtws_addModuleTypeWebserviceEntity($moduleName,$filePath,$className){ + global $adb; + $checkres = $adb->pquery('SELECT id FROM vtiger_ws_entity WHERE name=? AND handler_path=? AND handler_class=?', + array($moduleName, $filePath, $className)); + if($checkres && $adb->num_rows($checkres) == 0) { + $isModule=1; + $entityId = $adb->getUniqueID("vtiger_ws_entity"); + $adb->pquery('insert into vtiger_ws_entity(id,name,handler_path,handler_class,ismodule) values (?,?,?,?,?)', + array($entityId,$moduleName,$filePath,$className,$isModule)); + } +} + +function vtws_deleteWebserviceEntity($moduleName) { + global $adb; + $adb->pquery('DELETE FROM vtiger_ws_entity WHERE name=?',array($moduleName)); +} + +function vtws_addDefaultActorTypeEntity($actorName,$actorNameDetails,$withName = true){ + $actorHandler = array('file'=>'include/Webservices/VtigerActorOperation.php', + 'class'=>'VtigerActorOperation'); + if($withName == true){ + vtws_addActorTypeWebserviceEntityWithName($actorName,$actorHandler['file'],$actorHandler['class'], + $actorNameDetails); + }else{ + vtws_addActorTypeWebserviceEntityWithoutName($actorName,$actorHandler['file'],$actorHandler['class'], + $actorNameDetails); + } +} + +function vtws_addActorTypeWebserviceEntityWithName($moduleName,$filePath,$className,$actorNameDetails){ + global $adb; + $isModule=0; + $entityId = $adb->getUniqueID("vtiger_ws_entity"); + $adb->pquery('insert into vtiger_ws_entity(id,name,handler_path,handler_class,ismodule) values (?,?,?,?,?)', + array($entityId,$moduleName,$filePath,$className,$isModule)); + vtws_addActorTypeName($entityId,$actorNameDetails['fieldNames'],$actorNameDetails['indexField'], + $actorNameDetails['tableName']); +} + +function vtws_addActorTypeWebserviceEntityWithoutName($moduleName,$filePath,$className,$actorNameDetails){ + global $adb; + $isModule=0; + $entityId = $adb->getUniqueID("vtiger_ws_entity"); + $adb->pquery('insert into vtiger_ws_entity(id,name,handler_path,handler_class,ismodule) values (?,?,?,?,?)', + array($entityId,$moduleName,$filePath,$className,$isModule)); +} + +function vtws_addActorTypeName($entityId,$fieldNames,$indexColumn,$tableName){ + global $adb; + $adb->pquery('insert into vtiger_ws_entity_name(entity_id,name_fields,index_field,table_name) values (?,?,?,?)', + array($entityId,$fieldNames,$indexColumn,$tableName)); +} + +function vtws_getName($id,$user){ + global $log,$adb; + + $webserviceObject = VtigerWebserviceObject::fromId($adb,$id); + $handlerPath = $webserviceObject->getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + $meta = $handler->getMeta(); + return $meta->getName($id); +} + +function vtws_preserveGlobal($name,$value){ + return VTWS_PreserveGlobal::preserveGlobal($name,$value); +} + +/** + * Takes the details of a webservices and exposes it over http. + * @param $name name of the webservice to be added with namespace. + * @param $handlerFilePath file to be include which provides the handler method for the given webservice. + * @param $handlerMethodName name of the function to the called when this webservice is invoked. + * @param $requestType type of request that this operation should be, if in doubt give it as GET, + * general rule of thumb is that, if the operation is adding/updating data on server then it must be POST + * otherwise it should be GET. + * @param $preLogin 0 if the operation need the user to authorised to access the webservice and + * 1 if the operation is called before login operation hence the there will be no user authorisation happening + * for the operation. + * @return Integer operationId of successful or null upon failure. + */ +function vtws_addWebserviceOperation($name,$handlerFilePath,$handlerMethodName,$requestType,$preLogin = 0){ + global $adb; + $createOperationQuery = "insert into vtiger_ws_operation(operationid,name,handler_path,handler_method,type,prelogin) + values (?,?,?,?,?,?);"; + if(strtolower($requestType) != 'get' && strtolower($requestType) != 'post'){ + return null; + } + $requestType = strtoupper($requestType); + if(empty($preLogin)){ + $preLogin = 0; + }else{ + $preLogin = 1; + } + $operationId = $adb->getUniqueID("vtiger_ws_operation"); + $result = $adb->pquery($createOperationQuery,array($operationId,$name,$handlerFilePath,$handlerMethodName, + $requestType,$preLogin)); + if($result !== false){ + return $operationId; + } + return null; +} + +/** + * Add a parameter to a webservice. + * @param $operationId Id of the operation for which a webservice needs to be added. + * @param $paramName name of the parameter used to pickup value from request(POST/GET) object. + * @param $paramType type of the parameter, it can either 'string','datetime' or 'encoded' + * encoded type is used for input which will be encoded in JSON or XML(NOT SUPPORTED). + * @param $sequence sequence of the parameter in the definition in the handler method. + * @return Boolean true if the parameter was added successfully, false otherwise + */ +function vtws_addWebserviceOperationParam($operationId,$paramName,$paramType,$sequence){ + global $adb; + $supportedTypes = array('string','encoded','datetime','double','boolean'); + if(!is_numeric($sequence)){ + $sequence = 1; + }if($sequence <=1){ + $sequence = 1; + } + if(!in_array(strtolower($paramType),$supportedTypes)){ + return false; + } + $createOperationParamsQuery = "insert into vtiger_ws_operation_parameters(operationid,name,type,sequence) + values (?,?,?,?);"; + $result = $adb->pquery($createOperationParamsQuery,array($operationId,$paramName,$paramType,$sequence)); + return ($result !== false); +} + +/** + * + * @global PearDatabase $adb + * @global $log + * @param $name + * @param $user + * @return WebserviceEntityOperation + */ +function vtws_getModuleHandlerFromName($name,$user){ + global $adb, $log; + $webserviceObject = VtigerWebserviceObject::fromName($adb,$name); + $handlerPath = $webserviceObject->getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + return $handler; +} + +function vtws_getModuleHandlerFromId($id,$user){ + global $adb, $log; + $webserviceObject = VtigerWebserviceObject::fromId($adb,$id); + $handlerPath = $webserviceObject->getHandlerPath(); + $handlerClass = $webserviceObject->getHandlerClass(); + + require_once $handlerPath; + + $handler = new $handlerClass($webserviceObject,$user,$adb,$log); + return $handler; +} + +function vtws_CreateCompanyLogoFile($fieldname) { + global $root_directory; + $uploaddir = $root_directory ."/test/logo/"; + $allowedFileTypes = array("jpeg", "png", "jpg", "pjpeg" ,"x-png"); + $binFile = $_FILES[$fieldname]['name']; + $fileType = $_FILES[$fieldname]['type']; + $fileSize = $_FILES[$fieldname]['size']; + $fileTypeArray = explode("/",$fileType); + $fileTypeValue = strtolower($fileTypeArray[1]); + if($fileTypeValue == '') { + $fileTypeValue = substr($binFile,strrpos($binFile, '.')+1); + } + if($fileSize != 0) { + if(in_array($fileTypeValue, $allowedFileTypes)) { + move_uploaded_file($_FILES[$fieldname]["tmp_name"], + $uploaddir.$_FILES[$fieldname]["name"]); + return $binFile; + } + throw new WebServiceException(WebServiceErrorCode::$INVALIDTOKEN, + "$fieldname wrong file type given for upload"); + } + throw new WebServiceException(WebServiceErrorCode::$INVALIDTOKEN, + "$fieldname file upload failed"); +} + +function vtws_getActorEntityName ($name, $idList) { + $db = PearDatabase::getInstance(); + if (!is_array($idList) && count($idList) == 0) { + return array(); + } + $entity = VtigerWebserviceObject::fromName($db, $name); + return vtws_getActorEntityNameById($entity->getEntityId(), $idList); +} + +function vtws_getActorEntityNameById ($entityId, $idList) { + $db = PearDatabase::getInstance(); + if (!is_array($idList) && count($idList) == 0) { + return array(); + } + $nameList = array(); + $webserviceObject = VtigerWebserviceObject::fromId($db, $entityId); + $query = "select * from vtiger_ws_entity_name where entity_id = ?"; + $result = $db->pquery($query, array($entityId)); + if (is_object($result)) { + $rowCount = $db->num_rows($result); + if ($rowCount > 0) { + $nameFields = $db->query_result($result,0,'name_fields'); + $tableName = $db->query_result($result,0,'table_name'); + $indexField = $db->query_result($result,0,'index_field'); + if (!(strpos($nameFields,',') === false)) { + $fieldList = explode(',',$nameFields); + $nameFields = "concat("; + $nameFields = $nameFields.implode(",' ',",$fieldList); + $nameFields = $nameFields.")"; + } + + $query1 = "select $nameFields as entityname, $indexField from $tableName where ". + "$indexField in (".generateQuestionMarks($idList).")"; + $params1 = array($idList); + $result = $db->pquery($query1, $params1); + if (is_object($result)) { + $rowCount = $db->num_rows($result); + for ($i = 0; $i < $rowCount; $i++) { + $id = $db->query_result($result,$i, $indexField); + $nameList[$id] = $db->query_result($result,$i,'entityname'); + } + return $nameList; + } + } + } + return array(); +} + +function vtws_isRoleBasedPicklist($name) { + $db = PearDatabase::getInstance(); + $sql = "select picklistid from vtiger_picklist where name = ?"; + $result = $db->pquery($sql, array($name)); + return ($db->num_rows($result) > 0); +} + +function vtws_getConvertLeadFieldMapping(){ + global $adb; + $sql = "select * from vtiger_convertleadmapping"; + $result = $adb->pquery($sql,array()); + if($result === false){ + return null; + } + $mapping = array(); + $rowCount = $adb->num_rows($result); + for($i=0;$i<$rowCount;++$i){ + $row = $adb->query_result_rowdata($result,$i); + $mapping[$row['leadfid']] = array('Accounts'=>$row['accountfid'], + 'Potentials'=>$row['potentialfid'],'Contacts'=>$row['contactfid']); + } + return $mapping; +} + +/** Function used to get the lead related Notes and Attachments with other entities Account, Contact and Potential + * @param integer $id - leadid + * @param integer $relatedId - related entity id (accountid / contactid) + */ +function vtws_getRelatedNotesAttachments($id,$relatedId) { + global $adb,$log; + + $sql = "select * from vtiger_senotesrel where crmid=?"; + $result = $adb->pquery($sql, array($id)); + if($result === false){ + return false; + } + $rowCount = $adb->num_rows($result); + + $sql="insert into vtiger_senotesrel(crmid,notesid) values (?,?)"; + for($i=0; $i<$rowCount;++$i ) { + $noteId=$adb->query_result($result,$i,"notesid"); + $resultNew = $adb->pquery($sql, array($relatedId, $noteId)); + if($resultNew === false){ + return false; + } + } + + $sql = "select * from vtiger_seattachmentsrel where crmid=?"; + $result = $adb->pquery($sql, array($id)); + if($result === false){ + return false; + } + $rowCount = $adb->num_rows($result); + + $sql = "insert into vtiger_seattachmentsrel(crmid,attachmentsid) values (?,?)"; + for($i=0;$i<$rowCount;++$i) { + $attachmentId=$adb->query_result($result,$i,"attachmentsid"); + $resultNew = $adb->pquery($sql, array($relatedId, $attachmentId)); + if($resultNew === false){ + return false; + } + } + return true; +} + +/** Function used to save the lead related products with other entities Account, Contact and Potential + * $leadid - leadid + * $relatedid - related entity id (accountid/contactid/potentialid) + * $setype - related module(Accounts/Contacts/Potentials) + */ +function vtws_saveLeadRelatedProducts($leadId, $relatedId, $setype) { + global $adb; + + $result = $adb->pquery("select * from vtiger_seproductsrel where crmid=?", array($leadId)); + if($result === false){ + return false; + } + $rowCount = $adb->num_rows($result); + for($i = 0; $i < $rowCount; ++$i) { + $productId = $adb->query_result($result,$i,'productid'); + $resultNew = $adb->pquery("insert into vtiger_seproductsrel values(?,?,?)", array($relatedId, $productId, $setype)); + if($resultNew === false){ + return false; + } + } + return true; +} + +/** Function used to save the lead related services with other entities Account, Contact and Potential + * $leadid - leadid + * $relatedid - related entity id (accountid/contactid/potentialid) + * $setype - related module(Accounts/Contacts/Potentials) + */ +function vtws_saveLeadRelations($leadId, $relatedId, $setype) { + global $adb; + + $result = $adb->pquery("select * from vtiger_crmentityrel where crmid=?", array($leadId)); + if($result === false){ + return false; + } + $rowCount = $adb->num_rows($result); + for($i = 0; $i < $rowCount; ++$i) { + $recordId = $adb->query_result($result,$i,'relcrmid'); + $recordModule = $adb->query_result($result,$i,'relmodule'); + $adb->pquery("insert into vtiger_crmentityrel values(?,?,?,?)", + array($relatedId, $setype, $recordId, $recordModule)); + if($resultNew === false){ + return false; + } + } + $result = $adb->pquery("select * from vtiger_crmentityrel where relcrmid=?", array($leadId)); + if($result === false){ + return false; + } + $rowCount = $adb->num_rows($result); + for($i = 0; $i < $rowCount; ++$i) { + $recordId = $adb->query_result($result,$i,'crmid'); + $recordModule = $adb->query_result($result,$i,'module'); + $adb->pquery("insert into vtiger_crmentityrel values(?,?,?,?)", + array($relatedId, $setype, $recordId, $recordModule)); + if($resultNew === false){ + return false; + } + } + + return true; +} + +function vtws_getFieldfromFieldId($fieldId, $fieldObjectList){ + foreach ($fieldObjectList as $field) { + if($fieldId == $field->getFieldId()){ + return $field; + } + } + return null; +} + +/** Function used to get the lead related activities with other entities Account and Contact + * @param integer $leadId - lead entity id + * @param integer $accountId - related account id + * @param integer $contactId - related contact id + * @param integer $relatedId - related entity id to which the records need to be transferred + */ +function vtws_getRelatedActivities($leadId,$accountId,$contactId,$relatedId) { + + if(empty($leadId) || empty($relatedId) || (empty($accountId) && empty($contactId))){ + throw new WebServiceException(WebServiceErrorCode::$LEAD_RELATED_UPDATE_FAILED, + "Failed to move related Activities/Emails"); + } + global $adb; + $sql = "select * from vtiger_seactivityrel where crmid=?"; + $result = $adb->pquery($sql, array($leadId)); + if($result === false){ + return false; + } + $rowCount = $adb->num_rows($result); + for($i=0;$i<$rowCount;++$i) { + $activityId=$adb->query_result($result,$i,"activityid"); + + $sql ="select setype from vtiger_crmentity where crmid=?"; + $resultNew = $adb->pquery($sql, array($activityId)); + if($resultNew === false){ + return false; + } + $type=$adb->query_result($resultNew,0,"setype"); + + $sql="delete from vtiger_seactivityrel where crmid=?"; + $resultNew = $adb->pquery($sql, array($leadId)); + if($resultNew === false){ + return false; + } + if($type != "Emails") { + if(!empty($accountId)){ + $sql = "insert into vtiger_seactivityrel(crmid,activityid) values (?,?)"; + $resultNew = $adb->pquery($sql, array($accountId, $activityId)); + if($resultNew === false){ + return false; + } + } + if(!empty($contactId)){ + $sql="insert into vtiger_cntactivityrel(contactid,activityid) values (?,?)"; + $resultNew = $adb->pquery($sql, array($contactId, $activityId)); + if($resultNew === false){ + return false; + } + } + } else { + $sql = "insert into vtiger_seactivityrel(crmid,activityid) values (?,?)"; + $resultNew = $adb->pquery($sql, array($relatedId, $activityId)); + if($resultNew === false){ + return false; + } + } + } + return true; +} + +/** + * Function used to save the lead related Campaigns with Contact + * @param $leadid - leadid + * @param $relatedid - related entity id (contactid/accountid) + * @param $setype - related module(Accounts/Contacts) + * @return Boolean true on success, false otherwise. + */ +function vtws_saveLeadRelatedCampaigns($leadId, $relatedId, $seType) { + global $adb; + + $result = $adb->pquery("select * from vtiger_campaignleadrel where leadid=?", array($leadId)); + if($result === false){ + return false; + } + $rowCount = $adb->num_rows($result); + for($i = 0; $i < $rowCount; ++$i) { + $campaignId = $adb->query_result($result,$i,'campaignid'); + if($seType == 'Accounts') { + $resultNew = $adb->pquery("insert into vtiger_campaignaccountrel (campaignid, accountid) values(?,?)", + array($campaignId, $relatedId)); + } elseif ($seType == 'Contacts') { + $resultNew = $adb->pquery("insert into vtiger_campaigncontrel (campaignid, contactid) values(?,?)", + array($campaignId, $relatedId)); + } + if($resultNew === false){ + return false; + } + } + return true; +} + +/** + * Function used to transfer all the lead related records to given Entity(Contact/Account) record + * @param $leadid - leadid + * @param $relatedid - related entity id (contactid/accountid) + * @param $setype - related module(Accounts/Contacts) + */ +function vtws_transferLeadRelatedRecords($leadId, $relatedId, $seType) { + + if(empty($leadId) || empty($relatedId) || empty($seType)){ + throw new WebServiceException(WebServiceErrorCode::$LEAD_RELATED_UPDATE_FAILED, + "Failed to move related Records"); + } + $status = vtws_getRelatedNotesAttachments($leadId, $relatedId); + if($status === false){ + throw new WebServiceException(WebServiceErrorCode::$LEAD_RELATED_UPDATE_FAILED, + "Failed to move related Documents to the ".$seType); + } + //Retrieve the lead related products and relate them with this new account + $status = vtws_saveLeadRelatedProducts($leadId, $relatedId, $seType); + if($status === false){ + throw new WebServiceException(WebServiceErrorCode::$LEAD_RELATED_UPDATE_FAILED, + "Failed to move related Products to the ".$seType); + } + $status = vtws_saveLeadRelations($leadId, $relatedId, $seType); + if($status === false){ + throw new WebServiceException(WebServiceErrorCode::$LEAD_RELATED_UPDATE_FAILED, + "Failed to move Records to the ".$seType); + } + $status = vtws_saveLeadRelatedCampaigns($leadId, $relatedId, $seType); + if($status === false){ + throw new WebServiceException(WebServiceErrorCode::$LEAD_RELATED_UPDATE_FAILED, + "Failed to move Records to the ".$seType); + } + vtws_transferComments($leadId, $relatedId); +} + +function vtws_transferComments($sourceRecordId, $destinationRecordId) { + if(vtlib_isModuleActive('ModComments')) { + CRMEntity::getInstance('ModComments'); ModComments::transferRecords($sourceRecordId, $destinationRecordId); + } +} + +function vtws_transferOwnership($ownerId, $newOwnerId, $delete=true) { + $db = PearDatabase::getInstance(); + //Updating the smcreatorid,smownerid, modifiedby in vtiger_crmentity + $sql = "update vtiger_crmentity set smcreatorid=? where smcreatorid=?"; + $db->pquery($sql, array($newOwnerId, $ownerId)); + + $sql = "update vtiger_crmentity set smownerid=? where smownerid=?"; + $db->pquery($sql, array($newOwnerId, $ownerId)); + + $sql = "update vtiger_crmentity set modifiedby=? where modifiedby=?"; + $db->pquery($sql, array($newOwnerId, $ownerId)); + + //deleting from vtiger_tracker + if ($delete) { + $sql = "delete from vtiger_tracker where user_id=?"; + $db->pquery($sql, array($ownerId)); + } + + //updating created by in vtiger_lar + $sql = "update vtiger_lar set createdby=? where createdby=?"; + $db->pquery($sql, array($newOwnerId, $ownerId)); + + //updating the vtiger_import_maps + $sql ="update vtiger_import_maps set assigned_user_id=? where assigned_user_id=?"; + $db->pquery($sql, array($newOwnerId, $ownerId)); + + //update assigned_user_id in vtiger_files + $sql ="update vtiger_files set assigned_user_id=? where assigned_user_id=?"; + $db->pquery($sql, array($newOwnerId, $ownerId)); + + if(Vtiger_Utils::CheckTable('vtiger_customerportal_prefs')) { + $query = 'UPDATE vtiger_customerportal_prefs SET prefvalue = ? WHERE prefkey = ? AND prefvalue = ?'; + $params = array($newOwnerId, 'defaultassignee', $ownerId); + $db->pquery($query, $params); + + $query = 'UPDATE vtiger_customerportal_prefs SET prefvalue = ? WHERE prefkey = ? AND prefvalue = ?'; + $params = array($newOwnerId, 'userid', $ownerId); + $db->pquery($query, $params); + } + + //delete from vtiger_homestuff + if ($delete) { + $sql = "delete from vtiger_homestuff where userid=?"; + $db->pquery($sql, array($ownerId)); + } + + //delete from vtiger_users to group vtiger_table + if ($delete) { + $sql = "delete from vtiger_user2role where userid=?"; + $db->pquery($sql, array($ownerId)); + } + + //delete from vtiger_users to vtiger_role vtiger_table + if ($delete) { + $sql = "delete from vtiger_users2group where userid=?"; + $db->pquery($sql, array($ownerId)); + } + + $sql = "select tabid,fieldname,tablename,columnname from vtiger_field left join ". + "vtiger_fieldmodulerel on vtiger_field.fieldid=vtiger_fieldmodulerel.fieldid where uitype ". + "in (52,53,77,101) or (uitype=10 and relmodule='Users')"; + $result = $db->pquery($sql, array()); + $it = new SqlResultIterator($db, $result); + $columnList = array(); + foreach ($it as $row) { + $column = $row->tablename.'.'.$row->columnname; + if(!in_array($column, $columnList)) { + $columnList[] = $column; + $sql = "update $row->tablename set $row->columnname=? where $row->columnname=?"; + $db->pquery($sql, array($newOwnerId, $ownerId)); + } + } +} + + +function vtws_getWebserviceTranslatedStringForLanguage($label, $currentLanguage) { + static $translations = array(); + $currentLanguage = vtws_getWebserviceCurrentLanguage(); + if(empty($translations[$currentLanguage])) { + include 'include/Webservices/language/'.$currentLanguage.'.lang.php'; + $translations[$currentLanguage] = $webservice_strings; + } + if(isset($translations[$currentLanguage][$label])) { + return $translations[$currentLanguage][$label]; + } + return null; +} + +function vtws_getWebserviceTranslatedString($label) { + $currentLanguage = vtws_getWebserviceCurrentLanguage(); + $translation = vtws_getWebserviceTranslatedStringForLanguage($label, $currentLanguage); + if(!empty($translation)) { + return $translation; + } + + //current language doesn't have translation, return translation in default language + //if default language is english then LBL_ will not shown to the user. + $defaultLanguage = vtws_getWebserviceDefaultLanguage(); + $translation = vtws_getWebserviceTranslatedStringForLanguage($label, $defaultLanguage); + if(!empty($translation)) { + return $translation; + } + + //if default language is not en_us then do the translation in en_us to eliminate the LBL_ bit + //of label. + if('en_us' != $defaultLanguage) { + $translation = vtws_getWebserviceTranslatedStringForLanguage($label, 'en_us'); + if(!empty($translation)) { + return $translation; + } + } + return $label; +} + +function vtws_getWebserviceCurrentLanguage() { + global $default_language, $current_language; + if(empty($current_language)) { + return $default_language; + } + return $current_language; +} + +function vtws_getWebserviceDefaultLanguage() { + global $default_language; + return $default_language; +} + +?> \ No newline at end of file diff --git a/include/Webservices/VTQL_Lexer.php b/include/Webservices/VTQL_Lexer.php new file mode 100644 index 0000000..c9930fd --- /dev/null +++ b/include/Webservices/VTQL_Lexer.php @@ -0,0 +1,316 @@ +current_state++; +if($lexer->current_state === sizeof($lexer->mandatory_states)){ +$lexer->mandatory = false; +} +} +function handleselect($lexer, $val){ +if($lexer->mandatory){ +if(strcasecmp($val, $lexer->mandatory_states[$lexer->current_state])===0){ +incState($lexer); +return VTQL_Parser::SELECT; +} +} +} +function handlecolumn_list($lexer, $val){ +global $count; +if($lexer->mandatory){ +if(!(strcasecmp($val, $lexer->mandatory_states[2])===0)){ +if(strcmp($val, "*")===0){ +if(!$count){ +incrementN($lexer, 1); +} +return VTQL_Parser::ASTERISK; +}else if((strcmp($val, "(")===0)){ +return VTQL_Parser::PARENOPEN; +}else if(strcmp($val, ")")===0){ +return VTQL_Parser::PARENCLOSE; +}else if((strcasecmp($val, "count")===0)){ +$count = true; +return VTQL_Parser::COUNT; +}else if(strcmp($val, ",")===0){ +return VTQL_Parser::COMMA; +}else{ +return VTQL_Parser::COLUMNNAME; +} +}else{ +incrementN($lexer, 2); +return VTQL_Parser::FRM; +} +} +} +function handlefrom($lexer, $val){ +if((strcasecmp($val, $lexer->mandatory_states[$lexer->current_state])===0)){ +incState($lexer); +return VTQL_Parser::FRM; +} +} +function handletable($lexer, $val){ +if($lexer->mandatory){ +$lexer->current_state =0; +$lexer->mandatory = false; +if(!(strcasecmp($val, $lexer->optional_states[$lexer->current_state])===0)){ +return VTQL_Parser::TABLENAME; +} +} +} +function handlewhere($lexer, $val){ +global $where_col,$in_started; +$val = trim($val); +if((strcmp($val, "=")===0)){ +return VTQL_Parser::EQ; +}else if((strcasecmp($val, $lexer->optional_states[$lexer->current_state])===0)){ +return VTQL_Parser::WHERE; +}else if((strcmp($val, "<")===0)){ +return VTQL_Parser::LT; +}else if((strcmp($val, "<=")===0)){ +return VTQL_Parser::LTE; +}else if((strcmp($val, ">=")===0)){ +return VTQL_Parser::GTE; +}else if((strcmp($val, "!=")===0)){ +return VTQL_Parser::NE; +}else if((strcmp($val, ">")===0)){ +return VTQL_Parser::GT; +}else if((strcmp($val, "(")===0)){ +return VTQL_Parser::PARENOPEN; +}else if((strcmp($val, ")")===0)){ +if($in_started){ +$in_started = false; +$where_col = false; +} +return VTQL_Parser::PARENCLOSE; +}else if((strcasecmp($val, "and")===0)){ +return VTQL_Parser::LOGICAL_AND; +}else if((strcasecmp($val, "or")===0)){ +return VTQL_Parser::LOGICAL_OR; +}else if(!$where_col){ +$where_col = true; +return VTQL_Parser::COLUMNNAME; +}else if((strcasecmp($val, "in")===0)){ +$in_started = true; +return VTQL_Parser::IN; +}else if(strcmp($val, ",")===0){ +return VTQL_Parser::COMMA; +}else if(strcasecmp($val, "like")===0){ +return VTQL_Parser::LIKE; +}else if($where_col){ +if(!$in_started){ +$where_col = false; +} +return VTQL_Parser::VALUE; +} +} +function handleorderby($lexer, $val){ +global $orderby; +if(!$orderby){ + $orderby = true; + return VTQL_Parser::ORDERBY; +} +if(strcmp($val, ",")===0){ +return VTQL_Parser::COMMA; +}else if(strcasecmp($val, "asc")===0){ +return VTQL_Parser::ASC; +}else if(strcasecmp($val, "desc")===0){ +return VTQL_Parser::DESC; +}else{ +return VTQL_Parser::COLUMNNAME; +} +} +function handlelimit($lexer, $val){ +if((strcasecmp($val, "limit")===0)){ +return VTQL_Parser::LIMIT; +}else if((strcmp($val, "(")===0)){ +return VTQL_Parser::PARENOPEN; +}else if((strcmp($val, ")")===0)){ +return VTQL_Parser::PARENCLOSE; +}else if(strcmp($val, ",")===0){ +return VTQL_Parser::COMMA; +}else{ +return VTQL_Parser::VALUE; +} +} +function handleend($lexer, $val){ +return VTQL_Parser::SEMICOLON; +} +class VTQL_Lexer{ +private $index; +public $token; +public $value; +public $linenum; +public $state = 1; +private $data; +public $mandatory_states = array('select','column_list','from','table'); +public $optional_states = array('where', 'orderby', 'limit'); +public $mandatory ; +public $current_state ; +function __construct($data) +{ +$this->index = 0; +$this->data = $data; +$this->linenum = 1; +$this->mandatory = true; +$this->current_state = 0; +} +function __toString(){ +return $this->token.""; +} + + private $_yy_state = 1; + private $_yy_stack = array(); + + function yylex() + { + return $this->{'yylex' . $this->_yy_state}(); + } + + function yypushstate($state) + { + array_push($this->_yy_stack, $this->_yy_state); + $this->_yy_state = $state; + } + + function yypopstate() + { + $this->_yy_state = array_pop($this->_yy_stack); + } + + function yybegin($state) + { + $this->_yy_state = $state; + } + + + + function yylex1() + { + $tokenMap = array ( + 1 => 2, + 4 => 0, + ); + if ($this->index >= strlen($this->data)) { + return false; // end of input + } + $yy_global_pattern = "/^((\\w+|'(?:[^']|'')+'|\\(|\\)|(\\+|-)?\\d+|,|\\*|(?!<|>)=|<(?!=)|>(?!=)|<=|>=|!=|;))|^([ \t\r\n]+)/"; + + do { + if (preg_match($yy_global_pattern, substr($this->data, $this->index), $yymatches)) { + $yysubmatches = $yymatches; + $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns + if (!count($yymatches)) { + throw new Exception('Error: lexing failed because a rule matched' . + 'an empty string. Input "' . substr($this->data, + $this->index, 5) . '... state INITR'); + } + next($yymatches); // skip global match + $this->token = key($yymatches); // token number + if ($tokenMap[$this->token]) { + // extract sub-patterns for passing to lex function + $yysubmatches = array_slice($yysubmatches, $this->token + 1, + $tokenMap[$this->token]); + } else { + $yysubmatches = array(); + } + $this->value = current($yymatches); // token value + $r = $this->{'yy_r1_' . $this->token}($yysubmatches); + if ($r === null) { + $this->index += strlen($this->value); + $this->linenum += substr_count("\n", $this->value); + // accept this token + return true; + } elseif ($r === true) { + // we have changed state + // process this token in the new state + return $this->yylex(); + } elseif ($r === false) { + $this->index += strlen($this->value); + $this->linenum += substr_count("\n", $this->value); + if ($this->index >= strlen($this->data)) { + return false; // end of input + } + // skip this token + continue; + } else { $yy_yymore_patterns = array( + 1 => "^([ \t\r\n]+)", + 4 => "", + ); + + // yymore is needed + do { + if (!strlen($yy_yymore_patterns[$this->token])) { + throw new Exception('cannot do yymore for the last token'); + } + if (preg_match($yy_yymore_patterns[$this->token], + substr($this->data, $this->index), $yymatches)) { + $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns + next($yymatches); // skip global match + $this->token = key($yymatches); // token number + $this->value = current($yymatches); // token value + $this->linenum = substr_count("\n", $this->value); + } + } while ($this->{'yy_r1_' . $this->token}() !== null); + // accept + $this->index += strlen($this->value); + $this->linenum += substr_count("\n", $this->value); + return true; + } + } else { + throw new Exception('Unexpected input at line' . $this->linenum . + ': ' . $this->data[$this->index]); + } + break; + } while (true); + } // end function + + + const INITR = 1; + function yy_r1_1($yy_subpatterns) + { + +global $orderby; +//echo "
ql state: ",$this->current_state," ",$this->value,"
"; +if($this->mandatory){ +//echo "
ql state: ",$this->current_state," ",$this->value,"
"; +$handler = 'handle'.$this->mandatory_states[$this->current_state]; +$this->token = $handler($this, $this->value); +}else{ +$str = $this->value; +if(strcmp($this->value, ";")===0){ +$this->token = handleend($this, $this->value); +return; +} +if(strcasecmp($this->value, "order")===0){ +$orderby = true; +return false; +}else if(strcasecmp($this->value, "by") ===0 && $orderby ===true){ +$orderby = false; +$this->current_state = 1; +} +$index = array_search(strtolower($str), $this->optional_states, true); +if($index !== false){ +$this->current_state = $index; +} +$handler = 'handle'.$this->optional_states[$this->current_state]; +$this->token = $handler($this, $this->value); +}//$this->yypushstate($this->value); + } + function yy_r1_4($yy_subpatterns) + { + +return false; + } + +} + +?> diff --git a/include/Webservices/VTQL_Parser.php b/include/Webservices/VTQL_Parser.php new file mode 100644 index 0000000..fa6bec7 --- /dev/null +++ b/include/Webservices/VTQL_Parser.php @@ -0,0 +1,1656 @@ +string = $s->string; + $this->metadata = $s->metadata; + } else { + $this->string = (string) $s; + if ($m instanceof VTQL_ParseryyToken) { + $this->metadata = $m->metadata; + } elseif (is_array($m)) { + $this->metadata = $m; + } + } + } + + function __toString() + { + return $this->_string; + } + + function offsetExists($offset) + { + return isset($this->metadata[$offset]); + } + + function offsetGet($offset) + { + return $this->metadata[$offset]; + } + + function offsetSet($offset, $value) + { + if ($offset === null) { + if (isset($value[0])) { + $x = ($value instanceof VTQL_ParseryyToken) ? + $value->metadata : $value; + $this->metadata = array_merge($this->metadata, $x); + return; + } + $offset = count($this->metadata); + } + if ($value === null) { + return; + } + if ($value instanceof VTQL_ParseryyToken) { + if ($value->metadata) { + $this->metadata[$offset] = $value->metadata; + } + } elseif ($value) { + $this->metadata[$offset] = $value; + } + } + + function offsetUnset($offset) + { + unset($this->metadata[$offset]); + } +} + +/** The following structure represents a single element of the + * parser's stack. Information stored includes: + * + * + The state number for the parser at this level of the stack. + * + * + The value of the token stored at this level of the stack. + * (In other words, the "major" token.) + * + * + The semantic value stored at this level of the stack. This is + * the information used by the action routines in the grammar. + * It is sometimes called the "minor" token. + */ +class VTQL_ParseryyStackEntry +{ + public $stateno; /* The state-number */ + public $major; /* The major token value. This is the code + ** number for the token at this stack level */ + public $minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; + +// code external to the class is included here + +// declare_class is output here +#line 451 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" +class VTQL_Parser#line 102 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +{ +/* First off, code is included which follows the "include_class" declaration +** in the input file. */ +#line 199 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + +/* +add this rule to add parenthesis support. +condition ::= PARENOPEN expr_set expr(E) PARENCLOSE. +sample format(for contacts) for generated sql object +Array ( + [column_list] => c4,c3,c2,c1 + [tableName] => vtiger_crmentity,vtiger_contactdetails,vtiger_contactaddress,vtiger_contactsubdetails,vtiger_contactscf,vtiger_customerdetails + [where_condition] => Array ( + [column_operators] => Array ( + [0] => = + [1] => = + [2] => = + ) + [column_names] => Array ( + [0] => c1 + [1] => c2 + [2] => c3 + ) + [column_values] => Array ( + [0] => 'llet me' + [1] => 45 + [2] => -1 + ) + //TO BE DONE + [grouping] => Array ( + [0] => Array ( + [0] => 1 + [1] => 2 + ) + ) + [operators] => Array ( + [0] => and + [1] => or + ) + ) + [orderby] => Array ( + [0] => c4 + [1] => c5 + ) + [select] => SELECT + [from] => from + [semi_colon] => ; +)*/ + private $out; + public $lex; + private $success ; + private $query ; + private $error_msg; + private $syntax_error; + private $user; +function __construct($user, $lex,$out){ + if(!is_array($out)){ + $out = array(); + } + $this->out = &$out; + $this->lex = $lex; + $this->success = false; + $this->error_msg =''; + $this->query = ''; + $this->syntax_error = false; + $this->user = $user; +} + +function __toString(){ + return $this->value.""; +} +function buildSelectStmt($sqlDump){ + $meta = $sqlDump['meta']; + $fieldcol = $meta->getFieldColumnMapping(); + $columnTable = $meta->getColumnTableMapping(); + $this->query = 'SELECT '; + if(in_array('*', $sqlDump['column_list'])){ + $i=0; + foreach($fieldcol as $field=>$col){ + if($i===0){ + $this->query = $this->query.$columnTable[$col].'.'.$col; + $i++; + }else{ + $this->query = $this->query.','.$columnTable[$col].'.'.$col; + } + } + }else if(in_array('count(*)', $sqlDump['column_list'])){ + $this->query = $this->query." COUNT(*)"; + }else{ + $i=0; + foreach($sqlDump['column_list'] as $ind=>$field){ + if(!$fieldcol[$field]){ + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED, "Permission to access '.$field.' attribute denied."); + } + if($i===0){ + $this->query = $this->query.$columnTable[$fieldcol[$field]].'.'.$fieldcol[$field]; + $i++; + }else{ + $this->query = $this->query.','.$columnTable[$fieldcol[$field]].'.'.$fieldcol[$field]; + } + } + } + $this->query = $this->query.' FROM '.$sqlDump['tableName'].$sqlDump['defaultJoinConditions']; + $deletedQuery = $meta->getEntityDeletedQuery(); + $accessControlQuery = $meta->getEntityAccessControlQuery(); + $this->query = $this->query.' '.$accessControlQuery; + if($sqlDump['where_condition']){ + if((sizeof($sqlDump['where_condition']['column_names']) == + sizeof($sqlDump['where_condition']['column_values'])) && + (sizeof($sqlDump['where_condition']['column_operators']) == sizeof($sqlDump['where_condition']['operators'])+1)){ + $this->query = $this->query.' WHERE ('; + $i=0; + $referenceFields = $meta->getReferenceFieldDetails(); + $ownerFields = $meta->getOwnerFields(); + for(;$i$value){ + if(strpos($value,'x')===false){ + throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Id specified is incorrect"); + } + } + $whereValue = array_map(array($this, 'getReferenceValue'),$whereValue); + }else if(strpos($whereValue,'x')!==false){ + $whereValue = $this->getReferenceValue($whereValue); + if(strcasecmp($whereOperator,'like')===0){ + $whereValue = "'".$whereValue."'"; + } + }else{ + throw new WebServiceException(WebServiceErrorCode::$INVALIDID,"Id specified is incorrect"); + } + }else if(in_array($whereField,$ownerFields)){ + if(is_array($whereValue)){ + $groupId = array_map(array($this, 'getOwner'),$whereValue); + }else{ + $groupId = $this->getOwner($whereValue); + if(strcasecmp($whereOperator,'like')===0){ + $groupId = "'$groupId'"; + } + } + $whereValue = $groupId; + } + if(is_array($whereValue)){ + $whereValue = "(".implode(',',$whereValue).")"; + }elseif(strcasecmp($whereOperator, 'in') === 0){ + $whereValue = "($whereValue)"; + } + $this->query = $this->query.$columnTable[$fieldcol[$whereField]].'.'. + $fieldcol[$whereField]." ".$whereOperator." ".$whereValue; + if($i query = $this->query.' '; + $this->query = $this->query.$sqlDump['where_condition']['operators'][$i].' '; + } + } + }else{ + throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, "columns data inappropriate"); + } + $this->query = $this->query.")"; + $nextToken = ' AND '; + }else{ + if(!empty($deletedQuery)){ + $nextToken = " WHERE "; + } + } + if(strcasecmp('calendar',$this->out['moduleName'])===0){ + $this->query = $this->query." $nextToken activitytype='Task' AND "; + }elseif(strcasecmp('events',$this->out['moduleName'])===0){ + $this->query = $this->query."$nextToken activitytype!='Emails' AND activitytype!='Task' AND "; + }else if(strcasecmp('emails',$this->out['moduleName'])===0){ + $this->query = $this->query."$nextToken activitytype='Emails' AND "; + }elseif(!empty($deletedQuery)){ + $this->query = $this->query.$nextToken; + } + + $this->query = $this->query.' '.$deletedQuery; + + if($sqlDump['orderby']){ + $i=0; + $this->query = $this->query.' ORDER BY '; + foreach($sqlDump['orderby'] as $ind=>$field){ + if($i===0){ + $this->query = $this->query.$columnTable[$fieldcol[$field]].".".$fieldcol[$field]; + $i++; + }else{ + $this->query = $this->query.','.$columnTable[$fieldcol[$field]].".".$fieldcol[$field]; + } + } + if($sqlDump['sortOrder']) { + $this->query .= ' '.$sqlDump['sortOrder']; + } + } + if($sqlDump['limit']){ + $i=0; + $offset =false; + if(sizeof($sqlDump['limit'])>1){ + $offset = true; + } + $this->query = $this->query.' LIMIT '; + foreach($sqlDump['limit'] as $ind=>$field){ + if(!$offset){ + $field = ($field>100)? 100: $field; + } + if($i===0){ + $this->query = $this->query.$field; + $i++; + $offset = false; + }else{ + $this->query = $this->query.','.$field; + } + } + }else{ + $this->query = $this->query.' LIMIT 100'; + } + $this->query = $this->query.';'; +} +function getTables($sqlDump,$columns){ + $meta = $sqlDump['meta']; + $coltable = $meta->getColumnTableMapping(); + $tables = array(); + foreach($columns as $ind=>$col){ + $tables[$coltable[$col]] = $coltable[$col]; + } + $tables = array_keys($tables); + return ($tables); +} +function getReferenceValue($whereValue){ + $whereValue = trim($whereValue,'\'"'); + $whereValue = vtws_getIdComponents($whereValue); + $whereValue = $whereValue[1]; + return $whereValue; +} +function getOwner($whereValue){ + $whereValue = trim($whereValue,'\'"'); + $whereValue = vtws_getIdComponents($whereValue); + $whereValue = $whereValue[1]; + return $whereValue; +} +function isSuccess(){ + return $this->success; +} +function getErrorMsg(){ + return $this->error_msg; +} +function getQuery(){ + return $this->query; +} +function getObjectMetaData(){ + return $this->out['meta']; +} +#line 359 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" + +/* Next is all token values, as class constants +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ + const SELECT = 1; + const FRM = 2; + const COLUMNNAME = 3; + const ASTERISK = 4; + const COUNT = 5; + const PARENOPEN = 6; + const PARENCLOSE = 7; + const COMMA = 8; + const TABLENAME = 9; + const WHERE = 10; + const LOGICAL_AND = 11; + const LOGICAL_OR = 12; + const VALUE = 13; + const EQ = 14; + const LT = 15; + const GT = 16; + const LTE = 17; + const GTE = 18; + const NE = 19; + const IN = 20; + const LIKE = 21; + const ORDERBY = 22; + const ASC = 23; + const DESC = 24; + const LIMIT = 25; + const SEMICOLON = 26; + const YY_NO_ACTION = 102; + const YY_ACCEPT_ACTION = 101; + const YY_ERROR_ACTION = 100; + +/* Next are that tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < self::YYNSTATE Shift N. That is, +** push the lookahead +** token onto the stack +** and goto state N. +** +** self::YYNSTATE <= N < self::YYNSTATE+self::YYNRULE Reduce by rule N-YYNSTATE. +** +** N == self::YYNSTATE+self::YYNRULE A syntax error has occurred. +** +** N == self::YYNSTATE+self::YYNRULE+1 The parser accepts its +** input. (and concludes parsing) +** +** N == self::YYNSTATE+self::YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large static array $yy_action. +** Given state S and lookahead X, the action is computed as +** +** self::$yy_action[self::$yy_shift_ofst[S] + X ] +** +** If the index value self::$yy_shift_ofst[S]+X is out of range or if the value +** self::$yy_lookahead[self::$yy_shift_ofst[S]+X] is not equal to X or if +** self::$yy_shift_ofst[S] is equal to self::YY_SHIFT_USE_DFLT, it means that +** the action is not in the table and that self::$yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the static $yy_reduce_ofst array is used in place of +** the static $yy_shift_ofst array and self::YY_REDUCE_USE_DFLT is used in place of +** self::YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** self::$yy_action A single table containing all actions. +** self::$yy_lookahead A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** self::$yy_shift_ofst For each state, the offset into self::$yy_action for +** shifting terminals. +** self::$yy_reduce_ofst For each state, the offset into self::$yy_action for +** shifting non-terminals after a reduce. +** self::$yy_default Default action for each state. +*/ + const YY_SZ_ACTTAB = 60; +static public $yy_action = array( + /* 0 */ 36, 29, 28, 30, 31, 38, 39, 37, 41, 26, + /* 10 */ 18, 57, 7, 10, 27, 22, 50, 55, 17, 18, + /* 20 */ 15, 9, 12, 42, 43, 35, 25, 16, 33, 51, + /* 30 */ 52, 101, 56, 21, 47, 2, 19, 46, 52, 44, + /* 40 */ 3, 20, 53, 49, 24, 34, 23, 6, 40, 45, + /* 50 */ 1, 4, 13, 54, 11, 48, 5, 14, 32, 8, + ); + static public $yy_lookahead = array( + /* 0 */ 14, 15, 16, 17, 18, 19, 20, 21, 41, 42, + /* 10 */ 43, 13, 44, 33, 46, 47, 11, 12, 42, 43, + /* 20 */ 37, 38, 2, 23, 24, 4, 5, 30, 8, 7, + /* 30 */ 8, 28, 29, 36, 35, 22, 4, 13, 8, 8, + /* 40 */ 1, 6, 26, 3, 8, 3, 13, 3, 7, 45, + /* 50 */ 40, 6, 34, 39, 31, 48, 10, 32, 9, 25, +); + const YY_SHIFT_USE_DFLT = -15; + const YY_SHIFT_MAX = 27; + static public $yy_shift_ofst = array( + /* 0 */ 39, 45, -15, 21, -15, -15, -14, 0, 33, 44, + /* 10 */ 34, 46, 49, 16, 13, 5, 20, 22, -2, 41, + /* 20 */ 32, 42, 40, 36, 24, 35, 30, 31, +); + const YY_REDUCE_USE_DFLT = -34; + const YY_REDUCE_MAX = 14; + static public $yy_reduce_ofst = array( + /* 0 */ 3, -33, -32, -3, -24, -17, 10, 4, 7, 14, + /* 10 */ 18, 25, 23, -1, -20, +); + static public $yyExpectedTokens = array( + /* 0 */ array(1, ), + /* 1 */ array(6, ), + /* 2 */ array(), + /* 3 */ array(4, 5, ), + /* 4 */ array(), + /* 5 */ array(), + /* 6 */ array(14, 15, 16, 17, 18, 19, 20, 21, ), + /* 7 */ array(23, 24, ), + /* 8 */ array(13, ), + /* 9 */ array(3, ), + /* 10 */ array(25, ), + /* 11 */ array(10, ), + /* 12 */ array(9, ), + /* 13 */ array(26, ), + /* 14 */ array(22, ), + /* 15 */ array(11, 12, ), + /* 16 */ array(2, 8, ), + /* 17 */ array(7, 8, ), + /* 18 */ array(13, ), + /* 19 */ array(7, ), + /* 20 */ array(4, ), + /* 21 */ array(3, ), + /* 22 */ array(3, ), + /* 23 */ array(8, ), + /* 24 */ array(13, ), + /* 25 */ array(6, ), + /* 26 */ array(8, ), + /* 27 */ array(8, ), + /* 28 */ array(), + /* 29 */ array(), + /* 30 */ array(), + /* 31 */ array(), + /* 32 */ array(), + /* 33 */ array(), + /* 34 */ array(), + /* 35 */ array(), + /* 36 */ array(), + /* 37 */ array(), + /* 38 */ array(), + /* 39 */ array(), + /* 40 */ array(), + /* 41 */ array(), + /* 42 */ array(), + /* 43 */ array(), + /* 44 */ array(), + /* 45 */ array(), + /* 46 */ array(), + /* 47 */ array(), + /* 48 */ array(), + /* 49 */ array(), + /* 50 */ array(), + /* 51 */ array(), + /* 52 */ array(), + /* 53 */ array(), + /* 54 */ array(), + /* 55 */ array(), + /* 56 */ array(), + /* 57 */ array(), +); + static public $yy_default = array( + /* 0 */ 100, 77, 91, 64, 77, 71, 100, 94, 100, 100, + /* 10 */ 96, 67, 100, 100, 87, 66, 100, 100, 100, 100, + /* 20 */ 100, 100, 100, 97, 100, 100, 74, 88, 80, 79, + /* 30 */ 81, 82, 65, 63, 60, 61, 78, 85, 83, 84, + /* 40 */ 62, 72, 92, 93, 90, 86, 98, 59, 95, 89, + /* 50 */ 69, 73, 76, 99, 68, 70, 58, 75, +); +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** self::YYNOCODE is a number which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** self::YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** self::YYSTACKDEPTH is the maximum depth of the parser's stack. +** self::YYNSTATE the combined number of states. +** self::YYNRULE the number of rules in the grammar +** self::YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ + const YYNOCODE = 50; + const YYSTACKDEPTH = 100; + const YYNSTATE = 58; + const YYNRULE = 42; + const YYERRORSYMBOL = 27; + const YYERRSYMDT = 'yy0'; + const YYFALLBACK = 0; + /** The next table maps tokens into fallback tokens. If a construct + * like the following: + * + * %fallback ID X Y Z. + * + * appears in the grammer, then ID becomes a fallback token for X, Y, + * and Z. Whenever one of the tokens X, Y, or Z is input to the parser + * but it does not parse, the type of the token is changed to ID and + * the parse is retried before an error is thrown. + */ + static public $yyFallback = array( + ); + /** + * Turn parser tracing on by giving a stream to which to write the trace + * and a prompt to preface each trace message. Tracing is turned off + * by making either argument NULL + * + * Inputs: + * + * - A stream resource to which trace output should be written. + * If NULL, then tracing is turned off. + * - A prefix string written at the beginning of every + * line of trace output. If NULL, then tracing is + * turned off. + * + * Outputs: + * + * - None. + * @param resource + * @param string + */ + static function Trace($TraceFILE, $zTracePrompt) + { + if (!$TraceFILE) { + $zTracePrompt = 0; + } elseif (!$zTracePrompt) { + $TraceFILE = 0; + } + self::$yyTraceFILE = $TraceFILE; + self::$yyTracePrompt = $zTracePrompt; + } + + /** + * Output debug information to output (php://output stream) + */ + static function PrintTrace() + { + self::$yyTraceFILE = fopen('php://output', 'w'); + self::$yyTracePrompt = ''; + } + + /** + * @var resource|0 + */ + static public $yyTraceFILE; + /** + * String to prepend to debug output + * @var string|0 + */ + static public $yyTracePrompt; + /** + * @var int + */ + public $yyidx; /* Index of top element in stack */ + /** + * @var int + */ + public $yyerrcnt; /* Shifts left before out of the error */ + /** + * @var array + */ + public $yystack = array(); /* The parser's stack */ + + /** + * For tracing shifts, the names of all terminals and nonterminals + * are required. The following table supplies these names + * @var array + */ + static public $yyTokenName = array( + '$', 'SELECT', 'FRM', 'COLUMNNAME', + 'ASTERISK', 'COUNT', 'PARENOPEN', 'PARENCLOSE', + 'COMMA', 'TABLENAME', 'WHERE', 'LOGICAL_AND', + 'LOGICAL_OR', 'VALUE', 'EQ', 'LT', + 'GT', 'LTE', 'GTE', 'NE', + 'IN', 'LIKE', 'ORDERBY', 'ASC', + 'DESC', 'LIMIT', 'SEMICOLON', 'error', + 'sql', 'select_statement', 'selectcol_list', 'table_list', + 'where_condition', 'order_clause', 'limit_clause', 'end_stmt', + 'selectcolumn_exp', 'condition', 'expr_set', 'expr', + 'logical_term', 'valuelist', 'valueref', 'value_exp', + 'column_group', 'clause', 'column_list', 'column_exp', + 'limit_set', + ); + + /** + * For tracing reduce actions, the names of all rules are required. + * @var array + */ + static public $yyRuleName = array( + /* 0 */ "sql ::= select_statement", + /* 1 */ "select_statement ::= SELECT selectcol_list FRM table_list where_condition order_clause limit_clause end_stmt", + /* 2 */ "selectcol_list ::= selectcolumn_exp COLUMNNAME", + /* 3 */ "selectcol_list ::= ASTERISK", + /* 4 */ "selectcol_list ::= COUNT PARENOPEN ASTERISK PARENCLOSE", + /* 5 */ "selectcolumn_exp ::= selectcol_list COMMA", + /* 6 */ "selectcolumn_exp ::=", + /* 7 */ "table_list ::= TABLENAME", + /* 8 */ "where_condition ::= WHERE condition", + /* 9 */ "where_condition ::=", + /* 10 */ "condition ::= expr_set expr", + /* 11 */ "expr_set ::= condition LOGICAL_AND", + /* 12 */ "expr_set ::= condition LOGICAL_OR", + /* 13 */ "expr_set ::=", + /* 14 */ "expr ::= COLUMNNAME logical_term valuelist", + /* 15 */ "valuelist ::= PARENOPEN valueref PARENCLOSE", + /* 16 */ "valuelist ::= valueref", + /* 17 */ "valueref ::= value_exp VALUE", + /* 18 */ "value_exp ::= valueref COMMA", + /* 19 */ "value_exp ::=", + /* 20 */ "logical_term ::= EQ", + /* 21 */ "logical_term ::= LT", + /* 22 */ "logical_term ::= GT", + /* 23 */ "logical_term ::= LTE", + /* 24 */ "logical_term ::= GTE", + /* 25 */ "logical_term ::= NE", + /* 26 */ "logical_term ::= IN", + /* 27 */ "logical_term ::= LIKE", + /* 28 */ "order_clause ::= ORDERBY column_group clause", + /* 29 */ "order_clause ::=", + /* 30 */ "column_group ::= column_list", + /* 31 */ "column_list ::= column_exp COLUMNNAME", + /* 32 */ "column_exp ::= column_list COMMA", + /* 33 */ "column_exp ::=", + /* 34 */ "clause ::= ASC", + /* 35 */ "clause ::= DESC", + /* 36 */ "clause ::=", + /* 37 */ "limit_clause ::= LIMIT limit_set", + /* 38 */ "limit_clause ::=", + /* 39 */ "limit_set ::= VALUE", + /* 40 */ "limit_set ::= VALUE COMMA VALUE", + /* 41 */ "end_stmt ::= SEMICOLON", + ); + + /** + * This function returns the symbolic name associated with a token + * value. + * @param int + * @return string + */ + function tokenName($tokenType) + { + if ($tokenType === 0) { + return 'End of Input'; + } + if ($tokenType > 0 && $tokenType < count(self::$yyTokenName)) { + return self::$yyTokenName[$tokenType]; + } else { + return "Unknown"; + } + } + + /** + * The following function deletes the value associated with a + * symbol. The symbol can be either a terminal or nonterminal. + * @param int the symbol code + * @param mixed the symbol's value + */ + static function yy_destructor($yymajor, $yypminor) + { + switch ($yymajor) { + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + default: break; /* If no destructor action specified: do nothing */ + } + } + + /** + * Pop the parser's stack once. + * + * If there is a destructor routine associated with the token which + * is popped from the stack, then call it. + * + * Return the major token number for the symbol popped. + * @param VTQL_ParseryyParser + * @return int + */ + function yy_pop_parser_stack() + { + if (!count($this->yystack)) { + return; + } + $yytos = array_pop($this->yystack); + if (self::$yyTraceFILE && $this->yyidx >= 0) { + fwrite(self::$yyTraceFILE, + self::$yyTracePrompt . 'Popping ' . self::$yyTokenName[$yytos->major] . + "\n"); + } + $yymajor = $yytos->major; + self::yy_destructor($yymajor, $yytos->minor); + $this->yyidx--; + return $yymajor; + } + + /** + * Deallocate and destroy a parser. Destructors are all called for + * all stack elements before shutting the parser down. + */ + function __destruct() + { + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + if (is_resource(self::$yyTraceFILE)) { + fclose(self::$yyTraceFILE); + } + } + + /** + * Based on the current state and parser stack, get a list of all + * possible lookahead tokens + * @param int + * @return array + */ + function yy_get_expected_tokens($token) + { + $state = $this->yystack[$this->yyidx]->stateno; + $expected = self::$yyExpectedTokens[$state]; + if (in_array($token, self::$yyExpectedTokens[$state], true)) { + return $expected; + } + $stack = $this->yystack; + $yyidx = $this->yyidx; + do { + $yyact = $this->yy_find_shift_action($token); + if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) { + // reduce action + $done = 0; + do { + if ($done++ == 100) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // too much recursion prevents proper detection + // so give up + return array_unique($expected); + } + $yyruleno = $yyact - self::YYNSTATE; + $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs']; + $nextstate = $this->yy_find_reduce_action( + $this->yystack[$this->yyidx]->stateno, + self::$yyRuleInfo[$yyruleno]['lhs']); + if (isset(self::$yyExpectedTokens[$nextstate])) { + $expected += self::$yyExpectedTokens[$nextstate]; + if (in_array($token, + self::$yyExpectedTokens[$nextstate], true)) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + return array_unique($expected); + } + } + if ($nextstate < self::YYNSTATE) { + // we need to shift a non-terminal + $this->yyidx++; + $x = new VTQL_ParseryyStackEntry; + $x->stateno = $nextstate; + $x->major = self::$yyRuleInfo[$yyruleno]['lhs']; + $this->yystack[$this->yyidx] = $x; + continue 2; + } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // the last token was just ignored, we can't accept + // by ignoring input, this is in essence ignoring a + // syntax error! + return array_unique($expected); + } elseif ($nextstate === self::YY_NO_ACTION) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // input accepted, but not shifted (I guess) + return $expected; + } else { + $yyact = $nextstate; + } + } while (true); + } + break; + } while (true); + return array_unique($expected); + } + + /** + * Based on the parser state and current parser stack, determine whether + * the lookahead token is possible. + * + * The parser will convert the token value to an error token if not. This + * catches some unusual edge cases where the parser would fail. + * @param int + * @return bool + */ + function yy_is_expected_token($token) + { + if ($token === 0) { + return true; // 0 is not part of this + } + $state = $this->yystack[$this->yyidx]->stateno; + if (in_array($token, self::$yyExpectedTokens[$state], true)) { + return true; + } + $stack = $this->yystack; + $yyidx = $this->yyidx; + do { + $yyact = $this->yy_find_shift_action($token); + if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) { + // reduce action + $done = 0; + do { + if ($done++ == 100) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // too much recursion prevents proper detection + // so give up + return true; + } + $yyruleno = $yyact - self::YYNSTATE; + $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs']; + $nextstate = $this->yy_find_reduce_action( + $this->yystack[$this->yyidx]->stateno, + self::$yyRuleInfo[$yyruleno]['lhs']); + if (isset(self::$yyExpectedTokens[$nextstate]) && + in_array($token, self::$yyExpectedTokens[$nextstate], true)) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + return true; + } + if ($nextstate < self::YYNSTATE) { + // we need to shift a non-terminal + $this->yyidx++; + $x = new VTQL_ParseryyStackEntry; + $x->stateno = $nextstate; + $x->major = self::$yyRuleInfo[$yyruleno]['lhs']; + $this->yystack[$this->yyidx] = $x; + continue 2; + } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + if (!$token) { + // end of input: this is valid + return true; + } + // the last token was just ignored, we can't accept + // by ignoring input, this is in essence ignoring a + // syntax error! + return false; + } elseif ($nextstate === self::YY_NO_ACTION) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // input accepted, but not shifted (I guess) + return true; + } else { + $yyact = $nextstate; + } + } while (true); + } + break; + } while (true); + return true; + } + + /** + * Find the appropriate action for a parser given the terminal + * look-ahead token iLookAhead. + * + * If the look-ahead token is YYNOCODE, then check to see if the action is + * independent of the look-ahead. If it is, return the action, otherwise + * return YY_NO_ACTION. + * @param int The look-ahead token + */ + function yy_find_shift_action($iLookAhead) + { + $stateno = $this->yystack[$this->yyidx]->stateno; + + /* if ($this->yyidx < 0) return self::YY_NO_ACTION; */ + if (!isset(self::$yy_shift_ofst[$stateno])) { + // no shift actions + return self::$yy_default[$stateno]; + } + $i = self::$yy_shift_ofst[$stateno]; + if ($i === self::YY_SHIFT_USE_DFLT) { + return self::$yy_default[$stateno]; + } + if ($iLookAhead == self::YYNOCODE) { + return self::YY_NO_ACTION; + } + $i += $iLookAhead; + if ($i < 0 || $i >= self::YY_SZ_ACTTAB || + self::$yy_lookahead[$i] != $iLookAhead) { + if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback) + && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) { + if (self::$yyTraceFILE) { + fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " . + self::$yyTokenName[$iLookAhead] . " => " . + self::$yyTokenName[$iFallback] . "\n"); + } + return $this->yy_find_shift_action($iFallback); + } + return self::$yy_default[$stateno]; + } else { + return self::$yy_action[$i]; + } + } + + /** + * Find the appropriate action for a parser given the non-terminal + * look-ahead token $iLookAhead. + * + * If the look-ahead token is self::YYNOCODE, then check to see if the action is + * independent of the look-ahead. If it is, return the action, otherwise + * return self::YY_NO_ACTION. + * @param int Current state number + * @param int The look-ahead token + */ + function yy_find_reduce_action($stateno, $iLookAhead) + { + /* $stateno = $this->yystack[$this->yyidx]->stateno; */ + + if (!isset(self::$yy_reduce_ofst[$stateno])) { + return self::$yy_default[$stateno]; + } + $i = self::$yy_reduce_ofst[$stateno]; + if ($i == self::YY_REDUCE_USE_DFLT) { + return self::$yy_default[$stateno]; + } + if ($iLookAhead == self::YYNOCODE) { + return self::YY_NO_ACTION; + } + $i += $iLookAhead; + if ($i < 0 || $i >= self::YY_SZ_ACTTAB || + self::$yy_lookahead[$i] != $iLookAhead) { + return self::$yy_default[$stateno]; + } else { + return self::$yy_action[$i]; + } + } + + /** + * Perform a shift action. + * @param int The new state to shift in + * @param int The major token to shift in + * @param mixed the minor token to shift in + */ + function yy_shift($yyNewState, $yyMajor, $yypMinor) + { + $this->yyidx++; + if ($this->yyidx >= self::YYSTACKDEPTH) { + $this->yyidx--; + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will execute if the parser + ** stack ever overflows */ +#line 462 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + + throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, "Parser stack overflow"); +#line 1046 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" + return; + } + $yytos = new VTQL_ParseryyStackEntry; + $yytos->stateno = $yyNewState; + $yytos->major = $yyMajor; + $yytos->minor = $yypMinor; + array_push($this->yystack, $yytos); + if (self::$yyTraceFILE && $this->yyidx > 0) { + fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt, + $yyNewState); + fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt); + for($i = 1; $i <= $this->yyidx; $i++) { + fprintf(self::$yyTraceFILE, " %s", + self::$yyTokenName[$this->yystack[$i]->major]); + } + fwrite(self::$yyTraceFILE,"\n"); + } + } + + /** + * The following table contains information about every rule that + * is used during the reduce. + * + *
+     * array(
+     *  array(
+     *   int $lhs;         Symbol on the left-hand side of the rule
+     *   int $nrhs;     Number of right-hand side symbols in the rule
+     *  ),...
+     * );
+     * 
+ */ + static public $yyRuleInfo = array( + array( 'lhs' => 28, 'rhs' => 1 ), + array( 'lhs' => 29, 'rhs' => 8 ), + array( 'lhs' => 30, 'rhs' => 2 ), + array( 'lhs' => 30, 'rhs' => 1 ), + array( 'lhs' => 30, 'rhs' => 4 ), + array( 'lhs' => 36, 'rhs' => 2 ), + array( 'lhs' => 36, 'rhs' => 0 ), + array( 'lhs' => 31, 'rhs' => 1 ), + array( 'lhs' => 32, 'rhs' => 2 ), + array( 'lhs' => 32, 'rhs' => 0 ), + array( 'lhs' => 37, 'rhs' => 2 ), + array( 'lhs' => 38, 'rhs' => 2 ), + array( 'lhs' => 38, 'rhs' => 2 ), + array( 'lhs' => 38, 'rhs' => 0 ), + array( 'lhs' => 39, 'rhs' => 3 ), + array( 'lhs' => 41, 'rhs' => 3 ), + array( 'lhs' => 41, 'rhs' => 1 ), + array( 'lhs' => 42, 'rhs' => 2 ), + array( 'lhs' => 43, 'rhs' => 2 ), + array( 'lhs' => 43, 'rhs' => 0 ), + array( 'lhs' => 40, 'rhs' => 1 ), + array( 'lhs' => 40, 'rhs' => 1 ), + array( 'lhs' => 40, 'rhs' => 1 ), + array( 'lhs' => 40, 'rhs' => 1 ), + array( 'lhs' => 40, 'rhs' => 1 ), + array( 'lhs' => 40, 'rhs' => 1 ), + array( 'lhs' => 40, 'rhs' => 1 ), + array( 'lhs' => 40, 'rhs' => 1 ), + array( 'lhs' => 33, 'rhs' => 3 ), + array( 'lhs' => 33, 'rhs' => 0 ), + array( 'lhs' => 44, 'rhs' => 1 ), + array( 'lhs' => 46, 'rhs' => 2 ), + array( 'lhs' => 47, 'rhs' => 2 ), + array( 'lhs' => 47, 'rhs' => 0 ), + array( 'lhs' => 45, 'rhs' => 1 ), + array( 'lhs' => 45, 'rhs' => 1 ), + array( 'lhs' => 45, 'rhs' => 0 ), + array( 'lhs' => 34, 'rhs' => 2 ), + array( 'lhs' => 34, 'rhs' => 0 ), + array( 'lhs' => 48, 'rhs' => 1 ), + array( 'lhs' => 48, 'rhs' => 3 ), + array( 'lhs' => 35, 'rhs' => 1 ), + ); + + /** + * The following table contains a mapping of reduce action to method name + * that handles the reduction. + * + * If a rule is not set, it has no handler. + */ + static public $yyReduceMap = array( + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 7 => 7, + 11 => 11, + 12 => 11, + 14 => 14, + 17 => 17, + 20 => 20, + 21 => 21, + 22 => 22, + 23 => 23, + 24 => 24, + 25 => 25, + 26 => 26, + 27 => 27, + 31 => 31, + 34 => 34, + 35 => 35, + 39 => 39, + 40 => 40, + 41 => 41, + ); + /* Beginning here are the reduction cases. A typical example + ** follows: + ** #line + ** function yy_r0($yymsp){ ... } // User supplied code + ** #line + */ +#line 5 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r1(){ +if($this->yystack[$this->yyidx + -7]->minor){ +$this->out['select'] = $this->yystack[$this->yyidx + -7]->minor; +} +if($this->yystack[$this->yyidx + -5]->minor){ +$this->out['from'] = $this->yystack[$this->yyidx + -5]->minor ; +} +if(SEMI){ +$this->out['semi_colon'] = SEMI; +} +if($this->out['select']){ +$this->buildSelectStmt($this->out); +} + } +#line 1176 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 19 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r2(){ +$this->out['column_list'][] = $this->yystack[$this->yyidx + 0]->minor; + } +#line 1181 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 22 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r3(){ +$this->out['column_list'][] = $this->yystack[$this->yyidx + 0]->minor; + } +#line 1186 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 25 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r4(){ +$this->out['column_list'][] = 'count(*)'; + } +#line 1191 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 30 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r7(){ +if(!in_array("*", $this->out["column_list"]) && !in_array("count(*)", array_map(strtolower, $this->out["column_list"]))){ +if(!in_array("id",$this->out["column_list"])){ + $this->out["column_list"][] = "id"; +} +} +$moduleName = $this->yystack[$this->yyidx + 0]->minor; +if(!$moduleName){ + $this->syntax_error = true; + throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, "There is an syntax error in query"); +} +global $adb; +$handler = vtws_getModuleHandlerFromName($moduleName,$this->user); +$objectMeta = $handler->getMeta(); +$this->out['moduleName'] = $moduleName; +$this->out['tableName'] = implode(',',$objectMeta->getEntityTableList()); + } +#line 1210 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 50 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r11(){ +$this->out['where_condition']['operators'][] = $this->yystack[$this->yyidx + 0]->minor; + } +#line 1215 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 57 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r14(){ +$this->out['columnDone']=true; +$this->out['where_condition']['column_names'][] = $this->yystack[$this->yyidx + -2]->minor; +if(strcmp($this->yystack[$this->yyidx + -2]->minor, 'id')===0){ +$prev = $this->out['where_condition']['column_values'][sizeof($this->out['where_condition']['column_values'])-1]; +if(is_array($prev)){ +$new = array(); +foreach($prev as $ind=>$val){ +$val = trim($val,'\'"'); +$value = vtws_getIdComponents($val); +$new[] = $value[1]; +} +$this->out['where_condition']['column_values'][sizeof($this->out['where_condition']['column_values'])-1] = $new; +}else{ +$prev = trim($prev,'\'"'); +$value = vtws_getIdComponents($prev); +if(strcasecmp($this->out['where_condition']['column_operators'][sizeof($this->out['where_condition']['column_operators'])-1],'like')===0){ +$value[1] = "'".$value[1]."'"; +} +$this->out['where_condition']['column_values'][sizeof($this->out['where_condition']['column_values'])-1] = $value[1]; +} +} + } +#line 1240 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 82 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r17(){ +$length = sizeof($this->out['where_condition']['column_values']); +$pos = $length - 1; +if($pos < 0){ +$pos = 0; +} +if(strcasecmp($this->out['where_condition']['column_operators'][$pos],"in")===0 && + !empty($this->out['where_condition']['column_values'][$pos]) && !$this->out['columnDone']){ +if(!is_array($this->out['where_condition']['column_values'][$pos])){ +$prev = $this->out['where_condition']['column_values'][$pos]; +$this->out['where_condition']['column_values'][$pos] = array(); +$this->out['where_condition']['column_values'][$pos][] = $prev; +} +$this->out['where_condition']['column_values'][$pos][] = $this->yystack[$this->yyidx + 0]->minor; +}else{ +$this->out['columnDone'] = false; +$this->out['where_condition']['column_values'][] = $this->yystack[$this->yyidx + 0]->minor; +} + } +#line 1261 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 103 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r20(){ +$this->out['where_condition']['column_operators'][] = '='; + } +#line 1266 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 106 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r21(){ +$this->out['where_condition']['column_operators'][] = '<'; + } +#line 1271 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 109 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r22(){ +$this->out['where_condition']['column_operators'][] = '>'; + } +#line 1276 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 112 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r23(){ +$this->out['where_condition']['column_operators'][] = '<='; + } +#line 1281 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 115 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r24(){ +$this->out['where_condition']['column_operators'][] = '>='; + } +#line 1286 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 118 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r25(){ +$this->out['where_condition']['column_operators'][] = '!='; + } +#line 1291 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 121 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r26(){ +$this->out['where_condition']['column_operators'][] = 'IN'; + } +#line 1296 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 124 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r27(){ +$this->out['where_condition']['column_operators'][] = 'LIKE'; + } +#line 1301 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 130 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r31(){ +$this->out['orderby'][] = $this->yystack[$this->yyidx + 0]->minor; + } +#line 1306 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 135 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r34(){ +$this->out['sortOrder'] = 'ASC'; + } +#line 1311 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 138 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r35(){ +$this->out['sortOrder'] = 'DESC'; + } +#line 1316 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 144 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r39(){ +$this->out['limit'][] = $this->yystack[$this->yyidx + 0]->minor; + } +#line 1321 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 147 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r40(){ +$this->out['limit'][] = $this->yystack[$this->yyidx + -2]->minor; +$this->out['limit'][] = $this->yystack[$this->yyidx + 0]->minor; + } +#line 1327 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" +#line 151 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + function yy_r41(){ +global $adb; +if(!$this->out['meta']){ +$module = $this->out['moduleName']; +$handler = vtws_getModuleHandlerFromName($module,$this->user); +$objectMeta = $handler->getMeta(); +$this->out['meta'] = $objectMeta; +$meta = $this->out['meta']; +$fieldcol = $meta->getFieldColumnMapping(); +$columns = array(); +if(in_array('*', $this->out['column_list'])){ +$columns = array_values($fieldcol); +}elseif( !in_array('count(*)', array_map(strtolower, $this->out['column_list']))){ +foreach($this->out['column_list'] as $ind=>$field){ +$columns[] = $fieldcol[$field]; +} +} +if($this->out['where_condition']){ +foreach($this->out['where_condition']['column_names'] as $ind=>$field){ +$columns[] = $fieldcol[$field]; +} +} +$tables = $this->getTables($this->out, $columns); +if(!in_array($objectMeta->getEntityBaseTable(), $tables)){ +$tables[] = $objectMeta->getEntityBaseTable(); +} +$defaultTableList = $objectMeta->getEntityDefaultTableList(); +foreach($defaultTableList as $tableName){ +if(!in_array($tableName,$tables)){ +array_push($tables,$tableName); +} +} +$firstTable = $objectMeta->getEntityBaseTable(); +$tabNameIndex = $objectMeta->getEntityTableIndexList(); +$firstIndex = $tabNameIndex[$firstTable]; +foreach($tables as $ind=>$table){ +if($firstTable!=$table){ + if(!isset($tabNameIndex[$table]) && $table == "vtiger_crmentity"){ + $this->out['defaultJoinConditions'] = $this->out['defaultJoinConditions']." LEFT JOIN $table ON $firstTable.$firstIndex=$table.crmid"; + }else{ + $this->out['defaultJoinConditions'] = $this->out['defaultJoinConditions']." LEFT JOIN $table ON $firstTable.$firstIndex=$table.{$tabNameIndex[$table]}"; + } +}else{ + $this->out['tableName'] = $table; +} +} +} + } +#line 1377 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" + + /** + * placeholder for the left hand side in a reduce operation. + * + * For a parser with a rule like this: + *
+     * rule(A) ::= B. { A = 1; }
+     * 
+ * + * The parser will translate to something like: + * + * + * function yy_r0(){$this->_retvalue = 1;} + * + */ + private $_retvalue; + + /** + * Perform a reduce action and the shift that must immediately + * follow the reduce. + * + * For a rule such as: + * + *
+     * A ::= B blah C. { dosomething(); }
+     * 
+ * + * This function will first call the action, if any, ("dosomething();" in our + * example), and then it will pop three states from the stack, + * one for each entry on the right-hand side of the expression + * (B, blah, and C in our example rule), and then push the result of the action + * back on to the stack with the resulting state reduced to (as described in the .out + * file) + * @param int Number of the rule by which to reduce + */ + function yy_reduce($yyruleno) + { + //int $yygoto; /* The next state */ + //int $yyact; /* The next action */ + //mixed $yygotominor; /* The LHS of the rule reduced */ + //VTQL_ParseryyStackEntry $yymsp; /* The top of the parser's stack */ + //int $yysize; /* Amount to pop the stack */ + $yymsp = $this->yystack[$this->yyidx]; + if (self::$yyTraceFILE && $yyruleno >= 0 + && $yyruleno < count(self::$yyRuleName)) { + fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n", + self::$yyTracePrompt, $yyruleno, + self::$yyRuleName[$yyruleno]); + } + + $this->_retvalue = $yy_lefthand_side = null; + if (array_key_exists($yyruleno, self::$yyReduceMap)) { + // call the action + $this->_retvalue = null; + $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}(); + $yy_lefthand_side = $this->_retvalue; + } + $yygoto = self::$yyRuleInfo[$yyruleno]['lhs']; + $yysize = self::$yyRuleInfo[$yyruleno]['rhs']; + $this->yyidx -= $yysize; + for($i = $yysize; $i; $i--) { + // pop all of the right-hand side parameters + array_pop($this->yystack); + } + $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto); + if ($yyact < self::YYNSTATE) { + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if (!self::$yyTraceFILE && $yysize) { + $this->yyidx++; + $x = new VTQL_ParseryyStackEntry; + $x->stateno = $yyact; + $x->major = $yygoto; + $x->minor = $yy_lefthand_side; + $this->yystack[$this->yyidx] = $x; + } else { + $this->yy_shift($yyact, $yygoto, $yy_lefthand_side); + } + } elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) { + $this->yy_accept(); + } + } + + /** + * The following code executes when the parse fails + * + * Code from %parse_fail is inserted here + */ + function yy_parse_failed() + { + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will be executed whenever the + ** parser fails */ +#line 456 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + + if(!$this->syntax_error){ + throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, "Parsing failed"); + } +#line 1484 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" + } + + /** + * The following code executes when a syntax error first occurs. + * + * %syntax_error code is inserted here + * @param int The major type of the error token + * @param mixed The minor type of the error token + */ + function yy_syntax_error($yymajor, $TOKEN) + { +#line 466 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + + $synMsg = "Syntax Error on line " . $this->lex->linenum . ": token '" .$this->lex->value."' "; + $expect = array(); + foreach ($this->yy_get_expected_tokens($yymajor) as $token) { + $expect[] = self::$yyTokenName[$token]; + } + $synMsg =$synMsg.('Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN + . '), expected one of: ' . implode(',', $expect)); + + throw new WebServiceException(WebServiceErrorCode::$QUERYSYNTAX, $synMsg); +#line 1508 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" + } + + /** + * The following is executed when the parser accepts + * + * %parse_accept code is inserted here + */ + function yy_accept() + { + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $stack = $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +#line 452 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.y" + + $this->success = true; + #line 1530 "e:\workspace\nonadmin\pkg\vtiger\extensions\Webservices\VTQL_parser.php" + } + + /** + * The main parser program. + * + * The first argument is the major token number. The second is + * the token value string as scanned from the input. + * + * @param int the token number + * @param mixed the token value + * @param mixed any extra arguments that should be passed to handlers + */ + function doParse($yymajor, $yytokenvalue) + { +// $yyact; /* The parser action. */ +// $yyendofinput; /* True if we are at the end of input */ + $yyerrorhit = 0; /* True if yymajor has invoked an error */ + + /* (re)initialize the parser, if necessary */ + if ($this->yyidx === null || $this->yyidx < 0) { + /* if ($yymajor == 0) return; // not sure why this was here... */ + $this->yyidx = 0; + $this->yyerrcnt = -1; + $x = new VTQL_ParseryyStackEntry; + $x->stateno = 0; + $x->major = 0; + $this->yystack = array(); + array_push($this->yystack, $x); + } + $yyendofinput = ($yymajor==0); + + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sInput %s\n", + self::$yyTracePrompt, self::$yyTokenName[$yymajor]); + } + + do { + $yyact = $this->yy_find_shift_action($yymajor); + if ($yymajor < self::YYERRORSYMBOL && + !$this->yy_is_expected_token($yymajor)) { + // force a syntax error + $yyact = self::YY_ERROR_ACTION; + } + if ($yyact < self::YYNSTATE) { + $this->yy_shift($yyact, $yymajor, $yytokenvalue); + $this->yyerrcnt--; + if ($yyendofinput && $this->yyidx >= 0) { + $yymajor = 0; + } else { + $yymajor = self::YYNOCODE; + } + } elseif ($yyact < self::YYNSTATE + self::YYNRULE) { + $this->yy_reduce($yyact - self::YYNSTATE); + } elseif ($yyact == self::YY_ERROR_ACTION) { + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sSyntax Error!\n", + self::$yyTracePrompt); + } + if (self::YYERRORSYMBOL) { + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if ($this->yyerrcnt < 0) { + $this->yy_syntax_error($yymajor, $yytokenvalue); + } + $yymx = $this->yystack[$this->yyidx]->major; + if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ){ + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sDiscard input token %s\n", + self::$yyTracePrompt, self::$yyTokenName[$yymajor]); + } + $this->yy_destructor($yymajor, $yytokenvalue); + $yymajor = self::YYNOCODE; + } else { + while ($this->yyidx >= 0 && + $yymx != self::YYERRORSYMBOL && + ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE + ){ + $this->yy_pop_parser_stack(); + } + if ($this->yyidx < 0 || $yymajor==0) { + $this->yy_destructor($yymajor, $yytokenvalue); + $this->yy_parse_failed(); + $yymajor = self::YYNOCODE; + } elseif ($yymx != self::YYERRORSYMBOL) { + $u2 = 0; + $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2); + } + } + $this->yyerrcnt = 3; + $yyerrorhit = 1; + } else { + /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if ($this->yyerrcnt <= 0) { + $this->yy_syntax_error($yymajor, $yytokenvalue); + } + $this->yyerrcnt = 3; + $this->yy_destructor($yymajor, $yytokenvalue); + if ($yyendofinput) { + $this->yy_parse_failed(); + } + $yymajor = self::YYNOCODE; + } + } else { + $this->yy_accept(); + $yymajor = self::YYNOCODE; + } + } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); + } +} diff --git a/include/Webservices/VtigerActorOperation.php b/include/Webservices/VtigerActorOperation.php new file mode 100644 index 0000000..3465b0a --- /dev/null +++ b/include/Webservices/VtigerActorOperation.php @@ -0,0 +1,337 @@ +entityTableName = $this->getActorTables(); + if($this->entityTableName === null){ + throw new WebServiceException(WebServiceErrorCode::$UNKOWNENTITY,"Entity is not associated with any tables"); + } + $this->meta = $this->getMetaInstance(); + $this->moduleFields = null; + $this->element = null; + $this->id = null; + } + + protected function getMetaInstance(){ + if(empty(WebserviceEntityOperation::$metaCache[$this->webserviceObject->getEntityName()][$this->user->id])){ + WebserviceEntityOperation::$metaCache[$this->webserviceObject->getEntityName()][$this->user->id] + = new VtigerCRMActorMeta($this->entityTableName,$this->webserviceObject,$this->pearDB,$this->user); + } + return WebserviceEntityOperation::$metaCache[$this->webserviceObject->getEntityName()][$this->user->id]; + } + + protected function getActorTables(){ + static $actorTables = array(); + + if(isset($actorTables[$this->webserviceObject->getEntityName()])){ + return $actorTables[$this->webserviceObject->getEntityName()]; + } + $sql = 'select table_name from vtiger_ws_entity_tables where webservice_entity_id=?'; + $result = $this->pearDB->pquery($sql,array($this->webserviceObject->getEntityId())); + $tableName = null; + if($result){ + $rowCount = $this->pearDB->num_rows($result); + for($i=0;$i<$rowCount;++$i){ + $row = $this->pearDB->query_result_rowdata($result,$i); + $tableName = $row['table_name']; + } + // Cache the result for further re-use + $actorTables[$this->webserviceObject->getEntityName()] = $tableName; + } + return $tableName; + } + + public function getMeta(){ + return $this->meta; + } + + protected function getNextId($elementType,$element){ + if(strcasecmp($elementType,'Groups') === 0){ + $tableName="vtiger_users"; + }else{ + $tableName = $this->entityTableName; + + } + $meta = $this->getMeta(); + if(strcasecmp($elementType,'Groups') !== 0 && strcasecmp($elementType,'Users') !== 0) { + $sql = "update $tableName"."_seq set id=(select max(".$meta->getIdColumn().") + from $tableName)"; + $this->pearDB->pquery($sql,array()); + } + $id = $this->pearDB->getUniqueId($tableName); + return $id; + } + + public function __create($elementType,$element){ + require_once 'include/utils/utils.php'; + $db = PearDatabase::getInstance(); + + $this->id=$this->getNextId($elementType, $element); + + $element[$this->meta->getObectIndexColumn()] = $this->id; + + //Insert into group vtiger_table + $query = "insert into {$this->entityTableName}(".implode(',',array_keys($element)). + ") values(".generateQuestionMarks(array_keys($element)).")"; + $result = null; + $transactionSuccessful = vtws_runQueryAsTransaction($query, array_values($element), + $result); + return $transactionSuccessful; + } + + public function create($elementType,$element){ + $element = DataTransform::sanitizeForInsert($element,$this->meta); + + $element = $this->restrictFields($element); + + $success = $this->__create($elementType,$element); + if(!$success){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + return $this->retrieve(vtws_getId($this->meta->getEntityId(),$this->id)); + } + + protected function restrictFields($element, $selectedOnly = false){ + $fields = $this->getModuleFields(); + $newElement = array(); + foreach ($fields as $field) { + if(isset($element[$field['name']])){ + $newElement[$field['name']] = $element[$field['name']]; + }else if($field['name'] != 'id' && $selectedOnly == false){ + $newElement[$field['name']] = ''; + } + } + return $newElement; + } + + public function __retrieve($id){ + $query = "select * from {$this->entityTableName} where {$this->meta->getObectIndexColumn()}=?"; + $transactionSuccessful = vtws_runQueryAsTransaction($query,array($id),$result); + if(!$transactionSuccessful){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + $db = $this->pearDB; + if($result){ + $rowCount = $db->num_rows($result); + if($rowCount >0){ + $this->element = $db->query_result_rowdata($result,0); + return true; + } + } + return false; + } + + public function retrieve($id){ + + $ids = vtws_getIdComponents($id); + $elemId = $ids[1]; + $success = $this->__retrieve($elemId); + if(!$success){ + throw new WebServiceException(WebServiceErrorCode::$RECORDNOTFOUND, + "Record not found"); + } + $element = $this->getElement(); + + return DataTransform::filterAndSanitize($element,$this->meta); + } + + public function __update($element,$id){ + $columnStr = 'set '.implode('=?,',array_keys($element)).' =? '; + $query = 'update '.$this->entityTableName.' '.$columnStr.'where '. + $this->meta->getObectIndexColumn().'=?'; + $params = array_values($element); + array_push($params,$id); + $result = null; + $transactionSuccessful = vtws_runQueryAsTransaction($query,$params,$result); + return $transactionSuccessful; + } + + public function update($element){ + $ids = vtws_getIdComponents($element["id"]); + $element = DataTransform::sanitizeForInsert($element,$this->meta); + $element = $this->restrictFields($element); + + $success = $this->__update($element,$ids[1]); + if(!$success){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + return $this->retrieve(vtws_getId($this->meta->getEntityId(),$ids[1])); + } + + public function __revise($element,$id){ + $columnStr = 'set '.implode('=?,',array_keys($element)).' =? '; + $query = 'update '.$this->entityTableName.' '.$columnStr.'where '. + $this->meta->getObectIndexColumn().'=?'; + $params = array_values($element); + array_push($params,$id); + $result = null; + $transactionSuccessful = vtws_runQueryAsTransaction($query,$params,$result); + return $transactionSuccessful; + } + + public function revise($element){ + $ids = vtws_getIdComponents($element["id"]); + + $element = DataTransform::sanitizeForInsert($element,$this->meta); + $element = $this->restrictFields($element, true); + + $success = $this->__retrieve($ids[1]); + if(!$success){ + throw new WebServiceException(WebServiceErrorCode::$RECORDNOTFOUND, + "Record not found"); + } + + $allDetails = $this->getElement(); + foreach ($allDetails as $index=>$value) { + if(!isset($element)){ + $element[$index] = $value; + } + } + $success = $this->__revise($element,$ids[1]); + if(!$success){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + return $this->retrieve(vtws_getId($this->meta->getEntityId(),$ids[1])); + } + + public function __delete($elemId){ + $result = null; + $query = 'delete from '.$this->entityTableName.' where '. + $this->meta->getObectIndexColumn().'=?'; + $transactionSuccessful = vtws_runQueryAsTransaction($query,array($elemId),$result); + return $transactionSuccessful; + } + + public function delete($id){ + $ids = vtws_getIdComponents($id); + $elemId = $ids[1]; + + $success = $this->__delete($elemId); + if(!$success){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + return array("status"=>"successful"); + } + + public function describe($elementType){ + + $app_strings = VTWS_PreserveGlobal::getGlobal('app_strings'); + $current_user = vtws_preserveGlobal('current_user',$this->user);; + $label = (isset($app_strings[$elementType]))? $app_strings[$elementType]:$elementType; + $createable = $this->meta->hasWriteAccess(); + $updateable = $this->meta->hasWriteAccess(); + $deleteable = $this->meta->hasDeleteAccess(); + $retrieveable = $this->meta->hasReadAccess(); + $fields = $this->getModuleFields(); + return array("label"=>$label,"name"=>$elementType,"createable"=>$createable,"updateable"=>$updateable, + "deleteable"=>$deleteable,"retrieveable"=>$retrieveable,"fields"=>$fields, + "idPrefix"=>$this->meta->getEntityId(),'isEntity'=>$this->isEntity,'labelFields'=>$this->meta->getNameFields()); + } + + function getModuleFields(){ + $app_strings = VTWS_PreserveGlobal::getGlobal('app_strings'); + if($this->moduleFields === null){ + $fields = array(); + $moduleFields = $this->meta->getModuleFields(); + foreach ($moduleFields as $fieldName=>$webserviceField) { + array_push($fields,$this->getDescribeFieldArray($webserviceField)); + } + $label = ($app_strings[$this->meta->getObectIndexColumn()])? $app_strings[$this->meta->getObectIndexColumn()]: + $this->meta->getObectIndexColumn(); + $this->moduleFields = $fields; + } + return $this->moduleFields; + } + + function getDescribeFieldArray($webserviceField){ + $app_strings = VTWS_PreserveGlobal::getGlobal('app_strings'); + $fieldLabel = $webserviceField->getFieldLabelKey(); + if(isset($app_strings[$fieldLabel])){ + $fieldLabel = $app_strings[$fieldLabel]; + } + if(strcasecmp($webserviceField->getFieldName(),$this->meta->getObectIndexColumn()) === 0){ + return $this->getIdField($fieldLabel); + } + + $typeDetails = $this->getFieldTypeDetails($webserviceField); + + //set type name, in the type details array. + $typeDetails['name'] = $webserviceField->getFieldDataType(); + $editable = $this->isEditable($webserviceField); + + $describeArray = array('name'=>$webserviceField->getFieldName(),'label'=>$fieldLabel,'mandatory'=> + $webserviceField->isMandatory(),'type'=>$typeDetails,'nullable'=>$webserviceField->isNullable(), + "editable"=>$editable); + if($webserviceField->hasDefault()){ + $describeArray['default'] = $webserviceField->getDefault(); + } + return $describeArray; + } + public function query($q){ + + $parser = new Parser($this->user, $q); + $error = $parser->parse(); + + if($error){ + return $parser->getError(); + } + + $mysql_query = $parser->getSql(); + $meta = $parser->getObjectMetaData(); + $this->pearDB->startTransaction(); + $result = $this->pearDB->pquery($mysql_query, array()); + $error = $this->pearDB->hasFailedTransaction(); + $this->pearDB->completeTransaction(); + + if($error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + $noofrows = $this->pearDB->num_rows($result); + $output = array(); + for($i=0; $i<$noofrows; $i++){ + $row = $this->pearDB->fetchByAssoc($result,$i); + if(!$meta->hasPermission(EntityMeta::$RETRIEVE,$row["crmid"])){ + continue; + } + $output[] = DataTransform::sanitizeDataWithColumn($row,$meta); + } + + return $output; + } + + protected function getElement(){ + return $this->element; + } + +} +?> \ No newline at end of file diff --git a/include/Webservices/VtigerCRMActorMeta.php b/include/Webservices/VtigerCRMActorMeta.php new file mode 100644 index 0000000..8b6ea21 --- /dev/null +++ b/include/Webservices/VtigerCRMActorMeta.php @@ -0,0 +1,282 @@ +baseTable = $tableName; + $this->idColumn = null; + $this->pearDB = $adb; + + $fieldList = $this->getTableFieldList($tableName); + $this->moduleFields = array(); + foreach ($fieldList as $field) { + $this->moduleFields[$field->getFieldName()] = $field; + } + + $this->pearDB = $adb; + $this->tableList = array($this->baseTable); + $this->tableIndexList = array($this->baseTable=>$this->idColumn); + $this->defaultTableList = array(); + } + + protected function getTableFieldList($tableName){ + $tableFieldList = array(); + + $factory = WebserviceField::fromArray($this->pearDB,array('tablename'=>$tableName)); + $dbTableFields = $factory->getTableFields(); + foreach ($dbTableFields as $dbField) { + if($dbField->primary_key){ + if($this->idColumn === null){ + $this->idColumn = $dbField->name; + }else{ + throw new WebServiceException(WebServiceErrorCode::$UNKOWNENTITY, + "Entity table with multi column primary key is not supported"); + } + } + $field = $this->getFieldArrayFromDBField($dbField,$tableName); + $webserviceField = WebserviceField::fromArray($this->pearDB,$field); + $fieldDataType = $this->getFieldType($dbField,$tableName); + if($fieldDataType === null){ + $fieldDataType = $this->getFieldDataTypeFromDBType($dbField->type); + } + $webserviceField->setFieldDataType($fieldDataType); + if(strcasecmp($fieldDataType,'reference') === 0){ + $webserviceField->setReferenceList($this->getReferenceList($dbField,$tableName)); + } + array_push($tableFieldList,$webserviceField); + } + return $tableFieldList; + } + + protected function getFieldArrayFromDBField($dbField,$tableName){ + $field = array(); + $field['fieldname'] = $dbField->name; + $field['columnname'] = $dbField->name; + $field['tablename'] = $tableName; + $field['fieldlabel'] = str_replace('_', ' ',$dbField->name); + $field['displaytype'] = 1; + $field['uitype'] = 1; + $fieldDataType = $this->getFieldType($dbField,$tableName); + if($fieldDataType !== null){ + $fieldType = $this->getTypeOfDataForType($fieldDataType); + }else{ + $fieldType = $this->getTypeOfDataForType($dbField->type); + } + $typeOfData = null; + if(($dbField->not_null && !$dbField->primary_key) || $dbField->unique_key == 1){ + $typeOfData = $fieldType.'~M'; + }else{ + $typeOfData = $fieldType.'~O'; + } + $field['typeofdata'] = $typeOfData; + $field['tabid'] = null; + $field['fieldid'] = null; + $field['masseditable'] = 0; + $field['presence'] = '0'; + return $field; + } + + protected function getReferenceList($dbField, $tableName){ + static $referenceList = array(); + if(isset($referenceList[$dbField->name])){ + return $referenceList[$dbField->name]; + } + if(!isset(VtigerCRMActorMeta::$fieldTypeMapping[$tableName][$dbField->name])){ + $this->getFieldType($dbField, $tableName); + } + $fieldTypeData = VtigerCRMActorMeta::$fieldTypeMapping[$tableName][$dbField->name]; + $referenceTypes = array(); + $sql = "select * from vtiger_ws_entity_referencetype where fieldtypeid=?"; + $result = $this->pearDB->pquery($sql,array($fieldTypeData['fieldtypeid'])); + $numRows = $this->pearDB->num_rows($result); + for($i=0;$i<$numRows;++$i){ + array_push($referenceTypes,$this->pearDB->query_result($result,$i,"type")); + } + $referenceList[$dbField->name] = $referenceTypes; + return $referenceTypes; + } + + protected function getFieldType($dbField,$tableName){ + + if(isset(VtigerCRMActorMeta::$fieldTypeMapping[$tableName][$dbField->name])){ + if(VtigerCRMActorMeta::$fieldTypeMapping[$tableName][$dbField->name] === 'null'){ + return null; + } + $row = VtigerCRMActorMeta::$fieldTypeMapping[$tableName][$dbField->name]; + return $row['fieldtype']; + } + $sql = "select * from vtiger_ws_entity_fieldtype where table_name=? and field_name=?;"; + $result = $this->pearDB->pquery($sql,array($tableName,$dbField->name)); + $rowCount = $this->pearDB->num_rows($result); + if($rowCount > 0){ + $row = $this->pearDB->query_result_rowdata($result,0); + VtigerCRMActorMeta::$fieldTypeMapping[$tableName][$dbField->name] = $row; + return $row['fieldtype']; + }else{ + VtigerCRMActorMeta::$fieldTypeMapping[$tableName][$dbField->name] = 'null'; + return null; + } + } + + protected function getTypeOfDataForType($type){ + switch($type){ + case 'email': return 'E'; + case 'password': return 'P'; + case 'date': return 'D'; + case 'datetime': return 'DT'; + case 'timestamp': return 'T'; + case 'int': + case 'integer': return 'I'; + case 'decimal': + case 'numeric': return 'N'; + case 'varchar': + case 'text': + default: return 'V'; + } + } + + protected function getFieldDataTypeFromDBType($type){ + switch($type){ + case 'date': return 'date'; + case 'datetime': return 'datetime'; + case 'timestamp': return 'time'; + case 'int': + case 'integer': return 'integer'; + case 'real': + case 'decimal': + case 'numeric': return 'double'; + case 'text': return 'text'; + case 'varchar': return 'string'; + default: return $type; + } + } + + public function hasPermission($operation,$webserviceId){ + if(is_admin($this->user)){ + return true; + }else{ + if(strcmp($operation,EntityMeta::$RETRIEVE)===0){ + return true; + } + return false; + } + } + + public function hasAssignPrivilege($ownerWebserviceId){ + if(is_admin($this->user)){ + return true; + }else{ + $idComponents = vtws_getIdComponents($webserviceId); + $userId=$idComponents[1]; + if($this->user->id === $userId){ + return true; + } + return false; + } + } + + public function hasDeleteAccess(){ + if(is_admin($this->user)){ + return true; + }else{ + return false; + } + } + + public function hasAccess(){ + return true; + } + + public function hasReadAccess(){ + return true; + } + + public function hasWriteAccess(){ + if(is_admin($this->user)){ + return true; + }else{ + return false; + } + } + + public function getEntityName(){ + return $this->webserviceObject->getEntityName(); + } + public function getEntityId(){ + return $this->webserviceObject->getEntityId(); + } + + function getObjectEntityName($webserviceId){ + + $idComponents = vtws_getIdComponents($webserviceId); + $id=$idComponents[1]; + + if($this->exists($id)){ + return $this->webserviceObject->getEntityName(); + } + return null; + } + + function exists($recordId){ + $exists = false; + $sql = 'select * from '.$this->baseTable.' where '.$this->getObectIndexColumn().'=?'; + $result = $this->pearDB->pquery($sql , array($recordId)); + if($result != null && isset($result)){ + if($this->pearDB->num_rows($result)>0){ + $exists = true; + } + } + return $exists; + } + + public function getNameFields(){ + $query = "select name_fields from vtiger_ws_entity_name where entity_id = ?"; + $result = $this->pearDB->pquery($query, array($this->objectId)); + $fieldNames = ''; + if($result){ + $rowCount = $this->pearDB->num_rows($result); + if($rowCount > 0){ + $fieldNames = $this->pearDB->query_result($result,0,'name_fields'); + } + } + return $fieldNames; + } + + public function getName($webserviceId){ + + $idComponents = vtws_getIdComponents($webserviceId); + $entityId = $idComponents[0]; + $id=$idComponents[1]; + + $nameList = vtws_getActorEntityNameById($entityId, array($id)); + return $nameList[$id]; + } + + public function getEntityAccessControlQuery() { + return ''; + } + + public function getEntityDeletedQuery() { + if($this->getEntityName() == 'Currency'){ + return 'vtiger_currency_info.deleted=0'; + } + + return ''; + } + + public function isModuleEntity() { + return false; + } +} +?> \ No newline at end of file diff --git a/include/Webservices/VtigerCRMObject.php b/include/Webservices/VtigerCRMObject.php new file mode 100644 index 0000000..7e21943 --- /dev/null +++ b/include/Webservices/VtigerCRMObject.php @@ -0,0 +1,215 @@ +moduleId = $moduleCredential; + $this->moduleName = $this->getObjectTypeName($this->moduleId); + }else{ + $this->moduleName = $moduleCredential; + $this->moduleId = $this->getObjectTypeId($this->moduleName); + } + $this->instance = null; + $this->getInstance(); + } + + public function getModuleName(){ + return $this->moduleName; + } + + public function getModuleId(){ + return $this->moduleId; + } + + public function getInstance(){ + if($this->instance == null){ + $this->instance = $this->getModuleClassInstance($this->moduleName); + } + return $this->instance; + } + + public function getObjectId(){ + if($this->instance==null){ + $this->getInstance(); + } + return $this->instance->id; + } + + public function setObjectId($id){ + if($this->instance==null){ + $this->getInstance(); + } + $this->instance->id = $id; + } + + private function titleCase($str){ + $first = substr($str, 0, 1); + return strtoupper($first).substr($str,1); + } + + private function getObjectTypeId($objectName){ + + // Use getTabid API + $tid = getTabid($objectName); + + if($tid === false) { + global $adb; + + $sql = "select * from vtiger_tab where name=?;"; + $params = array($objectName); + $result = $adb->pquery($sql, $params); + $data1 = $adb->fetchByAssoc($result,1,false); + + $tid = $data1["tabid"]; + } + // END + + return $tid; + + } + + private function getModuleClassInstance($moduleName){ + return CRMEntity::getInstance($moduleName); + } + + private function getObjectTypeName($moduleId){ + + return getTabModuleName($moduleId); + + } + + private function getTabName(){ + if($this->moduleName == 'Events'){ + return 'Calendar'; + } + return $this->moduleName; + } + + public function read($id){ + global $adb; + + $error = false; + $adb->startTransaction(); + $this->instance->retrieve_entity_info($id,$this->getTabName()); + $error = $adb->hasFailedTransaction(); + $adb->completeTransaction(); + return !$error; + } + + public function create($element){ + global $adb; + + $error = false; + foreach($element as $k=>$v){ + $this->instance->column_fields[$k] = $v; + } + + $adb->startTransaction(); + $this->instance->Save($this->getTabName()); + $error = $adb->hasFailedTransaction(); + $adb->completeTransaction(); + return !$error; + } + + public function update($element){ + + global $adb; + $error = false; + + foreach($element as $k=>$v){ + $this->instance->column_fields[$k] = $v; + } + + $adb->startTransaction(); + $this->instance->mode = "edit"; + $this->instance->Save($this->getTabName()); + $error = $adb->hasFailedTransaction(); + $adb->completeTransaction(); + return !$error; + } + + public function revise($element){ + global $adb; + $error = false; + + $error = $this->read($this->getObjectId()); + if($error == false){ + return $error; + } + + foreach($element as $k=>$v){ + $this->instance->column_fields[$k] = $v; + } + + //added to fix the issue of utf8 characters + foreach($this->instance->column_fields as $key=>$value){ + $this->instance->column_fields[$key] = decode_html($value); + } + + $adb->startTransaction(); + $this->instance->mode = "edit"; + $this->instance->Save($this->getTabName()); + $error = $adb->hasFailedTransaction(); + $adb->completeTransaction(); + return !$error; + } + + public function delete($id){ + global $adb; + $error = false; + $adb->startTransaction(); + DeleteEntity($this->getTabName(), $this->getTabName(), $this->instance, $id,$returnid); + $error = $adb->hasFailedTransaction(); + $adb->completeTransaction(); + return !$error; + } + + public function getFields(){ + return $this->instance->column_fields; + } + + function exists($id){ + global $adb; + + $exists = false; + $sql = "select * from vtiger_crmentity where crmid=? and deleted=0"; + $result = $adb->pquery($sql , array($id)); + if($result != null && isset($result)){ + if($adb->num_rows($result)>0){ + $exists = true; + } + } + return $exists; + } + + function getSEType($id){ + global $adb; + + $seType = null; + $sql = "select * from vtiger_crmentity where crmid=? and deleted=0"; + $result = $adb->pquery($sql , array($id)); + if($result != null && isset($result)){ + if($adb->num_rows($result)>0){ + $seType = $adb->query_result($result,0,"setype"); + } + } + return $seType; + } + +} + +?> diff --git a/include/Webservices/VtigerCRMObjectMeta.php b/include/Webservices/VtigerCRMObjectMeta.php new file mode 100644 index 0000000..5e51d87 --- /dev/null +++ b/include/Webservices/VtigerCRMObjectMeta.php @@ -0,0 +1,521 @@ +columnTableMapping = null; + $this->fieldColumnMapping = null; + $this->userAccessibleColumns = null; + $this->mandatoryFields = null; + $this->emailFields = null; + $this->referenceFieldDetails = null; + $this->ownerFields = null; + $this->moduleFields = array(); + $this->hasAccess = false; + $this->hasReadAccess = false; + $this->hasWriteAccess = false; + $this->hasDeleteAccess = false; + $instance = vtws_getModuleInstance($this->webserviceObject); + $this->idColumn = $instance->tab_name_index[$instance->table_name]; + $this->baseTable = $instance->table_name; + $this->tableList = $instance->tab_name; + $this->tableIndexList = $instance->tab_name_index; + if(in_array('vtiger_crmentity',$instance->tab_name)){ + $this->defaultTableList = array('vtiger_crmentity'); + }else{ + $this->defaultTableList = array(); + } + $this->tabId = null; + } + + /** + * returns tabid of the current object. + * @return Integer + */ + public function getTabId(){ + if($this->tabId == null){ + $this->tabId = getTabid($this->objectName); + } + return $this->tabId; + } + + /** + * returns tabid that can be consumed for database lookup purpose generally, events and + * calendar are treated as the same module + * @return Integer + */ + public function getEffectiveTabId() { + return getTabid($this->getTabName()); + } + + public function getTabName(){ + if($this->objectName == 'Events'){ + return 'Calendar'; + } + return $this->objectName; + } + + private function computeAccess(){ + + global $adb; + + $active = vtlib_isModuleActive($this->getTabName()); + if($active == false){ + $this->hasAccess = false; + $this->hasReadAccess = false; + $this->hasWriteAccess = false; + $this->hasDeleteAccess = false; + return; + } + + require('user_privileges/user_privileges_'.$this->user->id.'.php'); + if($is_admin == true || $profileGlobalPermission[1] == 0 || $profileGlobalPermission[2] == 0){ + $this->hasAccess = true; + $this->hasReadAccess = true; + $this->hasWriteAccess = true; + $this->hasDeleteAccess = true; + }else{ + + //TODO get oer sort out the preference among profile2tab and profile2globalpermissions. + //TODO check whether create/edit seperate controls required for web sevices? + $profileList = getCurrentUserProfileList(); + + $sql = "select * from vtiger_profile2globalpermissions where profileid in (".generateQuestionMarks($profileList).");"; + $result = $adb->pquery($sql,array($profileList)); + + $noofrows = $adb->num_rows($result); + //globalactionid=1 is view all action. + //globalactionid=2 is edit all action. + for($i=0; $i<$noofrows; $i++){ + $permission = $adb->query_result($result,$i,"globalactionpermission"); + $globalactionid = $adb->query_result($result,$i,"globalactionid"); + if($permission != 1 || $permission != "1"){ + $this->hasAccess = true; + if($globalactionid == 2 || $globalactionid == "2"){ + $this->hasWriteAccess = true; + $this->hasDeleteAccess = true; + }else{ + $this->hasReadAccess = true; + } + } + } + + $sql = 'select * from vtiger_profile2tab where profileid in ('.generateQuestionMarks($profileList).') and tabid = ?;'; + $result = $adb->pquery($sql,array($profileList,$this->getTabId())); + $standardDefined = false; + $permission = $adb->query_result($result,1,"permissions"); + if($permission == 1 || $permission == "1"){ + $this->hasAccess = false; + return; + }else{ + $this->hasAccess = true; + } + + //operation=2 is delete operation. + //operation=0 or 1 is create/edit operation. precise 0 create and 1 edit. + //operation=3 index or popup. //ignored for websevices. + //operation=4 is view operation. + $sql = "select * from vtiger_profile2standardpermissions where profileid in (".generateQuestionMarks($profileList).") and tabid=?"; + $result = $adb->pquery($sql,array($profileList,$this->getTabId())); + + $noofrows = $adb->num_rows($result); + for($i=0; $i<$noofrows; $i++){ + $standardDefined = true; + $permission = $adb->query_result($result,$i,"permissions"); + $operation = $adb->query_result($result,$i,"Operation"); + if(!$operation){ + $operation = $adb->query_result($result,$i,"operation"); + } + + if($permission != 1 || $permission != "1"){ + $this->hasAccess = true; + if($operation == 0 || $operation == "0"){ + $this->hasWriteAccess = true; + }else if($operation == 1 || $operation == "1"){ + $this->hasWriteAccess = true; + }else if($operation == 2 || $operation == "2"){ + $this->hasDeleteAccess = true; + }else if($operation == 4 || $operation == "4"){ + $this->hasReadAccess = true; + } + } + } + if(!$standardDefined){ + $this->hasReadAccess = true; + $this->hasWriteAccess = true; + $this->hasDeleteAccess = true; + } + + } + } + + function hasAccess(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return $this->hasAccess; + } + + function hasWriteAccess(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return $this->hasWriteAccess; + } + + function hasReadAccess(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return $this->hasReadAccess; + } + + function hasDeleteAccess(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return $this->hasDeleteAccess; + } + + function hasPermission($operation,$webserviceId){ + + $idComponents = vtws_getIdComponents($webserviceId); + $id=$idComponents[1]; + + $permitted = isPermitted($this->getTabName(),$operation,$id); + if(strcmp($permitted,"yes")===0){ + return true; + } + return false; + } + + function hasAssignPrivilege($webserviceId){ + global $adb; + + // administrator's have assign privilege + if(is_admin($this->user)) return true; + + $idComponents = vtws_getIdComponents($webserviceId); + $userId=$idComponents[1]; + $ownerTypeId = $idComponents[0]; + + if($userId == null || $userId =='' || $ownerTypeId == null || $ownerTypeId ==''){ + return false; + } + $webserviceObject = VtigerWebserviceObject::fromId($adb,$ownerTypeId); + if(strcasecmp($webserviceObject->getEntityName(),"Users")===0){ + if($userId == $this->user->id){ + return true; + } + if(!$this->assign){ + $this->retrieveUserHierarchy(); + } + if(in_array($userId,array_keys($this->assignUsers))){ + return true; + }else{ + return false; + } + }elseif(strcasecmp($webserviceObject->getEntityName(),"Groups") === 0){ + $tabId = $this->getTabId(); + $groups = vtws_getUserAccessibleGroups($tabId, $this->user); + foreach ($groups as $group) { + if($group['id'] == $userId){ + return true; + } + } + return false; + } + + } + + function getUserAccessibleColumns(){ + + if(!$this->meta){ + $this->retrieveMeta(); + } + return parent::getUserAccessibleColumns(); + } + + public function getModuleFields() { + if(!$this->meta){ + $this->retrieveMeta(); + } + return parent::getModuleFields(); + } + + function getColumnTableMapping(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return parent::getColumnTableMapping(); + } + + function getFieldColumnMapping(){ + + if(!$this->meta){ + $this->retrieveMeta(); + } + if($this->fieldColumnMapping === null){ + $this->fieldColumnMapping = array(); + foreach ($this->moduleFields as $fieldName=>$webserviceField) { + if(strcasecmp($webserviceField->getFieldDataType(),'file') !== 0){ + $this->fieldColumnMapping[$fieldName] = $webserviceField->getColumnName(); + } + } + $this->fieldColumnMapping['id'] = $this->idColumn; + } + return $this->fieldColumnMapping; + } + + function getMandatoryFields(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return parent::getMandatoryFields(); + } + + function getReferenceFieldDetails(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return parent::getReferenceFieldDetails(); + } + + function getOwnerFields(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return parent::getOwnerFields(); + } + + function getEntityName(){ + return $this->objectName; + } + + function getEntityId(){ + return $this->objectId; + } + + function getEmailFields(){ + if(!$this->meta){ + $this->retrieveMeta(); + } + return parent::getEmailFields(); + } + + function getFieldIdFromFieldName($fieldName){ + if(!$this->meta){ + $this->retrieveMeta(); + } + + if(isset($this->moduleFields[$fieldName])){ + $webserviceField = $this->moduleFields[$fieldName]; + return $webserviceField->getFieldId(); + } + return null; + } + + function retrieveMeta(){ + + require_once('modules/CustomView/CustomView.php'); + $current_user = vtws_preserveGlobal('current_user',$this->user); + $theme = vtws_preserveGlobal('theme',$this->user->theme); + $default_language = VTWS_PreserveGlobal::getGlobal('default_language'); + $current_language = vtws_preserveGlobal('current_language',$default_language); + + $this->computeAccess(); + + $cv = new CustomView(); + $module_info = $cv->getCustomViewModuleInfo($this->getTabName()); + $blockArray = array(); + foreach($cv->module_list[$this->getTabName()] as $label=>$blockList){ + $blockArray = array_merge($blockArray,explode(',',$blockList)); + } + $this->retrieveMetaForBlock($blockArray); + + $this->meta = true; + VTWS_PreserveGlobal::flush(); + } + + private function retrieveUserHierarchy(){ + + $heirarchyUsers = get_user_array(false,"ACTIVE",$this->user->id); + $groupUsers = vtws_getUsersInTheSameGroup($this->user->id); + $this->assignUsers = $heirarchyUsers+$groupUsers; + $this->assign = true; + } + + private function retrieveMetaForBlock($block){ + + global $adb; + + $tabid = $this->getTabId(); + require('user_privileges/user_privileges_'.$this->user->id.'.php'); + if($is_admin == true || $profileGlobalPermission[1] == 0 || $profileGlobalPermission[2] ==0){ + $sql = "select *, '0' as readonly from vtiger_field where tabid =? and block in (".generateQuestionMarks($block).") and displaytype in (1,2,3,4)"; + $params = array($tabid, $block); + }else{ + $profileList = getCurrentUserProfileList(); + + if (count($profileList) > 0) { + $sql = "SELECT vtiger_field.*, vtiger_profile2field.readonly + FROM vtiger_field + INNER JOIN vtiger_profile2field + ON vtiger_profile2field.fieldid = vtiger_field.fieldid + INNER JOIN vtiger_def_org_field + ON vtiger_def_org_field.fieldid = vtiger_field.fieldid + WHERE vtiger_field.tabid =? AND vtiger_profile2field.visible = 0 + AND vtiger_profile2field.profileid IN (". generateQuestionMarks($profileList) .") + AND vtiger_def_org_field.visible = 0 and vtiger_field.block in (".generateQuestionMarks($block).") and vtiger_field.displaytype in (1,2,3,4) and vtiger_field.presence in (0,2) group by columnname"; + $params = array($tabid, $profileList, $block); + } else { + $sql = "SELECT vtiger_field.*, vtiger_profile2field.readonly + FROM vtiger_field + INNER JOIN vtiger_profile2field + ON vtiger_profile2field.fieldid = vtiger_field.fieldid + INNER JOIN vtiger_def_org_field + ON vtiger_def_org_field.fieldid = vtiger_field.fieldid + WHERE vtiger_field.tabid=? + AND vtiger_profile2field.visible = 0 + AND vtiger_def_org_field.visible = 0 and vtiger_field.block in (".generateQuestionMarks($block).") and vtiger_field.displaytype in (1,2,3,4) and vtiger_field.presence in (0,2) group by columnname"; + $params = array($tabid, $block); + } + } + + // Bulk Save Mode: Group by is not required!? + if(CRMEntity::isBulkSaveMode()) { + $sql = preg_replace("/group by [^ ]*/", " ", $sql); + } + // END + + $result = $adb->pquery($sql,$params); + + $noofrows = $adb->num_rows($result); + $referenceArray = array(); + $knownFieldArray = array(); + for($i=0; $i<$noofrows; $i++){ + $fieldname = $adb->query_result($result,$i,"fieldname"); + if(strcasecmp($fieldname,'imagename')===0){ + continue; + } + $webserviceField = WebserviceField::fromQueryResult($adb,$result,$i); + $this->moduleFields[$webserviceField->getFieldName()] = $webserviceField; + } + } + + function getObjectEntityName($webserviceId){ + global $adb; + + $idComponents = vtws_getIdComponents($webserviceId); + $id=$idComponents[1]; + + $seType = null; + if($this->objectName == 'Users'){ + $sql = "select user_name from vtiger_users where id=? and deleted=0"; + $result = $adb->pquery($sql , array($id)); + if($result != null && isset($result)){ + if($adb->num_rows($result)>0){ + $seType = 'Users'; + } + } + }else{ + $sql = "select * from vtiger_crmentity where crmid=? and deleted=0"; + $result = $adb->pquery($sql , array($id)); + if($result != null && isset($result)){ + if($adb->num_rows($result)>0){ + $seType = $adb->query_result($result,0,"setype"); + if($seType == "Calendar"){ + $seType = vtws_getCalendarEntityType($id); + } + } + } + } + + return $seType; + } + + function exists($recordId){ + global $adb; + + $exists = false; + $sql = ''; + if($this->objectName == 'Users'){ + $sql = "select * from vtiger_users where id=? and deleted=0 and status='Active'"; + }else{ + $sql = "select * from vtiger_crmentity where crmid=? and deleted=0 and setype='". + $this->getTabName()."'"; + } + $result = $adb->pquery($sql , array($recordId)); + if($result != null && isset($result)){ + if($adb->num_rows($result)>0){ + $exists = true; + } + } + return $exists; + } + + public function getNameFields(){ + global $adb; + + $query = "select fieldname,tablename,entityidfield from vtiger_entityname where tabid = ?"; + $result = $adb->pquery($query, array($this->getEffectiveTabId())); + $fieldNames = ''; + if($result){ + $rowCount = $adb->num_rows($result); + if($rowCount > 0){ + $fieldNames = $adb->query_result($result,0,'fieldname'); + } + } + return $fieldNames; + } + + public function getName($webserviceId){ + + $idComponents = vtws_getIdComponents($webserviceId); + $id=$idComponents[1]; + + $nameList = getEntityName($this->getTabName(),array($id)); + return $nameList[$id]; + } + + public function getEntityAccessControlQuery(){ + $accessControlQuery = ''; + $instance = vtws_getModuleInstance($this->webserviceObject); + if($this->getTabName() != 'Users') { + $accessControlQuery = $instance->getNonAdminAccessControlQuery($this->getTabName(), + $this->user); + } + return $accessControlQuery; + } + + public function getJoinClause($tableName) { + $instance = vtws_getModuleInstance($this->webserviceObject); + return $instance->getJoinClause($tableName); + } + + public function isModuleEntity() { + return true; + } +} +?> \ No newline at end of file diff --git a/include/Webservices/VtigerCompanyDetails.php b/include/Webservices/VtigerCompanyDetails.php new file mode 100644 index 0000000..bf89677 --- /dev/null +++ b/include/Webservices/VtigerCompanyDetails.php @@ -0,0 +1,51 @@ +pquery($sql,$params); + $rowCount = $db->num_rows($result); + if($rowCount > 0) { + $id = $db->query_result($result,0,'organization_id'); + $meta = $this->getMeta(); + $element['id'] = vtws_getId($meta->getEntityId(), $id); + return $this->update($element); + }else{ + $element = $this->handleFileUpload($element); + return parent::create($elementType, $element); + } + } + + function handleFileUpload($element) { + $fileFieldList = $this->meta->getFieldListByType('file'); + foreach ($fileFieldList as $field) { + $fieldname = $field->getFieldName(); + if(is_array($_FILES[$fieldname])) { + $element[$fieldname] = vtws_CreateCompanyLogoFile($fieldname); + } + } + return $element; + } + + public function update($element) { + $element = $this->handleFileUpload($element); + return parent::update($element); + } + +} +?> \ No newline at end of file diff --git a/include/Webservices/VtigerModuleOperation.php b/include/Webservices/VtigerModuleOperation.php new file mode 100644 index 0000000..47edc67 --- /dev/null +++ b/include/Webservices/VtigerModuleOperation.php @@ -0,0 +1,237 @@ +meta = $this->getMetaInstance(); + $this->tabId = $this->meta->getTabId(); + } + + protected function getMetaInstance(){ + if(empty(WebserviceEntityOperation::$metaCache[$this->webserviceObject->getEntityName()][$this->user->id])){ + WebserviceEntityOperation::$metaCache[$this->webserviceObject->getEntityName()][$this->user->id] = new VtigerCRMObjectMeta($this->webserviceObject,$this->user); + } + return WebserviceEntityOperation::$metaCache[$this->webserviceObject->getEntityName()][$this->user->id]; + } + + public function create($elementType,$element){ + $crmObject = new VtigerCRMObject($elementType, false); + + $element = DataTransform::sanitizeForInsert($element,$this->meta); + + $error = $crmObject->create($element); + if(!$error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + $id = $crmObject->getObjectId(); + + // Bulk Save Mode + if(CRMEntity::isBulkSaveMode()) { + // Avoiding complete read, as during bulk save mode, $result['id'] is enough + return array('id' => vtws_getId($this->meta->getEntityId(), $id) ); + } + + $error = $crmObject->read($id); + if(!$error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + return DataTransform::filterAndSanitize($crmObject->getFields(),$this->meta); + } + + public function retrieve($id){ + + $ids = vtws_getIdComponents($id); + $elemid = $ids[1]; + + $crmObject = new VtigerCRMObject($this->tabId, true); + $error = $crmObject->read($elemid); + if(!$error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + return DataTransform::filterAndSanitize($crmObject->getFields(),$this->meta); + } + + public function update($element){ + $ids = vtws_getIdComponents($element["id"]); + $element = DataTransform::sanitizeForInsert($element,$this->meta); + + $crmObject = new VtigerCRMObject($this->tabId, true); + $crmObject->setObjectId($ids[1]); + $error = $crmObject->update($element); + if(!$error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + $id = $crmObject->getObjectId(); + + $error = $crmObject->read($id); + if(!$error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + return DataTransform::filterAndSanitize($crmObject->getFields(),$this->meta); + } + + public function revise($element){ + $ids = vtws_getIdComponents($element["id"]); + $element = DataTransform::sanitizeForInsert($element,$this->meta); + + $crmObject = new VtigerCRMObject($this->tabId, true); + $crmObject->setObjectId($ids[1]); + $error = $crmObject->revise($element); + if(!$error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + $id = $crmObject->getObjectId(); + + $error = $crmObject->read($id); + if(!$error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + return DataTransform::filterAndSanitize($crmObject->getFields(),$this->meta); + } + + public function delete($id){ + $ids = vtws_getIdComponents($id); + $elemid = $ids[1]; + + $crmObject = new VtigerCRMObject($this->tabId, true); + + $error = $crmObject->delete($elemid); + if(!$error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + return array("status"=>"successful"); + } + + public function query($q){ + + $parser = new Parser($this->user, $q); + $error = $parser->parse(); + + if($error){ + return $parser->getError(); + } + + $mysql_query = $parser->getSql(); + $meta = $parser->getObjectMetaData(); + $this->pearDB->startTransaction(); + $result = $this->pearDB->pquery($mysql_query, array()); + $error = $this->pearDB->hasFailedTransaction(); + $this->pearDB->completeTransaction(); + + if($error){ + throw new WebServiceException(WebServiceErrorCode::$DATABASEQUERYERROR, + vtws_getWebserviceTranslatedString('LBL_'. + WebServiceErrorCode::$DATABASEQUERYERROR)); + } + + $noofrows = $this->pearDB->num_rows($result); + $output = array(); + for($i=0; $i<$noofrows; $i++){ + $row = $this->pearDB->fetchByAssoc($result,$i); + if(!$meta->hasPermission(EntityMeta::$RETRIEVE,$row["crmid"])){ + continue; + } + $output[] = DataTransform::sanitizeDataWithColumn($row,$meta); + } + + return $output; + } + + public function describe($elementType){ + $app_strings = VTWS_PreserveGlobal::getGlobal('app_strings'); + $current_user = vtws_preserveGlobal('current_user',$this->user);; + + $label = (isset($app_strings[$elementType]))? $app_strings[$elementType]:$elementType; + $createable = (strcasecmp(isPermitted($elementType,EntityMeta::$CREATE),'yes')===0)? true:false; + $updateable = (strcasecmp(isPermitted($elementType,EntityMeta::$UPDATE),'yes')===0)? true:false; + $deleteable = $this->meta->hasDeleteAccess(); + $retrieveable = $this->meta->hasReadAccess(); + $fields = $this->getModuleFields(); + return array("label"=>$label,"name"=>$elementType,"createable"=>$createable,"updateable"=>$updateable, + "deleteable"=>$deleteable,"retrieveable"=>$retrieveable,"fields"=>$fields, + "idPrefix"=>$this->meta->getEntityId(),'isEntity'=>$this->isEntity,'labelFields'=>$this->meta->getNameFields()); + } + + function getModuleFields(){ + + $fields = array(); + $moduleFields = $this->meta->getModuleFields(); + foreach ($moduleFields as $fieldName=>$webserviceField) { + if(((int)$webserviceField->getPresence()) == 1) { + continue; + } + array_push($fields,$this->getDescribeFieldArray($webserviceField)); + } + array_push($fields,$this->getIdField($this->meta->getObectIndexColumn())); + + return $fields; + } + + function getDescribeFieldArray($webserviceField){ + $default_language = VTWS_PreserveGlobal::getGlobal('default_language'); + + require 'modules/'.$this->meta->getTabName()."/language/$default_language.lang.php"; + $fieldLabel = $webserviceField->getFieldLabelKey(); + if(isset($mod_strings[$fieldLabel])){ + $fieldLabel = $mod_strings[$fieldLabel]; + } + $typeDetails = $this->getFieldTypeDetails($webserviceField); + + //set type name, in the type details array. + $typeDetails['name'] = $webserviceField->getFieldDataType(); + $editable = $this->isEditable($webserviceField); + + $describeArray = array('name'=>$webserviceField->getFieldName(),'label'=>$fieldLabel,'mandatory'=> + $webserviceField->isMandatory(),'type'=>$typeDetails,'nullable'=>$webserviceField->isNullable(), + "editable"=>$editable); + if($webserviceField->hasDefault()){ + $describeArray['default'] = $webserviceField->getDefault(); + } + return $describeArray; + } + + function getMeta(){ + return $this->meta; + } + + function getField($fieldName){ + $moduleFields = $this->meta->getModuleFields(); + return $this->getDescribeFieldArray($moduleFields[$fieldName]); + } + +} +?> diff --git a/include/Webservices/VtigerWebserviceObject.php b/include/Webservices/VtigerWebserviceObject.php new file mode 100644 index 0000000..091ca96 --- /dev/null +++ b/include/Webservices/VtigerWebserviceObject.php @@ -0,0 +1,108 @@ +id = $entityId; + $this->name = $entityName; + $this->handlerPath = $handler_path; + $this->handlerClass = $handler_class; + } + + // Cache variables to enable result re-use + private static $_fromNameCache = array(); + + static function fromName($adb,$entityName){ + + $rowData = false; + + // If the information not available in cache? + if(!isset(self::$_fromNameCache[$entityName])) { + $result = $adb->pquery("select * from vtiger_ws_entity where name=?",array($entityName)); + if($result){ + $rowCount = $adb->num_rows($result); + if($rowCount === 1){ + $rowData = $adb->query_result_rowdata($result,0); + self::$_fromNameCache[$entityName] = $rowData; + } + } + } + + $rowData = self::$_fromNameCache[$entityName]; + + if($rowData) { + return new VtigerWebserviceObject($rowData['id'],$rowData['name'], + $rowData['handler_path'],$rowData['handler_class']); + } + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied for name"); + } + + // Cache variables to enable result re-use + private static $_fromIdCache = array(); + + static function fromId($adb,$entityId){ + $rowData = false; + + // If the information not available in cache? + if(!isset(self::$_fromIdCache[$entityId])) { + $result = $adb->pquery("select * from vtiger_ws_entity where id=?",array($entityId)); + if($result){ + $rowCount = $adb->num_rows($result); + if($rowCount === 1){ + $rowData = $adb->query_result_rowdata($result,0); + self::$_fromIdCache[$entityId] = $rowData; + } + } + } + + $rowData = self::$_fromIdCache[$entityId]; + + if($rowData) { + return new VtigerWebserviceObject($rowData['id'],$rowData['name'], + $rowData['handler_path'],$rowData['handler_class']); + } + + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied for id"); + } + + static function fromQuery($adb,$query){ + $moduleRegex = "/[fF][rR][Oo][Mm]\s+([^\s;]+)/"; + $matches = array(); + $found = preg_match($moduleRegex,$query,$matches); + if($found === 1){ + return VtigerWebserviceObject::fromName($adb,trim($matches[1])); + } + throw new WebServiceException(WebServiceErrorCode::$ACCESSDENIED,"Permission to perform the operation is denied for query"); + } + + public function getEntityName(){ + return $this->name; + } + + public function getEntityId(){ + return $this->id; + } + + public function getHandlerPath(){ + return $this->handlerPath; + } + + public function getHandlerClass(){ + return $this->handlerClass; + } + +} +?> \ No newline at end of file diff --git a/include/Webservices/WebServiceError.php b/include/Webservices/WebServiceError.php new file mode 100644 index 0000000..13725b7 --- /dev/null +++ b/include/Webservices/WebServiceError.php @@ -0,0 +1,24 @@ +code = $errCode; + $this->message = $msg; + } + + } + +?> \ No newline at end of file diff --git a/include/Webservices/WebServiceErrorCode.php b/include/Webservices/WebServiceErrorCode.php new file mode 100644 index 0000000..28b2a4e --- /dev/null +++ b/include/Webservices/WebServiceErrorCode.php @@ -0,0 +1,42 @@ + diff --git a/include/Webservices/WebserviceEntityOperation.php b/include/Webservices/WebserviceEntityOperation.php new file mode 100644 index 0000000..390a010 --- /dev/null +++ b/include/Webservices/WebserviceEntityOperation.php @@ -0,0 +1,122 @@ +user = $user; + $this->log = $log; + $this->webserviceObject = $webserviceObject; + $this->pearDB = $adb; + } + + public function create($elementType,$element){ + throw new WebServiceException(WebServiceErrorCode::$OPERATIONNOTSUPPORTED, + "Operation Create is not supported for this entity"); + } + + public function retrieve($id){ + throw new WebServiceException(WebServiceErrorCode::$OPERATIONNOTSUPPORTED, + "Operation Retrieve is not supported for this entity"); + } + + public function update($element){ + throw new WebServiceException(WebServiceErrorCode::$OPERATIONNOTSUPPORTED, + "Operation Update is not supported for this entity"); + } + + public function revise($element){ + throw new WebServiceException(WebServiceErrorCode::$OPERATIONNOTSUPPORTED, + "Operation Update is not supported for this entity"); + } + + public function delete($id){ + throw new WebServiceException(WebServiceErrorCode::$OPERATIONNOTSUPPORTED, + "Operation delete is not supported for this entity"); + } + + public function query($q){ + throw new WebServiceException(WebServiceErrorCode::$OPERATIONNOTSUPPORTED, + "Operation query is not supported for this entity"); + } + + public function describe($elementType){ + throw new WebServiceException(WebServiceErrorCode::$OPERATIONNOTSUPPORTED, + "Operation describe is not supported for this entity"); + } + + function getFieldTypeDetails($webserviceField){ + global $upload_maxsize; + $typeDetails = array(); + switch($webserviceField->getFieldDataType()){ + case 'reference': $typeDetails['refersTo'] = $webserviceField->getReferenceList(); + break; + case 'multipicklist': + case 'picklist': $typeDetails["picklistValues"] = $webserviceField->getPicklistDetails($webserviceField); + $typeDetails['defaultValue'] = $typeDetails["picklistValues"][0]['value']; + break; + case 'file': $maxUploadSize = 0; + $maxUploadSize = ini_get('upload_max_filesize'); + $maxUploadSize = strtolower($maxUploadSize); + $maxUploadSize = explode('m',$maxUploadSize); + $maxUploadSize = $maxUploadSize[0]; + if(!is_numeric($maxUploadSize)){ + $maxUploadSize = 0; + } + $maxUploadSize = $maxUploadSize * 1000000; + if($upload_maxsize > $maxUploadSize){ + $maxUploadSize = $upload_maxsize; + } + $typeDetails['maxUploadFileSize'] = $maxUploadSize; + break; + case 'date': $typeDetails['format'] = $this->user->date_format; + } + return $typeDetails; + } + + function isEditable($webserviceField){ + if(((int)$webserviceField->getDisplayType()) === 2 || strcasecmp($webserviceField->getFieldDataType(),"autogenerated") + ===0 || strcasecmp($webserviceField->getFieldDataType(),"id")===0 || $webserviceField->isReadOnly() == true){ + return false; + } + //uitype 70 is vtiger generated fields, such as (of vtiger_crmentity table) createdtime + //and modified time fields. + if($webserviceField->getUIType() == 70 || $webserviceField->getUIType() == 4){ + return false; + } + return true; + } + + function getIdField($label){ + return array('name'=>'id','label'=>$label,'mandatory'=>false,'type'=>'id','editable'=>false,'type'=> + array('name'=>'autogenerated'),'nullable'=>false,'default'=>""); + } + + /** + * @return Intance of EntityMeta class. + * + */ + abstract public function getMeta(); + abstract protected function getMetaInstance(); + +} + +?> \ No newline at end of file diff --git a/include/Webservices/WebserviceField.php b/include/Webservices/WebserviceField.php new file mode 100644 index 0000000..800bce4 --- /dev/null +++ b/include/Webservices/WebserviceField.php @@ -0,0 +1,397 @@ +uitype = $row['uitype']; + $this->blockId = $row['block']; + $this->blockName = null; + $this->tableName = $row['tablename']; + $this->columnName = $row['columnname']; + $this->fieldName = $row['fieldname']; + $this->fieldLabel = $row['fieldlabel']; + $this->displayType = $row['displaytype']; + $this->massEditable = ($row['masseditable'] === '1')? true: false; + $typeOfData = $row['typeofdata']; + $this->presence = $row['presence']; + $this->typeOfData = $typeOfData; + $typeOfData = explode("~",$typeOfData); + $this->mandatory = ($typeOfData[1] == 'M')? true: false; + if($this->uitype == 4){ + $this->mandatory = false; + } + $this->fieldType = $typeOfData[0]; + $this->tabid = $row['tabid']; + $this->fieldId = $row['fieldid']; + $this->pearDB = $adb; + $this->fieldDataType = null; + $this->dataFromMeta = false; + $this->defaultValuePresent = false; + $this->referenceList = null; + $this->explicitDefaultValue = false; + + $this->readOnly = (isset($row['readonly']))? $row['readonly'] : 0; + + if(array_key_exists('defaultvalue', $row)) { + $this->setDefault($row['defaultvalue']); + } + } + + public static function fromQueryResult($adb,$result,$rowNumber){ + return new WebserviceField($adb,$adb->query_result_rowdata($result,$rowNumber)); + } + + public static function fromArray($adb,$row){ + return new WebserviceField($adb,$row); + } + + public function getTableName(){ + return $this->tableName; + } + + public function getFieldName(){ + return $this->fieldName; + } + + public function getFieldLabelKey(){ + return $this->fieldLabel; + } + + public function getFieldType(){ + return $this->fieldType; + } + + public function isMandatory(){ + return $this->mandatory; + } + + public function getTypeOfData(){ + return $this->typeOfData; + } + + public function getDisplayType(){ + return $this->displayType; + } + + public function getMassEditable(){ + return $this->massEditable; + } + + public function getFieldId(){ + return $this->fieldId; + } + + public function getDefault(){ + if($this->dataFromMeta !== true && $this->explicitDefaultValue !== true){ + $this->fillColumnMeta(); + } + return $this->default; + } + + public function getColumnName(){ + return $this->columnName; + } + + public function getBlockId(){ + return $this->blockId; + } + + public function getBlockName(){ + if(empty($this->blockName)) { + $this->blockName = getBlockName($this->blockId); + } + return $this->blockName; + } + + public function getTabId(){ + return $this->tabid; + } + + public function isNullable(){ + if($this->dataFromMeta !== true){ + $this->fillColumnMeta(); + } + return $this->nullable; + } + + public function hasDefault(){ + if($this->dataFromMeta !== true && $this->explicitDefaultValue !== true){ + $this->fillColumnMeta(); + } + return $this->defaultValuePresent; + } + + public function getUIType(){ + return $this->uitype; + } + + public function isReadOnly() { + if($this->readOnly == 1) return true; + return false; + } + + private function setNullable($nullable){ + $this->nullable = $nullable; + } + + public function setDefault($value){ + $this->default = $value; + $this->explicitDefaultValue = true; + $this->defaultValuePresent = true; + } + + public function setFieldDataType($dataType){ + $this->fieldDataType = $dataType; + } + + public function setReferenceList($referenceList){ + $this->referenceList = $referenceList; + } + + public function getTableFields(){ + $tableFields = null; + if(isset(WebserviceField::$tableMeta[$this->getTableName()])){ + $tableFields = WebserviceField::$tableMeta[$this->getTableName()]; + }else{ + $dbMetaColumns = $this->pearDB->database->MetaColumns($this->getTableName()); + $tableFields = array(); + foreach ($dbMetaColumns as $key => $dbField) { + $tableFields[$dbField->name] = $dbField; + } + WebserviceField::$tableMeta[$this->getTableName()] = $tableFields; + } + return $tableFields; + } + public function fillColumnMeta(){ + $tableFields = $this->getTableFields(); + foreach ($tableFields as $fieldName => $dbField) { + if(strcmp($fieldName,$this->getColumnName())===0){ + $this->setNullable(!$dbField->not_null); + if($dbField->has_default === true && !$this->explicitDefaultValue){ + $this->defaultValuePresent = $dbField->has_default; + $this->setDefault($dbField->default_value); + } + } + } + $this->dataFromMeta = true; + } + + public function getFieldDataType(){ + if($this->fieldDataType === null){ + $fieldDataType = $this->getFieldTypeFromUIType(); + if($fieldDataType === null){ + $fieldDataType = $this->getFieldTypeFromTypeOfData(); + } + if($fieldDataType == 'date' || $fieldDataType == 'datetime' || $fieldDataType == 'time') { + $tableFieldDataType = $this->getFieldTypeFromTable(); + if($tableFieldDataType == 'datetime'){ + $fieldDataType = $tableFieldDataType; + } + } + $this->fieldDataType = $fieldDataType; + } + return $this->fieldDataType; + } + + public function getReferenceList(){ + static $referenceList = array(); + if($this->referenceList === null){ + if(isset($referenceList[$this->getFieldId()])){ + $this->referenceList = $referenceList[$this->getFieldId()]; + return $referenceList[$this->getFieldId()]; + } + if(!isset(WebserviceField::$fieldTypeMapping[$this->getUIType()])){ + $this->getFieldTypeFromUIType(); + } + $fieldTypeData = WebserviceField::$fieldTypeMapping[$this->getUIType()]; + $referenceTypes = array(); + if($this->getUIType() != $this->genericUIType){ + $sql = "select * from vtiger_ws_referencetype where fieldtypeid=?"; + $params = array($fieldTypeData['fieldtypeid']); + }else{ + $sql = 'select relmodule as type from vtiger_fieldmodulerel where fieldid=?'; + $params = array($this->getFieldId()); + } + $result = $this->pearDB->pquery($sql,$params); + $numRows = $this->pearDB->num_rows($result); + for($i=0;$i<$numRows;++$i){ + array_push($referenceTypes,$this->pearDB->query_result($result,$i,"type")); + } + + //to handle hardcoding done for Calendar module todo activities. + if($this->tabid == 9 && $this->fieldName =='parent_id'){ + $referenceTypes[] = 'Invoice'; + $referenceTypes[] = 'Quotes'; + $referenceTypes[] = 'PurchaseOrder'; + $referenceTypes[] = 'SalesOrder'; + $referenceTypes[] = 'Campaigns'; + } + + global $current_user; + $types = vtws_listtypes(null, $current_user); + $accessibleTypes = $types['types']; + if(!is_admin($current_user)) { + array_push($accessibleTypes, 'Users'); + } + $referenceTypes = array_values(array_intersect($accessibleTypes,$referenceTypes)); + $referenceList[$this->getFieldId()] = $referenceTypes; + $this->referenceList = $referenceTypes; + return $referenceTypes; + } + return $this->referenceList; + } + + private function getFieldTypeFromTable(){ + $tableFields = $this->getTableFields(); + foreach ($tableFields as $fieldName => $dbField) { + if(strcmp($fieldName,$this->getColumnName())===0){ + return $dbField->type; + } + } + //This should not be returned if entries in DB are correct. + return null; + } + + private function getFieldTypeFromTypeOfData(){ + switch($this->fieldType){ + case 'T': return "time"; + case 'D': + case 'DT': return "date"; + case 'E': return "email"; + case 'N': + case 'NN': return "double"; + case 'P': return "password"; + case 'I': return "integer"; + case 'V': + default: return "string"; + } + } + + private function getFieldTypeFromUIType(){ + + // Cache all the information for futher re-use + if(empty(self::$fieldTypeMapping)) { + $result = $this->pearDB->pquery("select * from vtiger_ws_fieldtype", array()); + while($resultrow = $this->pearDB->fetch_array($result)) { + self::$fieldTypeMapping[$resultrow['uitype']] = $resultrow; + } + } + + if(isset(WebserviceField::$fieldTypeMapping[$this->getUIType()])){ + if(WebserviceField::$fieldTypeMapping[$this->getUIType()] === false){ + return null; + } + $row = WebserviceField::$fieldTypeMapping[$this->getUIType()]; + return $row['fieldtype']; + } else { + WebserviceField::$fieldTypeMapping[$this->getUIType()] = false; + return null; + } + } + + function getPicklistDetails(){ + $hardCodedPickListNames = array("hdntaxtype","email_flag"); + $hardCodedPickListValues = array( + "hdntaxtype"=>array( + array("label"=>"Individual","value"=>"individual"), + array("label"=>"Group","value"=>"group") + ), + "email_flag" => array( + array('label'=>'SAVED','value'=>'SAVED'), + array('label'=>'SENT','value' => 'SENT'), + array('label'=>'MAILSCANNER','value' => 'MAILSCANNER') + ) + ); + if(in_array(strtolower($this->getFieldName()),$hardCodedPickListNames)){ + return $hardCodedPickListValues[strtolower($this->getFieldName())]; + } + return $this->getPickListOptions($this->getFieldName()); + } + + function getPickListOptions(){ + $fieldName = $this->getFieldName(); + + $default_charset = VTWS_PreserveGlobal::getGlobal('default_charset'); + $options = array(); + $sql = "select * from vtiger_picklist where name=?"; + $result = $this->pearDB->pquery($sql,array($fieldName)); + $numRows = $this->pearDB->num_rows($result); + if($numRows == 0){ + $sql = "select * from vtiger_$fieldName"; + $result = $this->pearDB->pquery($sql,array()); + $numRows = $this->pearDB->num_rows($result); + for($i=0;$i<$numRows;++$i){ + $elem = array(); + $picklistValue = $this->pearDB->query_result($result,$i,$fieldName); + $picklistValue = decode_html($picklistValue); + $moduleName = getTabModuleName($this->getTabId()); + if($moduleName == 'Events') $moduleName = 'Calendar'; + $elem["label"] = getTranslatedString($picklistValue,$moduleName); + $elem["value"] = $picklistValue; + array_push($options,$elem); + } + }else{ + $user = VTWS_PreserveGlobal::getGlobal('current_user'); + $details = getPickListValues($fieldName,$user->roleid); + for($i=0;$igetTabId()); + if($moduleName == 'Events') $moduleName = 'Calendar'; + $elem["label"] = getTranslatedString($picklistValue,$moduleName); + $elem["value"] = $picklistValue; + array_push($options,$elem); + } + } + return $options; + } + + function getPresence() { + return $this->presence; + } + +} + +?> diff --git a/include/Webservices/language/de_de.lang.php b/include/Webservices/language/de_de.lang.php new file mode 100644 index 0000000..2f527ae --- /dev/null +++ b/include/Webservices/language/de_de.lang.php @@ -0,0 +1,28 @@ +'Gruppen', + 'DocumentFolders'=>'Dokumentenverzeichnisse', + 'Currency'=>'Währung', + 'SINGLE_Groups'=>'Gruppe', + 'SINGLE_DocumentFolders'=>'Dokumentenverzeichnis', + 'SINGLE_Currency'=>'Währung', +); + +$webservice_strings = array( + 'LBL_INVALID_OLD_PASSWORD' => 'Ungültige Angabe des alten Passwortes.', + 'LBL_NEW_PASSWORD_MISMATCH' => "Die beiden Passwörter stimmen nicht überein.", + 'LBL_DATABASE_QUERY_ERROR' => 'Ihre Anfrage konnte aufgrund eines Datenbankfehlers nicht verarbeitet werden.', + 'LBL_CHANGE_PASSWORD_FAILURE' => 'Passwort konnte nicht geändert werden.Failed to change password', +); + +?> \ No newline at end of file diff --git a/include/Webservices/language/en_gb.lang.php b/include/Webservices/language/en_gb.lang.php new file mode 100644 index 0000000..eae195e --- /dev/null +++ b/include/Webservices/language/en_gb.lang.php @@ -0,0 +1,26 @@ +'Groups', + 'DocumentFolders'=>'Document Folders', + 'Currency'=>'Currency', + 'SINGLE_Groups'=>'Group', + 'SINGLE_DocumentFolders'=>'Document Folder', + 'SINGLE_Currency'=>'Currency', +); + +$webservice_strings = array( + 'LBL_INVALID_OLD_PASSWORD' => 'Invalid value given for old password.', + 'LBL_NEW_PASSWORD_MISMATCH' => "New password and confirm password don't match", + 'LBL_DATABASE_QUERY_ERROR' => 'Database error while performing requested operation', + 'LBL_CHANGE_PASSWORD_FAILURE' => 'Failed to change password', +); +?> \ No newline at end of file diff --git a/include/Webservices/language/en_us.lang.php b/include/Webservices/language/en_us.lang.php new file mode 100644 index 0000000..8d191e7 --- /dev/null +++ b/include/Webservices/language/en_us.lang.php @@ -0,0 +1,27 @@ +'Groups', + 'DocumentFolders'=>'Document Folders', + 'Currency'=>'Currency', + 'SINGLE_Groups'=>'Group', + 'SINGLE_DocumentFolders'=>'Document Folder', + 'SINGLE_Currency'=>'Currency', +); + +$webservice_strings = array( + 'LBL_INVALID_OLD_PASSWORD' => 'Invalid value given for old password.', + 'LBL_NEW_PASSWORD_MISMATCH' => "New Password and confirm password don't match", + 'LBL_DATABASE_QUERY_ERROR' => 'Database error while performing requested operation', + 'LBL_CHANGE_PASSWORD_FAILURE' => 'Failed to change password', +); + +?> \ No newline at end of file diff --git a/include/Webservices/language/es_es.lang.php b/include/Webservices/language/es_es.lang.php new file mode 100644 index 0000000..d33aa63 --- /dev/null +++ b/include/Webservices/language/es_es.lang.php @@ -0,0 +1,27 @@ +'Grupos', + 'DocumentFolders'=>'Carpetas Documentos', + 'Currency'=>'Monedas', + 'SINGLE_Groups'=>'Grupo', + 'SINGLE_DocumentFolders'=>'Carpeta Documento', + 'SINGLE_Currency'=>'Moneda', +); + +$webservice_strings = array( + 'LBL_INVALID_OLD_PASSWORD' => 'Contraseña anterior inválida.', + 'LBL_NEW_PASSWORD_MISMATCH' => "Nueva contraseña y confirmación no coinciden.", + 'LBL_DATABASE_QUERY_ERROR' => 'Error de base de datos al procesar la operación', + 'LBL_CHANGE_PASSWORD_FAILURE' => 'No se ha podido cambiar la contraseña', +); + +?> diff --git a/include/Webservices/language/es_mx.lang.php b/include/Webservices/language/es_mx.lang.php new file mode 100644 index 0000000..aecf8d3 --- /dev/null +++ b/include/Webservices/language/es_mx.lang.php @@ -0,0 +1,27 @@ +'Grupos', + 'DocumentFolders'=>'Carpetas Documentos', + 'Currency'=>'Monedas', + 'SINGLE_Groups'=>'Grupo', + 'SINGLE_DocumentFolders'=>'Carpeta Documento', + 'SINGLE_Currency'=>'Moneda', +); + +$webservice_strings = array( + 'LBL_INVALID_OLD_PASSWORD' => 'Contraseña anterior inválida.', + 'LBL_NEW_PASSWORD_MISMATCH' => "Nueva contraseña y confirmación no coinciden.", + 'LBL_DATABASE_QUERY_ERROR' => 'Error de base de datos al procesar la operación', + 'LBL_CHANGE_PASSWORD_FAILURE' => 'No se ha podido cambiar la contraseña', +); + +?> diff --git a/include/Webservices/language/nl_nl.lang.php b/include/Webservices/language/nl_nl.lang.php new file mode 100644 index 0000000..feca0f0 --- /dev/null +++ b/include/Webservices/language/nl_nl.lang.php @@ -0,0 +1,50 @@ + + * - Weltevree.org + ********************************************************************************/ + +/******************************************************************************* + * Vicus eBusiness Solutions Version Control + * @package NL-Dutch + * Description Dutch language pack for vtiger CRM version 5.3.x + * @author $Author: luuk $ + * @version $Revision: 1.2 $ $Date: 2011/11/14 17:07:26 $ + * @source $Source: /var/lib/cvs/vtiger530/Dutch/include/Webservices/language/nl_nl.lang.php,v $ + * @copyright Copyright (c)2005-2011 Vicus eBusiness Solutions bv + * @license vtiger CRM Public License Version 1.0 (by definition) + ********************************************************************************/ + +$app_strings = array ( + 'Groups'=>'Groepen', + 'DocumentFolders'=>'Documentmappen', + 'Currency'=>'Valuta', + 'SINGLE_Groups'=>'Groep', + 'SINGLE_DocumentFolders'=>'Documentmap', + 'SINGLE_Currency'=>'Valuta', +); + +$webservice_strings = array( + 'LBL_INVALID_OLD_PASSWORD' => 'Ongeldige waarden gegeven voor het oude wachtwoord.', + 'LBL_NEW_PASSWORD_MISMATCH' => "Het nieuwe wachtwoord komt niet overeen met de bevestiging.", + 'LBL_DATABASE_QUERY_ERROR' => 'Database fout tijdens uitvoeren van de gevraagde operatie', + 'LBL_CHANGE_PASSWORD_FAILURE' => 'Wijzigen wachtwoord is mislukt', +); + +?> \ No newline at end of file diff --git a/include/Webservices/language/pt_br.lang.php b/include/Webservices/language/pt_br.lang.php new file mode 100644 index 0000000..109ecaf --- /dev/null +++ b/include/Webservices/language/pt_br.lang.php @@ -0,0 +1,27 @@ +'Grupos', + 'DocumentFolders'=>'Pastas Documentos', + 'Currency'=>'Moeda', + 'SINGLE_Groups'=>'Grupo', + 'SINGLE_DocumentFolders'=>'Pasta Documento', + 'SINGLE_Currency'=>'Moeda', +); + +$webservice_strings = array( + 'LBL_INVALID_OLD_PASSWORD' => 'Senha antiga inválida.', + 'LBL_NEW_PASSWORD_MISMATCH' => "A nova Senha e a Senha de confirmação não são iguais", + 'LBL_DATABASE_QUERY_ERROR' => 'Erro na base de dados ao executar a operação solicitada', + 'LBL_CHANGE_PASSWORD_FAILURE' => 'Falha ao alterar a Senha', +); + +?> diff --git a/include/Webservices/language/zh_cn.lang.php b/include/Webservices/language/zh_cn.lang.php new file mode 100644 index 0000000..6d07076 --- /dev/null +++ b/include/Webservices/language/zh_cn.lang.php @@ -0,0 +1,40 @@ +'Groups', + 'DocumentFolders'=>'Document Folders', + 'Currency'=>'Currency', + 'SINGLE_Groups'=>'Group', + 'SINGLE_DocumentFolders'=>'Document Folder', + 'SINGLE_Currency'=>'Currency', +); + +$webservice_strings = array( + 'LBL_INVALID_OLD_PASSWORD' => 'Invalid value given for old password.', + 'LBL_NEW_PASSWORD_MISMATCH' => "New Password and confirm password don't match", + 'LBL_DATABASE_QUERY_ERROR' => 'Database error while performing requested operation', + 'LBL_CHANGE_PASSWORD_FAILURE' => 'Failed to change password', +); + +?> \ No newline at end of file diff --git a/include/Zend/Exception.php b/include/Zend/Exception.php new file mode 100644 index 0000000..637f91b --- /dev/null +++ b/include/Zend/Exception.php @@ -0,0 +1,30 @@ +_source = $source; + $this->_sourceLength = strlen($source); + $this->_token = self::EOF; + $this->_offset = 0; + + // Normalize and set $decodeType + if (!in_array($decodeType, array(Zend_Json::TYPE_ARRAY, Zend_Json::TYPE_OBJECT))) + { + $decodeType = Zend_Json::TYPE_ARRAY; + } + $this->_decodeType = $decodeType; + + // Set pointer at first token + $this->_getNextToken(); + } + + /** + * Decode a JSON source string + * + * Decodes a JSON encoded string. The value returned will be one of the + * following: + * - integer + * - float + * - boolean + * - null + * - StdClass + * - array + * - array of one or more of the above types + * + * By default, decoded objects will be returned as associative arrays; to + * return a StdClass object instead, pass {@link Zend_Json::TYPE_OBJECT} to + * the $objectDecodeType parameter. + * + * Throws a Zend_Json_Exception if the source string is null. + * + * @static + * @access public + * @param string $source String to be decoded + * @param int $objectDecodeType How objects should be decoded; should be + * either or {@link Zend_Json::TYPE_ARRAY} or + * {@link Zend_Json::TYPE_OBJECT}; defaults to TYPE_ARRAY + * @return mixed + * @throws Zend_Json_Exception + */ + public static function decode($source = null, $objectDecodeType = Zend_Json::TYPE_ARRAY) + { + if (null === $source) { + throw new Zend_Json_Exception('Must specify JSON encoded source for decoding'); + } elseif (!is_string($source)) { + throw new Zend_Json_Exception('Can only decode JSON encoded strings'); + } + + $decoder = new self($source, $objectDecodeType); + + return $decoder->_decodeValue(); + } + + + /** + * Recursive driving rountine for supported toplevel tops + * + * @return mixed + */ + protected function _decodeValue() + { + switch ($this->_token) { + case self::DATUM: + $result = $this->_tokenValue; + $this->_getNextToken(); + return($result); + break; + case self::LBRACE: + return($this->_decodeObject()); + break; + case self::LBRACKET: + return($this->_decodeArray()); + break; + default: + return null; + break; + } + } + + /** + * Decodes an object of the form: + * { "attribute: value, "attribute2" : value,...} + * + * If ZJsonEnoder or ZJAjax was used to encode the original object + * then a special attribute called __className which specifies a class + * name that should wrap the data contained within the encoded source. + * + * Decodes to either an array or StdClass object, based on the value of + * {@link $_decodeType}. If invalid $_decodeType present, returns as an + * array. + * + * @return array|StdClass + */ + protected function _decodeObject() + { + $members = array(); + $tok = $this->_getNextToken(); + + while ($tok && $tok != self::RBRACE) { + if ($tok != self::DATUM || ! is_string($this->_tokenValue)) { + throw new Zend_Json_Exception('Missing key in object encoding: ' . $this->_source); + } + + $key = $this->_tokenValue; + $tok = $this->_getNextToken(); + + if ($tok != self::COLON) { + throw new Zend_Json_Exception('Missing ":" in object encoding: ' . $this->_source); + } + + $tok = $this->_getNextToken(); + $members[$key] = $this->_decodeValue(); + $tok = $this->_token; + + if ($tok == self::RBRACE) { + break; + } + + if ($tok != self::COMMA) { + throw new Zend_Json_Exception('Missing "," in object encoding: ' . $this->_source); + } + + $tok = $this->_getNextToken(); + } + + switch ($this->_decodeType) { + case Zend_Json::TYPE_OBJECT: + // Create new StdClass and populate with $members + $result = new StdClass(); + foreach ($members as $key => $value) { + $result->$key = $value; + } + break; + case Zend_Json::TYPE_ARRAY: + default: + $result = $members; + break; + } + + $this->_getNextToken(); + return $result; + } + + /** + * Decodes a JSON array format: + * [element, element2,...,elementN] + * + * @return array + */ + protected function _decodeArray() + { + $result = array(); + $starttok = $tok = $this->_getNextToken(); // Move past the '[' + $index = 0; + + while ($tok && $tok != self::RBRACKET) { + $result[$index++] = $this->_decodeValue(); + + $tok = $this->_token; + + if ($tok == self::RBRACKET || !$tok) { + break; + } + + if ($tok != self::COMMA) { + throw new Zend_Json_Exception('Missing "," in array encoding: ' . $this->_source); + } + + $tok = $this->_getNextToken(); + } + + $this->_getNextToken(); + return($result); + } + + + /** + * Removes whitepsace characters from the source input + */ + protected function _eatWhitespace() + { + if (preg_match( + '/([\t\b\f\n\r ])*/s', + $this->_source, + $matches, + PREG_OFFSET_CAPTURE, + $this->_offset) + && $matches[0][1] == $this->_offset) + { + $this->_offset += strlen($matches[0][0]); + } + } + + + /** + * Retrieves the next token from the source stream + * + * @return int Token constant value specified in class definition + */ + protected function _getNextToken() + { + $this->_token = self::EOF; + $this->_tokenValue = null; + $this->_eatWhitespace(); + + if ($this->_offset >= $this->_sourceLength) { + return(self::EOF); + } + + $str = $this->_source; + $str_length = $this->_sourceLength; + $i = $this->_offset; + $start = $i; + + switch ($str{$i}) { + case '{': + $this->_token = self::LBRACE; + break; + case '}': + $this->_token = self::RBRACE; + break; + case '[': + $this->_token = self::LBRACKET; + break; + case ']': + $this->_token = self::RBRACKET; + break; + case ',': + $this->_token = self::COMMA; + break; + case ':': + $this->_token = self::COLON; + break; + case '"': + $result = ''; + do { + $i++; + if ($i >= $str_length) { + break; + } + + $chr = $str{$i}; + if ($chr == '\\') { + $i++; + if ($i >= $str_length) { + break; + } + $chr = $str{$i}; + switch ($chr) { + case '"' : + $result .= '"'; + break; + case '\\': + $result .= '\\'; + break; + case '/' : + $result .= '/'; + break; + case 'b' : + $result .= chr(8); + break; + case 'f' : + $result .= chr(12); + break; + case 'n' : + $result .= chr(10); + break; + case 'r' : + $result .= chr(13); + break; + case 't' : + $result .= chr(9); + break; + case '\'' : + $result .= '\''; + break; + default: + throw new Zend_Json_Exception("Illegal escape " + . "sequence '" . $chr . "'"); + } + } elseif ($chr == '"') { + break; + } else { + $result .= $chr; + } + } while ($i < $str_length); + + $this->_token = self::DATUM; + //$this->_tokenValue = substr($str, $start + 1, $i - $start - 1); + $this->_tokenValue = $result; + break; + case 't': + if (($i+ 3) < $str_length && substr($str, $start, 4) == "true") { + $this->_token = self::DATUM; + } + $this->_tokenValue = true; + $i += 3; + break; + case 'f': + if (($i+ 4) < $str_length && substr($str, $start, 5) == "false") { + $this->_token = self::DATUM; + } + $this->_tokenValue = false; + $i += 4; + break; + case 'n': + if (($i+ 3) < $str_length && substr($str, $start, 4) == "null") { + $this->_token = self::DATUM; + } + $this->_tokenValue = NULL; + $i += 3; + break; + } + + if ($this->_token != self::EOF) { + $this->_offset = $i + 1; // Consume the last token character + return($this->_token); + } + + $chr = $str{$i}; + if ($chr == '-' || $chr == '.' || ($chr >= '0' && $chr <= '9')) { + if (preg_match('/-?([0-9])*(\.[0-9]*)?((e|E)((-|\+)?)[0-9]+)?/s', + $str, $matches, PREG_OFFSET_CAPTURE, $start) && $matches[0][1] == $start) { + + $datum = $matches[0][0]; + + if (is_numeric($datum)) { + if (preg_match('/^0\d+$/', $datum)) { + throw new Zend_Json_Exception("Octal notation not supported by JSON (value: $datum)"); + } else { + $val = intval($datum); + $fVal = floatval($datum); + $this->_tokenValue = ($val == $fVal ? $val : $fVal); + } + } else { + throw new Zend_Json_Exception("Illegal number format: $datum"); + } + + $this->_token = self::DATUM; + $this->_offset = $start + strlen($datum); + } + } else { + throw new Zend_Json_Exception('Illegal Token'); + } + + return($this->_token); + } +} + diff --git a/include/Zend/Json/Encoder.php b/include/Zend/Json/Encoder.php new file mode 100644 index 0000000..6ffa81d --- /dev/null +++ b/include/Zend/Json/Encoder.php @@ -0,0 +1,414 @@ +_cycleCheck = $cycleCheck; + } + + /** + * Use the JSON encoding scheme for the value specified + * + * @param mixed $value The value to be encoded + * @param boolean $cycleCheck Whether or not to check for possible object recursion when encoding + * @return string The encoded value + */ + public static function encode($value, $cycleCheck = false) + { + $encoder = new Zend_Json_Encoder(($cycleCheck) ? true : false); + + return $encoder->_encodeValue($value); + } + + /** + * Recursive driver which determines the type of value to be encoded + * and then dispatches to the appropriate method. $values are either + * - objects (returns from {@link _encodeObject()}) + * - arrays (returns from {@link _encodeArray()}) + * - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()}) + * + * @param $value mixed The value to be encoded + * @return string Encoded value + */ + protected function _encodeValue(&$value) + { + if (is_object($value)) { + return $this->_encodeObject($value); + } else if (is_array($value)) { + return $this->_encodeArray($value); + } + + return $this->_encodeDatum($value); + } + + + + /** + * Encode an object to JSON by encoding each of the public properties + * + * A special property is added to the JSON object called '__className' + * that contains the name of the class of $value. This is used to decode + * the object on the client into a specific class. + * + * @param $value object + * @return string + * @throws Zend_Json_Exception If recursive checks are enabled and the object has been serialized previously + */ + protected function _encodeObject(&$value) + { + if ($this->_cycleCheck) { + if ($this->_wasVisited($value)) { + throw new Zend_Json_Exception( + 'Cycles not supported in JSON encoding, cycle introduced by ' + . 'class "' . get_class($value) . '"' + ); + } + + $this->_visited[] = $value; + } + + $props = ''; + foreach (get_object_vars($value) as $name => $propValue) { + if (isset($propValue)) { + $props .= ',' + . $this->_encodeValue($name) + . ':' + . $this->_encodeValue($propValue); + } + } + + /*return '{"__className":"' . get_class($value) . '"' + . $props . '}';*/ + return '{'.substr($props,1).'}'; + } + + + /** + * Determine if an object has been serialized already + * + * @param mixed $value + * @return boolean + */ + protected function _wasVisited(&$value) + { + if (in_array($value, $this->_visited, true)) { + return true; + } + + return false; + } + + + /** + * JSON encode an array value + * + * Recursively encodes each value of an array and returns a JSON encoded + * array string. + * + * Arrays are defined as integer-indexed arrays starting at index 0, where + * the last index is (count($array) -1); any deviation from that is + * considered an associative array, and will be encoded as such. + * + * @param $array array + * @return string + */ + protected function _encodeArray(&$array) + { + $tmpArray = array(); + + // Check for associative array + if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) { + // Associative array + $result = '{'; + foreach ($array as $key => $value) { + $key = (string) $key; + $tmpArray[] = $this->_encodeString($key) + . ':' + . $this->_encodeValue($value); + } + $result .= implode(',', $tmpArray); + $result .= '}'; + } else { + // Indexed array + $result = '['; + $length = count($array); + for ($i = 0; $i < $length; $i++) { + $tmpArray[] = $this->_encodeValue($array[$i]); + } + $result .= implode(',', $tmpArray); + $result .= ']'; + } + + return $result; + } + + + /** + * JSON encode a basic data type (string, number, boolean, null) + * + * If value type is not a string, number, boolean, or null, the string + * 'null' is returned. + * + * @param $value mixed + * @return string + */ + protected function _encodeDatum(&$value) + { + $result = 'null'; + + if (is_int($value) || is_float($value)) { + $result = (string)$value; + } elseif (is_string($value)) { + $result = $this->_encodeString($value); + } elseif (is_bool($value)) { + $result = $value ? 'true' : 'false'; + } + + return $result; + } + + + /** + * JSON encode a string value by escaping characters as necessary + * + * @param $value string + * @return string + */ + protected function _encodeString(&$string) + { + // Escape these characters with a backslash: + // " \ / \n \r \t \b \f + $search = array('\\', "\n", "\t", "\r", "\b", "\f", '"'); + $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'); + $string = str_replace($search, $replace, $string); + + // Escape certain ASCII characters: + // 0x08 => \b + // 0x0c => \f + $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string); + + return '"' . $string . '"'; + } + + + /** + * Encode the constants associated with the ReflectionClass + * parameter. The encoding format is based on the class2 format + * + * @param $cls ReflectionClass + * @return string Encoded constant block in class2 format + */ + private static function _encodeConstants(ReflectionClass $cls) + { + $result = "constants : {"; + $constants = $cls->getConstants(); + + $tmpArray = array(); + if (!empty($constants)) { + foreach ($constants as $key => $value) { + $tmpArray[] = "$key: " . self::encode($value); + } + + $result .= implode(', ', $tmpArray); + } + + return $result . "}"; + } + + + /** + * Encode the public methods of the ReflectionClass in the + * class2 format + * + * @param $cls ReflectionClass + * @return string Encoded method fragment + * + */ + private static function _encodeMethods(ReflectionClass $cls) + { + $methods = $cls->getMethods(); + $result = 'methods:{'; + + $started = false; + foreach ($methods as $method) { + if (! $method->isPublic() || !$method->isUserDefined()) { + continue; + } + + if ($started) { + $result .= ','; + } + $started = true; + + $result .= '' . $method->getName(). ':function('; + + if ('__construct' != $method->getName()) { + $parameters = $method->getParameters(); + $paramCount = count($parameters); + $argsStarted = false; + + $argNames = "var argNames=["; + foreach ($parameters as $param) { + if ($argsStarted) { + $result .= ','; + } + + $result .= $param->getName(); + + if ($argsStarted) { + $argNames .= ','; + } + + $argNames .= '"' . $param->getName() . '"'; + + $argsStarted = true; + } + $argNames .= "];"; + + $result .= "){" + . $argNames + . 'var result = ZAjaxEngine.invokeRemoteMethod(' + . "this, '" . $method->getName() + . "',argNames,arguments);" + . 'return(result);}'; + } else { + $result .= "){}"; + } + } + + return $result . "}"; + } + + + /** + * Encode the public properties of the ReflectionClass in the class2 + * format. + * + * @param $cls ReflectionClass + * @return string Encode properties list + * + */ + private static function _encodeVariables(ReflectionClass $cls) + { + $properties = $cls->getProperties(); + $propValues = get_class_vars($cls->getName()); + $result = "variables:{"; + $cnt = 0; + + $tmpArray = array(); + foreach ($properties as $prop) { + if (! $prop->isPublic()) { + continue; + } + + $tmpArray[] = $prop->getName() + . ':' + . self::encode($propValues[$prop->getName()]); + } + $result .= implode(',', $tmpArray); + + return $result . "}"; + } + + /** + * Encodes the given $className into the class2 model of encoding PHP + * classes into JavaScript class2 classes. + * NOTE: Currently only public methods and variables are proxied onto + * the client machine + * + * @param $className string The name of the class, the class must be + * instantiable using a null constructor + * @param $package string Optional package name appended to JavaScript + * proxy class name + * @return string The class2 (JavaScript) encoding of the class + * @throws Zend_Json_Exception + */ + public static function encodeClass($className, $package = '') + { + $cls = new ReflectionClass($className); + if (! $cls->isInstantiable()) { + throw new Zend_Json_Exception("$className must be instantiable"); + } + + return "Class.create('$package$className',{" + . self::_encodeConstants($cls) ."," + . self::_encodeMethods($cls) ."," + . self::_encodeVariables($cls) .'});'; + } + + + /** + * Encode several classes at once + * + * Returns JSON encoded classes, using {@link encodeClass()}. + * + * @param array $classNames + * @param string $package + * @return string + */ + public static function encodeClasses(array $classNames, $package = '') + { + $result = ''; + foreach ($classNames as $className) { + $result .= self::encodeClass($className, $package); + } + + return $result; + } + +} + diff --git a/include/Zend/Json/Exception.php b/include/Zend/Json/Exception.php new file mode 100644 index 0000000..cf0999e --- /dev/null +++ b/include/Zend/Json/Exception.php @@ -0,0 +1,36 @@ +p=0; + $this->line = 1; + $this->charPositionInLine = 0; + $this->markDepth = 0; + $this->markers = null; + $this->lastMarker=0; + $this->name=null; + + $this->data = strToIntArray($input); + $this->n = strlen($input); + } + + /** Reset the stream so that it's in the same state it was + * when the object was created *except* the data array is not + * touched. + */ + public function reset() { + $this->p = 0; + $this->line = 1; + $this->charPositionInLine = 0; + $this->markDepth = 0; + } + + public function consume() { + if ( $this->p < $this->n ) { + $this->charPositionInLine++; + if ( $this->data[$this->p]==ord("\n") ) { + $this->line++; + $this->charPositionInLine=0; + } + $this->p++; + } + } + + public function LA($i) { + if ( $i==0 ) { + return 0; // undefined + } + if ( $i<0 ) { + $i++; // e.g., translate LA(-1) to use offset i=0; then data[p+0-1] + if ( ($this->p+$i-1) < 0 ) { + return CharStreamConst::$EOF; // invalid; no char before first char + } + } + + if ( ($this->p+$i-1) >= $this->n ) { + //System.out.println("char LA("+i+")=EOF; p="+p); + return CharStreamConst::$EOF; + } + //System.out.println("char LA("+i+")="+(char)data[p+i-1]+"; p="+p); + //System.out.println("LA("+i+"); p="+p+" n="+n+" data.length="+data.length); + return $this->data[$this->p+$i-1]; + } + + public function LT($i) { + return $this->LA($i); + } + + /** Return the current input symbol index 0..n where n indicates the + * last symbol has been read. The index is the index of char to + * be returned from LA(1). + */ + public function index() { + return $this->p; + } + + public function size() { + return $this->n; + } + + public function mark() { + if ( $this->markers == null) { + $this->markers = array(); + $this->markers[] = null; // depth 0 means no backtracking, leave blank + } + $this->markDepth++; + $state = null; + if ($this->markDepth>=sizeof($this->markers)) { + $state = new CharStreamState(); + $this->markers[] = $state; + } + else { + $state = $this->markers[$this->markDepth]; + } + $state->p = $this->p; + $state->line = $this->line; + $state->charPositionInLine = $this->charPositionInLine; + $this->lastMarker = $this->markDepth; + return $this->markDepth; + } + + public function rewind($m=null) { + if($m===null){ + $this->rewind((int)$this->lastMarker); + }else{ + $state = $this->markers[$m]; + // restore stream state + $this->seek($state->p); + $this->line = $state->line; + $this->charPositionInLine = $state->charPositionInLine; + $this->release($m); + } + } + + public function release($marker) { + // unwind any other markers made after m and release m + $this->markDepth = $marker; + // release this marker + $this->markDepth--; + } + + /** consume() ahead until p==index; can't just set p=index as we must + * update line and charPositionInLine. + */ + public function seek($index) { + if ( $index<=$this->p ) { + $this->p = $index; // just jump; don't update stream state (line, ...) + return; + } + // seek forward, consume until p hits index + while ( $this->p<$index ) { + $this->consume(); + } + } + + public function substring($start, $stop) { + return implode(array_map('chr', array_slice($this->data, $start, $stop-$start+1))); + } + + public function getLine() { + return $this->line; + } + + public function getCharPositionInLine() { + return $this->charPositionInLine; + } + + public function setLine($line) { + $this->line = $line; + } + + public function setCharPositionInLine($pos) { + $this->charPositionInLine = $pos; + } + + public function getSourceName() { + return $this->name; + } + } + +?> \ No newline at end of file diff --git a/include/antlr/AntlrLexer.php b/include/antlr/AntlrLexer.php new file mode 100644 index 0000000..ac52e3a --- /dev/null +++ b/include/antlr/AntlrLexer.php @@ -0,0 +1,299 @@ +state = $state; + $this->input = $input; + } + + public function reset() { + parent::reset(); // reset all recognizer state variables + // wack Lexer state variables + if ( $this->input!=null ) { + $this->input->seek(0); // rewind the input + } + if ( $this->state==null ) { + return; // no shared state work to do + } + $this->state->token = null; + $this->state->type = TokenConst::$INVALID_TOKEN_TYPE; + $this->state->channel = TokenConst::$DEFAULT_CHANNEL; + $this->state->tokenStartCharIndex = -1; + $this->state->tokenStartCharPositionInLine = -1; + $this->state->tokenStartLine = -1; + $this->state->text = null; + } + + + /** Return a token from this source; i.e., match a token on the char + * stream. + */ + public function nextToken() { + while (true) { + $this->state->token = null; + $this->state->channel = 0;//Token::DEFAULT_CHANNEL; + $this->state->tokenStartCharIndex = $this->input->index(); + $this->state->tokenStartCharPositionInLine = $this->input->getCharPositionInLine(); + $this->state->tokenStartLine = $this->input->getLine(); + $this->state->text = null; + if ( $this->input->LA(1)==CharStreamConst::$EOF ) { + return TokenConst::$EOF_TOKEN; + } + try { + $this->mTokens(); + if ( $this->state->token==null ) { + $this->emit(); + } + else if ( $this->state->token==Token::$SKIP_TOKEN ) { + continue; + } + return $this->state->token; + } + catch (NoViableAltException $nva) { + $this->reportError($nva); + $this->recover($nva); // throw out current char and try again + } + catch (RecognitionException $re) { + $this->reportError($re); + // match() routine has already called recover() + } + } + } + + /** Instruct the lexer to skip creating a token for current lexer rule + * and look for another token. nextToken() knows to keep looking when + * a lexer rule finishes with token set to SKIP_TOKEN. Recall that + * if token==null at end of any token rule, it creates one for you + * and emits it. + */ + public function skip() { + $this->state->token = TokenConst::$SKIP_TOKEN; + } + + /** This is the lexer entry point that sets instance var 'token' */ + public abstract function mTokens(); + + /** Set the char stream and reset the lexer */ + public function setCharStream($input) { + $this->input = null; + $this->reset(); + $this->input = $input; + } + + public function getCharStream() { + return $this->input; + } + + public function getSourceName() { + return $this->input->getSourceName(); + } + + /** Currently does not support multiple emits per nextToken invocation + * for efficiency reasons. Subclass and override this method and + * nextToken (to push tokens into a list and pull from that list rather + * than a single variable as this implementation does). + */ + /** The standard method called to automatically emit a token at the + * outermost lexical rule. The token object should point into the + * char buffer start..stop. If there is a text override in 'text', + * use that to set the token's text. Override this method to emit + * custom Token objects. + * + * If you are building trees, then you should also override + * Parser or TreeParser.getMissingSymbol(). + */ + public function emit($token=null) { + if($token==null){ + $token = CommonToken::forInput($this->input, $this->state->type, $this->state->channel, + $this->state->tokenStartCharIndex, $this->getCharIndex()-1); + $token->setLine($this->state->tokenStartLine); + $token->setText($this->state->text); + $token->setCharPositionInLine($this->state->tokenStartCharPositionInLine); + } + $this->state->token = $token; + return $token; + } + + function matchString($s){ + $i = 0; + while ( $iinput->LA(1)!=charAt($s, $i) ) { + if ( $this->state->backtracking>0 ) { + $this->state->failed = true; + return; + } + $mte = new MismatchedTokenException(charAt($s, $i), $this->input); + $this->recover($mte); + throw $mte; + } + $i++; + $this->input->consume(); + $state->failed = false; + } + } + + public function matchAny() { + $this->input->consume(); + } + + public function matchChar($c) { + if ($this->input->LA(1)!=$c ) { + if ( $this->state->backtracking>0 ) { + $this->state->failed = true; + return; + } + $mte = new MismatchedTokenException($c, $this->input); + $this->recover($mte); // don't really recover; just consume in lexer + throw $mte; + } + $this->input->consume(); + $this->state->failed = false; + } + + public function matchRange($a, $b) { + if ( $this->input->LA(1)<$a || $this->input->LA(1)>$b ) { + if ( $this->state->backtracking>0 ) { + $this->state->failed = true; + return; + } + $mre = new MismatchedRangeException($a, $b, $this->input); + $this->recover($mre); + throw $mre; + } + $this->input->consume(); + $this->state->failed = false; + } + + public function getLine() { + return $this->input->getLine(); + } + + public function getCharPositionInLine() { + return $this->input->getCharPositionInLine(); + } + + /** What is the index of the current character of lookahead? */ + public function getCharIndex() { + return $this->input->index(); + } + + + /** Return the text matched so far for the current token or any + * text override. + */ + public function getText() { + if ( $this->state->text!=null ) { + return $this->state->text; + } + return $this->input->substring($this->state->tokenStartCharIndex,$this->getCharIndex()-1); + } + + /** Set the complete text of this token; it wipes any previous + * changes to the text. + */ + public function setText($text) { + $this->state->text = $text; + } + + public function reportError($e) { + /** TODO: not thought about recovery in lexer yet. + * + // if we've already reported an error and have not matched a token + // yet successfully, don't report any errors. + if ( errorRecovery ) { + //System.err.print("[SPURIOUS] "); + return; + } + errorRecovery = true; + */ + + $this->displayRecognitionError($this->getTokenNames(), $e); + } + + public function getErrorMessage($e, $tokenNames) { + $msg = null; + if ( $e instanceof MismatchedTokenException ) { + $mte = $e; + $msg = "mismatched character ".$this->getCharErrorDisplay($e->c). + " expecting ".$this->getCharErrorDisplay($mte->expecting); + } + else if ( $e instanceof NoViableAltException ) { + $nvae = $e; + // for development, can add "decision=<<"+nvae.grammarDecisionDescription+">>" + // and "(decision="+nvae.decisionNumber+") and + // "state "+nvae.stateNumber + $msg = "no viable alternative at character ".$this->getCharErrorDisplay($e->c); + } + else if ( $e instanceof EarlyExitException ) { + $eee = $e; + // for development, can add "(decision="+eee.decisionNumber+")" + $msg = "required (...)+ loop did not match anything at character ".$this->getCharErrorDisplay($e->c); + } + else if ( $e instanceof MismatchedNotSetException ) { + $mse = $e; + $msg = "mismatched character ".$this->getCharErrorDisplay($e->c)." expecting set ".$mse->expecting; + } + else if ( $e instanceof MismatchedSetException ) { + $mse = $e; + $msg = "mismatched character ".$this->getCharErrorDisplay($e->c)." expecting set ".$mse->expecting; + } + else if ( $e instanceof MismatchedRangeException ) { + $mre = $e; + $msg = "mismatched character ".$this->getCharErrorDisplay($e->c)." expecting set ". + $this->getCharErrorDisplay($mre->a)."..".$this->getCharErrorDisplay($mre->b); + } + else { + $msg = parent::getErrorMessage($e, $tokenNames); + } + return $msg; + } + + public function getCharErrorDisplay($c) { + $s = chr($c); + switch ( $s ) { + case '\n' : + $s = "\\n"; + break; + case '\t' : + $s = "\\t"; + break; + case '\r' : + $s = "\\r"; + break; + } + if ($c==TokenConst::$EOF){ + $s = ""; + } + return "'".$s."'"; + } + + /** Lexers can normally match any char in it's vocabulary after matching + * a token, so do the easy thing and just kill a character and hope + * it all works out. You can instead use the rule invocation stack + * to do sophisticated error recovery if you are in a fragment rule. + */ + public function recover($re) { + $this->input->consume(); + } + + + public function traceIn($ruleName, $ruleIndex) { + $inputSymbol = $this->input->LT(1)." line=".$this->getLine().":".$this->getCharPositionInLine(); + parent::traceIn($ruleName, $ruleIndex, $inputSymbol); + } + + public function traceOut($ruleName, $ruleIndex) { + $inputSymbol = $this->input->LT(1)." line=".$this->getLine().":".$this->getCharPositionInLine(); + parent::traceOut($ruleName, $ruleIndex, $inputSymbol); + } +} + +?> \ No newline at end of file diff --git a/include/antlr/AntlrParser.php b/include/antlr/AntlrParser.php new file mode 100644 index 0000000..d195e96 --- /dev/null +++ b/include/antlr/AntlrParser.php @@ -0,0 +1,100 @@ +setTokenStream($input); + } + + public function reset() { + parent::reset(); // reset all recognizer state variables + if ( $this->input!=null ) { + $this->input->seek(0); // rewind the input + } + } + + protected function getCurrentInputSymbol($input) { + return $this->input->LT(1); + } + + protected function getMissingSymbol($input, $e, $expectedTokenType, $follow) + { + $tokenText = null; + if ( $expectedTokenType==TokenConst::$EOF ){ + $tokenText = ""; + } else { + $tokenNames = $this->getTokenNames(); + $tokenText = ""; + } + $t = CommonToken::forTypeAndText($expectedTokenType, $tokenText); + $current = $input->LT(1); + if ( $current->getType() == TokenConst::$EOF ) { + $current = $this->input->LT(-1); + } + $t->line = $current->getLine(); + $t->charPositionInLine = $current->getCharPositionInLine(); + $t->channel = $DEFAULT_TOKEN_CHANNEL; + return $t; + } + + /** Set the token stream and reset the parser */ + public function setTokenStream($input) { + $this->input = null; + $this->reset(); + $this->input = $input; + } + + public function getTokenStream() { + return $this->input; + } + + public function getSourceName() { + return $this->input->getSourceName(); + } + + public function traceIn($ruleName, $ruleIndex) { + parent::traceIn($ruleName, $ruleIndex, $this->input->LT(1)); + } + + public function traceOut($ruleName, $ruleIndex) { + parent::traceOut($ruleName, $ruleIndex, $this->input->LT(1)); + } + + +} + +?> \ No newline at end of file diff --git a/include/antlr/BaseRecognizer.php b/include/antlr/BaseRecognizer.php new file mode 100644 index 0000000..45ea538 --- /dev/null +++ b/include/antlr/BaseRecognizer.php @@ -0,0 +1,851 @@ +state = $state; + } + + /** reset the parser's state; subclasses must rewinds the input stream */ + public function reset() { + // wack everything related to error recovery + if ( $this->state==null ) { + return; // no shared state work to do + } + $this->state->_fsp = -1; + $this->state->errorRecovery = false; + $this->state->lastErrorIndex = -1; + $this->state->failed = false; + $this->state->syntaxErrors = 0; + // wack everything related to backtracking and memoization + $this->state->backtracking = 0; + for ($i = 0; $this->state->ruleMemo!=null && $i < $this->state->ruleMemo->length; $i++) { // wipe cache + $this->state->ruleMemo[$i] = null; + } + } + + + /** Match current input symbol against ttype. Attempt + * single token insertion or deletion error recovery. If + * that fails, throw MismatchedTokenException. + * + * To turn off single token insertion or deletion error + * recovery, override mismatchRecover() and have it call + * plain mismatch(), which does not recover. Then any error + * in a rule will cause an exception and immediate exit from + * rule. Rule would recover by resynchronizing to the set of + * symbols that can follow rule ref. + */ + public function match($input, $ttype, $follow) + { + //System.out.println("match "+((TokenStream)input).LT(1)); + $matchedSymbol = $this->getCurrentInputSymbol($input); + if ( $input->LA(1)==$ttype ) { + $input->consume(); + $this->state->errorRecovery = false; + $this->state->failed = false; + return $matchedSymbol; + } + if ( $this->state->backtracking>0 ) { + $this->state->failed = true; + return $matchedSymbol; + } + $matchedSymbol = $this->recoverFromMismatchedToken($input, $ttype, $follow); + return $matchedSymbol; + } + + /** Match the wildcard: in a symbol */ + public function matchAny($input) { + $this->state->errorRecovery = false; + $this->state->failed = false; + $input->consume(); + } + + public function mismatchIsUnwantedToken($input, $ttype) { + return $input->LA(2)==$ttype; + } + + public function mismatchIsMissingToken($input, $follow) { + if ( $follow==null ) { + // we have no information about the follow; we can only consume + // a single token and hope for the best + return $false; + } + // compute what can follow this grammar element reference + if ( $follow->member(TokenConst::$EOR_TOKEN_TYPE) ) { + $viableTokensFollowingThisRule = $this->computeContextSensitiveRuleFOLLOW(); + $follow = $follow->union($viableTokensFollowingThisRule); + if ( $this->state->_fsp>=0 ) { // remove EOR if we're not the start symbol + $follow->remove(TokenConst::$EOR_TOKEN_TYPE); + } + } + // if current token is consistent with what could come after set + // then we know we're missing a token; error recovery is free to + // "insert" the missing token + + //System.out.println("viable tokens="+follow.toString(getTokenNames())); + //System.out.println("LT(1)="+((TokenStream)input).LT(1)); + + // BitSet cannot handle negative numbers like -1 (EOF) so I leave EOR + // in follow set to indicate that the fall of the start symbol is + // in the set (EOF can follow). + if ( $follow->member($input->LA(1)) || $follow->member(TokenConst::$EOR_TOKEN_TYPE) ) { + //System.out.println("LT(1)=="+((TokenStream)input).LT(1)+" is consistent with what follows; inserting..."); + return true; + } + return false; + } + + /** Factor out what to do upon token mismatch so tree parsers can behave + * differently. Override and call mismatchRecover(input, ttype, follow) + * to get single token insertion and deletion. Use this to turn of + * single token insertion and deletion. Override mismatchRecover + * to call this instead. + */ + protected function mismatch($input, $ttype, $follow) + { + if ( $this->mismatchIsUnwantedToken($input, $ttype) ) { + throw new UnwantedTokenException($ttype, $input); + } + else if ( $this->mismatchIsMissingToken($input, $follow) ) { + throw new MissingTokenException($ttype, $input, null); + } + throw new MismatchedTokenException($ttype, $input); + } + + /** Report a recognition problem. + * + * This method sets errorRecovery to indicate the parser is recovering + * not parsing. Once in recovery mode, no errors are generated. + * To get out of recovery mode, the parser must successfully match + * a token (after a resync). So it will go: + * + * 1. error occurs + * 2. enter recovery mode, report error + * 3. consume until token found in resynch set + * 4. try to resume parsing + * 5. next match() will reset errorRecovery mode + * + * If you override, make sure to update syntaxErrors if you care about that. + */ + public function reportError($e) { + // if we've already reported an error and have not matched a token + // yet successfully, don't report any errors. + if ( $this->state->errorRecovery ) { + //System.err.print("[SPURIOUS] "); + return; + } + $this->state->syntaxErrors++; // don't count spurious + $this->state->errorRecovery = true; + + $this->displayRecognitionError($this->getTokenNames(), $e); + } + + + public function displayRecognitionError($tokenNames, $e){ + $hdr = $this->getErrorHeader($e); + $msg = $this->getErrorMessage($e, $tokenNames); + $this->emitErrorMessage($hdr." ".$msg); + } + + /** What error message should be generated for the various + * exception types? + * + * Not very object-oriented code, but I like having all error message + * generation within one method rather than spread among all of the + * exception classes. This also makes it much easier for the exception + * handling because the exception classes do not have to have pointers back + * to this object to access utility routines and so on. Also, changing + * the message for an exception type would be difficult because you + * would have to subclassing exception, but then somehow get ANTLR + * to make those kinds of exception objects instead of the default. + * This looks weird, but trust me--it makes the most sense in terms + * of flexibility. + * + * For grammar debugging, you will want to override this to add + * more information such as the stack frame with + * getRuleInvocationStack(e, this.getClass().getName()) and, + * for no viable alts, the decision description and state etc... + * + * Override this to change the message generated for one or more + * exception types. + */ + public function getErrorMessage($e, $tokenNames) { + $msg = $e->getMessage(); + if ( $e instanceof UnwantedTokenException ) { + $ute = $e; + $tokenName=""; + if ( $ute->expecting== TokenConst::$EOF ) { + $tokenName = "EOF"; + } + else { + $tokenName = $tokenNames[$ute->expecting]; + } + $msg = "extraneous input ".$this->getTokenErrorDisplay($ute->getUnexpectedToken()). + " expecting ".$tokenName; + } + else if ( $e instanceof MissingTokenException ) { + $mte = $e; + $tokenName=""; + if ( $mte->expecting== TokenConst::$EOF ) { + $tokenName = "EOF"; + } + else { + $tokenName = $tokenNames[$mte->expecting]; + } + $msg = "missing ".$tokenName." at ".$this->getTokenErrorDisplay($e->token); + } + else if ( $e instanceof MismatchedTokenException ) { + $mte = $e; + $tokenName=""; + if ( $mte->expecting== TokenConst::$EOF ) { + $tokenName = "EOF"; + } + else { + $tokenName = $tokenNames[$mte->expecting]; + } + $msg = "mismatched input ".$this->getTokenErrorDisplay($e->token). + " expecting ".$tokenName; + } + else if ( $e instanceof MismatchedTreeNodeException ) { + $mtne = $e; + $tokenName=""; + if ( $mtne->expecting==TokenConst::$EOF ) { + $tokenName = "EOF"; + } + else { + $tokenName = $tokenNames[$mtne->expecting]; + } + $msg = "mismatched tree node: ".$mtne->node. + " expecting ".$tokenName; + } + else if ( $e instanceof NoViableAltException ) { + $nvae = $e; + // for development, can add "decision=<<"+nvae.grammarDecisionDescription+">>" + // and "(decision="+nvae.decisionNumber+") and + // "state "+nvae.stateNumber + $msg = "no viable alternative at input ".$this->getTokenErrorDisplay($e->token); + } + else if ( $e instanceof EarlyExitException ) { + $eee = $e; + // for development, can add "(decision="+eee.decisionNumber+")" + $msg = "required (...)+ loop did not match anything at input ". + getTokenErrorDisplay($e->token); + } + else if ( $e instanceof MismatchedSetException ) { + $mse = $e; + $msg = "mismatched input ".$this->getTokenErrorDisplay($e->token). + " expecting set ".$mse->expecting; + } + else if ( $e instanceof MismatchedNotSetException ) { + $mse = $e; + $msg = "mismatched input ".$this->getTokenErrorDisplay($e->token). + " expecting set ".$mse->expecting; + } + else if ( $e instanceof FailedPredicateException ) { + $fpe = $e; + $msg = "rule ".$fpe->ruleName." failed predicate: {". + $fpe->predicateText."}?"; + } + return $msg; + } + + /** Get number of recognition errors (lexer, parser, tree parser). Each + * recognizer tracks its own number. So parser and lexer each have + * separate count. Does not count the spurious errors found between + * an error and next valid token match + * + * See also reportError() + */ + public function getNumberOfSyntaxErrors() { + return $state->syntaxErrors; + } + + /** What is the error header, normally line/character position information? */ + public function getErrorHeader($e) { + return "line ".$e->line.":".$e->charPositionInLine; + } + + + /** How should a token be displayed in an error message? The default + * is to display just the text, but during development you might + * want to have a lot of information spit out. Override in that case + * to use t.toString() (which, for CommonToken, dumps everything about + * the token). This is better than forcing you to override a method in + * your token objects because you don't have to go modify your lexer + * so that it creates a new Java type. + */ + public function getTokenErrorDisplay($t) { + $s = $t->getText(); + if ( $s==null ) { + if ( $t->getType()==TokenConst::$EOF ) { + $s = ""; + } + else { + $s = "<".$t->getType().">"; + } + } + $s = str_replace("\n", '\n', $s); + $s = str_replace("\r",'\r', $s); + $s = str_replace("\t",'\t', $s); + return "'".$s."'"; + } + + /** Override this method to change where error messages go */ + public function emitErrorMessage($msg) { + echo $msg; + } + + /** Recover from an error found on the input stream. This is + * for NoViableAlt and mismatched symbol exceptions. If you enable + * single token insertion and deletion, this will usually not + * handle mismatched symbol exceptions but there could be a mismatched + * token that the match() routine could not recover from. + */ + public function recover($input, $re) { + if ( $this->state->lastErrorIndex==$input->index() ) { + // uh oh, another error at same token index; must be a case + // where LT(1) is in the recovery token set so nothing is + // consumed; consume a single token so at least to prevent + // an infinite loop; this is a failsafe. + $input->consume(); + } + $this->state->lastErrorIndex = $input->index(); + $followSet = $this->computeErrorRecoverySet(); + $this->beginResync(); + $this->consumeUntilInSet($input, $followSet); + $this->endResync(); + } + + /** A hook to listen in on the token consumption during error recovery. + * The DebugParser subclasses this to fire events to the listenter. + */ + public function beginResync() { + } + + public function endResync() { + } + + /* Compute the error recovery set for the current rule. During + * rule invocation, the parser pushes the set of tokens that can + * follow that rule reference on the stack; this amounts to + * computing FIRST of what follows the rule reference in the + * enclosing rule. This local follow set only includes tokens + * from within the rule; i.e., the FIRST computation done by + * ANTLR stops at the end of a rule. + * + * EXAMPLE + * + * When you find a "no viable alt exception", the input is not + * consistent with any of the alternatives for rule r. The best + * thing to do is to consume tokens until you see something that + * can legally follow a call to r *or* any rule that called r. + * You don't want the exact set of viable next tokens because the + * input might just be missing a token--you might consume the + * rest of the input looking for one of the missing tokens. + * + * Consider grammar: + * + * a : '[' b ']' + * | '(' b ')' + * ; + * b : c '^' INT ; + * c : ID + * | INT + * ; + * + * At each rule invocation, the set of tokens that could follow + * that rule is pushed on a stack. Here are the various "local" + * follow sets: + * + * FOLLOW(b1_in_a) = FIRST(']') = ']' + * FOLLOW(b2_in_a) = FIRST(')') = ')' + * FOLLOW(c_in_b) = FIRST('^') = '^' + * + * Upon erroneous input "[]", the call chain is + * + * a -> b -> c + * + * and, hence, the follow context stack is: + * + * depth local follow set after call to rule + * 0 a (from main()) + * 1 ']' b + * 3 '^' c + * + * Notice that ')' is not included, because b would have to have + * been called from a different context in rule a for ')' to be + * included. + * + * For error recovery, we cannot consider FOLLOW(c) + * (context-sensitive or otherwise). We need the combined set of + * all context-sensitive FOLLOW sets--the set of all tokens that + * could follow any reference in the call chain. We need to + * resync to one of those tokens. Note that FOLLOW(c)='^' and if + * we resync'd to that token, we'd consume until EOF. We need to + * sync to context-sensitive FOLLOWs for a, b, and c: {']','^'}. + * In this case, for input "[]", LA(1) is in this set so we would + * not consume anything and after printing an error rule c would + * return normally. It would not find the required '^' though. + * At this point, it gets a mismatched token error and throws an + * exception (since LA(1) is not in the viable following token + * set). The rule exception handler tries to recover, but finds + * the same recovery set and doesn't consume anything. Rule b + * exits normally returning to rule a. Now it finds the ']' (and + * with the successful match exits errorRecovery mode). + * + * So, you cna see that the parser walks up call chain looking + * for the token that was a member of the recovery set. + * + * Errors are not generated in errorRecovery mode. + * + * ANTLR's error recovery mechanism is based upon original ideas: + * + * "Algorithms + Data Structures = Programs" by Niklaus Wirth + * + * and + * + * "A note on error recovery in recursive descent parsers": + * http://portal.acm.org/citation.cfm?id=947902.947905 + * + * Later, Josef Grosch had some good ideas: + * + * "Efficient and Comfortable Error Recovery in Recursive Descent + * Parsers": + * ftp://www.cocolab.com/products/cocktail/doca4.ps/ell.ps.zip + * + * Like Grosch I implemented local FOLLOW sets that are combined + * at run-time upon error to avoid overhead during parsing. + */ + protected function computeErrorRecoverySet() { + return $this->combineFollows(false); + } + + /** Compute the context-sensitive FOLLOW set for current rule. + * This is set of token types that can follow a specific rule + * reference given a specific call chain. You get the set of + * viable tokens that can possibly come next (lookahead depth 1) + * given the current call chain. Contrast this with the + * definition of plain FOLLOW for rule r: + * + * FOLLOW(r)={x | S=>*alpha r beta in G and x in FIRST(beta)} + * + * where x in T* and alpha, beta in V*; T is set of terminals and + * V is the set of terminals and nonterminals. In other words, + * FOLLOW(r) is the set of all tokens that can possibly follow + * references to r in *any* sentential form (context). At + * runtime, however, we know precisely which context applies as + * we have the call chain. We may compute the exact (rather + * than covering superset) set of following tokens. + * + * For example, consider grammar: + * + * stat : ID '=' expr ';' // FOLLOW(stat)=={EOF} + * | "return" expr '.' + * ; + * expr : atom ('+' atom)* ; // FOLLOW(expr)=={';','.',')'} + * atom : INT // FOLLOW(atom)=={'+',')',';','.'} + * | '(' expr ')' + * ; + * + * The FOLLOW sets are all inclusive whereas context-sensitive + * FOLLOW sets are precisely what could follow a rule reference. + * For input input "i=(3);", here is the derivation: + * + * stat => ID '=' expr ';' + * => ID '=' atom ('+' atom)* ';' + * => ID '=' '(' expr ')' ('+' atom)* ';' + * => ID '=' '(' atom ')' ('+' atom)* ';' + * => ID '=' '(' INT ')' ('+' atom)* ';' + * => ID '=' '(' INT ')' ';' + * + * At the "3" token, you'd have a call chain of + * + * stat -> expr -> atom -> expr -> atom + * + * What can follow that specific nested ref to atom? Exactly ')' + * as you can see by looking at the derivation of this specific + * input. Contrast this with the FOLLOW(atom)={'+',')',';','.'}. + * + * You want the exact viable token set when recovering from a + * token mismatch. Upon token mismatch, if LA(1) is member of + * the viable next token set, then you know there is most likely + * a missing token in the input stream. "Insert" one by just not + * throwing an exception. + */ + protected function computeContextSensitiveRuleFOLLOW() { + return $this->combineFollows(true); + } + + protected function combineFollows($exact) { + $top = $this->state->_fsp; + $followSet = new Set(array()); + for ($i=$top; $i>=0; $i--) { + $localFollowSet = $this->state->following[$i]; + /* + System.out.println("local follow depth "+i+"="+ + localFollowSet.toString(getTokenNames())+")"); + */ + $followSet->unionInPlace($localFollowSet); + if ( $this->exact ) { + // can we see end of rule? + if ( $localFollowSet->member(TokenConst::$EOR_TOKEN_TYPE) ) { + // Only leave EOR in set if at top (start rule); this lets + // us know if have to include follow(start rule); i.e., EOF + if ( $i>0 ) { + $followSet->remove(TokenConst::$EOR_TOKEN_TYPE); + } + } + else { // can't see end of rule, quit + break; + } + } + } + return $followSet; + } + + /** Attempt to recover from a single missing or extra token. + * + * EXTRA TOKEN + * + * LA(1) is not what we are looking for. If LA(2) has the right token, + * however, then assume LA(1) is some extra spurious token. Delete it + * and LA(2) as if we were doing a normal match(), which advances the + * input. + * + * MISSING TOKEN + * + * If current token is consistent with what could come after + * ttype then it is ok to "insert" the missing token, else throw + * exception For example, Input "i=(3;" is clearly missing the + * ')'. When the parser returns from the nested call to expr, it + * will have call chain: + * + * stat -> expr -> atom + * + * and it will be trying to match the ')' at this point in the + * derivation: + * + * => ID '=' '(' INT ')' ('+' atom)* ';' + * ^ + * match() will see that ';' doesn't match ')' and report a + * mismatched token error. To recover, it sees that LA(1)==';' + * is in the set of tokens that can follow the ')' token + * reference in rule atom. It can assume that you forgot the ')'. + */ + protected function recoverFromMismatchedToken($input, $ttype, $follow) + { + $e = null; + // if next token is what we are looking for then "delete" this token + + if ( $this->mismatchIsUnwantedToken($input, $ttype) ) { + $e = new UnwantedTokenException($ttype, $input); + /* + System.err.println("recoverFromMismatchedToken deleting "+ + ((TokenStream)input).LT(1)+ + " since "+((TokenStream)input).LT(2)+" is what we want"); + */ + $this->beginResync(); + $input->consume(); // simply delete extra token + $this->endResync(); + $this->reportError($e); // report after consuming so AW sees the token in the exception + // we want to return the token we're actually matching + $matchedSymbol = $this->getCurrentInputSymbol($input); + $input->consume(); // move past ttype token as if all were ok + return $matchedSymbol; + } + // can't recover with single token deletion, try insertion + if ( $this->mismatchIsMissingToken($input, $follow) ) { + $inserted = $this->getMissingSymbol($input, $e, $ttype, $follow); + $e = new MissingTokenException($ttype, $input, $inserted); + $this->reportError($e); // report after inserting so AW sees the token in the exception + return $inserted; + } + // even that didn't work; must throw the exception + $e = new MismatchedTokenException($ttype, $input); + throw $e; + } + + /** Not currently used */ + public function recoverFromMismatchedSet($input, $e, $follow) { + if ( $this->mismatchIsMissingToken($input, $follow) ) { + // System.out.println("missing token"); + reportError($e); + // we don't know how to conjure up a token for sets yet + return $this->getMissingSymbol($input, $e, TokenConst::$INVALID_TOKEN_TYPE, $follow); + } + // TODO do single token deletion like above for Token mismatch + throw $e; + } + + /** Match needs to return the current input symbol, which gets put + * into the label for the associated token ref; e.g., x=ID. Token + * and tree parsers need to return different objects. Rather than test + * for input stream type or change the IntStream interface, I use + * a simple method to ask the recognizer to tell me what the current + * input symbol is. + * + * This is ignored for lexers. + */ + protected function getCurrentInputSymbol($input) { return null; } + + /** Conjure up a missing token during error recovery. + * + * The recognizer attempts to recover from single missing + * symbols. But, actions might refer to that missing symbol. + * For example, x=ID {f($x);}. The action clearly assumes + * that there has been an identifier matched previously and that + * $x points at that token. If that token is missing, but + * the next token in the stream is what we want we assume that + * this token is missing and we keep going. Because we + * have to return some token to replace the missing token, + * we have to conjure one up. This method gives the user control + * over the tokens returned for missing tokens. Mostly, + * you will want to create something special for identifier + * tokens. For literals such as '{' and ',', the default + * action in the parser or tree parser works. It simply creates + * a CommonToken of the appropriate type. The text will be the token. + * If you change what tokens must be created by the lexer, + * override this method to create the appropriate tokens. + */ + protected function getMissingSymbol($input, $e, $expectedTokenType, $follow) { + return null; + } + + public function consumeUntilMatchesType($input, $tokenType) { + //System.out.println("consumeUntil "+tokenType); + $ttype = $input->LA(1); + while ($ttype != TokenConst::$EOF && $ttype != $tokenType) { + $input->consume(); + $ttype = $input->LA(1); + } + } + + /** Consume tokens until one matches the given token set */ + public function consumeUntilInSet($input, $set) { + //System.out.println("consumeUntil("+set.toString(getTokenNames())+")"); + $ttype = $input->LA(1); + while ($ttype != TokenConst::$EOF && !$set->member($ttype) ) { + //System.out.println("consume during recover LA(1)="+getTokenNames()[input.LA(1)]); + $input->consume(); + $ttype = $input->LA(1); + } + } + + /** Push a rule's follow set using our own hardcoded stack */ + protected function pushFollow($fset) { + // if ( ($this->state->_fsp +1)>=sizeof($this->state->following) ) { + // $f = array(); + // System.arraycopy(state.following, 0, f, 0, state.following.length-1); + // $this->state->following = f; + // } + $this->state->following[++$this->state->_fsp] = $fset; + } + + /** Return List of the rules in your parser instance + * leading up to a call to this method. You could override if + * you want more details such as the file/line info of where + * in the parser java code a rule is invoked. + * + * This is very useful for error messages and for context-sensitive + * error recovery. + */ + + /** A more general version of getRuleInvocationStack where you can + * pass in, for example, a RecognitionException to get it's rule + * stack trace. This routine is shared with all recognizers, hence, + * static. + * + * TODO: move to a utility class or something; weird having lexer call this + */ + public static function getRuleInvocationStack($e=null, + $recognizerClassName=null) + { + if($e==null){ + $e = new Exception(); + } + if($recognizerClassName==null){ + $recognizerClassName = get_class($this); + } + throw new Exception("Not implemented yet"); + // List rules = new ArrayList(); + // StackTraceElement[] stack = e.getStackTrace(); + // int i = 0; + // for (i=stack.length-1; i>=0; i--) { + // StackTraceElement t = stack[i]; + // if ( t.getClassName().startsWith("org.antlr.runtime.") ) { + // continue; // skip support code such as this method + // } + // if ( t.getMethodName().equals(NEXT_TOKEN_RULE_NAME) ) { + // continue; + // } + // if ( !t.getClassName().equals(recognizerClassName) ) { + // continue; // must not be part of this parser + // } + // rules.add(t.getMethodName()); + // } + // return rules; + } + + public function getBacktrackingLevel() { + return $this->state->backtracking; + } + + /** Used to print out token names like ID during debugging and + * error reporting. The generated parsers implement a method + * that overrides this to point to their String[] tokenNames. + */ + public function getTokenNames() { + return null; + } + + /** For debugging and other purposes, might want the grammar name. + * Have ANTLR generate an implementation for this method. + */ + public function getGrammarFileName() { + return null; + } + + public abstract function getSourceName(); + + /** A convenience method for use most often with template rewrites. + * Convert a List to List + */ + public function toStrings($tokens) { + if ( $tokens==null ) return null; + $strings = array(); + for ($i=0; $i<$tokens->size(); $i++) { + $strings[] = $tokens[$i]->getText(); + } + return $strings; + } + + /** Given a rule number and a start token index number, return + * MEMO_RULE_UNKNOWN if the rule has not parsed input starting from + * start index. If this rule has parsed input starting from the + * start index before, then return where the rule stopped parsing. + * It returns the index of the last token matched by the rule. + * + * For now we use a hashtable and just the slow Object-based one. + * Later, we can make a special one for ints and also one that + * tosses out data after we commit past input position i. + */ + public function getRuleMemoization($ruleIndex, $ruleStartIndex) { + if ( $this->state->ruleMemo[$ruleIndex]==null ) { + $this->state->ruleMemo[$ruleIndex] = array(); + } + $stopIndexI = + $this->state->ruleMemo[$ruleIndex][$ruleStartIndex]; + if ( $stopIndexI==null ) { + return self::$MEMO_RULE_UNKNOWN; + } + return $stopIndexI; + } + + /** Has this rule already parsed input at the current index in the + * input stream? Return the stop token index or MEMO_RULE_UNKNOWN. + * If we attempted but failed to parse properly before, return + * MEMO_RULE_FAILED. + * + * This method has a side-effect: if we have seen this input for + * this rule and successfully parsed before, then seek ahead to + * 1 past the stop token matched for this rule last time. + */ + public function alreadyParsedRule($input, $ruleIndex) { + $stopIndex = $this->getRuleMemoization($ruleIndex, $input->index()); + if ( $stopIndex==self::$MEMO_RULE_UNKNOWN ) { + return false; + } + if ( $stopIndex==self::$MEMO_RULE_FAILED ) { + //System.out.println("rule "+ruleIndex+" will never succeed"); + $this->state->failed=true; + } + else { + //System.out.println("seen rule "+ruleIndex+" before; skipping ahead to @"+(stopIndex+1)+" failed="+state.failed); + $input->seek($stopIndex+1); // jump to one past stop token + } + return true; + } + + /** Record whether or not this rule parsed the input at this position + * successfully. Use a standard java hashtable for now. + */ + public function memoize($input, $ruleIndex, $ruleStartIndex){ + $stopTokenIndex = $this->state->failed?self::$MEMO_RULE_FAILED:$input->index()-1; + if ( $this->state->ruleMemo==null ) { + echo("!!!!!!!!! memo array is null for ". getGrammarFileName()); + } + if ( $ruleIndex >= sizeof($this->state->ruleMemo) ) { + echo("!!!!!!!!! memo size is ".sizeof($this->state->ruleMemo).", but rule index is ".$ruleIndex); + } + if ( $this->state->ruleMemo[$ruleIndex]!=null ) { + $this->state->ruleMemo[$ruleIndex][$ruleStartIndex] = $stopTokenIndex; + } + } + + /** return how many rule/input-index pairs there are in total. + * TODO: this includes synpreds. :( + */ + public function getRuleMemoizationCacheSize() { + $n = 0; + for ($i = 0; $this->state->ruleMemo!=null && $i < sizeof($this->state->ruleMemo); $i++) { + $ruleMap = $this->state->ruleMemo[$i]; + if ( $ruleMap!=null ) { + $n += sizeof($ruleMap); // how many input indexes are recorded? + } + } + return $n; + } + + public function traceIn($ruleName, $ruleIndex, $inputSymbol) { + echo("enter ".$ruleName." ".$inputSymbol); + if ( $this->state->failed ) { + echo(" failed=".$this->state->failed); + } + if ( $this->state->backtracking>0 ) { + echo(" backtracking=".$this->state->backtracking); + } + echo "\n"; + } + + public function traceOut($ruleName, $ruleIndex, $inputSymbol) { + echo("exit ".$ruleName." ".$inputSymbol); + if ( $this->state->failed ) { + echo(" failed=".$this->state->failed); + } + if ( $this->state->backtracking>0 ) { + echo(" backtracking="+$this->state->backtracking); + } + echo "\n"; + } + + public function getToken($name){ + if(preg_match("/\d+/", $name)){ + return (integer)$name; + }else{ + return $this->$name; + } + } + + public function getTokenName($tokenId){ + + } + +} + +BaseRecognizer::$DEFAULT_TOKEN_CHANNEL = TokenConst::$DEFAULT_CHANNEL; +BaseRecognizer::$HIDDEN = TokenConst::$HIDDEN_CHANNEL; +?> \ No newline at end of file diff --git a/include/antlr/CharStream.php b/include/antlr/CharStream.php new file mode 100644 index 0000000..70293eb --- /dev/null +++ b/include/antlr/CharStream.php @@ -0,0 +1,65 @@ + \ No newline at end of file diff --git a/include/antlr/CharStreamState.php b/include/antlr/CharStreamState.php new file mode 100644 index 0000000..55ec73f --- /dev/null +++ b/include/antlr/CharStreamState.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/include/antlr/CommonToken.php b/include/antlr/CommonToken.php new file mode 100644 index 0000000..49e1d14 --- /dev/null +++ b/include/antlr/CommonToken.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/include/antlr/CommonTokenStream.php b/include/antlr/CommonTokenStream.php new file mode 100644 index 0000000..182241d --- /dev/null +++ b/include/antlr/CommonTokenStream.php @@ -0,0 +1,380 @@ + to override some Tokens' channel numbers */ + protected $channelOverrideMap; + + /** Set; discard any tokens with this type */ + protected $discardSet; + + /** Skip tokens on any channel but this one; this is how we skip whitespace... */ + protected $channel; + + /** By default, track all incoming tokens */ + protected $discardOffChannelTokens = false; + + /** Track the last mark() call result value for use in rewind(). */ + protected $lastMarker = 0; + + /** The index into the tokens list of the current token (next token + * to consume). p==-1 indicates that the tokens list is empty + */ + protected $p = -1; + + public function __construct($tokenSource, $channel=null) { + $this->channel = TokenConst::$DEFAULT_CHANNEL; + $this->tokens = array(); + $this->tokenSource = $tokenSource; + if($channel != null){ + $this->channel = $channel; + } + } + + /** Reset this token stream by setting its token source. */ + public function setTokenSource($tokenSource) { + $this->tokenSource = $tokenSource; + $this->tokens = array(); + $this->p = -1; + $this->channel = TokenConst::$DEFAULT_CHANNEL; + } + + /** Load all tokens from the token source and put in tokens. + * This is done upon first LT request because you might want to + * set some token type / channel overrides before filling buffer. + */ + protected function fillBuffer() { + $index = 0; + $t = $this->tokenSource->nextToken(); + while ( $t!=null && $t->getType()!=CharStreamConst::$EOF ) { + $discard = false; + // is there a channel override for token type? + if ( $this->channelOverrideMap!=null ) { + $channelI = $this->channelOverrideMap[$t->getType()]; + if ( $channelI!=null ) { + $t->setChannel($channelI); + } + } + if ( $this->discardSet!=null && + $this->discardSet->contains($t->getType())) + { + $discard = true; + } + else if ( $this->discardOffChannelTokens && $t->getChannel()!=$this->channel ) { + $discard = true; + } + if ( !$discard ) { + $t->setTokenIndex($index); + $this->tokens[] = $t; + $index++; + } + $t = $this->tokenSource->nextToken(); + } + // leave p pointing at first token on channel + $this->p = 0; + $this->p = $this->skipOffTokenChannels($this->p); + } + + /** Move the input pointer to the next incoming token. The stream + * must become active with LT(1) available. consume() simply + * moves the input pointer so that LT(1) points at the next + * input symbol. Consume at least one token. + * + * Walk past any token not on the channel the parser is listening to. + */ + public function consume() { + if ( $this->ptokens)) { + $this->p++; + $this->p = $this->skipOffTokenChannels($this->p); // leave p on valid token + } + } + + /** Given a starting index, return the index of the first on-channel + * token. + */ + protected function skipOffTokenChannels($i) { + $n = sizeof($this->tokens); + while ( $i<$n && $this->tokens[$i]->getChannel()!=$this->channel ) { + $i++; + } + return $i; + } + + protected function skipOffTokenChannelsReverse($i) { + while ( $i>=0 && $this->tokens[$i]->getChannel()!=$this->channel) { + $i--; + } + return $i; + } + + /** A simple filter mechanism whereby you can tell this token stream + * to force all tokens of type ttype to be on channel. For example, + * when interpreting, we cannot exec actions so we need to tell + * the stream to force all WS and NEWLINE to be a different, ignored + * channel. + */ + public function setTokenTypeChannel($ttype, $channel) { + if ( $this->channelOverrideMap==null ) { + $this->channelOverrideMap = array(); + } + $this->channelOverrideMap[$ttype] = $channel; + } + + public function discardTokenType($ttype) { + if ( $this->discardSet==null ) { + $this->discardSet = new Set(); + } + $this->discardSet.add($ttype); + } + + public function discardOffChannelTokens($discardOffChannelTokens) { + $this->discardOffChannelTokens = $discardOffChannelTokens; + } + + public function getTokens() { + if ( $this->p == -1 ) { + $this->fillBuffer(); + } + return $this->tokens; + } + + public function getTokensBetween($start, $stop) { + return $this->getTokens($start, $stop, null); + } + + /** Given a start and stop index, return a List of all tokens in + * the token type BitSet. Return null if no tokens were found. This + * method looks at both on and off channel tokens. + */ + public function getTokensOfTypeInSet($start, $stop, $types) { + if ( $p == -1 ) { + fillBuffer(); + } + if ( $stop>=sizeof($this->tokens)) { + $stop=sizeof($this->tokens) - 1; + } + if ( $start<0 ) { + $start=0; + } + if ( $start>$stop ) { + return null; + } + + // list = tokens[start:stop]:{Token t, t.getType() in types} + $filteredTokens = array(); + for ($i=$start; $i<=$stop; $i++) { + $t = $this->tokens[$i]; + if ( $types==null || $types->member($t->getType())) { + $filteredTokens->add($t); + } + } + if ( sizeof($filteredTokens)==0 ) { + $filteredTokens = null; + } + return $filteredTokens; + } + + public function getTokensOfTypeInArray($start, $stop, $types) { + return $this->getTokens($start, $stop,new Set(types)); + } + + public function getTokensofType($start, $stop, $ttype) { + return $this->getTokens($start, $stop, new Set(array(ttype))); + } + + /** Get the ith token from the current position 1..n where k=1 is the + * first symbol of lookahead. + */ + public function LT($k) { + if ( $this->p == -1 ) { + $this->fillBuffer(); + } + if ( $k==0 ) { + return null; + } + if ( $k<0 ) { + return $this->LB(-$k); + } + //System.out.print("LT(p="+p+","+k+")="); + if ( ($this->p+$k-1) >= sizeof($this->tokens)) { + return TokenConst::$EOF_TOKEN; + } + //System.out.println(tokens.get(p+k-1)); + $i = $this->p; + $n = 1; + // find k good tokens + while ( $n<$k ) { + // skip off-channel tokens + $i = $this->skipOffTokenChannels($i+1); // leave p on valid token + $n++; + } + if ( $i>=sizeof($this->tokens)) { + return TokenConst::$EOF_TOKEN; + } + return $this->tokens[$i]; + } + + /** Look backwards k tokens on-channel tokens */ + protected function LB($k) { + //System.out.print("LB(p="+p+","+k+") "); + if ( $this->p == -1 ) { + $this->fillBuffer(); + } + if ( $k==0 ) { + return null; + } + if ( ($this->p-$k)<0 ) { + return null; + } + + + $i = $this->p; + $n = 1; + // find k good tokens looking backwards + while ( $n<=$k ) { + // skip off-channel tokens + $i = $this->skipOffTokenChannelsReverse($i-1); // leave p on valid token + $n++; + } + if ( $i<0 ) { + return null; + } + return $this->tokens[$i]; + } + + /** Return absolute token i; ignore which channel the tokens are on; + * that is, count all tokens not just on-channel tokens. + */ + public function get($i) { + return $this->tokens[$i]; + } + + public function LA($i) { + $lt = $this->LT($i); + return $this->LT($i)->getType(); + } + + public function mark() { + if ( $this->p == -1 ) { + $this->fillBuffer(); + } + $this->lastMarker = $this->index(); + return $this->lastMarker; + } + + public function release($marker) { + // no resources to release + } + + public function size() { + return sizeof($this->tokens); + } + + public function index() { + return $this->p; + } + + public function rewind($marker = null) { + if($marker===null){ + $marker = $this->lastmarker; + } + $this->seek($marker); + } + + + public function reset() { + $this->p = 0; + $this->lastMarker = 0; + } + + public function seek($index) { + $this->p = $index; + } + + public function getTokenSource() { + return $this->tokenSource; + } + + public function getSourceName() { + return $this->getTokenSource()->getSourceName(); + } + + public function toString() { + if ( $this->p == -1 ) { + $this->fillBuffer(); + } + return $this->toStringBetween(0, sizeof($this->tokens)-1); + } + + public function toStringBetween($start, $stop) { + if ( $start<0 || $stop<0 ) { + return null; + } + if ( $this->p == -1 ) { + $this->fillBuffer(); + } + if ( $stop>=sizeof($this->tokens)) { + $stop = sizeof($this->tokens)-1; + } + $buf = ""; + for ($i = $start; $i <= $stop; $i++) { + $t = $this->tokens[$i]; + $buf.=$t->getText(); + } + return $buf; + } + + public function toStringBetweenTokens($start, $stop) { + if ( $start!=null && $stop!=null ) { + return toString($this->start->getTokenIndex(), $this->stop->getTokenIndex()); + } + return null; + } + + public function __toString(){ + return $this->toString(); + } +} + + +?> \ No newline at end of file diff --git a/include/antlr/DFA.php b/include/antlr/DFA.php new file mode 100644 index 0000000..a9e21a2 --- /dev/null +++ b/include/antlr/DFA.php @@ -0,0 +1,215 @@ +debug ) { + echo ("Enter DFA.predict for decision ".$this->decisionNumber); + } + $mark = $input->mark(); // remember where decision started in input + try { + $ret = $this->_predict($input); + + } + catch(Exception $e) { + $input->rewind($mark); + throw $e; + } + $input->rewind($mark); + return $ret; + } + + public function _predict($input) { + $s = 0; // we always start at s0 + while ( true ) { + if ( $this->debug ) echo ("DFA ".$this->decisionNumber." state ".$s." LA(1)=".$input->LA(1)."(".$input->LA(1)."), index=".$input->index()); + $specialState = $this->special[$s]; + if ( $specialState>=0 ) { + if ( $this->debug ) { + echo ("DFA ".$this->decisionNumber. + " state ".$s." is special state ".$specialState); + } + $s = $this->specialStateTransition($specialState, $input); + if ( $this->debug ) { + echo ("DFA ".$this->decisionNumber. + " returns from special state ".$specialState." to ".s); + } + if ( $s==-1 ) { + $this->noViableAlt($s, $input); + return 0; + } + $input->consume(); + continue; + } + + if ( $this->accept[$s] >= 1 ) { + if ( $this->debug ) echo ("accept; predict "+$this->accept[$s]+" from state "+$this->s); + return $this->accept[$s]; + } + // look for a normal char transition + $c = $input->LA(1); // -1 == \uFFFF, all tokens fit in 65000 space + if ($c>=$this->min[$s] && $c<=$this->max[$s]) { + $snext = $this->transition[$s][$c-$this->min[$s]]; // move to next state + if ( $snext < 0 ) { + // was in range but not a normal transition + // must check EOT, which is like the else clause. + // eot[s]>=0 indicates that an EOT edge goes to another + // state. + if ( $this->eot[$s]>=0 ) { // EOT Transition to accept state? + if ( $this->debug ) echo("EOT transition"); + $s = $this->eot[$s]; + $input->consume(); + // TODO: I had this as return accept[eot[s]] + // which assumed here that the EOT edge always + // went to an accept...faster to do this, but + // what about predicated edges coming from EOT + // target? + continue; + } + $this->noViableAlt($s,$input); + return 0; + } + $s = $snext; + $input->consume(); + continue; + } + if ( $eot[$s]>=0 ) { // EOT Transition? + if ( $this->debug ) println("EOT transition"); + $s = $this->eot[$s]; + $input->consume(); + continue; + } + if ( $c==Token::$EOF && $eof[$s]>=0 ) { // EOF Transition to accept state? + if ( $this->debug ) echo ("accept via EOF; predict "+$this->accept[$eof[$s]]+" from "+$eof[$s]); + return $this->accept[$eof[$s]]; + } + // not in range and not EOF/EOT, must be invalid symbol + if ( $this->debug ) { + echo("min[".$s."]=".$this->min[$s]); + echo("max[".$s."]=".$this->max[$s]); + echo("eot[".$s."]=".$this->eot[$s]); + echo("eof[".$s."]=".$this->eof[$s]); + for ($p=0; $p<$this->transition[$s]->length; $p++) { + echo $this->transition[$s][$p]+" "; + } + echo "\n"; + } + $this->noViableAlt($s, $input); + return 0; + } + + } + + function noViableAlt($s, $input){ + if ($this->recognizer->state->backtracking>0) { + $this->recognizer->state->failed=true; + return; + } + $nvae = + new NoViableAltException($this->getDescription(), + $decisionNumber, + $s, + $input); + $this->error($nvae); + throw $nvae; + } + + /** A hook for debugging interface */ + protected function error($nvae) { ; } + + function specialStateTransition($s, $input) + { + return -1; + } + + public function getDescription() { + return "n/a"; + } + + /** Given a String that has a run-length-encoding of some unsigned shorts + * like "\1\2\3\9", convert to short[] {2,9,9,9}. We do this to avoid + * static short[] which generates so much init code that the class won't + * compile. :( + */ + public static function unpackEncodedString($encodedString) { + $data = array(); + $di = 0; + for ($i=0,$len=strlen($encodedString); $i<$len; $i+=2) { + $n = charAt($encodedString, $i); + $v = charAt($encodedString, $i+1); + // add v n times to data + for ($j=1; $j<=$n; $j++) { + if($v==0xff) $v=-1; + $data[$di++] = $v; + } + } + return $data; + } + + function __call($fn, $params){ + return call_user_func_array(array($this->recognizer, $fn), $params); + } +} + +?> \ No newline at end of file diff --git a/include/antlr/EarlyExitException.php b/include/antlr/EarlyExitException.php new file mode 100644 index 0000000..5df58a0 --- /dev/null +++ b/include/antlr/EarlyExitException.php @@ -0,0 +1,40 @@ +decisionNumber = $decisionNumber; + } +} + +?> \ No newline at end of file diff --git a/include/antlr/IntStream.php b/include/antlr/IntStream.php new file mode 100644 index 0000000..4d51b45 --- /dev/null +++ b/include/antlr/IntStream.php @@ -0,0 +1,112 @@ + \ No newline at end of file diff --git a/include/antlr/MismatchedRangeException.php b/include/antlr/MismatchedRangeException.php new file mode 100644 index 0000000..afe5fa1 --- /dev/null +++ b/include/antlr/MismatchedRangeException.php @@ -0,0 +1,46 @@ +a = $a; + $this->b = $b; + } + + public function __toString() { + return "MismatchedNotSetException(".$this->getUnexpectedType()." not in [".a.",".b."])"; + } +} + + +?> \ No newline at end of file diff --git a/include/antlr/MismatchedSetException.php b/include/antlr/MismatchedSetException.php new file mode 100644 index 0000000..ba917ff --- /dev/null +++ b/include/antlr/MismatchedSetException.php @@ -0,0 +1,43 @@ +expecting = $expecting; + } + + public function __toString() { + return "MismatchedSetException(".$this->getUnexpectedType()."!=".$this->expecting.")"; + } +} + +?> \ No newline at end of file diff --git a/include/antlr/MismatchedTokenException.php b/include/antlr/MismatchedTokenException.php new file mode 100644 index 0000000..ef0df99 --- /dev/null +++ b/include/antlr/MismatchedTokenException.php @@ -0,0 +1,42 @@ +expecting = $expecting; + } + + public function __toString() { + return "MismatchedTokenException(".$this->getUnexpectedType()."!=".$this->expecting.")"; + } +} +?> diff --git a/include/antlr/MissingTokenException.php b/include/antlr/MissingTokenException.php new file mode 100644 index 0000000..b7e1afd --- /dev/null +++ b/include/antlr/MissingTokenException.php @@ -0,0 +1,54 @@ +inserted = $inserted; + } + + public function getMissingType() { + return $this->expecting; + } + + public function toString() { + if ( $this->inserted!=null && $this->token!=null ) { + return "MissingTokenException(inserted ".$this->inserted." at ".$this->token->getText()+")"; + } + if ( $this->token!=null ) { + return "MissingTokenException(at ".$token->getText().")"; + } + return "MissingTokenException"; + } +} + +?> \ No newline at end of file diff --git a/include/antlr/NoViableAltException.php b/include/antlr/NoViableAltException.php new file mode 100644 index 0000000..eff3442 --- /dev/null +++ b/include/antlr/NoViableAltException.php @@ -0,0 +1,55 @@ +grammarDecisionDescription = $grammarDecisionDescription; + $this->decisionNumber = $decisionNumber; + $this->stateNumber = $stateNumber; + } + + public function __toString() { + if ( $this->input instanceof CharStream ) { + return "NoViableAltException('".$this->getUnexpectedType()."'@[".$this->grammarDecisionDescription."])"; + } + else { + return "NoViableAltException(".$this->getUnexpectedType()."@[".$this->grammarDecisionDescription."])"; + } + } +} +?> diff --git a/include/antlr/RecognitionException.php b/include/antlr/RecognitionException.php new file mode 100644 index 0000000..a8a2cbc --- /dev/null +++ b/include/antlr/RecognitionException.php @@ -0,0 +1,179 @@ +input = $input; + /** What is index of token/char were we looking at when the error occurred? */ + $this->index = $input->index(); + + /** The current Token when an error occurred. Since not all streams + * can retrieve the ith Token, we have to track the Token object. + * For parsers. Even when it's a tree parser, token might be set. + */ + $this->token=null; + + /** If this is a tree parser exception, node is set to the node with + * the problem. + */ + $this->node=null; + + /** The current char when an error occurred. For lexers. */ + $this->c=0; + + /** Track the line at which the error occurred in case this is + * generated from a lexer. We need to track this since the + * unexpected char doesn't carry the line info. + */ + $this->line=0; + + $this->charPositionInLine=0; + + /** If you are parsing a tree node stream, you will encounter som + * imaginary nodes w/o line/col info. We now search backwards looking + * for most recent token with line/col info, but notify getErrorHeader() + * that info is approximate. + */ + $this->approximateLineInfo=false; + + + if ( $this->input instanceof TokenStream ) { + $this->token = $input->LT(1); + $this->line = $this->token->getLine(); + $this->charPositionInLine = $this->token->getCharPositionInLine(); + } + if ( $this->input instanceof TreeNodeStream ) { + $this->extractInformationFromTreeNodeStream($input); + } + else if ( $input instanceof CharStream ) { + $this->c = $input->LA(1); + $this->line = $input->getLine(); + $this->charPositionInLine = $input->getCharPositionInLine(); + } + else { + $this->c = $input->LA(1); + } + } + + protected function extractInformationFromTreeNodeStream($input) { + $nodes = $input; + $this->node = $nodes->LT(1); + $adaptor = $nodes->getTreeAdaptor(); + $payload = $adaptor->getToken($this->node); + if ( $payload!=null ) { + $this->token = $payload; + if ( $payload->getLine()<= 0 ) { + // imaginary node; no line/pos info; scan backwards + $i = -1; + $priorNode = $nodes->LT($i); + while ( $priorNode!=null ) { + $priorPayload = $adaptor->getToken($priorNode); + if ( $priorPayload!=null && $priorPayload->getLine()>0 ) { + // we found the most recent real line / pos info + $this->line = $priorPayload->getLine(); + $this->charPositionInLine = $priorPayload->getCharPositionInLine(); + $this->approximateLineInfo = true; + break; + } + --$i; + $priorNode = $nodes->LT($i); + } + } + else { // node created from real token + $this->line = $payload->getLine(); + $this->charPositionInLine = $payload->getCharPositionInLine(); + } + } + else if ( $this->node instanceof Tree) { + $this->line = $this->node->getLine(); + $this->charPositionInLine = $this->node->getCharPositionInLine(); + if ( $this->node instanceof CommonTree) { + $this->token = $this->node->token; + } + } + else { + $type = $adaptor->getType($this->node); + $text = $adaptor->getText($this->node); + $this->token = CommonToken::forTypeAndText($type, $text); + } + } + + /** Return the token type or char of the unexpected input element */ + public function getUnexpectedType() { + if ( $this->input instanceof TokenStream ) { + return $this->token->getType(); + } + else if ( $this->input instanceof TreeNodeStream ) { + $nodes = $this->input; + $adaptor = $nodes->getTreeAdaptor(); + return $adaptor->getType($this->node); + } + else { + return $this->c; + } + } +} + + +?> \ No newline at end of file diff --git a/include/antlr/RecognizerSharedState.php b/include/antlr/RecognizerSharedState.php new file mode 100644 index 0000000..9b57ed0 --- /dev/null +++ b/include/antlr/RecognizerSharedState.php @@ -0,0 +1,87 @@ +following = array(); //Should be an array of bitsets + $this->_fsp = -1; + //public BitSet[] following = new BitSet[BaseRecognizer.INITIAL_FOLLOW_STACK_SIZE]; + + /** This is true when we see an error and before having successfully + * matched a token. Prevents generation of more than one error message + * per error. + */ + $this->errorRecovery = false; + /** The index into the input stream where the last error occurred. + * This is used to prevent infinite loops where an error is found + * but no token is consumed during recovery...another error is found, + * ad naseum. This is a failsafe mechanism to guarantee that at least + * one token/tree node is consumed for two errors. + */ + $this->lastErrorIndex = -1; + /** In lieu of a return value, this indicates that a rule or token + * has failed to match. Reset to false upon valid token match. + */ + $this->failed=false; + /** Did the recognizer encounter a syntax error? Track how many. */ + $this->syntaxErrors=0; + + /** If 0, no backtracking is going on. Safe to exec actions etc... + * If >0 then it's the level of backtracking. + */ + $this->backtracking = 0; + + /** An array[size num rules] of Map that tracks + * the stop token index for each rule. ruleMemo[ruleIndex] is + * the memoization table for ruleIndex. For key ruleStartIndex, you + * get back the stop token for associated rule or MEMO_RULE_FAILED. + * + * This is only used if rule memoization is on (which it is by default). + */ + $this->ruleMemo = null; + + + // LEXER FIELDS (must be in same state object to avoid casting + // constantly in generated code and Lexer object) :( + + + /** The goal of all lexer rules/methods is to create a token object. + * This is an instance variable as multiple rules may collaborate to + * create a single token. nextToken will return this object after + * matching lexer rule(s). If you subclass to allow multiple token + * emissions, then set this to the last token to be matched or + * something nonnull so that the auto token emit mechanism will not + * emit another token. + */ + $this->token = null; + + /** What character index in the stream did the current token start at? + * Needed, for example, to get the text for current token. Set at + * the start of nextToken. + */ + $this->tokenStartCharIndex = -1; + + /** The line on which the first character of the token resides */ + $this->tokenStartLine = 0; + + /** The character position of first character within the line */ + $this->tokenStartCharPositionInLine = 0; + + /** The channel number for the current token */ + $this->channel = 0; + + /** The token type for the current token */ + $this->type = 0; + + /** You can set the text for the current token to override what is in + * the input char buffer. Use setText() or can set this instance var. + */ + $this->text=null; + + } + + } + +?> \ No newline at end of file diff --git a/include/antlr/Set.php b/include/antlr/Set.php new file mode 100644 index 0000000..e8f494c --- /dev/null +++ b/include/antlr/Set.php @@ -0,0 +1,34 @@ +store = array(); + foreach($arr as $el){ + $this->store[$el] = $el; + } + } + + public function add($value){ + $this->store[$value] = $value; + } + + public function member($value){ + return array_key_exists($value, $this->store); + } + + public function union($otherSet){ + return new Set(array_merge($this->store, $otherSet->store)); + } + + public function unionInPlace($otherSet){ + $this->store = $this->union($otherSet)->store; + } + + public function remove($value){ + unset($this->store[$value]); + } + } + + + +?> \ No newline at end of file diff --git a/include/antlr/Token.php b/include/antlr/Token.php new file mode 100644 index 0000000..cbcedb8 --- /dev/null +++ b/include/antlr/Token.php @@ -0,0 +1,222 @@ +charPositionInLine=-1; + $ct->input = $input; + $ct->type = $type; + $ct->channel = $channel; + $ct->start = $start; + $ct->stop = $stop; + return $ct; + } + + public static function forType($type){ + return CommonToken::forInput($input=null, $type); + } + + public static function forTypeAndText($type, $text) { + $ct = new CommonToken(); + $ct->type = $type; + $ct->channel = TokenConst::$DEFAULT_CHANNEL; + $ct->text = $text; + return $ct; + } +/* + public CommonToken(Token oldToken) { + text = oldToken.getText(); + type = oldToken.getType(); + line = oldToken.getLine(); + index = oldToken.getTokenIndex(); + charPositionInLine = oldToken.getCharPositionInLine(); + channel = oldToken.getChannel(); + if ( oldToken instanceof CommonToken ) { + start = ((CommonToken)oldToken).start; + stop = ((CommonToken)oldToken).stop; + } + } + */ + public function getType() { + return $this->type; + } + + public function setLine($line) { + $this->line = $this->line; + } + + public function getText() { + if ( $this->text!=null ) { + return $this->text; + } + if ( $this->input==null ) { + return null; + } + $this->text = $this->input->substring($this->start,$this->stop); + return $this->text; + } + + /** Override the text for this token. getText() will return this text + * rather than pulling from the buffer. Note that this does not mean + * that start/stop indexes are not valid. It means that that input + * was converted to a new string in the token object. + */ + public function setText($text) { + $this->text = $this->text; + } + + public function getLine() { + return $this->line; + } + + public function getCharPositionInLine() { + return $this->charPositionInLine; + } + + public function setCharPositionInLine($charPositionInLine) { + $this->charPositionInLine = $this->charPositionInLine; + } + + public function getChannel() { + return $this->channel; + } + + public function setChannel($channel) { + $this->channel = $this->channel; + } + + public function setType($type) { + $this->type = $this->type; + } + + public function getStartIndex() { + return $this->start; + } + + public function setStartIndex($start) { + $this->start = $this->start; + } + + public function getStopIndex() { + return $this->stop; + } + + public function setStopIndex($stop) { + $this->stop = $this->stop; + } + + public function getTokenIndex() { + return $this->index; + } + + public function setTokenIndex($index) { + $this->index = $this->index; + } + + public function getInputStream() { + return $this->input; + } + + public function setInputStream($input) { + $this->input = $this->input; + } + + public function toString() { + $channelStr = ""; + if ( $this->channel>0 ) { + $channelStr=",channel=".$this->channel; + } + $txt = $this->getText(); + if ( $txt!=null ) { + $txt = str_replace("\n",'\n', $txt); + $txt = str_replace("\r",'\r', $txt); + $txt = str_replace("\t",'\t', $txt); + } + else { + $txt = ""; + } + return "[@".$this->getTokenIndex().",".$this->start.":".$this->stop."='".$txt."',<".$this->type.">".$channelStr.",".$this->line.":".$this->getCharPositionInLine()."]"; + } + + public function __toString(){ + return $this->toString(); + } + } + + TokenConst::$DEFAULT_CHANNEL=0; + TokenConst::$INVALID_TOKEN_TYPE=0; + + TokenConst::$EOF = CharStreamConst::$EOF; + TokenConst::$EOF_TOKEN = CommonToken::forType(TokenConst::$EOF); + + TokenConst::$INVALID_TOKEN_TYPE = 0; + TokenConst::$INVALID_TOKEN = CommonToken::forType(TokenConst::$INVALID_TOKEN_TYPE); + /** In an action, a lexer rule can set token to this SKIP_TOKEN and ANTLR + * will avoid creating a token for this symbol and try to fetch another. + */ + TokenConst::$SKIP_TOKEN = CommonToken::forType(TokenConst::$INVALID_TOKEN_TYPE); + + /** All tokens go to the parser (unless skip() is called in that rule) + * on a particular "channel". The parser tunes to a particular channel + * so that whitespace etc... can go to the parser on a "hidden" channel. + */ + TokenConst::$DEFAULT_CHANNEL = 0; + + /** Anything on different channel than DEFAULT_CHANNEL is not parsed + * by parser. + */ + TokenConst::$HIDDEN_CHANNEL = 99; + + + + TokenConst::$MIN_TOKEN_TYPE = TokenConst::$UP+1; + + + + + +?> \ No newline at end of file diff --git a/include/antlr/TokenStream.php b/include/antlr/TokenStream.php new file mode 100644 index 0000000..c7df300 --- /dev/null +++ b/include/antlr/TokenStream.php @@ -0,0 +1,71 @@ +=n, return Token.EOFToken. + * Return null for LT(0) and any index that results in an absolute address + * that is negative. + */ + public function LT($k); + + /** Get a token at an absolute index i; 0..n-1. This is really only + * needed for profiling and debugging and token stream rewriting. + * If you don't want to buffer up tokens, then this method makes no + * sense for you. Naturally you can't use the rewrite stream feature. + * I believe DebugTokenStream can easily be altered to not use + * this method, removing the dependency. + */ + public function get($i); + + /** Where is this stream pulling tokens from? This is not the name, but + * the object that provides Token objects. + */ + public function getTokenSource(); + + /** Return the text of all tokens from start to stop, inclusive. + * If the stream does not buffer all the tokens then it can just + * return "" or null; Users should not access $ruleLabel.text in + * an action of course in that case. + */ + public function toStringBetween($start, $stop); + + + /** Because the user is not required to use a token with an index stored + * in it, we must provide a means for two token objects themselves to + * indicate the start/end location. Most often this will just delegate + * to the other toString(int,int). This is also parallel with + * the TreeNodeStream.toString(Object,Object). + */ + public function toStringBetweenTokens($start, $stop); +} + +?> \ No newline at end of file diff --git a/include/antlr/antlr.php b/include/antlr/antlr.php new file mode 100644 index 0000000..9bc480b --- /dev/null +++ b/include/antlr/antlr.php @@ -0,0 +1,28 @@ + \ No newline at end of file diff --git a/include/antlr/util.php b/include/antlr/util.php new file mode 100644 index 0000000..b545acd --- /dev/null +++ b/include/antlr/util.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/include/calculator/Calc.php b/include/calculator/Calc.php new file mode 100644 index 0000000..7c73a42 --- /dev/null +++ b/include/calculator/Calc.php @@ -0,0 +1,87 @@ +debug("Entering get_calc(".$image_path.") method ..."); +$the_calc = << + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + +
+ +
+
+ + + +EOQ; +$log->debug("Exiting get_calc method ..."); +return $the_calc; +} +?> diff --git a/include/calculator/calc.js b/include/calculator/calc.js new file mode 100644 index 0000000..80881c3 --- /dev/null +++ b/include/calculator/calc.js @@ -0,0 +1,164 @@ + + + + + + + \ No newline at end of file diff --git a/include/ckeditor/.htaccess b/include/ckeditor/.htaccess new file mode 100644 index 0000000..3a5e702 --- /dev/null +++ b/include/ckeditor/.htaccess @@ -0,0 +1,24 @@ +# +# Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +# For licensing, see LICENSE.html or http://ckeditor.com/license +# + +# +# On some specific Linux installations you could face problems with Firefox. +# It could give you errors when loading the editor saying that some illegal +# characters were found (three strange chars in the beginning of the file). +# This could happen if you map the .js or .css files to PHP, for example. +# +# Those characters are the Byte Order Mask (BOM) of the Unicode encoded files. +# All FCKeditor files are Unicode encoded. +# + +AddType application/x-javascript .js +AddType text/css .css + +# +# If PHP is mapped to handle XML files, you could have some issues. The +# following will disable it. +# + +AddType text/xml .xml diff --git a/include/ckeditor/CHANGES.html b/include/ckeditor/CHANGES.html new file mode 100644 index 0000000..d48df68 --- /dev/null +++ b/include/ckeditor/CHANGES.html @@ -0,0 +1,642 @@ + + + + + Changelog - CKEditor + + + + +

+ CKEditor Changelog +

+

+ CKEditor 3.2.1

+

+ New features:

+
    +
  • #4478 : Enable the SelectAll command in source mode.
  • +
  • #5150 : Allow names in the CKEDITOR.config.colorButton_colors setting.
  • +
  • #4810 : Adding configuration option for image dialog preview area filling text.
  • +
  • #536 : Object style now could be applied on any parent element of current selection.
  • +
  • #5290 : Unified stylesSet loading removing dependencies from the styles combo. + Now the configuration entry is named 'config.stylesSet' instead of config.stylesCombo_stylesSet and the default location + is under the 'styles' plugin instead of 'stylescombo'.
  • +
  • #5352 : Allow to define the stylesSet array in the config object for the editor.
  • +
  • #5302 : Adding config option "forceEnterMode".
  • +
  • #5216 : Extend CKEDITOR.appendTo to allow a data parameter for the initial value.
  • +
  • #5024 : Added sample to show how to output XHTML and avoid deprecated tags.
  • +
+

+ Fixed issues:

+
    +
  • #5152 : Indentation using class attribute doesn't work properly.
  • +
  • #4682 : It wasn't possible to edit block elements in IE that had styles like width, height or float.
  • +
  • #4750 : Correcting default order of buttons layout in dialogs on Mac.
  • +
  • #4932 : Fixed collapse button not clickable on simple toolbar.
  • +
  • #5228 : Link dialog is automatically changes protocol when URLs that starts with '?'.
  • +
  • #4877 : Fixed CKEditor displays source code in one long line (IE quirks mode + office2003 skin).
  • +
  • #5132 : Apply inline style leaks into sibling words which are seperated spaces.
  • +
  • #3599 : Background color style on sized text displayed as narrow band behind.
  • +
  • #4661 : Translation missing in link dialog.
  • +
  • #5240 : Flash alignment property is not presented visually on fake element.
  • +
  • #4910 : Pasting in IE scrolls document to the end.
  • +
  • #5041 : Table summary attribute can't be removed with dialog.
  • +
  • #5124 : All inline styles cannot be applied on empty spaces.
  • +
  • #3570 : SCAYT marker shouldn't appear inside elements path bar.
  • +
  • #4553 : Dirty check result incorrect when editor document is empty.
  • +
  • #4555 : Unreleased memory when editor is created and destroyed.
  • +
  • #5118 : Arrow keys navigation in RTL languages is incorrect.
  • +
  • #4721 : Remove attribute 'value' of checkbox in IE.
  • +
  • #5278 : IE: Add validation to check for bad window names of popup window.
  • +
  • #5171 : Dialogs contains lists don't have proper voice labels.
  • +
  • #4791 : Can't place cursor inside a form that end with a checkbox/radio.
  • +
  • #4479 : StylesCombo doesn't reflect the selection state until it's first opened.
  • +
  • #4717 : 'Unlink' and 'Outdent' command buttons should be disabled on editor startup.
  • +
  • #5119 : Disabled command buttons are not being properly styled when focused.
  • +
  • #5307 : Hide dialog page cause problem when there's two tab pages remain.
  • +
  • #5343 : Active list item ARIA role is wrongly placed.
  • +
  • #3599 : Background color style applying to text with font size style has been narrowly rendered.
  • +
  • #4711 : Line break character inside preformatted text makes it unable to type text at the end of previous line.
  • +
  • #4829 : [IE] Apply style from combo has wrong result on manually created selection.
  • +
  • #4830 : Retrieving selected element isn't always right, especially selecting using keyboard (SHIFT+ARROW).
  • +
  • #5128 : Element attribute inside preformatted text is corrupted when converting to other blocks.
  • +
  • #5190 : Template list entry shouldn't gain initial focus open templates list dialog opens.
  • +
  • #5238 : Menu button doesn't display arrow icon in high-contrast mode.
  • +
  • #3576 : Non-attributed element of the same name with the applied style is incorrectly removed.
  • +
  • #5221 : Insert table into empty document cause JavaScript error thrown.
  • +
  • #5242 : Apply 'automatic' color option of text color incorrectly removes background-color style.
  • +
  • #4719 : IE does not escape attribute values properly.
  • +
  • #5170 : Firefox does not insert text into styled element properly.
  • +
  • #4026 : Office2003 skin has no toolbar button borders in High Contrast in IE7.
  • +
  • #4348 : There should have exception thrown when 'CKEDITOR_BASEPATH' couldn't be figured out automatically.
  • +
  • #5364 : Focus may not be put into dialog correctly when dialog skin file is loading slow.
  • +
  • #4016 : Justify the layout of forms select dialog in Chrome and IE7.
  • +
  • #5373 : Variable 'pathBlockElements' defines wrong items in CKEDITOR.dom.elementPath.
  • +
  • #5082 : Ctrl key should be described as Cmd key on Mac.
  • +
  • #5182 : Context menu is not been announced correctly by ATs.
  • +
  • #4898 : Can't navigate outside table under the last paragraph of document.
  • +
  • #4950 : List commands could compromise list item attribute and styles.
  • +
  • #5018 : Find result highlighting remove normal font color styles unintentionally.
  • +
  • #5376 : Unable to exit list from within a empty block under list item.
  • +
  • #5145 : Various SCAYT fixes.
  • +
  • #5319 : Match whole word doesn't work anymore after replacement has happened.
  • +
  • #5363 : 'title' attribute now presents on all editor iframes.
  • +
  • #5374 : Unable to toggle inline style when the selection starts at the linefeed of the previous paragraph.
  • +
  • #4513 : Selected link element is not always correctly detected when using keyboard arrows to perform such selection.
  • +
  • #5372 : Newly created sub list should inherit nothing from the original (parent) list, except the list type.
  • +
  • #5274 : [IE6] Templates preview image is displayed in wrong size.
  • +
  • #5292 : Preview in font size and family doesn't work with custom styles.
  • +
  • #5396 : Selection is lost when use cell properties dialog to change cell type to header.
  • +
  • #4082 : [IE+Quirks] Preview text in the image dialog is not wrapping.
  • +
  • #4197 : Fixing format combo don't hide when editor blur on Safari.
  • +
  • #5401 : The context menu break layout with Office2003 and V2 skin on IE quirks mode.
  • +
  • #4825 : Fixing browser context menu is opened when clicking right mouse button twice.
  • +
  • #5356 : The SCAYT dialog had issues with Prototype enabled pages.
  • +
  • #5266 : SCAYT was disturbing the rendering of TH elements.
  • +
  • #4688 : SCAYT was interfering on checkDirty.
  • +
  • #5429 : High Contrast mode was being mistakenly detected when loading the editor through Dojo's xhrGet.
  • +
  • #5221 : Range is mangled when making collapsed selection in an empty paragraph.
  • +
  • #5261 : Config option 'scayt_autoStartup' slow down editor loading.
  • +
  • #3846 : Google Chrome - No Img properties after inserting.
  • +
  • #5465 : ShiftEnter=DIV doesn't respect list item when pressing enter at end of list item.
  • +
  • #5454 : After replaced success, the popup window couldn't be closed and a js error occured.
  • +
  • #4784 : Incorrect cursor position after delete table cells.
  • +
  • #5149 : [FF] Cursor disappears after maximize when the editor has focus.
  • +
  • #5220 : DTD now shows tolerance to <style> appear inside content.
  • +
  • #5540 : Mobile browsers (iPhone, Android...) are marked as incompatible as they don't support editing features.
  • +
  • #5504 : [IE6/7] 'Paste' dialog will always get opened even when user allows the clipboard access dialog when using 'Paste' button.
  • +
  • Updated the following language files:
  • +
+

+ CKEditor 3.2

+

+ New features:

+
    +
  • Several accessibility enhancements:
      +
    • #4502 : The editor accessibility is now totally based on WAI-ARIA.
    • +
    • #5015 : Adding accessibility help dialog plugin.
    • +
    • #5014 : Keyboard navigation compliance with screen reader suggested keys.
    • +
    • #4595 : Better accessibility in the Templates dialog.
    • +
    • #3389 : Esc/Arrow Key now works for closing sub menu.
    • +
  • +
  • #4973 : The Style field in the Div Container dialog is now loading the styles defined in the default styleset used by the Styles toolbar combo.
  • +
+

+ Fixed issues:

+
    +
  • #5049 : Form Field list command in JAWS incorrectly lists extra fields.
  • +
  • #5008 : Lock/Unlock ratio buttons in the Image dialog was poorly designed in High Contrast mode.
  • +
  • #3980 : All labels in dialogs now use <label> instead of <div>.
  • +
  • #5213 : Reorganization of some entries in the language files to make it more consistent.
  • +
  • #5199 : In IE, single row toolbars didn't have the bottom padding.
  • +
+

+ CKEditor 3.1.1

+

+ New features:

+
    +
  • #4399 : Improved support for external file browsers by allowing executing a callback function.
  • +
  • #4612 : The text of links is now updated if it matches the URL to which it points to.
  • +
  • #4936 : New localization support for the Welsh language.
  • +
+

+ Fixed issues:

+
    +
  • #4272 : Kama skin toolbar was broken in IE+Quirks+RTL.
  • +
  • #4987 : Changed the url which is called by the Browser Server button in the Link tab of Image Properties dialog.
  • +
  • #5030 : The CKEDITOR.timestamp wasn't been appended to the skin.js file.
  • +
  • #4993 : Removed the float style from images when the user selects 'not set' for alignment.
  • +
  • #4944 : Fixed a bug where nested list structures with inconsequent levels were not being pasted correctly from MS Word.
  • +
  • #4637 : Table cells' 'nowrap' attribute was not being loaded by the cell property dialog. Thanks to pomu0325.
  • +
  • #4724 : Using the mouse to insert a link in IE might create incorrect results.
  • +
  • #4640 : Small optimizations for the fileBrowser plugin.
  • +
  • #4583 : The "Target Frame Name" field is now visible when target is set to 'frame' only.
  • +
  • #4863 : Fixing iframedialog's height doesn't stretch to 100% (except IE Quirks).
  • +
  • #4964 : The BACKSPACE key positioning was not correct in some cases with Firefox.
  • +
  • #4980 : Setting border, vspace and hspace of images to zero was not working.
  • +
  • #4773 : The fileBrowser plugin was overwriting onClick functions eventually defined on fileButton elements.
  • +
  • #4731 : The clipboard plugin was missing a reference to the dialog plugin.
  • +
  • #5051 : The about plugin was missing a reference to the dialog plugin.
  • +
  • #5146 : The wsc plugin was missing a reference to the dialog plugin.
  • +
  • #4632 : The print command will now properly break on the insertion point of page break for printing.
  • +
  • #4862 : The English (United Kingdom) language file has been renamed to en-gb.js.
  • +
  • #4618 : Selecting an emoticon or the lock and reset buttons in the image dialog fired the onBeforeUnload event in IE.
  • +
  • #4678 : It was not possible to set tables' width to empty value.
  • +
  • #5012 : Fixed dependency issues with the menu plugin.
  • +
  • #5040 : The editor will not properly ignore font related settings that have extra item separators (semi-colons).
  • +
  • #4046 : Justify should respect config.enterMode = CKEDITOR.ENTER_BR.
  • +
  • #4622 : Inserting tables multiple times was corrupting the undo system.
  • +
  • #4647 : [IE] Selection on an element within positioned container is lost after open context-menu then click one menu item.
  • +
  • #4683 : Double-quote character in attribute values was not escaped in the editor output.
  • +
  • #4762 : [IE] Unexpected vertical-scrolling behavior happens whenever focus is moving out of editor in source mode.
  • +
  • #4772 : Text color was not being applied properly on links.
  • +
  • #4795 : [IE] Press 'Del' key on horizontal line or table result in error.
  • +
  • #4824 : [IE] <br/> at the very first table cell breaks the editor selection.
  • +
  • #4851 : [IE] Delete table rows with context-menu may cause error.
  • +
  • #4951 : Replacing text with empty string was throwing errors.
  • +
  • #4963 : Link dialog was not opening properly for e-mail type links.
  • +
  • #5043 : Removed the possibility of having an unwanted script tag being outputted with the editor contents.
  • +
  • #3678 : There were issues when editing links inside floating divs with IE.
  • +
  • #4763 : Pressing ENTER key with text selected was not deleting the text in some situations.
  • +
  • #5096 : Simple ampersand attribute value doesn't work for more than one occurrence.
  • +
  • #3494 : Context menu is too narrow in some translations.
  • +
  • #5005 : Fixed HTML errors in PHP samples.
  • +
  • #5123 : Fixed broken XHTML in User Interface Languages sample.
  • +
  • #4893 : Editor now understands table cell inline styles.
  • +
  • #4611 : Selection around <select> in editor doesn't cause error anymore.
  • +
  • #4886 : Extra BR tags were being created in the output HTML.
  • +
  • #4933 : Empty tags with BR were being left in the DOM.
  • +
  • #5127 : There were errors when removing dialog definition pages through code.
  • +
  • #4767 : CKEditor was not working when ckeditor_source.js is loaded in the <body> .
  • +
  • #5062 : Avoided security warning message when loading the wysiwyg area in IE6 under HTTPS.
  • +
  • #5135 : The TAB key will now behave properly when in Source mode.
  • +
  • #4988 : It wasn't possible to use forcePasteAsPlainText with Safari on Mac.
  • +
  • #5095 : Safari on Mac deleted the current selection in the editor when Edit menu was clicked.
  • +
  • #5140 : In High Contrast mode, arrows were now been displayed for menus with submenus.
  • +
  • #5163 : The undo system was not working on some specific cases.
  • +
  • #5162 : The ajax sample was throwing errors when loading data.
  • +
  • #4999 : The Template dialog was not generating an undo snapshot.
  • +
  • Updated the following language files:
  • +
+

+ CKEditor 3.1

+

+ New features:

+
    +
  • #4067 : Introduced the full page editing support (from <html> to </html>).
  • +
  • #4228 : Introduced the Shared Spaces feature.
  • +
  • #4379 : Introduced the new powerful pasting system and word cleanup procedure, including enhancements to the paste as plain text feature.
  • +
  • #2872 : Introduced the new native PHP API, the first standardized server side support.
  • +
  • #4210 : Added CKEditor plugin for jQuery.
  • +
  • #2885 : Added 'div' dialog and corresponding context menu options.
  • +
  • #4574 : Added the table merging tools and corresponding context menu options.
  • +
  • #4340 : Added the email protection option for link dialog.
  • +
  • #4463 : Added inline CSS support in all places where custom stylesheet could apply.
  • +
  • #3881 : Added color dialog for 'more color' option in color buttons.
  • +
  • #4341 : Added the 'showborder' plugin.
  • +
  • #4549 : Make the anti-cache query string configurable.
  • +
  • #4708 : Added the 'htmlEncodeOutput' config option.
  • +
  • #4342 : Introduced the bodyId and bodyClass settings to specify the id and class. to be used in the editing area at runtime.
  • +
  • #3401 : Introduced the baseHref setting so it's possible to set the URL to be used to resolve absolute and relative URLs in the contents.
  • +
  • #4729 : Added support to fake elements for comments.
  • +
+

+ Fixed issues:

+
    +
  • #4707 : Fixed invalid link is requested in image preview.
  • +
  • #4461 : Fixed toolbar separator line along side combo enlarging the toolbar height.
  • +
  • #4596 : Fixed image re-size lock buttons aren't accessible in high-contrast mode.
  • +
  • #4676 : Fixed editing tables using table properties dialog overwrites original style values.
  • +
  • #4714 : Fixed IE6 JavaScript error when editing flash by commit 'Flash' dialog.
  • +
  • #3905 : Fixed 'wysiwyg' mode causes unauthenticated content warnings over SSL in FF 3.5.
  • +
  • #4768 : Fixed open context menu in IE throws js error when focus is not inside document.
  • +
  • #4822 : Fixed applying 'Headers' to existing table does not work in IE.
  • +
  • #4855 : Fixed toolbar doesn't wrap well for 'v2' skin in all browsers.
  • +
  • #4882 : Fixed auto detect paste from MS-Word is not working for Safari.
  • +
  • #4882 : Fixed unexpected margin style left behind on content cleaning up from MS-Word.
  • +
  • #4896 : Fixed paste nested list from MS-Word with measurement units set to cm is broken.
  • +
  • #4899 : Fixed unable to undo pre-formatted style.
  • +
  • #4900 : Fixed ratio-lock inconsistent between browsers.
  • +
  • #4901 : Fixed unable to edit any link with popup window's features in Firefox.
  • +
  • #4904 : Fixed when paste happen from dialog, it always throw JavaScript error.
  • +
  • #4905 : Fixed paste plain text result incorrect when content from dialog.
  • +
  • #4889 : Fixed unable to undo 'New Page' command after typing inside editor.
  • +
  • #4892 : Fixed table alignment style is not properly represented by the wrapping div.
  • +
  • #4918 : Fixed switching mode when maximized is showing background page contents.
  • +
+

+ CKEditor 3.0.2

+

+ New features:

+
    +
  • #4343 : Added the configuration option 'browserContextMenuOnCtrl' so it's possible to enable the default browser context menu by holding the CTRL key.
  • +
+

+ Fixed issues:

+
    +
  • #4552 : Fixed float panel doesn't show up since editor instanced been destroyed once.
  • +
  • #3918 : Fixed fake object is editable with Image dialog.
  • +
  • #4053 : Fixed 'Form Properties' missing from context menu when selection collapsed inside form.
  • +
  • #4401 : Fixed customized by removing 'upload' tab page from 'Link dialog' cause JavaScript error.
  • +
  • #4477 : Adding missing tag names in object style elements.
  • +
  • #4567 : Fixed IE throw error when pressing BACKSPACE in source mode.
  • +
  • #4573 : Fixed 'IgnoreEmptyPargraph' config doesn't work with the config 'entities' is set to 'false'.
  • +
  • #4614 : Fixed attribute protection fails because of line-break.
  • +
  • #4546 : Fixed UIColor plugin doesn't work when editor id contains CSS selector preserved keywords.
  • +
  • #4609 : Fixed flash object is lost when loading data from outside editor.
  • +
  • #4625 : Fixed editor stays visible in a div with style 'visibility:hidden'.
  • +
  • #4621 : Fixed clicking below table caused an empty table been generated.
  • +
  • #3373 : Fixed empty context menu when there's no menu item at all.
  • +
  • #4473 : Fixed setting rules on the same element tag name throws error.
  • +
  • #4514 : Fixed press 'Back' button breaks wysiwyg editing mode is Firefox.
  • +
  • #4542 : Fixed unable to access buttons using tab key in Safari and Opera.
  • +
  • #4577 : Fixed relative link url is broken after opening 'Link' dialog.
  • +
  • #4597 : Fixed custom style with same attribute name but different attribute value doesn't work.
  • +
  • #4651 : Fixed 'Deleted' and 'Inserted' text style is not rendering in wysiwyg mode and is wrong is source mode.
  • +
  • #4654 : Fixed 'CKEDITOR.config.font_defaultLabel(fontSize_defaultLabel)' is not working.
  • +
  • #3950 : Fixed table column insertion incorrect when selecting empty cell area.
  • +
  • #3912 : Fixed UIColor not working in IE when page has more than 30+ editors.
  • +
  • #4031 : Fixed mouse cursor on toolbar combo has more than 3 shapes.
  • +
  • #4041 : Fixed open context menu on multiple cells to remove them result in only one removed.
  • +
  • #4185 : Fixed resize handler effect doesn't affect flash object on output.
  • +
  • #4196 : Fixed 'Remove Numbered/Bulleted List' on nested list doesn't work well on nested list.
  • +
  • #4200 : Fixed unable to insert 'password' type filed with attributes.
  • +
  • #4530 : Fixed context menu couldn't open in Opera.
  • +
  • #4536 : Fixed keyboard navigation doesn't work at all in IE quirks mode.
  • +
  • #4584 : Fixed updated link Target field is not updating when updating to certain values.
  • +
  • #4603 : Fixed unable to disable submenu items in contextmenu.
  • +
  • #4672 : Fixed unable to redo the insertion of horizontal line.
  • +
  • #4677 : Fixed 'Tab' key is trapped by hidden dialog elements.
  • +
  • #4073 : Fixed insert template with replace option could result in empty document.
  • +
  • #4455 : Fixed unable to start editing when image inside document not loaded.
  • +
  • #4517 : Fixed 'dialog_backgroundCoverColor' doesn't work on IE6.
  • +
  • #3165 : Fixed enter key in empty list item before nested one result in collapsed line.
  • +
  • #4527 : Fixed checkbox generate invalid 'checked' attribute.
  • +
  • #1659 : Fixed unable to click below content to start editing in IE with 'config.docType' setting to standard compliant.
  • +
  • #3933 : Fixed extra <br> left at the end of document when the last element is a table.
  • +
  • #4736 : Fixed PAGE UP and PAGE DOWN keys in standards mode are not working.
  • +
  • #4725 : Fixed hitting 'enter' before html comment node produces a JavaScript error.
  • +
  • #4522 : Fixed unable to redo when typing after insert an image with relative url.
  • +
  • #4594 : Fixed context menu goes off-screen when mouse is at right had side of screen.
  • +
  • #4673 : Fixed undo not available straight away if shift key is used to enter first character.
  • +
  • #4690 : Fixed the parsing of nested inline elements.
  • +
  • #4450 : Fixed selecting multiple table cells before apply justify commands generates spurious paragraph in Firefox.
  • +
  • #4733 : Fixed dialog opening sometimes hang up Firefox and Safari.
  • +
  • #4498 : Fixed toolbar collapse button missing tooltip.
  • +
  • #4738 : Fixed inserting table inside bold/italic/underline generates error on ENTER_BR mode.
  • +
  • #4246 : Fixed avoid XHTML deprecated attributes for image styling.
  • +
  • #4543 : Fixed unable to move cursor between table and hr.
  • +
  • #4764 : Fixed wrong exception message when CKEDITOR.editor.append() to non-existing elements.
  • +
  • #4521 : Fixed dialog layout in IE6/7 may have scroll-bar and other weird effects.
  • +
  • #4709 : Fixed inconsistent scroll-bar behavior on IE.
  • +
  • #4776 : Fixed preview page failed to open when relative URl contains in document.
  • +
  • #4812 : Fixed 'Esc' key not working on dialogs in Opera.
  • +
  • Updated the following language files:
  • +
+

+ CKEditor 3.0.1

+

+ New features:

+
    +
  • #4219 : Added fallback mechanism for config.language.
  • +
  • #4194 : Added support for using multiple css style sheets within the editor.
  • +
+

+ Fixed issues:

+
    +
  • #3898 : Added validation for URL value in Image dialog.
  • +
  • #3528 : Fixed Context Menu issue when triggered using Shift+F10.
  • +
  • #4028 : Maximize control's tool tip was wrong once it is maximized.
  • +
  • #4237 : Toolbar is chopped off in Safari browser 3.x.
  • +
  • #4241 : Float panels are left on screen while editor is destroyed.
  • +
  • #4274 : Double click event is incorrect handled in 'divreplace' sample.
  • +
  • #4354 : Fixed TAB key on toolbar to not focus disabled buttons.
  • +
  • #3856 : Fixed focus and blur events in source view mode.
  • +
  • #3438 : Floating panels are off by (-1px, 0px) in RTL mode.
  • +
  • #3370 : Refactored use of CKEDITOR.env.isCustomDomain().
  • +
  • #4230 : HC detection caused js error.
  • +
  • #3978 : Fixed setStyle float on IE7 strict.
  • +
  • #4262 : Tab and Shift+Tab was not working to cycle through CTRL+SHIFT+F10 context menu in IE.
  • +
  • #3633 : Default context menu isn't disabled in toolbar, status bar, panels...
  • +
  • #3897 : Now there is no image previews when the URL is empty in image dialog.
  • +
  • #4048 : Context submenu was lacking uiColor.
  • +
  • #3568 : Dialogs now select all text when tabbing to text inputs.
  • +
  • #3727 : Cell Properties dialog was missing color selection option.
  • +
  • #3517 : Fixed "Match cyclic" field in Find & Replace dialog.
  • +
  • #4368 : borderColor table cell attribute haven't worked for none-IE
  • +
  • #4203 : In IE quirks mode + toolbar collapsed + source mode editing block height was incorrect.
  • +
  • #4387 : Fixed: right clicking in Kama skin can lead to a javascript error.
  • +
  • #4397 : Wysiwyg mode caused the host page scroll.
  • +
  • #4385 : Fixed editor's auto adjusting on DOM structure were confusing the dirty checking mechanism.
  • +
  • #4397 : Fixed regression of [3816] where turn on design mode was causing Firefox3 to scroll the host page.
  • +
  • #4254 : Added basic API sample.
  • +
  • #4107 : Normalize css font-family style text for correct comparision.
  • +
  • #3664 : Insert block element in empty editor document should not create new paragraph.
  • +
  • #4037 : 'id' attribute is missing with Flash dialog advanced page.
  • +
  • #4047 : Delete selected control type element when 'Backspace' is pressed on it.
  • +
  • #4191 : Fixed: dialog changes confirmation on image dialog appeared even when no changes have been made.
  • +
  • #4351 : Dash and dot could appear in attribute names.
  • +
  • #4355 : 'maximize' and 'showblock' commands shouldn't take editor focus.
  • +
  • #4504 : Fixed 'Enter'/'Esc' key is not working on dialog button.
  • +
  • #4245 : 'Strange Template' now come with a style attribute for width.
  • +
  • #4512 : Fixed styles plugin incorrectly adding semicolons to style text.
  • +
  • #3855 : Fixed loading unminified _source files when ckeditor_source.js is used.
  • +
  • #3717 : Dialog settings defaults can now be overridden in-page through the CKEDITOR.config object.
  • +
  • #4481 : The 'stylesCombo_stylesSet' configuration entry didn't work for full URLs.
  • +
  • #4480 : Fixed scope attribute in th.
  • +
  • #4467 : Fixed bug to use custom icon in context menus. Thanks to george.
  • +
  • #4190 : Fixed select field dialog layout in Safari.
  • +
  • #4518 : Fixed unable to open dialog without editor focus in IE.
  • +
  • #4519 : Fixed maximize without editor focus throw error in IE.
  • +
  • Updated the following language files:
  • +
+

+ CKEditor 3.0

+

+ New features:

+
    +
  • #3188 : Introduce + <pre> formatting feature when converting from other blocks.
  • +
  • #4445 : editor::setData now support an optional callback parameter.
  • +
+

+ Fixed issues:

+
    +
  • #2856 : Fixed problem with inches in Paste From Word plugin.
  • +
  • #3929 : Using Paste dialog, + the text is pasted into current selection
  • +
  • #3920 : Mouse cursor over characters in + Special Character dialog now is correct
  • +
  • #3882 : Fixed an issue + with PasteFromWord dialog in which default values was ignored
  • +
  • #3859 : Fixed Flash dialog layout in Webkit
  • +
  • #3852 : Disabled textarea resizing in dialogs
  • +
  • #3831 : The attempt to remove the contextmenu plugin + will not anymore break the editor
  • +
  • #3781 : Colorbutton is now disabled in 'source' mode
  • +
  • #3848 : Fixed an issue with Webkit in witch + elements in the Image and Link dialogs had wrong dimensions.
  • +
  • #3808 : Fixed UI Color Picker dialog size in example page.
  • +
  • #3658 : Editor had horizontal scrollbar in IE6.
  • +
  • #3819 : The cursor was not visible + when applying style to collapsed selections in Firefox 2.
  • +
  • #3809 : Fixed beam cursor + when mouse cursor is over text-only buttons in IE.
  • +
  • #3815 : Fixed an issue + with the form dialog in which the "enctype" attribute is outputted as "encoding".
  • +
  • #3785 : Fixed an issue + in CKEDITOR.tools.htmlEncode() which incorrectly outputs &nbsp; in IE8.
  • +
  • #3820 : Fixed an issue in + bullet list command in which a list created at the bottom of another gets merged to the top. +
  • +
  • #3830 : Table cell properties dialog + doesn't apply to all selected cells.
  • +
  • #3835 : Element path is not refreshed + after click on 'newpage'; and safari is not putting focus on document also. +
  • +
  • #3821 : Fixed an issue with JAWS in which + toolbar items are read inconsistently between virtual cursor modes.
  • +
  • #3789 : The "src" attribute + was getting duplicated in some situations.
  • +
  • #3591 : Protecting flash related elements + including '<object>', '<embed>' and '<param>'. +
  • +
  • #3759 : Fixed CKEDITOR.dom.element::scrollIntoView + logic bug which scroll even element is inside viewport. +
  • +
  • #3773 : Fixed remove list will merge lines. +
  • +
  • #3829 : Fixed remove empty link on output data.
  • +
  • #3730 : Indent is performing on the whole + block instead of selected lines in enterMode = BR.
  • +
  • #3844 : Fixed UndoManager register keydown on obsoleted document
  • +
  • #3805 : Enabled SCAYT plugin for IE.
  • +
  • #3834 : Context menu on table caption was incorrect.
  • +
  • #3812 : Fixed an issue in which the editor + may show up empty or uneditable in IE7, 8 and Firefox 3.
  • +
  • #3825 : Fixed JS error when opening spellingcheck.
  • +
  • #3862 : Fixed html parser infinite loop on certain malformed + source code.
  • +
  • #3639 : Button size was inconsistent.
  • +
  • #3874 : Paste as plain text in Safari loosing lines.
  • +
  • #3849 : Fixed IE8 crashes when applying lists and indenting.
  • +
  • #3876 : Changed dialog checkbox and radio labels to explicit labels.
  • +
  • #3843 : Fixed context submenu position in IE 6 & 7 RTL.
  • +
  • #3864 : [FF]Document is not editable after inserting element on a fresh page.
  • +
  • #3883 : Fixed removing inline style logic incorrect on Firefox2.
  • +
  • #3884 : Empty "href" attribute was duplicated on output data.
  • +
  • #3858 : Fixed the issue where toolbars + break up in IE6 and IE7 after the browser is resized.
  • +
  • #3868 : [chrome] SCAYT toolbar options was in reversed order.
  • +
  • #3875 : Fixed an issue in Safari where + table row/column/cell menus are not useable when table cells are selected.
  • +
  • #3896 : The editing area was + flashing when switching forth and back to source view.
  • +
  • #3894 : Fixed an issue where editor failed to initialize when using the on-demand loading way.
  • +
  • #3903 : Color button plugin doesn't read config entry from editor instance correctly.
  • +
  • #3801 : Comments at the start of the document was lost in IE.
  • +
  • #3871 : Unable to redo when undos to the front of snapshots stack.
  • +
  • #3909 : Move focus from editor into a text input control is broken.
  • +
  • #3870 : The empty paragraph + desappears when hitting ENTER after "New Page".
  • +
  • #3887 : Fixed an issue in which the create + list command may leak outside of a selected table cell and into the rest of document.
  • +
  • #3916 : Fixed maximize does not enlarge editor width when width is set.
  • +
  • #3879 : [webkit] Color button panel had incorrect size on first open.
  • +
  • #3839 : Update Scayt plugin to reflect the latest change from SpellChecker.net.
  • +
  • #3742 : Fixed wrong dialog layout for dialogs without tab bar in IE RTL mode .
  • +
  • #3671 : Fixed body fixing should be applied to the real type under fake elements.
  • +
  • #3836 : Fixed remove list in enterMode=BR will merge sibling text to one line.
  • +
  • #3949 : Fixed enterKey within pre-formatted text introduce wrong line-break.
  • +
  • #3878 : Whenever possible, + dialogs will not present scrollbars if the content is too big for its standard + size.
  • +
  • #3782 : Remove empty list in table cell result in collapsed cell.
  • +
  • Updated the following language files:
  • +
  • #3984 : [IE]The pre-formatted style is generating error.
  • +
  • #3946 : Fixed unable to hide contextmenu.
  • +
  • #3956 : Fixed About dialog in Source Mode for IE.
  • +
  • #3953 : Fixed keystroke for close Paste dialog.
  • +
  • #3951 : Reset size and lock ratio options were not accessible in Image dialog.
  • +
  • #3921 : Fixed Container scroll issue on IE7.
  • +
  • #3940 : Fixed list operation doesn't stop at table.
  • +
  • #3891 : [IE] Fixed 'automatic' font color doesn't work.
  • +
  • #3972 : Fixed unable to remove a single empty list in document in Firefox with enterMode=BR.
  • +
  • #3973 : Fixed list creation error at the end of document.
  • +
  • #3959 : Pasting styled text from word result in content lost.
  • +
  • #3793 : Combined images into sprites.
  • +
  • #3783 : Fixed indenting command in table cells create collapsed paragraph.
  • +
  • #3968 : About dialog layout was broken with IE+Standards+RTL.
  • +
  • #3991 : In IE quirks, text was not visible in v2 and office2003 skins.
  • +
  • #3983 : In IE, we'll now + silently ignore wrong toolbar definition settings which have extra commas being + left around.
  • +
  • Fixed the following test cases:
      +
    • #3992 : core/ckeditor2.html
    • +
    • #4138 : core/plugins.html
    • +
    • #3801 : plugins/htmldataprocessor/htmldataprocessor.html
    • +
  • +
  • #3989 : Host page horizontal scrolling a lot when on having righ-to-left direction.
  • +
  • #4001 : Create link around existing image result incorrect.
  • +
  • #3988 : Destroy editor on form submit event cause error.
  • +
  • #3994 : Insert horizontal line at end of document cause error.
  • +
  • #4074 : Indent error with 'indentClasses' config specified.
  • +
  • #4057 : Fixed anchor is lost after switch between editing modes.
  • +
  • #3644 : Image dialog was missin radio lock.
  • +
  • #4014 : Firefox2 had no dialog button backgrounds.
  • +
  • #4018 : Firefox2 had no richcombo text visible.
  • +
  • #4035 : [IE6] Paste dialog size was too small.
  • +
  • #4049 : Kama skin was too wide with config.width.
  • +
  • The following released files now doesn't require the _source folder
      +
    • #4086 : _samples/ui_languages.html
    • +
    • #4093 : _tests/core/dom/document.html
    • +
    • #4094 : Smiley plugin file
    • +
    • #4097 : No undo/redo support for fontColor and backgroundColor buttons.
    • +
  • +
  • #4085 : Paste and Paste from Word dialogs were not well styled in IE+RTL.
  • +
  • #3982 : Fixed enterKey on empty list item result in weird dom structure.
  • +
  • #4101 : Now it is possible to close dialog before gets focus.
  • +
  • #4075 : [IE6/7]Fixed apply custom inline style with "class" attribute failed.
  • +
  • #4087 : [Firefox]Fixed extra blocks created on create list when full document selected.
  • +
  • #4097 : No undo/redo support for fontColor and backgroundColor buttons.
  • +
  • #4111 : Fixed apply block style after inline style applied on full document error.
  • +
  • #3622 : Fixed shift enter with selection not deleting highlighted text.
  • +
  • #4092 : [IE6] Close button was missing for dialog without multiple tabs.
  • +
  • #4003 : Markup on the image dialog was disrupted when removing the border input.
  • +
  • #4096 : Editor content area was pushed down in IE RTL quirks.
  • +
  • #4112 : [FF] Paste dialog had scrollbars in quirks.
  • +
  • #4118 : Dialog dragging was + occasionally behaving strangely .
  • +
  • #4077 : The toolbar combos + were rendering incorrectly in some languages, like Chinese.
  • +
  • #3622 : The toolbar in the v2 + skin was wrapping improperly in some languages.
  • +
  • #4119 : Unable to edit image link with image dialog.
  • +
  • #4117 : Fixed dialog error when transforming image into button.
  • +
  • #4058 : [FF] wysiwyg mode is sometimes not been activated.
  • +
  • #4114 : [IE] RTE + IE6/IE7 Quirks = dialog mispositoned.
  • +
  • #4123 : Some dialog buttons were broken in IE7 quirks.
  • +
  • #4122 : [IE] The image dialog + was being rendered improperly when loading an image with long URL.
  • +
  • #4144 : Fixed the white-spaces at the end of <pre> is incorrectly removed.
  • +
  • #4143 : Fixed element id is lost when extracting contents from the range.
  • +
  • #4007 : [IE] Source area overflow from editor chrome.
  • +
  • #4145 : Fixed the on demand + ("basic") loading model of the editor.
  • +
  • #4139 : Fixed list plugin regression of [3903].
  • +
  • #4147 : Unify style text normalization logic when comparing styles.
  • +
  • #4150 : Fixed enlarge list result incorrect at the inner boundary of block.
  • +
  • #4164 : Now it is possible to paste text + in Source mode even if forcePasteAsPlainText = true.
  • +
  • #4129 : [FF]Unable to remove list with Ctrl-A.
  • +
  • #4172 : [Safari] The trailing + <br> was not been always added to blank lines ending with &nbsp;.
  • +
  • #4178 : It's now possible to + copy and paste Flash content among different editor instances.
  • +
  • #4193 : Automatic font color produced empty span on Firefox 3.5.
  • +
  • #4186 : [FF] Fixed First open float panel cause host page scrollbar blinking.
  • +
  • #4227 : Fixed destroy editor instance created on textarea which is not within form cause error.
  • +
  • #4240 : Fixed editor name containing hyphen break editor completely.
  • +
  • #3828 : Malformed nested list is now corrected by the parser.
  • +
+

+ CKEditor 3.0 RC

+

+ Changelog starts at this release.

+ + + diff --git a/include/ckeditor/INSTALL.html b/include/ckeditor/INSTALL.html new file mode 100644 index 0000000..7b593e6 --- /dev/null +++ b/include/ckeditor/INSTALL.html @@ -0,0 +1,92 @@ + + + + + Installation Guide - CKEditor + + + + +

+ CKEditor Installation Guide

+

+ What's CKEditor?

+

+ CKEditor is a text editor to be used inside web pages. It's not a replacement + for desktop text editors like Word or OpenOffice, but a component to be used as + part of web applications and web sites.

+

+ Installation

+

+ Installing CKEditor is an easy task. Just follow these simple steps:

+
    +
  1. Download the latest version of the editor from our web site: http://ckeditor.com. You should have already completed + this step, but be sure you have the very latest version.
  2. +
  3. Extract (decompress) the downloaded file into the root of your + web site.
  4. +
+

+ Note: CKEditor is by default installed in the "ckeditor" + folder. You can place the files in whichever you want though.

+

+ Checking Your Installation +

+

+ The editor comes with a few sample pages that can be used to verify that installation + proceeded properly. Take a look at the _samples directory.

+

+ To test your installation, just call the following page at your web site:

+
+http://<your site>/<CKEditor installation path>/_samples/index.html
+
+For example:
+http://www.example.com/ckeditor/_samples/index.html
+

+ Documentation

+

+ The full editor documentation is available online at the following address:
+ http://docs.cksource.com/ckeditor

+ + + diff --git a/include/ckeditor/LICENSE.html b/include/ckeditor/LICENSE.html new file mode 100644 index 0000000..f7ba067 --- /dev/null +++ b/include/ckeditor/LICENSE.html @@ -0,0 +1,1334 @@ + + + + + License - CKEditor + + +

+ Software License Agreement +

+

+ CKEditor™ - The text editor for Internet™ - + http://ckeditor.com
+ Copyright © 2003-2010, CKSource - Frederico Knabben. All rights reserved. +

+

+ Licensed under the terms of any of the following licenses at your choice: +

+ +

+ You are not required to, but if you want to explicitly declare the license you have + chosen to be bound to when using, reproducing, modifying and distributing this software, + just include a text file titled "LEGAL" in your version of this software, indicating + your license choice. In any case, your choice will not restrict any recipient of + your version of this software to use, reproduce, modify and distribute this software + under any of the above licenses. +

+

+ Sources of Intellectual Property Included in CKEditor +

+

+ Where not otherwise indicated, all CKEditor content is authored by CKSource engineers + and consists of CKSource-owned intellectual property. In some specific instances, + CKEditor will incorporate work done by developers outside of CKSource with their + express permission. +

+

+ YUI Test: At _source/tests/yuitest.js + can be found part of the source code of YUI, which is licensed under the terms of + the BSD License. YUI is + Copyright © 2008, Yahoo! Inc. +

+

+ Trademarks +

+

+ CKEditor is a trademark of CKSource - Frederico Knabben. All other brand and product + names are trademarks, registered trademarks or service marks of their respective + holders. +

+ + diff --git a/include/ckeditor/_source/adapters/jquery.js b/include/ckeditor/_source/adapters/jquery.js new file mode 100644 index 0000000..e2b9214 --- /dev/null +++ b/include/ckeditor/_source/adapters/jquery.js @@ -0,0 +1,297 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview jQuery adapter provides easy use of basic CKEditor functions + * and access to internal API. It also integrates some aspects of CKEditor with + * jQuery framework. + * + * Every TEXTAREA, DIV and P elements can be converted to working editor. + * + * Plugin exposes some of editor's event to jQuery event system. All of those are namespaces inside + * ".ckeditor" namespace and can be binded/listened on supported textarea, div and p nodes. + * + * Available jQuery events: + * - instanceReady.ckeditor( editor, rootNode ) + * Triggered when new instance is ready. + * - destroy.ckeditor( editor ) + * Triggered when instance is destroyed. + * - getData.ckeditor( editor, eventData ) + * Triggered when getData event is fired inside editor. It can change returned data using eventData reference. + * - setData.ckeditor( editor ) + * Triggered when getData event is fired inside editor. + * + * @example + * + * + * + */ + +(function() +{ + /** + * Allow CKEditor to override jQuery.fn.val(). This results in ability to use val() + * function on textareas as usual and having those calls synchronized with CKEditor + * Rich Text Editor component. + * + * This config option is global and executed during plugin load. + * Can't be customized across editor instances. + * + * @type Boolean + * @example + * $( 'textarea' ).ckeditor(); + * // ... + * $( 'textarea' ).val( 'New content' ); + */ + CKEDITOR.config.jqueryOverrideVal = typeof CKEDITOR.config.jqueryOverrideVal == 'undefined' + ? true : CKEDITOR.config.jqueryOverrideVal; + + var jQuery = window.jQuery; + + if ( typeof jQuery == 'undefined' ) + return; + + // jQuery object methods. + jQuery.extend( jQuery.fn, + /** @lends jQuery.fn */ + { + /** + * Return existing CKEditor instance for first matched element. + * Allows to easily use internal API. Doesn't return jQuery object. + * + * Raised exception if editor doesn't exist or isn't ready yet. + * + * @name jQuery.ckeditorGet + * @return CKEDITOR.editor + * @see CKEDITOR.editor + */ + ckeditorGet: function() + { + var instance = this.eq( 0 ).data( 'ckeditorInstance' ); + if ( !instance ) + throw "CKEditor not yet initialized, use ckeditor() with callback."; + return instance; + }, + /** + * Triggers creation of CKEditor in all matched elements (reduced to DIV, P and TEXTAREAs). + * Binds callback to instanceReady event of all instances. If editor is already created, than + * callback is fired right away. + * + * Mixed parameter order allowed. + * + * @param callback Function to be run on editor instance. Passed parameters: [ textarea ]. + * Callback is fiered in "this" scope being ckeditor instance and having source textarea as first param. + * + * @param config Configuration options for new instance(s) if not already created. + * See URL + * + * @example + * $( 'textarea' ).ckeditor( function( textarea ) { + * $( textarea ).val( this.getData() ) + * } ); + * + * @name jQuery.fn.ckeditor + * @return jQuery.fn + */ + ckeditor: function( callback, config ) + { + if ( !jQuery.isFunction( callback )) + { + var tmp = config; + config = callback; + callback = tmp; + } + config = config || {}; + + this.filter( 'textarea, div, p' ).each( function() + { + var $element = jQuery( this ), + editor = $element.data( 'ckeditorInstance' ), + instanceLock = $element.data( '_ckeditorInstanceLock' ), + element = this; + + if ( editor && !instanceLock ) + { + if ( callback ) + callback.apply( editor, [ this ] ); + } + else if ( !instanceLock ) + { + // CREATE NEW INSTANCE + + // Handle config.autoUpdateElement inside this plugin if desired. + if ( config.autoUpdateElement + || ( typeof config.autoUpdateElement == 'undefined' && CKEDITOR.config.autoUpdateElement ) ) + { + config.autoUpdateElementJquery = true; + } + + // Always disable config.autoUpdateElement. + config.autoUpdateElement = false; + $element.data( '_ckeditorInstanceLock', true ); + + // Set instance reference in element's data. + editor = CKEDITOR.replace( element, config ); + $element.data( 'ckeditorInstance', editor ); + + // Register callback. + editor.on( 'instanceReady', function( event ) + { + var editor = event.editor; + setTimeout( function() + { + // Delay bit more if editor is still not ready. + if ( !editor.element ) + { + setTimeout( arguments.callee, 100 ); + return; + } + + // Remove this listener. + event.removeListener( 'instanceReady', this.callee ); + + // Forward setData on dataReady. + editor.on( 'dataReady', function() + { + $element.trigger( 'setData' + '.ckeditor', [ editor ] ); + }); + + // Forward getData. + editor.on( 'getData', function( event ) { + $element.trigger( 'getData' + '.ckeditor', [ editor, event.data ] ); + }, 999 ); + + // Forward destroy event. + editor.on( 'destroy', function() + { + $element.trigger( 'destroy.ckeditor', [ editor ] ); + }); + + // Integrate with form submit. + if ( editor.config.autoUpdateElementJquery && $element.is( 'textarea' ) && $element.parents( 'form' ).length ) + { + var onSubmit = function() + { + $element.ckeditor( function() + { + editor.updateElement(); + }); + }; + + // Bind to submit event. + $element.parents( 'form' ).submit( onSubmit ); + + // Bind to form-pre-serialize from jQuery Forms plugin. + $element.parents( 'form' ).bind( 'form-pre-serialize', onSubmit ); + + // Unbind when editor destroyed. + $element.bind( 'destroy.ckeditor', function() + { + $element.parents( 'form' ).unbind( 'submit', onSubmit ); + $element.parents( 'form' ).unbind( 'form-pre-serialize', onSubmit ); + }); + } + + // Garbage collect on destroy. + editor.on( 'destroy', function() + { + $element.data( 'ckeditorInstance', null ); + }); + + // Remove lock. + $element.data( '_ckeditorInstanceLock', null ); + + // Fire instanceReady event. + $element.trigger( 'instanceReady.ckeditor', [ editor ] ); + + // Run given (first) code. + if ( callback ) + callback.apply( editor, [ element ] ); + }, 0 ); + }, null, null, 9999); + } + else + { + // Editor is already during creation process, bind our code to the event. + CKEDITOR.on( 'instanceReady', function( event ) + { + var editor = event.editor; + setTimeout( function() + { + // Delay bit more if editor is still not ready. + if ( !editor.element ) + { + setTimeout( arguments.callee, 100 ); + return; + } + + if ( editor.element.$ == element ) + { + // Run given code. + if ( callback ) + callback.apply( editor, [ element ] ); + } + }, 0 ); + }, null, null, 9999); + } + }); + return this; + } + }); + + // New val() method for objects. + if ( CKEDITOR.config.jqueryOverrideVal ) + { + jQuery.fn.val = CKEDITOR.tools.override( jQuery.fn.val, function( oldValMethod ) + { + /** + * CKEditor-aware val() method. + * + * Acts same as original jQuery val(), but for textareas which have CKEditor instances binded to them, method + * returns editor's content. It also works for settings values. + * + * @param oldValMethod + * @name jQuery.fn.val + */ + return function( newValue, forceNative ) + { + var isSetter = typeof newValue != 'undefined', + result; + + this.each( function() + { + var $this = jQuery( this ), + editor = $this.data( 'ckeditorInstance' ); + + if ( !forceNative && $this.is( 'textarea' ) && editor ) + { + if ( isSetter ) + editor.setData( newValue ); + else + { + result = editor.getData(); + // break; + return null; + } + } + else + { + if ( isSetter ) + oldValMethod.call( $this, newValue ); + else + { + result = oldValMethod.call( $this ); + // break; + return null; + } + } + + return true; + }); + return isSetter ? this : result; + }; + }); + } +})(); diff --git a/include/ckeditor/_source/core/_bootstrap.js b/include/ckeditor/_source/core/_bootstrap.js new file mode 100644 index 0000000..3904b75 --- /dev/null +++ b/include/ckeditor/_source/core/_bootstrap.js @@ -0,0 +1,91 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview API initialization code. + */ + +(function() +{ + // Disable HC detaction in WebKit. (#5429) + if ( CKEDITOR.env.webkit ) + { + CKEDITOR.env.hc = false; + return; + } + + // Check is High Contrast is active by creating a temporary element with a + // background image. + + var useSpacer = CKEDITOR.env.ie && CKEDITOR.env.version < 7, + useBlank = CKEDITOR.env.ie && CKEDITOR.env.version == 7; + + var backgroundImageUrl = useSpacer ? ( CKEDITOR.basePath + 'images/spacer.gif' ) : + useBlank ? 'about:blank' : 'data:image/png;base64,'; + + var hcDetect = CKEDITOR.dom.element.createFromHtml( + '
', CKEDITOR.document ); + + hcDetect.appendTo( CKEDITOR.document.getHead() ); + + // Update CKEDITOR.env. + // Catch exception needed sometimes for FF. (#4230) + try + { + CKEDITOR.env.hc = ( hcDetect.getComputedStyle( 'background-image' ) == 'none' ); + } + catch (e) + { + CKEDITOR.env.hc = false; + } + + if ( CKEDITOR.env.hc ) + CKEDITOR.env.cssClass += ' cke_hc'; + + hcDetect.remove(); +})(); + +// Load core plugins. +CKEDITOR.plugins.load( CKEDITOR.config.corePlugins.split( ',' ), function() + { + CKEDITOR.status = 'loaded'; + CKEDITOR.fire( 'loaded' ); + + // Process all instances created by the "basic" implementation. + var pending = CKEDITOR._.pending; + if ( pending ) + { + delete CKEDITOR._.pending; + + for ( var i = 0 ; i < pending.length ; i++ ) + CKEDITOR.add( pending[ i ] ); + } + }); + +/* +TODO: Enable the following and check if effective. + +if ( CKEDITOR.env.ie ) +{ + // Remove IE mouse flickering on IE6 because of background images. + try + { + document.execCommand( 'BackgroundImageCache', false, true ); + } + catch (e) + { + // We have been reported about loading problems caused by the above + // line. For safety, let's just ignore errors. + } +} +*/ + +/** + * Fired when a CKEDITOR core object is fully loaded and ready for interaction. + * @name CKEDITOR#loaded + * @event + */ diff --git a/include/ckeditor/_source/core/ajax.js b/include/ckeditor/_source/core/ajax.js new file mode 100644 index 0000000..176d1a2 --- /dev/null +++ b/include/ckeditor/_source/core/ajax.js @@ -0,0 +1,143 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.ajax} object, which holds ajax methods for + * data loading. + */ + +/** + * Ajax methods for data loading. + * @namespace + * @example + */ +CKEDITOR.ajax = (function() +{ + var createXMLHttpRequest = function() + { + // In IE, using the native XMLHttpRequest for local files may throw + // "Access is Denied" errors. + if ( !CKEDITOR.env.ie || location.protocol != 'file:' ) + try { return new XMLHttpRequest(); } catch(e) {} + + try { return new ActiveXObject( 'Msxml2.XMLHTTP' ); } catch (e) {} + try { return new ActiveXObject( 'Microsoft.XMLHTTP' ); } catch (e) {} + + return null; + }; + + var checkStatus = function( xhr ) + { + // HTTP Status Codes: + // 2xx : Success + // 304 : Not Modified + // 0 : Returned when running locally (file://) + // 1223 : IE may change 204 to 1223 (see http://dev.jquery.com/ticket/1450) + + return ( xhr.readyState == 4 && + ( ( xhr.status >= 200 && xhr.status < 300 ) || + xhr.status == 304 || + xhr.status === 0 || + xhr.status == 1223 ) ); + }; + + var getResponseText = function( xhr ) + { + if ( checkStatus( xhr ) ) + return xhr.responseText; + return null; + }; + + var getResponseXml = function( xhr ) + { + if ( checkStatus( xhr ) ) + { + var xml = xhr.responseXML; + return new CKEDITOR.xml( xml && xml.firstChild ? xml : xhr.responseText ); + } + return null; + }; + + var load = function( url, callback, getResponseFn ) + { + var async = !!callback; + + var xhr = createXMLHttpRequest(); + + if ( !xhr ) + return null; + + xhr.open( 'GET', url, async ); + + if ( async ) + { + // TODO: perform leak checks on this closure. + /** @ignore */ + xhr.onreadystatechange = function() + { + if ( xhr.readyState == 4 ) + { + callback( getResponseFn( xhr ) ); + xhr = null; + } + }; + } + + xhr.send(null); + + return async ? '' : getResponseFn( xhr ); + }; + + return /** @lends CKEDITOR.ajax */ { + + /** + * Loads data from an URL as plain text. + * @param {String} url The URL from which load data. + * @param {Function} [callback] A callback function to be called on + * data load. If not provided, the data will be loaded + * asynchronously, passing the data value the function on load. + * @returns {String} The loaded data. For asynchronous requests, an + * empty string. For invalid requests, null. + * @example + * // Load data synchronously. + * var data = CKEDITOR.ajax.load( 'somedata.txt' ); + * alert( data ); + * @example + * // Load data asynchronously. + * var data = CKEDITOR.ajax.load( 'somedata.txt', function( data ) + * { + * alert( data ); + * } ); + */ + load : function( url, callback ) + { + return load( url, callback, getResponseText ); + }, + + /** + * Loads data from an URL as XML. + * @param {String} url The URL from which load data. + * @param {Function} [callback] A callback function to be called on + * data load. If not provided, the data will be loaded + * asynchronously, passing the data value the function on load. + * @returns {CKEDITOR.xml} An XML object holding the loaded data. For asynchronous requests, an + * empty string. For invalid requests, null. + * @example + * // Load XML synchronously. + * var xml = CKEDITOR.ajax.loadXml( 'somedata.xml' ); + * alert( xml.getInnerXml( '//' ) ); + * @example + * // Load XML asynchronously. + * var data = CKEDITOR.ajax.loadXml( 'somedata.xml', function( xml ) + * { + * alert( xml.getInnerXml( '//' ) ); + * } ); + */ + loadXml : function( url, callback ) + { + return load( url, callback, getResponseXml ); + } + }; +})(); diff --git a/include/ckeditor/_source/core/ckeditor.js b/include/ckeditor/_source/core/ckeditor.js new file mode 100644 index 0000000..8ce635e --- /dev/null +++ b/include/ckeditor/_source/core/ckeditor.js @@ -0,0 +1,103 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Contains the third and last part of the {@link CKEDITOR} object + * definition. + */ + +// Remove the CKEDITOR.loadFullCore reference defined on ckeditor_basic. +delete CKEDITOR.loadFullCore; + +/** + * Holds references to all editor instances created. The name of the properties + * in this object correspond to instance names, and their values contains the + * {@link CKEDITOR.editor} object representing them. + * @type {Object} + * @example + * alert( CKEDITOR.instances.editor1.name ); // "editor1" + */ +CKEDITOR.instances = {}; + +/** + * The document of the window holding the CKEDITOR object. + * @type {CKEDITOR.dom.document} + * @example + * alert( CKEDITOR.document.getBody().getName() ); // "body" + */ +CKEDITOR.document = new CKEDITOR.dom.document( document ); + +/** + * Adds an editor instance to the global {@link CKEDITOR} object. This function + * is available for internal use mainly. + * @param {CKEDITOR.editor} editor The editor instance to be added. + * @example + */ +CKEDITOR.add = function( editor ) +{ + CKEDITOR.instances[ editor.name ] = editor; + + editor.on( 'focus', function() + { + if ( CKEDITOR.currentInstance != editor ) + { + CKEDITOR.currentInstance = editor; + CKEDITOR.fire( 'currentInstance' ); + } + }); + + editor.on( 'blur', function() + { + if ( CKEDITOR.currentInstance == editor ) + { + CKEDITOR.currentInstance = null; + CKEDITOR.fire( 'currentInstance' ); + } + }); +}; + +/** + * Removes and editor instance from the global {@link CKEDITOR} object. his function + * is available for internal use mainly. + * @param {CKEDITOR.editor} editor The editor instance to be added. + * @example + */ +CKEDITOR.remove = function( editor ) +{ + delete CKEDITOR.instances[ editor.name ]; +}; + +// Load the bootstrap script. +CKEDITOR.loader.load( 'core/_bootstrap' ); // @Packager.RemoveLine + +// Tri-state constants. + +/** + * Used to indicate the ON or ACTIVE state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_ON = 1; + +/** + * Used to indicate the OFF or NON ACTIVE state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_OFF = 2; + +/** + * Used to indicate DISABLED state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_DISABLED = 0; + +/** + * Fired when the CKEDITOR.currentInstance object reference changes. This may + * happen when setting the focus on different editor instances in the page. + * @name CKEDITOR#currentInstance + * @event + */ diff --git a/include/ckeditor/_source/core/ckeditor_base.js b/include/ckeditor/_source/core/ckeditor_base.js new file mode 100644 index 0000000..ecdeb55 --- /dev/null +++ b/include/ckeditor/_source/core/ckeditor_base.js @@ -0,0 +1,193 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Contains the first and essential part of the {@link CKEDITOR} + * object definition. + */ + +// #### Compressed Code +// Must be updated on changes in the script, as well as updated in the +// ckeditor_source.js and ckeditor_basic_source.js files. + +// if(!window.CKEDITOR)window.CKEDITOR=(function(){var a={timestamp:'',version:'3.2.1',rev:'5372',_:{},status:'unloaded',basePath:(function(){var d=window.CKEDITOR_BASEPATH||'';if(!d){var e=document.getElementsByTagName('script');for(var f=0;f=0?'&':'?')+('t=')+this.timestamp;return d;}},b=window.CKEDITOR_GETURL;if(b){var c=a.getUrl;a.getUrl=function(d){return b.call(a,d)||c.call(a,d);};}return a;})(); + +// #### Raw code +// ATTENTION: read the above "Compressed Code" notes when changing this code. + +if ( !window.CKEDITOR ) +{ + /** + * This is the API entry point. The entire CKEditor code runs under this object. + * @name CKEDITOR + * @namespace + * @example + */ + window.CKEDITOR = (function() + { + var CKEDITOR = + /** @lends CKEDITOR */ + { + + /** + * A constant string unique for each release of CKEditor. Its value + * is used, by default, to build the URL for all resources loaded + * by the editor code, guaranteing clean cache results when + * upgrading. + * @type String + * @example + * alert( CKEDITOR.timestamp ); // e.g. '87dm' + */ + // The production implementation contains a fixed timestamp, unique + // for each release, generated by the releaser. + // (Base 36 value of each component of YYMMDDHH - 4 chars total - e.g. 87bm == 08071122) + timestamp : 'A39E', + + /** + * Contains the CKEditor version number. + * @type String + * @example + * alert( CKEDITOR.version ); // e.g. 'CKEditor 3.0 Beta' + */ + version : '3.2.1', + + /** + * Contains the CKEditor revision number. + * Revision number is incremented automatically after each modification of CKEditor source code. + * @type String + * @example + * alert( CKEDITOR.revision ); // e.g. '3975' + */ + revision : '5372', + + /** + * Private object used to hold core stuff. It should not be used out of + * the API code as properties defined here may change at any time + * without notice. + * @private + */ + _ : {}, + + /** + * Indicates the API loading status. The following status are available: + *
    + *
  • unloaded: the API is not yet loaded.
  • + *
  • basic_loaded: the basic API features are available.
  • + *
  • basic_ready: the basic API is ready to load the full core code.
  • + *
  • loading: the full API is being loaded.
  • + *
  • ready: the API can be fully used.
  • + *
+ * @type String + * @example + * if ( CKEDITOR.status == 'ready' ) + * { + * // The API can now be fully used. + * } + */ + status : 'unloaded', + + /** + * Contains the full URL for the CKEditor installation directory. + * It's possible to manually provide the base path by setting a + * global variable named CKEDITOR_BASEPATH. This global variable + * must be set "before" the editor script loading. + * @type String + * @example + * alert( CKEDITOR.basePath ); // "http://www.example.com/ckeditor/" (e.g.) + */ + basePath : (function() + { + // ATTENTION: fixes on this code must be ported to + // var basePath in "core/loader.js". + + // Find out the editor directory path, based on its ")' ); + } + } + + return $ && new CKEDITOR.dom.document( $.contentWindow.document ); + }, + + /** + * Copy all the attributes from one node to the other, kinda like a clone + * skipAttributes is an object with the attributes that must NOT be copied. + * @param {CKEDITOR.dom.element} dest The destination element. + * @param {Object} skipAttributes A dictionary of attributes to skip. + * @example + */ + copyAttributes : function( dest, skipAttributes ) + { + var attributes = this.$.attributes; + skipAttributes = skipAttributes || {}; + + for ( var n = 0 ; n < attributes.length ; n++ ) + { + var attribute = attributes[n]; + + // Lowercase attribute name hard rule is broken for + // some attribute on IE, e.g. CHECKED. + var attrName = attribute.nodeName.toLowerCase(), + attrValue; + + // We can set the type only once, so do it with the proper value, not copying it. + if ( attrName in skipAttributes ) + continue; + + if ( attrName == 'checked' && ( attrValue = this.getAttribute( attrName ) ) ) + dest.setAttribute( attrName, attrValue ); + // IE BUG: value attribute is never specified even if it exists. + else if ( attribute.specified || + ( CKEDITOR.env.ie && attribute.nodeValue && attrName == 'value' ) ) + { + attrValue = this.getAttribute( attrName ); + if ( attrValue === null ) + attrValue = attribute.nodeValue; + + dest.setAttribute( attrName, attrValue ); + } + } + + // The style: + if ( this.$.style.cssText !== '' ) + dest.$.style.cssText = this.$.style.cssText; + }, + + /** + * Changes the tag name of the current element. + * @param {String} newTag The new tag for the element. + */ + renameNode : function( newTag ) + { + // If it's already correct exit here. + if ( this.getName() == newTag ) + return; + + var doc = this.getDocument(); + + // Create the new node. + var newNode = new CKEDITOR.dom.element( newTag, doc ); + + // Copy all attributes. + this.copyAttributes( newNode ); + + // Move children to the new node. + this.moveChildren( newNode ); + + // Replace the node. + this.$.parentNode.replaceChild( newNode.$, this.$ ); + newNode.$._cke_expando = this.$._cke_expando; + this.$ = newNode.$; + }, + + /** + * Gets a DOM tree descendant under the current node. + * @param {Array|Number} indices The child index or array of child indices under the node. + * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist. + * @example + * var strong = p.getChild(0); + */ + getChild : function( indices ) + { + var rawNode = this.$; + + if ( !indices.slice ) + rawNode = rawNode.childNodes[ indices ]; + else + { + while ( indices.length > 0 && rawNode ) + rawNode = rawNode.childNodes[ indices.shift() ]; + } + + return rawNode ? new CKEDITOR.dom.node( rawNode ) : null; + }, + + getChildCount : function() + { + return this.$.childNodes.length; + }, + + disableContextMenu : function() + { + this.on( 'contextmenu', function( event ) + { + // Cancel the browser context menu. + if ( !event.data.getTarget().hasClass( 'cke_enable_context_menu' ) ) + event.data.preventDefault(); + } ); + } + }); diff --git a/include/ckeditor/_source/core/dom/elementpath.js b/include/ckeditor/_source/core/dom/elementpath.js new file mode 100644 index 0000000..3b41c67 --- /dev/null +++ b/include/ckeditor/_source/core/dom/elementpath.js @@ -0,0 +1,104 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + // Elements that may be considered the "Block boundary" in an element path. + var pathBlockElements = { address:1,blockquote:1,dl:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1,li:1,dt:1,dd:1 }; + + // Elements that may be considered the "Block limit" in an element path. + var pathBlockLimitElements = { body:1,div:1,table:1,tbody:1,tr:1,td:1,th:1,caption:1,form:1 }; + + // Check if an element contains any block element. + var checkHasBlock = function( element ) + { + var childNodes = element.getChildren(); + + for ( var i = 0, count = childNodes.count() ; i < count ; i++ ) + { + var child = childNodes.getItem( i ); + + if ( child.type == CKEDITOR.NODE_ELEMENT && CKEDITOR.dtd.$block[ child.getName() ] ) + return true; + } + + return false; + }; + + CKEDITOR.dom.elementPath = function( lastNode ) + { + var block = null; + var blockLimit = null; + var elements = []; + + var e = lastNode; + + while ( e ) + { + if ( e.type == CKEDITOR.NODE_ELEMENT ) + { + if ( !this.lastElement ) + this.lastElement = e; + + var elementName = e.getName(); + if ( CKEDITOR.env.ie && e.$.scopeName != 'HTML' ) + elementName = e.$.scopeName.toLowerCase() + ':' + elementName; + + if ( !blockLimit ) + { + if ( !block && pathBlockElements[ elementName ] ) + block = e; + + if ( pathBlockLimitElements[ elementName ] ) + { + // DIV is considered the Block, if no block is available (#525) + // and if it doesn't contain other blocks. + if ( !block && elementName == 'div' && !checkHasBlock( e ) ) + block = e; + else + blockLimit = e; + } + } + + elements.push( e ); + + if ( elementName == 'body' ) + break; + } + e = e.getParent(); + } + + this.block = block; + this.blockLimit = blockLimit; + this.elements = elements; + }; +})(); + +CKEDITOR.dom.elementPath.prototype = +{ + /** + * Compares this element path with another one. + * @param {CKEDITOR.dom.elementPath} otherPath The elementPath object to be + * compared with this one. + * @returns {Boolean} "true" if the paths are equal, containing the same + * number of elements and the same elements in the same order. + */ + compare : function( otherPath ) + { + var thisElements = this.elements; + var otherElements = otherPath && otherPath.elements; + + if ( !otherElements || thisElements.length != otherElements.length ) + return false; + + for ( var i = 0 ; i < thisElements.length ; i++ ) + { + if ( !thisElements[ i ].equals( otherElements[ i ] ) ) + return false; + } + + return true; + } +}; diff --git a/include/ckeditor/_source/core/dom/event.js b/include/ckeditor/_source/core/dom/event.js new file mode 100644 index 0000000..24b75d0 --- /dev/null +++ b/include/ckeditor/_source/core/dom/event.js @@ -0,0 +1,142 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.event} class, which + * represents the a native DOM event object. + */ + +/** + * Represents a native DOM event object. + * @constructor + * @param {Object} domEvent A native DOM event object. + * @example + */ +CKEDITOR.dom.event = function( domEvent ) +{ + /** + * The native DOM event object represented by this class instance. + * @type Object + * @example + */ + this.$ = domEvent; +}; + +CKEDITOR.dom.event.prototype = +{ + /** + * Gets the key code associated to the event. + * @returns {Number} The key code. + * @example + * alert( event.getKey() ); "65" is "a" has been pressed + */ + getKey : function() + { + return this.$.keyCode || this.$.which; + }, + + /** + * Gets a number represeting the combination of the keys pressed during the + * event. It is the sum with the current key code and the {@link CKEDITOR.CTRL}, + * {@link CKEDITOR.SHIFT} and {@link CKEDITOR.ALT} constants. + * @returns {Number} The number representing the keys combination. + * @example + * alert( event.getKeystroke() == 65 ); // "a" key + * alert( event.getKeystroke() == CKEDITOR.CTRL + 65 ); // CTRL + "a" key + * alert( event.getKeystroke() == CKEDITOR.CTRL + CKEDITOR.SHIFT + 65 ); // CTRL + SHIFT + "a" key + */ + getKeystroke : function() + { + var keystroke = this.getKey(); + + if ( this.$.ctrlKey || this.$.metaKey ) + keystroke += CKEDITOR.CTRL; + + if ( this.$.shiftKey ) + keystroke += CKEDITOR.SHIFT; + + if ( this.$.altKey ) + keystroke += CKEDITOR.ALT; + + return keystroke; + }, + + /** + * Prevents the original behavior of the event to happen. It can optionally + * stop propagating the event in the event chain. + * @param {Boolean} [stopPropagation] Stop propagating this event in the + * event chain. + * @example + * var element = CKEDITOR.document.getById( 'myElement' ); + * element.on( 'click', function( ev ) + * { + * // The DOM event object is passed by the "data" property. + * var domEvent = ev.data; + * // Prevent the click to chave any effect in the element. + * domEvent.preventDefault(); + * }); + */ + preventDefault : function( stopPropagation ) + { + var $ = this.$; + if ( $.preventDefault ) + $.preventDefault(); + else + $.returnValue = false; + + if ( stopPropagation ) + this.stopPropagation(); + }, + + stopPropagation : function() + { + var $ = this.$; + if ( $.stopPropagation ) + $.stopPropagation(); + else + $.cancelBubble = true; + }, + + /** + * Returns the DOM node where the event was targeted to. + * @returns {CKEDITOR.dom.node} The target DOM node. + * @example + * var element = CKEDITOR.document.getById( 'myElement' ); + * element.on( 'click', function( ev ) + * { + * // The DOM event object is passed by the "data" property. + * var domEvent = ev.data; + * // Add a CSS class to the event target. + * domEvent.getTarget().addClass( 'clicked' ); + * }); + */ + + getTarget : function() + { + var rawNode = this.$.target || this.$.srcElement; + return rawNode ? new CKEDITOR.dom.node( rawNode ) : null; + } +}; + +/** + * CTRL key (1000). + * @constant + * @example + */ +CKEDITOR.CTRL = 1000; + +/** + * SHIFT key (2000). + * @constant + * @example + */ +CKEDITOR.SHIFT = 2000; + +/** + * ALT key (4000). + * @constant + * @example + */ +CKEDITOR.ALT = 4000; diff --git a/include/ckeditor/_source/core/dom/node.js b/include/ckeditor/_source/core/dom/node.js new file mode 100644 index 0000000..d9a76ee --- /dev/null +++ b/include/ckeditor/_source/core/dom/node.js @@ -0,0 +1,662 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.node} class, which is the base + * class for classes that represent DOM nodes. + */ + +/** + * Base class for classes representing DOM nodes. This constructor may return + * and instance of classes that inherits this class, like + * {@link CKEDITOR.dom.element} or {@link CKEDITOR.dom.text}. + * @augments CKEDITOR.dom.domObject + * @param {Object} domNode A native DOM node. + * @constructor + * @see CKEDITOR.dom.element + * @see CKEDITOR.dom.text + * @example + */ +CKEDITOR.dom.node = function( domNode ) +{ + if ( domNode ) + { + switch ( domNode.nodeType ) + { + // Safari don't consider document as element node type. (#3389) + case CKEDITOR.NODE_DOCUMENT : + return new CKEDITOR.dom.document( domNode ); + + case CKEDITOR.NODE_ELEMENT : + return new CKEDITOR.dom.element( domNode ); + + case CKEDITOR.NODE_TEXT : + return new CKEDITOR.dom.text( domNode ); + } + + // Call the base constructor. + CKEDITOR.dom.domObject.call( this, domNode ); + } + + return this; +}; + +CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject(); + +/** + * Element node type. + * @constant + * @example + */ +CKEDITOR.NODE_ELEMENT = 1; + +/** + * Document node type. + * @constant + * @example + */ +CKEDITOR.NODE_DOCUMENT = 9; + +/** + * Text node type. + * @constant + * @example + */ +CKEDITOR.NODE_TEXT = 3; + +/** + * Comment node type. + * @constant + * @example + */ +CKEDITOR.NODE_COMMENT = 8; + +CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11; + +CKEDITOR.POSITION_IDENTICAL = 0; +CKEDITOR.POSITION_DISCONNECTED = 1; +CKEDITOR.POSITION_FOLLOWING = 2; +CKEDITOR.POSITION_PRECEDING = 4; +CKEDITOR.POSITION_IS_CONTAINED = 8; +CKEDITOR.POSITION_CONTAINS = 16; + +CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, + /** @lends CKEDITOR.dom.node.prototype */ + { + /** + * Makes this node child of another element. + * @param {CKEDITOR.dom.element} element The target element to which append + * this node. + * @returns {CKEDITOR.dom.element} The target element. + * @example + * var p = new CKEDITOR.dom.element( 'p' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.appendTo( p ); + * + * // result: "<p><strong></strong></p>" + */ + appendTo : function( element, toStart ) + { + element.append( this, toStart ); + return element; + }, + + clone : function( includeChildren, cloneId ) + { + var $clone = this.$.cloneNode( includeChildren ); + + if ( !cloneId ) + { + var removeIds = function( node ) + { + if ( node.nodeType != CKEDITOR.NODE_ELEMENT ) + return; + + node.removeAttribute( 'id', false ) ; + node.removeAttribute( '_cke_expando', false ) ; + + var childs = node.childNodes; + for ( var i=0 ; i < childs.length ; i++ ) + removeIds( childs[ i ] ); + }; + + // The "id" attribute should never be cloned to avoid duplication. + removeIds( $clone ); + } + + return new CKEDITOR.dom.node( $clone ); + }, + + hasPrevious : function() + { + return !!this.$.previousSibling; + }, + + hasNext : function() + { + return !!this.$.nextSibling; + }, + + /** + * Inserts this element after a node. + * @param {CKEDITOR.dom.node} node The that will preceed this element. + * @returns {CKEDITOR.dom.node} The node preceeding this one after + * insertion. + * @example + * var em = new CKEDITOR.dom.element( 'em' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.insertAfter( em ); + * + * // result: "<em></em><strong></strong>" + */ + insertAfter : function( node ) + { + node.$.parentNode.insertBefore( this.$, node.$.nextSibling ); + return node; + }, + + /** + * Inserts this element before a node. + * @param {CKEDITOR.dom.node} node The that will be after this element. + * @returns {CKEDITOR.dom.node} The node being inserted. + * @example + * var em = new CKEDITOR.dom.element( 'em' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.insertBefore( em ); + * + * // result: "<strong></strong><em></em>" + */ + insertBefore : function( node ) + { + node.$.parentNode.insertBefore( this.$, node.$ ); + return node; + }, + + insertBeforeMe : function( node ) + { + this.$.parentNode.insertBefore( node.$, this.$ ); + return node; + }, + + /** + * Retrieves a uniquely identifiable tree address for this node. + * The tree address returns is an array of integers, with each integer + * indicating a child index of a DOM node, starting from + * document.documentElement. + * + * For example, assuming is the second child from ( + * being the first), and we'd like to address the third child under the + * fourth child of body, the tree address returned would be: + * [1, 3, 2] + * + * The tree address cannot be used for finding back the DOM tree node once + * the DOM tree structure has been modified. + */ + getAddress : function( normalized ) + { + var address = []; + var $documentElement = this.getDocument().$.documentElement; + var node = this.$; + + while ( node && node != $documentElement ) + { + var parentNode = node.parentNode; + var currentIndex = -1; + + if ( parentNode ) + { + for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) + { + var candidate = parentNode.childNodes[i]; + + if ( normalized && + candidate.nodeType == 3 && + candidate.previousSibling && + candidate.previousSibling.nodeType == 3 ) + { + continue; + } + + currentIndex++; + + if ( candidate == node ) + break; + } + + address.unshift( currentIndex ); + } + + node = parentNode; + } + + return address; + }, + + /** + * Gets the document containing this element. + * @returns {CKEDITOR.dom.document} The document. + * @example + * var element = CKEDITOR.document.getById( 'example' ); + * alert( element.getDocument().equals( CKEDITOR.document ) ); // "true" + */ + getDocument : function() + { + var document = new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument ); + + return ( + this.getDocument = function() + { + return document; + })(); + }, + + getIndex : function() + { + var $ = this.$; + + var currentNode = $.parentNode && $.parentNode.firstChild; + var currentIndex = -1; + + while ( currentNode ) + { + currentIndex++; + + if ( currentNode == $ ) + return currentIndex; + + currentNode = currentNode.nextSibling; + } + + return -1; + }, + + getNextSourceNode : function( startFromSibling, nodeType, guard ) + { + // If "guard" is a node, transform it in a function. + if ( guard && !guard.call ) + { + var guardNode = guard; + guard = function( node ) + { + return !node.equals( guardNode ); + }; + } + + var node = ( !startFromSibling && this.getFirst && this.getFirst() ), + parent; + + // Guarding when we're skipping the current element( no children or 'startFromSibling' ). + // send the 'moving out' signal even we don't actually dive into. + if ( !node ) + { + if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) + return null; + node = this.getNext(); + } + + while ( !node && ( parent = ( parent || this ).getParent() ) ) + { + // The guard check sends the "true" paramenter to indicate that + // we are moving "out" of the element. + if ( guard && guard( parent, true ) === false ) + return null; + + node = parent.getNext(); + } + + if ( !node ) + return null; + + if ( guard && guard( node ) === false ) + return null; + + if ( nodeType && nodeType != node.type ) + return node.getNextSourceNode( false, nodeType, guard ); + + return node; + }, + + getPreviousSourceNode : function( startFromSibling, nodeType, guard ) + { + if ( guard && !guard.call ) + { + var guardNode = guard; + guard = function( node ) + { + return !node.equals( guardNode ); + }; + } + + var node = ( !startFromSibling && this.getLast && this.getLast() ), + parent; + + // Guarding when we're skipping the current element( no children or 'startFromSibling' ). + // send the 'moving out' signal even we don't actually dive into. + if ( !node ) + { + if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) + return null; + node = this.getPrevious(); + } + + while ( !node && ( parent = ( parent || this ).getParent() ) ) + { + // The guard check sends the "true" paramenter to indicate that + // we are moving "out" of the element. + if ( guard && guard( parent, true ) === false ) + return null; + + node = parent.getPrevious(); + } + + if ( !node ) + return null; + + if ( guard && guard( node ) === false ) + return null; + + if ( nodeType && node.type != nodeType ) + return node.getPreviousSourceNode( false, nodeType, guard ); + + return node; + }, + + getPrevious : function( evaluator ) + { + var previous = this.$, retval; + do + { + previous = previous.previousSibling; + retval = previous && new CKEDITOR.dom.node( previous ); + } + while ( retval && evaluator && !evaluator( retval ) ) + return retval; + }, + + /** + * Gets the node that follows this element in its parent's child list. + * @param {Function} evaluator Filtering the result node. + * @returns {CKEDITOR.dom.node} The next node or null if not available. + * @example + * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b> <i>next</i></div>' ); + * var first = element.getFirst().getNext(); + * alert( first.getName() ); // "i" + */ + getNext : function( evaluator ) + { + var next = this.$, retval; + do + { + next = next.nextSibling; + retval = next && new CKEDITOR.dom.node( next ); + } + while ( retval && evaluator && !evaluator( retval ) ) + return retval; + }, + + /** + * Gets the parent element for this node. + * @returns {CKEDITOR.dom.element} The parent element. + * @example + * var node = editor.document.getBody().getFirst(); + * var parent = node.getParent(); + * alert( node.getName() ); // "body" + */ + getParent : function() + { + var parent = this.$.parentNode; + return ( parent && parent.nodeType == 1 ) ? new CKEDITOR.dom.node( parent ) : null; + }, + + getParents : function( closerFirst ) + { + var node = this; + var parents = []; + + do + { + parents[ closerFirst ? 'push' : 'unshift' ]( node ); + } + while ( ( node = node.getParent() ) ) + + return parents; + }, + + getCommonAncestor : function( node ) + { + if ( node.equals( this ) ) + return this; + + if ( node.contains && node.contains( this ) ) + return node; + + var start = this.contains ? this : this.getParent(); + + do + { + if ( start.contains( node ) ) + return start; + } + while ( ( start = start.getParent() ) ); + + return null; + }, + + getPosition : function( otherNode ) + { + var $ = this.$; + var $other = otherNode.$; + + if ( $.compareDocumentPosition ) + return $.compareDocumentPosition( $other ); + + // IE and Safari have no support for compareDocumentPosition. + + if ( $ == $other ) + return CKEDITOR.POSITION_IDENTICAL; + + // Only element nodes support contains and sourceIndex. + if ( this.type == CKEDITOR.NODE_ELEMENT && otherNode.type == CKEDITOR.NODE_ELEMENT ) + { + if ( $.contains ) + { + if ( $.contains( $other ) ) + return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING; + + if ( $other.contains( $ ) ) + return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; + } + + if ( 'sourceIndex' in $ ) + { + return ( $.sourceIndex < 0 || $other.sourceIndex < 0 ) ? CKEDITOR.POSITION_DISCONNECTED : + ( $.sourceIndex < $other.sourceIndex ) ? CKEDITOR.POSITION_PRECEDING : + CKEDITOR.POSITION_FOLLOWING; + } + } + + // For nodes that don't support compareDocumentPosition, contains + // or sourceIndex, their "address" is compared. + + var addressOfThis = this.getAddress(), + addressOfOther = otherNode.getAddress(), + minLevel = Math.min( addressOfThis.length, addressOfOther.length ); + + // Determinate preceed/follow relationship. + for ( var i = 0 ; i <= minLevel - 1 ; i++ ) + { + if ( addressOfThis[ i ] != addressOfOther[ i ] ) + { + if ( i < minLevel ) + { + return addressOfThis[ i ] < addressOfOther[ i ] ? + CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING; + } + break; + } + } + + // Determinate contains/contained relationship. + return ( addressOfThis.length < addressOfOther.length ) ? + CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING : + CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; + }, + + /** + * Gets the closes ancestor node of a specified node name. + * @param {String} name Node name of ancestor node. + * @param {Boolean} includeSelf (Optional) Whether to include the current + * node in the calculation or not. + * @returns {CKEDITOR.dom.node} Ancestor node. + */ + getAscendant : function( name, includeSelf ) + { + var $ = this.$; + + if ( !includeSelf ) + $ = $.parentNode; + + while ( $ ) + { + if ( $.nodeName && $.nodeName.toLowerCase() == name ) + return new CKEDITOR.dom.node( $ ); + + $ = $.parentNode; + } + return null; + }, + + hasAscendant : function( name, includeSelf ) + { + var $ = this.$; + + if ( !includeSelf ) + $ = $.parentNode; + + while ( $ ) + { + if ( $.nodeName && $.nodeName.toLowerCase() == name ) + return true; + + $ = $.parentNode; + } + return false; + }, + + move : function( target, toStart ) + { + target.append( this.remove(), toStart ); + }, + + /** + * Removes this node from the document DOM. + * @param {Boolean} [preserveChildren] Indicates that the children + * elements must remain in the document, removing only the outer + * tags. + * @example + * var element = CKEDITOR.dom.element.getById( 'MyElement' ); + * element.remove(); + */ + remove : function( preserveChildren ) + { + var $ = this.$; + var parent = $.parentNode; + + if ( parent ) + { + if ( preserveChildren ) + { + // Move all children before the node. + for ( var child ; ( child = $.firstChild ) ; ) + { + parent.insertBefore( $.removeChild( child ), $ ); + } + } + + parent.removeChild( $ ); + } + + return this; + }, + + replace : function( nodeToReplace ) + { + this.insertBefore( nodeToReplace ); + nodeToReplace.remove(); + }, + + trim : function() + { + this.ltrim(); + this.rtrim(); + }, + + ltrim : function() + { + var child; + while ( this.getFirst && ( child = this.getFirst() ) ) + { + if ( child.type == CKEDITOR.NODE_TEXT ) + { + var trimmed = CKEDITOR.tools.ltrim( child.getText() ), + originalLength = child.getLength(); + + if ( !trimmed ) + { + child.remove(); + continue; + } + else if ( trimmed.length < originalLength ) + { + child.split( originalLength - trimmed.length ); + + // IE BUG: child.remove() may raise JavaScript errors here. (#81) + this.$.removeChild( this.$.firstChild ); + } + } + break; + } + }, + + rtrim : function() + { + var child; + while ( this.getLast && ( child = this.getLast() ) ) + { + if ( child.type == CKEDITOR.NODE_TEXT ) + { + var trimmed = CKEDITOR.tools.rtrim( child.getText() ), + originalLength = child.getLength(); + + if ( !trimmed ) + { + child.remove(); + continue; + } + else if ( trimmed.length < originalLength ) + { + child.split( trimmed.length ); + + // IE BUG: child.getNext().remove() may raise JavaScript errors here. + // (#81) + this.$.lastChild.parentNode.removeChild( this.$.lastChild ); + } + } + break; + } + + if ( !CKEDITOR.env.ie && !CKEDITOR.env.opera ) + { + child = this.$.lastChild; + + if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) + { + // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324). + child.parentNode.removeChild( child ) ; + } + } + } + } +); diff --git a/include/ckeditor/_source/core/dom/nodelist.js b/include/ckeditor/_source/core/dom/nodelist.js new file mode 100644 index 0000000..155f0ad --- /dev/null +++ b/include/ckeditor/_source/core/dom/nodelist.js @@ -0,0 +1,23 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dom.nodeList = function( nativeList ) +{ + this.$ = nativeList; +}; + +CKEDITOR.dom.nodeList.prototype = +{ + count : function() + { + return this.$.length; + }, + + getItem : function( index ) + { + var $node = this.$[ index ]; + return $node ? new CKEDITOR.dom.node( $node ) : null; + } +}; diff --git a/include/ckeditor/_source/core/dom/range.js b/include/ckeditor/_source/core/dom/range.js new file mode 100644 index 0000000..919b781 --- /dev/null +++ b/include/ckeditor/_source/core/dom/range.js @@ -0,0 +1,1836 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dom.range = function( document ) +{ + this.startContainer = null; + this.startOffset = null; + this.endContainer = null; + this.endOffset = null; + this.collapsed = true; + + this.document = document; +}; + +(function() +{ + // Updates the "collapsed" property for the given range object. + var updateCollapsed = function( range ) + { + range.collapsed = ( + range.startContainer && + range.endContainer && + range.startContainer.equals( range.endContainer ) && + range.startOffset == range.endOffset ); + }; + + // This is a shared function used to delete, extract and clone the range + // contents. + // V2 + var execContentsAction = function( range, action, docFrag ) + { + range.optimizeBookmark(); + + var startNode = range.startContainer; + var endNode = range.endContainer; + + var startOffset = range.startOffset; + var endOffset = range.endOffset; + + var removeStartNode; + var removeEndNode; + + // For text containers, we must simply split the node and point to the + // second part. The removal will be handled by the rest of the code . + if ( endNode.type == CKEDITOR.NODE_TEXT ) + endNode = endNode.split( endOffset ); + else + { + // If the end container has children and the offset is pointing + // to a child, then we should start from it. + if ( endNode.getChildCount() > 0 ) + { + // If the offset points after the last node. + if ( endOffset >= endNode.getChildCount() ) + { + // Let's create a temporary node and mark it for removal. + endNode = endNode.append( range.document.createText( '' ) ); + removeEndNode = true; + } + else + endNode = endNode.getChild( endOffset ); + } + } + + // For text containers, we must simply split the node. The removal will + // be handled by the rest of the code . + if ( startNode.type == CKEDITOR.NODE_TEXT ) + { + startNode.split( startOffset ); + + // In cases the end node is the same as the start node, the above + // splitting will also split the end, so me must move the end to + // the second part of the split. + if ( startNode.equals( endNode ) ) + endNode = startNode.getNext(); + } + else + { + // If the start container has children and the offset is pointing + // to a child, then we should start from its previous sibling. + + // If the offset points to the first node, we don't have a + // sibling, so let's use the first one, but mark it for removal. + if ( !startOffset ) + { + // Let's create a temporary node and mark it for removal. + startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) ); + removeStartNode = true; + } + else if ( startOffset >= startNode.getChildCount() ) + { + // Let's create a temporary node and mark it for removal. + startNode = startNode.append( range.document.createText( '' ) ); + removeStartNode = true; + } + else + startNode = startNode.getChild( startOffset ).getPrevious(); + } + + // Get the parent nodes tree for the start and end boundaries. + var startParents = startNode.getParents(); + var endParents = endNode.getParents(); + + // Compare them, to find the top most siblings. + var i, topStart, topEnd; + + for ( i = 0 ; i < startParents.length ; i++ ) + { + topStart = startParents[ i ]; + topEnd = endParents[ i ]; + + // The compared nodes will match until we find the top most + // siblings (different nodes that have the same parent). + // "i" will hold the index in the parents array for the top + // most element. + if ( !topStart.equals( topEnd ) ) + break; + } + + var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling; + + // Remove all successive sibling nodes for every node in the + // startParents tree. + for ( var j = i ; j < startParents.length ; j++ ) + { + levelStartNode = startParents[j]; + + // For Extract and Clone, we must clone this level. + if ( clone && !levelStartNode.equals( startNode ) ) // action = 0 = Delete + levelClone = clone.append( levelStartNode.clone() ); + + currentNode = levelStartNode.getNext(); + + while ( currentNode ) + { + // Stop processing when the current node matches a node in the + // endParents tree or if it is the endNode. + if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) ) + break; + + // Cache the next sibling. + currentSibling = currentNode.getNext(); + + // If cloning, just clone it. + if ( action == 2 ) // 2 = Clone + clone.append( currentNode.clone( true ) ); + else + { + // Both Delete and Extract will remove the node. + currentNode.remove(); + + // When Extracting, move the removed node to the docFrag. + if ( action == 1 ) // 1 = Extract + clone.append( currentNode ); + } + + currentNode = currentSibling; + } + + if ( clone ) + clone = levelClone; + } + + clone = docFrag; + + // Remove all previous sibling nodes for every node in the + // endParents tree. + for ( var k = i ; k < endParents.length ; k++ ) + { + levelStartNode = endParents[ k ]; + + // For Extract and Clone, we must clone this level. + if ( action > 0 && !levelStartNode.equals( endNode ) ) // action = 0 = Delete + levelClone = clone.append( levelStartNode.clone() ); + + // The processing of siblings may have already been done by the parent. + if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode ) + { + currentNode = levelStartNode.getPrevious(); + + while ( currentNode ) + { + // Stop processing when the current node matches a node in the + // startParents tree or if it is the startNode. + if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) ) + break; + + // Cache the next sibling. + currentSibling = currentNode.getPrevious(); + + // If cloning, just clone it. + if ( action == 2 ) // 2 = Clone + clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ; + else + { + // Both Delete and Extract will remove the node. + currentNode.remove(); + + // When Extracting, mode the removed node to the docFrag. + if ( action == 1 ) // 1 = Extract + clone.$.insertBefore( currentNode.$, clone.$.firstChild ); + } + + currentNode = currentSibling; + } + } + + if ( clone ) + clone = levelClone; + } + + if ( action == 2 ) // 2 = Clone. + { + // No changes in the DOM should be done, so fix the split text (if any). + + var startTextNode = range.startContainer; + if ( startTextNode.type == CKEDITOR.NODE_TEXT ) + { + startTextNode.$.data += startTextNode.$.nextSibling.data; + startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling ); + } + + var endTextNode = range.endContainer; + if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling ) + { + endTextNode.$.data += endTextNode.$.nextSibling.data; + endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling ); + } + } + else + { + // Collapse the range. + + // If a node has been partially selected, collapse the range between + // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs). + if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) ) + { + var endIndex = topEnd.getIndex(); + + // If the start node is to be removed, we must correct the + // index to reflect the removal. + if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode ) + endIndex--; + + range.setStart( topEnd.getParent(), endIndex ); + } + + // Collapse it to the start. + range.collapse( true ); + } + + // Cleanup any marked node. + if ( removeStartNode ) + startNode.remove(); + + if ( removeEndNode && endNode.$.parentNode ) + endNode.remove(); + }; + + var inlineChildReqElements = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 }; + + // Creates the appropriate node evaluator for the dom walker used inside + // check(Start|End)OfBlock. + function getCheckStartEndBlockEvalFunction( isStart ) + { + var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true ); + return function( node ) + { + // First ignore bookmark nodes. + if ( bookmarkEvaluator( node ) ) + return true; + + if ( node.type == CKEDITOR.NODE_TEXT ) + { + // If there's any visible text, then we're not at the start. + if ( CKEDITOR.tools.trim( node.getText() ).length ) + return false; + } + else if ( node.type == CKEDITOR.NODE_ELEMENT ) + { + // If there are non-empty inline elements (e.g. ), then we're not + // at the start. + if ( !inlineChildReqElements[ node.getName() ] ) + { + // If we're working at the end-of-block, forgive the first
in non-IE + // browsers. + if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr ) + hadBr = true; + else + return false; + } + } + return true; + }; + } + + // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any + // text node and non-empty elements unless it's being bookmark text. + function elementBoundaryEval( node ) + { + // Reject any text node unless it's being bookmark + // OR it's spaces. (#3883) + return node.type != CKEDITOR.NODE_TEXT + && node.getName() in CKEDITOR.dtd.$removeEmpty + || !CKEDITOR.tools.trim( node.getText() ) + || node.getParent().hasAttribute( '_fck_bookmark' ); + } + + var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(), + bookmarkEval = new CKEDITOR.dom.walker.bookmark(); + + function nonWhitespaceOrBookmarkEval( node ) + { + // Whitespaces and bookmark nodes are to be ignored. + return !whitespaceEval( node ) && !bookmarkEval( node ); + } + + CKEDITOR.dom.range.prototype = + { + clone : function() + { + var clone = new CKEDITOR.dom.range( this.document ); + + clone.startContainer = this.startContainer; + clone.startOffset = this.startOffset; + clone.endContainer = this.endContainer; + clone.endOffset = this.endOffset; + clone.collapsed = this.collapsed; + + return clone; + }, + + collapse : function( toStart ) + { + if ( toStart ) + { + this.endContainer = this.startContainer; + this.endOffset = this.startOffset; + } + else + { + this.startContainer = this.endContainer; + this.startOffset = this.endOffset; + } + + this.collapsed = true; + }, + + // The selection may be lost when cloning (due to the splitText() call). + cloneContents : function() + { + var docFrag = new CKEDITOR.dom.documentFragment( this.document ); + + if ( !this.collapsed ) + execContentsAction( this, 2, docFrag ); + + return docFrag; + }, + + deleteContents : function() + { + if ( this.collapsed ) + return; + + execContentsAction( this, 0 ); + }, + + extractContents : function() + { + var docFrag = new CKEDITOR.dom.documentFragment( this.document ); + + if ( !this.collapsed ) + execContentsAction( this, 1, docFrag ); + + return docFrag; + }, + + /** + * Creates a bookmark object, which can be later used to restore the + * range by using the moveToBookmark function. + * This is an "intrusive" way to create a bookmark. It includes tags + * in the range boundaries. The advantage of it is that it is possible to + * handle DOM mutations when moving back to the bookmark. + * Attention: the inclusion of nodes in the DOM is a design choice and + * should not be changed as there are other points in the code that may be + * using those nodes to perform operations. See GetBookmarkNode. + * @param {Boolean} [serializable] Indicates that the bookmark nodes + * must contain ids, which can be used to restore the range even + * when these nodes suffer mutations (like a clonation or innerHTML + * change). + * @returns {Object} And object representing a bookmark. + */ + createBookmark : function( serializable ) + { + var startNode, endNode; + var baseId; + var clone; + + startNode = this.document.createElement( 'span' ); + startNode.setAttribute( '_fck_bookmark', 1 ); + startNode.setStyle( 'display', 'none' ); + + // For IE, it must have something inside, otherwise it may be + // removed during DOM operations. + startNode.setHtml( ' ' ); + + if ( serializable ) + { + baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber(); + startNode.setAttribute( 'id', baseId + 'S' ); + } + + // If collapsed, the endNode will not be created. + if ( !this.collapsed ) + { + endNode = startNode.clone(); + endNode.setHtml( ' ' ); + + if ( serializable ) + endNode.setAttribute( 'id', baseId + 'E' ); + + clone = this.clone(); + clone.collapse(); + clone.insertNode( endNode ); + } + + clone = this.clone(); + clone.collapse( true ); + clone.insertNode( startNode ); + + // Update the range position. + if ( endNode ) + { + this.setStartAfter( startNode ); + this.setEndBefore( endNode ); + } + else + this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END ); + + return { + startNode : serializable ? baseId + 'S' : startNode, + endNode : serializable ? baseId + 'E' : endNode, + serializable : serializable + }; + }, + + /** + * Creates a "non intrusive" and "mutation sensible" bookmark. This + * kind of bookmark should be used only when the DOM is supposed to + * remain stable after its creation. + * @param {Boolean} [normalized] Indicates that the bookmark must + * normalized. When normalized, the successive text nodes are + * considered a single node. To sucessful load a normalized + * bookmark, the DOM tree must be also normalized before calling + * moveToBookmark. + * @returns {Object} An object representing the bookmark. + */ + createBookmark2 : function( normalized ) + { + var startContainer = this.startContainer, + endContainer = this.endContainer; + + var startOffset = this.startOffset, + endOffset = this.endOffset; + + var child, previous; + + // If there is no range then get out of here. + // It happens on initial load in Safari #962 and if the editor it's + // hidden also in Firefox + if ( !startContainer || !endContainer ) + return { start : 0, end : 0 }; + + if ( normalized ) + { + // Find out if the start is pointing to a text node that will + // be normalized. + if ( startContainer.type == CKEDITOR.NODE_ELEMENT ) + { + child = startContainer.getChild( startOffset ); + + // In this case, move the start information to that text + // node. + if ( child && child.type == CKEDITOR.NODE_TEXT + && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT ) + { + startContainer = child; + startOffset = 0; + } + } + + // Normalize the start. + while ( startContainer.type == CKEDITOR.NODE_TEXT + && ( previous = startContainer.getPrevious() ) + && previous.type == CKEDITOR.NODE_TEXT ) + { + startContainer = previous; + startOffset += previous.getLength(); + } + + // Process the end only if not normalized. + if ( !this.isCollapsed ) + { + // Find out if the start is pointing to a text node that + // will be normalized. + if ( endContainer.type == CKEDITOR.NODE_ELEMENT ) + { + child = endContainer.getChild( endOffset ); + + // In this case, move the start information to that + // text node. + if ( child && child.type == CKEDITOR.NODE_TEXT + && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT ) + { + endContainer = child; + endOffset = 0; + } + } + + // Normalize the end. + while ( endContainer.type == CKEDITOR.NODE_TEXT + && ( previous = endContainer.getPrevious() ) + && previous.type == CKEDITOR.NODE_TEXT ) + { + endContainer = previous; + endOffset += previous.getLength(); + } + } + } + + return { + start : startContainer.getAddress( normalized ), + end : this.isCollapsed ? null : endContainer.getAddress( normalized ), + startOffset : startOffset, + endOffset : endOffset, + normalized : normalized, + is2 : true // It's a createBookmark2 bookmark. + }; + }, + + moveToBookmark : function( bookmark ) + { + if ( bookmark.is2 ) // Created with createBookmark2(). + { + // Get the start information. + var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ), + startOffset = bookmark.startOffset; + + // Get the end information. + var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ), + endOffset = bookmark.endOffset; + + // Set the start boundary. + this.setStart( startContainer, startOffset ); + + // Set the end boundary. If not available, collapse it. + if ( endContainer ) + this.setEnd( endContainer, endOffset ); + else + this.collapse( true ); + } + else // Created with createBookmark(). + { + var serializable = bookmark.serializable, + startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode, + endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode; + + // Set the range start at the bookmark start node position. + this.setStartBefore( startNode ); + + // Remove it, because it may interfere in the setEndBefore call. + startNode.remove(); + + // Set the range end at the bookmark end node position, or simply + // collapse it if it is not available. + if ( endNode ) + { + this.setEndBefore( endNode ); + endNode.remove(); + } + else + this.collapse( true ); + } + }, + + getBoundaryNodes : function() + { + var startNode = this.startContainer, + endNode = this.endContainer, + startOffset = this.startOffset, + endOffset = this.endOffset, + childCount; + + if ( startNode.type == CKEDITOR.NODE_ELEMENT ) + { + childCount = startNode.getChildCount(); + if ( childCount > startOffset ) + startNode = startNode.getChild( startOffset ); + else if ( childCount < 1 ) + startNode = startNode.getPreviousSourceNode(); + else // startOffset > childCount but childCount is not 0 + { + // Try to take the node just after the current position. + startNode = startNode.$; + while ( startNode.lastChild ) + startNode = startNode.lastChild; + startNode = new CKEDITOR.dom.node( startNode ); + + // Normally we should take the next node in DFS order. But it + // is also possible that we've already reached the end of + // document. + startNode = startNode.getNextSourceNode() || startNode; + } + } + if ( endNode.type == CKEDITOR.NODE_ELEMENT ) + { + childCount = endNode.getChildCount(); + if ( childCount > endOffset ) + endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true ); + else if ( childCount < 1 ) + endNode = endNode.getPreviousSourceNode(); + else // endOffset > childCount but childCount is not 0 + { + // Try to take the node just before the current position. + endNode = endNode.$; + while ( endNode.lastChild ) + endNode = endNode.lastChild; + endNode = new CKEDITOR.dom.node( endNode ); + } + } + + // Sometimes the endNode will come right before startNode for collapsed + // ranges. Fix it. (#3780) + if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) + startNode = endNode; + + return { startNode : startNode, endNode : endNode }; + }, + + /** + * Find the node which fully contains the range. + * @param includeSelf + * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type. + */ + getCommonAncestor : function( includeSelf , ignoreTextNode ) + { + var start = this.startContainer, + end = this.endContainer, + ancestor; + + if ( start.equals( end ) ) + { + if ( includeSelf + && start.type == CKEDITOR.NODE_ELEMENT + && this.startOffset == this.endOffset - 1 ) + ancestor = start.getChild( this.startOffset ); + else + ancestor = start; + } + else + ancestor = start.getCommonAncestor( end ); + + return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor; + }, + + /** + * Transforms the startContainer and endContainer properties from text + * nodes to element nodes, whenever possible. This is actually possible + * if either of the boundary containers point to a text node, and its + * offset is set to zero, or after the last char in the node. + */ + optimize : function() + { + var container = this.startContainer; + var offset = this.startOffset; + + if ( container.type != CKEDITOR.NODE_ELEMENT ) + { + if ( !offset ) + this.setStartBefore( container ); + else if ( offset >= container.getLength() ) + this.setStartAfter( container ); + } + + container = this.endContainer; + offset = this.endOffset; + + if ( container.type != CKEDITOR.NODE_ELEMENT ) + { + if ( !offset ) + this.setEndBefore( container ); + else if ( offset >= container.getLength() ) + this.setEndAfter( container ); + } + }, + + /** + * Move the range out of bookmark nodes if they're been the container. + */ + optimizeBookmark: function() + { + var startNode = this.startContainer, + endNode = this.endContainer; + + if ( startNode.is && startNode.is( 'span' ) + && startNode.hasAttribute( '_fck_bookmark' ) ) + this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START ); + if ( endNode && endNode.is && endNode.is( 'span' ) + && endNode.hasAttribute( '_fck_bookmark' ) ) + this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END ); + }, + + trim : function( ignoreStart, ignoreEnd ) + { + var startContainer = this.startContainer, + startOffset = this.startOffset, + collapsed = this.collapsed; + if ( ( !ignoreStart || collapsed ) + && startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) + { + // If the offset is zero, we just insert the new node before + // the start. + if ( !startOffset ) + { + startOffset = startContainer.getIndex(); + startContainer = startContainer.getParent(); + } + // If the offset is at the end, we'll insert it after the text + // node. + else if ( startOffset >= startContainer.getLength() ) + { + startOffset = startContainer.getIndex() + 1; + startContainer = startContainer.getParent(); + } + // In other case, we split the text node and insert the new + // node at the split point. + else + { + var nextText = startContainer.split( startOffset ); + + startOffset = startContainer.getIndex() + 1; + startContainer = startContainer.getParent(); + + // Check all necessity of updating the end boundary. + if ( this.startContainer.equals( this.endContainer ) ) + this.setEnd( nextText, this.endOffset - this.startOffset ); + else if ( startContainer.equals( this.endContainer ) ) + this.endOffset += 1; + } + + this.setStart( startContainer, startOffset ); + + if ( collapsed ) + { + this.collapse( true ); + return; + } + } + + var endContainer = this.endContainer; + var endOffset = this.endOffset; + + if ( !( ignoreEnd || collapsed ) + && endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) + { + // If the offset is zero, we just insert the new node before + // the start. + if ( !endOffset ) + { + endOffset = endContainer.getIndex(); + endContainer = endContainer.getParent(); + } + // If the offset is at the end, we'll insert it after the text + // node. + else if ( endOffset >= endContainer.getLength() ) + { + endOffset = endContainer.getIndex() + 1; + endContainer = endContainer.getParent(); + } + // In other case, we split the text node and insert the new + // node at the split point. + else + { + endContainer.split( endOffset ); + + endOffset = endContainer.getIndex() + 1; + endContainer = endContainer.getParent(); + } + + this.setEnd( endContainer, endOffset ); + } + }, + + enlarge : function( unit ) + { + switch ( unit ) + { + case CKEDITOR.ENLARGE_ELEMENT : + + if ( this.collapsed ) + return; + + // Get the common ancestor. + var commonAncestor = this.getCommonAncestor(); + + var body = this.document.getBody(); + + // For each boundary + // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge. + // b. Go ahead checking siblings and enlarging the boundary as much as possible until the common ancestor is not reached. After reaching the common ancestor, just save the enlargeable node to be used later. + + var startTop, endTop; + + var enlargeable, sibling, commonReached; + + // Indicates that the node can be added only if whitespace + // is available before it. + var needsWhiteSpace = false; + var isWhiteSpace; + var siblingText; + + // Process the start boundary. + + var container = this.startContainer; + var offset = this.startOffset; + + if ( container.type == CKEDITOR.NODE_TEXT ) + { + if ( offset ) + { + // Check if there is any non-space text before the + // offset. Otherwise, container is null. + container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container; + + // If we found only whitespace in the node, it + // means that we'll need more whitespace to be able + // to expand. For example, can be expanded in + // "A [B]", but not in "A [B]". + needsWhiteSpace = !!container; + } + + if ( container ) + { + if ( !( sibling = container.getPrevious() ) ) + enlargeable = container.getParent(); + } + } + else + { + // If we have offset, get the node preceeding it as the + // first sibling to be checked. + if ( offset ) + sibling = container.getChild( offset - 1 ) || container.getLast(); + + // If there is no sibling, mark the container to be + // enlarged. + if ( !sibling ) + enlargeable = container; + } + + while ( enlargeable || sibling ) + { + if ( enlargeable && !sibling ) + { + // If we reached the common ancestor, mark the flag + // for it. + if ( !commonReached && enlargeable.equals( commonAncestor ) ) + commonReached = true; + + if ( !body.contains( enlargeable ) ) + break; + + // If we don't need space or this element breaks + // the line, then enlarge it. + if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' ) + { + needsWhiteSpace = false; + + // If the common ancestor has been reached, + // we'll not enlarge it immediately, but just + // mark it to be enlarged later if the end + // boundary also enlarges it. + if ( commonReached ) + startTop = enlargeable; + else + this.setStartBefore( enlargeable ); + } + + sibling = enlargeable.getPrevious(); + } + + // Check all sibling nodes preceeding the enlargeable + // node. The node wil lbe enlarged only if none of them + // blocks it. + while ( sibling ) + { + // This flag indicates that this node has + // whitespaces at the end. + isWhiteSpace = false; + + if ( sibling.type == CKEDITOR.NODE_TEXT ) + { + siblingText = sibling.getText(); + + if ( /[^\s\ufeff]/.test( siblingText ) ) + sibling = null; + + isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); + } + else + { + // If this is a visible element. + // We need to check for the bookmark attribute because IE insists on + // rendering the display:none nodes we use for bookmarks. (#3363) + if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) ) + { + // We'll accept it only if we need + // whitespace, and this is an inline + // element with whitespace only. + if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] ) + { + // It must contains spaces and inline elements only. + + siblingText = sibling.getText(); + + if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF) + sibling = null; + else + { + var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' ); + for ( var i = 0, child ; child = allChildren[ i++ ] ; ) + { + if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] ) + { + sibling = null; + break; + } + } + } + + if ( sibling ) + isWhiteSpace = !!siblingText.length; + } + else + sibling = null; + } + } + + // A node with whitespaces has been found. + if ( isWhiteSpace ) + { + // Enlarge the last enlargeable node, if we + // were waiting for spaces. + if ( needsWhiteSpace ) + { + if ( commonReached ) + startTop = enlargeable; + else if ( enlargeable ) + this.setStartBefore( enlargeable ); + } + else + needsWhiteSpace = true; + } + + if ( sibling ) + { + var next = sibling.getPrevious(); + + if ( !enlargeable && !next ) + { + // Set the sibling as enlargeable, so it's + // parent will be get later outside this while. + enlargeable = sibling; + sibling = null; + break; + } + + sibling = next; + } + else + { + // If sibling has been set to null, then we + // need to stop enlarging. + enlargeable = null; + } + } + + if ( enlargeable ) + enlargeable = enlargeable.getParent(); + } + + // Process the end boundary. This is basically the same + // code used for the start boundary, with small changes to + // make it work in the oposite side (to the right). This + // makes it difficult to reuse the code here. So, fixes to + // the above code are likely to be replicated here. + + container = this.endContainer; + offset = this.endOffset; + + // Reset the common variables. + enlargeable = sibling = null; + commonReached = needsWhiteSpace = false; + + if ( container.type == CKEDITOR.NODE_TEXT ) + { + // Check if there is any non-space text after the + // offset. Otherwise, container is null. + container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container; + + // If we found only whitespace in the node, it + // means that we'll need more whitespace to be able + // to expand. For example, can be expanded in + // "A [B]", but not in "A [B]". + needsWhiteSpace = !( container && container.getLength() ); + + if ( container ) + { + if ( !( sibling = container.getNext() ) ) + enlargeable = container.getParent(); + } + } + else + { + // Get the node right after the boudary to be checked + // first. + sibling = container.getChild( offset ); + + if ( !sibling ) + enlargeable = container; + } + + while ( enlargeable || sibling ) + { + if ( enlargeable && !sibling ) + { + if ( !commonReached && enlargeable.equals( commonAncestor ) ) + commonReached = true; + + if ( !body.contains( enlargeable ) ) + break; + + if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' ) + { + needsWhiteSpace = false; + + if ( commonReached ) + endTop = enlargeable; + else if ( enlargeable ) + this.setEndAfter( enlargeable ); + } + + sibling = enlargeable.getNext(); + } + + while ( sibling ) + { + isWhiteSpace = false; + + if ( sibling.type == CKEDITOR.NODE_TEXT ) + { + siblingText = sibling.getText(); + + if ( /[^\s\ufeff]/.test( siblingText ) ) + sibling = null; + + isWhiteSpace = /^[\s\ufeff]/.test( siblingText ); + } + else + { + // If this is a visible element. + // We need to check for the bookmark attribute because IE insists on + // rendering the display:none nodes we use for bookmarks. (#3363) + if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) ) + { + // We'll accept it only if we need + // whitespace, and this is an inline + // element with whitespace only. + if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] ) + { + // It must contains spaces and inline elements only. + + siblingText = sibling.getText(); + + if ( (/[^\s\ufeff]/).test( siblingText ) ) + sibling = null; + else + { + allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' ); + for ( i = 0 ; child = allChildren[ i++ ] ; ) + { + if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] ) + { + sibling = null; + break; + } + } + } + + if ( sibling ) + isWhiteSpace = !!siblingText.length; + } + else + sibling = null; + } + } + + if ( isWhiteSpace ) + { + if ( needsWhiteSpace ) + { + if ( commonReached ) + endTop = enlargeable; + else + this.setEndAfter( enlargeable ); + } + } + + if ( sibling ) + { + next = sibling.getNext(); + + if ( !enlargeable && !next ) + { + enlargeable = sibling; + sibling = null; + break; + } + + sibling = next; + } + else + { + // If sibling has been set to null, then we + // need to stop enlarging. + enlargeable = null; + } + } + + if ( enlargeable ) + enlargeable = enlargeable.getParent(); + } + + // If the common ancestor can be enlarged by both boundaries, then include it also. + if ( startTop && endTop ) + { + commonAncestor = startTop.contains( endTop ) ? endTop : startTop; + + this.setStartBefore( commonAncestor ); + this.setEndAfter( commonAncestor ); + } + break; + + case CKEDITOR.ENLARGE_BLOCK_CONTENTS: + case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS: + + // Enlarging the start boundary. + var walkerRange = new CKEDITOR.dom.range( this.document ); + + body = this.document.getBody(); + + walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START ); + walkerRange.setEnd( this.startContainer, this.startOffset ); + + var walker = new CKEDITOR.dom.walker( walkerRange ), + blockBoundary, // The node on which the enlarging should stop. + tailBr, // + defaultGuard = CKEDITOR.dom.walker.blockBoundary( + ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ), + // Record the encountered 'blockBoundary' for later use. + boundaryGuard = function( node ) + { + var retval = defaultGuard( node ); + if ( !retval ) + blockBoundary = node; + return retval; + }, + // Record the encounted 'tailBr' for later use. + tailBrGuard = function( node ) + { + var retval = boundaryGuard( node ); + if ( !retval && node.is && node.is( 'br' ) ) + tailBr = node; + return retval; + }; + + walker.guard = boundaryGuard; + + enlargeable = walker.lastBackward(); + + // It's the body which stop the enlarging if no block boundary found. + blockBoundary = blockBoundary || body; + + // Start the range at different position by comparing + // the document position of it with 'enlargeable' node. + this.setStartAt( + blockBoundary, + !blockBoundary.is( 'br' ) && + ( !enlargeable && this.checkStartOfBlock() + || enlargeable && blockBoundary.contains( enlargeable ) ) ? + CKEDITOR.POSITION_AFTER_START : + CKEDITOR.POSITION_AFTER_END ); + + // Enlarging the end boundary. + walkerRange = this.clone(); + walkerRange.collapse(); + walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); + walker = new CKEDITOR.dom.walker( walkerRange ); + + // tailBrGuard only used for on range end. + walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? + tailBrGuard : boundaryGuard; + blockBoundary = null; + // End the range right before the block boundary node. + + enlargeable = walker.lastForward(); + + // It's the body which stop the enlarging if no block boundary found. + blockBoundary = blockBoundary || body; + + // Start the range at different position by comparing + // the document position of it with 'enlargeable' node. + this.setEndAt( + blockBoundary, + ( !enlargeable && this.checkEndOfBlock() + || enlargeable && blockBoundary.contains( enlargeable ) ) ? + CKEDITOR.POSITION_BEFORE_END : + CKEDITOR.POSITION_BEFORE_START ); + // We must include the
at the end of range if there's + // one and we're expanding list item contents + if ( tailBr ) + this.setEndAfter( tailBr ); + } + }, + + /** + * Descrease the range to make sure that boundaries + * always anchor beside text nodes or innermost element. + * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode. + */ + shrink : function( mode ) + { + // Unable to shrink a collapsed range. + if ( !this.collapsed ) + { + mode = mode || CKEDITOR.SHRINK_TEXT; + + var walkerRange = this.clone(); + + var startContainer = this.startContainer, + endContainer = this.endContainer, + startOffset = this.startOffset, + endOffset = this.endOffset, + collapsed = this.collapsed; + + // Whether the start/end boundary is moveable. + var moveStart = 1, + moveEnd = 1; + + if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) + { + if ( !startOffset ) + walkerRange.setStartBefore( startContainer ); + else if ( startOffset >= startContainer.getLength( ) ) + walkerRange.setStartAfter( startContainer ); + else + { + // Enlarge the range properly to avoid walker making + // DOM changes caused by triming the text nodes later. + walkerRange.setStartBefore( startContainer ); + moveStart = 0; + } + } + + if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) + { + if ( !endOffset ) + walkerRange.setEndBefore( endContainer ); + else if ( endOffset >= endContainer.getLength( ) ) + walkerRange.setEndAfter( endContainer ); + else + { + walkerRange.setEndAfter( endContainer ); + moveEnd = 0; + } + } + + var walker = new CKEDITOR.dom.walker( walkerRange ); + + walker.evaluator = function( node ) + { + return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? + CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); + }; + + var currentElement; + walker.guard = function( node, movingOut ) + { + // Stop when we're shrink in element mode while encountering a text node. + if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT ) + return false; + + // Stop when we've already walked "through" an element. + if ( movingOut && node.equals( currentElement ) ) + return false; + + if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT ) + currentElement = node; + + return true; + }; + + if ( moveStart ) + { + var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next'](); + textStart && this.setStartBefore( textStart ); + } + + if ( moveEnd ) + { + walker.reset(); + var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous'](); + textEnd && this.setEndAfter( textEnd ); + } + + return !!( moveStart || moveEnd ); + } + }, + + /** + * Inserts a node at the start of the range. The range will be expanded + * the contain the node. + */ + insertNode : function( node ) + { + this.optimizeBookmark(); + this.trim( false, true ); + + var startContainer = this.startContainer; + var startOffset = this.startOffset; + + var nextNode = startContainer.getChild( startOffset ); + + if ( nextNode ) + node.insertBefore( nextNode ); + else + startContainer.append( node ); + + // Check if we need to update the end boundary. + if ( node.getParent().equals( this.endContainer ) ) + this.endOffset++; + + // Expand the range to embrace the new node. + this.setStartBefore( node ); + }, + + moveToPosition : function( node, position ) + { + this.setStartAt( node, position ); + this.collapse( true ); + }, + + selectNodeContents : function( node ) + { + this.setStart( node, 0 ); + this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() ); + }, + + /** + * Sets the start position of a Range. + * @param {CKEDITOR.dom.node} startNode The node to start the range. + * @param {Number} startOffset An integer greater than or equal to zero + * representing the offset for the start of the range from the start + * of startNode. + */ + setStart : function( startNode, startOffset ) + { + // W3C requires a check for the new position. If it is after the end + // boundary, the range should be collapsed to the new start. It seams + // we will not need this check for our use of this class so we can + // ignore it for now. + + this.startContainer = startNode; + this.startOffset = startOffset; + + if ( !this.endContainer ) + { + this.endContainer = startNode; + this.endOffset = startOffset; + } + + updateCollapsed( this ); + }, + + /** + * Sets the end position of a Range. + * @param {CKEDITOR.dom.node} endNode The node to end the range. + * @param {Number} endOffset An integer greater than or equal to zero + * representing the offset for the end of the range from the start + * of endNode. + */ + setEnd : function( endNode, endOffset ) + { + // W3C requires a check for the new position. If it is before the start + // boundary, the range should be collapsed to the new end. It seams we + // will not need this check for our use of this class so we can ignore + // it for now. + + this.endContainer = endNode; + this.endOffset = endOffset; + + if ( !this.startContainer ) + { + this.startContainer = endNode; + this.startOffset = endOffset; + } + + updateCollapsed( this ); + }, + + setStartAfter : function( node ) + { + this.setStart( node.getParent(), node.getIndex() + 1 ); + }, + + setStartBefore : function( node ) + { + this.setStart( node.getParent(), node.getIndex() ); + }, + + setEndAfter : function( node ) + { + this.setEnd( node.getParent(), node.getIndex() + 1 ); + }, + + setEndBefore : function( node ) + { + this.setEnd( node.getParent(), node.getIndex() ); + }, + + setStartAt : function( node, position ) + { + switch( position ) + { + case CKEDITOR.POSITION_AFTER_START : + this.setStart( node, 0 ); + break; + + case CKEDITOR.POSITION_BEFORE_END : + if ( node.type == CKEDITOR.NODE_TEXT ) + this.setStart( node, node.getLength() ); + else + this.setStart( node, node.getChildCount() ); + break; + + case CKEDITOR.POSITION_BEFORE_START : + this.setStartBefore( node ); + break; + + case CKEDITOR.POSITION_AFTER_END : + this.setStartAfter( node ); + } + + updateCollapsed( this ); + }, + + setEndAt : function( node, position ) + { + switch( position ) + { + case CKEDITOR.POSITION_AFTER_START : + this.setEnd( node, 0 ); + break; + + case CKEDITOR.POSITION_BEFORE_END : + if ( node.type == CKEDITOR.NODE_TEXT ) + this.setEnd( node, node.getLength() ); + else + this.setEnd( node, node.getChildCount() ); + break; + + case CKEDITOR.POSITION_BEFORE_START : + this.setEndBefore( node ); + break; + + case CKEDITOR.POSITION_AFTER_END : + this.setEndAfter( node ); + } + + updateCollapsed( this ); + }, + + fixBlock : function( isStart, blockTag ) + { + var bookmark = this.createBookmark(), + fixedBlock = this.document.createElement( blockTag ); + + this.collapse( isStart ); + + this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS ); + + this.extractContents().appendTo( fixedBlock ); + fixedBlock.trim(); + + if ( !CKEDITOR.env.ie ) + fixedBlock.appendBogus(); + + this.insertNode( fixedBlock ); + + this.moveToBookmark( bookmark ); + + return fixedBlock; + }, + + splitBlock : function( blockTag ) + { + var startPath = new CKEDITOR.dom.elementPath( this.startContainer ), + endPath = new CKEDITOR.dom.elementPath( this.endContainer ); + + var startBlockLimit = startPath.blockLimit, + endBlockLimit = endPath.blockLimit; + + var startBlock = startPath.block, + endBlock = endPath.block; + + var elementPath = null; + // Do nothing if the boundaries are in different block limits. + if ( !startBlockLimit.equals( endBlockLimit ) ) + return null; + + // Get or fix current blocks. + if ( blockTag != 'br' ) + { + if ( !startBlock ) + { + startBlock = this.fixBlock( true, blockTag ); + endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block; + } + + if ( !endBlock ) + endBlock = this.fixBlock( false, blockTag ); + } + + // Get the range position. + var isStartOfBlock = startBlock && this.checkStartOfBlock(), + isEndOfBlock = endBlock && this.checkEndOfBlock(); + + // Delete the current contents. + // TODO: Why is 2.x doing CheckIsEmpty()? + this.deleteContents(); + + if ( startBlock && startBlock.equals( endBlock ) ) + { + if ( isEndOfBlock ) + { + elementPath = new CKEDITOR.dom.elementPath( this.startContainer ); + this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END ); + endBlock = null; + } + else if ( isStartOfBlock ) + { + elementPath = new CKEDITOR.dom.elementPath( this.startContainer ); + this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START ); + startBlock = null; + } + else + { + endBlock = this.splitElement( startBlock ); + + // In Gecko, the last child node must be a bogus
. + // Note: bogus
added under