<?php
// Copyright 2013-present Facebook.  All rights reserved.
// @author: rahulgulati (rahulgulati@fb.com)

class FacebookMySQLLintEngine extends ArcanistLintEngine {

  public function buildLinters() {
    $linters = array();
    $paths = $this->getPaths();

    foreach ($paths as $key => $path) {
      if (!Filesystem::pathExists($this->getFilePathOnDisk($path))) {
        unset($paths[$key]);
      }
    }

    $text_extensions = (
      '/\.(cpp|cxx|c|cc|h|hpp|hxx|tcc|txt|py|sh|cmake'.
      '|css|sql|inc|pl|php|json|java|html|i|ic|yy)$/'
    );

    // All paths that have a recognizable text extension
    $text_paths = preg_grep($text_extensions, $paths);

    $cpp_extensions = ('/\.(cpp|cxx|c|cc|h|hpp|hxx|tcc|i|ic)$/');

    // All paths that have a C++ extension
    $all_cpp_paths = preg_grep($cpp_extensions, $paths);

    // Run on all C++ files that are in MyRocks (include 'storage/rocksdb')
    $myrocks_cpp_paths = preg_grep('/storage\/rocksdb/', $all_cpp_paths);

    // ArcanistGeneratedLinter stops other linters from running on generated
    // code.
    $linters[] = id(new ArcanistGeneratedLinter())->setPaths($text_paths);

    // ArcanistNoLintLinter stops other linters from running on code marked
    // with a nolint annotation.
    $linters[] = id(new ArcanistNoLintLinter())->setPaths($text_paths);

    // FacebookMySqlLinter enforces the following lint checks: max line length
    // is 80 characters, use Unix newlines instead of DOS newlines, Files
    // should end in a new line, and, lines containing trailing whitespace.
    $linters[] = id(new FacebookMySQLLinter())->setPaths($text_paths);

    // ArcanistCpplintLinter runs cpplint.py
    $linters[] = id(new ArcanistCpplintLinter())->setPaths($myrocks_cpp_paths);

    // Clang format only MyRocks for the 5.6 codebase
    $linters[] = id(new ArcanistClangFormatLinter())->setPaths($myrocks_cpp_paths);

    // This linter looks to see if any changes in InnoDB use tabs as most of
    // the files there expect tabs instead of spaces.
    // Run on all C++ files that are in InnoDB (include 'storage/innobase')
    $innodb_cpp_paths = preg_grep('/storage\/innobase/', $all_cpp_paths);
    $linters[] = id(new FacebookInnoDBTabLinter())
        ->setPaths($innodb_cpp_paths);

    // ArcanistSpellingLinter enforces basic spelling. A blacklisted set of
    // words that are commonly spelled incorrectly are used.
    $spelling_linter = new ArcanistSpellingLinter();
    $spelling_linter->setCustomSeverityMap(
      array(
        ArcanistSpellingLinter::LINT_SPELLING_PICKY
          => ArcanistLintSeverity::SEVERITY_WARNING,
        ArcanistSpellingLinter::LINT_SPELLING_IMPORTANT
          => ArcanistLintSeverity::SEVERITY_WARNING,
      )
    );
    $spelling_linter->setPaths($text_paths);
    $linters[] = $spelling_linter;

    // ArcanistFilenameLinter stifles creativity in choosing imaginative file
    // names.
    $linters[] = id(new ArcanistFilenameLinter())->setPaths($paths);

    $linters[] = id(new FacebookMySQLPrintfLinter())
        ->setPaths($all_cpp_paths);

    $linters[] = id(new FacebookMySQLAssertUsageLinter())
        ->setPaths($myrocks_cpp_paths);

    //
    // If SKIP_HOWTOEVEN is specified then don't run Howtoeven linter.
    // Readability note: strcmp() returns a non-zero value if strings aren't
    // equal.
    //
    if (strcmp(getenv("SKIP_HOWTOEVEN"), "1")) {
      // Advanced static analysis will be only applied to MyRocks because the
      // existing MySQL codebase differs too much from our requirements.
      $linters[] = id(new FacebookMySQLHowtoevenLinter())
          ->setPaths($myrocks_cpp_paths);
    }

    // All paths in mysql-test that have a .test or .result extension
    $mysql_test_paths = preg_grep('/mysql-test\/.*\.(test|result)$/', $paths);

    // This linter validates that new .test files (in mysql-test/) have
    // have matchine .result files
    $linters[] = id(new FacebookMySQLTestResultLinter())
        ->setPaths($mysql_test_paths);

    return $linters;
  }
}


<?php
// Copyright 2004-present Facebook. All Rights Reserved.

abstract class BaseDirectoryScopedFormatLinter extends ArcanistLinter {

  const LINT_FORMATTING = 1;

  private $changedLines = array();
  private $rawLintOutput = array();
  private $pathsThatRaisedLint = array();

  abstract protected function getPathsToLint();

  /**
   * Override and add paths to exclude subdirectories of paths returned by
   * `getPathsToLint`. For example, if you want to include `a/*` except for
   * `a/b`, you can add `a/` to `getPathsToLint` and `a/b` to
   * `getPathsToExcludeFromLint`.
   */
  protected function getPathsToExcludeFromLint() {
    return array();
  }

  protected function shouldLintPath($path) {
    return self::matchPath($path, $this->getPathsToLint()) &&
      !self::matchPath($path, $this->getPathsToExcludeFromLint());
  }

  protected static function matchPath($needle, $haystack) {
    foreach ($haystack as $p) {
      // check if $path starts with $p
      if (strncmp($needle, $p, strlen($p)) === 0) {
        return true;
      }
    }
    return false;
  }

  // API to tell this linter which lines were changed
  final public function setPathChangedLines($path, $changed) {
    $this->changedLines[$path] = $changed;
  }

  final public function willLintPaths(array $paths) {
    $futures = array();

    $this->prepareFormat();

    foreach ($paths as $path) {
      if (!$this->shouldLintPath($path)) {
        continue;
      }

      $changed = $this->changedLines[$path];
      if (!isset($changed)) {
        // do not run linter if there are no changes
        continue;
      }

      $futures[$path] = $this->getFormatFuture($path, $changed);
    }

    foreach (Futures($futures)->limit(8) as $p => $f) {
      $this->rawLintOutput[$p] = $f->resolvex();
    }
  }

  protected function prepareFormat() {}
  abstract protected function getFormatFuture($path, array $changed);
  abstract protected function getLintMessage($diff);

  final public function lintPath($path) {
    if (!isset($this->rawLintOutput[$path])) {
      return;
    }

    list($new_content) = $this->rawLintOutput[$path];
    $old_content = $this->getData($path);

    if ($new_content != $old_content) {
      $diff = ArcanistDiffUtils::renderDifferences($old_content, $new_content);
      $this->raiseLintAtOffset(
        0,
        self::LINT_FORMATTING,
        $this->getLintMessage($diff),
        $old_content,
        $new_content);
      $this->setDidModifySource();
      $this->pathsThatRaisedLint[$path] = true;
    }
  }

  final public function hasRaisedLint($path) {
    return array_key_exists($path, $this->pathsThatRaisedLint);
  }
}


<?php
// Copyright 2015, Facebook, Inc.  All rights reserved.
// @author: Jay Edgar (jkedgar@fb.com)

class FacebookInnoDBTabLinter extends ArcanistLinter {

  const LINT_INCORRECT_TAB = 1;
  const LINT_TABSTOP = 8;

  public function getLinterName() {
    return 'InnoDB_tab';
  }

  public function getLintSeverityMap() {
    return array(
      self::LINT_INCORRECT_TAB => ArcanistLintSeverity::SEVERITY_AUTOFIX,
    );
  }

  public function getLintNameMap() {
    return array(
      self::LINT_INCORRECT_TAB => 'Incorrect tab usage',
    );
  }

  public function lintPath($path) {
    if ($this->didStopAllLinters()) {
      return;
    }

    if (!$this->shouldLintBinaryFiles() &&
        $this->GetEngine()->isBinaryFile($path)) {
      return;
    }

    $this->fileData = $this->getData($path);

    if (strpos($this->fileData, "\t")) {
      // There are a few files in storage/innobase that don't use tabs.
      // Only check for correct usage of tabs if there is at least one
      // tab in the file.
      $this->lintInnoDBTabs();
    }
  }

