Skip to content

Feat/zip import #227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 78 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
61ca999
feat: add filter hook for excluding files from parsing
EvanShaw Sep 23, 2020
e8d7bf9
Merge branch 'WordPress:master' into master
EvanShaw May 13, 2021
8efd3dd
feat: add wp-parser-source-type taxonomy with default terms. Parse do…
EvanShaw Aug 19, 2021
a405a55
fix: delete p2p relationships on a per-plugin/theme/package basis on …
EvanShaw Aug 20, 2021
b26f723
feat: add get_source_code_root_dir api-function
EvanShaw Aug 20, 2021
9841ed1
feat: move majority of API like functions from wporg-developer theme …
EvanShaw Aug 20, 2021
e3bc484
fix: fix hierarchical taxonomy cache for source-type not being cleare…
EvanShaw Aug 23, 2021
023614c
feat: add function that returns the 'plugin' source type term
EvanShaw Aug 23, 2021
2f3be94
feat: add rewrite rules for all wp-parser-* post types. Force 404 red…
EvanShaw Aug 25, 2021
6212668
feat: add Role taxonomy (区分) for functions/methods. Fix rewrite rules…
EvanShaw Aug 25, 2021
cc31b23
fix: prevent namespace backslashes from being stripped by update_post…
EvanShaw Aug 25, 2021
d11d440
fix: use PR proposed in #213 to fix namespace child terms not being d…
EvanShaw Aug 26, 2021
f9d8262
feat: add code-reference post_type landing pages for plugin/theme/com…
EvanShaw Aug 27, 2021
df60ab0
fix: only create reference landing page on import if it doesn't exist…
EvanShaw Aug 30, 2021
ff1c792
feat: move some utility functions from wporg theme to api-functions.php
EvanShaw Aug 30, 2021
9c7b639
feat: add landing pages for functions, hooks, and the source itself. …
EvanShaw Sep 1, 2021
d497ecc
feat: add functions for getting the reference baseurl when on single/…
EvanShaw Sep 2, 2021
c315160
feat: add more utility functions
EvanShaw Sep 2, 2021
542e873
fix: only create default reference landing pages on import to avoid c…
EvanShaw Sep 6, 2021
bfa1619
refactor: return WP_Query instances rather than post ID arrays for va…
EvanShaw Sep 7, 2021
b3cca99
feat: add translated_summary and translated_description post meta fie…
EvanShaw Sep 10, 2021
684cf1b
feat: allow adding translations for parameters and return value descr…
EvanShaw Sep 13, 2021
ba2e138
feat: move formatting methods/hooks from theme to this plugin
EvanShaw Sep 14, 2021
0b357a7
refactor: move all importer files from lib to src. Make all files PSR…
EvanShaw Sep 14, 2021
1ddf3cc
chore: finish fixing all linting errors
EvanShaw Sep 14, 2021
7c1297a
merge
seiyuinoue Sep 14, 2021
487441e
feat: add ability to trash no longer existing references on import
EvanShaw Sep 14, 2021
70ae3b2
feat: allow controlling file/folder exclusions with docparser-meta.js…
EvanShaw Sep 15, 2021
c0273d9
feat: add config option to import the source's version number
EvanShaw Sep 15, 2021
4d5f227
fix: add slashes to strings in tag data on import so that references …
EvanShaw Sep 15, 2021
82a8130
fix: make hook posts api function respect source type terms
EvanShaw Sep 16, 2021
7e10d6b
fix: only show roles that have posts for the given source type terms
EvanShaw Sep 17, 2021
953f151
Merge branch 'master' of https://github.com/aivec/phpdoc-parser
seiyuinoue Sep 21, 2021
5886df6
sourcetype svg upload added
seiyuinoue Sep 21, 2021
13ed749
Merge pull request #1 from aivec/feat/source-type-term-image
EvanShaw Sep 21, 2021
617175d
Merge branch 'master' of https://github.com/aivec/phpdoc-parser
seiyuinoue Sep 21, 2021
9642b92
review content reflected
seiyuinoue Sep 21, 2021
bf387d0
feat: add settings page for importing
EvanShaw Sep 22, 2021
fd13540
feat: finish frontend import feature
EvanShaw Sep 22, 2021
e08c035
posts added filter
seiyuinoue Sep 28, 2021
2f57e3e
Merge branch 'master' into feat/source-type-term-image
EvanShaw Sep 30, 2021
e77d660
Merge pull request #2 from aivec/feat/source-type-term-image
EvanShaw Sep 30, 2021
e2776c6
Merge branch 'master' of https://github.com/aivec/phpdoc-parser
seiyuinoue Sep 30, 2021
478f44c
Merge branch 'master' into feat/import-plugin-list-filter
seiyuinoue Sep 30, 2021
e75a588
review content reflected
seiyuinoue Sep 30, 2021
a4b0503
refactor: change importer config file name from 'docparser-meta.json'…
EvanShaw Oct 1, 2021
72f864e
Merge pull request #3 from aivec/feat/import-plugin-list-filter
EvanShaw Oct 1, 2021
b5f9d61
feat: add avcpdp_get_associated_tags api function
EvanShaw Oct 6, 2021
606e373
feat: add search helper functions
EvanShaw Oct 6, 2021
b348833
fix: encode values for search link/args functions
EvanShaw Oct 6, 2021
42a1181
fix: fix search post_type filtering
EvanShaw Oct 7, 2021
adcdbe4
fix: order by not deprecated for wp-parser-* archive and search results
EvanShaw Oct 7, 2021
7c561d6
feat: add utility method for retrieving the reference source type log…
EvanShaw Oct 7, 2021
5d5d1a5
feat: add a few more utility functions
EvanShaw Oct 13, 2021
f556b69
fix: combine the functionality of a few functions into one
EvanShaw Oct 18, 2021
0924439
fix: fix handling of @see tag internal references
EvanShaw Oct 21, 2021
191e883
chore: add bundle script
EvanShaw Oct 27, 2021
89e46c7
fix: install missing cross-env package
EvanShaw Oct 27, 2021
bff3226
chore: fix bundle script
EvanShaw Oct 27, 2021
9462043
feat: save @important tag as post meta data. Add meta box field to po…
EvanShaw Oct 29, 2021
9708fbe
fix: fix bundle script erroneously removing nikic/php-parser package
EvanShaw Oct 29, 2021
b5e7d47
feat: on admin posts list page, add abilty to order by item importance
EvanShaw Oct 29, 2021
eca26a6
feat: add method for retrieving a hierarchical array for a param hash…
EvanShaw Nov 1, 2021
9c0ff9a
add-post-edit-items
seiyuinoue Nov 2, 2021
d485a4a
Merge branch 'master' into feat/add-post-edit-items
seiyuinoue Nov 2, 2021
c9821ae
Review content reflected
seiyuinoue Nov 2, 2021
4f56468
4#discussion_r740733728
seiyuinoue Nov 2, 2021
1f33a58
Degreasing correction
seiyuinoue Nov 2, 2021
f3e0e93
Merge pull request #4 from aivec/feat/add-post-edit-items
EvanShaw Nov 2, 2021
d62a513
feat(wp-parser-* post edit): recursively display table rows for hash …
EvanShaw Nov 4, 2021
3493212
Merge branch 'feat/translated-return-hash-recursive'
EvanShaw Nov 4, 2021
e4703a2
feat: save translated since tags as key value map. Add get_deprecated…
EvanShaw Nov 4, 2021
12bcbad
feat: handle hash type parameters for arguments
EvanShaw Nov 4, 2021
638aa2c
feat: finish making ALL post content translatable
EvanShaw Nov 5, 2021
a851e47
fix(reference list pages): use multiple meta queries for the two vari…
EvanShaw Nov 10, 2021
673f99f
fix: fix archive pagination URL rewrite rule for wp-parser-class post…
EvanShaw Nov 10, 2021
b21fd4f
fix: only run ItemImportance pre_get_posts hook on admin post list pages
EvanShaw Nov 11, 2021
4e810cf
作業中コミット(残;blobDataZip処理、bitbucketインポート処理)
seiyuinoue Nov 22, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
vendor
vendor
node_modules
dist
*.zip
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -31,3 +31,6 @@ Activate the plugin first:
In your site's directory:

