text:
<?php
/**
 * Piwik - Open source web analytics
 *
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 *
 * @category Piwik_Plugins
 * @package Piwik_Goals
 */

/**
 *
 * @package Piwik_Goals
 */
class Piwik_Goals extends Piwik_Plugin
{
    const VISITS_UNTIL_RECORD_NAME = 'visits_until_conv';
    const DAYS_UNTIL_CONV_RECORD_NAME = 'days_until_conv';

    /**
     * This array stores the ranges to use when displaying the 'visits to conversion'
     * report.
     */
    public static $visitCountRanges = array(
        array(1, 1),
        array(2, 2),
        array(3, 3),
        array(4, 4),
        array(5, 5),
        array(6, 6),
        array(7, 7),
        array(8, 8),
        array(9, 14),
        array(15, 25),
        array(26, 50),
        array(51, 100),
        array(100)
    );

    /**
     * This array stores the ranges to use when displaying the 'days to conversion'
     * report.
     */
    public static $daysToConvRanges = array(
        array(0, 0),
        array(1, 1),
        array(2, 2),
        array(3, 3),
        array(4, 4),
        array(5, 5),
        array(6, 6),
        array(7, 7),
        array(8, 14),
        array(15, 30),
        array(31, 60),
        array(61, 120),
        array(121, 364),
        array(364)
    );

    public function getInformation()
    {
        $info = array(
            'description'     => Piwik_Translate('Goals_PluginDescription') . ' ' . Piwik_Translate('SitesManager_PiwikOffersEcommerceAnalytics', array('', '')),
            'author'          => 'Piwik',
            'author_homepage' => 'http://piwik.org/',
            'version'         => Piwik_Version::VERSION,
            'TrackerPlugin'   => true, // this plugin must be loaded during the stats logging
        );
        return $info;
    }

    function getListHooksRegistered()
    {
        $hooks = array(
            'AssetManager.getJsFiles'          => 'getJsFiles',
            'AssetManager.getCssFiles'         => 'getCssFiles',
            'Common.fetchWebsiteAttributes'    => 'fetchGoalsFromDb',
            'ArchiveProcessing_Day.compute'    => 'archiveDay',
            'ArchiveProcessing_Period.compute' => 'archivePeriod',
            'API.getReportMetadata.end'        => 'getReportMetadata',
            'API.getSegmentsMetadata'          => 'getSegmentsMetadata',
            'WidgetsList.add'                  => 'addWidgets',
            'Menu.add'                         => 'addMenus',
            'SitesManager.deleteSite'          => 'deleteSiteGoals',
            'Goals.getReportsWithGoalMetrics'  => 'getActualReportsWithGoalMetrics',
        );
        return $hooks;
    }

    /**
     * Delete goals recorded for this site
     *
     * @param Piwik_Event_Notification $notification  notification object
     */
    function deleteSiteGoals($notification)
    {
        $idSite = & $notification->getNotificationObject();
        Piwik_Query("DELETE FROM " . Piwik_Common::prefixTable('goal') . " WHERE idsite = ? ", array($idSite));
    }