  private function move_to_tabstop($pos) {
    $past_tabstop = $pos % self::LINT_TABSTOP;
    if ($past_tabstop > 0) {
      $pos += self::LINT_TABSTOP - $past_tabstop;
    }

    return $pos;
  }

  private function tabify($line) {
    $adjusted = "";    // Adjust result
    $whitespace = "";  // Buffer holding recently seen whitespace chars
    $pos = 0;          // Current position (when tabs are considered)

    $len = strlen($line);
    for ($ii = 0; $ii < $len; $ii++) {
      $char = $line[$ii];
      // Whenever the current position is at a tabstop we need to see if we
      // need to make any adjustments.
      if ($pos % self::LINT_TABSTOP == 0) {
        $wslen = strlen($whitespace);
        if ($wslen > 1 || ($wslen > 0 && ($char == "\t" || $char == " "))) {
          // If we have seen 2 or more whitespace chars or a single whitespace
          // char and the next char is also a whitespace char then replace the
          // seen whitespace chars with a tab.
          $adjusted .= "\t";
        } else {
          // Otherwise just append whatever whitespace char we have seen.
          $adjusted .= $whitespace;
        }

        // Clear out the seen whitespace.
        $whitespace = "";
      }

      $pos++;
      if ($char == "\t") {
        // The current char is a tab - append it to the seen whitespace chars
        // and adjust the position to the next tab stop.
        $whitespace .= "\t";
        $pos = $this->move_to_tabstop($pos);
      } else if ($char == " ") {
        // The current char is a space - append it to the seen whitespace and
        // increment the position.
        $whitespace .= " ";
      } else {
        // The current char is not a tab or space - append any seen
        // whitespace chars and the current char.  Clear out the seen
        // whitespace and increment the position.
        $adjusted .= $whitespace . $char;
        $whitespace = "";
      }
    }

    // Add any seen whitespace to the end.  This linter is not attempting to
    // notify about extra whitespace on the end of a line.  Other linters can
    // handle that job.
    $adjusted .= $whitespace;

    return $adjusted;
  }

  private function lintInnoDBTabs() {
    $lines = explode("\n", $this->fileData);
    foreach ($lines as $line_idx => $line) {
      // Replace spaces with tabs where it is expected.  Compare the result
      // with the original.  If there are differences then the line does not
      // follow the convention and is flagged.
      $tabified = $this->tabify($line);
      if ($line !== $tabified) {
        $this->raiseLintAtLine($line_idx + 1, 1, self::LINT_INCORRECT_TAB,
          'This line does not use tabs in the correct convention',
          $line, $tabified);
      }
    }
  }
}


<?php
// Copyright 2015, Facebook, Inc.  All rights reserved.
// @author: Gunnar Kudrjavets (gunnarku@fb.com)

class FacebookMySQLAssertUsageLinter extends ArcanistLinter {
  const LINT_ASSERT = 1;

  public function willLintPaths(array $paths) {
    return;
  }

  public function getLinterName() {
    return 'MySQLAssert';
  }

  public function getLintSeverityMap() {
    return array(
      self::LINT_ASSERT
        => ArcanistLintSeverity::SEVERITY_ADVICE,
    );
  }

  public function getLintNameMap() {
    return array(
      self::LINT_ASSERT
        => 'Assertion macro usage',
    );
  }

  public function lintPath($path) {
    if ($this->didStopAllLinters()) {
      return;
    }

    $lines = explode("\n", $this->getData($path));

    foreach ($lines as $line_idx => $line) {
      $trimmedLine = trim($line);

      // We want to make sure that macros or type definitions like
      // mysql_mutex_assert_owner or compile_time_assert won't be flagged
      // by this rule and we'll have an opportunity to turn the warning off
      // as well when needed.

      // NO_LINT_DEBUG comment can be used to turn off this advice.
      if (($line_idx > 0 &&
          strpos($lines[$line_idx - 1], 'NO_LINT_DEBUG') === false) &&
          !(preg_match('/[\t ]*static_assert[\t ]*\(/', $trimmedLine)) &&
          (preg_match('/[\t ]*assert[\t ]*\(/', $trimmedLine) === 1)) {
        $this->raiseLintAtLine(
          $line_idx + 1,
          1,
          self::LINT_ASSERT,
          'Please use DBUG_ASSERT macro instead of assert() macro. '.
          'DBUG_ASSERT is a standard MySQL debugging macro and this way '.
          'the code will look more unified. Once you have made the changes '.
          'then please verify them with a release build. '.
          'You can suppress this advice by adding a comment NO_LINT_DEBUG '.
          'above this line.',
          $line);
      }
    }
  }
}


<?php
// Copyright 2013-present Facebook.  All rights reserved.
// @author: rahulgulati (rahulgulati@fb.com)

final class FacebookMySQLLinter extends ArcanistLinter {

  const LINT_DOS_NEWLINE         = 1;
  const LINT_LINE_WRAP           = 2;
  const LINT_EOF_NEWLINE         = 3;
  const LINT_TRAILING_WHITESPACE = 4;

  private $maxLineLength = 80;
  private $fileData;

  public function setMaxLineLength($new_length) {
    $this->maxLineLength = $new_length;
    return $this;
  }

  public function willLintPaths(array $paths) {
    return;
  }

  public function getLinterName() {
    return 'MYSQL';
  }

  public function getLintSeverityMap() {
    return array(
      self::LINT_DOS_NEWLINE
        => ArcanistLintSeverity::SEVERITY_WARNING,
      self::LINT_LINE_WRAP
        => ArcanistLintSeverity::SEVERITY_WARNING,
      self::LINT_EOF_NEWLINE
        => ArcanistLintSeverity::SEVERITY_WARNING,
      self::LINT_TRAILING_WHITESPACE
        => ArcanistLintSeverity::SEVERITY_AUTOFIX,
      );
  }

  public function getLintNameMap() {
    return array(
      self::LINT_DOS_NEWLINE
        => 'DOS Newlines',
      self::LINT_LINE_WRAP
        => 'Line Too Long',
      self::LINT_EOF_NEWLINE
        => 'File Does Not End in Newline',
      self::LINT_TRAILING_WHITESPACE
        => 'Trailing Whitespace',
    );
  }

  public function lintPath($path) {
    if ($this->didStopAllLinters()) {
      return;
    }

    if (!$this->shouldLintBinaryFiles() &&
        $this->GetEngine()->isBinaryFile($path)) {
      return;
    }

    $this->fileData = $this->getData($path);

    if (!strlen($this->fileData)) {
      /* If the file is empty, then we don't require to
      add a new line */
      return;
    }

    $this->lintNewLines();
    $this->lintLineLength();
    $this->lintEOFNewline();
    $this->lintTrailingWhitespace();
  }

  protected function lintNewlines() {
    $pos = strpos($this->fileData, "\r");
    if ($pos !== false) {
      $this->raiseLintAtOffset(
      $pos,
      self::LINT_DOS_NEWLINE,
      'You must only use UNIX linebreaks ("\n") in source code',
      "\r");
    }
  }

  protected function lintLineLength() {
    $lines = explode("\n", $this->fileData);
    $width = $this->maxLineLength;
    $tabLength = 8;
    foreach ($lines as $line_idx => $line) {
      $lineLength = 0;
      $stringLength = strlen($line);
      for ($i = 0; $i < $stringLength; $i++) {
        $curr = $line[$i];
        if ($curr === "\t") {
          $lineLength += $tabLength;
          $lineLength -= ($lineLength % $tabLength);
        }
        else
          $lineLength++;
      }
      if ($lineLength > $width) {
        $this->raiseLintAtLine(
        $line_idx + 1,
        1,
        self::LINT_LINE_WRAP,
        'This line is '.$lineLength.' characters long, '.
        'but the convention is '.$width.' characters.',
        $line);
      }
    }
  }

  protected function lintEOFNewline() {
    $len = strlen($this->fileData);
    if (!$len || $this->fileData[$len - 1] != "\n") {
      $this->raiseLintAtOffset(
        $len,
        self::LINT_EOF_NEWLINE,
        "Files must end in a newline.",
        '',
        "\n");
    }
  }