wp parser create /path/to/source/code --user=<id|login>

## Known Parser Issues
- The parser will crash if it encounters an invokation of an anonymous function returned by a call to a method/function all on the same line. (ie: $this->getAndExecuteFunc()())
31 changes: 31 additions & 0 deletions bundle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env php
<?php

use Aivec\PtBundler\Bundler;

passthru('composer install');
passthru('npm install');

require_once(__DIR__ . '/vendor/autoload.php');

(new Bundler('wp-phpdoc-parser'))
->setFoldersToInclude(['dist', 'languages', 'src', 'vendor'])
->setFilesToInclude(['plugin.php'])
->setTargetsToCleanBeforeBuild(['dist'])
->setBuildCallback(function () {
passthru('npm run build:prod');
passthru('composer build');
})
->setTargetsToCleanAfterBuild([
'vendor/aivec',
'vendor/nikic/fast-route',
'vendor/bin',
])
->setCleanupCallback(function () {
passthru('composer dump-autoload');
})
->setArchiveTargetsToClean([
'dist/**/*.map',
'src/**/*.tsx',
])
->createZipArchive();
110 changes: 77 additions & 33 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,79 @@
{
"name" : "wordpress/phpdoc-parser",
"description": "Static code parser for WordPress source.",
"keywords" : ["wordpress"],
"type" : "wordpress-plugin",
"homepage" : "https://github.com/WordPress/phpdoc-parser",
"license" : "GPL-2.0-or-later",
"authors" : [
{
"name" : "Ryan McCue",
"homepage": "http://ryanmccue.info",
"role" : "Developer"
},
{
"name" : "Contributors",
"homepage": "https://github.com/WordPress/phpdoc-parser/graphs/contributors"
}
],
"support" : {
"issues": "https://github.com/WordPress/phpdoc-parser/issues"
},
"require" : {
"php" : ">=5.4",
"composer/installers" : "~1.0",
"phpdocumentor/reflection" : "~3.0",
"erusev/parsedown" : "~1.7",
"scribu/lib-posts-to-posts": "dev-master@dev",
"scribu/scb-framework" : "dev-master@dev",
"psr/log" : "~1.0"
},
"autoload" : {
"classmap": ["lib"],
"files" : ["lib/runner.php", "lib/template.php"]
}
"name": "wordpress/phpdoc-parser",
"description": "Static code parser for WordPress source.",
"keywords": [
"wordpress"
],
"type": "wordpress-plugin",
"homepage": "https://github.com/WordPress/phpdoc-parser",
"license": "GPL-2.0-or-later",
"authors": [
{
"name": "Ryan McCue",
"homepage": "http://ryanmccue.info",
"role": "Developer"
},
{
"name": "Contributors",
"homepage": "https://github.com/WordPress/phpdoc-parser/graphs/contributors"
}
],
"support": {
"issues": "https://github.com/WordPress/phpdoc-parser/issues"
},
"extra": {
"mozart": {
"dep_namespace": "AVCPDP\\",
"dep_directory": "/dist/AVCPDP/",
"classmap_directory": "/dist/classes/",
"classmap_prefix": "AVCPDP_",
"packages": [
"aivec/wordpress-router",
"aivec/response-handler",
"aivec/core-css"
]
}
},
"require": {
"php": ">=5.4",
"composer/installers": "~1.0",
"phpdocumentor/reflection": "~3.0",
"erusev/parsedown": "~1.7",
"scribu/lib-posts-to-posts": "dev-master@dev",
"scribu/scb-framework": "dev-master@dev",
"psr/log": "~1.0",
"aivec/wordpress-router": "^7.0",
"aivec/response-handler": "^5.0",
"wp-cli/wp-cli": "^2.5",
"aivec/core-css": "^3.1"
},
"require-dev": {
"wp-cli/i18n-command": "^2.2",
"aivec/phpcs-wp": "^2.0",
"coenjacobs/mozart": "^0.7.1",
"aivec/pt-bundler": "^1.1"
},
"autoload": {
"files": [
"src/api-functions.php",
"src/template-functions.php"
],
"psr-4": {
"AVCPDP\\": "dist/AVCPDP",
"Aivec\\Plugins\\DocParser\\": "src"
}
},
"scripts": {
"lint": "phpcs -ps --standard=AivecWP-5 --ignore=*/tests/* .",
"lint:fix": "phpcbf -ps --standard=AivecWP-5 --ignore=*/tests/* .",
"i18n:create-pot": "./vendor/bin/wp i18n make-pot . languages/messages.pot",
"i18n:update-pos": "@composer i18n:create-pot && find ./languages -name \"*.po\" | xargs -I % msgmerge -o % % languages/messages.pot",
"i18n:make-mo": "./vendor/bin/wp i18n make-mo languages",
"i18n:make-json": "./vendor/bin/wp i18n make-json languages --no-purge --pretty-print",
"build": [
"composer install",
"mozart compose",
"composer install --no-dev"
]
}
}
2,412 changes: 2,391 additions & 21 deletions composer.lock

Large diffs are not rendered by default.