    /**
     * Returns the Metadata for the Goals plugin API.
     * The API returns general Goal metrics: conv, conv rate and revenue globally
     * and for each goal.
     *
     * Also, this will update metadata of all other reports that have Goal segmentation
     *
     * @param Piwik_Event_Notification $notification  notification object
     */
    public function getReportMetadata($notification)
    {
        $info = $notification->getNotificationInfo();
        $idSites = $info['idSites'];
        $reports = & $notification->getNotificationObject();

        // Processed in AddColumnsProcessedMetricsGoal
        // These metrics will also be available for some reports, for each goal
        // Example: Conversion rate for Goal 2 for the keyword 'piwik'
        $goalProcessedMetrics = array(
            'revenue_per_visit' => Piwik_Translate('General_ColumnValuePerVisit'),
        );

        $goalMetrics = array(
            'nb_conversions'      => Piwik_Translate('Goals_ColumnConversions'),
            'nb_visits_converted' => Piwik_Translate('General_ColumnVisitsWithConversions'),
            'conversion_rate'     => Piwik_Translate('General_ColumnConversionRate'),
            'revenue'             => Piwik_Translate('Goals_ColumnRevenue')
        );

        $conversionReportMetrics = array(
            'nb_conversions' => Piwik_Translate('Goals_ColumnConversions')
        );

        // General Goal metrics: conversions, conv rate, revenue
        $goalsCategory = Piwik_Translate('Goals_Goals');
        $reports[] = array(
            'category'         => $goalsCategory,
            'name'             => Piwik_Translate('Goals_Goals'),
            'module'           => 'Goals',
            'action'           => 'get',
            'metrics'          => $goalMetrics,
            'processedMetrics' => array(),
            'order'            => 1
        );

        // If only one website is selected, we add the Goal metrics
        if (count($idSites) == 1) {
            $idSite = reset($idSites);
            $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);

            // Add overall visits to conversion report
            $reports[] = array(
                'category'          => $goalsCategory,
                'name'              => Piwik_Translate('Goals_VisitsUntilConv'),
                'module'            => 'Goals',
                'action'            => 'getVisitsUntilConversion',
                'dimension'         => Piwik_Translate('Goals_VisitsUntilConv'),
                'constantRowsCount' => true,
                'parameters'        => array(),
                'metrics'           => $conversionReportMetrics,
                'order'             => 5
            );

            // Add overall days to conversion report
            $reports[] = array(
                'category'          => $goalsCategory,
                'name'              => Piwik_Translate('Goals_DaysToConv'),
                'module'            => 'Goals',
                'action'            => 'getDaysToConversion',
                'dimension'         => Piwik_Translate('Goals_DaysToConv'),
                'constantRowsCount' => true,
                'parameters'        => array(),
                'metrics'           => $conversionReportMetrics,
                'order'             => 10
            );

            foreach ($goals as $goal) {
                // Add the general Goal metrics: ie. total Goal conversions,
                // Goal conv rate or Goal total revenue.
                // This API call requires a custom parameter
                $reports[] = array(
                    'category'         => $goalsCategory,
                    'name'             => Piwik_Translate('Goals_GoalX', $goal['name']),
                    'module'           => 'Goals',
                    'action'           => 'get',
                    'parameters'       => array('idGoal' => $goal['idgoal']),
                    'metrics'          => $goalMetrics,
                    'processedMetrics' => false,
                    'order'            => 50 + $goal['idgoal'] * 3
                );

                // Add visits to conversion report
                $reports[] = array(
                    'category'          => $goalsCategory,
                    'name'              => $goal['name'] . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
                    'module'            => 'Goals',
                    'action'            => 'getVisitsUntilConversion',
                    'dimension'         => Piwik_Translate('Goals_VisitsUntilConv'),
                    'constantRowsCount' => true,
                    'parameters'        => array('idGoal' => $goal['idgoal']),
                    'metrics'           => $conversionReportMetrics,
                    'order'             => 51 + $goal['idgoal'] * 3
                );

                // Add days to conversion report
                $reports[] = array(
                    'category'          => $goalsCategory,
                    'name'              => $goal['name'] . ' - ' . Piwik_Translate('Goals_DaysToConv'),
                    'module'            => 'Goals',
                    'action'            => 'getDaysToConversion',
                    'dimension'         => Piwik_Translate('Goals_DaysToConv'),
                    'constantRowsCount' => true,
                    'parameters'        => array('idGoal' => $goal['idgoal']),
                    'metrics'           => $conversionReportMetrics,
                    'order'             => 52 + $goal['idgoal'] * 3
                );
            }

            $site = new Piwik_Site($idSite);
            if ($site->isEcommerceEnabled()) {
                $category = Piwik_Translate('Goals_Ecommerce');
                $ecommerceMetrics = array_merge($goalMetrics, array(
                                                                   'revenue_subtotal'  => Piwik_Translate('General_Subtotal'),
                                                                   'revenue_tax'       => Piwik_Translate('General_Tax'),
                                                                   'revenue_shipping'  => Piwik_Translate('General_Shipping'),
                                                                   'revenue_discount'  => Piwik_Translate('General_Discount'),
                                                                   'items'             => Piwik_Translate('General_PurchasedProducts'),
                                                                   'avg_order_revenue' => Piwik_Translate('General_AverageOrderValue')
                                                              ));
                $ecommerceMetrics['nb_conversions'] = Piwik_Translate('General_EcommerceOrders');

                // General Ecommerce metrics
                $reports[] = array(
                    'category'         => $category,
                    'name'             => Piwik_Translate('General_EcommerceOrders'),
                    'module'           => 'Goals',
                    'action'           => 'get',
                    'parameters'       => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
                    'metrics'          => $ecommerceMetrics,
                    'processedMetrics' => false,
                    'order'            => 10
                );
                $reports[] = array(
                    'category'          => $category,
                    'name'              => Piwik_Translate('General_EcommerceOrders') . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
                    'module'            => 'Goals',
                    'action'            => 'getVisitsUntilConversion',
                    'dimension'         => Piwik_Translate('Goals_VisitsUntilConv'),
                    'constantRowsCount' => true,
                    'metrics'           => $conversionReportMetrics,
                    'parameters'        => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
                    'order'             => 11
                );
                $reports[] = array(
                    'category'          => $category,
                    'name'              => Piwik_Translate('General_EcommerceOrders') . ' - ' . Piwik_Translate('Goals_DaysToConv'),
                    'module'            => 'Goals',
                    'action'            => 'getDaysToConversion',
                    'dimension'         => Piwik_Translate('Goals_DaysToConv'),
                    'constantRowsCount' => true,
                    'metrics'           => $conversionReportMetrics,
                    'parameters'        => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER),
                    'order'             => 12
                );

                // Abandoned cart general metrics
                $abandonedCartMetrics = $goalMetrics;
                $abandonedCartMetrics['nb_conversions'] = Piwik_Translate('General_AbandonedCarts');
                $abandonedCartMetrics['revenue'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_ColumnRevenue'));
                $abandonedCartMetrics['items'] = Piwik_Translate('Goals_LeftInCart', Piwik_Translate('Goals_Products'));
                unset($abandonedCartMetrics['nb_visits_converted']);

                // Abandoned Cart metrics
                $reports[] = array(
                    'category'         => $category,
                    'name'             => Piwik_Translate('General_AbandonedCarts'),
                    'module'           => 'Goals',
                    'action'           => 'get',
                    'parameters'       => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
                    'metrics'          => $abandonedCartMetrics,
                    'processedMetrics' => false,
                    'order'            => 15
                );

                $reports[] = array(
                    'category'          => $category,
                    'name'              => Piwik_Translate('General_AbandonedCarts') . ' - ' . Piwik_Translate('Goals_VisitsUntilConv'),
                    'module'            => 'Goals',
                    'action'            => 'getVisitsUntilConversion',
                    'dimension'         => Piwik_Translate('Goals_VisitsUntilConv'),
                    'constantRowsCount' => true,
                    'metrics'           => $conversionReportMetrics,
                    'parameters'        => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
                    'order'             => 20
                );
                $reports[] = array(
                    'category'          => $category,
                    'name'              => Piwik_Translate('General_AbandonedCarts') . ' - ' . Piwik_Translate('Goals_DaysToConv'),
                    'module'            => 'Goals',
                    'action'            => 'getDaysToConversion',
                    'dimension'         => Piwik_Translate('Goals_DaysToConv'),
                    'constantRowsCount' => true,
                    'metrics'           => $conversionReportMetrics,
                    'parameters'        => array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_CART),
                    'order'             => 25
                );

                // Product reports metadata
                $productColumns = self::getProductReportColumns();
                foreach ($this->ecommerceReports as $i => $ecommerceReport) {
                    $reports[] = array(
                        'category'         => $category,
                        'name'             => Piwik_Translate($ecommerceReport[0]),
                        'module'           => 'Goals',
                        'action'           => $ecommerceReport[2],
                        'dimension'        => Piwik_Translate($ecommerceReport[0]),
                        'metrics'          => $productColumns,
                        'processedMetrics' => false,
                        'order'            => 30 + $i
                    );
                }
            }
        }

        unset($goalMetrics['nb_visits_converted']);

        /*
         * Add the metricsGoal and processedMetricsGoal entry
         * to all reports that have Goal segmentation
         */
        $reportsWithGoals = array();
        Piwik_PostEvent('Goals.getReportsWithGoalMetrics', $reportsWithGoals);
        foreach ($reportsWithGoals as $reportWithGoals) {
            // Select this report from the API metadata array
            // and add the Goal metrics to it
            foreach ($reports as &$apiReportToUpdate) {
                if ($apiReportToUpdate['module'] == $reportWithGoals['module']
                    && $apiReportToUpdate['action'] == $reportWithGoals['action']
                ) {
                    $apiReportToUpdate['metricsGoal'] = $goalMetrics;
                    $apiReportToUpdate['processedMetricsGoal'] = $goalProcessedMetrics;
                    break;
                }
            }
        }

    }

    /**
     * @param Piwik_Event_Notification $notification  notification object
     */
    public function getSegmentsMetadata($notification)
    {
        $segments =& $notification->getNotificationObject();
        $segments[] = array(
            'type'           => 'dimension',
            'category'       => Piwik_Translate('General_Visit'),
            'name'           => 'General_VisitConvertedGoalId',
            'segment'        => 'visitConvertedGoalId',
            'sqlSegment'     => 'log_conversion.idgoal',
            'acceptedValues' => '1, 2, 3, etc.',
        );
    }

    protected $ecommerceReports = array(
        array('Goals_ProductSKU', 'Goals', 'getItemsSku'),
        array('Goals_ProductName', 'Goals', 'getItemsName'),
        array('Goals_ProductCategory', 'Goals', 'getItemsCategory')
    );

    static public function getProductReportColumns()
    {
        return array(
            'revenue'         => Piwik_Translate('General_ProductRevenue'),
            'quantity'        => Piwik_Translate('General_Quantity'),
            'orders'          => Piwik_Translate('General_UniquePurchases'),
            'avg_price'       => Piwik_Translate('General_AveragePrice'),
            'avg_quantity'    => Piwik_Translate('General_AverageQuantity'),
            'nb_visits'       => Piwik_Translate('General_ColumnNbVisits'),
            'conversion_rate' => Piwik_Translate('General_ProductConversionRate'),
        );
    }

    static public function getReportsWithGoalMetrics()
    {
        $dimensions = array();
        Piwik_PostEvent('Goals.getReportsWithGoalMetrics', $dimensions);
        $dimensionsByGroup = array();
        foreach ($dimensions as $dimension) {
            $group = $dimension['category'];
            unset($dimension['category']);
            $dimensionsByGroup[$group][] = $dimension;
        }
        return $dimensionsByGroup;
    }

    /**
     * @param Piwik_Event_Notification $notification  notification object
     */
    function getJsFiles($notification)
    {
        $jsFiles = & $notification->getNotificationObject();
        $jsFiles[] = "plugins/Goals/templates/GoalForm.js";
    }

    /**
     * @param Piwik_Event_Notification $notification  notification object
     */
    function getCssFiles($notification)
    {
        $cssFiles = & $notification->getNotificationObject();
        $cssFiles[] = "plugins/Goals/templates/goals.css";
    }

    /**
     * @param Piwik_Event_Notification $notification  notification object
     */
    function fetchGoalsFromDb($notification)
    {
        $idsite = $notification->getNotificationInfo();

        // add the 'goal' entry in the website array
        $array =& $notification->getNotificationObject();
        $array['goals'] = Piwik_Goals_API::getInstance()->getGoals($idsite);
    }

    function addWidgets()
    {
        $idSite = Piwik_Common::getRequestVar('idSite', null, 'int');

        // Ecommerce widgets
        $site = new Piwik_Site($idSite);
        if ($site->isEcommerceEnabled()) {
            Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceOverview', 'Goals', 'widgetGoalReport', array('idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER));
            Piwik_AddWidget('Goals_Ecommerce', 'Goals_EcommerceLog', 'Goals', 'getEcommerceLog');
            foreach ($this->ecommerceReports as $widget) {
                Piwik_AddWidget('Goals_Ecommerce', $widget[0], $widget[1], $widget[2]);
            }
        }

        // Goals widgets
        Piwik_AddWidget('Goals_Goals', 'Goals_GoalsOverview', 'Goals', 'widgetGoalsOverview');
        $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
        if (count($goals) > 0) {
            foreach ($goals as $goal) {
                Piwik_AddWidget('Goals_Goals', Piwik_Common::sanitizeInputValue($goal['name']), 'Goals', 'widgetGoalReport', array('idGoal' => $goal['idgoal']));
            }
        }
    }

    protected function getGoalCategoryName($idSite)
    {
        $site = new Piwik_Site($idSite);
        return $site->isEcommerceEnabled() ? 'Goals_EcommerceAndGoalsMenu' : 'Goals_Goals';
    }

    function addMenus()
    {
        $idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
        $goals = Piwik_Goals_API::getInstance()->getGoals($idSite);
        $mainGoalMenu = $this->getGoalCategoryName($idSite);
        $site = new Piwik_Site($idSite);
        if (count($goals) == 0) {
            Piwik_AddMenu($mainGoalMenu, '', array(
                                                  'module' => 'Goals',
                                                  'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'addNewGoal'),
                                                  'idGoal' => ($site->isEcommerceEnabled() ? Piwik_Archive::LABEL_ECOMMERCE_ORDER : null)),
                true,
                25);
            if ($site->isEcommerceEnabled()) {
                Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), true, 1);
            }
            Piwik_AddMenu($mainGoalMenu, 'Goals_AddNewGoal', array('module' => 'Goals', 'action' => 'addNewGoal'));
        } else {
            Piwik_AddMenu($mainGoalMenu, '', array(
                                                  'module' => 'Goals',
                                                  'action' => ($site->isEcommerceEnabled() ? 'ecommerceReport' : 'index'),
                                                  'idGoal' => ($site->isEcommerceEnabled() ? Piwik_Archive::LABEL_ECOMMERCE_ORDER : null)),
                true,
                25);

            if ($site->isEcommerceEnabled()) {
                Piwik_AddMenu($mainGoalMenu, 'Goals_Ecommerce', array('module' => 'Goals', 'action' => 'ecommerceReport', 'idGoal' => Piwik_Archive::LABEL_ECOMMERCE_ORDER), true, 1);
            }
            Piwik_AddMenu($mainGoalMenu, 'Goals_GoalsOverview', array('module' => 'Goals', 'action' => 'index'), true, 2);
            foreach ($goals as $goal) {
                Piwik_AddMenu($mainGoalMenu, str_replace('%', '%%', Piwik_TranslationWriter::clean($goal['name'])), array('module' => 'Goals', 'action' => 'goalReport', 'idGoal' => $goal['idgoal']));
            }
        }
    }

    /**
     * @param string $recordName 'nb_conversions'
     * @param int|bool $idGoal idGoal to return the metrics for, or false to return overall
     * @return string Archive record name
     */
    static public function getRecordName($recordName, $idGoal = false)
    {
        $idGoalStr = '';
        if ($idGoal !== false) {
            $idGoalStr = $idGoal . "_";
        }
        return 'Goal_' . $idGoalStr . $recordName;
    }

    /**
     * Hooks on Period archiving.
     * Sums up Goal conversions stats, and processes overall conversion rate
     *
     * @param Piwik_Event_Notification $notification
     * @return void
     */
    function archivePeriod($notification)
    {
        /**
         * @var Piwik_ArchiveProcessing
         */
        $archiveProcessing = $notification->getNotificationObject();

        if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;

        /*
         * Archive Ecommerce Items
         */
        if ($this->shouldArchiveEcommerceItems($archiveProcessing)) {
            $dataTableToSum = $this->dimensions;
            foreach ($this->dimensions as $recordName) {
                $dataTableToSum[] = self::getItemRecordNameAbandonedCart($recordName);
            }
            $archiveProcessing->archiveDataTable($dataTableToSum);
        }

        /*
         *  Archive General Goal metrics
         */
        $goalIdsToSum = Piwik_Tracker_GoalManager::getGoalIds($archiveProcessing->idsite);

        //Ecommerce
        $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_ORDER;
        $goalIdsToSum[] = Piwik_Tracker_GoalManager::IDGOAL_CART; //bug here if idgoal=1
        // Overall goal metrics
        $goalIdsToSum[] = false;

        $fieldsToSum = array();
        foreach ($goalIdsToSum as $goalId) {
            $metricsToSum = Piwik_Goals::getGoalColumns($goalId);
            unset($metricsToSum[array_search('conversion_rate', $metricsToSum)]);
            foreach ($metricsToSum as $metricName) {
                $fieldsToSum[] = self::getRecordName($metricName, $goalId);
            }
        }
        $records = $archiveProcessing->archiveNumericValuesSum($fieldsToSum);

        // also recording conversion_rate for each goal
        foreach ($goalIdsToSum as $goalId) {
            $nb_conversions = $records[self::getRecordName('nb_visits_converted', $goalId)];
            $conversion_rate = $this->getConversionRate($nb_conversions, $archiveProcessing);
            $archiveProcessing->insertNumericRecord(self::getRecordName('conversion_rate', $goalId), $conversion_rate);

            // sum up the visits to conversion data table & the days to conversion data table
            $archiveProcessing->archiveDataTable(array(
                                                      self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $goalId),
                                                      self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $goalId)));
        }

        // sum up goal overview reports
        $archiveProcessing->archiveDataTable(array(
                                                  self::getRecordName(self::VISITS_UNTIL_RECORD_NAME),
                                                  self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME)));
    }

    static public function getGoalColumns($idGoal)
    {
        $columns = array(
            'nb_conversions',
            'nb_visits_converted',
            'conversion_rate',
            'revenue',
        );
        if ($idGoal === false) {
            return $columns;
        }
        // Orders
        if ($idGoal === Piwik_Tracker_GoalManager::IDGOAL_ORDER) {
            $columns = array_merge($columns, array(
                                                  'revenue_subtotal',
                                                  'revenue_tax',
                                                  'revenue_shipping',
                                                  'revenue_discount',
                                             ));
        }
        // Abandoned carts & orders
        if ($idGoal <= Piwik_Tracker_GoalManager::IDGOAL_ORDER) {
            $columns[] = 'items';
        }
        return $columns;
    }

    /**
     * Hooks on the Daily archiving.
     * Will process Goal stats overall and for each Goal.
     * Also processes the New VS Returning visitors conversion stats.
     *
     * @param Piwik_Event_Notification $notification
     * @return void
     */
    function archiveDay($notification)
    {
        /**
         * @var Piwik_ArchiveProcessing_Day
         */
        $archiveProcessing = $notification->getNotificationObject();

        if (!$archiveProcessing->shouldProcessReportsForPlugin($this->getPluginName())) return;

        $this->archiveGeneralGoalMetrics($archiveProcessing);
        $this->archiveEcommerceItems($archiveProcessing);
    }

    /**
     * @param Piwik_ArchiveProcessing_Day $archiveProcessing
     */
    function archiveGeneralGoalMetrics($archiveProcessing)
    {
        // extra aggregate selects for the visits to conversion report
        $visitToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
            'visitor_count_visits', self::$visitCountRanges, 'log_conversion', 'vcv');

        // extra aggregate selects for the days to conversion report
        $daysToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect(
            'visitor_days_since_first', self::$daysToConvRanges, 'log_conversion', 'vdsf');

        $query = $archiveProcessing->queryConversionsByDimension(
            array(), '', array_merge($visitToConvExtraCols, $daysToConvExtraCols));

        if ($query === false) {
            return;
        }

        $goals = array();
        $visitsToConvReport = array();
        $daysToConvReport = array();

        // Get a standard empty goal row
        $overall = $archiveProcessing->getNewGoalRow($idGoal = 1);
        while ($row = $query->fetch()) {
            $idgoal = $row['idgoal'];

            if (!isset($goals[$idgoal])) {
                $goals[$idgoal] = $archiveProcessing->getNewGoalRow($idgoal);

                $visitsToConvReport[$idgoal] = new Piwik_DataTable();
                $daysToConvReport[$idgoal] = new Piwik_DataTable();
            }
            $archiveProcessing->updateGoalStats($row, $goals[$idgoal]);

            // We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits
            // since it is a "negative conversion"
            if ($idgoal != Piwik_Tracker_GoalManager::IDGOAL_CART) {
                $archiveProcessing->updateGoalStats($row, $overall);
            }

            // map the goal + visit number of a visitor with the # of conversions that happened on that visit
            $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vcv');
            $visitsToConvReport[$idgoal]->addDataTable($table);

            // map the goal + day number of a visit with the # of conversion that happened on that day
            $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vdsf');
            $daysToConvReport[$idgoal]->addDataTable($table);
        }

        // these data tables hold reports for every goal of a site
        $visitsToConvOverview = new Piwik_DataTable();
        $daysToConvOverview = new Piwik_DataTable();

        // Stats by goal, for all visitors
        foreach ($goals as $idgoal => $values) {
            foreach ($values as $metricId => $value) {
                $metricName = Piwik_Archive::$mappingFromIdToNameGoal[$metricId];
                $recordName = self::getRecordName($metricName, $idgoal);
                $archiveProcessing->insertNumericRecord($recordName, $value);
            }
            $conversion_rate = $this->getConversionRate($values[Piwik_Archive::INDEX_GOAL_NB_VISITS_CONVERTED], $archiveProcessing);
            $recordName = self::getRecordName('conversion_rate', $idgoal);
            $archiveProcessing->insertNumericRecord($recordName, $conversion_rate);

            // if the goal is not a special goal (like ecommerce) add it to the overview report
            if ($idgoal !== Piwik_Tracker_GoalManager::IDGOAL_CART &&
                $idgoal !== Piwik_Tracker_GoalManager::IDGOAL_ORDER
            ) {
                $visitsToConvOverview->addDataTable($visitsToConvReport[$idgoal]);
                $daysToConvOverview->addDataTable($daysToConvReport[$idgoal]);
            }

            // visit count until conversion stats
            $archiveProcessing->insertBlobRecord(
                self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $idgoal),
                $visitsToConvReport[$idgoal]->getSerialized());

            // day count until conversion stats
            $archiveProcessing->insertBlobRecord(
                self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $idgoal),
                $daysToConvReport[$idgoal]->getSerialized());
        }

        // archive overview reports
        $archiveProcessing->insertBlobRecord(
            self::getRecordName(self::VISITS_UNTIL_RECORD_NAME), $visitsToConvOverview->getSerialized());
        $archiveProcessing->insertBlobRecord(
            self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME), $daysToConvOverview->getSerialized());

        // Stats for all goals
        $totalAllGoals = array(
            self::getRecordName('conversion_rate')     => $this->getConversionRate($archiveProcessing->getNumberOfVisitsConverted(), $archiveProcessing),
            self::getRecordName('nb_conversions')      => $overall[Piwik_Archive::INDEX_GOAL_NB_CONVERSIONS],
            self::getRecordName('nb_visits_converted') => $archiveProcessing->getNumberOfVisitsConverted(),
            self::getRecordName('revenue')             => $overall[Piwik_Archive::INDEX_GOAL_REVENUE],
        );
        foreach ($totalAllGoals as $recordName => $value) {
            $archiveProcessing->insertNumericRecord($recordName, $value);
        }
    }

    protected $dimensions = array(
        'idaction_sku'      => 'Goals_ItemsSku',
        'idaction_name'     => 'Goals_ItemsName',
        'idaction_category' => 'Goals_ItemsCategory'
    );

    protected function shouldArchiveEcommerceItems($archiveProcessing)
    {
        // Per item doesn't support segment
        // Also, when querying Goal metrics for visitorType==returning, we wouldnt want to trigger an extra request
        // event if it did support segment
        // (if this is implented, we should have shouldProcessReportsForPlugin() support partial archiving based on which metric is requested)
        if (!$archiveProcessing->getSegment()->isEmpty()) {
            return false;
        }
        return true;
    }

    /**
     * @param Piwik_ArchiveProcessing_Day $archiveProcessing
     */
    function archiveEcommerceItems($archiveProcessing)
    {
        if (!$this->shouldArchiveEcommerceItems($archiveProcessing)) {
            return false;
        }
        $items = array();

        $dimensionsToQuery = $this->dimensions;
        $dimensionsToQuery['idaction_category2'] = 'AdditionalCategory';
        $dimensionsToQuery['idaction_category3'] = 'AdditionalCategory';
        $dimensionsToQuery['idaction_category4'] = 'AdditionalCategory';
        $dimensionsToQuery['idaction_category5'] = 'AdditionalCategory';

        foreach ($dimensionsToQuery as $dimension => $recordName) {
            $query = $archiveProcessing->queryEcommerceItems($dimension);
            if ($query == false) {
                continue;
            }

            while ($row = $query->fetch()) {
                $label = $row['label'];
                $ecommerceType = $row['ecommerceType'];

                if (empty($label)) {
                    // idaction==0 case:
                    // If we are querying any optional category, we do not include idaction=0
                    // Otherwise we over-report in the Product Categories report
                    if ($recordName == 'AdditionalCategory') {
                        continue;
                    }
                    // Product Name/Category not defined"
                    if (class_exists('Piwik_CustomVariables')) {
                        $label = Piwik_CustomVariables::LABEL_CUSTOM_VALUE_NOT_DEFINED;
                    } else {
                        $label = "Value not defined";
                    }
                }
                // For carts, idorder = 0. To count abandoned carts, we must count visits with an abandoned cart
                if ($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART) {
                    $row[Piwik_Archive::INDEX_ECOMMERCE_ORDERS] = $row[Piwik_Archive::INDEX_NB_VISITS];
                }
                unset($row[Piwik_Archive::INDEX_NB_VISITS]);
                unset($row['label']);
                unset($row['ecommerceType']);

                $columnsToRound = array(
                    Piwik_Archive::INDEX_ECOMMERCE_ITEM_REVENUE,
                    Piwik_Archive::INDEX_ECOMMERCE_ITEM_QUANTITY,
                    Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE,
                    Piwik_Archive::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED,
                );
                foreach ($columnsToRound as $column) {
                    if (isset($row[$column])
                        && $row[$column] == round($row[$column])
                    ) {
                        $row[$column] = round($row[$column]);
                    }
                }
                $items[$dimension][$ecommerceType][$label] = $row;
            }
        }

        foreach ($this->dimensions as $dimension => $recordName) {
            foreach (array(Piwik_Tracker_GoalManager::IDGOAL_CART, Piwik_Tracker_GoalManager::IDGOAL_ORDER) as $ecommerceType) {
                if (!isset($items[$dimension][$ecommerceType])) {
                    continue;
                }
                $recordNameInsert = $recordName;
                if ($ecommerceType == Piwik_Tracker_GoalManager::IDGOAL_CART) {
                    $recordNameInsert = self::getItemRecordNameAbandonedCart($recordName);
                }
                $table = $archiveProcessing->getDataTableFromArray($items[$dimension][$ecommerceType]);

                // For "category" report, we aggregate all 5 category queries into one datatable
                if ($dimension == 'idaction_category') {
                    foreach (array('idaction_category2', 'idaction_category3', 'idaction_category4', 'idaction_category5') as $categoryToSum) {
                        if (!empty($items[$categoryToSum][$ecommerceType])) {
                            $tableToSum = $archiveProcessing->getDataTableFromArray($items[$categoryToSum][$ecommerceType]);
                            $table->addDataTable($tableToSum);
                        }
                    }
                }
                $archiveProcessing->insertBlobRecord($recordNameInsert, $table->getSerialized());
            }
        }
    }

    static public function getItemRecordNameAbandonedCart($recordName)
    {
        return $recordName . '_Cart';
    }

    function getConversionRate($count, $archiveProcessing)
    {
        $visits = $archiveProcessing->getNumberOfVisits();
        return round(100 * $count / $visits, Piwik_Tracker_GoalManager::REVENUE_PRECISION);
    }

    /**
     * This function executes when the 'Goals.getReportsWithGoalMetrics' event fires. It
     * adds the 'visits to conversion' report metadata to the list of goal reports so
     * this report will be displayed.
     *
     * @param Piwik_Event_Notification $notification  notification object
     */
    function getActualReportsWithGoalMetrics($notification)
    {
        $dimensions =& $notification->getNotificationObject();
        $dimensions = array_merge($dimensions, array(
                                                    array('category' => Piwik_Translate('General_Visit'),
                                                          'name'     => Piwik_Translate('Goals_VisitsUntilConv'),
                                                          'module'   => 'Goals',
                                                          'action'   => 'getVisitsUntilConversion'
                                                    ),
                                                    array('category' => Piwik_Translate('General_Visit'),
                                                          'name'     => Piwik_Translate('Goals_DaysToConv'),
                                                          'module'   => 'Goals',
                                                          'action'   => 'getDaysToConversion'
                                                    )
                                               ));
    }
}