  protected function lintTrailingWhitespace() {
    $matches = null;
    $preg = preg_match_all(
      '/[ \t]+$/m',
      $this->fileData,
      $matches,
      PREG_OFFSET_CAPTURE);

    if (!$preg) {
      return;
    }

    foreach($matches[0] as $match) {
      list($string, $offset) = $match;
      $this->raiseLintAtOffset(
        $offset,
        self::LINT_TRAILING_WHITESPACE,
        'This line contains trailing whitespace.',
        $string,
        '');

    }
  }
}


<?php
// Copyright 2015, Facebook, Inc.  All rights reserved.
// @author: Santosh Banda (santoshb@fb.com)

class FacebookMySQLPrintfLinter extends ArcanistLinter {
  const LINT_PRINTF = 1;

  public function willLintPaths(array $paths) {
    return;
  }

  public function getLinterName() {
    return 'MySQLPrintf';
  }

  public function getLintSeverityMap() {
    return array(
      self::LINT_PRINTF
        => ArcanistLintSeverity::SEVERITY_ADVICE,
    );
  }

  public function getLintNameMap() {
    return array(
      self::LINT_PRINTF
        => 'Error log output',
    );
  }

  public function lintPath($path) {
    if ($this->didStopAllLinters()) {
      return;
    }
    $lines = explode("\n", $this->getData($path));
    foreach ($lines as $line_idx => $line) {
      $trimmedLine = trim($line);
      // NO_LINT_DEBUG comment can be used to turn off this advice.
      if (($line_idx > 0 &&
          strpos($lines[$line_idx-1], 'NO_LINT_DEBUG') === false) &&
          (preg_match('/fprintf[\t\ ]*\([\t\ ]*stderr[.]*/', $trimmedLine) === 1
          || strpos($trimmedLine, 'sql_print_') === 0)) {
        $this->raiseLintAtLine(
          $line_idx + 1,
          1,
          self::LINT_PRINTF,
          'Please ensure this debug output is not going to flood error log. '.
          'Ignore this advice if you think this is safe. You can suppress '.
          'this advice by adding a comment NO_LINT_DEBUG above this line.',
          $line);
      }
    }
  }
}


<?php
// Copyright 2016, Facebook, Inc.  All rights reserved.
// @author: Jay Edgar (jkedgar@fb.com)

class FacebookMySQLTestResultLinter extends ArcanistLinter {

  const MISSING_RESULT = 1;
  const MISSING_TEST = 2;

  const TEST_FILE_REGEX = '/(.*mysql-test.*\/)t(\/.*)\.test$/';
  const RESULT_FILE_REGEX = '/(.*mysql-test.*\/)r(\/.*)\.result/';

  public function getLinterName() {
    return 'MySQLTestResult';
  }

  public function willLintPaths(array $paths) {
    return;
  }

  public function getLintSeverityMap() {
    return array(
      self::MISSING_RESULT => ArcanistLintSeverity::SEVERITY_ERROR,
      self::MISSING_TEST =>   ArcanistLintSeverity::SEVERITY_ERROR,
    );
  }

  public function getLintNameMap() {
    return array(
      self::MISSING_RESULT => 'Missing result file',
      self::MISSING_TEST =>   'Missing test file',
    );
  }

  public function lintPath($path) {
    $project_root = $this->getEngine()->getWorkingCopy()->getProjectRoot();

    if (preg_match_all(self::TEST_FILE_REGEX, $path, $matches)) {
      $result_path = $project_root . '/' . $matches[1][0] . 'r' .
        $matches[2][0] . '.result';
      if (!file_exists($result_path)) {
        $this->raiseLintAtPath(self::MISSING_RESULT,
          "The result file ($result_path) for $path is missing");
      }
    }
    elseif (preg_match_all(self::RESULT_FILE_REGEX, $path, $matches)) {
      $test_path = $project_root . '/' . $matches[1][0] . 't' .
        $matches[2][0] . '.test';
      if (!file_exists($test_path)) {
        $this->raiseLintAtPath(self::MISSING_TEST,
          "The test file ($test_path) for $path is missing");
      }
    }
  }

}


<?php
	require_once("./JSON.php");

	$filename = "./logs/analytics.log";
	$json = new Services_JSON;

	$id = $_REQUEST["id"];
	$items = $json->decode($_REQUEST["data"]);

	if (!$handle = fopen($filename, 'a+')) {
		print '{error: "server error"}';
		exit;
	}

	foreach($items as $i=>$item){
		$item->_analyticsId = $id;
		$item->_analyticsTimeStamp = time();
		$log = $json->encode($item) . "\n";
		fwrite($handle, $log);
	}

	fclose($handle);

	$response = "{'eventsRecieved': '" . sizeof($items) . "', 'id': '" . $id . "'}";
	if ($_REQUEST["callback"]){
		print htmlentities($_REQUEST["callback"]) . "(" . $response . ");";
	}else{
		print $response;
	}

?>


<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Converts to and from JSON format.
 *
 * JSON (JavaScript Object Notation) is a lightweight data-interchange
 * format. It is easy for humans to read and write. It is easy for machines
 * to parse and generate. It is based on a subset of the JavaScript
 * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
 * This feature can also be found in  Python. JSON is a text format that is
 * completely language independent but uses conventions that are familiar
 * to programmers of the C-family of languages, including C, C++, C#, Java,
 * JavaScript, Perl, TCL, and many others. These properties make JSON an
 * ideal data-interchange language.
 *
 * This package provides a simple encoder and decoder for JSON notation. It
 * is intended for use with client-side Javascript applications that make
 * use of HTTPRequest to perform server communication functions - data can
 * be encoded into JSON notation for use in a client-side javascript, or
 * decoded from incoming Javascript requests. JSON format is native to
 * Javascript, and can be directly eval()'ed with no further parsing
 * overhead
 *
 * All strings should be in ASCII or UTF-8 format!
 *
 * LICENSE: Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met: Redistributions of source code must retain the
 * above copyright notice, this list of conditions and the following
 * disclaimer. Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * @category
 * @package     Services_JSON
 * @author      Michal Migurski <mike-json@teczno.com>
 * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
 * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
 * @copyright   2005 Michal Migurski
 * @license     http://www.opensource.org/licenses/bsd-license.php
 * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
 */

/**
 * Marker constant for Services_JSON::decode(), used to flag stack state
 */
define('SERVICES_JSON_SLICE',   1);

/**
 * Marker constant for Services_JSON::decode(), used to flag stack state
 */
define('SERVICES_JSON_IN_STR',  2);

/**
 * Marker constant for Services_JSON::decode(), used to flag stack state
 */
define('SERVICES_JSON_IN_ARR',  4);

/**
 * Marker constant for Services_JSON::decode(), used to flag stack state
 */
define('SERVICES_JSON_IN_OBJ',  8);

/**
 * Marker constant for Services_JSON::decode(), used to flag stack state
 */
define('SERVICES_JSON_IN_CMT', 16);

/**
 * Behavior switch for Services_JSON::decode()
 */
define('SERVICES_JSON_LOOSE_TYPE', 10);

/**
 * Behavior switch for Services_JSON::decode()
 */
define('SERVICES_JSON_STRICT_TYPE', 11);

/**
 * Converts to and from JSON format.
 *
 * Brief example of use:
 *
 * <code>
 * // create a new instance of Services_JSON
 * $json = new Services_JSON();
 *
 * // convert a complexe value to JSON notation, and send it to the browser
 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
 * $output = $json->encode($value);
 *
 * print($output);
 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
 *
 * // accept incoming POST data, assumed to be in JSON notation
 * $input = file_get_contents('php://input', 1000000);
 * $value = $json->decode($input);
 * </code>
 */
class Services_JSON
{
   /**
    * constructs a new JSON instance
    *
    * @param    int     $use    object behavior: when encoding or decoding,
    *                           be loose or strict about object/array usage
    *
    *                           possible values:
    *                           - SERVICES_JSON_STRICT_TYPE: strict typing, default.
    *                                                        "{...}" syntax creates objects in decode().
    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
    *                                                        "{...}" syntax creates associative arrays in decode().
    */
    function Services_JSON($use = SERVICES_JSON_STRICT_TYPE)
    {
        $this->use = $use;
    }