649 changes: 649 additions & 0 deletions languages/messages.pot

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions languages/wp-parser-en-89e3a54d76b08785eadd9f81bcccf6b1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"translation-revision-date": "YEAR-MO-DA HO:MI+ZONE",
"generator": "WP-CLI\/2.5.0",
"source": "dist\/js\/Views\/ImporterPage\/App.js",
"domain": "messages",
"locale_data": {
"messages": {
"": {
"domain": "messages",
"lang": "en",
"plural-forms": "nplurals=2; plural=(n != 1);"
},
"Import": [
""
],
"Enter the folder name of the plugin\/theme\/composer-package": [
""
],
"Trash old references": [
""
],
"Importing...": [
""
],
"Updated": [
""
],
"Source folders parent path": [
""
],
"Enter the absolute path to the location of the source folders": [
""
],
"Updating...": [
""
],
"Update": [
""
]
}
}
}
Binary file added languages/wp-parser-en.mo
Binary file not shown.
644 changes: 644 additions & 0 deletions languages/wp-parser-en.po

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions languages/wp-parser-ja-89e3a54d76b08785eadd9f81bcccf6b1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"translation-revision-date": "YEAR-MO-DA HO:MI+ZONE",
"generator": "WP-CLI\/2.5.0",
"source": "dist\/js\/Views\/ImporterPage\/App.js",
"domain": "messages",
"locale_data": {
"messages": {
"": {
"domain": "messages",
"lang": "ja",
"plural-forms": "nplurals=2; plural=(n != 1);"
},
"Import": [
"\u30a4\u30f3\u30dd\u30fc\u30c8"
],
"Enter the folder name of the plugin\/theme\/composer-package": [
"\u30d7\u30e9\u30b0\u30a4\u30f3\u30fb\u30c6\u30fc\u30de\u30fbComposer\u30d1\u30c3\u30b1\u30fc\u30b8\u306e\u30d5\u30a9\u30eb\u30c0\u30fc\u540d\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002"
],
"Trash old references": [
"\u904e\u53bb\u306e\u30ec\u30d5\u30a1\u30ec\u30f3\u30b9\u3092\u30b4\u30df\u7bb1\u306b\u79fb\u52d5"
],
"Importing...": [
"\u30a4\u30f3\u30dd\u30fc\u30c8\u4e2d..."
],
"Updated": [
"\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002"
],
"Source folders parent path": [
"\u30bd\u30fc\u30b9\u306e\u89aa\u30d5\u30a9\u30eb\u30c0\u30fc"
],
"Enter the absolute path to the location of the source folders": [
"\u30bd\u30fc\u30b9\u30d5\u30a9\u30eb\u30c0\u30fc\u304c\u5165\u3063\u3066\u3044\u308b\u89aa\u30d5\u30a9\u30eb\u30c0\u30fc\u306e\u7d76\u5bfe\u30d1\u30b9"
],
"Updating...": [
"\u66f4\u65b0\u4e2d..."
],
"Update": [
"\u30d1\u30b9\u3092\u66f4\u65b0\u3059\u308b"
]
}
}
}
Binary file added languages/wp-parser-ja.mo
Binary file not shown.
625 changes: 625 additions & 0 deletions languages/wp-parser-ja.po

Large diffs are not rendered by default.

143 changes: 0 additions & 143 deletions lib/class-command.php

This file was deleted.

236 changes: 0 additions & 236 deletions lib/class-file-reflector.php

This file was deleted.

51 changes: 0 additions & 51 deletions lib/class-function-call-reflector.php

This file was deleted.

99 changes: 0 additions & 99 deletions lib/class-hook-reflector.php

This file was deleted.

839 changes: 0 additions & 839 deletions lib/class-importer.php

This file was deleted.

158 changes: 0 additions & 158 deletions lib/class-method-call-reflector.php

This file was deleted.

258 changes: 0 additions & 258 deletions lib/class-plugin.php

This file was deleted.

22 changes: 0 additions & 22 deletions lib/class-pretty-printer.php

This file was deleted.

436 changes: 0 additions & 436 deletions lib/class-relationships.php

This file was deleted.

29 changes: 0 additions & 29 deletions lib/class-static-method-call-reflector.php

This file was deleted.

38 changes: 0 additions & 38 deletions lib/class-wp-cli-logger.php

This file was deleted.

410 changes: 0 additions & 410 deletions lib/runner.php

This file was deleted.

330 changes: 0 additions & 330 deletions lib/template.php

This file was deleted.

33,032 changes: 33,032 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"name": "@aivec/wp-phpdoc-parser",
"description": "WP-Parser is the parser for creating the new code reference at [developer.wordpress.org](https://developer.wordpress.org/reference). It parses the inline documentation and produces custom post type entries in WordPress.",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint --ext .ts,.tsx src",
"lint:fix": "eslint --fix --ext .ts,.tsx src",
"format": "prettier --check \"src/**/*.{ts,tsx}\"",
"format:fix": "prettier --write \"src/**/*.{ts,tsx}\"",
"build:dev": "cross-env NODE_ENV=development wp-scripts build --config ./node_modules/@aivec/wp-typescript-react/src/configs/webpack.config.js",
"build:prod": "cross-env NODE_ENV=production wp-scripts build --config ./node_modules/@aivec/wp-typescript-react/src/configs/webpack.config.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/aivec/phpdoc-parser.git"
},
"license": "GPL-2.0",
"bugs": {
"url": "https://github.com/aivec/phpdoc-parser/issues"
},
"homepage": "https://github.com/aivec/phpdoc-parser#readme",
"dependencies": {
"@aivec/react-material-components": "^3.0.3",
"@aivec/reqres-utils": "^6.0.1",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@wordpress/i18n": "^4.2.2",
"axios": "^0.21.4"
},
"devDependencies": {
"@aivec/wp-typescript-react": "^2.0.1",
"@types/react": "^17.0.22",
"@types/react-dom": "^17.0.9",
"@typescript-eslint/eslint-plugin": "^4.31.2",
"@typescript-eslint/parser": "^4.31.2",
"@wordpress/scripts": "^18.0.1",
"cross-env": "^7.0.3",
"eslint": "^7.32.0",
"eslint-config-airbnb-typescript": "^14.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.25.3",
"eslint-plugin-react-hooks": "^4.2.0",
"typescript": "^4.4.3"
},
"eslintIgnore": [
"test/**/*.js"
],
"eslintConfig": {
"plugins": [
"prettier"
],
"extends": [
"airbnb-typescript",
"airbnb/hooks",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:prettier/recommended"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"react/state-in-constructor": [
1,
"never"
],
"react/jsx-wrap-multilines": [
"error",
{
"declaration": false,
"assignment": false
}
],
"prettier/prettier": "error",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "default",
"format": [
"camelCase",
"PascalCase",
"UPPER_CASE"
],
"leadingUnderscore": "allow",
"trailingUnderscore": "allow"
}
]
},
"settings": {
"import/resolver": {
"typescript": {}
}
}
},
"prettier": {
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "all"
}
}
45 changes: 31 additions & 14 deletions plugin.php
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
<?php

/**
* Plugin Name: WP Parser
* Description: Create a function reference site powered by WordPress
* Author: Ryan McCue, Paul Gibbs, Andrey "Rarst" Savchenko and Contributors
* Author URI: https://github.com/WordPress/phpdoc-parser/graphs/contributors
* Plugin URI: https://github.com/WordPress/phpdoc-parser
* Version:
* Plugin Name: AVC WP Parser
* Description: Create a plugin/theme/composer-package source reference site powered by WordPress
* Author: Evan Shaw, Ryan McCue, Paul Gibbs, Andrey "Rarst" Savchenko and Contributors
* Author URI: https://github.com/aivec/phpdoc-parser/graphs/contributors
* Plugin URI: https://github.com/aivec/phpdoc-parser
* Version: %%VERSION%%
* Text Domain: wp-parser
* Domain Path: /languages/
*/