text:
module ClayTests (tests) where

import Urbit.Noun.Conversions
import Urbit.Prelude

import Test.QuickCheck       hiding ((.&.))
import Test.Tasty
import Test.Tasty.QuickCheck
import Test.Tasty.TH

instance Arbitrary Knot where
  arbitrary = (MkKnot . pack) <$> sublistOf ['a'..'z']

nonEmptyList :: (Arbitrary a) => Gen [a]
nonEmptyList = sized $ \n ->
  do k <- choose (1, max 1 n)
     vector k

instance Arbitrary Path where
  arbitrary = Path <$> nonEmptyList

testPathRoundTrip :: Path -> Property
testPathRoundTrip p =
  classify (1 == (length $ unPath p)) "singleton" $
  (filePathToPath (pathToFilePath p)) === p

tests = testGroup "Clay"
  [ testProperty "Path round trip" $ testPathRoundTrip ]

redpajama_set_name: RedPajamaGithub


text:
{% extends "admin/base_site.html" %}
{% load i18n %}

{% block breadcrumbs %}
 › {% trans "Server error" %}
{% endblock %}

{% block inner-title %}{% trans 'Server error (500)' %}{% endblock %}

{% block content %}

{% trans 'Server Error (500)' %}

{% trans "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." %}

{% endblock %}