   /**
    * convert a string from one UTF-16 char to one UTF-8 char
    *
    * Normally should be handled by mb_convert_encoding, but
    * provides a slower PHP-only method for installations
    * that lack the multibye string extension.
    *
    * @param    string  $utf16  UTF-16 character
    * @return   string  UTF-8 character
    * @access   private
    */
    function utf162utf8($utf16)
    {
        // oh please oh please oh please oh please oh please
        if(function_exists('mb_convert_encoding'))
            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');

        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});

        switch(true) {
            case ((0x7F & $bytes) == $bytes):
                // this case should never be reached, because we are in ASCII range
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                return chr(0x7F & $bytes);

            case (0x07FF & $bytes) == $bytes:
                // return a 2-byte UTF-8 character
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                return chr(0xC0 | (($bytes >> 6) & 0x1F))
                     . chr(0x80 | ($bytes & 0x3F));

            case (0xFFFF & $bytes) == $bytes:
                // return a 3-byte UTF-8 character
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                return chr(0xE0 | (($bytes >> 12) & 0x0F))
                     . chr(0x80 | (($bytes >> 6) & 0x3F))
                     . chr(0x80 | ($bytes & 0x3F));
        }

        // ignoring UTF-32 for now, sorry
        return '';
    }

   /**
    * convert a string from one UTF-8 char to one UTF-16 char
    *
    * Normally should be handled by mb_convert_encoding, but
    * provides a slower PHP-only method for installations
    * that lack the multibye string extension.
    *
    * @param    string  $utf8   UTF-8 character
    * @return   string  UTF-16 character
    * @access   private
    */
    function utf82utf16($utf8)
    {
        // oh please oh please oh please oh please oh please
        if(function_exists('mb_convert_encoding'))
            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');

        switch(strlen($utf8)) {
            case 1:
                // this case should never be reached, because we are in ASCII range
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                return $utf8;

            case 2:
                // return a UTF-16 character from a 2-byte UTF-8 char
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                return chr(0x07 & (ord($utf8{0}) >> 2))
                     . chr((0xC0 & (ord($utf8{0}) << 6))
                         | (0x3F & ord($utf8{1})));

            case 3:
                // return a UTF-16 character from a 3-byte UTF-8 char
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                return chr((0xF0 & (ord($utf8{0}) << 4))
                         | (0x0F & (ord($utf8{1}) >> 2)))
                     . chr((0xC0 & (ord($utf8{1}) << 6))
                         | (0x7F & ord($utf8{2})));
        }

        // ignoring UTF-32 for now, sorry
        return '';
    }

   /**
    * encodes an arbitrary variable into JSON format
    *
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
    *                           if var is a strng, note that encode() always expects it
    *                           to be in ASCII or UTF-8 format!
    *
    * @return   string  JSON string representation of input var
    * @access   public
    */
    function encode($var)
    {
        switch (gettype($var)) {
            case 'boolean':
                return $var ? 'true' : 'false';

            case 'NULL':
                return 'null';

            case 'integer':
                return (int) $var;

            case 'double':
            case 'float':
                return (float) $var;

            case 'string':
                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
                $ascii = '';
                $strlen_var = strlen($var);

               /*
                * Iterate over every character in the string,
                * escaping with a slash or encoding to UTF-8 where necessary
                */
                for ($c = 0; $c < $strlen_var; ++$c) {

                    $ord_var_c = ord($var{$c});

                    switch (true) {
                        case $ord_var_c == 0x08:
                            $ascii .= '\b';
                            break;
                        case $ord_var_c == 0x09:
                            $ascii .= '\t';
                            break;
                        case $ord_var_c == 0x0A:
                            $ascii .= '\n';
                            break;
                        case $ord_var_c == 0x0C:
                            $ascii .= '\f';
                            break;
                        case $ord_var_c == 0x0D:
                            $ascii .= '\r';
                            break;

                        case $ord_var_c == 0x22:
                        case $ord_var_c == 0x2F:
                        case $ord_var_c == 0x5C:
                            // double quote, slash, slosh
                            $ascii .= '\\'.$var{$c};
                            break;

                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
                            // characters U-00000000 - U-0000007F (same as ASCII)
                            $ascii .= $var{$c};
                            break;

                        case (($ord_var_c & 0xE0) == 0xC0):
                            // characters U-00000080 - U-000007FF, mask 110XXXXX
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
                            $c += 1;
                            $utf16 = $this->utf82utf16($char);
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
                            break;

                        case (($ord_var_c & 0xF0) == 0xE0):
                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                            $char = pack('C*', $ord_var_c,
                                         ord($var{$c + 1}),
                                         ord($var{$c + 2}));
                            $c += 2;
                            $utf16 = $this->utf82utf16($char);
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
                            break;

                        case (($ord_var_c & 0xF8) == 0xF0):
                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                            $char = pack('C*', $ord_var_c,
                                         ord($var{$c + 1}),
                                         ord($var{$c + 2}),
                                         ord($var{$c + 3}));
                            $c += 3;
                            $utf16 = $this->utf82utf16($char);
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
                            break;

                        case (($ord_var_c & 0xFC) == 0xF8):
                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                            $char = pack('C*', $ord_var_c,
                                         ord($var{$c + 1}),
                                         ord($var{$c + 2}),
                                         ord($var{$c + 3}),
                                         ord($var{$c + 4}));
                            $c += 4;
                            $utf16 = $this->utf82utf16($char);
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
                            break;

                        case (($ord_var_c & 0xFE) == 0xFC):
                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                            $char = pack('C*', $ord_var_c,
                                         ord($var{$c + 1}),
                                         ord($var{$c + 2}),
                                         ord($var{$c + 3}),
                                         ord($var{$c + 4}),
                                         ord($var{$c + 5}));
                            $c += 5;
                            $utf16 = $this->utf82utf16($char);
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
                            break;
                    }
                }

                return '"'.$ascii.'"';

            case 'array':
               /*
                * As per JSON spec if any array key is not an integer
                * we must treat the the whole array as an object. We
                * also try to catch a sparsely populated associative
                * array with numeric keys here because some JS engines
                * will create an array with empty indexes up to
                * max_index which can cause memory issues and because
                * the keys, which may be relevant, will be remapped
                * otherwise.
                *
                * As per the ECMA and JSON specification an object may
                * have any string as a property. Unfortunately due to
                * a hole in the ECMA specification if the key is a
                * ECMA reserved word or starts with a digit the
                * parameter is only accessible using ECMAScript's
                * bracket notation.
                */

                // treat as a JSON object
                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
                    return '{' .
                           join(',', array_map(array($this, 'name_value'),
                                               array_keys($var),
                                               array_values($var)))
                           . '}';
                }

                // treat it like a regular array
                return '[' . join(',', array_map(array($this, 'encode'), $var)) . ']';

            case 'object':
                $vars = get_object_vars($var);
                return '{' .
                       join(',', array_map(array($this, 'name_value'),
                                           array_keys($vars),
                                           array_values($vars)))
                       . '}';

            default:
                return '';
        }
    }

   /**
    * array-walking function for use in generating JSON-formatted name-value pairs
    *
    * @param    string  $name   name of key to use
    * @param    mixed   $value  reference to an array element to be encoded
    *
    * @return   string  JSON-formatted name-value pair, like '"name":value'
    * @access   private
    */
    function name_value($name, $value)
    {
        return $this->encode(strval($name)) . ':' . $this->encode($value);
    }

   /**
    * reduce a string by removing leading and trailing comments and whitespace
    *
    * @param    $str    string      string value to strip of comments and whitespace
    *
    * @return   string  string value stripped of comments and whitespace
    * @access   private
    */
    function reduce_string($str)
    {
        $str = preg_replace(array(

                // eliminate single line comments in '// ...' form
                '#^\s*//(.+)$#m',

                // eliminate multi-line comments in '/* ... */' form, at start of string
                '#^\s*/\*(.+)\*/#Us',

                // eliminate multi-line comments in '/* ... */' form, at end of string
                '#/\*(.+)\*/\s*$#Us'

            ), '', $str);

        // eliminate extraneous space
        return trim($str);
    }

   /**
    * decodes a JSON string into appropriate variable
    *
    * @param    string  $str    JSON-formatted string
    *
    * @return   mixed   number, boolean, string, array, or object
    *                   corresponding to given JSON input string.
    *                   See argument 1 to Services_JSON() above for object-output behavior.
    *                   Note that decode() always returns strings
    *                   in ASCII or UTF-8 format!
    * @access   public
    */
    function decode($str)
    {
        $str = $this->reduce_string($str);

        switch (strtolower($str)) {
            case 'true':
                return true;

            case 'false':
                return false;

            case 'null':
                return null;

            default:
                if (is_numeric($str)) {
                    // Lookie-loo, it's a number

                    // This would work on its own, but I'm trying to be
                    // good about returning integers where appropriate:
                    // return (float)$str;

                    // Return float or int, as appropriate
                    return ((float)$str == (integer)$str)
                        ? (integer)$str
                        : (float)$str;

                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
                    // STRINGS RETURNED IN UTF-8 FORMAT
                    $delim = substr($str, 0, 1);
                    $chrs = substr($str, 1, -1);
                    $utf8 = '';
                    $strlen_chrs = strlen($chrs);

                    for ($c = 0; $c < $strlen_chrs; ++$c) {

                        $substr_chrs_c_2 = substr($chrs, $c, 2);
                        $ord_chrs_c = ord($chrs{$c});

                        switch (true) {
                            case $substr_chrs_c_2 == '\b':
                                $utf8 .= chr(0x08);
                                ++$c;
                                break;
                            case $substr_chrs_c_2 == '\t':
                                $utf8 .= chr(0x09);
                                ++$c;
                                break;
                            case $substr_chrs_c_2 == '\n':
                                $utf8 .= chr(0x0A);
                                ++$c;
                                break;
                            case $substr_chrs_c_2 == '\f':
                                $utf8 .= chr(0x0C);
                                ++$c;
                                break;
                            case $substr_chrs_c_2 == '\r':
                                $utf8 .= chr(0x0D);
                                ++$c;
                                break;

                            case $substr_chrs_c_2 == '\\"':
                            case $substr_chrs_c_2 == '\\\'':
                            case $substr_chrs_c_2 == '\\\\':
                            case $substr_chrs_c_2 == '\\/':
                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
                                    $utf8 .= $chrs{++$c};
                                }
                                break;

                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
                                // single, escaped unicode character
                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
                                $utf8 .= $this->utf162utf8($utf16);
                                $c += 5;
                                break;

                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
                                $utf8 .= $chrs{$c};
                                break;

                            case ($ord_chrs_c & 0xE0) == 0xC0:
                                // characters U-00000080 - U-000007FF, mask 110XXXXX
                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                                $utf8 .= substr($chrs, $c, 2);
                                ++$c;
                                break;

                            case ($ord_chrs_c & 0xF0) == 0xE0:
                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                                $utf8 .= substr($chrs, $c, 3);
                                $c += 2;
                                break;

                            case ($ord_chrs_c & 0xF8) == 0xF0:
                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                                $utf8 .= substr($chrs, $c, 4);
                                $c += 3;
                                break;

                            case ($ord_chrs_c & 0xFC) == 0xF8:
                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                                $utf8 .= substr($chrs, $c, 5);
                                $c += 4;
                                break;

                            case ($ord_chrs_c & 0xFE) == 0xFC:
                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
                                $utf8 .= substr($chrs, $c, 6);
                                $c += 5;
                                break;

                        }

                    }

                    return $utf8;

                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
                    // array, or object notation

                    if ($str{0} == '[') {
                        $stk = array(SERVICES_JSON_IN_ARR);
                        $arr = array();
                    } else {
                        if ($this->use == SERVICES_JSON_LOOSE_TYPE) {
                            $stk = array(SERVICES_JSON_IN_OBJ);
                            $obj = array();
                        } else {
                            $stk = array(SERVICES_JSON_IN_OBJ);
                            $obj = new stdClass();
                        }
                    }

                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
                                           'where' => 0,
                                           'delim' => false));

                    $chrs = substr($str, 1, -1);
                    $chrs = $this->reduce_string($chrs);

                    if ($chrs == '') {
                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
                            return $arr;

                        } else {
                            return $obj;

                        }
                    }

                    //print("\nparsing {$chrs}\n");

                    $strlen_chrs = strlen($chrs);

                    for ($c = 0; $c <= $strlen_chrs; ++$c) {

                        $top = end($stk);
                        $substr_chrs_c_2 = substr($chrs, $c, 2);

                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
                            // found a comma that is not inside a string, array, etc.,
                            // OR we've reached the end of the character list
                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");

                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
                                // we are in an array, so just push an element onto the stack
                                array_push($arr, $this->decode($slice));

                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
                                // we are in an object, so figure
                                // out the property name and set an
                                // element in an associative array,
                                // for now
                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
                                    // "name":value pair
                                    $key = $this->decode($parts[1]);
                                    $val = $this->decode($parts[2]);

                                    if ($this->use == SERVICES_JSON_LOOSE_TYPE) {
                                        $obj[$key] = $val;
                                    } else {
                                        $obj->$key = $val;
                                    }
                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
                                    // name:value pair, where name is unquoted
                                    $key = $parts[1];
                                    $val = $this->decode($parts[2]);

                                    if ($this->use == SERVICES_JSON_LOOSE_TYPE) {
                                        $obj[$key] = $val;
                                    } else {
                                        $obj->$key = $val;
                                    }
                                }

                            }

                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
                            // found a quote, and we are not inside a string
                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
                            //print("Found start of string at {$c}\n");

                        } elseif (($chrs{$c} == $top['delim']) &&
                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
                                 (($chrs{$c - 1} != '\\') ||
                                 ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) {
                            // found a quote, we're in a string, and it's not escaped
                            array_pop($stk);
                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");

                        } elseif (($chrs{$c} == '[') &&
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
                            // found a left-bracket, and we are in an array, object, or slice
                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
                            //print("Found start of array at {$c}\n");

                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
                            // found a right-bracket, and we're in an array
                            array_pop($stk);
                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");

                        } elseif (($chrs{$c} == '{') &&
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
                            // found a left-brace, and we are in an array, object, or slice
                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
                            //print("Found start of object at {$c}\n");

                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
                            // found a right-brace, and we're in an object
                            array_pop($stk);
                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");

                        } elseif (($substr_chrs_c_2 == '/*') &&
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
                            // found a comment start, and we are in an array, object, or slice
                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
                            $c++;
                            //print("Found start of comment at {$c}\n");

                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
                            // found a comment end, and we're in one now
                            array_pop($stk);
                            $c++;

                            for ($i = $top['where']; $i <= $c; ++$i)
                                $chrs = substr_replace($chrs, ' ', $i, 1);

                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");

                        }

                    }

                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
                        return $arr;

                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
                        return $obj;

                    }

                }
        }
    }

}