if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
require __DIR__ . '/vendor/autoload.php';
define('AVC_WP_PARSER', true);
define('AVCPDP_VERSION', '%%VERSION%%');
define('AVCPDP_LANG_DIR', __DIR__ . '/languages');
define('AVCPDP_PLUGIN_DIR', ABSPATH . 'wp-content/plugins/' . plugin_basename(dirname(__FILE__)));
define('AVCPDP_PLUGIN_URL', site_url() . '/wp-content/plugins/' . plugin_basename(dirname(__FILE__)));
define('AVCPDP_DIST_DIR', AVCPDP_PLUGIN_DIR . '/dist');
define('AVCPDP_DIST_URL', AVCPDP_PLUGIN_URL . '/dist');
load_plugin_textdomain('wp-parser', false, dirname(plugin_basename(__FILE__)) . '/languages');

if (file_exists(__DIR__ . '/vendor/autoload.php')) {
require __DIR__ . '/vendor/autoload.php';
}

global $wp_parser;
$wp_parser = new WP_Parser\Plugin();
$wp_parser->on_load();
(new Aivec\Plugins\DocParser\Master())->init();

register_activation_hook(__FILE__, ['P2P_Storage', 'init']);
register_activation_hook(__FILE__, ['P2P_Storage', 'install']);
register_activation_hook(__FILE__, function () {
(new Aivec\Plugins\DocParser\Registrations())->registerPostTypes();
flush_rewrite_rules();
});

register_activation_hook( __FILE__, array( 'P2P_Storage', 'init' ) );
register_activation_hook( __FILE__, array( 'P2P_Storage', 'install' ) );
register_deactivation_hook(__FILE__, function () {
flush_rewrite_rules();
});

// TODO safer handling for uninstall
//register_uninstall_hook( __FILE__, array( 'P2P_Storage', 'uninstall' ) );
// register_uninstall_hook( __FILE__, array( 'P2P_Storage', 'uninstall' ) );
183 changes: 183 additions & 0 deletions src/API/Commands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php

namespace Aivec\Plugins\DocParser\API;

use Aivec\Plugins\DocParser\CLI\CliErrorException;
use Aivec\Plugins\DocParser\CLI\Logger;
use Aivec\Plugins\DocParser\Importer\Importer;
use Aivec\Plugins\DocParser\Importer\Parser;
use Aivec\Plugins\DocParser\Models\ImportConfig;

// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
// phpcs:disable PSR2.Methods.MethodDeclaration.Underscore

/**
* Converts PHPDoc markup into a template ready for import to a WordPress blog.
*/
class Commands
{
/**
* Generate JSON containing the PHPDoc markup, convert it into WordPress posts, and insert into DB.
*
* @param string $directory
* @param bool $trashOldRefs
* @param null|bool $quick
* @param null|bool $importInternal
* @throws CliErrorException Thrown when an error occurs.
* @return void
*/
public function create($directory, $trashOldRefs = false, $quick = null, $importInternal = null) {
if (empty($directory)) {
throw new CliErrorException(sprintf("Can't read %1\$s. Does the file exist?", $directory), 1);
}

do_action('avcpdp_command_print_line', '');

$parser_meta_filen = 'docparser.config.json';
$parser_meta_filep = '';
if ($directory === '.') {
$parser_meta_filep = "./{$parser_meta_filen}";
} else {
$parser_meta_filep = "{$directory}/{$parser_meta_filen}";
}

do_action('avcpdp_command_print_line', sprintf('Getting source meta data from %1$s', $parser_meta_filep));

if (!file_exists($parser_meta_filep)) {
throw new CliErrorException(sprintf('Missing required file: %1$s', $parser_meta_filep), 1);
}

$metaf = file_get_contents($parser_meta_filep);
if (empty($metaf)) {
throw new CliErrorException(sprintf("Can't read %1\$s. Possible permissions error.", $parser_meta_filep), 1);
}

$parser_meta = json_decode($metaf, true);
if ($parser_meta === null) {
throw new CliErrorException(sprintf(
'%1$s is malformed. Make sure the file is in proper JSON format.',
$parser_meta_filen
), 1);
}

$types = ['plugin', 'theme', 'composer-packages'];
$validtypesm = 'Valid types are "plugin", "theme", and "composer-package"';
if (empty($parser_meta['type'])) {
throw new CliErrorException("The \"type\" key is missing.\r\n{$validtypesm}", 1);
}

if (!in_array($parser_meta['type'], $types, true)) {
throw new CliErrorException($validtypesm, 1);
}

if (empty($parser_meta['name'])) {
throw new CliErrorException('The "name" key is missing or contains an empty value.', 1);
}

if (!is_string($parser_meta['name'])) {
throw new CliErrorException('"name" must be a string.', 1);
}

if (isset($parser_meta['exclude'])) {
if (!is_array($parser_meta['exclude'])) {
throw new CliErrorException('"exclude" must be an array of strings.', 1);
}

foreach ($parser_meta['exclude'] as $target) {
if (!is_string($target)) {
throw new CliErrorException('"exclude" must be an array of strings.', 1);
}
}
}

if (isset($parser_meta['excludeStrict'])) {
if (!is_bool($parser_meta['excludeStrict'])) {
throw new CliErrorException('"excludeStrict" must be a boolean.', 1);
}
}

if (isset($parser_meta['version'])) {
if (!is_string($parser_meta['version'])) {
throw new CliErrorException('"version" must be a string.', 1);
}
}

// handle file/folder exclusions
$exclude = !empty($parser_meta['exclude']) ? $parser_meta['exclude'] : [];
add_filter('wp_parser_exclude_directories', function () use ($exclude) {
return $exclude;
});

$exclude_strict = isset($parser_meta['excludeStrict']) ? (bool)$parser_meta['excludeStrict'] : false;
add_filter('wp_parser_exclude_directories_strict', function () use ($exclude_strict) {
return $exclude_strict;
});

$version = isset($parser_meta['version']) ? $parser_meta['version'] : null;

$data = $this->_get_phpdoc_data($directory, 'array');
$data = [
'config' => new ImportConfig(
$parser_meta['type'],
$parser_meta['name'],
$version,
$exclude,
$exclude_strict
),
'trash_old_refs' => $trashOldRefs,
'files' => $data,
];

// Import data
$this->_do_import($data, isset($quick), isset($importInternal));
}

/**
* Generate the data from the PHPDoc markup.
*
* @param string $path Directory or file to scan for PHPDoc
* @param string $format What format the data is returned in: [json|array].
* @throws CliErrorException Thrown when an error occurs.
* @return string|array
*/
protected function _get_phpdoc_data($path, $format = 'json') {
do_action('avcpdp_command_print_line', sprintf('Extracting PHPDoc from %1$s. This may take a few minutes...', $path));
$is_file = is_file($path);
$files = $is_file ? [$path] : Parser::getWpFiles($path);
$path = $is_file ? dirname($path) : $path;

if ($files instanceof \WP_Error) {
throw new CliErrorException(sprintf('Problem with %1$s: %2$s', $path, $files->get_error_message()), 1);
}

$output = Parser::parseFiles($files, $path);

if ('json' == $format) {
return json_encode($output, JSON_PRETTY_PRINT);
}

return $output;
}

/**
* Import the PHPDoc $data into WordPress posts and taxonomies
*
* @param array $data
* @param bool $skip_sleep If true, the sleep() calls are skipped.
* @param bool $import_ignored If true, functions marked `@ignore` will be imported.
* @throws CliErrorException Thrown when an error occurs.
* @return void
*/
protected function _do_import(array $data, $skip_sleep = false, $import_ignored = false) {
if (!wp_get_current_user()->exists()) {
throw new CliErrorException('Please specify a valid user: --user=<id|login>', 1);
}

// Run the importer
$importer = new Importer($data['config'], $data['trash_old_refs']);
$importer->setLogger(new Logger());
$importer->import($data['files'], $skip_sleep, $import_ignored);

do_action('avcpdp_command_print_line', '');
}
}
161 changes: 161 additions & 0 deletions src/API/ZipImport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