text:
<!DOCTYPE html>
<html>
<body>
<script>
  navigator.leaveAdInterestGroup();
</script>
This is a text.
</body>
</html>


text:
Developers trying to incorporate speech recognition interfaces in a low-resource language (LRL) into their applications currently face the hurdle of not finding recognition engines trained on their target language. Although tools such as Carnegie Mellon University's Sphinx simplify the creation of new acoustic models for recognition, they require large amounts of training data (audio recordings) in the target language. However, for small-vocabulary applications, an existing recognizer for a high-resource language (HRL) can be used to perform recognition in the target language. This requires a pronunciation lexicon mapping the relevant words in the target language into sequences of sounds in the HRL.

[lex4all](https://github.com/lex4all/lex4all) is an easy-to-use desktop application for Windows that will allow even naive users to automatically create a pronunciation lexicon for words in any language, using a small number of audio recordings and a pre-existing recognition engine in a HRL such as English. The resulting lexicon can then be used to add small-vocabulary speech recognition functionality to applications in the LRL.

#### How it works

A simple user interface allows the user to easily specify one written form (text string) and and one or more audio samples (`.wav` files) for each word in the target vocabulary, and to set other options (e.g. number of pronunciations per word, name/save location of lexicon file, etc.). The audio is then passed to a speech recognition engine for a HRL (English). An automatic pronunciation generation algorithm (the Salaam method, [2–3]) finds the best pronunciation(s) for each word in the LRL vocabulary. The program outputs a pronunciation lexicon (`.pls` XML file). This lexicon file follows the standard pronunciation lexicon format (http://www.w3.org/TR/pronunciation-lexicon/), so it can be directly included in a speech recognition application, e.g. one built using the [Microsoft Speech Platform](http://msdn.microsoft.com/en-us/library/hh361572) API.

For a guided step-by-step walkthrough with screenshots, see:
http://lex4all.github.io/lex4all/walkthrough.html

#### Features

* Simple graphical interface
* Use existing `.wav` audio files, or use the built-in audio recorder
* Advanced options (number of pronunciations per word, discriminative training [3])
* Evaluation module for testing/research
* Built-in support for 5 source languages: German (de-DE), English (en-US), French (fr-FR), Japanese (ja-JP), Chinese (zh-CN)

#### Requirements & Installation

Requirements:
- Windows 7 or 8, 64-bit
- Microsoft Speech Platform (MSP) runtime (version 11.0). Available here: http://www.microsoft.com/en-us/download/details.aspx?id=27225
- MSP speech recognition engine(s) for US English (and optionally other languages). Available here:
http://www.microsoft.com/en-us/download/details.aspx?id=27224
(From the download page, select the Speech Recognition (SR) engines for the languages you want to use, e.g.  `MSSpeech_SR_en-US_TELE.msi` for US English)

Installation:
- Download the project from GitHub & unzip the archive.
- Double-click the link `run-lex4all.exe` in the folder you just downloaded.
- Enjoy using lex4all!

For troubleshooting help, please see our wiki page:
https://github.com/lex4all/lex4all/wiki/Installation-&-troubleshooting

#### Backend & resources

This approach to language-independent recognition requires an existing high-quality speech recognition engine with a usable API; we chose to use the English recognition engine of the [Microsoft Speech Platform](http://msdn.microsoft.com/en-us/library/hh361572), so lex4all is written in C#. The audio recording feature was built using the [NAudio API](http://naudio.codeplex.com/).

To automatically discover the pronunciation mappings we implement the Salaam algorithm as presented in [2-3]; a slight modification was made to reduce the algorithm's running time. In addition to the basic discovery algorithm [2], users have the choice of applying the discriminative training algorithm [3] as well.

#### Publications

Anjana Vakil, Max Paulus, Alexis Palmer and Michaela Regneri. 2014. "lex4all: A language-independent tool for building and evaluating pronunciation lexicons for small-vocabulary speech recognition." In: Proceedings of 52nd Annual Meeting of the Association for Computational Linguistics (ACL 2014): System Demonstrations. [\[pdf\]](http://www.anthology.aclweb.org/P/P14/P14-5.pdf#page=121)

Anjana Vakil and Alexis Palmer. 2014. "Cross-language mapping for small-vocabulary ASR in under-resourced languages: investigating the impact of source language choice." In: Proceedings of the 4th Workshop on Spoken Language Technologies for Under-resourced Languages (SLTU'14). [\[pdf\]](http://www.coli.uni-saarland.de/~apalmer/docs/vakil_crosslg_sltu2014.pdf)

Chibuye, N.K., Rosenstock, T. and DeRenzi, B., 2018. "Cross-language Phoneme Mapping for Low-resource Languages: An Exploration of Benefits and Trade-offs." In: INTERSPEECH (pp. 2623-2627). [\[pdf\]](https://www.isca-speech.org/archive/Interspeech_2018/pdfs/2454.pdf)

#### References

[1] Jahanzeb Sherwani. 2009. "Speech interfaces for information access by low literate users". PhD thesis. Pittsburgh, PA, USA: Carnegie Mellon University. [\[pdf\]](http://reports-archive.adm.cs.cmu.edu/anon/anon/home/ftp/usr/ftp/2009/CMU-CS-09-131.pdf).

[2] Fang Qiao, Jahanzeb Sherwani, and Roni Rosenfeld. 2010. "Small-vocabulary speech recognition for resource-scarce languages." In: Proceedings of the First ACM Symposium on Computing for Development (ACM DEV '10). [\[pdf\]](http://www.cs.cmu.edu/~roni/papers/salaam-DEV2010.pdf).

[3] Hao Yee Chan and Roni Rosenfeld. 2012. "Discriminative pronunciation learning for speech recognition for resource scarce languages." In: Proceedings of the 2nd ACM Symposium on Computing for Development (ACM DEV '12). [\[pdf\]](http://www.cs.cmu.edu/~roni/papers/salaam-DEV2012.pdf).


text:
ORSSerialPort is an easy-to-use Objective-C serial port library for macOS. It is useful for programmers writing Objective-C or Swift Mac apps that communicate with external devices through a serial port (most commonly RS-232). You can use ORSSerialPort to write apps that connect to Arduino projects, robots, data acquisition devices, ham radios, and all kinds of other devices. Using ORSSerialPort to open a port and send data can be as simple as this:

```swift
let serialPort = ORSSerialPort(path: "/dev/cu.KeySerial1")
serialPort.baudRate = 4800
serialPort.open()
serialPort.send(someData) // someData is an NSData object
serialPort.close() // Later, when you're done with the port
```

Or, in Objective-C:

```objective-c
ORSSerialPort *serialPort = [ORSSerialPort serialPortWithPath:@"/dev/cu.KeySerial1"];
serialPort.baudRate = @4800;
[serialPort open];
[serialPort sendData:someData]; // someData is an NSData object
[serialPort close]; // Later, when you're done with the port
```

ORSSerialPort is released under an MIT license, meaning you're free to use it in both closed and open source projects. However, even in a closed source project, you must include a publicly-accessible copy of ORSSerialPort's copyright notice, which you can find in the LICENSE file.

If you have any questions about, suggestions for, or contributions to ORSSerialPort, please [contact me](mailto:andrew@openreelsoftware.com). I'd also love to hear about any cool projects you're using it in.

This readme provides an overview of the ORSSerialPort library and is meant to provide enough information to get up and running quickly. You can read complete technical documentation for ORSSerialPort on [http://cocoadocs.org/docsets/ORSSerialPort/](http://cocoadocs.org/docsets/ORSSerialPort/). The [ORSSerialPort wiki](https://github.com/armadsen/ORSSerialPort/wiki) also contains detailed documentation.

Most of the example code in this readme is in Swift. However, ORSSerialPort can also easily be used from Objective-C code. The Examples folder contains Swift and Objective-C versions of all four example projects. See the Example Projects section below for more information.

# How to Use ORSSerialPort

There are a number of ways to add ORSSerialPort to your project. You can use the included framework project, [Carthage](https://github.com/Carthage), [CocoaPods](http://cocoapods.org), or the [Swift Package Manager](https://swift.org/package-manager/). See the [Guide to Installing ORSSerialPort](https://github.com/armadsen/ORSSerialPort/wiki/Installing-ORSSerialPort) for detailed instructions for each of these methods.

### Opening a Port and Setting It Up

You can get an `ORSSerialPort` instance either of two ways. The easiest is to use `ORSSerialPortManager`'s `availablePorts` property (explained below). The other way is to get a new `ORSSerialPort` instance using the serial port's BSD device path:

```swift
let port = ORSSerialPort(path: "/dev/cu.KeySerial1")
```

Note that you must give `ORSSerialPort.init(path:)` the full path to the device, as shown in the example above.

After you've got a port instance, you can open it with the `open()` method. When you're done using the port, close it using the `close()` method.

Port settings such as baud rate, number of stop bits, parity, and flow control settings can be set using the various properties `ORSSerialPort` provides:

```swift
port.baudRate = 9600
port.parity = .none
port.numberOfStopBits = 1
port.usesRTSCTSFlowControl = true
```

For more information, see the [Getting Started Guide](https://github.com/armadsen/ORSSerialPort/wiki/Getting-Started#opening-a-port-and-setting-it-up).

### Sending Data

Send raw data by passing a `Data` object to the `send(_:)` method:

```swift
let dataToSend = "Hello".data(using: .utf8)
port.send(dataToSend)
```

### Receiving Data

To receive data, you can implement the `ORSSerialPortDelegate` protocol's `serialPort(_:, didReceive:)` method, and set the `ORSSerialPort` instance's delegate property. As noted below, this method is always called on the main queue. An example implementation is included below:

```swift
func serialPort(_ serialPort: ORSSerialPort, didReceive data: Data) {
    let string = String(data: data, encoding: .utf8)
    print("Got \(string) from the serial port!")
}
```

### ORSSerialPortDelegate

`ORSSerialPort` includes a delegate property, and a delegate protocol called `ORSSerialPortDelegate`. A port informs its delegate of events including receipt of data, port open/close events, removal from the system, and errors. For more information, see the [Getting Started Guide](https://github.com/armadsen/ORSSerialPort/wiki/Getting-Started#orsserialportdelegate), or read the documentation in [ORSSerialPort.h](https://github.com/armadsen/ORSSerialPort/blob/master/Source/ORSSerialPort.h#L443).

### ORSSerialPortManager

`ORSSerialPortManager` is a singleton class (one instance per application) that can be used to get a list of available serial ports. Use the manager's `availablePorts` property to get a list of ports:

```swift
let ports = ORSSerialPortManager.shared().availablePorts
```

ORSSerialPortManager's `availablePorts` can be observed with Key Value Observing to be notified when a USB to serial adapter is plugged in or removed. Additionally, it posts NSNotifications when these events occur. It will also handle closing open serial ports when the Mac goes to sleep, and reopening them automatically on wake. This prevents problems I've seen with serial port drivers that can hang if the port is left open when putting the machine to sleep. Note that using `ORSSerialPortManager` is optional. It provides some nice functionality, but only `ORSSerialPort` is necessary to simply send and receive data.

For more information about ORSSerialPortManager, see the [Getting Started Guide](https://github.com/armadsen/ORSSerialPort/wiki/Getting-Started#orsserialportmanager), or read the documentation in [ORSSerialPortManager.h](https://github.com/armadsen/ORSSerialPort/blob/master/Source/ORSSerialPortManager.h).

### ORSSerialPacketDescriptor

Incoming serial data is delivered to your application as it is received. A low level library like ORSSerialPort has no way of knowing anything about the structure and format of the data you're sending and receiving. For example, you may be expecting a complete packet of data, but receive callbacks for each byte. Normally, this requires you to maintain a buffer which you fill up with incoming data, only processing it when a complete packet has been received. In order to eliminate the need for manual management and buffering of incoming data, ORSSerialPort includes a packet parsing API. This is implemented by `ORSSerialPacketDescriptor` and associated methods on `ORSSerialPort`.

For more information about ORSSerialPort's packet parsing API, see the [Packet Parsing API Guide](https://github.com/armadsen/ORSSerialPort/wiki/Packet-Parsing-API), read the documentation in [ORSSerialPacketDescriptor.h](https://github.com/armadsen/ORSSerialPort/blob/master/Source/ORSSerialPacketDescriptor.h), and see the [PacketParsingDemo](https://github.com/armadsen/ORSSerialPort/tree/master/Examples/PacketParsingDemo) example app.

### ORSSerialRequest

Often, applications will want to send a command to a device, then wait to receive a specific response before continuing. To ease implementing this kind of scenario, ORSSerialPort includes a request/response API. This is implemented by `ORSSerialRequest` and associated methods on `ORSSerialPort`.

For example, a program that read the temperature from a connected device might do the following:

```swift
func readTemperature() {
    let command = "$TEMP?;".data(using: String.Encoding.ascii)!
    let responseDescriptor = ORSSerialPacketDescriptor(prefixString: "!TEMP", suffixString: ";", maximumPacketLength: 10, userInfo: nil)
    let request = ORSSerialRequest(dataToSend: command,
        userInfo: SerialBoardRequestType.readTemperature.rawValue,
        timeoutInterval: 0.5,
        responseDescriptor: responseDescriptor)
    serialPort?.send(request)
}

func serialPort(_ serialPort: ORSSerialPort, didReceiveResponse responseData: Data, to request: ORSSerialRequest) {
    temperature = temperatureFromResponsePacket(responseData)!
}

func serialPort(_ serialPort: ORSSerialPort, requestDidTimeout request: ORSSerialRequest) {
    print("Command timed out!")
}
```

For more information about ORSSerialPort's request/response API, see the [Request/Response API Guide](https://github.com/armadsen/ORSSerialPort/wiki/Request-Response-API), read the documentation in [ORSSerialRequest.h](https://github.com/armadsen/ORSSerialPort/blob/master/Source/ORSSerialRequest.h), and see the [RequestResponseDemo](https://github.com/armadsen/ORSSerialPort/tree/master/Examples/RequestResponseDemo) example app.

# Example Projects

Included with ORSSerialPort is a folder called Examples, containing Xcode projects for small programs demonstrating the use of ORSSerialPort. Each example is available in *both* Objective-C and Swift. The following example apps are included:

- [ORSSerialPortDemo](https://github.com/armadsen/ORSSerialPort/wiki/ORSSerialPortDemo) - Simple graphical serial terminal example app. This is the main ORSSerialPort example.
- [CommandLineDemo](https://github.com/armadsen/ORSSerialPort/wiki/Command-Line-Demo) - Command line app example.
- [PacketParsingDemo](https://github.com/armadsen/ORSSerialPort/wiki/Packet-Parsing-API) - GUI app demonstrating the use of ORSSerialPacketDescriptor.
- [RequestResponseDemo](https://github.com/armadsen/ORSSerialPort/wiki/Request-Response-API) - GUI app demonstrating the use of ORSSerialRequest.

You can read more about these examples on the [ORSSerialPort wiki](https://github.com/armadsen/ORSSerialPort/wiki).

# Contributing

Contributions to ORSSerialPort are very welcome. However, contributors are encouraged to read the [contribution guidelines](CONTRIBUTING.md) before starting work on any contributions. Please also feel free to open a GitHub issue or [email](mailto:andrew@openreelsoftware.com) with questions about specific contributions.

[![GitHub License Badge](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/armadsen/ORSSerialPort/master/LICENSE)
[![Build Status Badge](https://travis-ci.org/armadsen/ORSSerialPort.svg?branch=master)](https://travis-ci.org/armadsen/ORSSerialPort)
[![CocoaPods Badge](https://img.shields.io/cocoapods/v/ORSSerialPort.svg)]()


text:
smartmessage
============

A simple sample to ouput message based on mood using Dynamic prototype Pattern and constructor/Prototype Pattern to create.

## How to install

npm install smartmessage

## Examples:

node test.js

Using the combination of constructor/Prototype pattern

Son,This is amazing!!!
Man,I cannot stand this!!
Karen,This is wonderful!!!

Using the Dynamic Prototype pattern

Son,This is amazing!!!
Man,I cannot stand this!!
Karen,This is wonderful!!!


text:
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const fetch = require('node-fetch');
const undici = require('undici');
const Service = require('../lib');
const { SolrClient } = require('../lib');
const solrServer = 'http://localhost:8983/solr/gettingstarted';

// Create an Express compatible Feathers application instance.
const app = express(feathers());
// Turn on JSON parser for REST services
app.use(express.json());
// Turn on URL-encoded parser for REST services
app.use(express.urlencoded({ extended: true }));
// Enable REST services
app.configure(express.rest());
// Enable REST services

// init Adapter witch Fetch
app.use(
  'fetch',
  new Service({
    Model: SolrClient(fetch, solrServer),
    paginate: {},
    events: ['testing'],
    multi: true
  })
);
// init Adapter witch Undici
app.use(
  'undici',
  new Service({
    Model: SolrClient(undici, solrServer),
    paginate: {},
    events: ['testing'],
    multi: true
  })
);

// add some docs
app.service('fetch').create([
  {
    id: 'TWINX2048-3200PRO',
    name: 'Product One'
  },
  {
    id: 'HELIX1015-1800SL',
    name: 'Product Two'
  }
]);

// add a doc ....
app.service('undici').create({
  id: 'delete-test',
  name: 'Product to delete'
});
// and delete it
app.service('undici').remove(null, { query: { id: 'delete-test' } });

// Start the server.
const port = 3030;

var server = app.listen(port, () => {
  console.log(`Feathers server listening on port http://127.0.0.1:${port}`);
});

process.on('SIGINT', function () {
  console.error('Caught SIGINT, shutting down.');
  server.close();
});


text:
To interact with [VirtualBox](http://www.virtualbox.org/), Veewee executes shell-commands through the 'VboxManage' command. The `virtualbox` gem library proved to be less stable.

To simulate the typing, Veewee uses the `VBoxManage` command:

    $ VBoxManage controlvm 'myubuntu' keyboardputscancode <keycode>

[Scancodes](http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html) are injected directly
from the keyboard buffer with this command. Because this buffer is small, Veewee can't type fast(er). This is why you'll notice a delay before Veewee types the commands. Speeding it up would make the keyboard buffer lose keystrokes.


text:
<title>
Making teachers pay for the budget crisis
</title>

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<?php include "../../legacy-includes/Script.htmlf" ?>

<?php include "../../legacy-includes/TopLogo.htmlf" ?>

<?php include "../../legacy-includes/TopAd.htmlf" ?>

<?php include "../../legacy-includes/LeftButtons.htmlf" ?>

<?php include "../../legacy-includes/BodyInsert.htmlf" ?>

Making teachers pay for the budget crisis

May 23, 2003 | Page 4

Dear Socialist Worker,

A massive heist is happening in Rhode Island. Like many states, Rhode Island is facing a budget shortfall. This has prompted the new Republican Gov. Donald Carcieri to call for reductions in teacher and state employee pensions, as well as for concessions from state employees' unions.

This request is particularly perverse in light of that fact that the state brings in more money from gambling than it does from all taxes on business combined.

One of the two main gambling establishments is located in the Providence suburb of Lincoln. Lincoln is known for its corrupt town officials and its brand-new luxury housing subdivisions--and now, its overcrowded schools. Despite all the newfound local wealth, Lincoln High School is at 122 percent capacity, and its middle school is at 139 percent (The state recommends that schools be at no more than 85 percent capacity).

So on May 9, 60 students picketed the town hall in favor of a ballot resolution to raise $700,000 in local tax money to add "modular classrooms"--trailers--to alleviate the overcrowding.

If this resolution fails, the only option is for the schools to institute a double shift, operating from early morning to early evening--and eliminating all extracurricular activities. Similarly, the Providence public schools just lost $6 million from next year's budget, to make up for the $59 million deficit in the city. Two hundred employees have been laid off, and sports, art and music programs cut back or eliminated.

It is nothing short of a crime when kids suffer for budget deficits, while massive corporations like Fidelity Investments and Brown University--through deals they have cut with state and local governments--get away without paying a penny in taxes.

Brian Chidester, Providence, R.I.

<?php include "../../legacy-includes/BottomNavLinks.htmlf" ?>

<?php include "../../legacy-includes/RightAdFolder.htmlf" ?>