?>


<?php

// THIS IS AN EXAMPLE
// you will obviously need to do more server side work than I am doing here to check and move your upload.
// API is up for discussion, jump on http://dojotoolkit.org/forums

// JSON.php is available in dojo svn checkout
require("../../../dojo/tests/resources/JSON.php");
$json = new Services_JSON();

// fake delay
sleep(3);
$name = empty($_REQUEST['name'])? "default" : $_REQUEST['name'];
if(is_array($_FILES)){
	$ar = array(
		// lets just pass lots of stuff back and see what we find.
		// the _FILES aren't coming through in IE6 (maybe 7)
		'status' => "success",
		'name' => $name,
		'request' => $_REQUEST,
		'postvars' => $_POST,
		'details' => $_FILES,
		// and some static subarray just to see
		'foo' => array('foo'=>"bar")
	);

}else{
	$ar = array(
		'status' => "failed",
		'details' => ""
	);
}

// yeah, seems you have to wrap iframeIO stuff in textareas?
$foo = $json->encode($ar);
?>
<textarea><?php print $foo; ?></textarea>


<?php // IF you don't have PHP5 installed, you can't use this index! ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
	"http://www.w3.org/TR/html4/strict.dtd">

	<link rel="stylesheet" href="../../dijit/tests/css/dijitTests.css">
	<link rel="stylesheet" href="../../dijit/themes/tundra/tundra.css">
	<link rel="stylesheet" href="../../dojox/widget/Dialog/Dialog.css">

	<title>Dojo Toolkit - DojoX Demos and Tests by Project</title>

DojoX test files overview

Test	Tundra	Nihilo	Soria

Normal	a11y	rtl	a11y + rtlNormal	a11y	rtl	a11y + rtlNormal	a11y	rtl	a11y + rtl

<?php

			foreach(getprojects() as $project){
				$note = "";
				if($project['readme']){
					$note = "about";
				}
				if($project['tests'] || $project['demos']){
					print "
dojox.". $project['name'] . " ".$note."
";

					if($project['tests']){
						printLinks($project['tests']);
					}
					if($project['demos']){
						printLinks($project['demos']);
					}

				}

			}
		//	printLinks("./tree","Dijit Tree Tests");

		?>