namespace Aivec\Plugins\DocParser\API;

use Aivec\Plugins\DocParser\ErrorStore;
use Aivec\Plugins\DocParser\Master;
use Aivec\Plugins\DocParser\Views\ImporterPage\ImporterPage;
use Aivec\Plugins\DocParser\REST\Import;
use AVCPDP\Aivec\ResponseHandler\GenericError;
use Aivec\Welcart\Extensions\Cptm\Admin\Settings\Settings;
use UnexpectedValueException;
use WCEXCPTM\Firebase\JWT\BeforeValidException;
use WCEXCPTM\Firebase\JWT\ExpiredException;
use WCEXCPTM\Firebase\JWT\SignatureInvalidException;
use AVCPDP\Firebase\JWT\JWT as FirebaseJWT;
use Exception;
use ZipArchive;

/**
* Converts PHPDoc markup into a template ready for import to a WordPress blog.
*/
class ZipImport
{
/**
* Master object
*
* @var Master
*/
private $master;

/**
* SHA256 public key string
*
* @var string
*/
private $publicKey;

/**
* TestMehod
*
* @author Seiyu Inoue <s.inoue@aivec.co.jp>
* @param Aivec\Plugins\DocParser\API\FunctionalTester $I
*/
public function testZIpImport(FunctionalTester $I) {
$sku = $I->getDomainUnrestrictedUniqueId();

// ajaxParamSet
$I->sendAjaxPostRequest('/', [
'asmp_action' => 'zipImport',
'jwt' => [
'zipFileBase64' => $sku,
'sourceName' => 'wcex-wishlist',
'trashOldRefs' => true,
],
]);

$I->seeResponseCodeIs(200);
$I->seeResponseContains('success');
}

/**
* Injects `$master`
*
* @author Evan D Shaw <evandanielshaw@gmail.com>
* @param Master $master
*/
public function __construct(Master $master) {
$this->master = $master;

$settings = ImporterPage::getSettings();
$publicKey = $settings['sshPublicKeyValue'];
$this->publicKey = $publicKey;
}

/**
* Uploads a new ZIP archive to the `packages` directory for the given unique ID
*
* This API uses JWT RS256 authentication.
*
* @author Evan D Shaw <evandanielshaw@gmail.com>
* @param array $args
* @param array $payload Expects an array with the JWT payload set as the value of a key named `jwt`
* @return string|GenericError Returns `success` on success
*/
public function deployToUpdateServerPackagesDir($args, array $payload) {
// SSH decode
try {
$data = $this->decode($payload['jwt']);
} catch (Exception $e) {
return $this->master->estore->getErrorResponse(ErrorStore::JWT_UNAUTHORIZED, [$e->getMessage()]);
}

// Get base64 zipfile
$base64Field = 'zipFileBase64';
if (empty($data[$base64Field])) {
return $this->master->estore->getErrorResponse(
ErrorStore::REQUIRED_FIELDS_MISSING,
[$base64Field],
['The field "' . $base64Field . '" is required.']
);
}
$base64 = $data[$base64Field];

// Exchange blob
$blob = base64_decode($base64);
if (empty($blob)) {
return $this->master->estore->getErrorResponse(ErrorStore::BASE64_DECODE_ERROR);
}

// Get source name
$sourceNameField = 'sourceName';
if (empty($data[$sourceNameField])) {
return $this->master->estore->getErrorResponse(
ErrorStore::REQUIRED_FIELDS_MISSING,
[$sourceNameField],
['The field "' . $sourceNameField . '" is required.']
);
}
$sourceName = $data[$sourceNameField];

// Create zip file
// 下の直解凍ができれば不要
// $tempzip = '/' . trim(get_temp_dir(), '/') . '/' . $sourceName . '.zip';
// $res = file_put_contents($tempzip, $blob);
// if ($res === false) {
// return $this->master->estore->getErrorResponse(ErrorStore::TEMP_ZIPFILE_WRITE_ERROR, [$tempzip]);
// }

// todo:not test
// blobを直解凍できるかな??できたらする。
// todo:zipの解凍処理phpの標準解凍処理を調べてみる
$fpath = ImporterPage::getSettings()['sourceFoldersAbspath'];
$zip = new ZipArchive();
if ($zip->open($blob) === true) {
$zip->extractTo($fpath . $sourceName);
$zip->close();
}

// Create import instance
$import = new Import($this->master);
// Import package refarence
$importArgs = (['fname' => $sourceName]);
$importPayload = (['trashOldRefs' => $data['trashOldRefs']]);
return $import->create($importArgs, $importPayload);
}

/**
* Decodes a JWT payload
*
* @author Evan D Shaw <evandanielshaw@gmail.com>
* @param string $payload
* @return array
* @throws UnexpectedValueException Firebase library exception.
* @throws SignatureInvalidException Firebase library exception.
* @throws BeforeValidException Firebase library exception.
* @throws ExpiredException Firebase library exception.
*/
public function decode($payload) {
return (array)FirebaseJWT::decode($payload, $this->publicKey, ['RS256']);
}
}
66 changes: 66 additions & 0 deletions src/Admin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Aivec\Plugins\DocParser;

/**
* Class to handle admin area customization and tools.
*/
class Admin
{
/**
* Initializes class
*
* @return void
*/
public static function init() {
add_action('admin_init', [get_class(), 'doInit']);
}

/**
* Handles adding/removing hooks.
*
* @return void
*/
public static function doInit() {
add_action('admin_enqueue_scripts', [get_class(), 'adminEnqueueScripts']);
}

/**
* Returns array of screen IDs for parsed post types.
*
* @return array
*/
public static function getParsedPostTypesScreenIds() {
$screen_ids = [];
foreach (avcpdp_get_parsed_post_types() as $parsed_post_type) {
$screen_ids[] = $parsed_post_type;
$screen_ids[] = "edit-{$parsed_post_type}";
}

return $screen_ids;
}

/**
* Enqueue JS and CSS on the edit screens for all parsed post types.
*
* @return void
*/
public static function adminEnqueueScripts() {
// By default, only enqueue on parsed post type screens.
$screen_ids = self::getParsedPostTypesScreenIds();

/*
* Filters whether or not admin.css should be enqueued.
*
* @param bool True if admin.css should be enqueued, false otherwise.
*/
if ((bool)apply_filters('avcpdp_admin_enqueue_scripts', in_array(get_current_screen()->id, $screen_ids))) {
wp_enqueue_style(
'avcpdp-admin',
AVCPDP_PLUGIN_URL . '/src/styles/admin.css',
[],
AVCPDP_VERSION
);
}
}
}
42 changes: 42 additions & 0 deletions src/CLI/CliErrorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Aivec\Plugins\DocParser\CLI;

