diff --git a/src/Core/Console.php b/src/Core/Console.php index f9c37dde1e..39e2941a32 100644 --- a/src/Core/Console.php +++ b/src/Core/Console.php @@ -95,8 +95,16 @@ HELP; break; case 'docbloxerrorchecker' : $subconsole = new Console\DocBloxErrorChecker($subargs); break; + case 'extract' : $subconsole = new Console\Extract($subargs); + break; case 'globalcommunityblock': $subconsole = new Console\GlobalCommunityBlock($subargs); break; + case 'globalcommunitysilence': $subconsole = new Console\GlobalCommunitySilence($subargs); + break; + case 'maintenance': $subconsole = new Console\Maintenance($subargs); + break; + case 'php2po': $subconsole = new Console\PhpToPo($subargs); + break; default: throw new \Asika\SimpleConsole\CommandArgsException('Command ' . $command . ' doesn\'t exist'); } diff --git a/src/Core/Console/Extract.php b/src/Core/Console/Extract.php new file mode 100644 index 0000000000..e6cab0654a --- /dev/null +++ b/src/Core/Console/Extract.php @@ -0,0 +1,140 @@ + + */ +class Extract extends \Asika\SimpleConsole\Console +{ + protected $helpOptions = ['h', 'help', '?']; + + protected function getHelp() + { + $help = <<getOption('v')) { + $this->out('Class: ' . __CLASS__); + $this->out('Arguments: ' . var_export($this->args, true)); + $this->out('Options: ' . var_export($this->options, true)); + } + + if (count($this->args) > 0) { + throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments'); + } + + $s = 'globRecursive('src') + ); + + foreach ($files as $file) { + $str = file_get_contents($file); + + $pat = '|L10n::t\(([^\)]*+)[\)]|'; + $patt = '|L10n::tt\(([^\)]*+)[\)]|'; + + $matches = []; + $matchestt = []; + + preg_match_all($pat, $str, $matches); + preg_match_all($patt, $str, $matchestt); + + if (count($matches) || count($matchestt)) { + $s .= '// ' . $file . PHP_EOL; + } + + if (!empty($matches[1])) { + foreach ($matches[1] as $long_match) { + $match_arr = preg_split('/(?<=[\'"])\s*,/', $long_match); + $match = $match_arr[0]; + if (!in_array($match, $arr)) { + if (substr($match, 0, 1) == '$') { + continue; + } + + $arr[] = $match; + + $s .= '$a->strings[' . $match . '] = ' . $match . ';' . "\n"; + } + } + } + if (!empty($matchestt[1])) { + foreach ($matchestt[1] as $match) { + $matchtkns = preg_split("|[ \t\r\n]*,[ \t\r\n]*|", $match); + if (count($matchtkns) == 3 && !in_array($matchtkns[0], $arr)) { + if (substr($matchtkns[1], 0, 1) == '$') { + continue; + } + + $arr[] = $matchtkns[0]; + + $s .= '$a->strings[' . $matchtkns[0] . "] = array(\n"; + $s .= "\t0 => " . $matchtkns[0] . ",\n"; + $s .= "\t1 => " . $matchtkns[1] . ",\n"; + $s .= ");\n"; + } + } + } + } + + $s .= '// Timezones' . PHP_EOL; + + $zones = timezone_identifiers_list(); + foreach ($zones as $zone) { + $s .= '$a->strings[\'' . $zone . '\'] = \'' . $zone . '\';' . "\n"; + } + + $this->out($s); + + return 0; + } + + private function globRecursive($path) { + $dir_iterator = new \RecursiveDirectoryIterator($path); + $iterator = new \RecursiveIteratorIterator($dir_iterator, \RecursiveIteratorIterator::SELF_FIRST); + + $return = []; + foreach ($iterator as $file) { + if ($file->getBasename() != '.' && $file->getBasename() != '..') { + $return[] = $file->getPathname(); + } + } + + return $return; + } +} diff --git a/src/Core/Console/GlobalCommunitySilence.php b/src/Core/Console/GlobalCommunitySilence.php new file mode 100644 index 0000000000..069750afc6 --- /dev/null +++ b/src/Core/Console/GlobalCommunitySilence.php @@ -0,0 +1,94 @@ + + */ +class GlobalCommunitySilence extends \Asika\SimpleConsole\Console +{ + protected $helpOptions = ['h', 'help', '?']; + + protected function getHelp() + { + $help = << [-h|--help|-?] [-v] + +Description + With this tool, you can silence an account on the global community page. + Postings from silenced accounts will not be displayed on the community page. + This silencing does only affect the display on the community page, accounts + following the silenced accounts will still get their postings. + +Options + -h|--help|-? Show help information + -v Show more debug information. +HELP; + return $help; + } + + protected function doExecute() + { + if ($this->getOption('v')) { + $this->out('Class: ' . __CLASS__); + $this->out('Arguments: ' . var_export($this->args, true)); + $this->out('Options: ' . var_export($this->options, true)); + } + + if (count($this->args) == 0) { + $this->out($this->getHelp()); + return 0; + } + + if (count($this->args) > 1) { + throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments'); + } + + require_once '.htconfig.php'; + $result = \dba::connect($db_host, $db_user, $db_pass, $db_data); + unset($db_host, $db_user, $db_pass, $db_data); + + if (!$result) { + throw new \RuntimeException('Unable to connect to database'); + } + + /** + * 1. make nurl from last parameter + * 2. check DB (contact) if there is a contact with uid=0 and that nurl, get the ID + * 3. set the flag hidden=1 for the contact entry with the found ID + * */ + $net = Probe::uri($this->getArgument(0)); + if (in_array($net['network'], [Protocol::PHANTOM, Protocol::MAIL])) { + throw new \RuntimeException('This account seems not to exist.'); + } + + $nurl = normalise_link($net['url']); + $contact = \dba::selectFirst("contact", ["id"], ["nurl" => $nurl, "uid" => 0]); + if (DBM::is_result($contact)) { + \dba::update("contact", ["hidden" => true], ["id" => $contact["id"]]); + $this->out('NOTICE: The account should be silenced from the global community page'); + } else { + throw new \RuntimeException('NOTICE: Could not find any entry for this URL (' . $nurl . ')'); + } + + return 0; + } +} diff --git a/src/Core/Console/Maintenance.php b/src/Core/Console/Maintenance.php new file mode 100644 index 0000000000..f9a4f9d2d2 --- /dev/null +++ b/src/Core/Console/Maintenance.php @@ -0,0 +1,121 @@ + util/global_community_silence.php http://example.com/profile/bob + * + * will silence bob@example.com so that his postings won't appear at + * the global community page. + * + * License: AGPLv3 or later, same as Friendica + * + * @author Tobias Diekershoff + * @author Hypolite Petovan + */ +class Maintenance extends \Asika\SimpleConsole\Console +{ + protected $helpOptions = ['h', 'help', '?']; + + protected function getHelp() + { + $help = << [] [-h|--help|-?] [-v] + +Description + cen be either 0 or 1 to disabled or enable the maintenance mode on this node. + + is a quote-enclosed string with the optional reason for the maintenance mode. + +Examples + bin/console maintenance 1 + Enables the maintenance mode without setting a reason message + + bin/console maintenance 1 "SSL certification update" + Enables the maintenance mode with setting a reason message + + bin/console maintenance 0 + Disables the maintenance mode + +Options + -h|--help|-? Show help information + -v Show more debug information. +HELP; + return $help; + } + + protected function doExecute() + { + if ($this->getOption('v')) { + $this->out('Class: ' . __CLASS__); + $this->out('Arguments: ' . var_export($this->args, true)); + $this->out('Options: ' . var_export($this->options, true)); + } + + if (count($this->args) == 0) { + $this->out($this->getHelp()); + return 0; + } + + if (count($this->args) > 2) { + throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments'); + } + + require_once '.htconfig.php'; + $result = \dba::connect($db_host, $db_user, $db_pass, $db_data); + unset($db_host, $db_user, $db_pass, $db_data); + + if (!$result) { + throw new \RuntimeException('Unable to connect to database'); + } + + Core\Config::load(); + + $lang = Core\L10n::getBrowserLanguage(); + Core\L10n::loadTranslationTable($lang); + + $enabled = intval($this->getArgument(0)); + + Core\Config::set('system', 'maintenance', $enabled); + + $reason = $this->getArgument(1); + + if ($enabled && $this->getArgument(1)) { + Core\Config::set('system', 'maintenance_reason', $this->getArgument(1)); + } else { + Core\Config::set('system', 'maintenance_reason', ''); + } + + if ($enabled) { + $mode_str = "maintenance mode"; + } else { + $mode_str = "normal mode"; + } + + $this->out('System set in ' . $mode_str); + + if ($enabled && $reason != '') { + $this->out('Maintenance reason: ' . $reason); + } + + return 0; + } + +} diff --git a/src/Core/Console/PhpToPo.php b/src/Core/Console/PhpToPo.php new file mode 100644 index 0000000000..e26ea88921 --- /dev/null +++ b/src/Core/Console/PhpToPo.php @@ -0,0 +1,241 @@ + + */ +class PhpToPo extends \Asika\SimpleConsole\Console +{ + + protected $helpOptions = ['h', 'help', '?']; + + private $normBaseMsgIds = []; + const NORM_REGEXP = "|[\\\]|"; + + protected function getHelp() + { + $help = <<] [-h|--help|-?] [-v] + +Options: + -p Number of plural forms/ Default: 2 + +Description + Read a strings.php file and create the according messages.po in the same directory + +Options + -h|--help|-? Show help information + -v Show more debug information. +HELP; + return $help; + } + + protected function doExecute() + { + if ($this->getOption('v')) { + $this->out('Class: ' . __CLASS__); + $this->out('Arguments: ' . var_export($this->args, true)); + $this->out('Options: ' . var_export($this->options, true)); + } + + if (count($this->args) == 0) { + $this->out($this->getHelp()); + return 0; + } + + if (count($this->args) > 1) { + throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments'); + } + + $a = get_app(); + + $phpfile = realpath($this->getArgument(0)); + + if (!file_exists($phpfile)) { + throw new \RuntimeException('Supplied file path doesn\'t exist.'); + } + + if (!is_writable(dirname($phpfile))) { + throw new \RuntimeException('Supplied directory isn\'t writable.'); + } + + $pofile = dirname($phpfile) . '/messages.po'; + + // start ! + include_once($phpfile); + + $out = ''; + $out .= "# FRIENDICA Distributed Social Network\n"; + $out .= "# Copyright (C) 2010, 2011, 2012, 2013 the Friendica Project\n"; + $out .= "# This file is distributed under the same license as the Friendica package.\n"; + $out .= "# \n"; + $out .= 'msgid ""' . "\n"; + $out .= 'msgstr ""' . "\n"; + $out .= '"Project-Id-Version: friendica\n"' . "\n"; + $out .= '"Report-Msgid-Bugs-To: \n"' . "\n"; + $out .= '"POT-Creation-Date: ' . date("Y-m-d H:i:sO") . '\n"' . "\n"; + $out .= '"MIME-Version: 1.0\n"' . "\n"; + $out .= '"Content-Type: text/plain; charset=UTF-8\n"' . "\n"; + $out .= '"Content-Transfer-Encoding: 8bit\n"' . "\n"; + + // search for plural info + $lang = ""; + $lang_logic = ""; + $lang_pnum = 2; + + $_idx = array_search('-p', $argv); + if ($_idx !== false) { + $lang_pnum = $argv[$_idx + 1]; + } + + $infile = file($phpfile); + foreach ($infile as $l) { + $l = trim($l); + if ($this->startsWith($l, 'function string_plural_select_')) { + $lang = str_replace('function string_plural_select_', '', str_replace('($n){', '', $l)); + } + if ($this->startsWith($l, 'return')) { + $lang_logic = str_replace('$', '', trim(str_replace('return ', '', $l), ';')); + break; + } + } + + $this->out('Language: ' . $lang); + $this->out('Plural forms: ' . $lang_pnum); + $this->out('Plural forms: ' . $lang_logic); + + $out .= sprintf('"Language: %s\n"', $lang) . "\n"; + $out .= sprintf('"Plural-Forms: nplurals=%s; plural=%s;\n"', $lang_pnum, $lang_logic) . "\n"; + $out .= "\n"; + + $this->out('Loading base message.po...'); + + // load base messages.po and extract msgids + $base_msgids = []; + $base_f = file("util/messages.po"); + if (!$base_f) { + throw new \RuntimeException('The base util/messages.po file is missing.'); + } + + $_f = 0; + $_mid = ""; + $_mids = []; + foreach ($base_f as $l) { + $l = trim($l); + + if ($this->startsWith($l, 'msgstr')) { + if ($_mid != '""') { + $base_msgids[$_mid] = $_mids; + $this->normBaseMsgIds[preg_replace(self::NORM_REGEXP, "", $_mid)] = $_mid; + } + + $_f = 0; + $_mid = ""; + $_mids = []; + } + + if ($this->startsWith($l, '"') && $_f == 2) { + $_mids[count($_mids) - 1] .= "\n" . $l; + } + if ($this->startsWith($l, 'msgid_plural ')) { + $_f = 2; + $_mids[] = str_replace('msgid_plural ', '', $l); + } + + if ($this->startsWith($l, '"') && $_f == 1) { + $_mid .= "\n" . $l; + $_mids[count($_mids) - 1] .= "\n" . $l; + } + if ($this->startsWith($l, 'msgid ')) { + $_f = 1; + $_mid = str_replace('msgid ', '', $l); + $_mids = [$_mid]; + } + } + + $this->out('Done.'); + $this->out('Creating ' . $pofile . '...'); + + // create msgid and msgstr + $warnings = ""; + foreach ($a->strings as $key => $str) { + $msgid = $this->massageString($key); + + if (preg_match("|%[sd0-9](\$[sn])*|", $msgid)) { + $out .= "#, php-format\n"; + } + $msgid = $this->findOriginalMsgId($msgid); + $out .= 'msgid ' . $msgid . "\n"; + + if (is_array($str)) { + if (array_key_exists($msgid, $base_msgids) && isset($base_msgids[$msgid][1])) { + $out .= 'msgid_plural ' . $base_msgids[$msgid][1] . "\n"; + } else { + $out .= 'msgid_plural ' . $msgid . "\n"; + $warnings .= "[W] No source plural form for msgid:\n" . str_replace("\n", "\n\t", $msgid) . "\n\n"; + } + foreach ($str as $n => $msgstr) { + $out .= 'msgstr[' . $n . '] ' . $this->massageString($msgstr) . "\n"; + } + } else { + $out .= 'msgstr ' . $this->massageString($str) . "\n"; + } + + $out .= "\n"; + } + + file_put_contents($pofile, $out); + + $this->out('Done.'); + + if ($warnings == "") { + $this->out('No warnings.'); + } else { + $this->out($warnings); + } + + return 0; + } + + private function startsWith($haystack, $needle) + { + // search backwards starting from haystack length characters from the end + return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE; + } + + /** + * Get a string and retun a message.po ready text + * - replace " with \" + * - replace tab char with \t + * - manage multiline strings + */ + private function massageString($str) + { + $str = str_replace('\\', '\\\\', $str); + $str = str_replace('"', '\"', $str); + $str = str_replace("\t", '\t', $str); + $str = str_replace("\n", '\n"' . "\n" . '"', $str); + if (strpos($str, "\n") !== false && $str[0] !== '"') { + $str = '"' . "\n" . $str; + } + + $str = preg_replace("|\n([^\"])|", "\n\"$1", $str); + return sprintf('"%s"', $str); + } + + private function findOriginalMsgId($str) + { + $norm_str = preg_replace(self::NORM_REGEXP, "", $str); + if (array_key_exists($norm_str, $this->normBaseMsgIds)) { + return $this->normBaseMsgIds[$norm_str]; + } + + return $str; + } + +}