* note: All themes and modes included for completeness. Some projects don't even use themes. The "basic" link
		is the direct link to the file with no enhancements. The test file must include _testCommon from dijit to
		include theme / rtl / a11y testing capabilities.

<?php

function getprojects(){
	$projects = array();
	$path = "../../dojox";
	$handle = opendir($path);
	while(false !== ($file = readdir($handle))){
		$README = "";
		$full = $path."/".$file;
		if(is_dir($full)){
			if(file_exists($full."/README")){
				$README = $full."/README";
			}
			$tests = false; $demos = false;
			if(is_dir($full."/tests")){
				$tests = $full."/tests";
			}
			if(is_dir($full."/demos")){
				$demos = $full."/demos";
			}
			$projects[] = array(
				"name" => $file,
				"tests" => $tests,
				"demos" => $demos,
				"readme" => $README
			);
		}
	}
	return $projects;
}

function printLinks($path){
	$handle = opendir($path);
	$i = 0;

	while(false !== ($file = readdir($handle))){
		if(preg_match("/([a-zA-Z])(.*)\.html/", $file, $matches)){
			$base = $matches[0];
			$link = $path."/".$matches[0];
			print
			"
" .

				"" . $base . "	" .

			    // standard / tundra:
				"basic	" .
				"a11y	" .
				"rtl	" .
				"both	" .

				// nihilo
				"nihilo	" .
				"a11y	" .
				"rtl	" .
				"combo	" .

				// soria
				"soria	" .
				"a11y	" .
				"rtl	" .
				"combo	" .

			 "
";
		}
	}
}

?>


<?php
/*
 *  Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

if (!defined('__ROOT__')) {
  define('__ROOT__', realpath(dirname(__FILE__)));
}
require_once(__ROOT__.'/ThreatExchangeConfig.php');
ThreatExchangeConfig::init();

abstract class BaseSearch {

  public abstract function getEndpoint();

  public abstract function getResultsAsCSV($results);

  public static function getSearcher($name) {
    switch ($name) {
      case 'malware':
        return new MalwareSearch();
      case 'families':
        return new MalwareFamilySearch();
      case 'indicators':
        return new IndicatorSearch();
      default:
        throw new Exception(
          'Search type (-t) must be one of: families, indicators or malware'
        );
    }
  }

  public function getUsage() {
    return "\t-q query string\n".
      "\t-m strict match the query string (optional)\n".
      "\t-s beginning timestamp or date time string for a time windowed query\n".
      "\t-u ending timestamp or date time string for a time windowed query\n";
  }

  public function getOptions() {
    $options = array(
      'm' => 'strict_text',
      'q' => 'text',
      's' => 'since',
      'u' => 'until',
    );
    return array_merge($options, $this->getAdditionalOptions());
  }

  public function hasValidOptions($options) {
    if (isset($options['h'])) {
      return false;
    } else if (!(isset($options['q']) xor isset($options['f'])) &&
               !isset($options['s']) &&
               !isset($options['u'])) {
      return false;
    }
    return true;
  }

  public function getAdditionalOptions() {
    return array();
  }

  public function buildAPIRequest($query_params, $optional_params=array()) {
    $uri = ThreatExchangeConfig::FACEBOOK_SERVER.$this->getEndpoint().'/?';

    // build the param array
    $params = array (
      'access_token' => ThreatExchangeConfig::getAccessToken(),
    );

    if (isset($query_params['q'])) {
      $params['text'] = $query_params['q'];
    }
    if (isset($query_params['m'])) {
      $params['strict_text'] = true;
    }
    if (isset($query_params['s'])) {
      $params['since'] = $query_params['s'];
    }
    if (isset($query_params['u'])) {
      $params['until'] = $query_params['u'];
    }

    foreach ($optional_params as $cli_opt => $param_name) {
      if (isset($query_params[$cli_opt])) {
        $params[$param_name] = $query_params[$cli_opt];
      }
    }

    $param_str = http_build_query($params);
    $uri .= $param_str;

    return $uri;
  }
}


<?php
/*
 *  Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

if (!defined('__ROOT__')) {
  define('__ROOT__', realpath(dirname(__FILE__)));
}
require_once(__ROOT__.'/ThreatExchangeConfig.php');
ThreatExchangeConfig::init();

abstract class BaseUpload {

  private $fields = null;

  public abstract function getEndpoint();

  protected abstract function getTypeSpecificFields();

  protected abstract function getPostDataFromCSV($data_row);

  public function getFields() {
    if (!$this->fields) {
      $this->fields = array(
        'confidence',
        'description',
        'privacy_type',
        'privacy_members',
        'severity',
        'share_level',
        'status',
      );
      $this->fields = array_merge(
        $this->fields,
        $this->getTypeSpecificFields()
      );
    }
    return $this->fields;
  }

  public static function getUploader($name) {
    switch ($name) {
      case 'indicators':
        return new IndicatorUpload();
      case 'malware':
      case 'families':
        throw new Exception(
          'Malware and Malware Family uploads are not supported yet.'
        );
      default:
        throw new Exception(
          'Search type (-t) must be one of: families, indicators or malware'
        );
    }
  }

  public function getUsage() {
    return "\t-f file containing data to upload (CSV format)\n".
      "\t-d default description, if none provided\n".
      "\t-g tag to prepend to all descriptions\n".
      "\t-m default members for privacy, if none provided\n".
      "\t-p default privacy, if none provided\n";
  }

  public function hasValidOptions($options) {
    return !isset($options['h']) && isset($options['f']);
  }

  public function parseDataFile($file_name, $options) {
    if (!file_exists($file_name)) {
      throw new Exception(
        "ERROR: Unable to find/read file '$file_name'\n"
      );
    }
    $fh = fopen($file_name, "r");
    $header = null;
    $entries = array();
    $def_privacy_data = $this->getPrivacyDataFromOptions($options);
    $def_description = (isset($options['d']))
      ? $options['d']
      : 'No description provided';
    $def_tag = (isset($options['g'])) ? $options['g'] : '';
    while (($row = fgetcsv($fh)) !== FALSE) {
      if (!count($row) || (count($row) === 1 && !trim($row[0]))) {
        continue; // skip empty rows / queries
      }
      if (strpos($row[0], '#') === 0) {
        continue; // skip lines with comments
      }

      if (!$header) {
        $header = $row;
        continue;
      }

      $post_data = $this->getPostDataFromCSV(array_combine($header, $row));

      // fold in the defaults provided
      if (!isset($post_data['description'])) {
        $post_data['description'] = $def_description;
      }
      if ($def_tag) {
        $post_data['description'] = '['.$def_tag.'] '.$post_data['description'];
      }
      if (!isset($post_data['privacy_type']) && count($def_privacy_data) > 0) {
        $post_data = array_merge($post_data, $def_privacy_data);
      }

      $entries[] = $post_data;
    }
    return $entries;
  }

  private function getPrivacyDataFromOptions($options) {
    $privacy_data = array();
    if (isset($options['p'])) {
      switch(strtoupper(trim($options['p']))) {
        case 'HAS_WHITELIST':
        case 'WHITELIST':
          $privacy_data['privacy_type'] = 'HAS_WHITELIST';
          break;
        default:
          throw new Exception(
            'You must set a privacy type!'
          );
          break;
      }
    }
    if (isset($options['m'])) {
      $privacy_data['privacy_members'] = $options['m'];
    }
    return $privacy_data;
  }

  public function buildAPIUploadRequest() {
    $uri = ThreatExchangeConfig::FACEBOOK_SERVER.$this->getEndpoint().'/?';

    // build the param array
    $params = array (
      'access_token' => ThreatExchangeConfig::getAccessToken(),
    );
    $param_str = http_build_query($params);
    $uri .= $param_str;

    return $uri;
  }

  public function getResultsAsCSV($results) {
    $csv = "# ThreatExchange Results - uploaded at ".strftime('%F %T (%z)').
      "\nid,error\n";
    foreach ($results as $result) {
      $csv .= isset($result['id']) ? $result['id'] : 'NO_ID';
      $csv .= ',';
      $csv .= isset($result['error']) ? $result['error'] : 'OK';
      $csv .= "\n";
    }
    return $csv;
  }
}


<?php
/*
 *  Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

if (!defined('__ROOT__')) {
  define('__ROOT__', realpath(dirname(__FILE__)));
}
require_once(__ROOT__.'/ThreatExchangeConfig.php');
ThreatExchangeConfig::init();

final class IndicatorSearch extends BaseSearch {

  public function getAdditionalOptions() {
    return array (
      'b' => 'type',
      'o' => 'owner',
      'r' => 'threat_type',
    );
  }

  public function getEndpoint() {
    return '/threat_descriptors';
  }

  public function getUsage() {
    return "\t-b type of indicators to search for (optional, see API docs for ".
        "full list)\n".
      "\t-m strict match the query string (optional)\n".
      "\t-o the ThreatExchange ID of the owner (optional)\n".
      "\t-r the threat type to search for (optional)\n".
      "\t-q query string\n".
      "\t-s beginning timestamp or date time string for a time windowed query\n".
      "\t-u ending timestamp or date time string for a time windowed query\n";
  }

  public function getResultsAsCSV($results) {
    $csv = "# ThreatExchange Results - queried at ".strftime('%c')."\n".
      "id,type,indicator,status,description,threat_type,owner,owner_name\n";
    foreach ($results as $result) {
      $row = array(
        $result ['id'],
	isset ( $result ['indicator'] ) ? $result ['indicator'] ['type'] : '',
	isset ( $result ['indicator'] ) ? $result ['indicator'] ['indicator'] : '',
	isset ( $result ['status'] ) ? $result ['status'] : '',
	isset ( $result ['description'] ) ? $result ['description'] : '',
	isset ( $result ['threat_type'] ) ? $result ['threat_type'] : '',
	isset ( $result ['owner'] ) ? $result ['owner'] ['id'] : '',
	isset ( $result ['owner'] ) ? $result ['owner'] ['name'] : ''
      );
      $csv .= implode(',', $row)."\n";
    }
    return $csv;
  }
}


<?php
/*
 *  Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

if (!defined('__ROOT__')) {
  define('__ROOT__', realpath(dirname(__FILE__)));
}
require_once(__ROOT__.'/ThreatExchangeConfig.php');
ThreatExchangeConfig::init();

final class IndicatorUpload extends BaseUpload {

  public function getEndpoint() {
    return '/threat_descriptors';
  }

  protected function getTypeSpecificFields() {
    return array(
      'expired_on',
      'indicator',
      'passwords',
      'type',
      'threat_type',
    );
  }

  protected function getPostDataFromCSV($data_row) {
    $post_data = array();
    foreach ($this->getFields() as $field_name) {
      if (isset($data_row[$field_name]) &&
          $data_row[$field_name] !== '' &&
          $data_row[$field_name] !== null) {
        $post_data[$field_name] = $data_row[$field_name];
      }
    }
    return $post_data;
  }
}


<?php
/*
 *  Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

if (!defined('__ROOT__')) {
  define('__ROOT__', realpath(dirname(__FILE__)));
}
require_once(__ROOT__.'/ThreatExchangeConfig.php');
ThreatExchangeConfig::init();

// Get the command line options
$options = getopt('b:f:hmq:s:u:t:');
if (!isset($options['t'])) {
  echo print_usage();
  exit(1);
}

// Load user details
$app_id = ThreatExchangeConfig::getAppID();
$app_secret = ThreatExchangeConfig::getAppSecret();

$searcher = BaseSearch::getSearcher($options['t']);
if (!$searcher->hasValidOptions($options)) {
  echo print_usage().$searcher->getUsage();
  exit(1);
}

// Build query sets
$requests = array();
if (isset($options['q']) ||
    (isset($options['s']) && isset($options['u']))) {
  $requests[] = $searcher->buildAPIRequest($options);
} else if (isset($options['f'])) {
  $queries = ThreatExchangeUtils::parseQueryFile($options['f']);
  foreach ($queries as $query) {
    $options['q'] = $query;
    $requests[] = $searcher->buildAPIRequest($options);
  }
} else {
  echo print_usage().$searcher->getUsage();
  exit(1);
}

// make the requests via Curl
$results = array();
foreach ($requests as $request) {
  try {
    $raw_result = ThreatExchangeUtils::makeCurlRequest($request);
    $json = json_decode(
      $raw_result,
      true,
      512,
      JSON_BIGINT_AS_STRING
    );
    if (isset($json['data'])) {
      $results = array_merge($results, $json['data']);
    }
  } catch (Exception $e) {
    echo $e->getMessage()."\n";
    exit(1);
  }
}
echo $searcher->getResultsAsCSV($results)."\n";

function print_usage() {
  return "usage: php threat_exchange/search/search.php\n".
    "\t-t search type, one of: 'families', 'indicators', or 'malware'\n".
    "\t-f query file (optional, use to bulk search instead of using -q)\n";
}


<?php
/*
 *  Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

if (!defined('__ROOT__')) {
  define('__ROOT__', realpath(dirname(__FILE__)));
}

/**
 * SET YOUR TX_APP_ID and TX_APP_SECRET ENV VARIABLES BEFORE CONTINUING!!!
 * VALUES FOR BOTH ARE AVAILABLE BY GOING TO
 * https://developers.facebook.com/apps/
 */