use Exception;

/**
* WP-CLI error exception that holds paramaters for `WP_CLI::error()`
*/
class CliErrorException extends Exception
{
/**
* Whether to exit or not
*
* @var true
*/
private $exit = true;

/**
* Constructs a WP-CLI error exception
*
* @author Evan D Shaw <evandanielshaw@gmail.com>
* @param string $message
* @param int $code
* @param bool $exit
* @return void
*/
public function __construct($message = '', $code = 0, $exit = true) {
$this->exit = $exit;
parent::__construct($message, $code);
}

/**
* Getter for `$this->exit`
*
* @author Evan D Shaw <evandanielshaw@gmail.com>
* @return true
*/
public function getExit() {
return $this->exit;
}
}
159 changes: 159 additions & 0 deletions src/CLI/Commands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

namespace Aivec\Plugins\DocParser\CLI;

use Aivec\Plugins\DocParser\Importer\Importer;
use Aivec\Plugins\DocParser\Importer\Parser;
use Aivec\Plugins\DocParser\API\Commands as API;
use WP_CLI;
use WP_CLI_Command;

// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
// phpcs:disable PSR2.Methods.MethodDeclaration.Underscore

/**
* Converts PHPDoc markup into a template ready for import to a WordPress blog.
*/
class Commands extends WP_CLI_Command
{
/**
* Generate a JSON file containing the PHPDoc markup, and save to filesystem.
*
* @synopsis <directory> [<output_file>]
*
* @param array $args
* @return void
*/
public function export($args) {
$directory = realpath($args[0]);
$output_file = empty($args[1]) ? 'phpdoc.json' : $args[1];
$json = $this->_get_phpdoc_data($directory);
$result = file_put_contents($output_file, $json);
WP_CLI::line();

if (false === $result) {
WP_CLI::error(sprintf('Problem writing %1$s bytes of data to %2$s', strlen($json), $output_file));
exit;
}

WP_CLI::success(sprintf('Data exported to %1$s', $output_file));
WP_CLI::line();
}

/**
* Read a JSON file containing the PHPDoc markup, convert it into WordPress posts, and insert into DB.
*
* @synopsis <file> [--quick] [--import-internal]
*
* @param array $args
* @param array $assoc_args
* @return void
*/
public function import($args, $assoc_args) {
list( $file ) = $args;
WP_CLI::line();

// Get the data from the <file>, and check it's valid.
$phpdoc = false;

if (is_readable($file)) {
$phpdoc = file_get_contents($file);
}

if (!$phpdoc) {
WP_CLI::error(sprintf("Can't read %1\$s. Does the file exist?", $file));
exit;
}

$phpdoc = json_decode($phpdoc, true);
if (is_null($phpdoc)) {
WP_CLI::error(sprintf("JSON in %1\$s can't be decoded :(", $file));
exit;
}

// Import data
$this->_do_import($phpdoc, isset($assoc_args['quick']), isset($assoc_args['import-internal']));
}

/**
* Generate JSON containing the PHPDoc markup, convert it into WordPress posts, and insert into DB.
*
* @subcommand create
* @synopsis <directory> [--quick] [--import-internal] [--user] [--trash-old-refs]
*
* @param array $args
* @param array $assoc_args
* @return void
*/
public function create($args, $assoc_args) {
list( $directory ) = $args;
$directory = realpath($directory);

add_action('avcpdp_command_print_line', function ($message) {
WP_CLI::line($message);
}, 10, 1);

$trashOldRefs = isset($assoc_args['trash-old-refs']) && $assoc_args['trash-old-refs'] === true;
try {
(new API())->create($directory, $trashOldRefs, $assoc_args['quick'], $assoc_args['import-internal']);
} catch (CliErrorException $e) {
WP_CLI::error($e->getMessage());
exit;
}
}

/**
* Generate the data from the PHPDoc markup.
*
* @param string $path Directory or file to scan for PHPDoc
* @param string $format What format the data is returned in: [json|array].
* @return string|array
*/
protected function _get_phpdoc_data($path, $format = 'json') {
WP_CLI::line(sprintf('Extracting PHPDoc from %1$s. This may take a few minutes...', $path));
$is_file = is_file($path);
$files = $is_file ? [$path] : Parser::getWpFiles($path);
$path = $is_file ? dirname($path) : $path;

if ($files instanceof \WP_Error) {
WP_CLI::error(sprintf('Problem with %1$s: %2$s', $path, $files->get_error_message()));
exit;
}

$output = Parser::parseFiles($files, $path);

if ('json' == $format) {
return json_encode($output, JSON_PRETTY_PRINT);
}

return $output;
}

/**
* Import the PHPDoc $data into WordPress posts and taxonomies
*
* @param array $data
* @param bool $skip_sleep If true, the sleep() calls are skipped.
* @param bool $import_ignored If true, functions marked `@ignore` will be imported.
* @return void
*/
protected function _do_import(array $data, $skip_sleep = false, $import_ignored = false) {
if (!wp_get_current_user()->exists()) {
WP_CLI::error('Please specify a valid user: --user=<id|login>');
exit;
}

// Run the importer
$importer = new Importer($data['config'], $data['trash_old_refs']);
$importer->setLogger(new Logger());

try {
$importer->import($data['files'], $skip_sleep, $import_ignored);
} catch (CliErrorException $e) {
WP_CLI::error($e->getMessage());
exit;
}

WP_CLI::line();
}
}
39 changes: 39 additions & 0 deletions src/CLI/Logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Aivec\Plugins\DocParser\CLI;

use Psr\Log\AbstractLogger;
use Psr\Log\LogLevel;
use WP_CLI\Loggers\Execution;

/**
* PSR-3 logger for WP CLI.
*/
class Logger extends AbstractLogger
{
/**
* Logs messages
*
* @param string $level
* @param string $message
* @param array $context
* @return void
*/
public function log($level, $message, array $context = []) {
switch ($level) {
case LogLevel::WARNING:
\WP_CLI::warning($message);
break;

case LogLevel::ERROR:
case LogLevel::ALERT:
case LogLevel::EMERGENCY:
case LogLevel::CRITICAL:
\WP_CLI::error($message, false);
break;

default:
\WP_CLI::log($message);
}
}
}
64 changes: 64 additions & 0 deletions src/ErrorStore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Aivec\Plugins\DocParser;

use AVCPDP\Aivec\ResponseHandler\ErrorStore as ES;
use AVCPDP\Aivec\ResponseHandler\GenericError;

