) will be sorted instead
23 | * of the itself.
24 | */
25 | jQuery.fn.sortElements = (function(){
26 |
27 | var sort = [].sort;
28 |
29 | return function(comparator, getSortable) {
30 |
31 | getSortable = getSortable || function(){return this;};
32 |
33 | var placements = this.map(function(){
34 |
35 | var sortElement = getSortable.call(this),
36 | parentNode = sortElement.parentNode,
37 |
38 | // Since the element itself will change position, we have
39 | // to have some way of storing it's original position in
40 | // the DOM. The easiest way is to have a 'flag' node:
41 | nextSibling = parentNode.insertBefore(
42 | document.createTextNode(''),
43 | sortElement.nextSibling
44 | );
45 |
46 | return function() {
47 |
48 | if (parentNode === this) {
49 | throw new Error(
50 | "You can't sort elements if any one is a descendant of another."
51 | );
52 | }
53 |
54 | // Insert before flag:
55 | parentNode.insertBefore(this, nextSibling);
56 | // Remove flag:
57 | parentNode.removeChild(nextSibling);
58 |
59 | };
60 |
61 | });
62 |
63 | return sort.call(this, comparator).each(function(i){
64 | placements[i].call(getSortable.call(this));
65 | });
66 |
67 | };
68 |
69 | })();
--------------------------------------------------------------------------------
/templates/pages/classlike.twig:
--------------------------------------------------------------------------------
1 | {% extends 'page.twig' %}
2 |
3 | {% block title type|capitalize ~ ' ' ~ ref.name %}
4 |
5 | {% block content %}
6 |
7 |
8 | {{ type|capitalize }}
9 | {% if ref.doc.tags.deprecated is defined %}
10 | {{ ref.name }}
11 | {% else %}
12 | {{ ref.name }}
13 | {% endif %}
14 | {% if ref.source.inProject %}
15 |
16 | {% endif %}
17 |
18 |
19 |
20 | {{ ref.doc.summary|markdown_to_html }}
21 | {{ ref.doc.description|markdown_to_html }}
22 |
23 |
24 | {#{{ macros.showInheritanceTree(ref.inheritanceTree) }}#}
25 |
26 |
27 | {% if ref.final ?? false %}
28 |
Final
29 | {% endif %}
30 | {% if ref.abstract ?? false %}
31 |
Abstract
32 | {% endif %}
33 |
34 |
Namespace: {{ ref.context.namespace }}
35 |
36 | {% if ref.doc.tags.deprecated is defined %}
37 |
Deprecated: {{ ref.doc.tags.deprecated }}
38 | {% endif %}
39 |
40 | {% if ref.doc.tags.experimental is defined %}
41 |
Experimental: Expect API changes in minor releases.
42 | {% endif %}
43 |
44 | {% for tag in ref.doc.tags.see|default([]) %}
45 |
See: {{ tag.value }}
46 | {% endfor %}
47 |
48 | {% for tag in ref.doc.tags.link|default([]) %}
49 |
Link: {{ tag.value }}
50 | {% endfor %}
51 |
52 |
53 | {% if ref.constants %}
54 | {{ include('pages/parts/constant-detail.twig', {constants: ref.constants}) }}
55 | {% endif %}
56 |
57 | {% if ref.properties %}
58 | {{ include('pages/parts/property-summary.twig', {properties: ref.properties}) }}
59 | {% endif %}
60 |
61 | {% if ref.methods %}
62 | {{ include('pages/parts/function-summary.twig', {functions: ref.methods}) }}
63 |
64 | {{ include('pages/parts/function-detail.twig', {functions: ref.methods}) }}
65 | {% endif %}
66 |
67 | {% if ref.properties %}
68 | {{ include('pages/parts/property-detail.twig', {properties: ref.properties}) }}
69 | {% endif %}
70 |
71 | {% endblock %}
72 |
--------------------------------------------------------------------------------
/src/Util/DocUtil.php:
--------------------------------------------------------------------------------
1 | parse(new TokenIterator(static::$docLexer->tokenize($comment)));
48 | }
49 |
50 | return new PhpDocNode([]);
51 | }
52 |
53 | /**
54 | * @param string $type Type string
55 | * @return \PHPStan\PhpDocParser\Ast\Type\TypeNode
56 | */
57 | public static function parseType(string $type): TypeNode
58 | {
59 | static::init();
60 |
61 | return static::$typeParser->parse(new TokenIterator(static::$docLexer->tokenize($type)));
62 | }
63 |
64 | /**
65 | * Initialize parser.
66 | *
67 | * @return void
68 | */
69 | protected static function init(): void
70 | {
71 | if (!static::$initialized) {
72 | $exprParser = new ConstExprParser();
73 | static::$typeParser = new TypeParser($exprParser);
74 | static::$docParser = new PhpDocParser(static::$typeParser, $exprParser);
75 | static::$docLexer = new Lexer();
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Reflection/Context.php:
--------------------------------------------------------------------------------
1 | namespace = $namespace;
36 | }
37 |
38 | /**
39 | * @param string $name ClassLike name
40 | * @return string
41 | */
42 | public function resolveConstant(string $name): string
43 | {
44 | return $this->resolve($this->constants, $name);
45 | }
46 |
47 | /**
48 | * @param string $name Function name
49 | * @return string
50 | */
51 | public function resolveFunction(string $name): string
52 | {
53 | return $this->resolve($this->functions, $name);
54 | }
55 |
56 | /**
57 | * @param string $name ClassLike name
58 | * @return string
59 | */
60 | public function resolveClassLike(string $name): string
61 | {
62 | return $this->resolve($this->classLikes, $name);
63 | }
64 |
65 | /**
66 | * @param array $imports Imports
67 | * @param string $name Element name
68 | * @return string
69 | */
70 | protected function resolve(array $imports, string $name): string
71 | {
72 | if (!$name) {
73 | return $name;
74 | }
75 |
76 | if ($name[0] === '\\') {
77 | return substr($name, 1);
78 | }
79 |
80 | $first = strchr($name, '\\', true);
81 | if ($first === false) {
82 | $first = $name;
83 | $append = '';
84 | } else {
85 | $append = substr($name, strlen($first));
86 | }
87 |
88 | if (isset($imports[$first])) {
89 | return $imports[$first] . $append;
90 | }
91 |
92 | return $this->namespace ? ($this->namespace . '\\' . $name) : $name;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Loader.php:
--------------------------------------------------------------------------------
1 | projectPath = $projectPath;
44 | $this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
45 | }
46 |
47 | /**
48 | * @param string $path Directory path
49 | * @param bool $inProject Whether directory is within project
50 | * @return array
51 | */
52 | public function loadDirectory(string $path, bool $inProject): array
53 | {
54 | $nodes = [];
55 | $directoryIterator = new RecursiveDirectoryIterator(
56 | $path,
57 | FilesystemIterator::SKIP_DOTS | FilesystemIterator::CURRENT_AS_PATHNAME
58 | );
59 | foreach (new RecursiveIteratorIterator($directoryIterator) as $filePath) {
60 | if (preg_match('/\.php$/', $filePath)) {
61 | $nodes = array_merge($nodes, $this->loadFile($filePath, $inProject));
62 | }
63 | }
64 |
65 | return $nodes;
66 | }
67 |
68 | /**
69 | * @param string $path File path
70 | * @param bool $inProject Whether file is within project
71 | * @return array
72 | */
73 | public function loadFile(string $path, bool $inProject): array
74 | {
75 | $stmts = $this->parser->parse(file_get_contents($path));
76 |
77 | $traverser = new NodeTraverser();
78 | $visitor = new FileVisitor(new Factory(), substr($path, strlen($this->projectPath) + 1), $inProject);
79 | $traverser->addVisitor($visitor);
80 | $traverser->traverse($stmts);
81 |
82 | $nodes = $visitor->getNodes();
83 | foreach ($nodes as $node) {
84 | if ($node instanceof ReflectedClassLike) {
85 | $this->cache[$node->qualifiedName()] = $node;
86 | }
87 | }
88 |
89 | return $nodes;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/static/assets/resources/css/prism.css:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.22.0
2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+javadoclike+markup-templating+php+phpdoc+php-extras */
3 | /**
4 | * prism.js default theme for JavaScript, CSS and HTML
5 | * Based on dabblet (http://dabblet.com)
6 | * @author Lea Verou
7 | */
8 |
9 | code[class*="language-"],
10 | pre[class*="language-"] {
11 | color: black;
12 | background: none;
13 | text-shadow: 0 1px white;
14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
15 | text-align: left;
16 | white-space: pre;
17 | word-spacing: normal;
18 | word-break: normal;
19 | word-wrap: normal;
20 | line-height: 1.5;
21 |
22 | -moz-tab-size: 4;
23 | -o-tab-size: 4;
24 | tab-size: 4;
25 |
26 | -webkit-hyphens: none;
27 | -moz-hyphens: none;
28 | -ms-hyphens: none;
29 | hyphens: none;
30 | }
31 |
32 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
33 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
34 | text-shadow: none;
35 | background: #b3d4fc;
36 | }
37 |
38 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
39 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
40 | text-shadow: none;
41 | background: #b3d4fc;
42 | }
43 |
44 | @media print {
45 | code[class*="language-"],
46 | pre[class*="language-"] {
47 | text-shadow: none;
48 | }
49 | }
50 |
51 | /* Code blocks */
52 | pre[class*="language-"] {
53 | padding: 1em;
54 | margin: .5em 0;
55 | overflow: auto;
56 | }
57 |
58 | :not(pre) > code[class*="language-"],
59 | pre[class*="language-"] {
60 | background: #f5f2f0;
61 | }
62 |
63 | /* Inline code */
64 | :not(pre) > code[class*="language-"] {
65 | padding: .1em;
66 | border-radius: .3em;
67 | white-space: normal;
68 | }
69 |
70 | .token.comment,
71 | .token.prolog,
72 | .token.doctype,
73 | .token.cdata {
74 | color: slategray;
75 | }
76 |
77 | .token.punctuation {
78 | color: #999;
79 | }
80 |
81 | .token.namespace {
82 | opacity: .7;
83 | }
84 |
85 | .token.property,
86 | .token.tag,
87 | .token.boolean,
88 | .token.number,
89 | .token.constant,
90 | .token.symbol,
91 | .token.deleted {
92 | color: #905;
93 | }
94 |
95 | .token.selector,
96 | .token.attr-name,
97 | .token.string,
98 | .token.char,
99 | .token.builtin,
100 | .token.inserted {
101 | color: #690;
102 | }
103 |
104 | .token.operator,
105 | .token.entity,
106 | .token.url,
107 | .language-css .token.string,
108 | .style .token.string {
109 | color: #9a6e3a;
110 | /* This background color was intended by the author of this theme. */
111 | background: hsla(0, 0%, 100%, .5);
112 | }
113 |
114 | .token.atrule,
115 | .token.attr-value,
116 | .token.keyword {
117 | color: #07a;
118 | }
119 |
120 | .token.function,
121 | .token.class-name {
122 | color: #DD4A68;
123 | }
124 |
125 | .token.regex,
126 | .token.important,
127 | .token.variable {
128 | color: #e90;
129 | }
130 |
131 | .token.important,
132 | .token.bold {
133 | font-weight: bold;
134 | }
135 | .token.italic {
136 | font-style: italic;
137 | }
138 |
139 | .token.entity {
140 | cursor: help;
141 | }
142 |
143 |
--------------------------------------------------------------------------------
/templates/footer.twig:
--------------------------------------------------------------------------------
1 |
17 |
18 |
78 |
--------------------------------------------------------------------------------
/src/ProjectNamespace.php:
--------------------------------------------------------------------------------
1 |
36 | */
37 | public array $children = [];
38 |
39 | /**
40 | * @var array
41 | */
42 | public array $defines = [];
43 |
44 | /**
45 | * @var array
46 | */
47 | public array $constants = [];
48 |
49 | /**
50 | * @var array
51 | */
52 | public array $functions = [];
53 |
54 | /**
55 | * @var array
56 | */
57 | public array $interfaces = [];
58 |
59 | /**
60 | * @var array
61 | */
62 | public array $classes = [];
63 |
64 | /**
65 | * @var array
66 | */
67 | public array $traits = [];
68 |
69 | /**
70 | * @param string $name Display name
71 | * @param string|null $qualifiedName Qualified name
72 | */
73 | public function __construct(string $name, ?string $qualifiedName)
74 | {
75 | $this->name = $name;
76 | $this->qualifiedName = $qualifiedName;
77 | }
78 |
79 | /**
80 | * @return bool
81 | */
82 | public function isEmpty(): bool
83 | {
84 | return empty($this->children) &&
85 | empty($this->constants) &&
86 | empty($this->functions) &&
87 | empty($this->interfaces) &&
88 | empty($this->classes) &&
89 | empty($this->traits);
90 | }
91 |
92 | /**
93 | * @param \Cake\ApiDocs\Reflection\ReflectedNode $ref Ref to add
94 | * @return void
95 | */
96 | public function addNode(ReflectedNode $ref): void
97 | {
98 | if ($ref instanceof ReflectedDefine) {
99 | $this->constants[$ref->name] = $ref;
100 | ksort($this->constants);
101 | } elseif ($ref instanceof ReflectedFunction) {
102 | $this->functions[$ref->name] = $ref;
103 | ksort($this->functions);
104 | } elseif ($ref instanceof ReflectedInterface) {
105 | $this->interfaces[$ref->name] = $ref;
106 | ksort($this->interfaces);
107 | } elseif ($ref instanceof ReflectedClass) {
108 | $this->classes[$ref->name] = $ref;
109 | ksort($this->classes);
110 | } elseif ($ref instanceof ReflectedTrait) {
111 | $this->traits[$ref->name] = $ref;
112 | ksort($this->traits);
113 | } else {
114 | throw new InvalidArgumentException(sprintf('Cannot add node of type %s.', get_class($ref)));
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Twig/Extension/ReflectionExtension.php:
--------------------------------------------------------------------------------
1 | project = $project;
40 | }
41 |
42 | /**
43 | * @inheritDoc
44 | */
45 | public function getFilters()
46 | {
47 | return [
48 | new TwigFilter('classlike_to_url', function (string $name, string $type) {
49 | return sprintf('%s-%s.html', $type, preg_replace('[\\\\]', '.', $name));
50 | }),
51 | new TwigFilter('namespace_to_url', function (?string $name) {
52 | return sprintf('namespace-%s.html', preg_replace('[\\\\]', '.', $name ?: 'Global'));
53 | }),
54 | new TwigFilter('type', function (?TypeNode $type) {
55 | // Remove parenthesis added by phpstan around union and intersection types
56 | // Remove space around union and intersection delimiter
57 | // Remove leading \ in front of types
58 | return preg_replace(
59 | [
60 | '/ ([|&]) /',
61 | '/<\(/',
62 | '/\)>/',
63 | '/\), /',
64 | '/, \(/',
65 | '/^\((.*)\)$/',
66 | '/(?<=[(<|&])\\\\/',
67 | '/^\\\\/',
68 | ],
69 | ['${1}', '<', '>', ', ', ', ', '${1}', '', ''],
70 | (string)$type
71 | );
72 | }),
73 | new TwigFilter('node_to_repo_url', function (ReflectedNode $node) {
74 | preg_match('/^(?:origin\/)?(.*)$/', $this->project->getConfig('tag'), $matches);
75 |
76 | return sprintf(
77 | '%s/blob/%s/%s#L%d',
78 | $this->project->getConfig('repo'),
79 | $matches[1],
80 | $node->source->path,
81 | $node->source->startLine
82 | );
83 | }),
84 | ];
85 | }
86 |
87 | /**
88 | * @inheritDoc
89 | */
90 | public function getTests()
91 | {
92 | return [
93 | ];
94 | }
95 |
96 | /**
97 | * @inheritDoc
98 | */
99 | public function getFunctions()
100 | {
101 | return [
102 | new TwigFunction('inNamespace', function (?string $name, ?string $namespace) {
103 | if ($name === null && $namespace !== null) {
104 | return false;
105 | }
106 |
107 | if ($name === null && $namespace == null) {
108 | return true;
109 | }
110 |
111 | return str_starts_with($namespace ?? '', $name);
112 | }),
113 | ];
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Command/GenerateCommand.php:
--------------------------------------------------------------------------------
1 | addArgument('project-path', [
42 | 'required' => true,
43 | 'help' => 'The project root path. Source directories are relative to this.',
44 | ]);
45 |
46 | $parser->addOption('output-dir', [
47 | 'required' => true,
48 | 'help' => 'The render output directory.',
49 | ]);
50 |
51 | $parser->addOption('config', [
52 | 'required' => true,
53 | 'help' => 'The config name to use, for example: cakephp4.',
54 | ]);
55 |
56 | $parser->addOption('version', [
57 | 'required' => true,
58 | 'help' => 'The project version (example: 4.0)',
59 | ]);
60 |
61 | $parser->addOption('tag', [
62 | 'required' => true,
63 | 'help' => 'The github tag or branch name',
64 | ]);
65 |
66 | return $parser;
67 | }
68 |
69 | /**
70 | * @inheritDoc
71 | */
72 | public function execute(Arguments $args, ConsoleIo $io): ?int
73 | {
74 | Configure::config('default', new PhpConfig());
75 | Configure::load($args->getOption('config'), 'default', false);
76 |
77 | Configure::write('Project.version', $args->getOption('version'));
78 | Configure::write('Project.tag', $args->getOption('tag'));
79 |
80 | $project = new Project($args->getArgumentAt(0), Configure::read('Project'));
81 |
82 | $twig = $this->createTwig(Configure::read('Twig.templateDir'), $project);
83 | $twig->addGlobal('version', Configure::read('Project.version'));
84 | foreach (Configure::read('Twig.globals') as $name => $value) {
85 | $twig->addGlobal($name, $value);
86 | }
87 |
88 | $generator = new Generator($twig, $args->getOption('output-dir'));
89 | $generator->generate($project);
90 |
91 | return static::CODE_SUCCESS;
92 | }
93 |
94 | /**
95 | * @param string $templateDir Twig template dirctory
96 | * @param \Cake\ApiDocs\Project $project Api Project
97 | * @return \Twig\Environment
98 | */
99 | protected function createTwig(string $templateDir, Project $project): Environment
100 | {
101 | $twig = new Environment(
102 | new FilesystemLoader($templateDir),
103 | ['strict_variables' => true]
104 | );
105 |
106 | $twig->addRuntimeLoader(new TwigRuntimeLoader());
107 | $twig->addExtension(new MarkdownExtension());
108 | $twig->addExtension(new ReflectionExtension($project));
109 |
110 | return $twig;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Reflection/DocBlock.php:
--------------------------------------------------------------------------------
1 | children as $childNode) {
40 | if (!$childNode instanceof PhpDocTextNode) {
41 | break;
42 | }
43 |
44 | if (!$this->summary) {
45 | $this->summary = $childNode->text;
46 | } else {
47 | if ($this->description) {
48 | $this->description .= "\n" . $childNode->text;
49 | } else {
50 | $this->description = $childNode->text;
51 | }
52 | }
53 | }
54 |
55 | $tags = $node->getTagsByName('@template');
56 | array_walk($tags, fn($tag) => $this->tags['template'][] = $tag->value);
57 |
58 | $tags = $node->getTagsByName('@var');
59 | if ($tags) {
60 | $this->tags['var'] = current($tags)->value;
61 | }
62 |
63 | $tags = $node->getTagsByName('@param');
64 | foreach ($tags as $tag) {
65 | if ($tag->value instanceof InvalidTagValueNode) {
66 | continue;
67 | }
68 | $this->tags['param'][$tag->value->parameterName] = $tag->value;
69 | }
70 |
71 | $tags = $node->getTagsByName('@return');
72 | if ($tags) {
73 | $this->tags['return'] = current($tags)->value;
74 | }
75 |
76 | foreach (['@property', '@property-read', '@property-write'] as $tagName) {
77 | $tags = $node->getTagsByName($tagName);
78 | foreach ($tags as $tag) {
79 | if ($tag->value instanceof InvalidTagValueNode) {
80 | continue;
81 | }
82 | $this->tags[$tagName][$tag->value->propertyName] = $tag->value;
83 | }
84 | }
85 |
86 | $tags = $node->getTagsByName('@method');
87 | foreach ($tags as $tag) {
88 | if ($tag->value instanceof InvalidTagValueNode) {
89 | continue;
90 | }
91 | $this->tags['@method'][$tag->value->methodName] = $tag->value;
92 | }
93 |
94 | $tags = $node->getTagsByName('@throws');
95 | array_walk($tags, fn($tag) => $this->tags['throws'][] = $tag->value);
96 |
97 | $tags = $node->getTagsByName('@see');
98 | array_walk($tags, fn($tag) => $this->tags['see'][] = $tag->value);
99 |
100 | $tags = $node->getTagsByName('@link');
101 | array_walk($tags, fn($tag) => $this->tags['link'][] = $tag->value);
102 |
103 | $tags = $node->getTagsByName('@deprecated');
104 | if ($tags) {
105 | $this->tags['deprecated'] = current($tags)->value;
106 | }
107 |
108 | $tags = $node->getTagsByName('@experimental');
109 | if ($tags) {
110 | $this->tags['experimental'] = current($tags)->value;
111 | }
112 |
113 | $tags = $node->getTagsByName('@internal');
114 | if ($tags) {
115 | $this->tags['internal'] = current($tags)->value;
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/templates/page.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 | {% block title 'API' %} | {{ project ~ ' ' ~ version }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
55 |
56 |
57 |
58 | {{ include('header.twig') }}
59 |
60 |
61 |
62 |
63 |
87 |
88 |
89 |
90 | {% block content %}{% endblock %}
91 |
92 |
93 |
94 |
95 |
96 | {{ include('footer.twig') }}
97 |
98 |
99 |
--------------------------------------------------------------------------------
/static/assets/js/jquery.cookie.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Cookie plugin
3 | *
4 | * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
5 | * Dual licensed under the MIT and GPL licenses:
6 | * http://www.opensource.org/licenses/mit-license.php
7 | * http://www.gnu.org/licenses/gpl.html
8 | *
9 | */
10 |
11 | /**
12 | * Create a cookie with the given name and value and other optional parameters.
13 | *
14 | * @example $.cookie('the_cookie', 'the_value');
15 | * @desc Set the value of a cookie.
16 | * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
17 | * @desc Create a cookie with all available options.
18 | * @example $.cookie('the_cookie', 'the_value');
19 | * @desc Create a session cookie.
20 | * @example $.cookie('the_cookie', null);
21 | * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
22 | * used when the cookie was set.
23 | *
24 | * @param String name The name of the cookie.
25 | * @param String value The value of the cookie.
26 | * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
27 | * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
28 | * If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
29 | * If set to null or omitted, the cookie will be a session cookie and will not be retained
30 | * when the the browser exits.
31 | * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
32 | * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
33 | * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
34 | * require a secure protocol (like HTTPS).
35 | * @type undefined
36 | *
37 | * @name $.cookie
38 | * @cat Plugins/Cookie
39 | * @author Klaus Hartl/klaus.hartl@stilbuero.de
40 | */
41 |
42 | /**
43 | * Get the value of a cookie with the given name.
44 | *
45 | * @example $.cookie('the_cookie');
46 | * @desc Get the value of a cookie.
47 | *
48 | * @param String name The name of the cookie.
49 | * @return The value of the cookie.
50 | * @type String
51 | *
52 | * @name $.cookie
53 | * @cat Plugins/Cookie
54 | * @author Klaus Hartl/klaus.hartl@stilbuero.de
55 | */
56 | jQuery.cookie = function(name, value, options) {
57 | if (typeof value != 'undefined') { // name and value given, set cookie
58 | options = options || {};
59 | if (value === null) {
60 | value = '';
61 | options.expires = -1;
62 | }
63 | var expires = '';
64 | if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
65 | var date;
66 | if (typeof options.expires == 'number') {
67 | date = new Date();
68 | date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
69 | } else {
70 | date = options.expires;
71 | }
72 | expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
73 | }
74 | // CAUTION: Needed to parenthesize options.path and options.domain
75 | // in the following expressions, otherwise they evaluate to undefined
76 | // in the packed version for some reason...
77 | var path = options.path ? '; path=' + (options.path) : '';
78 | var domain = options.domain ? '; domain=' + (options.domain) : '';
79 | var secure = options.secure ? '; secure' : '';
80 | document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
81 | } else { // only name given, get cookie
82 | var cookieValue = null;
83 | if (document.cookie && document.cookie != '') {
84 | var cookies = document.cookie.split(';');
85 | for (var i = 0; i < cookies.length; i++) {
86 | var cookie = jQuery.trim(cookies[i]);
87 | // Does this cookie string begin with the name we want?
88 | if (cookie.substring(0, name.length + 1) == (name + '=')) {
89 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
90 | break;
91 | }
92 | }
93 | }
94 | return cookieValue;
95 | }
96 | };
--------------------------------------------------------------------------------
/templates/pages/parts/function-detail.twig:
--------------------------------------------------------------------------------
1 |
2 |
{{ title|default('Method') }} Detail
3 | {% for function in functions %}
4 |
5 |
6 |
7 | {{ function.name }}()
8 | ¶
9 | {% if function.source.inProject %}
10 |
11 | {% endif %}
12 |
13 | {% if function.abstract|default(false) %}
14 | abstract
15 | {% endif %}
16 |
17 | {% if function.visibility is defined %}
18 | {{ function.visibility }}
19 | {% endif %}
20 | {% if function.static %}
21 | static
22 | {% endif %}
23 | {% if function.annotation|default(null) %}
24 | {{ function.annotation }}
25 | {% endif %}
26 | {% if function.doc.tags.depecated is defined %}
27 | deprecated
28 | {% endif %}
29 |
30 |
31 |
32 | {{- function.name }}(
33 | {%- for param in function.params %}
34 | {{- param.type|type|default('mixed') }} {% if param.variadic %}...{% endif %}${{ param.name }}{{ param.default is not null ? ' = ' ~ param.default : '' }}{{ loop.last?'':', ' }}
35 | {%- endfor -%}
36 | ){{ function.returnType ? ': ' ~ function.returnType|type : '' -}}
37 |
38 |
39 |
40 | {{ function.doc.summary|markdown_to_html }}
41 | {{ function.doc.description|markdown_to_html }}
42 |
43 | {% if function.doc.tags.template is defined %}
44 |
Templates
45 |
46 | {% for tag in function.doc.tags.template %}
47 |
48 | {{ tag.name }}
49 | {% if tag.bound is not null %} of {{tag.bound}}{% endif %}
50 | {% if tag.default is not null %} = {{tag.default}}{% endif %}
51 |
52 | {{ tag.description }}{{ loop.last ? '' : ' ' }}
53 | {% endfor %}
54 |
55 | {% endif %}
56 |
57 | {% if function.params %}
58 |
Parameters
59 |
60 |
61 | {% for param in function.params %}
62 |
63 | {{ param.type|type }}
64 | {% if param.variadic %}...{% endif %}${{ param.name }}
65 | {% if param.default is not null %}
66 | optional
67 | {% endif %}
68 |
69 | {{ (param.description ?? '')|markdown_to_html }}
70 | {% endfor %}
71 |
72 |
73 | {% endif %}
74 |
75 | {% if function.returnType %}
76 |
Returns
77 |
78 | {{ function.returnType|type }}
79 | {% if function.returnDescription %}
80 | {{ function.returnDescription|markdown_to_html }}
81 | {% endif %}
82 |
83 | {% endif %}
84 |
85 | {% if function.doc.tags.throws is defined %}
86 |
Throws
87 |
88 | {% for tag in function.doc.tags.throws %}
89 | {{ tag.type|type }}
90 | {{ tag.description }}{{ loop.last ? '' : ' ' }}
91 | {% endfor %}
92 |
93 | {% endif %}
94 |
95 | {% if function.doc.tags.see is defined %}
96 |
See Also
97 |
98 | {% for tag in function.doc.tags.see %}
99 | {{ tag.value }}{{ loop.last ? '': ' ' }}
100 | {% endfor %}
101 |
102 | {% endif %}
103 |
104 | {% if function.doc.tags.link is defined %}
105 |
Links
106 |
107 | {% for tag in function.doc.tags.link %}
108 | {{ tag.value }} {{ loop.last ? '' : ' ' }}
109 | {% endfor %}
110 |
111 | {% endif %}
112 |
113 |
114 | {% endfor %}
115 |
116 |
--------------------------------------------------------------------------------
/src/Util/MergeUtil.php:
--------------------------------------------------------------------------------
1 | constants as $name => $sourceConstant) {
45 | $targetConstant = $target->constants[$name] ?? null;
46 | if (!$targetConstant) {
47 | $target->constants[$name] = clone $sourceConstant;
48 | continue;
49 | }
50 |
51 | static::mergeDoc($targetConstant->doc, $sourceConstant->doc);
52 | if ($targetConstant->type === null) {
53 | $targetConstant->type = $sourceConstant->type;
54 | }
55 | }
56 | ksort($target->constants);
57 | }
58 |
59 | /**
60 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $target Target class-like
61 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $source Source class-like
62 | * @return void
63 | */
64 | protected static function mergeProperties(ReflectedClassLike $target, ReflectedClassLike $source): void
65 | {
66 | foreach ($source->properties as $name => $sourceProperty) {
67 | $targetProperty = $target->properties[$name] ?? null;
68 | if (!$targetProperty) {
69 | $target->properties[$name] = clone $sourceProperty;
70 | continue;
71 | }
72 |
73 | static::mergeDoc($targetProperty->doc, $sourceProperty->doc);
74 | if (
75 | $targetProperty->type === null ||
76 | (
77 | !isset($targetProperty->doc->tags['var']) &&
78 | $targetProperty->nativeType == $sourceProperty->nativeType
79 | )
80 | ) {
81 | $targetProperty->type = $sourceProperty->type;
82 | }
83 | }
84 | ksort($target->properties);
85 | }
86 |
87 | /**
88 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $target Target class-like
89 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $source Source class-like
90 | * @return void
91 | */
92 | protected static function mergeMethods(ReflectedClassLike $target, ReflectedClassLike $source): void
93 | {
94 | foreach ($source->methods as $name => $sourceMethod) {
95 | $targetMethod = $target->methods[$name] ?? null;
96 | if (!$targetMethod) {
97 | $target->methods[$name] = clone $sourceMethod;
98 | continue;
99 | }
100 |
101 | if (count($targetMethod->params) !== count($sourceMethod->params)) {
102 | // Ignore methods that have different number of parameters such as overriden constructors
103 | continue;
104 | }
105 |
106 | static::mergeDoc($targetMethod->doc, $sourceMethod->doc);
107 | if (
108 | $targetMethod->returnType === null ||
109 | (
110 | !isset($targetMethod->doc->tags['return']) &&
111 | $targetMethod->nativeReturnType == $sourceMethod->nativeReturnType
112 | )
113 | ) {
114 | $targetMethod->returnType = $sourceMethod->returnType;
115 | }
116 |
117 | foreach ($targetMethod->params as $param) {
118 | if (
119 | isset($sourceMethod->params[$param->name]) &&
120 | (
121 | $param->type === null ||
122 | (
123 | !isset($targetMethod->doc->tags['param'][$param->name]) &&
124 | $param->nativeType == $sourceMethod->params[$param->name]->nativeType
125 | )
126 | )
127 | ) {
128 | $param->type = $sourceMethod->params[$param->name]->type;
129 | }
130 | }
131 | }
132 | ksort($target->methods);
133 | }
134 |
135 | /**
136 | * @param \Cake\ApiDocs\Reflection\DocBlock $target Target doc
137 | * @param \Cake\ApiDocs\Reflection\DocBlock $source Source doc
138 | * @return bool
139 | */
140 | protected static function mergeDoc(DocBlock $target, DocBlock $source): bool
141 | {
142 | $inherited = false;
143 | if (preg_match('/(^$)|(^@inheritDoc$)|(^{@inheritDoc}$)/i', $target->summary)) {
144 | $target->summary = $source->summary;
145 | $inherited = true;
146 | }
147 |
148 | if (preg_match('/(^$)|(^@inheritDoc$)|(^{@inheritDoc}$)/i', $target->description)) {
149 | $target->description = $source->description;
150 | $inherited = true;
151 | } elseif (preg_match('/^(@inheritDoc|{@inheritDoc})\n\n([\s\S]+)/i', $target->description, $matches)) {
152 | $target->description = $source->description . "\n\n---\n\n" . $matches[2];
153 | $inherited = true;
154 | }
155 |
156 | return $inherited;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/Generator.php:
--------------------------------------------------------------------------------
1 | twig = $twig;
44 | $this->outputDir = $outputDir;
45 | }
46 |
47 | /**
48 | * Render project.
49 | *
50 | * @param \Cake\ApiDocs\Project $project Project
51 | * @return void
52 | */
53 | public function generate(Project $project): void
54 | {
55 | $this->log(sprintf('Generating project into `%s`', $this->outputDir), 'info');
56 |
57 | $this->twig->addGlobal('namespaces', $project->namespaces);
58 |
59 | $this->renderOverview();
60 | $this->renderSearch($project->namespaces);
61 |
62 | array_walk($project->namespaces, fn ($namespace) => $this->renderNamespace($namespace));
63 | }
64 |
65 | /**
66 | * Render overview page.
67 | *
68 | * @return void
69 | */
70 | public function renderOverview(): void
71 | {
72 | $this->renderTemplate('pages/overview.twig', 'index.html');
73 | }
74 |
75 | /**
76 | * Render all child namespaces, classes, interfaces and traits in namespace.
77 | *
78 | * @param \Cake\ApiDocs\ProjectNamespace $ns Project namespace
79 | * @return void
80 | */
81 | public function renderNamespace(ProjectNamespace $ns): void
82 | {
83 | $renderer = function (ProjectNamespace $ns) use (&$renderer): void {
84 | $this->renderTemplate(
85 | 'pages/namespace.twig',
86 | sprintf('namespace-%s.html', str_replace('\\', '.', $ns->qualifiedName ?? $ns->name)),
87 | ['namespace' => $ns, 'contextName' => $ns->name]
88 | );
89 |
90 | array_map(fn($interface) => $this->renderClassLike($interface, 'interface'), $ns->interfaces);
91 | array_map(fn($class) => $this->renderClassLike($class, 'class'), $ns->classes);
92 | array_map(fn($trait) => $this->renderClassLike($trait, 'trait'), $ns->traits);
93 |
94 | foreach ($ns->children as $child) {
95 | $renderer($child);
96 | }
97 | };
98 |
99 | $renderer($ns);
100 | }
101 |
102 | /**
103 | * Renders class, interface or trait.
104 | *
105 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $ref Reflected classlike
106 | * @param string $type Class-like type
107 | * @return void
108 | */
109 | protected function renderClassLike(ReflectedClassLike $ref, string $type): void
110 | {
111 | $filename = sprintf('%s-%s.html', $type, str_replace('\\', '.', $ref->qualifiedName()));
112 | $this->renderTemplate(
113 | 'pages/classlike.twig',
114 | $filename,
115 | ['ref' => $ref, 'type' => $type, 'contextName' => $ref->context->namespace]
116 | );
117 | }
118 |
119 | /**
120 | * Renders search data.
121 | *
122 | * @param array $namespaces Project namespaces
123 | * @return void
124 | */
125 | protected function renderSearch(array $namespaces): void
126 | {
127 | $entries = [];
128 |
129 | $addClassLike = function (ReflectedClassLike $classLike) use (&$entries) {
130 | $type = match (true) {
131 | $classLike instanceof ReflectedInterface => 'i',
132 | $classLike instanceof ReflectedTrait => 't',
133 | $classLike instanceof ReflectedClassLike => 'c'
134 | };
135 |
136 | $entries[] = [$type, $classLike->qualifiedName()];
137 | foreach ($classLike->constants as $constant) {
138 | if (
139 | $constant->visibility === 'public' &&
140 | (
141 | !$constant->source->inProject || $constant->owner === $classLike
142 | )
143 | ) {
144 | $entries[] = [$type, sprintf('%s::%s', $classLike->qualifiedName(), $constant->name)];
145 | }
146 | }
147 | foreach ($classLike->properties as $property) {
148 | if (
149 | $property->visibility === 'public' &&
150 | (
151 | !$property->source->inProject || $property->owner === $classLike
152 | )
153 | ) {
154 | $entries[] = [$type, sprintf('%s::$%s', $classLike->qualifiedName(), $property->name)];
155 | }
156 | }
157 | foreach ($classLike->methods as $method) {
158 | if (
159 | $method->visibility === 'public' &&
160 | (
161 | !$method->source->inProject || $method->owner === $classLike
162 | )
163 | ) {
164 | $entries[] = [$type, sprintf('%s::%s()', $classLike->qualifiedName(), $method->name)];
165 | }
166 | }
167 | };
168 |
169 | $addNamespace = function (ProjectNamespace $ns) use (&$addNamespace, $addClassLike) {
170 | array_walk($ns->children, fn ($ns) => $addNamespace($ns));
171 | array_walk($ns->interfaces, fn ($classLike) => $addClassLike($classLike));
172 | array_walk($ns->traits, fn ($classLike) => $addClassLike($classLike));
173 | array_walk($ns->classes, fn ($classLike) => $addClassLike($classLike));
174 | };
175 |
176 | array_walk($namespaces, $addNamespace);
177 |
178 | $this->renderTemplate('searchlist.twig', 'searchlist.js', ['entries' => json_encode(array_values($entries))]);
179 | }
180 |
181 | /**
182 | * @param string $template Twig template name
183 | * @param string $filename Output filename
184 | * @param array $context Twig render context
185 | * @return void
186 | */
187 | protected function renderTemplate(string $template, string $filename, array $context = []): void
188 | {
189 | $path = getcwd() . DS . $this->outputDir . DS . $filename;
190 | file_put_contents($path, $this->twig->render($template, $context));
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/static/assets/resources/css/responsive.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Phone/phablet layout.
3 | */
4 | @media (max-width: 767px) {
5 | .description {
6 | padding: 0;
7 | }
8 |
9 | .logo-cake img {
10 | margin-top: 20px;
11 | margin-bottom: 14px;
12 | }
13 |
14 | .navbar-right {
15 | float: right!important;
16 | margin-right: -15px;
17 | }
18 |
19 | .title-red {
20 | font-size: 40px;
21 | }
22 |
23 | #quote h4 {
24 | font-size: 35px;
25 | }
26 |
27 | #quote h5 {
28 | text-align: left;
29 | line-height: 1;
30 | margin-top: 30px;
31 | }
32 |
33 | #quote h6 {
34 | text-align: left;
35 | }
36 |
37 | .subtitle-black {
38 | font-size: 30px;
39 | line-height: 34px;
40 | }
41 |
42 |
43 | .search-form {
44 | top: -21px;
45 | }
46 |
47 | .icon-irc, .icon-social-q {
48 | margin-bottom: 30px;
49 | }
50 |
51 | .submenu {
52 | background-color: #D33C44;
53 | }
54 |
55 | #wrap .ac_input:focus {
56 | width: 270px;
57 | }
58 |
59 | #wrap input[type="submit"] {
60 | height: 58px;
61 | }
62 |
63 | .nav-up {
64 | top: -600px;
65 | }
66 |
67 | .menu-title-m {
68 | margin-top: 180px;
69 | }
70 |
71 | .logo {
72 | padding: 14px 0;
73 | }
74 |
75 | .toggle-menu .fa {
76 | margin: 21px 0;
77 | }
78 |
79 | #sub {
80 | margin-top: 60px;
81 | }
82 |
83 | #cakefest p {
84 | text-align: center;
85 | }
86 |
87 | .sub-expertise {
88 | text-align: center;
89 | }
90 |
91 | #expertise p {
92 | text-align: center;
93 | }
94 |
95 | .icon-expertise-2 {
96 | text-align: center;
97 | }
98 |
99 | .title-expertise{
100 | text-align: center;
101 | }
102 |
103 | .box-services-d:hover .icon-expertise-2 {
104 | text-align: center;
105 | }
106 |
107 | .git-frame iframe{
108 | padding-left: 34%;
109 | }
110 |
111 | .social-footer a {
112 | padding: 10px 6px;
113 | }
114 |
115 | .social {
116 | margin-bottom: 15px;
117 | }
118 |
119 | .social iframe {
120 | margin: 5px;
121 | }
122 |
123 | /* ---------------------- COOK BOOK ------------------------- */
124 |
125 |
126 | #cookbook {
127 | padding-top: 50px;
128 | }
129 |
130 | .page-container {
131 | padding-top: 20px;
132 | }
133 |
134 | .nav-btn {
135 | padding-top: 73px;
136 | padding-bottom: 17px;
137 | }
138 |
139 | .read-the-book a {
140 | margin: 15px 5px 22px;
141 | }
142 |
143 | .page-container .row {
144 | margin-left: -10px;
145 | margin-right: -10px;
146 | }
147 |
148 | .btn-nav {
149 | font-size: 11px;
150 | }
151 |
152 | #improve-slideout {
153 | bottom: 0;
154 | }
155 |
156 | #improve-slideout-inner {
157 | bottom: 0;
158 | }
159 |
160 | .page-contents {
161 | float: none;
162 | margin-left: 0;
163 | }
164 |
165 | .list,
166 | .property-type {
167 | overflow-x: auto;
168 | }
169 |
170 | h1 {
171 | font-size: 34px;
172 | }
173 | }
174 |
175 | @media (min-width:768px) {
176 | .row.col-p30 { margin-left:-15px; margin-right:-15px; }
177 | }
178 |
179 | @media (min-width:1200px) {
180 | .row.col-p30 { margin-left:-30px; margin-right:-30px; }
181 |
182 | .row.col-p30 [class*="col-"].business-solution {
183 | padding: 0 15px;
184 | }
185 | }
186 |
187 |
188 |
189 |
190 | /**
191 | * Tablet Portrait layout.
192 | */
193 | @media (min-width: 768px) and (max-width: 991px) {
194 | .header-transparent .main-header {
195 | background-color: #d33c44;
196 | }
197 |
198 | .search-form {
199 | top: -21px;
200 | }
201 |
202 | .menu > li > a:hover, .menu > li > a:focus, .menu > li:hover > a {
203 | color: #ffffff;
204 | }
205 |
206 | .menu > li > a {
207 | padding: 25px 25px;
208 | }
209 |
210 | .logo-cake img {
211 | margin-top: 15px;
212 | margin-bottom: 14px;
213 | }
214 |
215 | #sub {
216 | margin-top: 60px;
217 | }
218 |
219 | /* ---------------------- COOK BOOK ------------------------- */
220 |
221 | .page-container {
222 | padding-top: 20px;
223 | }
224 |
225 | #wrap .ac_input:focus {
226 | width: 450px;
227 | }
228 |
229 | #wrap input[type="submit"] {
230 | height: 58px;
231 | }
232 |
233 | #improve-slideout {
234 | bottom: 0;
235 | }
236 |
237 | #improve-slideout-inner {
238 | bottom: 0;
239 | }
240 | }
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 | @media (min-width: 992px) and (max-width: 1199px) {
252 | /*CSS LIDO EM DISPOSITIVOS ATÉ 1199 PX DE LARGURA*/
253 | .menu > li > a {
254 | padding: 25px 20px;
255 | }
256 |
257 | .menu-fixed .menu > li > a {
258 | padding: 25px 25px;
259 | }
260 | .fa-menu-title {
261 | font-size: 17px;
262 | display: block;
263 | padding-bottom: 8px;
264 | }
265 | /* ---------------------- COOK BOOK ------------------------- */
266 |
267 | .t-language h6 {
268 | font-size: 9px;
269 | }
270 |
271 | .dropdown > a {
272 | font-size: 12px;
273 | padding: 5px 5px !important;
274 | }
275 | }
276 |
277 | /* Site menu in modal window */
278 | #modal .menu {
279 | float: none;
280 | }
281 | #modal .menu > li {
282 | float: none;
283 | display: block;
284 | width: 100%;
285 | margin-bottom: 1em;
286 | }
287 | #modal .menu .fa-chevron-down {
288 | display: none;
289 | }
290 | #modal .menu > li {
291 | background: #aaabac;
292 | border-bottom: 2px solid #aaabac;
293 | }
294 | #modal .menu > li > a {
295 | color: #363638;
296 | font-weight: bold;
297 | display: block;
298 | }
299 | #modal .menu a {
300 | color: #667;
301 | padding: 10px;
302 | }
303 | #modal .menu-title {
304 | padding: 2px 0 0 10px;
305 | color: #363637;
306 | }
307 | #modal .megamenu,
308 | #modal .submenu .submenu,
309 | #modal .submenu {
310 | margin: 2px 2px 0 2px;
311 | padding: 0;
312 | background: #F1F1F1;
313 | min-width: auto;
314 | width: auto;
315 | top: 0;
316 | left: 0;
317 | position: relative;
318 | z-index: auto;
319 | }
320 |
321 | #modal .megamenu {
322 | padding-top: 20px;
323 | }
324 |
325 | #modal .megamenu-list {
326 | padding-top: 5px;
327 | }
328 | #modal .megamenu-list li {
329 | padding: 0;
330 | }
331 | #modal .col-3 {
332 | padding: 0;
333 | width: 100%;
334 | border-bottom: 2px solid #aaabac;
335 | margin-bottom: 0;
336 | }
337 | #modal .col-3:last-child {
338 | border-bottom: 0;
339 | }
340 |
341 | /* Book nav in modal menu */
342 | #modal .back-book:hover,
343 | #modal .back-book {
344 | background: transparent;
345 | }
346 | #modal .back-book h6,
347 | #modal .back-book h2 {
348 | color: #363638;
349 | }
350 | #modal .t-language h6 {
351 | text-align: left;
352 | color: #363638;
353 | }
354 | #modal .search,
355 | #modal .dropdown .fa-chevron-down {
356 | display: none;
357 | }
358 | #modal .navbar-nav {
359 | float: none;
360 | }
361 | #modal .dropdown-toggle {
362 | display: inline-block;
363 | position: static;
364 | }
365 | #modal .dropdown-menu {
366 | float: none;
367 | display: inline;
368 | position: static;
369 | background: transparent;
370 | box-shadow: none;
371 | }
372 | #modal .dropdown-menu li {
373 | float: none;
374 | display: inline-block
375 | }
376 |
377 | @media (min-width: 768px) and (max-width: 1199px) {
378 | .social {
379 | margin-bottom: 20px;
380 | }
381 | }
382 |
--------------------------------------------------------------------------------
/src/FileVisitor.php:
--------------------------------------------------------------------------------
1 | factory = $factory;
58 | $this->filePath = $filePath;
59 | $this->inProject = $inProject;
60 | $this->context = new Context(null);
61 | }
62 |
63 | /**
64 | * @return array
65 | */
66 | public function getNodes(): array
67 | {
68 | return $this->nodes;
69 | }
70 |
71 | /**
72 | * @inheritDoc
73 | */
74 | public function enterNode(Node $node)
75 | {
76 | if ($node instanceof Namespace_) {
77 | $this->context = new Context((string)$node->name ?: null);
78 |
79 | return null;
80 | }
81 |
82 | if ($node instanceof Use_) {
83 | foreach ($node->uses as $use) {
84 | switch ($node->type) {
85 | case Use_::TYPE_NORMAL:
86 | $this->context->classLikes += $this->normalizeUse($use);
87 | break;
88 | case Use_::TYPE_FUNCTION:
89 | $this->context->functions += $this->normalizeUse($use);
90 | break;
91 | case Use_::TYPE_CONSTANT:
92 | $this->context->constants += $this->normalizeUse($use);
93 | break;
94 | }
95 | }
96 |
97 | return NodeTraverser::DONT_TRAVERSE_CHILDREN;
98 | }
99 |
100 | if ($node instanceof GroupUse) {
101 | $prefix = (string)$node->prefix;
102 | foreach ($node->uses as $use) {
103 | switch ($use->type) {
104 | case Use_::TYPE_NORMAL:
105 | $this->context->classLikes += $this->normalizeUse($use, $prefix);
106 | break;
107 | case Use_::TYPE_FUNCTION:
108 | $this->context->functions += $this->normalizeUse($use, $prefix);
109 | break;
110 | case Use_::TYPE_CONSTANT:
111 | $this->context->constants += $this->normalizeUse($use, $prefix);
112 | break;
113 | }
114 | }
115 |
116 | return NodeTraverser::DONT_TRAVERSE_CHILDREN;
117 | }
118 |
119 | if ($node instanceof FuncCall && (string)$node->name === 'define') {
120 | if (count($node->args) === 2 && $node->args[0]->value instanceof String_) {
121 | $const = new NodeConst_(
122 | (string)$node->args[0]->value->value,
123 | $node->args[1]->value,
124 | $node->getAttributes()
125 | );
126 | $source = new Source($this->filePath, $this->inProject, $const->getStartLine(), $const->getEndLine());
127 | $this->nodes[] = $this->factory->createDefine($const, $this->context, $source);
128 | }
129 |
130 | return NodeTraverser::DONT_TRAVERSE_CHILDREN;
131 | }
132 |
133 | if ($node instanceof Const_) {
134 | foreach ($node->consts as $const) {
135 | $source = new Source($this->filePath, $this->inProject, $const->getStartLine(), $const->getEndLine());
136 | $this->nodes[] = $this->factory->createDefine($const, $this->context, $source);
137 | }
138 |
139 | return NodeTraverser::DONT_TRAVERSE_CHILDREN;
140 | }
141 |
142 | if ($node instanceof Function_) {
143 | $source = new Source($this->filePath, $this->inProject, $node->getStartLine(), $node->getEndLine());
144 | $this->nodes[] = $this->factory->createFunction($node, $this->context, $source);
145 |
146 | return NodeTraverser::DONT_TRAVERSE_CHILDREN;
147 | }
148 |
149 | if ($node instanceof Interface_) {
150 | $source = new Source($this->filePath, $this->inProject, $node->getStartLine(), $node->getEndLine());
151 | $this->nodes[] = $this->factory->createInterface($node, $this->context, $source);
152 |
153 | return NodeTraverser::DONT_TRAVERSE_CHILDREN;
154 | }
155 |
156 | if ($node instanceof Class_) {
157 | $source = new Source($this->filePath, $this->inProject, $node->getStartLine(), $node->getEndLine());
158 | $this->nodes[] = $this->factory->createClass($node, $this->context, $source);
159 |
160 | return NodeTraverser::DONT_TRAVERSE_CHILDREN;
161 | }
162 |
163 | if ($node instanceof Trait_) {
164 | $source = new Source($this->filePath, $this->inProject, $node->getStartLine(), $node->getEndLine());
165 | $this->nodes[] = $this->factory->createTrait($node, $this->context, $source);
166 |
167 | return NodeTraverser::DONT_TRAVERSE_CHILDREN;
168 | }
169 |
170 | return null;
171 | }
172 |
173 | /**
174 | * @inheritDoc
175 | */
176 | public function leaveNode(Node $node)
177 | {
178 | if ($node instanceof Namespace_) {
179 | $this->context = new Context(null);
180 | }
181 |
182 | return null;
183 | }
184 |
185 | /**
186 | * @param \PhpParser\Node\Stmt\UseUse $use Use node
187 | * @param string|null $prefix Group use prefix
188 | * @return array
189 | */
190 | protected function normalizeUse(UseUse $use, ?string $prefix = null): array
191 | {
192 | $name = (string)$use->name;
193 | if ($prefix) {
194 | $name = $prefix . '\\' . $name;
195 | }
196 |
197 | $alias = $use->alias;
198 | if (!$alias) {
199 | $last = strrpos($name, '\\', -1);
200 | if ($last !== false) {
201 | $alias = substr($name, strrpos($name, '\\', -1) + 1);
202 | } else {
203 | $alias = $name;
204 | }
205 | }
206 |
207 | return [(string)$alias => $name];
208 | }
209 |
210 | /**
211 | * @param string $name Node name
212 | * @return string
213 | */
214 | protected function addNamespace(string $name): string
215 | {
216 | if ($this->context->namespace) {
217 | return $this->context->namespace . '\\' . $name;
218 | }
219 |
220 | return $name;
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/templates/header.twig:
--------------------------------------------------------------------------------
1 | {# Red Mega Menu #}
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
44 |
45 |
46 |
47 |
48 |
49 | {# Mobile responsive header #}
50 |
51 |
52 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
70 |
71 |
72 |
73 |
74 |
75 | {# Grey site bar #}
76 |
77 |
78 |
79 |
88 |
89 |
90 |
91 | {#
92 |
93 |
94 | Overview
95 |
96 | #}
97 |
98 | {#
99 |
100 |
101 | Tree
102 |
103 | #}
104 |
105 | {#
106 |
107 |
108 | Deprecated
109 |
110 | #}
111 |
112 | {# Project picker #}
113 |
114 | Project:
115 |
130 |
131 |
132 | {# Version picker #}
133 |
134 | Version:
135 |
147 |
148 |
149 |
150 |
151 |
163 |
164 |
165 |
166 |
167 |
168 | {# Responsive grey bar navigation. This is outside of header so it scrolls with the page. #}
169 |
170 |
171 |
172 |
173 | Navigation
174 |
175 |
176 |
177 | Class Navigation
178 |
179 |
180 |
181 |
182 |
183 | {# modal used in mobile responsive views #}
184 |
185 |
186 |
187 |
193 |
{# body is injected via js #}
194 |
195 |
196 |
197 |
--------------------------------------------------------------------------------
/static/assets/resources/favicons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/Project.php:
--------------------------------------------------------------------------------
1 |
37 | */
38 | public array $namespaces;
39 |
40 | protected Loader $loader;
41 |
42 | protected ?ClassLoader $classLoader;
43 |
44 | protected array $cache = [];
45 |
46 | protected array $_defaultConfig = [];
47 |
48 | /**
49 | * Loads projects.
50 | *
51 | * @param string $projectPath Project path
52 | * @param array $config Project config
53 | */
54 | public function __construct(string $projectPath, array $config)
55 | {
56 | $this->setConfig($config);
57 |
58 | foreach ((array)$this->_config['namespaces'] as $namespace) {
59 | $this->namespaces[$namespace] = new ProjectNamespace($namespace, $namespace);
60 | }
61 |
62 | $this->loader = new Loader($projectPath);
63 | $this->classLoader = $this->createClassLoader($projectPath);
64 |
65 | foreach ($this->_config['sourceDirs'] as $dir) {
66 | $this->log(sprintf('Loading sources from `%s`', $dir), 'info');
67 | $nodes = $this->loader->loadDirectory($projectPath . DIRECTORY_SEPARATOR . $dir, true);
68 |
69 | foreach ($nodes as $node) {
70 | if ($node instanceof ReflectedClassLike) {
71 | $this->cache[$node->qualifiedName()] = $node;
72 |
73 | if (in_array($node->qualifiedName(), $config['exclude']['classes'] ?? [], true)) {
74 | continue;
75 | }
76 | }
77 |
78 | if (in_array($node->context->namespace, $config['exclude']['namespaces'] ?? [], true)) {
79 | continue;
80 | }
81 |
82 | $namespace = $this->getNamespace($node->context->namespace);
83 | $namespace->addNode($node);
84 | }
85 | }
86 | ksort($this->namespaces);
87 |
88 | $this->mergeInherited();
89 | }
90 |
91 | /**
92 | * @return void
93 | */
94 | protected function mergeInherited(): void
95 | {
96 | $classLikeMerger = function (ReflectedClassLike $ref) use (&$classLikeMerger) {
97 | foreach ($ref->uses as $use) {
98 | $trait = $this->findClassLike($ref->context->resolveClassLike($use));
99 | if (!$trait) {
100 | continue;
101 | }
102 | $classLikeMerger($trait);
103 | MergeUtil::mergeClassLike($ref, $trait);
104 | }
105 |
106 | if ($ref instanceof ReflectedClass) {
107 | foreach ($ref->implements as $implement) {
108 | $interface = $this->findClassLike($ref->context->resolveClassLike($implement));
109 | if (!$interface) {
110 | continue;
111 | }
112 | $classLikeMerger($interface);
113 | MergeUtil::mergeClassLike($ref, $interface);
114 | }
115 | }
116 |
117 | if ($ref instanceof ReflectedInterface || $ref instanceof ReflectedClass) {
118 | foreach ((array)$ref->extends ?? [] as $extend) {
119 | $interface = $this->findClassLike($ref->context->resolveClassLike($extend));
120 | if (!$interface) {
121 | continue;
122 | }
123 | $classLikeMerger($interface);
124 | MergeUtil::mergeClassLike($ref, $interface);
125 | }
126 | }
127 | };
128 |
129 | $namespaceMerger = function (ProjectNamespace $ns) use (&$namespaceMerger, $classLikeMerger) {
130 | foreach ($ns->children as $child) {
131 | $namespaceMerger($child);
132 | }
133 |
134 | foreach ($ns->interfaces as $interface) {
135 | $classLikeMerger($interface);
136 | }
137 | foreach ($ns->traits as $trait) {
138 | $classLikeMerger($trait);
139 | }
140 | foreach ($ns->classes as $class) {
141 | $classLikeMerger($class);
142 | }
143 | };
144 |
145 | array_walk($this->namespaces, fn ($namespace) => $namespaceMerger($namespace));
146 | }
147 |
148 | /**
149 | * @param string $qualifiedName Qualified name
150 | * @return \Cake\ApiDocs\Reflection\ReflectedClassLike|null
151 | */
152 | protected function findClassLike(string $qualifiedName): ?ReflectedClassLike
153 | {
154 | if (array_key_exists($qualifiedName, $this->cache)) {
155 | return $this->cache[$qualifiedName];
156 | }
157 |
158 | if (!$this->classLoader) {
159 | return null;
160 | }
161 |
162 | $path = $this->classLoader->findFile($qualifiedName);
163 | if ($path === false) {
164 | return null;
165 | }
166 |
167 | $nodes = $this->loader->loadFile($path, false);
168 | foreach ($nodes as $node) {
169 | if ($node instanceof ReflectedClassLike && $node->qualifiedName() === $qualifiedName) {
170 | return $node;
171 | }
172 | }
173 |
174 | return $this->cache[$qualifiedName] = null;
175 | }
176 |
177 | /**
178 | * @param string $projectPath Project path
179 | * @return \Composer\Autoload\ClassLoader|null
180 | */
181 | protected function createClassLoader(string $projectPath): ?ClassLoader
182 | {
183 | // try to find vendor/ relative to sourceDir
184 | $vendorDir = $projectPath . DIRECTORY_SEPARATOR . 'vendor';
185 |
186 | $autoloadPath = $vendorDir . DIRECTORY_SEPARATOR . 'autoload.php';
187 | if (!file_exists($autoloadPath)) {
188 | $this->log("Unable to find class loader at `$autoloadPath`", 'warning');
189 |
190 | return null;
191 | }
192 |
193 | $this->log("Found class loader at `$autoloadPath`", 'info');
194 | $loader = new ClassLoader($vendorDir);
195 |
196 | // Get geneated class name from autoload_static.php
197 | $staticPath = $vendorDir . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'autoload_static.php';
198 | $contents = file_get_contents($staticPath);
199 | if ($contents === false) {
200 | $this->log("Unable to load `$staticPath`", 'error');
201 |
202 | return false;
203 | }
204 |
205 | if (preg_match('/class (.*)\n/', $contents, $matches) === false) {
206 | $this->log("Unable to find class in `$staticPath`", 'error');
207 |
208 | return false;
209 | }
210 |
211 | require $staticPath;
212 |
213 | $staticClass = '\\Composer\\Autoload\\' . $matches[1];
214 | call_user_func($staticClass::getInitializer($loader));
215 |
216 | return $loader;
217 | }
218 |
219 | /**
220 | * Finds or creates project namespace.
221 | *
222 | * @param string|null $qualifiedName Qualified name
223 | * @return self|null
224 | */
225 | protected function getNamespace(?string $qualifiedName): ProjectNamespace
226 | {
227 | if ($qualifiedName === null) {
228 | return $this->namespaces[''] ??= new ProjectNamespace('Global', null);
229 | }
230 |
231 | $namespace = null;
232 | foreach ($this->namespaces as $root) {
233 | if (str_starts_with($qualifiedName, $root->qualifiedName)) {
234 | $namespace = $root;
235 | break;
236 | }
237 | }
238 |
239 | if ($namespace === null) {
240 | throw new RuntimeException(sprintf('Namespace `%s` is part of the project namespaces.', $qualifiedName));
241 | }
242 |
243 | if ($namespace->qualifiedName === $qualifiedName) {
244 | return $namespace;
245 | }
246 |
247 | $names = explode('\\', substr($qualifiedName, strlen($namespace->qualifiedName) + 1));
248 | foreach ($names as $name) {
249 | if (isset($namespace->children[$name])) {
250 | $namespace = $namespace->children[$name];
251 | continue;
252 | }
253 |
254 | $child = new ProjectNamespace($name, $namespace->qualifiedName . '\\' . $name);
255 | $namespace->children[$name] = $child;
256 | ksort($namespace->children);
257 |
258 | $namespace = $child;
259 | }
260 |
261 | return $namespace;
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CAKEPHP_SOURCE_DIR=../cakephp
2 | CHRONOS_SOURCE_DIR=../chronos
3 | ELASTIC_SOURCE_DIR=../elastic-search
4 | QUEUE_SOURCE_DIR=../queue
5 | AUTHENTICATION_SOURCE_DIR=../authentication
6 | AUTHORIZATION_SOURCE_DIR=../authorization
7 | BUILD_DIR=./build/api
8 | DEPLOY_DIR=./website
9 | PHP=php
10 | COMPOSER=$(PWD)/composer.phar
11 |
12 | .PHONY: clean help
13 | .PHONY: build-cakephp-3
14 | .PHONY: build-cakephp-4
15 | .PHONY: build-cakephp-5
16 | .PHONY: build-chronos
17 | .PHONY: build-elastic
18 | .PHONY: build-queue
19 | .PHONY: build-authentication
20 | .PHONY: build-authorization
21 | .PHONY: build-active-and-missing
22 | .ALL: help
23 |
24 | # Versions that can be built.
25 | CAKEPHP3_VERSIONS = 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10
26 | CAKEPHP4_VERSIONS = 4.0 4.1 4.2 4.3 4.4 4.5 4.6
27 | CAKEPHP5_VERSIONS = 5.0 5.1 5.2
28 |
29 | CHRONOS_VERSIONS = 1.x 2.x 3.x
30 |
31 | ELASTIC_VERSIONS = 2.x 3.x 4.x
32 |
33 | QUEUE_VERSIONS = 1.x 2.x
34 |
35 | AUTHENTICATION_VERSIONS = 2.x 3.x
36 |
37 | AUTHORIZATION_VERSIONS = 2.x 3.x
38 |
39 | help:
40 | @echo "CakePHP API Documentation generator"
41 | @echo "-----------------------------------"
42 | @echo ""
43 | @echo "Tasks:"
44 | @echo ""
45 | @echo " clean - Clean the build output directory"
46 | @echo ""
47 | @echo " build-name-v - Build the version v documentation. The versions that can be"
48 | @echo " built are:"
49 | @echo " $(VERSIONS)"
50 | @echo ""
51 | @echo "Variables:"
52 | @echo ""
53 | @echo " CAKEPHP_SOURCE_DIR - Define where your cakephp clone is."
54 | @echo " CHRONOS_SOURCE_DIR - Define where your chronos clone is."
55 | @echo " ELASTIC_SOURCE_DIR - Define where your elastic-search clone is."
56 | @echo " QUEUE_SOURCE_DIR - Define where your queue clone is."
57 | @echo " AUTHENTICATION_SOURCE_DIR - Define where your authentication clone is."
58 | @echo " AUTHORIZATION_SOURCE_DIR - Define where your authentication clone is."
59 | @echo " QUEUE_SOURCE_DIR - Define where your queue clone is."
60 | @echo " BUILD_DIR - The directory where the output should go. Default: $(BUILD_DIR)"
61 | @echo " DEPLOY_DIR - The directory files shold be copied to in deploy Default: $(DEPLOY_DIR)"
62 | @echo ""
63 | @echo "NOTE: Source directories will have their checkout branch changed."
64 | @echo " Make sure all working directories are clean."
65 |
66 | clean:
67 | rm -rf $(DEPLOY_DIR)
68 | rm -rf $(BUILD_DIR)
69 |
70 |
71 | # Make the deployment directory
72 | $(DEPLOY_DIR):
73 | mkdir -p $(DEPLOY_DIR)
74 |
75 | deploy: $(DEPLOY_DIR)
76 | for release in $$(ls $(BUILD_DIR)); do \
77 | rm -rf $(DEPLOY_DIR)/$$release; \
78 | mkdir -p $(DEPLOY_DIR)/$$release; \
79 | mv $(BUILD_DIR)/$$release $(DEPLOY_DIR)/; \
80 | done
81 |
82 |
83 | composer.phar:
84 | curl -sS https://getcomposer.org/installer | php
85 |
86 | install: composer.phar
87 | $(PHP) $(COMPOSER) install
88 |
89 | define cakephp3-no-vendor
90 | build-cakephp-$(VERSION): install
91 | cd $(CAKEPHP_SOURCE_DIR) && git checkout -f $(TAG)
92 | cd $(CAKEPHP_SOURCE_DIR) && rm -rf ./vendor
93 | mkdir -p $(BUILD_DIR)/cakephp/$(VERSION)
94 | cp -r static/assets/* $(BUILD_DIR)/cakephp/$(VERSION)
95 |
96 | $(PHP) bin/apitool.php generate --config cakephp3 --version $(VERSION) --tag $(TAG) \
97 | --output-dir $(BUILD_DIR)/cakephp/$(VERSION) $(CAKEPHP_SOURCE_DIR)
98 | endef
99 |
100 | define cakephp3
101 | build-cakephp-$(VERSION): install
102 | cd $(CAKEPHP_SOURCE_DIR) && git checkout -f $(TAG)
103 | cd $(CAKEPHP_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs
104 | mkdir -p $(BUILD_DIR)/cakephp/$(VERSION)
105 | cp -r static/assets/* $(BUILD_DIR)/cakephp/$(VERSION)
106 |
107 | $(PHP) bin/apitool.php generate --config cakephp3 --version $(VERSION) --tag $(TAG) \
108 | --output-dir $(BUILD_DIR)/cakephp/$(VERSION) $(CAKEPHP_SOURCE_DIR)
109 | endef
110 |
111 | define cakephp4
112 | build-cakephp-$(VERSION): install
113 | cd $(CAKEPHP_SOURCE_DIR) && git checkout -f $(TAG)
114 | cd $(CAKEPHP_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs
115 | mkdir -p $(BUILD_DIR)/cakephp/$(VERSION)
116 | cp -r static/assets/* $(BUILD_DIR)/cakephp/$(VERSION)
117 |
118 | $(PHP) bin/apitool.php generate --config cakephp4 --version $(VERSION) --tag $(TAG) \
119 | --output-dir $(BUILD_DIR)/cakephp/$(VERSION) $(CAKEPHP_SOURCE_DIR)
120 | endef
121 |
122 | define cakephp5
123 | build-cakephp-$(VERSION): install
124 | cd $(CAKEPHP_SOURCE_DIR) && git checkout -f $(TAG)
125 | cd $(CAKEPHP_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs
126 | mkdir -p $(BUILD_DIR)/cakephp/$(VERSION)
127 | cp -r static/assets/* $(BUILD_DIR)/cakephp/$(VERSION)
128 |
129 | $(PHP) bin/apitool.php generate --config cakephp5 --version $(VERSION) --tag $(TAG) \
130 | --output-dir $(BUILD_DIR)/cakephp/$(VERSION) $(CAKEPHP_SOURCE_DIR)
131 | endef
132 |
133 | define chronos
134 | build-chronos-$(VERSION): install
135 | cd $(CHRONOS_SOURCE_DIR) && git checkout -f $(TAG)
136 | cd $(CHRONOS_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs
137 | mkdir -p $(BUILD_DIR)/chronos/$(VERSION)
138 | cp -r static/assets/* $(BUILD_DIR)/chronos/$(VERSION)
139 |
140 | $(PHP) bin/apitool.php generate --config chronos --version $(VERSION) --tag $(TAG) \
141 | --output-dir $(BUILD_DIR)/chronos/$(VERSION) $(CHRONOS_SOURCE_DIR)
142 | endef
143 |
144 | define elastic
145 | build-elastic-$(VERSION): install
146 | cd $(ELASTIC_SOURCE_DIR) && git checkout -f $(TAG)
147 | cd $(ELASTIC_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs
148 | mkdir -p $(BUILD_DIR)/elastic-search/$(VERSION)
149 | cp -r static/assets/* $(BUILD_DIR)/elastic-search/$(VERSION)
150 |
151 | $(PHP) bin/apitool.php generate --config elastic --version $(VERSION) --tag $(TAG) \
152 | --output-dir $(BUILD_DIR)/elastic-search/$(VERSION) $(ELASTIC_SOURCE_DIR)
153 | endef
154 |
155 | define queue
156 | build-queue-$(VERSION): install
157 | cd $(QUEUE_SOURCE_DIR) && git checkout -f $(TAG)
158 | cd $(QUEUE_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs
159 | mkdir -p $(BUILD_DIR)/queue/$(VERSION)
160 | cp -r static/assets/* $(BUILD_DIR)/queue/$(VERSION)
161 |
162 | $(PHP) bin/apitool.php generate --config queue --version $(VERSION) --tag $(TAG) \
163 | --output-dir $(BUILD_DIR)/queue/$(VERSION) $(QUEUE_SOURCE_DIR)
164 | endef
165 |
166 | define authentication
167 | build-authentication-$(VERSION): install
168 | cd $(AUTHENTICATION_SOURCE_DIR) && git checkout -f $(TAG)
169 | cd $(AUTHENTICATION_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs
170 | mkdir -p $(BUILD_DIR)/authentication/$(VERSION)
171 | cp -r static/assets/* $(BUILD_DIR)/authentication/$(VERSION)
172 |
173 | $(PHP) bin/apitool.php generate --config authentication --version $(VERSION) --tag $(TAG) \
174 | --output-dir $(BUILD_DIR)/authentication/$(VERSION) $(AUTHENTICATION_SOURCE_DIR)
175 | endef
176 |
177 | define authorization
178 | build-authorization-$(VERSION): install
179 | cd $(AUTHORIZATION_SOURCE_DIR) && git checkout -f $(TAG)
180 | cd $(AUTHORIZATION_SOURCE_DIR) && $(PHP) $(COMPOSER) update --no-plugins --ignore-platform-reqs
181 | mkdir -p $(BUILD_DIR)/authorization/$(VERSION)
182 | cp -r static/assets/* $(BUILD_DIR)/authorization/$(VERSION)
183 |
184 | $(PHP) bin/apitool.php generate --config authorization --version $(VERSION) --tag $(TAG) \
185 | --output-dir $(BUILD_DIR)/authorization/$(VERSION) $(AUTHORIZATION_SOURCE_DIR)
186 | endef
187 |
188 | # Build all the versions in a loop.
189 | build-cakephp3-all: $(foreach version, $(CAKEPHP3_VERSIONS), build-cakephp-$(version))
190 | build-cakephp4-all: $(foreach version, $(CAKEPHP4_VERSIONS), build-cakephp-$(version))
191 | build-cakephp5-all: $(foreach version, $(CAKEPHP5_VERSIONS), build-cakephp-$(version))
192 |
193 | build-chronos-all: $(foreach version, $(CHRONOS_VERSIONS), build-chronos-$(version))
194 | build-elastic-all: $(foreach version, $(ELASTIC_VERSIONS), build-elastic-$(version))
195 | build-queue-all: $(foreach version, $(QUEUE_VERSIONS), build-queue-$(version))
196 | build-authentication-all: $(foreach version, $(AUTHENTICATION_VERSIONS), build-authentication-$(version))
197 | build-authorization-all: $(foreach version, $(AUTHORIZATION_VERSIONS), build-authorization-$(version))
198 |
199 | # Generate build targets for cakephp
200 | TAG:=3.0.19
201 | VERSION:=3.0
202 | $(eval $(cakephp3-no-vendor))
203 |
204 | TAG:=3.1.14
205 | VERSION:=3.1
206 | $(eval $(cakephp3-no-vendor))
207 |
208 | TAG:=3.2.14
209 | VERSION:=3.2
210 | $(eval $(cakephp3))
211 |
212 | TAG:=3.3.16
213 | VERSION:=3.3
214 | $(eval $(cakephp3))
215 |
216 | TAG:=3.4.14
217 | VERSION:=3.4
218 | $(eval $(cakephp3))
219 |
220 | TAG:=3.5.18
221 | VERSION:=3.5
222 | $(eval $(cakephp3))
223 |
224 | TAG:=3.6.15
225 | VERSION:=3.6
226 | $(eval $(cakephp3))
227 |
228 | TAG:=3.7.9
229 | VERSION:=3.7
230 | $(eval $(cakephp3))
231 |
232 | TAG:=3.8.13
233 | VERSION:=3.8
234 | $(eval $(cakephp3))
235 |
236 | TAG:=3.9.10
237 | VERSION:=3.9
238 | $(eval $(cakephp3))
239 |
240 | TAG:=origin/3.x
241 | VERSION:=3.10
242 | $(eval $(cakephp3))
243 |
244 | TAG:=4.0.9
245 | VERSION:=4.0
246 | $(eval $(cakephp4))
247 |
248 | TAG:=4.1.7
249 | VERSION:=4.1
250 | $(eval $(cakephp4))
251 |
252 | TAG:=4.2.10
253 | VERSION:=4.2
254 | $(eval $(cakephp4))
255 |
256 | TAG:=4.3.10
257 | VERSION:=4.3
258 | $(eval $(cakephp4))
259 |
260 | TAG:=4.4.18
261 | VERSION:=4.4
262 | $(eval $(cakephp4))
263 |
264 | TAG:=4.5.10
265 | VERSION:=4.5
266 | $(eval $(cakephp4))
267 |
268 | TAG:=origin/4.x
269 | VERSION:=4.6
270 | $(eval $(cakephp4))
271 |
272 | TAG:=5.0.11
273 | VERSION:=5.0
274 | $(eval $(cakephp5))
275 |
276 | TAG:=5.1.6
277 | VERSION:=5.1
278 | $(eval $(cakephp5))
279 |
280 | TAG:=origin/5.x
281 | VERSION:=5.2
282 | $(eval $(cakephp5))
283 |
284 | # Generate build targets for chronos
285 | TAG:=origin/1.x
286 | VERSION:=1.x
287 | $(eval $(chronos))
288 |
289 | TAG:=origin/2.x
290 | VERSION:=2.x
291 | $(eval $(chronos))
292 |
293 | TAG:=origin/3.x
294 | VERSION:=3.x
295 | $(eval $(chronos))
296 |
297 | # Generate build targets for elastic-search
298 | TAG:=origin/2.x
299 | VERSION:=2.x
300 | $(eval $(elastic))
301 |
302 | TAG:=origin/3.x
303 | VERSION:=3.x
304 | $(eval $(elastic))
305 |
306 | TAG:=origin/4.x
307 | VERSION:=4.x
308 | $(eval $(elastic))
309 |
310 | # Generate build targets for queue
311 | TAG:=origin/1.x
312 | VERSION:=1.x
313 | $(eval $(queue))
314 |
315 | TAG:=origin/2.x
316 | VERSION:=2.x
317 | $(eval $(queue))
318 |
319 | # Generate build targets for authetication
320 | TAG:=origin/2.x
321 | VERSION:=2.x
322 | $(eval $(authentication))
323 |
324 | TAG:=origin/3.x
325 | VERSION:=3.x
326 | $(eval $(authentication))
327 |
328 | # Generate build targets for authorization
329 | TAG:=origin/2.x
330 | VERSION:=2.x
331 | $(eval $(authorization))
332 |
333 | TAG:=origin/3.x
334 | VERSION:=3.x
335 | $(eval $(authorization))
336 |
--------------------------------------------------------------------------------
/static/assets/js/main.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ApiGen 3.0dev - API documentation generator for PHP 5.3+
3 | *
4 | * Copyright (c) 2010-2011 David Grudl (http://davidgrudl.com)
5 | * Copyright (c) 2011-2012 Jaroslav Hanslík (https://github.com/kukulich)
6 | * Copyright (c) 2011-2012 Ondřej Nešpor (https://github.com/Andrewsville)
7 | *
8 | * For the full copyright and license information, please view
9 | * the file LICENSE.md that was distributed with this source code.
10 | */
11 |
12 | $(function() {
13 | var $win = $(window);
14 | var $body = $('body');
15 | var $document = $(document);
16 | var $left = $('#left');
17 | var $right = $('#right');
18 | var $rightInner = $('#rightInner');
19 | var $groups = $('#groups');
20 | var $content = $('#content');
21 |
22 | // Hide deep packages and namespaces
23 | $('ul span', $groups).click(function(event) {
24 | event.preventDefault();
25 | event.stopPropagation();
26 | $(this)
27 | .toggleClass('collapsed')
28 | .parent()
29 | .next('ul')
30 | .toggleClass('collapsed');
31 | }).click();
32 |
33 | $active = $('ul li.active', $groups);
34 | if ($active.length > 0) {
35 | // Open active
36 | $('> a > span', $active).click();
37 | } else {
38 | $main = $('> ul > li.main', $groups);
39 | if ($main.length > 0) {
40 | // Open first level of the main project
41 | $('> a > span', $main).click();
42 | } else {
43 | // Open first level of all
44 | $('> ul > li > a > span', $groups).click();
45 | }
46 | }
47 |
48 | /* Validate function */
49 | function validate(data, def) {
50 | return (data !== undefined) ? data : def;
51 | }
52 |
53 | // Window width without scrollbar
54 | $windowWidth = $win.width(),
55 |
56 | // Media Query fix (outerWidth -- scrollbar)
57 | // Media queries width include the scrollbar
58 | mqWidth = $win.outerWidth(true, true),
59 |
60 | // Detect Mobile Devices
61 | isMobileDevice = (( navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone|IEMobile|Opera Mini|Mobi/i) || (mqWidth < 767) ) ? true : false );
62 |
63 | // detect IE browsers
64 | var ie = (function(){
65 | var rv = 0,
66 | ua = window.navigator.userAgent,
67 | msie = ua.indexOf('MSIE '),
68 | trident = ua.indexOf('Trident/');
69 |
70 | if (msie > 0) {
71 | // IE 10 or older => return version number
72 | rv = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
73 | } else if (trident > 0) {
74 | // IE 11 (or newer) => return version number
75 | var rvNum = ua.indexOf('rv:');
76 | rv = parseInt(ua.substring(rvNum + 3, ua.indexOf('.', rvNum)), 10);
77 | }
78 |
79 | return ((rv > 0) ? rv : 0);
80 | }());
81 |
82 | // Responsive Menus
83 | $('#modal').on('show.bs.modal', function (event) {
84 | var modal = $(this);
85 | var button = $(event.relatedTarget);
86 | var id = button.attr('id');
87 | var contents, title;
88 | if (id == 'btn-menu') {
89 | title = 'Menu';
90 | contents = $('#cake-nav').html();
91 | }
92 | if (id == 'btn-nav') {
93 | title = 'Navigation';
94 | contents = $('#nav-cook').html();
95 | }
96 | if (id == 'btn-toc') {
97 | title = 'Class Navigation';
98 | contents = $('#class-nav').html();
99 | }
100 |
101 | modal.find('.modal-body').html(contents);
102 | modal.find('.modal-title-cookbook').text(title);
103 |
104 | // Bind click events for sub menus.
105 | modal.find('li > a').on('click', function() {
106 | var el = $(this).parent(),
107 | subMenu = el.find('.submenu, .megamenu');
108 | // No menu, bail
109 | if (subMenu.length === 0) {
110 | return;
111 | }
112 | subMenu.toggle();
113 | return false;
114 | });
115 | });
116 |
117 | /* ********************* Megamenu ********************* */
118 | var menu = $(".menu"),
119 | Megamenu = {
120 | desktopMenu: function() {
121 |
122 | menu.children("li").show(0);
123 |
124 | // Mobile touch for tablets > 768px
125 | if (isMobileDevice) {
126 | menu.on("click touchstart","a", function(e){
127 | if ($(this).attr('href') === '#') {
128 | e.preventDefault();
129 | e.stopPropagation();
130 | }
131 |
132 | var $this = $(this),
133 | $sub = $this.siblings(".submenu, .megamenu");
134 |
135 | $this.parent("li").siblings("li").find(".submenu, .megamenu").stop(true, true).fadeOut(300);
136 |
137 | if ($sub.css("display") === "none") {
138 | $sub.stop(true, true).fadeIn(300);
139 | } else {
140 | $sub.stop(true, true).fadeOut(300);
141 | $this.siblings(".submenu").find(".submenu").stop(true, true).fadeOut(300);
142 | }
143 | });
144 |
145 | $(document).on("click.menu touchstart.menu", function(e){
146 | if ($(e.target).closest(menu).length === 0) {
147 | menu.find(".submenu, .megamenu").fadeOut(300);
148 | }
149 | });
150 |
151 | // Desktop hover effect
152 | } else {
153 | menu.find('li').on({
154 | "mouseenter": function() {
155 | $(this).children(".submenu, .megamenu").stop(true, true).fadeIn(300);
156 | },
157 | "mouseleave": function() {
158 | $(this).children(".submenu, .megamenu").stop(true, true).fadeOut(300);
159 | }
160 | });
161 | }
162 | },
163 |
164 | mobileMenu: function() {
165 | var children = menu.children("li"),
166 | toggle = menu.children("li.toggle-menu");
167 |
168 | toggle.show(0).on("click", function(){
169 |
170 | if ($children.is(":hidden")){
171 | children.slideDown(300);
172 | } else {
173 | toggle.show(0);
174 | }
175 | });
176 |
177 | // Click (touch) effect
178 | menu.find("li").not(".toggle-menu").each(function(){
179 | var el = $(this);
180 | if (el.children(".submenu, .megamenu").length) {
181 | el.children("a").on("click", function(e){
182 | if ($(this).attr('href') === '#') {
183 | e.preventDefault();
184 | e.stopPropagation();
185 | }
186 |
187 | var $sub = $(this).siblings(".submenu, .megamenu");
188 |
189 | if ($sub.hasClass("open")) {
190 | $sub.slideUp(300).removeClass("open");
191 | } else {
192 | $sub.slideDown(300).addClass("open");
193 | }
194 | });
195 | }
196 | });
197 | },
198 | unbindEvents: function() {
199 | menu.find("li, a").off();
200 | $(document).off("click.menu touchstart.menu");
201 | menu.find(".submenu, .megamenu").hide(0);
202 | }
203 | }; // END Megamenu object
204 |
205 | if ($windowWidth < 768) {
206 | Megamenu.mobileMenu();
207 | } else {
208 | Megamenu.desktopMenu();
209 | }
210 |
211 | /* **************** Hide header on scroll down *************** */
212 | (function() {
213 | // Hide Header on on scroll down
214 | var didScroll;
215 | var lastScrollTop = 0;
216 | var delta = 5;
217 | var navbarHeight = $('header').outerHeight();
218 |
219 | $win.scroll(function(event){
220 | didScroll = true;
221 | });
222 |
223 | // Debounce the header toggling to ever 250ms
224 | var toggleHeader = function() {
225 | if (didScroll) {
226 | hasScrolled();
227 | didScroll = false;
228 | }
229 | setTimeout(toggleHeader, 250);
230 | };
231 | setTimeout(toggleHeader, 250);
232 |
233 | function hasScrolled() {
234 | var st = $win.scrollTop();
235 |
236 | // Make sure they scroll more than delta
237 | if (Math.abs(lastScrollTop - st) <= delta) {
238 | return;
239 | }
240 |
241 | // If they scrolled down and are past the navbar, add class .nav-up.
242 | // This is necessary so you never see what is "behind" the navbar.
243 | if (st > lastScrollTop && st > navbarHeight){
244 | // Scroll Down
245 | $('header').removeClass('nav-down').addClass('nav-up');
246 | // Scroll Up
247 | } else if (st + $(window).height() < $(document).height()) {
248 | $('header').removeClass('nav-up').addClass('nav-down');
249 | }
250 | lastScrollTop = st;
251 | }
252 |
253 | // If we're directly linking to a section, hide the nav.
254 | if (window.location.hash.length) {
255 | $('header').addClass('nav-up');
256 | }
257 | }());
258 |
259 | // Footer Tooltips
260 | $("[data-toggle='tooltip']").tooltip();
261 |
262 | // Search autocompletion
263 | var autocompleteFound = false;
264 | var autocompleteFiles = {'c': 'class', 'i': 'interface', 't': 'trait', 'co': 'constant', 'f': 'function'};
265 |
266 | var $search = $('.search input[name=q]');
267 | $search
268 | .autocomplete(searchEntries, {
269 | matchContains: true,
270 | scrollHeight: 200,
271 | max: 20,
272 | formatItem: function(data) {
273 | return '' + data[1].replace(/^(.+\\)(.+)$/, '$1 $2') + ' ';
274 | },
275 | formatMatch: function(data) {
276 | return data[1];
277 | },
278 | formatResult: function(data) {
279 | return data[1];
280 | },
281 | show: function(list) {
282 | var listWidth = list.width();
283 | var items = $('li span', list);
284 | if (isMobileDevice) {
285 | // Make the results full width
286 | list.width('100%').css('left', 0);
287 | } else {
288 | var listLeft = parseInt(list.css('left'), 10);
289 | var maxWidth = Math.max.apply(null, items.map(function() {
290 | return $(this).width();
291 | }));
292 | // Make the results wider, and shift left to accomodate new width.
293 | list
294 | .width(Math.max(maxWidth, $search.innerWidth()))
295 | .css('left', listLeft - Math.max(0, maxWidth - listWidth));
296 | }
297 | }
298 | }).result(function(event, data) {
299 | autocompleteFound = true;
300 | var location = window.location.href.split('/');
301 | location.pop();
302 | var parts = data[1].split(/::|$/);
303 | var file = autocompleteFiles[data[0]] + '-' + parts[0].replace(/\\/g, '.') + '.html';
304 | if (parts[1]) {
305 | file += '#' + parts[1];
306 | }
307 | location.push(file);
308 | window.location = location.join('/');
309 |
310 | // Workaround for Opera bug
311 | $(this).closest('form').attr('action', location.join('/'));
312 | }).closest('form')
313 | .submit(function() {
314 | var query = $search.val();
315 | if ('' === query) {
316 | return false;
317 | }
318 |
319 | var label = $('#search input[name=more]').val();
320 | if (!autocompleteFound && label && -1 === query.indexOf('more:')) {
321 | $search.val(query + ' more:' + label);
322 | }
323 |
324 | return !autocompleteFound && '' !== $('#search input[name=cx]').val();
325 | });
326 |
327 | // Open details
328 | if (false/*elementDetailsCollapsed*/) {
329 | $('tr', $content).filter(':has(.detailed)')
330 | .click(function() {
331 | var $this = $(this);
332 | $('.short', $this).hide();
333 | $('.detailed', $this).show();
334 | });
335 | }
336 |
337 | // Select selected lines
338 | var matches = window.location.hash.substr(1).match(/^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$/);
339 | if (null !== matches) {
340 | var lists = matches[0].split(',');
341 | for (var i = 0; i < lists.length; i++) {
342 | var lines = lists[i].split('-');
343 | lines[1] = lines[1] || lines[0];
344 | for (var j = lines[0]; j <= lines[1]; j++) {
345 | $('#' + j).addClass('selected');
346 | }
347 | }
348 |
349 | var scrollToLineNumber = function() {
350 | console.log('plop');
351 | var offset = 0;
352 | var $header = $('header');
353 | if ($header.length > 0) {
354 | offset = $header.height() + parseInt($header.css('top'));
355 | }
356 |
357 | var $firstLine = $('#' + parseInt(matches[0]));
358 | if ($firstLine.length > 0) {
359 | $(document).scrollTop($firstLine.offset().top - offset);
360 | }
361 | };
362 | scrollToLineNumber();
363 | setTimeout(scrollToLineNumber, 400);
364 | }
365 |
366 | // Save selected lines
367 | var lastLine;
368 | $('a.l').click(function(event) {
369 | event.preventDefault();
370 |
371 | var $selectedLine = $(this).parent();
372 | var selectedLine = parseInt($selectedLine.attr('id'));
373 |
374 | if (event.shiftKey) {
375 | if (lastLine) {
376 | for (var i = Math.min(selectedLine, lastLine); i <= Math.max(selectedLine, lastLine); i++) {
377 | $('#' + i).addClass('selected');
378 | }
379 | } else {
380 | $selectedLine.addClass('selected');
381 | }
382 | } else if (event.ctrlKey) {
383 | $selectedLine.toggleClass('selected');
384 | } else {
385 | var $selected = $('.l.selected')
386 | .not($selectedLine)
387 | .removeClass('selected');
388 | if ($selected.length > 0) {
389 | $selectedLine.addClass('selected');
390 | } else {
391 | $selectedLine.toggleClass('selected');
392 | }
393 | }
394 |
395 | lastLine = $selectedLine.hasClass('selected') ? selectedLine : null;
396 |
397 | // Update hash
398 | var lines = $('.l.selected')
399 | .map(function() {
400 | return parseInt($(this).attr('id'));
401 | })
402 | .get()
403 | .sort(function(a, b) {
404 | return a - b;
405 | });
406 |
407 | var hash = [];
408 | var list = [];
409 | for (var j = 0; j < lines.length; j++) {
410 | if (0 === j && j + 1 === lines.length) {
411 | hash.push(lines[j]);
412 | } else if (0 === j) {
413 | list[0] = lines[j];
414 | } else if (lines[j - 1] + 1 !== lines[j] && j + 1 === lines.length) {
415 | hash.push(list.join('-'));
416 | hash.push(lines[j]);
417 | } else if (lines[j - 1] + 1 !== lines[j]) {
418 | hash.push(list.join('-'));
419 | list = [lines[j]];
420 | } else if (j + 1 === lines.length) {
421 | list[1] = lines[j];
422 | hash.push(list.join('-'));
423 | } else {
424 | list[1] = lines[j];
425 | }
426 | }
427 |
428 | window.location.hash = hash.join(',');
429 | });
430 | });
431 |
--------------------------------------------------------------------------------
/src/Factory.php:
--------------------------------------------------------------------------------
1 | getDocComment()?->getText());
63 |
64 | $const = new ReflectedDefine($node->name->name, $doc, $context, $source);
65 | $const->value = PrintUtil::expr($node->value);
66 |
67 | if (isset($doc->tags['var'])) {
68 | $const->type = $doc->tags['var']->type;
69 | }
70 |
71 | return $const;
72 | }
73 |
74 | /**
75 | * @param \PhpParser\Node\Stmt\Function_ $node Function node
76 | * @param \Cake\ApiDocs\Reflection\Context $context Reflection context
77 | * @param \Cake\ApiDocs\Reflection\Source $source Reflection source
78 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine
79 | */
80 | public function createFunction(Function_ $node, Context $context, Source $source): ReflectedFunction
81 | {
82 | $doc = new DocBlock($node->getDocComment()?->getText());
83 |
84 | $func = new ReflectedFunction($node->name->name, $doc, $context, $source);
85 | $this->reflectFuncLike($func, $node, $doc);
86 |
87 | return $func;
88 | }
89 |
90 | /**
91 | * @param \PhpParser\Node\Stmt\Interface_ $node Interface node
92 | * @param \Cake\ApiDocs\Reflection\Context $context Reflection context
93 | * @param \Cake\ApiDocs\Reflection\Source $source Reflection source
94 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine
95 | */
96 | public function createInterface(Interface_ $node, Context $context, Source $source): ReflectedInterface
97 | {
98 | $doc = new DocBlock($node->getDocComment()?->getText());
99 |
100 | $interface = new ReflectedInterface($node->name->name, $doc, $context, $source);
101 | $this->reflectClassLike($interface, $node);
102 |
103 | $interface->extends = array_map(fn($name) => (string)$name, $node->extends);
104 |
105 | return $interface;
106 | }
107 |
108 | /**
109 | * @param \PhpParser\Node\Stmt\Class_ $node Class node
110 | * @param \Cake\ApiDocs\Reflection\Context $context Reflection context
111 | * @param \Cake\ApiDocs\Reflection\Source $source Reflection source
112 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine
113 | */
114 | public function createClass(Class_ $node, Context $context, Source $source): ReflectedClass
115 | {
116 | $doc = new DocBlock($node->getDocComment()?->getText());
117 |
118 | $class = new ReflectedClass($node->name->name, $doc, $context, $source);
119 | $this->reflectClassLike($class, $node);
120 |
121 | $class->abstract = $node->isAbstract();
122 | $class->final = $node->isFinal();
123 | $class->extends = (string)$node->extends ?: null;
124 | $class->implements = array_map(fn($name) => (string)$name, $node->implements);
125 |
126 | return $class;
127 | }
128 |
129 | /**
130 | * @param \PhpParser\Node\Stmt\Trait_ $node Trait node
131 | * @param \Cake\ApiDocs\Reflection\Context $context Reflection context
132 | * @param \Cake\ApiDocs\Reflection\Source $source Reflection source
133 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine
134 | */
135 | public function createTrait(Trait_ $node, Context $context, Source $source): ReflectedTrait
136 | {
137 | $doc = new DocBlock($node->getDocComment()?->getText());
138 | $trait = new ReflectedTrait($node->name->name, $doc, $context, $source);
139 | $this->reflectClassLike($trait, $node);
140 |
141 | return $trait;
142 | }
143 |
144 | /**
145 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike
146 | * @param \PhpParser\Node\Stmt\ClassConst $classNode Constant node
147 | * @param \PhpParser\Node\Const_ $constNode Constant node
148 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine
149 | */
150 | protected function createConstant(
151 | ReflectedClassLike $classLike,
152 | ClassConst $classNode,
153 | Const_ $constNode
154 | ): ReflectedConstant {
155 | $doc = new DocBlock($classNode->getDocComment()?->getText());
156 | $source = new Source(
157 | $classLike->source->path,
158 | $classLike->source->inProject,
159 | $classNode->getStartLine(),
160 | $classNode->getEndLine()
161 | );
162 |
163 | $const = new ReflectedConstant(
164 | $constNode->name->name,
165 | $doc,
166 | $classLike->context,
167 | $source
168 | );
169 | $const->owner = $classLike;
170 |
171 | $const->value = PrintUtil::expr($constNode->value);
172 | if (isset($doc->tags['var'])) {
173 | $const->type = $doc->tags['var']->type;
174 | }
175 |
176 | $const->visibility = $classNode->isPublic() ? 'public' : ($classNode->isProtected() ? 'protected' : 'private');
177 |
178 | return $const;
179 | }
180 |
181 | /**
182 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike
183 | * @param \PhpParser\Node\Stmt\Property $classNode Property node
184 | * @param \PhpParser\Node\Stmt\PropertyProperty $propNode Property node
185 | * @return \Cake\ApiDocs\Reflection\ReflectedDefine
186 | */
187 | protected function createProperty(
188 | ReflectedClassLike $classLike,
189 | Property $classNode,
190 | PropertyProperty $propNode
191 | ): ReflectedProperty {
192 | $doc = new DocBlock($classNode->getDocComment()?->getText());
193 | $source = new Source(
194 | $classLike->source->path,
195 | $classLike->source->inProject,
196 | $classNode->getStartLine(),
197 | $classNode->getEndLine()
198 | );
199 |
200 | $prop = new ReflectedProperty($propNode->name->name, $doc, $classLike->context, $source);
201 | $prop->owner = $classLike;
202 |
203 | $prop->nativeType = $classNode->type ? DocUtil::parseType(PrintUtil::node($classNode->type)) : null;
204 | $prop->type = $doc->tags['var']?->type ?? $prop->nativeType;
205 | $prop->default = $propNode->default ? PrintUtil::expr($propNode->default) : null;
206 |
207 | $prop->visibility = $classNode->isPublic() ? 'public' : ($classNode->isProtected() ? 'protected' : 'private');
208 | $prop->static = $classNode->isStatic();
209 |
210 | return $prop;
211 | }
212 |
213 | /**
214 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike
215 | * @param string $tagName Property tag name
216 | * @param \PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode $tagValue Property tag value
217 | * @return \Cake\ApiDocs\Reflection\ReflectedProperty
218 | */
219 | protected function createdAnnotatedProperty(
220 | ReflectedClassLike $classLike,
221 | string $tagName,
222 | PropertyTagValueNode $tagValue
223 | ): ReflectedProperty {
224 | $doc = new DocBlock(null);
225 | $doc->summary = $tagValue->description;
226 | $source = clone $classLike->source;
227 |
228 | $prop = new ReflectedProperty(substr($tagValue->propertyName, 1), $doc, $classLike->context, $source);
229 | $prop->owner = $classLike;
230 | $prop->annotation = $tagName;
231 |
232 | $prop->nativeType = $tagValue->type;
233 | $prop->type = $tagValue->type;
234 |
235 | return $prop;
236 | }
237 |
238 | /**
239 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike
240 | * @param \PhpParser\Node\Stmt\ClassMethod $node Method node
241 | * @return \Cake\ApiDocs\Reflection\ReflectedMethod
242 | */
243 | protected function createMethod(ReflectedClassLike $classLike, ClassMethod $node): ReflectedMethod
244 | {
245 | $doc = new DocBlock($node->getDocComment()?->getText());
246 | $source = new Source(
247 | $classLike->source->path,
248 | $classLike->source->inProject,
249 | $node->getStartLine(),
250 | $node->getEndLine()
251 | );
252 |
253 | $func = new ReflectedMethod(
254 | $node->name->name,
255 | $doc,
256 | $classLike->context,
257 | $source
258 | );
259 | $func->owner = $classLike;
260 |
261 | $this->reflectFuncLike($func, $node, $doc);
262 | $func->visibility = $node->isPublic() ? 'public' : ($node->isProtected() ? 'protected' : 'private');
263 | $func->abstract = $node->isAbstract();
264 | $func->static = $node->isStatic();
265 |
266 | return $func;
267 | }
268 |
269 | /**
270 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike
271 | * @param string $tagName Method tag name
272 | * @param \PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode $tagValue Method tag value
273 | * @return \Cake\ApiDocs\Reflection\ReflectedMethod
274 | */
275 | protected function createdAnnotatedMethod(
276 | ReflectedClassLike $classLike,
277 | string $tagName,
278 | MethodTagValueNode $tagValue
279 | ): ReflectedMethod {
280 | $doc = new DocBlock(null);
281 | $doc->summary = $tagValue->description;
282 | $source = clone $classLike->source;
283 |
284 | $method = new ReflectedMethod($tagValue->methodName, $doc, $classLike->context, $source);
285 | $method->owner = $classLike;
286 | $method->annotation = $tagName;
287 |
288 | foreach ($tagValue->parameters as $paramNode) {
289 | $param = new ReflectedParam(substr($paramNode->parameterName, 1));
290 | $param->type = $paramNode->type;
291 | $param->byRef = $paramNode->isReference;
292 | $param->variadic = $paramNode->isVariadic;
293 | $param->default = $paramNode->defaultValue?->__toString();
294 | $method->params[$param->name] = $param;
295 | }
296 | $method->returnType = $tagValue->returnType;
297 |
298 | return $method;
299 | }
300 |
301 | /**
302 | * @param \Cake\ApiDocs\Reflection\ReflectedClassLike $classLike Reflected classLike
303 | * @param \PhpParser\Node\Stmt\ClassLike $node ClassLike node
304 | * @return void
305 | */
306 | protected function reflectClassLike(ReflectedClassLike $classLike, ClassLike $node): void
307 | {
308 | foreach ($node->getConstants() as $classConstNode) {
309 | foreach ($classConstNode->consts as $constNode) {
310 | if (!$classConstNode->isPrivate()) {
311 | $constant = $this->createConstant($classLike, $classConstNode, $constNode);
312 | $classLike->constants[$constant->name] = $constant;
313 | }
314 | }
315 | }
316 | ksort($classLike->constants);
317 |
318 | foreach ($node->getProperties() as $classPropNode) {
319 | foreach ($classPropNode->props as $propNode) {
320 | if (!$classPropNode->isPrivate()) {
321 | $property = $this->createProperty($classLike, $classPropNode, $propNode);
322 | $classLike->properties[$property->name] = $property;
323 | }
324 | }
325 | }
326 | foreach (['@property', '@property-read', '@property-write'] as $tagName) {
327 | foreach ($classLike->doc->tags[$tagName] ?? [] as $tagValue) {
328 | $property = $this->createdAnnotatedProperty($classLike, $tagName, $tagValue);
329 | $classLike->properties[$property->name] = $property;
330 | }
331 | }
332 | ksort($classLike->properties);
333 |
334 | foreach ($node->getmethods() as $methodNode) {
335 | if (!$methodNode->isPrivate()) {
336 | $method = $this->createMethod($classLike, $methodNode);
337 | $classLike->methods[$method->name] = $method;
338 | }
339 | }
340 | foreach ($classLike->doc->tags['@method'] ?? [] as $tagValue) {
341 | $method = $this->createdAnnotatedMethod($classLike, '@method', $tagValue);
342 | $classLike->methods[$method->name] = $method;
343 | }
344 | ksort($classLike->methods);
345 |
346 | $traits = $node->getTraitUses();
347 | foreach ($traits as $trait) {
348 | foreach ($trait->traits as $use) {
349 | $classLike->uses[] = (string)$use;
350 | }
351 | }
352 | }
353 |
354 | /**
355 | * @param \Cake\ApiDocs\Reflection\ReflectedFunction $func Reflected function
356 | * @param \PhpParser\Node\FunctionLike $node Function node
357 | * @param \Cake\ApiDocs\Reflection\DocBlock $doc Reflected docblock
358 | * @return void
359 | */
360 | protected function reflectFuncLike(ReflectedFunction $func, FunctionLike $node, DocBlock $doc): void
361 | {
362 | $params = [];
363 | foreach ($node->getParams() as $paramNode) {
364 | $param = $this->createParam($func, $paramNode);
365 | $func->params[$param->name] = $param;
366 | }
367 |
368 | if ($node->getReturnType() !== null) {
369 | $func->nativeReturnType = DocUtil::parseType(PrintUtil::node($node->getReturnType()));
370 | }
371 | $func->returnType = $doc->tags['return']?->type ?? $func->nativeReturnType;
372 | $func->returnDescription = $doc->tags['return']?->description ?? '';
373 | }
374 |
375 | /**
376 | * @param \Cake\ApiDocs\Reflection\ReflectedFunction $func Reflected function
377 | * @param \PhpParser\Node\Param $node Param node
378 | * @return \Cake\ApiDocs\Reflection\ReflectedParam
379 | */
380 | protected function createParam(ReflectedFunction $func, Param $node): ReflectedParam
381 | {
382 | $param = new ReflectedParam($node->var->name);
383 |
384 | $tag = $this->getParamTag($param->name, $func->doc);
385 | $param->nativeType = $node->type ? DocUtil::parseType(PrintUtil::node($node->type)) : null;
386 | if ($tag) {
387 | $param->type = $tag->type;
388 | $param->description = $tag->description;
389 | } else {
390 | $param->type = $param->nativeType;
391 | }
392 |
393 | $param->variadic = $node->variadic;
394 | $param->byRef = $node->byRef;
395 | $param->default = $node->default ? PrintUtil::expr($node->default) : null;
396 |
397 | return $param;
398 | }
399 |
400 | /**
401 | * @param string $variable Variable name
402 | * @param \Cake\ApiDocs\Reflection\DocBlock $doc Reflected docblock
403 | * @return \PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|null
404 | */
405 | protected function getParamTag(string $variable, DocBlock $doc): ?ParamTagValueNode
406 | {
407 | $variable = '$' . $variable;
408 | foreach ($doc->tags['param'] ?? [] as $tag) {
409 | if ($tag instanceof InvalidTagValueNode) {
410 | continue;
411 | }
412 | if ($tag->parameterName === $variable) {
413 | return $tag;
414 | }
415 | }
416 |
417 | return null;
418 | }
419 | }
420 |
--------------------------------------------------------------------------------