class ThreatExchangeConfig {

  const FACEBOOK_SERVER = 'https://graph.facebook.com/v2.4';

  private static $appID = 0;
  private static $appSecret = null;

  public static function getAppID() {
    if (!self::$appID) {
      throw new Exception(
        'Set the TX_APP_ID environment variable before continuing!'
      );
    }
    return self::$appID;
  }

  public static function getAppSecret() {
    if (!self::$appSecret) {
      throw new Exception(
        'Set the TX_APP_SECRET environment variable before continuing!'
      );
    }
    return self::$appSecret;
  }

  public static function getAccessToken() {
    return self::getAppID().'|'.self::getAppSecret();
  }

  public static function init() {
    // bootstraping method, forces call to __autoload()

    // load credentials from system environment variables
    self::$appID = $_ENV['TX_APP_ID'];
    self::$appSecret = $_ENV['TX_APP_SECRET'];
  }
}

/**
 * Handles auto-loading of PHP classes
 *
 * @see http://us1.php.net/manual/en/language.oop5.autoload.php
 */
function __autoload($class) {
  $dirs = array (
      __ROOT__,
      __ROOT__.'/search',
  );

  foreach ($dirs as $dir) {
    if (file_exists($dir.'/'.$class.'.php')) {
      require_once($dir.'/'.$class.'.php');
      return;
    }
  }
  throw new Exception('ERROR: Unable to load '.$class.' make sure the '.
    'directory where the class resides is listed in __autoload() in '.
    __ROOT__.'/'.__FILE__
  );
}


<?php
/*
 *  Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

if (!defined('__ROOT__')) {
  define('__ROOT__', realpath(dirname(__FILE__)));
}
require_once(__ROOT__.'/ThreatExchangeConfig.php');
ThreatExchangeConfig::init();

class ThreatExchangeUtils {

  private static $debugMode = false;

  public static function setDebugMode($debug_mode) {
    self::$debugMode = $debug_mode;
  }

  public static function isDebugMode() {
    return self::$debugMode;
  }

  /**
   * @throws Exceptions
   */
  public static function parseQueryFile($query_file) {
    if (!file_exists($query_file)) {
      throw new Exception(
        "ERROR: Unable to find/read query file '$query_file'\n"
      );
    }
    $fh = fopen($query_file, "r");
    $queries = array();
    while (($row = fgetcsv($fh)) !== FALSE) {
      if (!count($row) || (count($row) === 1 && !trim($row[0]))) {
        continue; // skip empty rows / queries
      }
      if (strpos($row[0], '#') === 0) {
        continue; // skip lines with comments
      }
      $queries[] = trim($row[0]);
    }
    return $queries;
  }

  /**
   * @throws Exception
   */
  public static function makeCurlRequest($uri) {
    $curler = curl_init($uri);
    curl_setopt($curler, CURLOPT_RETURNTRANSFER, true);
    $raw_json = curl_exec($curler);
    self::checkCurlResults($curler, $raw_json);
    curl_close($curler);

    if (self::isDebugMode()) {
      $json_ar = json_decode($json_string, true /* assoc array */);
      self::debugPrettyPrint($json_ar);
    }
    return $raw_json;
  }

  /**
   * @throws Exception
   */
  public static function makeCurlPostRequest($uri, $post_data) {
    $curler = curl_init($uri);
    curl_setopt($curler, CURLOPT_POST, true);
    curl_setopt($curler, CURLOPT_POSTFIELDS, http_build_query($post_data));
    curl_setopt($curler, CURLOPT_RETURNTRANSFER, true);
    $raw_json = curl_exec($curler);
    self::checkCurlResults($curler, $raw_json);
    curl_close($curler);

    if (self::isDebugMode()) {
      $json_ar = json_decode($raw_json, true /* assoc array */);
      self::debugPrettyPrint($json_ar);
    }
    return $raw_json;
  }

  /**
   * @throws Exception
   */
  protected function checkCurlResults($curler, $results) {
    $return_code = curl_getinfo($curler, CURLINFO_HTTP_CODE);
    if ($return_code !== 200) {
      throw new Exception(
        "CURL ERROR: $return_code, ".
        curl_error($curler).' - '.
        $results
      );
    }
  }

  public static function debugPrettyPrint($object) {
    var_dump($object);
  }

}