/**
* Error store
*/
class ErrorStore extends ES
{
const REQUIRED_FIELDS_MISSING = 'RequiredFieldsMissing';
const SOURCE_NOT_FOUND = 'SourceNotFound';
const IMPORT_ERROR = 'ImportError';
const JWT_UNAUTHORIZED = 'JWTUnauthorized';
const BASE64_DECODE_ERROR = 'Base64DecodeError';
const TEMP_ZIPFILE_WRITE_ERROR = 'TempZipFileWriteError';

/**
* Adds errors to the store
*
* @author Evan D Shaw <evandanielshaw@gmail.com>
* @return void
*/
public function populate() {
$this->addError(new GenericError(
self::REQUIRED_FIELDS_MISSING,
$this->getConstantNameByValue(self::REQUIRED_FIELDS_MISSING),
400,
function ($field) {
// translators: name of the missing field
return sprintf(__('"%s" is required', 'wp-parser'), $field);
},
function ($message) {
return $message;
}
));

$em = function ($name) {
// translators: name of the plugin/theme/composer-package
return sprintf(__('"%s" does not exist', 'wp-parser'), $name);
};
$this->addError(new GenericError(
self::SOURCE_NOT_FOUND,
$this->getConstantNameByValue(self::SOURCE_NOT_FOUND),
404,
$em,
$em
));

$this->addError(new GenericError(
self::IMPORT_ERROR,
$this->getConstantNameByValue(self::IMPORT_ERROR),
422,
function ($message) {
return $message;
},
function ($message) {
return $message;
}
));
}
}
643 changes: 643 additions & 0 deletions src/Explanations/Explanations.php

Large diffs are not rendered by default.

108 changes: 108 additions & 0 deletions src/Explanations/explanations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Explanations JS.
*/

(function ($) {
//
// Explanations AJAX handlers.
//

var statusLabel = $("#status-label"),
createLink = $("#create-expl"),
unPublishLink = $("#unpublish-expl"),
rowActions = $("#expl-row-actions");

var rowCreateLink = $(".create-expl");

/**
* AJAX handler for creating and associating a new explanation post.
*
* @param {object} event Event object.
*/
function createExplanation(event) {
event.preventDefault();

wp.ajax.send("new_explanation", {
success: createExplSuccess,
error: createExplError,
data: {
nonce: $(this).data("nonce"),
post_id: $(this).data("id"),
context: event.data.context,
},
});
}

/**
* Success callback for creating a new explanation via AJAX.
*
* @param {object} data Data response object.
*/
function createExplSuccess(data) {
var editLink =
'<a href="post.php?post=' +
data.post_id +
'&action=edit">' +
wporg.editContentLabel +
"</a>";

if ("edit" == data.context) {
// Action in the parsed post type edit screen.
createLink.hide();
rowActions.html(editLink);
statusLabel.text(wporg.statusLabel.draft);
} else {
// Row link in the list table.
$("#post-" + data.parent_id + " .add-expl").html(editLink + " | ");
}
}

/**
* Error callback for creating a new explanation via AJAX.
*
* @param {object} data Data response object.
*/
function createExplError(data) {}

/**
* Handler for un-publishing an existing Explanation.
*
* @param {object} event Event object.
*/
function unPublishExplantaion(event) {
event.preventDefault();

wp.ajax.send("un_publish", {
success: unPublishSuccess,
error: unPublishError,
data: {
nonce: $(this).data("nonce"),
post_id: $(this).data("id"),
},
});
}

/**
* Success callback for un-publishing an explanation via AJAX.
*
* @param {object} data Data response object.
*/
function unPublishSuccess(data) {
if (statusLabel.hasClass("pending") || statusLabel.hasClass("publish")) {
statusLabel.removeClass("pending publish").text(wporg.statusLabel.draft);
}
unPublishLink.hide();
}

/**
* Error callback for un-publishing an explanation via AJAX.
*
* @param {object} data Data response object.
*/
function unPublishError(data) {}

// Events.
createLink.on("click", { context: "edit" }, createExplanation);
rowCreateLink.on("click", { context: "list" }, createExplanation);
unPublishLink.on("click", unPublishExplantaion);
})(jQuery);
707 changes: 707 additions & 0 deletions src/Formatting.php

Large diffs are not rendered by default.

245 changes: 245 additions & 0 deletions src/Importer/FileReflector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
<?php

namespace Aivec\Plugins\DocParser\Importer;

use phpDocumentor\Reflection\FileReflector as BaseFileReflector;