<?php
/*
 *  Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

if (!defined('__ROOT__')) {
  define('__ROOT__', realpath(dirname(__FILE__)));
}
require_once(__ROOT__.'/ThreatExchangeConfig.php');
ThreatExchangeConfig::init();

// Get the command line options
$options = getopt('d:f:g:hm:p:t:');
if (isset($options['h']) || !isset($options['t'])) {
  echo print_usage();
  exit(1);
}

// Load user details
$app_id = ThreatExchangeConfig::getAppID();
$app_secret = ThreatExchangeConfig::getAppSecret();

$uploader = BaseUpload::getUploader($options['t']);
if (!$uploader->hasValidOptions($options)) {
  echo print_usage().$uploader->getUsage();
  exit(1);
}

// Build POST data sets
$entries = $uploader->parseDataFile($options['f'], $options);

// make the requests via Curl
$results = array();
foreach ($entries as $request_uri => $post_data) {
  try {
    echo "Calling $request_uri with ".implode(',',$post_data)."\n";
    $raw_result = ThreatExchangeUtils::makeCurlPostRequest(
      $uploader->buildAPIUploadRequest(),
      $post_data
    );
    $json = json_decode(
      $raw_result,
      true,
      512,
      JSON_BIGINT_AS_STRING
    );
    $results[] = $json;
  } catch (Exception $e) {
    echo $e->getMessage()."\n";
  }
}
echo $uploader->getResultsAsCSV($results)."\n";

function print_usage() {
  return "usage: php threat_exchange/upload.php\n".
    "\t-t upload type, one of: 'families', 'indicators', or 'malware'\n".
    BaseUpload::getUsage();
}


<?php

// ================================================================
// Copyright (c) Meta Platforms, Inc. and affiliates.
// ================================================================

/**
 * A PDQ hash is simply 256 bits. The only string representation is 64 hex
 * digits. Bit-ordering is a concern, but is a non-issue as long as the
 * to-string and from-string methods in this class are consistent.
 */

class PDQHash {
  const PDQHASH_NUM_SLOTS = 16;
  const PDQHASH_HEX_LENGTH = 64;

  // 16 16-bit words: sliced this way for mutually indexed hashing (MIH).
  private $slots = array();

  /**
   * The constructor is private since the only valid representation is
   * hex-string. Yet we have a PDQHash class, not just strings, due to lessons
   * learned from other projects. Separating ser/des format (string) from
   * internal representation helps avoid false negatives.
   */
  private function __construct() {
  }

  public static function makeZeroesHash() {
    $hash = new PDQHash();
    for ($i = 0; $i < self::PDQHASH_NUM_SLOTS; $i++) {
      $hash->slots[$i] = 0;
    }
    return $hash;
  }

  /**
   * Creates a new instance of the hash from a hexadecimal-formatted PDQ
   * hash string. Throws MalformedPDQHashException for improperly
   * formatted hashes.
   *
   * @param hex_string string representation of the hash in 'hex' format.
   * @return PDQHash newly created hash.
   * @throws MalformedPDQHashException if the given hash is not valid.
   */

  public static function fromHexString(
    /*string*/ $hex_string
  )/*: PDQHash*/ {
    if (strlen($hex_string) !== self::PDQHASH_HEX_LENGTH) {
      throw new MalformedPDQHashException(
        $hex_string,
        sprintf(
          'Expected hash to have length %d, received hash with length %d',
          self::PDQHASH_HEX_LENGTH,
          strlen($hex_string)
        )
      );
    }

    $hash = new PDQHash();

    try {
      // Unpack goes 1-up not 0-up
      $slots = unpack('n*', hex2bin($hex_string));

    } catch (ErrorException $_) {
      throw new MalformedPDQHashException(
        $hex_string,
        'Length is 64 but is not pure hexadecimal.'
      );
    }

    $hash->slots = array();
    $k = self::PDQHASH_NUM_SLOTS - 1;
    foreach ($slots as $slot) {
      $hash->slots[$k] = $slot;
      $k--;
    }

    return $hash;
  }

  public function toHexString() {
    $string = "";
    for ($i = self::PDQHASH_NUM_SLOTS - 1; $i >= 0; $i--) {
      // dechex doesn't zero-pad
      $string .= sprintf("%04x", $this->slots[$i]);
    }
    return $string;
  }

  public function to64BitStrings() {
    $strings = array();
    for ($i = self::PDQHASH_NUM_SLOTS - 1; $i >= 0; $i-=4) {
      $strings[] =
        sprintf("%04x", $this->slots[$i]) .
        sprintf("%04x", $this->slots[$i-1]) .
        sprintf("%04x", $this->slots[$i-2]) .
        sprintf("%04x", $this->slots[$i-3]);
    }
    return $strings;
  }
  public function to32BitStrings() {
    $strings = array();
    for ($i = self::PDQHASH_NUM_SLOTS - 1; $i >= 0; $i-=2) {
      $strings[] =
        sprintf("%04x", $this->slots[$i]) .
        sprintf("%04x", $this->slots[$i-1]);
    }
    return $strings;
  }
  public function to16BitStrings() {
    $strings = array();
    for ($i = self::PDQHASH_NUM_SLOTS - 1; $i >= 0; $i--) {
      $strings[] = sprintf("%04x", $this->slots[$i]);
    }
    return $strings;
  }

  public function setBit(/*int*/ $bit_index) {
    $slot_index = (int)($bit_index / self::PDQHASH_NUM_SLOTS);
    $slot_bit_index = (int)($bit_index % self::PDQHASH_NUM_SLOTS);
    $this->slots[$slot_index] |= 1 << $slot_bit_index;
  }

  /**
   * The Hamming distance between this hash and another.
   *
   * @param The hash to compare this hash to.
   * @return int
   */
  public function hammingDistanceTo(PDQHash $that) /*: int*/ {
    $sum = 0;
    for ($i = 0; $i < self::PDQHASH_NUM_SLOTS; $i++) {
      $sum += self::popCount16($this->slots[$i] ^ $that->slots[$i]);
    }
    return $sum;
  }

  public function isWithinHammingDistanceOf(
    /*PDQHash*/ $that,
    /*int*/ $threshold
  )/*: bool*/ {
    $current = 0;
    for ($i = 0; $i < self::PDQHASH_NUM_SLOTS; $i++) {
      $current += self::popCount16($this->slots[$i] ^ $that->slots[$i]);
      if ($current > $threshold) {
        return false;
      }
    }
    return true;
  }

  public function hammingNorm()/*: int*/ {
    $sum = 0;
    for ($i = 0; $i < self::PDQHASH_NUM_SLOTS; $i++) {
      $sum += self::popCount16($this->slots[$i]);
    }
    return $sum;
  }

  private static function popCount16(/*int */$n)/*: int*/ {
    $n -= (($n >> 1) & 0x5555);
    $n = ((($n >> 2) & 0x3333) + ($n & 0x3333));
    $n = ((($n >> 4) + $n) & 0x0f0f);
    $n += ($n >> 8);
    return($n & 0x1f);
  }

  private static function popCount16Slow(/*int */$n)/*: int*/ {
    $count = 0;
    for ($count = 0; $n != 0; $count++, $n &= $n-1) { }
    return $count;
  }

} // class PDQHash