/**
* Reflection class for a full file.
*
* Extends the FileReflector from phpDocumentor to parse out WordPress
* hooks and note function relationships.
*/
class FileReflector extends BaseFileReflector
{
/**
* List of elements used in global scope in this file, indexed by element type.
*
* @var array {
* @type HookReflector[] $hooks The action and filters.
* @type FunctionCallReflector[] $functions The functions called.
* }
*/
public $uses = [];

/**
* List of elements used in the current class scope, indexed by method.
*
* @var array[][] {@see \Aivec\Plugins\DocParser\Importer\FileReflector::$uses}
*/
protected $method_uses_queue = [];

/**
* Stack of classes/methods/functions currently being parsed.
*
* @see \Aivec\Plugins\DocParser\Importer\FileReflector::getLocation()
* @var \phpDocumentor\Reflection\BaseReflector[]
*/
protected $location = [];

/**
* Last DocBlock associated with a non-documentable element.
*
* @var \PHPParser_Comment_Doc
*/
protected $last_doc = null;

/**
* Add hooks to the queue and update the node stack when we enter a node.
*
* If we are entering a class, function or method, we push it to the location
* stack. This is just so that we know whether we are in the file scope or not,
* so that hooks in the main file scope can be added to the file.
*
* We also check function calls to see if there are any actions or hooks. If
* there are, they are added to the file's hooks if in the global scope, or if
* we are in a function/method, they are added to the queue. They will be
* assigned to the function by leaveNode(). We also check for any other function
* calls and treat them similarly, so that we can export a list of functions
* used by each element.
*
* Finally, we pick up any docblocks for nodes that usually aren't documentable,
* so they can be assigned to the hooks to which they may belong.
*
* @param \PHPParser_Node $node
* @return void
*/
public function enterNode(\PHPParser_Node $node) {
parent::enterNode($node);

switch ($node->getType()) {
// Add classes, functions, and methods to the current location stack
case 'Stmt_Class':
case 'Stmt_Function':
case 'Stmt_ClassMethod':
array_push($this->location, $node);
break;

// Parse out hook definitions and function calls and add them to the queue.
case 'Expr_FuncCall':
$function = new FunctionCallReflector($node, $this->context);

// Add the call to the list of functions used in this scope.
$this->getLocation()->uses['functions'][] = $function;

if ($this->isFilter($node)) {
if ($this->last_doc && !$node->getDocComment()) {
$node->setAttribute('comments', [$this->last_doc]);
$this->last_doc = null;
}

$hook = new HookReflector($node, $this->context);

// Add it to the list of hooks used in this scope.
$this->getLocation()->uses['hooks'][] = $hook;
}
break;

// Parse out method calls, so we can export where methods are used.
case 'Expr_MethodCall':
$method = new MethodCallReflector($node, $this->context);

// Add it to the list of methods used in this scope.
$this->getLocation()->uses['methods'][] = $method;
break;

// Parse out method calls, so we can export where methods are used.
case 'Expr_StaticCall':
$method = new StaticMethodCallReflector($node, $this->context);

// Add it to the list of methods used in this scope.
$this->getLocation()->uses['methods'][] = $method;
break;

// Parse out `new Class()` calls as uses of Class::__construct().
case 'Expr_New':
$method = new MethodCallReflector($node, $this->context);

// Add it to the list of methods used in this scope.
$this->getLocation()->uses['methods'][] = $method;
break;
}

// Pick up DocBlock from non-documentable elements so that it can be assigned
// to the next hook if necessary. We don't do this for name nodes, since even
// though they aren't documentable, they still carry the docblock from their
// corresponding class/constant/function/etc. that they are the name of. If
// we don't ignore them, we'll end up picking up docblocks that are already
// associated with a named element, and so aren't really from a non-
// documentable element after all.
if (!$this->isNodeDocumentable($node) && 'Name' !== $node->getType() && ($docblock = $node->getDocComment())) {
$this->last_doc = $docblock;
}
}

/**
* Assign queued hooks to functions and update the node stack on leaving a node.
*
* We can now access the function/method reflectors, so we can assign any queued
* hooks to them. The reflector for a node isn't created until the node is left.
*
* @param \PHPParser_Node $node
* @return void
*/
public function leaveNode(\PHPParser_Node $node) {
parent::leaveNode($node);

switch ($node->getType()) {
case 'Stmt_Class':
$class = end($this->classes);
if (!empty($this->method_uses_queue)) {
/*
* @var Reflection\ClassReflector\MethodReflector $method
*/
foreach ($class->getMethods() as $method) {
if (isset($this->method_uses_queue[$method->getName()])) {
if (isset($this->method_uses_queue[$method->getName()]['methods'])) {
/*
* For methods used in a class, set the class on the method call.
* That allows us to later get the correct class name for $this, self, parent.
*/
foreach ($this->method_uses_queue[$method->getName()]['methods'] as $method_call) {
/*
* @var MethodCallReflector $method_call
*/
$method_call->setClass($class);
}
}

$method->uses = $this->method_uses_queue[$method->getName()];
}
}
}

$this->method_uses_queue = [];
array_pop($this->location);
break;

case 'Stmt_Function':
$function = array_pop($this->location);
if (isset($function->uses) && !empty($function->uses)) {
end($this->functions)->uses = $function->uses;
}
break;

case 'Stmt_ClassMethod':
$method = array_pop($this->location);

/*
* Store the list of elements used by this method in the queue. We'll
* assign them to the method upon leaving the class (see above).
*/
if (!empty($method->uses)) {
$this->method_uses_queue[$method->name] = $method->uses;
}
break;
}
}

/**
* Returns `true` if filter
*
* @param \PHPParser_Node $node
* @return bool
*/
protected function isFilter(\PHPParser_Node $node) {
// Ignore variable functions
if ('Name' !== $node->name->getType()) {
return false;
}

$calling = (string)$node->name;

$functions = [
'apply_filters',
'apply_filters_ref_array',
'apply_filters_deprecated',
'do_action',
'do_action_ref_array',
'do_action_deprecated',
];

return in_array($calling, $functions);
}

/**
* Returns location instance
*
* @return \Aivec\Plugins\DocParser\Importer\FileReflector
*/
protected function getLocation() {
return empty($this->location) ? $this : end($this->location);
}

/**
* Returns `true` if node is documentable
*
* @param \PHPParser_Node $node
* @return bool
*/
protected function isNodeDocumentable(\PHPParser_Node $node) {
return parent::isNodeDocumentable($node)
|| ($node instanceof \PHPParser_Node_Expr_FuncCall
&& $this->isFilter($node));
}
}
51 changes: 51 additions & 0 deletions src/Importer/FunctionCallReflector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Aivec\Plugins\DocParser\Importer;

use phpDocumentor\Reflection\BaseReflector;

/**
* A reflection of a function call expression.
*/
class FunctionCallReflector extends BaseReflector
{
/**
* Returns the name for this Reflector instance.
*
* @return string
*/
public function getName() {
if (isset($this->node->namespacedName)) {
return '\\' . implode('\\', $this->node->namespacedName->parts);
}

$shortName = $this->getShortName();

if (is_a($shortName, 'PHPParser_Node_Name_FullyQualified')) {
return '\\' . (string)$shortName;
}

if (is_a($shortName, 'PHPParser_Node_Name')) {
return (string)$shortName;
}

/*
* @var \PHPParser_Node_Expr_ArrayDimFetch $shortName
*/
if (is_a($shortName, 'PHPParser_Node_Expr_ArrayDimFetch')) {
$var = $shortName->var->name;
$dim = $shortName->dim->name->parts[0];

return "\${$var}[{$dim}]";
}

/*
* @var \PHPParser_Node_Expr_Variable $shortName
*/
if (is_a($shortName, 'PHPParser_Node_Expr_Variable')) {
return $shortName->name;
}

return (string)$shortName;
}
}
111 changes: 111 additions & 0 deletions src/Importer/HookReflector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace Aivec\Plugins\DocParser\Importer;

use phpDocumentor\Reflection\BaseReflector;
use PHPParser_PrettyPrinter_Default;

/**
* Custom reflector for WordPress hooks.
*/
class HookReflector extends BaseReflector
{
/**
* Returns name
*
* @return string
*/
public function getName() {
$printer = new PHPParser_PrettyPrinter_Default();
return $this->cleanupName($printer->prettyPrintExpr($this->node->args[0]->value));
}

/**
* Cleans up name
*
* @param string $name
* @return string
*/
private function cleanupName($name) {
$matches = [];

// quotes on both ends of a string
if (preg_match('/^[\'"]([^\'"]*)[\'"]$/', $name, $matches)) {
return $matches[1];
}

// two concatenated things, last one of them a variable
if (
preg_match(
'/(?:[\'"]([^\'"]*)[\'"]\s*\.\s*)?' . // First filter name string (optional)
'(\$[^\s]*)' . // Dynamic variable
'(?:\s*\.\s*[\'"]([^\'"]*)[\'"])?/', // Second filter name string (optional)
$name,
$matches
)
) {
if (isset($matches[3])) {
return $matches[1] . '{' . $matches[2] . '}' . $matches[3];
} else {
return $matches[1] . '{' . $matches[2] . '}';
}
}

return $name;
}

/**
* Returns short name
*
* @return string
*/
public function getShortName() {
return $this->getName();
}

/**
* Returns type
*
* @return string
*/
public function getType() {
$type = 'filter';
switch ((string)$this->node->name) {
case 'do_action':
$type = 'action';
break;
case 'do_action_ref_array':
$type = 'action_reference';
break;
case 'do_action_deprecated':
$type = 'action_deprecated';
break;
case 'apply_filters_ref_array':
$type = 'filter_reference';
break;
case 'apply_filters_deprecated':
$type = 'filter_deprecated';
break;
}

return $type;
}

/**
* Returns arguments
*
* @return array
*/
public function getArgs() {
$printer = new PrettyPrinter();
$args = [];
foreach ($this->node->args as $arg) {
$args[] = $printer->prettyPrintArg($arg);
}

// Skip the filter name
array_shift($args);

return $args;
}
}
Loading